1112
О примере "Статистика посещений"

 

Содержание

Назначение
В начало

В 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-файлов.

 

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-клиента.

 

Замечания к Script-коду
В начало

Прежде всего, посмотрим на нижеследующий код, помещённый в какой-нибудь 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 "Статистика посещений" можно здесь, при условии, что у Вас установлено программное обеспечение указанное в заголовке Что используется.

См. также:

Литература
В начало

В качестве введения в тему могу посоветовать те книги, которые подвернулись мне под руку и помогли понят - с какого боку подъезжать к этому делу :-)

 
 
Hosted by uCoz