О примере "Статистика посещений" |
В VFP 7.0 появилась поддержка XML ... и немного прочитав про MS XML мне захотелось это к чему-нибудь применить. Кроме того, я не имею полномочий на хоть какой-нибудь вид программирования на моём Web сервере, в то время как увидел, что XML в частности позволяет пользоваться данными сервера и преобразовывать их средствами клиента. Так вот, эти два обстоятельства воодушевили меня в качестве примера нарисовать "смотрелку" статистики посещения моего сайта для посетителей.
В данной заметке я пытаюсь кратко изложить: что из этого получилось и как это в конце концов работает, в надежде, что для совершенно незнакомого с этим предметом посетителя эти мои слова окажутся полезными.
Прежде всего нужно сказать, что в примере используется программное обеспечение, которое возможно у Вас не установлено. В нижеследующей таблицы сделаны пояснения:
Что |
Почему |
Как установить |
Microsoft XML 3.0 | Я не имею возможности программировать на стороне Web Server, в то время как для примера необходима работа с таблицами данных, вот почему я использовал XML на стороне Internet client для этих целей | Может быть установлено с версией Microsoft Internet Explorer 5.0 и выше. Последнюю версию можно загрузить здесь См. также Extensible Markup Language (XML) |
Microsoft Chart ActiveX Control (ver 6.00.8804) - Mschrt20.ocx | С помощью этого control я решил отображать информацию в виде столбиковых диаграмм. Никаких особых преимуществ данный control, на мой взгляд не имеет, кроме возможно отсутствия каких-либо особых проблем для его установки. | Если у Вас установлена MS Visual Studio 6.0, то скорее всего у Вас нет проблем, но в любом случае, зипованный Mschrt20.ocx (Microsoft Chart Control 6.0 (SP4) (OLEDB)) можно загрузить с моего сайта mschrt20.zip (433,0KB). После распаковки необходимо положить в системный каталог и зарегистрировать, используя Regsvr32.exe. После успешной регистрации следует также выполнить файл Mschrt20.reg, положенный в архив. |
Таб. 1 Используемое программное обеспечение.
Исходные данные берутся с Web Server (обычно раз в месяц) и помещаются в VFP-таблицу со следующей структурой:
MyStat (date d, visitos i, pages i)
Поскольку посетителю необходимо предоставить выбор месяца в двух языковых вариантах, то также заполнена VFP-таблица списка месяцев:
Month_r(Month_e) (month_id n(2,0), month_nm c(8))
Выше т.с. исходные данные. Теперь, поскольку желательно иметь итоговые результаты, как за годы, так и по месяцам в каждом году, то составлены следующие запроса, выполняющие небольшие преобразования и соответствующие суммирования:
lvStat: SELECT YEAR(Mystat.date) AS year, MONTH(Mystat.date) AS month,; DAY(Mystat.date) AS day, Mystat.visitors, Mystat.loads AS pages; FROM mystat!mystat; ORDER BY 1, 2, 3 lvStaty: SELECT YEAR(Mystat.date) AS year, SUM(Mystat.visitors) AS visitors,; SUM(Mystat.pages) AS pages, COUNT(*) AS days; FROM mystat!mystat; GROUP BY 1; ORDER BY 1 lvStatm: SELECT YEAR(Mystat.date) AS year, MONTH(Mystat.date) AS month,; SUM(Mystat.visitors) AS visitors, SUM(Mystat.pages) AS pages,; COUNT(*) AS days; FROM mystat!mystat; GROUP BY 1, 2; ORDER BY 1, 2
SQL-запросы формирования используемых таблиц.
В итоге, используя VFP 7.0 функцию CursorToXML(), мы получаем и помещаем на наш Web сервер следующие файлы:
Названия |
Источник |
Назначение |
month_r.xml (month_e.xml), month.xsd | Month_r(Month_e) | Список названий месяцев |
statd.xml, statd.xsd | lvStat | Список количества посетителей и страниц по дням |
statm.xml, statm.xsd | lvStatm | Список количества посетителей и страниц по месяцам |
staty.xml, staty.xsd | lvStaty | Список количества посетителей и страниц по годам |
Таб. 2 Список файлов, помещаемых на Web сервер
Так например, для получения month_r.xml при открытом файле month_r.dbf достаточно выполнить в окне команд: ?CursorToXML("Month_r", "Month_r.xml", 1, 8+16+512, 16, "Month.xsd")
Небольшая особенность такова, что как я понял, метод ADODB.Recordset.Save(<MyFile>, adPersistXML) сохраняет свои данные в формате т.с. attribute-centric XML и по этому, для statd.xml, statm.xml и state.xml я не стал отступать от этого правила (т.е. использовал 3 – RAW в качестве третьего параметра к CursorToXML()) в то время как для month_r.xml (month_e.xml) - применил 1 – ELEMENTS, чтобы внести хоть какое-то разнообразие :-)
Понятно, что данные всех трех файлов: statd.xml, statm.xml и staty.xml должны быть согласованы, т.е. изменения в statd.xml должны повлечь соответствующие изменения в statm.xml и staty.xml. Можно было бы объединить данные этих трех файлов в один, устроив в нём иерархию: год\месяц\день и это, как понимаю, хорошо согласуется с философией XML/XSL, однако привело бы к необходимости написания утилиты-генератора такого объединённого файла. Я не стал этого делать, и оставил файлы прямо в таком виде, в каком они получаются после применения функции CursorToXML().
Ниже показаны отрывки содержания полученных файлов:
<?xml version = "1.0"
encoding="Windows-1251" standalone="yes"?>
<!-- File Name: month_r.xml -->
<VFPData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="month.xsd">
<month>
<id_month>1</id_month>
<nm_month>Январь</nm_month>
</month>
...
<month>
<id_month>12</id_month>
<nm_month>Декабрь</nm_month>
</month>
</VFPData>
<?xml version = "1.0"
standalone="yes"?>
<!-- File Name: statd.xml -->
<VFPData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="statd.xsd">
<row year="2000" month="7"
day="15" visitors="25" pages="0"/>
...
<row year="2001" month="11"
day="30" visitors="76" pages="268"/>
</VFPData>
<?xml version = "1.0"
standalone="yes"?>
<!-- File Name: statm.xml -->
<VFPData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="statm.xsd">
<row year="2000" month="7"
visitors="428" pages="0" days="17"/>
...
<row year="2001" month="11"
visitors="1824" pages="7055" days="30"/>
</VFPData>
<?xml version = "1.0"
standalone="yes"?>
<!-- File Name: staty.xml -->
<VFPData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="staty.xsd">
<row year="2000" visitors="4097"
pages="0" days="167"/>
<row year="2001" visitors="12666"
pages="69379" days="332"/>
</VFPData>
Фрагменты файлов month_r.xml, statd.xml, statm.xml, staty.xml, помещаемых на Web сервер.
Файлы с расширением xsd описывают/контролируют структуру соответствующих файлов и здесь не рассматриваются. Рассмотрим теперь вопрос формирования xsl-файлов.
Обратимся теперь к вопросу преобразования данных из XML-файлов приведённых выше, к формату, требуемому при непосредственном использовании в Web Browser на клиенте. Так, из данных файла month_r.xml (month_e.xml) необходимо строить наполнение ComboBox (cobMonth), чтобы упростить подобную задачу я написал достаточно простой month.xsl-файл, обеспечивающий подобное преобразование:
<?xml version="1.0"?>
<!-- File Name: month.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<xsl:element
name="SELECT">
<xsl:attribute
name="ID">cobMonth</xsl:attribute>
<xsl:attribute
name="NAME">Month</xsl:attribute>
<xsl:for-each select="VFPData/month">
<xsl:element name="OPTION">
<xsl:attribute name="VALUE"><xsl:value-of
select="id_month"/></xsl:attribute>
<xsl:value-of select="nm_month"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Файл month.xsl.
Т.е. в качестве корневого элемента результата вставляется <SELECT ID="cobMonth" NAME="Month"> и далее для каждого month из VFPData (см. фрагмент файла month_r.xml выше) формируется соответствующий элемент <OPTION>, причём в него добавляется атрибут VALUE со значением элемента id_month исходного файла ... Таким образом, полученный результат как раз и будет html-код для ComboBox, содержащего список месяцев в году.
Далее, если предусмотреть возможность выбора посетителем конкретного года и месяца для построения диаграммы в выбранном месяце для указанного года, то необходимо уметь выделять соответствующее подмножество данных из statd.xml-файла. Не имея сколько-нибудь практических примеров использования языка XSL в этих целая, не могу сказать, что это было для меня легко :-) Однако, мной был всё же написан statd.xsl-файл, осуществляющий подобную двухпараметрическую фильтрацию, используя xsl:for-each совместно с xsl:if :
<?xml version="1.0"?>
<!-- File Name: statd.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<xsl:element
name="VFPData">
<xsl:for-each
select="VFPData/row[@year='2001']">
<xsl:if
test="@month[.='10']">
<xsl:element name="row">
<xsl:attribute name="day"><xsl:value-of
select="@day"/></xsl:attribute>
<xsl:attribute name="visitors"><xsl:value-of
select="@visitors"/></xsl:attribute>
<xsl:attribute name="pages"><xsl:value-of
select="@pages"/></xsl:attribute>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Пример statd.xsl файла.
Выше показан пример для выбора данных из statd.xml-файла в 10-ом месяце для 2001-го года. Обратите внимание, что получаемый результат имеет почти исходную структуру, за исключением отсутствия в нём атрибутов: year и month (фрагмент файла statd.xml см. выше). Другими словами преобразованные данные будут
Наконец, если предусмотреть возможность вывода диаграмм для указанного года по месяцам, то возникнет необходимость выделения подмножества данных из statm.xml-файла для указанного года... и эту достаточно тривиальную задачу выполняет statm.xsl-файл показанный ниже:
<?xml version="1.0"?>
<!-- File Name: statm.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<xsl:element
name="VFPData">
<xsl:for-each select="VFPData/row[@year='2001']">
<xsl:element name="row">
<xsl:attribute name="month"><xsl:value-of
select="@month"/></xsl:attribute>
<xsl:attribute name="visitors"><xsl:value-of
select="@visitors"/></xsl:attribute>
<xsl:attribute name="pages"><xsl:value-of
select="@pages"/></xsl:attribute>
<xsl:attribute name="days"><xsl:value-of
select="@days"/></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Пример statm.xsl файла.
На этот раз показан пример фильтрации данных для 2001-го года ... и также очевидно, что из исходной структуры (т.е. statm.xml-файла см. выше) исключён атрибут year (по значению которого осуществлялась выборка).
Вот теперь, когда как надеюсь понятно, относительно того, как осуществляется выборка/преобразования требуемого подмножества данных из соответствующих xml-файлов, обратимся к некоторым деталям программирования на JavaScrip на стороне Internet-клиента.
Прежде всего, посмотрим на нижеследующий код, помещённый в какой-нибудь html-файл, загружаемый клиентом:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<meta content="text/html; charset=windows-1251"
http-equiv="Content-Type">
...
<SCRIPT LANGUAGE="JavaScript">
<!--
// Parse error formatting function
function reportParseError(error)
{
var s = "";
for (var i=1; i<error.linepos; i++)
{
s += " ";
}
r = "<font size=4>XML Error loading '" +
error.url +
"'</font><br>" +
"<B>" + error.reason
+
"</B></font>";
if (error.line > 0)
r += "<font
size=3><XMP>" +
"at
line " + error.line + ", character " + error.linepos +
"\n" + error.srcText +
"\n" + s + "^" +
"</XMP></font>";
return r;
}
// Load sSourceFile file [XML (or XSL)] for oXmlDom object
function loadXML(oXmlDom, sSourceFile)
{
with (oXmlDom)
{
async = "false";
load(sSourceFile);
}
if (oXmlDom.parseError.errorCode != 0)
{
document.body.innerHTML =
reportParseError(oXmlDom.parseError);
return false;
}
return true;
}
// Load sXmlFile file for oXmlDom object & sXslFile file for oXslDom object
// ... and transform first by used second
function transformXML(oXmlDom, oXslDom, sXmlFile, sXslFile)
{
// Check args
if (oXmlDom == null
|| oXslDom == null
|| sXmlFile.length == 0
|| sXslFile.length == 0)
return "";
// Load XML-file
if (!loadXML(oXmlDom, sXmlFile))
return "";
// Load XSL-file
if (!loadXML(oXslDom, sXslFile))
return "";
return oXmlDom.transformNode(oXslDom.documentElement);
}
//-->
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript" FOR="window"
EVENT="onload">
<!--
var oxmlDoc = new ActiveXObject("Microsoft.xmldom");
var oxslDoc = new ActiveXObject("Microsoft.xmldom");
var sRetVal = transformXML(oxmlDoc, oxslDoc, "month_r.xml",
"month.xsl");
if (sRetVal.length == 0)
return false;
// Reset xslMonth.innerHTML
document.all.item("xslMonth").innerHTML
= sRetVal;
sRetVal = "";
//-->
</SCRIPT>
</HEAD>
<BODY>
<SPAN id="xslMonth"></SPAN>
</BODY>
</HTML>
Пример samp1_r.html
Думаю беглого взгляда достаточно, чтобы понять: единственный SPAN элемент на странице после успешной загрузки файла month_r.xml и преобразования его с учётом month.xsl будет заменён результатом преобразования (выше были приведены фрагменты файлов month_r.xml и month.xsl)... и в нашем случае это
<SELECT ID="cobMonth" NAME="Month">
<OPTION VALUE=1>Январь</OPTION>
...
<OPTION VALUE=12>Декабрь</OPTION>
</SELECT>
Фрагмент результат преобразования файла month_r.xml с использованием month.xsl
Посмотреть работу примера samp1_r.html можно здесь.
Попробуйте ещё проделать такой эксперимент:
<SCRIPT LANGUAGE="JavaScript" FOR="window"
EVENT="onload">
<!--
var oXmlDom = new ActiveXObject("Microsoft.xmldom");
var oXslDom = new ActiveXObject("Microsoft.xmldom");
var oResultTree = new ActiveXObject("Microsoft.xmldom");
if (!loadXML(oXmlDom, "statd.xml"))
return false;
if (!loadXML(oXslDom, "statd.xsl"))
return false;
//debugger;
var ErrCode = oXmlDom.transformNodeToObject(oXslDom.documentElement,
oResultTree);
if (oResultTree.documentElement != null
&& oResultTree.documentElement.childNodes.length
> 0)
{
var rest = oResultTree.documentElement.childNodes;
var child = null;
var num = 0;
var Day = "";
var Visitors = "";
var Pages = "";
for (num = 0; num < rest.length; num++)
{
child = rest.item(num).attributes;
Day = child.item(0).text
Visitors = child.item(1).text
Pages = child.item(2).text;
document.body.innerHTML +=
"Day: <b>"+Day+"</b> Visitors:
<b>"+Visitors+"</b> Pages:
<b>"+Pages+"</b><br>";
}
}
else
{
document.body.innerHTML =
reportParseError(oResultTree.parseError);
return false;
}
//-->
</SCRIPT>
Пример samp2.html выполняет разбор деревянной структуры, полученной из statd.xml в результате преобразования statd.xsl.
Выше были приведены фрагменты файлов statd.xml и
statd.xsl.
Посмотреть работу примера samp2.html можно
здесь.
Оба примера, приведённые выше, загружают xml-файлы в IE из JavaScript-кода явно, используя последовательность команд типа:
var oXmlDom = new ActiveXObject("Microsoft.xmldom");
oXmlDom.Load("My.xml")
однако, в MS Internet Explorer 5.0 это не единственная возможность. Так, имеется по крайне мере два других варианта:
<OBJECT width=0
height=0
classid="clsid:550dda30-0541-11d2-9ca9-0060b0ec3d39"
id="xmlDso">
</OBJECT>
<SCRIPT for=window
event=onload>
var
doc = xmlDso.XMLDocument;
doc.load("My.xml");
if
(doc.documentNode == null)
{
HandleError(doc);
}
</SCRIPT>
в обоих случаях, в IE Вы получаете объект XMLDSO (т.е. Data Source Object (DSO) из объектной модели DHTML), свойство XMLDocument которого возвращает ссылку на XMLDOMDocument (обратите внимание: именно XMLDOMDocument, а не XMLDocument, не смотря на название)
Наконец, XSL преобразования к xml-файлу можно применить и явно указав stylesheet, т.е. вставив в качестве второй строки в xml-файл соответствующую ссылку:
<?xml:stylesheet type="text/xsl" href="My.xsl" ?>
В последнем случае, если указанный xsl-файл будет осуществлять преобразование в html-формат, то соответствующий xml-файл может быть открыт из MS IE непосредственно.
Вот теперь, поняв как отработает код выше (возможно под отладчиком), Вам совершенно легко будет разобрать и код моего примера stat_r.html (см. ниже), если возникнет желание, конечно :-) Только в нём я не использую файлов: statd.xsl или statm.xsl показанных выше. Просто написана функция getXSL(sYear, sNumMoth), которая в качестве результата возвращает строку XSL-кода, причём, если значение второго параметра строка не нулевой длины, то для statd.xml, в противном случае для statm.xml. Полученную в результате строку XSL-кода можно использовать по тому, что объект XMLDOMDocument имеет метод loadXML для загрузки из строки (не из файла.). Для облегчения просмотра исходного кода, я не поместил его в js-файл, как это обычно делают, т.е. весь код Вы можете просмотреть используя пункт меню Вид/В виде HTML для страницы stat_r.html.
Я не думаю, что Вас всерьёз интересуют именно данные статистики посещения моего сайта :-) однако в любом случае, требуется сделать ряд пояснений относительно собственно данных:
Мне не удалось предотвратить появление данных по умолчанию при начальной загрузке Microsoft Chart ActiveX Control, так что будьте терпимы к временному появлению диаграммы с данными, вставленными в него по умолчанию. После полной загрузке страницы они будут обновлены на действительные прикладные данные.
Кажется странноватым как размер области на странице, отведённой под диаграмму, так и собственно расположение самой диаграммы в этой области. Хм... и это не спроста, такое расположение мной было выбрано после долгих экспериментов на экране с разрешением 800х600 pic с тем, чтобы не пропадали подписи для горизонтальной оси.
Собственно посмотреть работу примера stat_r.html "Статистика посещений" можно здесь, при условии, что у Вас установлено программное обеспечение указанное в заголовке Что используется.
См. также:
В качестве введения в тему могу посоветовать те книги, которые подвернулись мне под руку и помогли понят - с какого боку подъезжать к этому делу :-)