Juri Shutenko Personal Homepage. Visual FoxPro.

XML. Часть 3. Структура XML-документа. (Новая редакция - частично)

В первой части мы кратко посмотрели структуру документа XML. Теперь посмотрим на структуру более пристально. Сформируем документ с помощью VFP, используя родную функцию CursorToXML(). При формировании документа мы можем определить структуру документа на основе трех типов:

  • Element centric
  • Atrribute centric
  • RAW

Воспользуемся таблицей Team из базы данных Common приложения примера VFPTests. Структура таблицы определена следующим образом включает в себя поля пяти типов - символьные, даты, двоичного мемо, денежного и численного:

Structure Team.dbf

Конвертируем таблицу в файлы XML, используя, по очереди, все три типа структуры.

=CURSORTOXML("Team","c:\vfptests\team_type1.xml",1,4608)
=CURSORTOXML("Team","c:\vfptests\team_type2.xml",2,4608)
=CURSORTOXML("Team","c:\vfptests\team_type3.xml",3,4608)

В результате получим файлы, содержание которых приведено ниже:

 
<?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>
 
<?xml version = "1.0" encoding="Windows-1252" standalone="yes"?>
<VFPData>
...<team id="TM_1A60NGA0C" last_name="Ivanov" first_name="Ivan"
birthdate="1980-01-10" 
occupation="Manager" 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" salary="8000.0000"/>
<team id="TM_1A60NL66D" last_name="Petrov" first_name="Petr"
birthdate="1976-01-12" 
occupation="Chief-director" oc_history="01.10.2000- Chief_Director" 
salary="12000.0000"/>
</VFPData>
 
<?xml version = "1.0" encoding="Windows-1252" standalone="yes"?>
<VFPData>
   <team id="TM_1A60NG" last_name="Ivanov" first_name="Ivan" 
birthdate="1980-10-01" occupation="Manager" 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" salary="8000.0000" perquisite="1.25"/>
   <team id="TM_1A60NL" last_name="Petrov" first_name="Petr"
birthdate="1976-01-12" occupation="Chief-director" oc_history="01.10.2000- 
Chief_Director" salary="12000.0000" perquisite="1.40"/>
</VFPData>

Разберем эти три файла по порядку.
 
1. Первый файл был конвертирован как "Element centric". В этом случае каждая строка таблицы описана содержимым, обрамленным тэгами "team", а содержимое записи предcтавляет набор узлов, представляющих поля таблицы, обрамленное тэгами, имена которых сопадают с именами полей и заполненные содержимым этих полей.
 
Все было бы прекрасно, но из этого файла мы не можем извлечь информации о типах данных.
 
2. Второй файл был конвертирован как "Attribute centric". В этом случае каждая строка описана содержимым, обрамленным тэгами "team", а содержимое предcтавляет собой набор аттрибутов, по аттрибуту на каждое поле, при этом аттрибуты имеют имена полей и каждому аттрибуту присвоено содержимое соответствующего поля.
 
Как и в первом случае, из этого файла мы не можем извлечь информации о типах данных.
 
3. Третий файл был конвертирован как "RAW". Этот формат еще называют generic, поскольку информация в нем представлена строками, что и отражено в названии узлов. В этом случае каждая строка описана содержимым, обрамленным тэгами "row", а содержимое, как и во втором случае предcтавляет собой набор аттрибутов, по аттрибуту на каждое поле, при этом аттрибуты имеют имена полей и каждому аттрибуту присвоено содержимое соответствующего поля.

Как и ранее, из этого файла мы не можем извлечь информации о типах данных. Это означает, что при обратной конверсии мы теряем поля "memo" и "currency", в чем легко убедиться, выдав команду:.

XMLTOCURSOR("c:\vfptests\team_type1.xml","Test",4608)
COPY TO C:\vfptests\test_table.dbf
use C:\vfptests\test_table.dbf EXCLUSIVE
MODIFY STRUCTURE

...и взглянув на структуру:

Memo and currency fields are lost!

