Juri Shutenko Personal Homepage. Visual FoxPro.

XML. Экспортируем данные их MS Excel. Часть 2 (новая редакция)

В предыдущей статье был приведен пример XML-документа, полученного из рабочей книги Excel. Прямой импорт такого документа в курсоры или таблицы VFP невозможен из-за несоответствия структур. С другой стороны, в VFP можно создать объект парсера XML DOM и работать с ним. Приступим?

Создать объект парсера очень просто, например:

oXMLDom=CREATEOBJECT("MSXML2.DOMDocument.4.0")

Теперь нужно загрузить в него XML документ. Если вы желаете загрузить документ из файла, то используйте метод load, а если ваш документ помещен в переменную памяти, используйте метод loadXML

Для начала попробуем работать с Generic XML документом. В качестве примера можно использовать файл team_type1.xml, показанный во второй статье цикла, который вы можете взять здесь. Сохраните этот документ на диск в каталог "c:\vfptest" или в какой-нибудь другой каталог (в этом случае исправьте имя каталога в примерах на требуемое). Или просто скопируйте его содержание с этой страницы в Notepad и сохраните его под указанным именем. Его содержание приведено ниже.

<?xml version = "1.0" encoding="Windows-1252" standalone="yes"?>
<VFPData>
   <team>
      <id>TM_1A60NG</id>
      <last_name>Ivanov</last_name>
      <first_name>Ivan</first_name>
      <birthdate>1980-10-01</birthdate>
      <occupation>Manager</occupation>
      <oc_history>02.10.2003- Manager at Marketing division
02.12.2001- 01.10.2003 Chif-engineer at Marketing division
02.02.2001-01.12.2001 Engineer at Marketing division
01.10.2000-01.02.2001 Probationer at Marketing division
</oc_history>
      <salary>8000.0000</salary>
      <perquisite>1.25</perquisite>
   </team>
   <team>
      <id>TM_1A60NL</id>
      <last_name>Petrov</last_name>
      <first_name>Petr</first_name>
      <birthdate>1976-01-12</birthdate>
      <occupation>Chief-director</occupation>
      <oc_history>01.10.2000- Chief_Director
</oc_history>
      <salary>12000.0000</salary>
      <perquisite>1.40</perquisite>
   </team>
</VFPData>

Загрузим этот документ из файла:

oXMLDom.load("c:\vfptests\team_type1.xml") && или
*? oXMLDom.load("c:\vfptests\team_type1.xml")

Вторая строка вернет вам .T., если загрузка документа прошла успешно. Итак, документ загружен. Положим я хочу получить значения элементов ID из этого документа. Нет ничего проще. В XML DOM есть два замечательных метода - getElementsByTagName и selectNodes

Оба метода возвращают коллекции узлов, имя которых передано им в качестве параметра. В отличие от родных объектов класса коллекции VFP, индексы, получаемых коллекций построены на базе 0, то есть на первый элемент нужно ссылаться путем вызова дефолтного метода item(0).

Разница в вызове этих методов заключена в следующем:
getElementsByTagName принимает в качестве параметра имя элемента;
selectNodes принимает в качестве параметра выражение XPath (которому посвящена отдельная статья цикла).

Осталось исполнить задуманное...

oIDs=oXMLDOM.getElementsByTagName('id')
? oIDs.length
oIDs=oXMLDOM.selectNodes('VFPData//id')
? oIDs.length
* следующая строка также абсолютно правильна, с точки зрения XPath
* oIDs=oXMLDOM.selectNodes('.//id')
* ? oIDs.length
? oIDs.item(0).text
? oIDs.item(1).text

Класс! Если взглянуть на файл, полученный из Excel, то мы увидим, что требуемые нам данные располагаются между тэгами <Row> </Row> Где наши чепчики? ... Пусть там и лежат!

oXMLDom.load("c:\vfptests\oil_consumption_excel.xml")
oRows=oXMLDOM.getElementsByTagName('Row')
? oRows.length

Что имеем? Zero! Условимся пока не делать никаких изменений в исходном файле. Может мы делаем что-то не так? Хорошо, давайте получим объектную ссылку на корневой элемент:

oRoot=oXMLDom.documentElement
? oRoot.nodeName

Получаем Workbook. Теперь снова можете попробовать выбрать требуемые узлы с помощью методов выборки, как мы это делали раньше в примерах. Увы! Результат по-прежнему ноль.

Как быть? Попробуем пройтись по узлам с помощью цикла FOR EACH ... IN / ENDFOR. Поскольку работа цикла не поддерживается в командном окне, создадим программный файл excel_xml_parsing.prg.

LPARAMETERS pcXML
Local ;
   oDOMXLS As MSXML2.DOMDocument.4.0, ;
   oResDOM As As MSXML2.DOMDocument.4.0

Local oRoot, oWSheet, oTable, oRow

oDOMXLS=Createobject("MSXML2.DOMDocument.4.0")
oResDOM=Createobject("MSXML2.DOMDocument.4.0")

oDOMXLS.load(pcXML)
Do While oDOMXLS.readyState !=4
   DoEvents
Enddo

