6161
|
|
|
Как получить dbf-таблицы из xls-файла при наличии групп в данных? |
Содержание:
Вопрос действительно интересный! :-) Схема получения dbf-таблиц могла бы быть следующей:
- получение xml-данных из xls-файла
- преобразование полученных xml-данных в структуру,
пригодную для преобразований в связанные dbf-файлы
- ну и собственно преобразование xml-данных
в dbf-таблицы, используя
VFP-класс XMLAdapter
Первый шаг может быть выполнен достаточно просто, если вы имеет
Excel из MS Office 2003 (или
выше). Чтобы выполнить второй шаг, на мой взгляд, проще всего воспользоваться
технологией XSLT-преобразований, для чего следует хотя
бы в общих чертах иметь представление об этой технологии.
Другие пути для выполнения преобразований над xml-данными
в использовании xml-парсеров и объектной модели
DOM, также возможно написание кода обработки с
использованием SAX2. Если у вас большие объёмы данных, то последнее выглядит наиболее
предпочтительным. Наконец, чтобы воспользоваться VFP-классом
XMLAdapter, появившемся в версии 8.0, вы должны быть обладателем именно версии VFP 9.0 (или выше), т.к. в
перелагаемом в этой статье VFP-коде, используется
свойство XMLField.XMLNameIsXPath. Ниже
предполагается, что все перечисленные условия удовлетворены.
Итак, попробуем пройти все шаги, разбирая конкретный пример данных в
MS Excel 2003. Допустим, что требуется получить
dbf-таблицы для данных, которые в MS Excel выглядят так:
Рис.1
Представленная таблица имеет "заголовок таблицы" (строки: 3-6), со строк: 7,
15, ... начинаются группы, а со строк: 8,13;16,... начинаются соответствующие
подгруппы... Каждая подгруппа помимо своего "заголовка" (строки: 8, 13, 16,
...), имеет некоторое множество строк - "содержания подгруппы" (строки:
9-12,14,17-20, ...).
По этим данным, требуется получить три VFP-таблицы
связанные отношениями:
- для заголовков групп (назовём её group),
- для заголовков подгрупп (назовём её subgroup)
- и наконец, для данных подгрупп (назовём её cells)
Как было сказано выше, для этого средствами MS Excel
2003 (или выше), выделив соответствующую таблицу (из
файла: table.xls), выполним пункт
меню: Файл/Сохранить как...[Другие
форматы/Тип файла: Таблица XML 2003].
Рис.2
при этом, на возникший запрос о несоответствии формата следует ответить
утвердительно. После чего мы получим файл с данными таблицы в xml-формате
примерно такой структуры:
<?xml version="1.0" ?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40">
<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
...
</DocumentProperties>
<OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office">
...
</OfficeDocumentSettings>
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
...
</ExcelWorkbook>
<Styles>
<Style ss:ID="Default" ss:Name="Normal">
...
</Style>
<Style ss:ID="s23">
...
</Style>
...
<Style ss:ID="sNN">
...
</Style>
...
</Styles>
<Worksheet ss:Name="...Лист1">
<Table ...>
<Column ss:AutoFitWidth="0" ss:Width="24" />
<Column ss:AutoFitWidth="0" ss:Width="216" />
<Column ss:AutoFitWidth="0" ss:Width="26.25" />
<Column ss:AutoFitWidth="0" ss:Width="40.5" />
<Column ss:AutoFitWidth="0" ss:Width="196.5" />
<Column ss:AutoFitWidth="0" ss:Width="65.25" />
<Column ss:AutoFitWidth="0" ss:Width="57.75" />
<Column ss:AutoFitWidth="0" ss:Width="55.5" />
<Column ss:AutoFitWidth="0" ss:Width="53.25" />
<Row ss:AutoFitHeight="0" ss:Height="21">
<Cell ss:Index="2" ss:StyleID="s23">
<Data ss:Type="String">Форма №7</Data>
</Cell>
<Cell ss:StyleID="s23" />
</Row>
<Row ss:AutoFitHeight="0" ss:Height="42.75">
<Cell ss:Index="2" ss:StyleID="s24">
<Data ss:Type="String">Перечень работ кап. строительства на 2007 г. (без НДС,
тыс.руб.)</Data>
</Cell>
<Cell ss:StyleID="s24" />
</Row>
...
<Row ss:AutoFitHeight="0" ss:Height="22.5">
<Cell ss:StyleID="s32" />
<Cell ss:StyleID="s43">
<Data ss:Type="String">79.01 - СКРУ №1</Data>
</Cell>
<Cell ss:StyleID="s43" />
<Cell ss:StyleID="s31" />
<Cell ss:StyleID="s31" />
<Cell ss:StyleID="s32" />
<Cell ss:StyleID="s33" />
<Cell ss:StyleID="s32" />
<Cell ss:StyleID="s32" />
</Row>
<Row>
<Cell ss:StyleID="s32" />
<Cell ss:StyleID="s44">
<Data ss:Type="String">01020000 - РУДНИК СКРУ-1</Data>
</Cell>
<Cell ss:StyleID="s44" />
<Cell ss:StyleID="s31" />
<Cell ss:StyleID="s31" />
<Cell ss:StyleID="s32" />
<Cell ss:StyleID="s33" />
<Cell ss:StyleID="s32" />
<Cell ss:StyleID="s32" />
</Row>
<Row ss:Height="29.25">
<Cell ss:StyleID="s42">
<Data ss:Type="Number">3</Data>
</Cell>
<Cell ss:StyleID="s35">
<Data ss:Type="String">ОАО СИЛЬВИНИТ, ШСУ</Data>
</Cell>
<Cell ss:StyleID="s36">
<Data ss:Type="String">1</Data>
</Cell>
<Cell ss:StyleID="s35">
<Data ss:Type="String">0601011067</Data>
</Cell>
<Cell ss:StyleID="s37">
<Data ss:Type="String">ВСКРЫТИЕ И ПОДГОТОВКА ЗАПАСОВ НОВО-СОЛИКАМСКОГО УЧАСТКА...</Data>
</Cell>
<Cell ss:StyleID="s38">
<Data ss:Type="Number">104440</Data>
</Cell>
<Cell ss:StyleID="s39">
<Data ss:Type="Number">29440</Data>
</Cell>
<Cell ss:StyleID="s40">
<Data ss:Type="Number">9480</Data>
</Cell>
<Cell ss:StyleID="s40">
<Data ss:Type="Number">75000</Data>
</Cell>
</Row>
<Row ss:Height="19.5">
<Cell ss:StyleID="s42">
<Data ss:Type="Number">4</Data>
</Cell>
<Cell ss:StyleID="s35">
<Data ss:Type="String">ОАО СИЛЬВИНИТ, ШСУ</Data>
</Cell>
<Cell ss:StyleID="s36">
<Data ss:Type="String">1</Data>
</Cell>
<Cell ss:StyleID="s35">
<Data ss:Type="String">0601011070</Data>
</Cell>
<Cell ss:StyleID="s37">
<Data ss:Type="String">ОТРАБОТКА ПЛ.ВК В I И II СЕВЕРНЫХ ПАНЕЛЯХ РУДНИКА</Data>
</Cell>
<Cell ss:StyleID="s38">
<Data ss:Type="Number">12905</Data>
</Cell>
<Cell ss:StyleID="s39">
<Data ss:Type="Number">12905</Data>
</Cell>
<Cell ss:StyleID="s40">
<Data ss:Type="Number">6536</Data>
</Cell>
<Cell ss:StyleID="s34" />
</Row>
...
</Table>
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
...
</WorksheetOptions>
</Worksheet>
</Workbook>
Файл: _table.xml
здесь незначимая для нас информация упущена и оставлено только то, что нам
необходимо для дальнейшей работы. Обратите внимание на следующие моменты:
- корневой элемент имеет "область имён" (namespace)
"по
умолчанию": xmlns="urn:schemas-microsoft-com:office:spreadsheet".
- собственно "сами данные" располагаются в элементах: /.//Workbook/Worksheet/Table/Row, а
точнее в /.//Workbook/Worksheet/Table/Row/Cell/Data, в
то время как значение атрибута ss:StyleID="s..." у
элемента Cell позволяет достаточно чётко различать
функциональную особенность данных элемента.
К сожалению, наличие "области имён по умолчанию" делает невозможным использование таких
средств как XSLT-преобразования. Поэтому первым шагом
на нашем пути, удалим xmlns="urn:schemas-microsoft-com:office:spreadsheet" из корневого элемента
Workbook, а полученный после такого редактирования файл
сохраним с новым именем (из _table.xml в table.xml в нашем случае).
Относительно "признаков выбора", требуемых нам "прикладных данных",
глядя на содержимое полученного xml-файла, можно
заметить следующее:
- информация "о группах" располагается в таких элементах
/.//Workbook/Worksheet/Table/Row/Cell/Data, у которых для элемента
Cell атрибут ss:StyleID="s43"
- в свою очередь, признаком выбора информации "о подгруппах" является значение
этого атрибута ss:StyleID="s44"
- наконец, элемент Row тогда содержит множество
ячеек Cell содержащих "данные подгруппы", если
среди них есть элемент Cell с атрибутом
ss:StyleID="s42"
Далее, мы постараемся воспользоваться этими обстоятельствами, чтобы
организовать обработку данных с помощью XSLT-преобразования.
Если вы уже имели опыт написания XSLT-преобразований,
вам наверное известно, что всякий раз, когда требуется решить задачу,
связанную "с организацией группировок в XSLT",
мы испытываем вполне ощутимые трудности... :-( см. например, одно из решений здесь:
http://xmlhack.ru/books/xslt/ch_11.html.
В нашем случае, "на
входе" мы имеем "линейную" последовательность элементов
Row, тогда как "на выходе" т.е. в результате преобразований,
нам требуется получить "вложенные" xml-структуры
согласно связям типа: "родитель -> дети".
Чтобы обеспечить это, воспользуемся перебором всего множества элементов из /.//Workbook/Worksheet/Table/Row с
помощью XSLT-конструкции: <xsl:for-each>,
организуя требуемую вложенность элементов их "динамическим формированием" в
выходной поток. Что же конкретно нам нужно? Попробуем сформулировать:
- всякий раз, когда "на входе" встречается элемент
Row,
содержащий в себе элемент Cell с атрибутом
ss:StyleID="s43",
нам нужно "организовать начало новой группы" (закрыв прежде открытую, если такая
имеется, естественно). В качестве имени для такого группового элемент возьмём
название group.
- в рамках групп, организованных с помощью предыдущего пункта, при обнаружении
"на входе" в текущем Row элемента
Cell с атрибутом ss:StyleID="s44",
нам следует организовать начало новой вложенной подгруппу (закрыв аналогичную
предшествующую, если такая имеется). В качестве имени элемента для подгруппы
возьмём subgroup.
- по завершению цикла обработки всего множества элементов
Row мы должны закрыть как подгруппу
subgroup так и группу group,
если они не были ещё закрыты.
Здесь предполагается, что во входном потоке отсутствуют "разрывы" в смысле
групп/подгрупп. Т.е. данные во входном потоке упорядочены в строгом соответствии
с требованиями группировок, а также и то, что во входном потоке нет данных, не
принадлежащих ни группам, ни их подгруппам. Основная трудность в реализации этой
схемы заключается в проверке условий на момент открытия новой группы/подгруппы,
позволяющих ответить на вопрос: а есть ли ранее открытые аналогичные
группы/подгруппы, которые следует закрыть при открытии новых?
При вышеупомянутых предположениях ответ на этот вопрос достаточно прост:
- для самой внешней группы: закрытие группы следует выполнять всегда, если
только это не первый раз встретившаяся группа.
- для подгруппы ответ в общем-то аналогичный: закрытие подгруппы следует
выполнять всегда, если это только не первая подгруппа в группе.
Ниже XSTL-код, реализующий описанную схему:
<?xml
version="1.0"
encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="o
x ss html xsl msxsl msdata">
<xsl:output
method="xml"
encoding="windows-1251"
version="1.0"
omit-xml-declaration="yes"
/>
<xsl:variable
name="varLft">
<xsl:text
disable-output-escaping="yes"><</xsl:text>
</xsl:variable>
<xsl:variable
name="varLft1">
<xsl:text
disable-output-escaping="yes"></</xsl:text>
</xsl:variable>
<xsl:variable
name="varRgt">
<xsl:text
disable-output-escaping="yes">></xsl:text>
</xsl:variable>
<xsl:variable
name="varBegGroup">
<xsl:value-of
disable-output-escaping="yes"
select="concat($varLft,
'group', $varRgt)"
/>
</xsl:variable>
<xsl:variable
name="varEofGroup">
<xsl:value-of
disable-output-escaping="yes"
select="concat($varLft1,
'group', $varRgt)"
/>
</xsl:variable>
<xsl:variable
name="varBegSubGroup">
<xsl:value-of
disable-output-escaping="yes"
select="concat($varLft,
'subgroup', $varRgt)"
/>
</xsl:variable>
<xsl:variable
name="varEofSubGroup">
<xsl:value-of
disable-output-escaping="yes"
select="concat($varLft1,
'subgroup', $varRgt)"
/>
</xsl:variable>
<xsl:variable
name="varBegName">
<xsl:value-of
disable-output-escaping="yes"
select="concat($varLft,
'name', $varRgt)" />
</xsl:variable>
<xsl:variable
name="varEofName">
<xsl:value-of
disable-output-escaping="yes"
select="concat($varLft1,
'name', $varRgt)" />
</xsl:variable>
<xsl:variable
name="varBegId">
<xsl:value-of
disable-output-escaping="yes"
select="concat($varLft,
'id', $varRgt)" />
</xsl:variable>
<xsl:variable
name="varEofId">
<xsl:value-of
disable-output-escaping="yes"
select="concat($varLft1,
'id', $varRgt)" />
</xsl:variable>
<xsl:variable
name="varFirstGrpID">
<xsl:value-of
select="generate-id(/.//Workbook/Worksheet/Table/Row/Cell[@ss:StyleID='s43']/..)"/>
</xsl:variable>
<xsl:template
match="/">
<xsl:text
disable-output-escaping="yes"><![CDATA[<?xml
version="1.0" encoding="windows-1251"?>]]></xsl:text>
<xsl:text
disable-output-escaping="yes">
</xsl:text>
<root>
<xsl:attribute
name="form">
<xsl:value-of
select="normalize-space(/.//Workbook/Worksheet/Table/Row/Cell[@ss:StyleID='s23']/Data)"/>
</xsl:attribute>
<xsl:attribute
name="title">
<xsl:value-of
select="normalize-space(/.//Workbook/Worksheet/Table/Row/Cell[@ss:StyleID='s24']/Data)"/>
</xsl:attribute>
<xsl:for-each
select="/.//Workbook/Worksheet/Table/Row">
<!-- // group
-->
<xsl:if
test="Cell[@ss:StyleID='s43']/Data">
<xsl:if
test="generate-id()!=$varFirstGrpID">
<!-- </subgroup>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varEofSubGroup"
/>
<!-- </group>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varEofGroup"
/>
</xsl:if>
<!-- <group>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varBegGroup"
/>
<xsl:variable
name="varGroupName">
<xsl:value-of
disable-output-escaping="yes"
select="normalize-space(Cell[@ss:StyleID='s43']/Data)"
/>
</xsl:variable>
<xsl:variable
name="varGroupID">
<xsl:value-of
select="generate-id()"
/>
</xsl:variable>
<!-- <id>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varBegId"
/>
<xsl:value-of
disable-output-escaping="yes"
select="$varGroupID"
/>
<xsl:value-of
disable-output-escaping="yes"
select="$varEofId"
/>
<!-- <name>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varBegName"
/>
<xsl:value-of
disable-output-escaping="yes"
select="$varGroupName"
/>
<xsl:value-of
disable-output-escaping="yes"
select="$varEofName"
/>
</xsl:if>
<!-- group //
-->
<!-- // subgroup
-->
<xsl:if
test="Cell[@ss:StyleID='s44']/Data">
<xsl:if
test="string-length((preceding-sibling::*)[last()]/Cell[@ss:StyleID='s43']/Data)=0">
<!-- </subgroup>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varEofSubGroup"
/>
</xsl:if>
<!-- <subgroup>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varBegSubGroup"
/>
<xsl:variable
name="varGroupName">
<xsl:value-of
disable-output-escaping="yes"
select="normalize-space(Cell[@ss:StyleID='s44']/Data)"
/>
</xsl:variable>
<xsl:variable
name="varGroupID">
<xsl:value-of
select="generate-id()"
/>
</xsl:variable>
<!-- <id>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varBegId"
/>
<xsl:value-of
disable-output-escaping="yes"
select="$varGroupID"
/>
<xsl:value-of
disable-output-escaping="yes"
select="$varEofId"
/>
<!-- <name>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varBegName"
/>
<xsl:value-of
disable-output-escaping="yes"
select="$varGroupName"
/>
<xsl:value-of
disable-output-escaping="yes"
select="$varEofName"
/>
</xsl:if>
<!-- subgroup //
-->
<xsl:apply-templates
select="."
/>
</xsl:for-each>
<!-- </subgroup>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varEofSubGroup"
/>
<!-- </group>
-->
<xsl:value-of
disable-output-escaping="yes"
select="$varEofGroup"
/>
</root>
</xsl:template>
<xsl:template
match="Data"
/>
<xsl:template
match="Row">
<xsl:if
test="Cell[@ss:StyleID='s42']/Data">
<cells>
<xsl:for-each
select="Cell">
<xsl:if
test="Data">
<xsl:element
name="{concat('d',string(position()))}">
<xsl:value-of
disable-output-escaping="yes"
select="normalize-space(Data)"
/>
</xsl:element>
</xsl:if>
</xsl:for-each>
</cells>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Файл: ConvertTable.xslt
Здесь
- в переменные типа: varBegXXX/varEofXXX формируются
строки-названий начал/концов элементов: <group/>,
<subgroup/>, <name/>, <id/>
- в переменную varFirstGrpID формируется
уникальный код, соответствующий первому элементу
Row, в котором имеется
элемент
<group/>
- условие: test="string-length((preceding-sibling::*)[last()]/Cell[@ss:StyleID='s43']/Data)=0"
проверяет: не является ли непосредственно предшествующий обработке элемент
Row таким, который "открывает" группу <group/>?
- наконец, смысл шаблона для элемента Row
в том, чтобы сформировать элемент <cells>...</cells> с
включёнными в него элементами: d1, ..., dN, каждый
из которых содержит значения элементов Data, т.е.
собственно "сами данные подгруппы".
Фрагмент результата выполнения преобразования ConvertTable.xslt
над файлом table.xml выглядит так:
<?xml
version="1.0"
encoding="windows-1251"?>
<root
form="Форма
№7"
title="Перечень
работ кап. строительства на 2007 г. (без НДС, тыс.руб.)">
<group>
<id>IDAJL1P</id>
<name>79.01
- СКРУ №1</name>
<subgroup>
<id>IDAOM1P</id>
<name>01020000
- РУДНИК СКРУ-1</name>
<cells>
<d1>3</d1>
<d2>ОАО
СИЛЬВИНИТ, ШСУ</d2>
<d3>1</d3>
<d4>0601011067</d4>
<d5>ВСКРЫТИЕ
И ПОДГОТОВКА ЗАПАСОВ НОВО-СОЛИКАМСКОГО УЧАСТКА ВКМКС РУДНИКОМ СКРУ-1 ОАО
"СИЛЬВИНИТ"</d5>
<d6>104440</d6>
<d7>29440</d7>
<d8>9480</d8>
<d9>75000</d9>
</cells>
<cells>
<d1>4</d1>
<d2>ОАО
СИЛЬВИНИТ, ШСУ</d2>
<d3>1</d3>
<d4>0601011070</d4>
<d5>ОТРАБОТКА
ПЛ.ВК В I И II СЕВЕРНЫХ ПАНЕЛЯХ РУДНИКА</d5>
<d6>12905</d6>
<d7>12905</d7>
<d8>6536</d8>
</cells>
<cells>
<d1>5</d1>
<d2>ОАО
СИЛЬВИНИТ, ШСУ</d2>
<d3>1</d3>
<d4>0601011071</d4>
<d5>ЗАКЛАДКА
ГИДРАВЛИЧЕСКИМ СПОСОБОМ 10 И 12 ЗАПАДНЫХ ПАНЕЛЕЙ РУДНИКА</d5>
<d6>36015</d6>
<d7>32015</d7>
<d8>19764</d8>
<d9>4000</d9>
</cells>
<cells>
<d1>12</d1>
<d2>ОАО
СИЛЬВИНИТ, ШСУ</d2>
<d3>1</d3>
<d4>0601022785</d4>
<d5>ГИДРАВЛИЧЕСКАЯ
ЗАКЛАДКА В БЛОКАХ 70,72,74,80,82,84,90,92,94</d5>
<d6>13135</d6>
<d7>13135</d7>
</cells>
</subgroup>
<subgroup>
<id>IDAVW1P</id>
<name>01040000
- СИЛЬВИНИТОВАЯ ОБОГАТИТЕЛЬНАЯ ФАБРИКА</name>
<cells>
<d1>14</d1>
<d2>ОАО
СИЛЬВИНИТ, ШСУ</d2>
<d3>1</d3>
<d4>0601011106</d4>
<d5>УСТАНОВКА
ЦЕНТРИФУГ ПОЗ.238-4 НА СОФ</d5>
<d6>43000</d6>
<d7>2000</d7>
<d8>1600</d8>
<d9>41000</d9>
</cells>
</subgroup>
</group>
<group>
<id>IDAI01P</id>
<name>79.02
- СКРУ №2</name>
<subgroup>
<id>IDAM11P</id>
<name>02020000
- РУДНИК СКРУ-2</name>
<cells>
...
...
</group>
</root>
Фрагменты файла: out.xml
Вот только теперь можно воспользоваться VFP-классом
XMLAdapter для получения соответствующих
dbf-файлов. Однако, в полученном нами
xml-файле имеется небольшая проблема, заключающаяся в
том, что информация по первичным ключам в данных для групп/подгрупп имеется
(значения элементов <id>...</id>,
динамически нами сформированных во время
выполнения вышеприведённого XSLT-преобразования), в то время как
внешние ключи (foreign keys)
в xml-файле непосредственно отсутствуют. К счастью,
гибкость VFP-класса XMLAdapter
в версии 9.0 позволяет устранить эту проблему путём формирования как самих внешних
ключей, так и их значений во время выполнения преобразования.
Код, выполняющий подобное преобразование мог бы быть таким:
ACTIVATE SCREEN
CLEAR
CLOSE TABLES ALL
LOCAL loXA
as XMLAdapter
loXA = NEWOBJECT("XMLAdapter")
*// Create tables
CREATE CURSOR group (id
C(7),
name
C(120))
CREATE CURSOR subgroup (id
C(7),
name
C(120), fk_id C(7))
CREATE CURSOR cells (d1 I, d2 C(120),
d3 I, d4 I, d5 C(120), d6 F(18,2), d7 I, d8 I, d9 F(18,2), fk_id C(7))
*// Load XML-data
loXA.RespectCursorCP
== .T.
loXA.UseCodePage
= .T.
loXA.LoadXML("out.xml",
.T., .F.)
*// AddTableSchemas
LOCAL loXtSg
as XMLTable,
loXtSl as XMLTable
loXA.AddTableSchema("group",
.T.)
loXtSg = loXA.AddTableSchema("subgroup",
.T.)
loXtSl = loXA.AddTableSchema("cells",
.T.)
*/////////////////////////////
*// Create foreign keys
LOCAL loXf
as XMLField
*// Add foreign key to XMLTable subgroup
loXf = NEWOBJECT("XMLField")
WITH loXf
.Alias
= "fk_id"
.XMLNameIsXPath
= .T.
.DataType
= "C"
.MaxLength
= 7
.XMLName
=
STRCONV("parent::group/id",
5)
loXtSg.Fields.Add(loXf,
.XMLName)
ENDWITH
*// Add foreign key to XMLTable cells
loXf = NEWOBJECT("XMLField")
WITH loXf
.Alias
= "fk_id"
.XMLNameIsXPath
= .T.
.DataType
= "C"
.MaxLength
= 7
.XMLName
=
STRCONV("parent::subgroup/id",
5)
loXtSl.Fields.Add(loXf,
.XMLName)
ENDWITH
*// Create VFP-tables
FOR EACH loTable
as XMLTable IN
loXA.Tables
IF VARTYPE(loTable) = 'O' AND
loTable.ToCursor(.T.)
AND USED(loTable.Alias)
SELECT
(loTable.Alias)
LIST
ENDIF
ENDFOR
*// Save result as XML-data
SET SAFETY OFF
loXA.ReleaseXML(.F.)
loXA.ToXML("output.xml","",.T.)
SET SAFETY ON
Файл: getdbf.prg
На рисунке ниже представлен результат работы вышеприведённого кода:
Рис.3
Используя XSLT-преобразования можно решить и
обратную задачу: преобразование dbf-файла в
xml-данные такой структуры, которые будут пригодны для
просмотра из-под MS Excel 2003 (или выше). В
простейшем случае, задача может быть решена за два шага:
- получение из dbf-таблицы xml-файла,
содержащего в себе встроенную (inline) XSD-схему
- преобразование таким образом полученных xml-данных,
в xml 2003 таблицу, используя
XSLT-преобразование
Проделаем это для фрагмента данных из dbf-таблицы, представленной на
рисунке ниже:
Рис.4
Первый шаг может быть решён использованием
функции CursorToXML(), например таким
VFP-кодом:
#DEFINE
DBF_FILE "spisok"
SET DEFAULT TO (LEFT(SYS(16),
RATC("\",
SYS(16))))
IF !USED(DBF_FILE)
USE
(DBF_FILE)
IN
0
SHARED
IF !USED(DBF_FILE)
RETURN
.F.
ENDIF
ENDIF
SET SAFETY OFF
CURSORTOXML(DBF_FILE, DBF_FILE, 3,
4+16+512, 0, "1")
SET SAFETY ON
USE IN (DBF_FILE)
Файл: dbftoxml.prg
в результате выполнения которого, получим вот такой xml-файл:
<?xml
version
= "1.0"
encoding="Windows-1251"
standalone="yes"?>
<VFPData
xml:space="preserve">
<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="row"
minOccurs="0"
maxOccurs="unbounded">
<xsd:complexType>
<xsd:attribute
name="kod_atc"
type="xsd:int"
use="required"/>
<xsd:attribute
name="raion"
type="xsd:int"
use="optional"/>
<xsd:attribute
name="name_atc"
use="optional">
<xsd:simpleType>
<xsd:restriction
base="xsd:string">
<xsd:maxLength
value="15"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute
name="adr"
use="optional">
<xsd:simpleType>
<xsd:restriction
base="xsd:string">
<xsd:maxLength
value="50"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:anyAttribute
namespace="http://www.w3.org/XML/1998/namespace"
processContents="lax"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<row
kod_atc="1"
raion="1"
name_atc="ОПТС-31
"
adr="ул.
Коноплянниковой, 4а "/>
<row
kod_atc="2"
raion="1"
name_atc="АТСК
33 "
adr="ул.
Новоторжская, 18 "/>
<row
kod_atc="3"
raion="1"
name_atc="ОПТС-36
"
adr="ул.
Склизкова, 36 "/>
<row
kod_atc="4"
raion="1"
name_atc="АТСК
100/2000 "
adr="пос.
"Сахарово"
"/>
<row
kod_atc="5"
raion="1"
name_atc="АТС
"ВНИИСВ"
"
adr="пос.Химинститутa
"/>
<row
kod_atc="6"
raion="1"
name_atc="ПСК
"Южный"
"
adr="ПСК
"Южный",
ПСК "Чайка",пр-т
Победы,64 "/>
<row
kod_atc="7"
raion="1"
name_atc="ОПТС-32,34,50
"
adr="ул.
Новоторжская, д.18 "/>
<row
kod_atc="8"
raion="1"
name_atc="ОПТС-43,45
"
adr="ул.
Склизкова, 36 "/>
<row
kod_atc="9"
raion="1"
name_atc="ОПТС-(55-56)
"
adr="ул.Оборонная,д.4
"/>
<row
kod_atc="10"
raion="1"
name_atc="OПТC-42,44
"
adr="ул.
Баррикадная, д.8 "/>
</VFPData>
Файл: spisok.XML
Если теперь к полученному файлу spisok.XML
применить вот такое XSLT-преобразование:
<?xml
version="1.0"
encoding="windows-1251"?>
<!-- file: convertToXslXml.xslt & see
also: Q319180 "How to transform a DataSet to spreadsheet XML for Excel by using
Visual Basic .NET and ASP.NET"-->
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
exclude-result-prefixes="xsl
msxsl xsd msdata ss">
<xsl:output
version="1.0"
method="xml"
encoding="windows-1251"/>
<!-- Выборка типа xsd:attribute из
схемы по названию атрибута (@name) -->
<xsl:key
name="rowType"
match="/VFPData/xsd:schema/xsd:element/xsd:complexType/xsd:choice/xsd:element/xsd:complexType/xsd:attribute"
use="@name"
/>
<!-- Ряд параметров, значения которых
можно переустановить перед обращением к преобразованию
-->
<xsl:param
name="prmLastAuthor">
<xsl:text>Michael</xsl:text>
</xsl:param>
<xsl:param
name="prmCreated">
<xsl:text>2007-04-21T09:11:11Z</xsl:text>
</xsl:param>
<xsl:param
name="prmWorksheetName">
<xsl:text>spisok</xsl:text>
</xsl:param>
<xsl:template
match="/">
<!-- Выводим "заголовок" специфичный
для MS Excel -->
<xsl:text
disable-output-escaping="yes"><![CDATA[<?mso-application
progid="Excel.Sheet"?>]]></xsl:text>
<Workbook
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40">
<DocumentProperties
xmlns="urn:schemas-microsoft-com:office:office">
<LastAuthor>
<xsl:value-of
select="$prmLastAuthor"
disable-output-escaping="yes"
/>
</LastAuthor>
<Created>
<xsl:value-of
select="$prmCreated"
disable-output-escaping="yes"
/>
</Created>
<Version>12.00</Version>
</DocumentProperties>
<ExcelWorkbook
xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>12345</WindowHeight>
<WindowWidth>18960</WindowWidth>
<WindowTopX>120</WindowTopX>
<WindowTopY>75</WindowTopY>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
<Styles>
<Style
ss:ID="Default"
ss:Name="Normal">
<Alignment
ss:Vertical="Bottom"/>
<Borders/>
<Font
ss:FontName="Calibri"
x:CharSet="204"
x:Family="Swiss"
ss:Size="11"
ss:Color="#000000"/>
<Interior/>
<NumberFormat/>
<Protection/>
</Style>
</Styles>
<Worksheet
ss:Name="{$prmWorksheetName}">
<Table
ss:ExpandedColumnCount="256"
ss:ExpandedRowCount="11"
x:FullColumns="1"
x:FullRows="1"
ss:DefaultRowHeight="15">
<!-- Выводим колонки (Column), взяв
информацию из списка xsd:attribute в схеме, а также определяем приблизительное
значение ss:Width для каждой из колонок
-->
<xsl:apply-templates
select="/./*/xsd:schema"
/>
<!-- Выводим заголовки колонок, взяв
информацию также из списка xsd:attribute в схеме...
-->
<xsl:apply-templates
select="/./*/xsd:schema/xsd:element/xsd:complexType/xsd:choice/xsd:element"
mode="flds"
/>
<!-- ... наконец, выводим собственно
содержание dbf-файла (т.е. данные из каждого элеменета <row />)
-->
<xsl:apply-templates
select="/./*/row"
/>
</Table>
<!-- Выводим "завершающий остаток"
специфичный для MS Excel -->
<WorksheetOptions
xmlns="urn:schemas-microsoft-com:office:excel">
<Selected/>
<ProtectObjects>False</ProtectObjects>
<ProtectScenarios>False</ProtectScenarios>
</WorksheetOptions>
</Worksheet>
</Workbook>
</xsl:template>
<!-- Выводим информацию для каждой
колонки (Column) взяв информацию из списка xsd:attribute в схеме, а также
определяем приблизительное значение ss:Width для каждой из колонок
-->
<xsl:template
match="xsd:attribute">
<!-- Пытаемся приблизительно определить
длину обрабатываемой колонки-->
<xsl:variable
name="varWidth">
<xsl:choose>
<xsl:when
test="count(child::*)>0
and xsd:simpleType/xsd:restriction/xsd:maxLength/@value">
<xsl:value-of
select="number(xsd:simpleType/xsd:restriction/xsd:maxLength/@value)*10"
/>
</xsl:when>
<xsl:otherwise>
<xsl:text>20</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<Column
ss:AutoFitWidth="0"
ss:Width="{$varWidth}"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
/>
</xsl:template>
<!-- Выводим заголовок текущей колонки,
взяв информацию также из списка xsd:attribute в схеме...
-->
<xsl:template
match="xsd:element"
mode="flds">
<xsl:if
test="@name='row'">
<Row
xmlns="urn:schemas-microsoft-com:office:spreadsheet">
<xsl:for-each
select="xsd:complexType/xsd:attribute">
<Cell>
<Data
ss:Type="String">
<xsl:value-of
select="@name"
/>
</Data>
</Cell>
</xsl:for-each>
</Row>
</xsl:if>
</xsl:template>
<!-- ... наконец, определяем вывод для
каждого элемента <row />, т.е. собственно содержание dbf-файла
-->
<xsl:template
match="row">
<Row
xmlns="urn:schemas-microsoft-com:office:spreadsheet">
<!-- для каждого из атрибутов...
-->
<xsl:for-each
select="@*">
<!-- пытаемся выбрать его тип из
схемы... -->
<xsl:variable
name="varRowType">
<xsl:value-of
select="key('rowType',local-name())/@type"
/>
</xsl:variable>
<!-- ... определяем текущий Excel-тип:
либо как 'Number' (для xsd:int) или как 'String' в противном случае
-->
<xsl:variable
name="varType">
<xsl:choose>
<xsl:when
test="$varRowType='xsd:int'">
<xsl:text>Number</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>String</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- выводим значение очередной ячейки
-->
<Cell>
<Data
ss:Type="{$varType}">
<xsl:value-of
select="."
/>
</Data>
</Cell>
</xsl:for-each>
</Row>
</xsl:template>
</xsl:stylesheet>
Файл: convertToXslXml.xslt
то полученный результат
из-под MS Excel выглядит так:
Файл: result.xml