Все три файла мы можем открыть в MS Excel 2003 (с использованием опции As Read-only Workbook). Обратите внимание, что в случае первого файла в лист рабочей книги будет добавлен еще один столбец с именем salary#agg. Второй и третий файл будут открыты одинаково.

Как получить информацию о типах данных, которые содержаться в конвертированных файлах? Для этого мы должны конвертировать наши курсоры с использованием опций schema.

Выполним команду:

=CURSORTOXML(;
   "Team",;
   "c:\vfptests\team_type1_xsd.xml",;
   1,;
   4608,;
   0,;
   "c:\vfptests\team_type1_xsd.xsd";
)

Что иы получим в результате исполнения этой команды? Мы конвертируем таблицу Team в файл "c:\vfptests\team_type1_xsd.xml" с типом "Element centric", то есть каждое поле будет выведено в файл как самостоятельный узел, при этом запретим конвертирование бинарного мемо-поля :(4608 составлено из флагов 512 - трактовать второе выражение команды, как имя файла, и 4096 - запрет конверсии бинарных мемо-полей с использованием кодирования base64), в файл XML будут выведены все записи и схема будет содержаться в файле "c:\vfptests\team_type1_xsd.xsd".

Содержимое полученных файлов "team_type1_xsd.xml" и "team_type1_xsd.xsd" приведено ниже.

<?xml version = "1.0" encoding="Windows-1252" standalone="yes"?>
<VFPData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="c:\vfptests\team_type1_xsd.xsd">
   <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>
<?xml version = "1.0" encoding="Windows-1252" standalone="yes"?>
<xsd:schema id="VFPData" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
   <xsd:element name="VFPData" msdata:IsDataSet="true">
      <xsd:complexType>
         <xsd:choice maxOccurs="unbounded">
            <xsd:element name="team" minOccurs="0" maxOccurs="unbounded">
               <xsd:complexType>
                  <xsd:sequence>
                     <xsd:element name="id">
                        <xsd:simpleType>
                           <xsd:restriction base="xsd:string">
                              <xsd:maxLength value="12"/>
                           </xsd:restriction>
                        </xsd:simpleType>
                     </xsd:element>
                     <xsd:element name="last_name">
                        <xsd:simpleType>
                           <xsd:restriction base="xsd:string">
                              <xsd:maxLength value="20"/>
                           </xsd:restriction>
                        </xsd:simpleType>
                     </xsd:element>
                     <xsd:element name="first_name">
                        <xsd:simpleType>
                           <xsd:restriction base="xsd:string">
                              <xsd:maxLength value="20"/>
                           </xsd:restriction>
                        </xsd:simpleType>
                     </xsd:element>
                     <xsd:element name="birthdate" type="xsd:date"/>
                     <xsd:element name="occupation">
                        <xsd:simpleType>
                           <xsd:restriction base="xsd:string">
                              <xsd:maxLength value="20"/>
                           </xsd:restriction>
                        </xsd:simpleType>
                     </xsd:element>
                     <xsd:element name="oc_history">
                        <xsd:simpleType>
                           <xsd:restriction base="xsd:string">
                              <xsd:maxLength value="2147483647"/>
                           </xsd:restriction>
                        </xsd:simpleType>
                     </xsd:element>
                     <xsd:element name="salary">
                        <xsd:simpleType>
                           <xsd:restriction base="xsd:decimal">
                              <xsd:totalDigits value="19"/>
                              <xsd:fractionDigits value="4"/>
                           </xsd:restriction>
                        </xsd:simpleType>
                     </xsd:element>
                     <xsd:element name="perquisite">
                        <xsd:simpleType>
                           <xsd:restriction base="xsd:decimal">
                              <xsd:totalDigits value="3"/>
                              <xsd:fractionDigits value="2"/>
                           </xsd:restriction>
                        </xsd:simpleType>
                     </xsd:element>
                  </xsd:sequence>
               </xsd:complexType>
            </xsd:element>
         </xsd:choice>
         <xsd:anyAttribute namespace="http://www.w3.org/XML/1998/namespace" processContents="lax"/>
      </xsd:complexType>
   </xsd:element>