Приведенный выше фрагмент кода создает два объекта XML DOM и загружает в первый из них наш исследуемый файл, переданный в качестве параметра. Далее, добавим строку кода, в которой получим объектную ссылку на корневой элемент и строку подтверждающую, что дочерние узлы имеются.

oRoot=oDOMXLS.documentElement
? oRoot.childNodes.length

Вновь взглянем на исследуемый файл. Таблица с данными находится в узле Worksheet..., причем узлов с такими именами три (по числу листов в книге), но нас интересует в данном примере только первый, поэтому, пока не обременяя себя универсальностью кода, ищем первое появление узла с таким именем в дочерних узлах корневого элемента:

? oRoot.childNodes.length
For Each oNode In oRoot.childNodes
   If Lower(oNode.nodeName)="worksheet"
      oWSheet=oNode
      Exit
   Endif
Endfor
? oWsheet.childNodes.length
? oWsheet.childNodes.item(0).nodeName
? oWsheet.childNodes.item(1).nodeName

Продолжим добавление кода:

For Each oNode In oWsheet.childNodes
   If Lower(oNode.nodeName)="table"
      oTable=oNode
      Exit
   Endif
Endfor
? oTable.childNodes.length

Отлично - объект oTable представляет собой объектую ссылку на коллекцию из 12 дочерних узлов узла <Table>

Осталось пройтись по этим узлам и найти все дочерние узла с именем "Row". Но куда положить результат? Давайте пока разместим объектные ссылки на требуемые узлы в коллекции colRows, которую создадим на базе родного класса коллекции Visual FoxPro.

LOCAL colRows as Collection
For Each oNode In oTable.childNodes
   If Lower(oNode.nodeName)="row"
      colRows.Add(oNode)
   Endif
Endfor

В результате исполнения приведенного выше фрагмента кода, получаем коллекцию объектных ссылок на узлы с именем "Row". Осталось перебрать объекты-члены коллекции и посмотреть на их содержимое.

Local lnColCounter, oCurNode

For lnColCounter=1 To colRows.Count
   oCurNode=colRows.Item(lnColCounter)
   For Each oNode In oCurNode.childNodes
         ? oNode.firstChild.Text
   Endfor
Endfor

Return

Где наши чепчики? ... Ну уже можно подбросить, ну скажем на полметра, поскольку мы все-таки вытащили данные, но они все имеют символьный тип! К счастью, нет ничего проще! Достаточно в приведенный выше цикл перебора добавить выборку значения аттрибута "Type" тэга содержащего данные.

For lnColCounter=1 To colRows.Count
   oCurNode=colRows.Item(lnColCounter)
   For Each oNode In oCurNode.childNodes
         ? oNode.firstChild.Text
         ? oNode.firstChild.Attributes.item(0).text
   Endfor
Endfor

Собранный до кучи код примера содержит следующее:

LPARAMETERS pcXML
Local ;
   oDOMXLS As MSXML2.DOMDocument.4.0, ;
   oResDOM As As MSXML2.DOMDocument.4.0
Local oRoot, oWSheet, oTable, oRow
oDOMXLS=Createobject("MSXML2.DOMDocument.4.0")
oResDOM=Createobject("MSXML2.DOMDocument.4.0")
oDOMXLS.Load(pcXML)
Do While oDOMXLS.readyState !=4
   DoEvents
Enddo
oRoot=oDOMXLS.documentElement
? oRoot.childNodes.Length
For Each oNode In oRoot.childNodes
   If Lower(oNode.nodeName)="worksheet"
      oWSheet=oNode
      Exit
   Endif
Endfor
? oWSheet.childNodes.Length
? oWSheet.childNodes.Item(0).nodeName
? oWSheet.childNodes.Item(1).nodeName
For Each oNode In oWSheet.childNodes
   If Lower(oNode.nodeName)="table"
      oTable=oNode
      Exit
   Endif
Endfor
? oTable.childNodes.Length
Local colRows As Collection, colRowsData As Collection
colRows=Createobject("Collection")
For Each oNode In oTable.childNodes
   If Lower(oNode.nodeName)="row"
      colRows.Add(oNode)
   Endif
Endfor
Local lnColCounter, oCurNode
For lnColCounter=1 To colRows.Count
   oCurNode=colRows.Item(lnColCounter)
   For Each oNode In oCurNode.childNodes
         ? oNode.firstChild.Text
         ? oNode.firstChild.Attributes.item(0).text
   Endfor
Endfor
Return

А продолжим мы в следующей статье, так, чтобы время загрузки файла было приемлемым. Как там насчет конверторов стоимостью кучу убитых енотов? Скажем, пока, словами классиков - "Шура, заплатите за кефир". Шутка, но думаю, что даже приведенного здесь достаточно, чтобы вы уже смогли продолжить работать. Но нас интересует импорт не столь простых файлов. Сейчас мне приходится работать с файлами Excel объемом более 50MB.

Sizes! Incredible

Как вам такие файлики? И кто это там, по-поводу будущего XML, как хранилища данных? Я пока в сторонке постою...

Cелектор для быстрого перехода на сайты, связанные с Visual FoxPro.