</xsd:schema>

Полученный файл открывается без проблем в MS Excel 2003 (As XML List), но колонка salary игнорирует указанный в схеме тип данных "decimal" и выводит его как General. Кроме того, для содержимого поля oc_history в первой записи MS Excel автоматически устанавливает формат ячейки с параметром Wrap text, а для содержимого одноименного поля во второй записи - нет.

Подробнее использование схем рассмотрим в одной из следующих частей.

Если теперь провести обратное преобразование, то есть выполнить команду:

XMLTOCURSOR("c:\vfptests\team_type1_xsd.xml","Test",6656)

то мы получим курсор с "почти правильной" структурой и заполненный данными, содержащимися в XML-файле. Почему с "почти правильной"? Потому, что в исходной таблице для поля "memo" было указано NOCP, то есть мы имели дело с memo(binary), то в курсоре полученном из XML-документа мы получили простое memo-поле.

Result with XMLTOCURSOR()

А что мы получим мы получим, если будем использовать новый класс XMLAdapter? Мы получим правильную структуру, правда при этом мы должны позаботиться о том, чтобы поля получили правильные типы. Чтобы поле salary получило тип "currency", необходимо указать объекту, полученному из класса XMLAdapter, что необходимо произвести разметку поля, имеющиего в схеме аттрибуты "decimal, 19, 4", для чего установить значение его свойства MapN19_4ToCurrency в .T.. Несколько сложнее указать, что поле "oc_history" должно быть бинарным. Дело в том, что если мы установим значение свойства NoCpTrans этого объекта в .T., то в бинарные будут превращены оба типа полей - как "character", так и "memo". В этом случае, мы должны установить значение такого свойства персонально для требуемого поля. После того, как XML загружен, мы можем обратиться к свойству соответствующего поля. Это может выглядеть так:

oXMLAdapter = Createobject('XMLAdapter')
oXMLAdapter.MapN19_4ToCurrency=.T.
oXMLAdapter.XMLSchemaLocation="c:\vfptests\team_type1_xsd.xsd"
oXMLAdapter.LoadXML("c:\vfptests\team_type1_xsd.xml",.T.,.T.)

For Each oField In oXMLAdapter.Tables.Item(1).Fields
   If oField.Alias='oc_history'
      oField.NoCPTrans=.T.
      Exit
   Endif
Endfor

oXMLAdapter.Tables.Item(1).ToCursor
Copy To C:\vfptests\test_table.Dbf
Use C:\vfptests\test_table.Dbf
Modify Structure

В таком случае мы имеем требуемую структуру.

Класс XMLAdapter будет подробно рассмотрен в одной из последующий статей.

И, наконец, аналогичный результат мы получим, если будем использовать новый класс CursorAdapter, который по сравнению с XMLAdapter'ом потребует большей писанины:

oCA=CREATEOBJECT("CURSORADAPTER")
oCA.DataSourceType="XML"
oCA.InsertCmdDataSourceType="XML"
oCA.UpdateCmdDataSourceType="XML"
oCA.DeleteCmdDataSourceType="XML"
oCA.CursorSchema="id C(12), last_name C(20), first_name C(20), birthdate D,;
   occupation C(20), oc_history M NOCPTRANS, salary Y, perquisite N(4,2)"
oCA.SelectCmd="c:\vfptests\team_type1_xsd.xml"
oCA.CursorFill(.T.,.f.,512)
COPY TO C:\vfptests\test_table.dbf
USE 
use C:\vfptests\test_table.dbf
MODIFY STRUCTURE

Замечание 1. Преобразование таблицы в XML с последующим обратным преобразованием XML -> таблица, может быть использовано, как вариант переброса таблицы из одной базы данных в другую, или в свободную таблицу. Правда,при этом может потребоваться правка структуры после проведенной конверсии с использованием XMLTOCURSOR.

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