6262
|
|
|
Как VFP-таблицу данных
вставить/извлечь у приложения Word из MS Office 2007 используя структуру данных формата Open XML? |
Содержание:
Часто возникает необходимость экспорта VFP-таблицы
данных в виде таблицы в MS Word. Это можно сделать
многими способами, однако в данной заметки нами будет рассмотрен только один: с
использованием возможностей, появившихся в MS Word
2007. Если вы имеете установленную у Вас серию продуктов из MS Office
2007, Вы наверное обратили внимание на тип файла "Документ
Word (*.docx)",
имеющийся в диалогах "Сохранить [как/Другие форматы]",
расширение которого, как и структура содержащихся в нём данных, отличается от
прежнего doc-формата (документ
Word 97-2003). Этот типа принадлежит к т.н. типам файлов
Office 2007, известных как Office XML Open Format. См. также
статьи Алексея Федорова в разделе: Ссылки по теме.
Ниже приведена таблица новых форматов, поддерживаемых Word
из MS Office 2007 с краткими пояснениями к ним:
Формат |
Расширение |
Описание |
Документ |
DOCX |
Стандартный формат файлов
Office Word 2007 на основе XML. Не сохраняет код VBA-макросов. |
Документ с макросами |
DOCM |
Документ с включением макросов. |
Шаблон документа |
DOTX |
Стандартный формат файлов
шаблонов Office Word 2007. Не сохраняет код VBA-макросов. |
Шаблон (код) |
DOTM |
Формат файлов шаблонов
Office Word 2007, поддерживающий сохранение
макросов. |
На самом деле, любой файл docx-формата представляет собой
zip-файл, содержащий в себе несколько подкаталогов, в которых имеются ряд
файлов с данными в xml(eXtensible Markup Language)-формате. В этом нетрудно
убедиться, если заменить расширение docx-файла на
zip и попробовать разархивировать полученный после
переименования zip-файл с помощью какой-нибудь
утилиты, способной это сделать, например используя WinRAR.exe, а в старших версиях OS Windows это можно
также осуществить и с помощью обычного "Проводника" OS
Windows.
Цель данной заметки заключается в том, чтобы на конкретном примере показать способ
преобразования xml-таблицы данных, в частности полученной
например из
dbf-файла, в docx-файл, используя
XSLT(eXtensible Stylesheet Language Transformations)-преобразования над xml-данными. Нами будет проделано
следующее:
- во-первых, на основе данных из dbf-файла
средствами VFP мы получим соответствующие
xml и xsd файлы.
- во-вторых, подсоединив xsd-схему данных к
MS Word-документу, мы подготовим таблицу-шаблон
для заполнения его xml-данными.
- в-третьих, опираясь на шаблон из пункта выше, мы модифицируем его так,
чтобы получилось XSLT-преобразование для
заполнения содержимого документа нашими исходными xml-данными
из первого пункта, а для выполнения необходимых преобразований при этом, мы будем использовать
XSLT-утилиты msxsl.exe или средств, входящих
в MSXML 4.0 Service Pack 2 (Microsoft XML Core Services),
которые можно свободно и бесплатно загрузить по ссылкам, приведённом мной в разделе:
Ссылки по теме.
В результате мы получим файл с xml-данными,
который может быть использован как данные для MS Word 2007
документа.
- наконец, мы попробуем решить и обратную задачу: извлечь таблицу данных
из MS Word 2007 документа в xml-формате,
а полученные xml-данные преобразовать в
VFP-курсор.
В MS Visual FoxPro начиная с версии 7.0 задача
преобразования данных из dbf-формата в
xml+xsd никакой трудности не представляет и достаточно
лишь воспользоваться функцией CursorToXml() правильно
указав ей параметры. Так, в результате выполнения вот такого кода:
CLEAR
CLOSE TABLES ALL
SET DEFAULT TO (LEFT(SYS(16),
RATC("\",
SYS(16))))
SELECT TOP 5 lastname, firstname,
birthdate, notes ;
FROM
(HOME(2) + 'northwind\employees.dbf')
ORDER BY lastname, firstname INTO CURSOR
employee
IF USED('employee')
SET SAFETY OFF
?CURSORTOXML('employee',
'employee', 1, 512+8+48, 0, 'employee')
SET SAFETY ON
ELSE
ERROR 101, 'Not found file: ' +
LOWER(HOME(2) + 'northwind\employees.dbf')
ENDIF
CLOSE TABLES ALL
CLOSE DATABASES ALL
Получаем вот такой файл employee.XML:
<?xml
version
= "1.0"
encoding="UTF-8"
standalone="yes"?>
<VFPData
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="employee.XSD">
<employee>
<lastname>Buchanan</lastname>
<firstname>Steven</firstname>
<birthdate>1955-03-04</birthdate>
<notes><![CDATA[Steven
Buchanan graduated from St. Andrews University, Scotland, with a BSC degree.
Upon joining the company as a sales representative, he spent 6 months in an
orientation program at the Seattle office and then returned to his permanent
post in London, where he was promoted to sales manager. Mr. Buchanan has
completed the courses "Successful Telemarketing" and "International Sales
Management." He is fluent in French.]]></notes>
</employee>
<employee>
<lastname>Callahan</lastname>
<firstname>Laura</firstname>
<birthdate>1958-01-09</birthdate>
<notes><![CDATA[Laura
received a BA in psychology from the University of Washington. She has also
completed a course in business French. She reads and writes French.]]></notes>
</employee>
<employee>
<lastname>Davolio</lastname>
<firstname>Nancy</firstname>
<birthdate>1968-12-08</birthdate>
<notes><![CDATA[Education
includes a BA in psychology from Colorado State University. She also completed
"The Art of the Cold Call." Nancy is a member of Toastmasters International.]]></notes>
</employee>
<employee>
<lastname>Dodsworth</lastname>
<firstname>Anne</firstname>
<birthdate>1969-07-02</birthdate>
<notes><![CDATA[Anne
has a BA degree in English from St. Lawrence College. She is fluent in French
and German.]]></notes>
</employee>
<employee>
<lastname>Fuller</lastname>
<firstname>Andrew</firstname>
<birthdate>1952-02-19</birthdate>
<notes><![CDATA[Andrew
received his BTS commercial and a Ph.D. in international marketing from the
University of Dallas. He is fluent in French and Italian and reads German. He
joined the company as a sales representative, was promoted to sales manager and
was then named vice president of sales. Andrew is a member of the Sales
Management Roundtable, the Seattle Chamber of Commerce, and the Pacific Rim
Importers Association.]]></notes>
</employee>
</VFPData>
а также файл employee.XSD:
<?xml
version
=
"1.0"
encoding="UTF-8"
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="employee"
minOccurs="0"
maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element
name="lastname">
<xsd:simpleType>
<xsd:restriction
base="xsd:string">
<xsd:maxLength
value="20"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element
name="firstname">
<xsd:simpleType>
<xsd:restriction
base="xsd:string">
<xsd:maxLength
value="10"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element
name="birthdate"
type="xsd:date"
minOccurs="0"/>
<xsd:element
name="notes"
minOccurs="0">
<xsd:simpleType>
<xsd:restriction
base="xsd:string">
<xsd:maxLength
value="2147483647"/>
</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>
содержащий схему для него. В полученную схему следует внести небольшие
изменения с тем, чтобы мы имели возможность делать вставку произвольного текста
в Word документ и этот текст не противоречил бы нашей
схеме. Для этого добавьте атрибут mixed со значением
true для первых двух элементов <xsl:complexType> так как показано/выделено в фрагменте файла
employee.XSD ниже:
<?xml
version
=
"1.0"
encoding="UTF-8"
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
mixed="true">
<xsd:choice
mxOccurs="unbounded">
<xsd:element
nme="employee"
minOccurs="0"
maxOccurs="unbounded">
<xsd:complexType
mixed="true">
<xsd:sequence>
<xsd:element
name="lastname">
...
Далее, создадим на основе данных этих двух файлов таблицу в
MS Word из MS Office 2007.
Ниже шаг за шагом и т.с. в картинках показано, как это можно сделать. Но прежде
всего, если у Вас на "Панели
быстрого доступа" отсутствует вкладка "Разработчик", то проделайте действия,
изображённые на Рис.1:
Рис.1
т.е. отметьте пункт "Показывать вкладку "Разработчик" на ленте" в диалоге
"Параметры Excel" и подтвердите изменения, нажав
кнопку "Ok" в правой нижней части окна этого диалога.
Следующим действием загрузим файл-схему employee.XSD
в MS Word 2007, для чего открыв новый
Word-документ, выполним следующее:
Рис.2
В возникшем при этом диалоге "Параметры схемы", определите поля:
URI и Псевдоним так, как показано на картинке ниже и подтвердите установку
схемы.
Рис.3
После удачной установки схемы в правой части окна у Вас должна появиться
панель "Структура XML" подобная также изображенная на картинке
ниже. Введите текст в качестве заголовка всего документа, выделите его весь мышкой и
примените к нему всю схему, ткнув мышкой в нижней части панели "Структура
XML" в текст
"VFPData {Schema Employee from VFP}" подобно тому, как это сделано на
картинке ниже:
Рис.4
В возникшем при этом диалоге "Применить ко всему документу?" подтвердите
выбором кнопки "Применить ко всему документу". После чего у Вас должно
получиться нечто похожее представленному на рисунке ниже:
Рис.5
Перемесив позицию курсора к началу конечного элемента
VFPData, нажмите мышкой на элемент employee в
нижней части панели "Структура XML". Поле чего Вы
должны получить то, что показано на рисунке ниже:
Рис.6
Последовательно выбирая мышкой подчинённые элементы:
lastname, firstname, birthdate, notes у элемента
employee, в нижней части панели "Структура XML"
и после ввода очередного элемента, перемещая текущую позицию в документе к
началу конечного элемента employee, Вы должны получить
подобное тому, что изображено на картинке ниже:
Рис.7
Следующим шагом, мы должны создать таблицу таким образом, чтобы элемент
employee с вложенными в него элементами разместился бы
в первой строке создаваемой нами таблицы, а каждый из вложенных элементом попал
бы в отведённый ему столбец таблицы. Строку заголовков столбцов таблицы при этом,
следует поместить сразу после заголовка документа, но перед элементом
employee. Проделав это, Вы должны получить подобное
тому, что показано на картинке ниже:
Рис.8
К этому же результату можно придти, если сначала создать таблицу с
заголовками столбцов и одной пустой строкой в качестве её данных, а затем,
выделив всю строку данных в таблице, связать с ней групповой элемент
employee. Далее, последовательно смещаясь по столбцам,
связать с каждым из них элементы, принадлежащие групповому элементу
employee: lastname, firstname, birthdate
и birthdate.
Чтобы придать полученной таблице презентабельный вид, можно воспользоваться
одним из стилей таблиц, например таким:
Рис.9
Наконец, полученный результат сохраняем как MS Word 2007
документ:
Рис.10
Следующим шагом нам нужно извлечь xml-данные в
формате Open XML у полученного документа. Это можно
сделать
- либо сделав копию полученного документа с расширением .zip и далее воспользоваться возможностями одной из утилит работы с
zip-архивами данных,
- либо, если у Вас имеется
установленный WinRAR.exe, то создать командный файл
getContentsUseWinRAR.cmd,
подобный следующему:
@echo off
mode con: cp select=1251 > _tmp.txt
"C:\Program Files\WinRAR\WinRAR.exe" X -ad -ibck -o+ "Пример VFP-таблицы в виде MS Word 2007.docx"
del _tmp.txt
Файл: getContentsUseWinRAR.cmd
и выполнить его из того каталога, куда Вы только что сохранили файл "Пример
VFP-таблицы в виде MS Word 2007.docx". В данном случае, нас будет интересовать только файл
document.xml из
подкаталога word:
Рис.11
Вот каким он получился у меня:
<?xml
version="1.0"
encoding="UTF-8"
standalone="yes"?>
<w:document
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
<w:body>
<w:customXml
w:uri="Schema_Employee"
w:element="VFPData">
<w:p
w:rsidR="00883633"
w:rsidRPr="00AF60CD"
w:rsidRDefault="00883633"
w:rsidP="00AF60CD">
<w:pPr>
<w:jc
w:val="center"/>
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
</w:rPr>
</w:pPr>
<w:r
w:rsidRPr="00883633">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
</w:rPr>
<w:t>Пример</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
</w:rPr>
<w:t
xml:space="preserve">
</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
<w:lang
w:val="en-US"/>
</w:rPr>
<w:t>VFP</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
</w:rPr>
<w:t>-</w:t>
</w:r>
<w:r
w:rsidRPr="00883633">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
</w:rPr>
<w:t
xml:space="preserve">таблицы в виде таблицы в </w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
<w:lang
w:val="en-US"/>
</w:rPr>
<w:t>MS</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD"
w:rsidRPr="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
</w:rPr>
<w:t
xml:space="preserve">
</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
<w:lang
w:val="en-US"/>
</w:rPr>
<w:t>Word</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD"
w:rsidRPr="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
</w:rPr>
<w:t
xml:space="preserve">
2007</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"/>
</w:rPr>
<w:t>.</w:t>
</w:r>
</w:p>
<w:tbl>
<w:tblPr>
<w:tblStyle
w:val="1-1"/>
<w:tblW
w:w="0"
w:type="auto"/>
<w:tblLook
w:val="04A0"/>
</w:tblPr>
<w:tblGrid>
<w:gridCol
w:w="2392"/>
<w:gridCol
w:w="2393"/>
<w:gridCol
w:w="2393"/>
<w:gridCol
w:w="2393"/>
</w:tblGrid>
<w:tr
w:rsidR="00174F3F"
w:rsidTr="00174F3F">
<w:trPr>
<w:cnfStyle
w:val="100000000000"/>
</w:trPr>
<w:tc>
<w:tcPr>
<w:cnfStyle
w:val="001000000000"/>
<w:tcW
w:w="2392"
w:type="dxa"/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="007F26DB"
w:rsidRDefault="007F26DB"
w:rsidP="00174F3F">
<w:r>
<w:t>Фамилия</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="100000000000"/>
</w:pPr>
<w:r>
<w:t>Имя</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="100000000000"/>
</w:pPr>
<w:r>
<w:t>День рождения</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="100000000000"/>
</w:pPr>
<w:r>
<w:t>Комментарий</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<w:customXml
w:element="employee">
<w:tr
w:rsidR="00174F3F"
w:rsidTr="00174F3F">
<w:trPr>
<w:cnfStyle
w:val="000000100000"/>
</w:trPr>
<w:customXml
w:element="lastname">
<w:tc>
<w:tcPr>
<w:cnfStyle
w:val="001000000000"/>
<w:tcW
w:w="2392"
w:type="dxa"/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:rPr>
<w:lang
w:val="en-US"/>
</w:rPr>
</w:pPr>
</w:p>
</w:tc>
</w:customXml>
<w:customXml
w:element="firstname">
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="000000100000"/>
<w:rPr>
<w:lang
w:val="en-US"/>
</w:rPr>
</w:pPr>
</w:p>
</w:tc>
</w:customXml>
<w:customXml
w:element="birthdate">
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="000000100000"/>
<w:rPr>
<w:lang
w:val="en-US"/>
</w:rPr>
</w:pPr>
</w:p>
</w:tc>
</w:customXml>
<w:customXml
w:element="notes">
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="000000100000"/>
<w:rPr>
<w:lang
w:val="en-US"/>
</w:rPr>
</w:pPr>
</w:p>
</w:tc>
</w:customXml>
</w:tr>
</w:customXml>
</w:tbl>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="006C46B9"
w:rsidP="00174F3F">
<w:pPr>
<w:rPr>
<w:lang
w:val="en-US"/>
</w:rPr>
</w:pPr>
</w:p>
</w:customXml>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00883633"
w:rsidRDefault="00174F3F"/>
<w:sectPr
w:rsidR="00174F3F"
w:rsidRPr="00883633"
w:rsidSect="00EC3577">
<w:pgSz
w:w="11906"
w:h="16838"/>
<w:pgMar
w:top="1134"
w:right="850"
w:bottom="1134"
w:left="1701"
w:header="708"
w:footer="708"
w:gutter="0"/>
<w:cols
w:space="708"/>
<w:docGrid
w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>
Файл: document.xml
Здесь мной выделены начальные/конечные теги элементов
w:customXml, относящиеся к соответствующим элементам XSD-схемы.
Обратите внимание, что содержимое MS Word-документа,
отвечающее определённому элементу схемы, располагается внутри соответствующего
w:customXml элемента. Так тег параграфа, включающего в
себя заголовок для всего документа, следует сразу за начальным элементом
<w:customXml w:uri="Schema_Employee" w:element="VFPData">,
отвечающим корневому элементу XSD-схемы. Строка данных
таблицы, расположенная в элементе <w:tr>...</w:tr>,
следует сразу за групповым элементом <w:customXml
w:element="employee">, наконец любой столбец таблицы, содержащий данные
таблицы (т.е. любой элемент <w:tc>...</w:tc>),
располагается внутри соответствующего w:customXml-элемента,
отвечающего соответствующему элементу данных в XSD-схеме.
Экспериментируя с MS Word-документом при
подготовке шаблона документа, созданного на основе XSD-схемы,
я обнаружил, что это правило выполняется не всегда, а возможны случаи, когда
w:customXml-элемент оказывается "пустым" и в таких
случаях, он располагается в том месте MS Word-документа,
где должен бы быть расположен текст непосредственно, точнее элемент
<w:r>...<w:t>...</w:t></w:r>, как в фрагменте
xml-документа, приведённого ниже:
...
<w:customXml
w:element="employee">
<w:tr
w:rsidR="00883633"
w:rsidRPr="00883633"
w:rsidTr="00883633">
<w:trPr>
<w:cnfStyle
w:val="000000100000"
/>
</w:trPr>
<w:tc>
<w:tcPr>
<w:cnfStyle
w:val="001000000000"
/>
<w:tcW
w:w="2392"
w:type="dxa"
/>
</w:tcPr>
<w:p
w:rsidR="00883633"
w:rsidRPr="00883633"
w:rsidRDefault="00883633"
w:rsidP="00883633">
<w:customXml
w:element="lastname"
/>
</w:p>
</w:tc>
...
Здесь w:customXml-элемент, отвечающий полю данных
lastname, расположен не снаружи элемента
<w:tc>...</w:tc>, а внутри его, точнее внутри его
элемента <w:p>.
Выше шаблон документа (word\document.htm)
был получен путём прямого редактирования из среды MS
Word 2007. Однако, можно написать XSLT-преобразование,
способное для заданной xsd-схемы динамически создать
подобный шаблон. У меня это получилось так:
<?xml
version="1.0"
encoding="UTF-8"
standalone="yes"?>
<!-- file: genXsltTableTemplate.xslt
-->
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
exclude-result-prefixes="xsi
msxsl xsd msdata">
<xsl:output
version="1.0"
method="xml"
encoding="utf-8"
standalone="yes"
/>
<xsl:param
name="prmSchemaUri">
<xsl:text>Schema_Employee</xsl:text>
</xsl:param>
<xsl:param
name="prmSchemaElement">
<xsl:text>VFPData</xsl:text>
</xsl:param>
<xsl:param
name="prmTblStyle">
<xsl:text>1-1</xsl:text>
</xsl:param>
<xsl:param
name="prmTblType">
<xsl:text>auto</xsl:text>
</xsl:param>
<xsl:param
name="prmTblLook">
<xsl:text>04A0</xsl:text>
</xsl:param>
<xsl:param
name="prmTblColTwipsMeasure">
<xsl:text>2393</xsl:text>
</xsl:param>
<xsl:param
name="prmTblRsid">
<xsl:text>00174F3F</xsl:text>
</xsl:param>
<xsl:param
name="prmTblRStyle">
<xsl:text>100000000000</xsl:text>
</xsl:param>
<xsl:param
name="prmTblCStyle">
<xsl:text>001000000000</xsl:text>
</xsl:param>
<xsl:param
name="prmTblCType">
<xsl:text>dxa</xsl:text>
</xsl:param>
<xsl:param
name="prmTblColumns">
<tblColumns>
<tbl
SchemaUri="Schema_Employee">
<tblCol>
<header>Фамилия</header>
<width>2391</width>
<lang>en-US</lang>
</tblCol>
<tblCol>
<header>Имя</header>
<width>2392</width>
<lang>en-US</lang>
</tblCol>
<tblCol>
<header>День рождения</header>
<width>2393</width>
<lang>ru-RU</lang>
</tblCol>
<tblCol>
<header>Комментарий</header>
<width>2394</width>
<lang>en-US</lang>
</tblCol>
</tbl>
<!-- ...
-->
</tblColumns>
</xsl:param>
<xsl:template
match="/">
<w:document
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
<w:body>
<xsl:call-template
name="genTable">
<xsl:with-param
name="prmSchemaUri"
select="$prmSchemaUri"
/>
<xsl:with-param
name="prmSchemaElement"
select="$prmSchemaElement"
/>
<xsl:with-param
name="prmTblStyle"
select="$prmTblStyle"
/>
<xsl:with-param
name="prmTblType"
select="$prmTblType"
/>
<xsl:with-param
name="prmTblLook"
select="$prmTblLook"
/>
<xsl:with-param
name="prmTblRStyle"
select="$prmTblRStyle"
/>
<xsl:with-param
name="prmTblCStyle"
select="$prmTblCStyle"
/>
<xsl:with-param
name="prmTblCType"
select="$prmTblCType"
/>
</xsl:call-template>
<w:sectPr
w:rsidR="00174F3F"
w:rsidRPr="00883633"
w:rsidSect="00EC3577">
<w:pgSz
w:w="11906"
w:h="16838"/>
<w:pgMar
w:top="1134"
w:right="850"
w:bottom="1134"
w:left="1701"
w:header="708"
w:footer="708"
w:gutter="0"/>
<w:cols
w:space="708"/>
<w:docGrid
w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>
</xsl:template>
<xsl:template
name="genTable">
<xsl:param
name="prmSchemaUri"
/>
<xsl:param
name="prmSchemaElement"
/>
<xsl:param
name="prmTblStyle"
/>
<xsl:param
name="prmTblType"
/>
<xsl:param
name="prmTblLook"
/>
<xsl:param
name="prmTblRStyle"
/>
<xsl:param
name="prmTblCStyle"
/>
<xsl:param
name="prmTblCType"
/>
<w:customXml
w:uri="{$prmSchemaUri}"
w:element="{$prmSchemaElement}">
<w:p
w:rsidR="{$prmTblRsid}"
w:rsidRDefault="{$prmTblRsid}"/>
<w:tbl>
<w:tblPr>
<w:tblStyle
w:val="{$prmTblStyle}"/>
<w:tblW
w:w="0"
w:type="{$prmTblType}"/>
<w:tblLook
w:val="{$prmTblLook}"/>
</w:tblPr>
<w:tblGrid>
<!-- Generate set of <w:gridCol ...>
elements-->
<xsl:for-each
select="/.//xsd:schema/xsd:element[@name=$prmSchemaElement]//xsd:sequence/*">
<xsl:variable
name="curPos">
<xsl:value-of
select="position()"/>
</xsl:variable>
<xsl:variable
name="curTblCol">
<xsl:copy-of
select="msxsl:node-set($prmTblColumns)/./tblColumns/tbl[@SchemaUri=$prmSchemaUri]/tblCol[number($curPos)]"/>
</xsl:variable>
<!-- <w:gridCol w:w="{$prmTblColTwipsMeasure}"/>
-->
<w:gridCol
w:w="{msxsl:node-set($curTblCol)/./tblCol/width}"/>
</xsl:for-each>
</w:tblGrid>
<w:tr
w:rsidR="{$prmTblRsid}"
w:rsidTr="{$prmTblRsid}">
<w:trPr>
<w:cnfStyle
w:val="{$prmTblRStyle}"/>
</w:trPr>
<!-- Generate set of <w:tc>...<w:tc>
elements as columns header -->
<xsl:for-each
select="/.//xsd:schema/xsd:element[@name=$prmSchemaElement]//xsd:sequence/*">
<xsl:variable
name="curPos">
<xsl:value-of
select="position()"
/>
</xsl:variable>
<xsl:variable
name="curTblCol">
<xsl:copy-of
select="msxsl:node-set($prmTblColumns)/./tblColumns/tbl[@SchemaUri=$prmSchemaUri]/tblCol[number($curPos)]"
/>
</xsl:variable>
<xsl:variable
name="curTblColWidth">
<xsl:value-of
select="msxsl:node-set($curTblCol)/./tblCol/width"
/>
</xsl:variable>
<w:tc>
<w:tcPr>
<!-- <w:tcW w:w="{$prmTblColTwipsMeasure}"
w:type="{$prmTblCType}"/> -->
<w:tcW
w:w="{$curTblColWidth}"
w:type="{$prmTblCType}"/>
</w:tcPr>
<w:p
w:rsidR="{$prmTblRsid}"
w:rsidRPr="{$prmTblRsid}"
w:rsidRDefault="{$prmTblRsid}"
w:rsidP="{$prmTblRsid}">
<w:pPr>
<w:cnfStyle
w:val="{$prmTblCStyle}"/>
</w:pPr>
<w:r>
<w:t>
<!-- <xsl:value-of select="@name"/>
-->
<xsl:value-of
select="msxsl:node-set($curTblCol)/./tblCol/header"/>
</w:t>
</w:r>
</w:p>
</w:tc>
</xsl:for-each>
</w:tr>
<!-- Generate group element
<w:customXml...>...</w:customXml> -->
<w:customXml
w:element="{/.//xsd:schema/xsd:element[@name=$prmSchemaElement]//xsd:element/@name}">
<w:tr
w:rsidR="{$prmTblRsid}"
w:rsidTr="{$prmTblRsid}">
<w:trPr>
<w:cnfStyle
w:val="{$prmTblRStyle}"/>
</w:trPr>
<!-- Generate set of
<w:customXml...><w:tc>...<w:tc></w:customXml> elements for columns data
-->
<xsl:for-each
select="/.//xsd:schema/xsd:element[@name=$prmSchemaElement]//xsd:sequence/*">
<xsl:variable
name="curPos">
<xsl:value-of
select="position()"/>
</xsl:variable>
<xsl:variable
name="curTblCol">
<xsl:copy-of
select="msxsl:node-set($prmTblColumns)/./tblColumns/tbl[@SchemaUri=$prmSchemaUri]/tblCol[number($curPos)]"/>
</xsl:variable>
<xsl:variable
name="curTblColWidth">
<xsl:value-of
select="msxsl:node-set($curTblCol)/./tblCol/width"
/>
</xsl:variable>
<w:customXml
w:element="{@name}">
<w:tc>
<w:tcPr>
<!-- <w:tcW
w:w="{$prmTblColTwipsMeasure}" w:type="{$prmTblCType}"/>
-->
<w:tcW
w:w="{$curTblColWidth}"
w:type="{$prmTblCType}"/>
</w:tcPr>
<w:p
w:rsidR="{$prmTblRsid}"
w:rsidRDefault="{$prmTblRsid}"
w:rsidP="{$prmTblRsid}">
<w:pPr>
<w:cnfStyle
w:val="{$prmTblCStyle}"/>
<w:rPr>
<w:lang
w:val="{msxsl:node-set($curTblCol)/./tblCol/lang}"/>
</w:rPr>
</w:pPr>
</w:p>
</w:tc>
</w:customXml>
</xsl:for-each>
</w:tr>
</w:customXml>
</w:tbl>
<w:p
w:rsidR="{$prmTblRsid}"
w:rsidRDefault="{$prmTblRsid}"/>
</w:customXml>
</xsl:template>
</xsl:stylesheet>
Файл: genXsltTableTemplate.xslt
Здесь параметр prmTblColumns для каждой схемы
содержит список дополнительных параметров для колонок таблиц, Вы можете
расширить его по своему усмотрению добавив соответствующий код в шаблон
genTable. Применив этот шаблон к
xsd-схеме employee.xsd, используя командный
файл:
msxsl.exe employee.XSD ../XSLT/genXsltTableTemplate.xslt -o documentTableTemplate.xml -u '4.0'
Файл: documentTableTemplate.cmd
мы получим файл documentTableTemplate.xml,
представляющий из себя заготовку для MS Word 2007 шаблона таблицы, схема к которой была указана в качестве первого параметра у утилиты
msxsl.exe.
Далее нам потребуется возможность из папки, содержащей в себе подпапки с
файлами, создавать zip-архив (точнее
docx-файл). Если у Вас установлен
WinRAR.exe, то это можно сделать, поместив вот такую
последовательности DOS-команд:
@echo off
mode con: cp select=1251 > _tmp.txt
"C:\Program Files\WinRAR\WinRAR.exe" a -r -ibck -o+ -y -ep1 "Пример VFP-таблицы в виде MS Word 2007.zip" "Пример VFP-таблицы в виде MS Word 2007\*.*"
if not exist "Пример VFP-таблицы в виде MS Word 2007.docx" goto cont
del "Пример VFP-таблицы в виде MS Word 2007.docx"
:cont
rename "Пример VFP-таблицы в виде MS Word 2007.zip" "Пример VFP-таблицы в виде MS Word 2007.docx"
del _tmp.txt
Файл: fromContentsToDocxUseWinRAR.cmd
в командный файл fromContentsToDocxUseWinRAR.cmd
в подкаталог ../DOCX/.
Теперь переименовав полученный после выполнения
documentTableTemplate.cmd файл documentTableTemplate.xml
на document.xml (в подкаталоге
XML), подменив им
файл word\document.xml (в подкаталоге
..\DOCX\Пример VFP-таблицы в виде MS Word 2007\),
получив соответствующий docx-файл (выполнением
fromContentsToDocxUseWinRAR.cmd) и открыв его в MS Word
2007, я наблюдаю следующую картинку:
Как видим, здесь всё в полом соответствии с нашими желаниями. Таким образом,
если у нас имеется несколько таблиц с разными xsd-схемами,
то мы вполне можем создавать docx-шаблон, содержащий
их программно, используя шаблон genTable из файла
genXsltTableTemplate.xslt. Только нужно иметь ввиду, что
- соответствующие xsd-схемы
должны быть предварительно зарегистрированы в библиотеке схем (см. под ключом
HKEY_CURRENT_USER\Software\Microsoft\Schema Library\.. системного реестра).
- информация об используемых в документе xsd-схемах
в части документа word\settings.xml в рамках содержащихся там элементов,
подобных: <w:attachedSchema w:val="Schema_Employee"/>, должна быть
корректной. Т.е. с помощью подобных элементов должны быть перечислены все
xsd-схемы, используемые в документе.
Сколько-то разобравшись с содержимым файла document.xml,
каковы же будут наши дальнейшие действия? Наша цель на данном этапе: это на
основе приведённого выше document.xml, создать файл
xslt-преобразования, которое получив бы на входе
данные из файла employee.xml, возвратило бы в качестве
результата преобразований xml-документ, подобный файлу
document.xml, но уже не с пустой строкой в данных у
таблицы, а с заполненными всеми строками данных таблицы, взяв их из файла
employee.xml. Это можно сделать
- либо прямым редактирование файла docement.xml,
- либо создать дополнительное xslt-преобразование,
позволяющее преобразовать файл, подобный файлу
document.xml, в файл соответствующего ему xslt-преобразования.
Такая работа мной проделана и ниже xslt-файл (getXmlDocument.xslt),
представляющий результат этого дополнительного xslt-преобразования:
<?xml
version="1.0"
encoding="utf-8"
standalone="yes"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
exclude-result-prefixes="xsi
msxsl msdata">
<xsl:output
version="1.0"
method="xml"
encoding="utf-8"
standalone="yes"
/>
<xsl:template
match="/">
<w:document
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
<w:body>
<w:customXml
w:uri="Schema_Employee"
w:element="VFPData">
<w:p
w:rsidR="00883633"
w:rsidRPr="00AF60CD"
w:rsidRDefault="00883633"
w:rsidP="00AF60CD">
<w:pPr>
<w:jc
w:val="center"></w:jc>
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
</w:rPr>
</w:pPr>
<w:r
w:rsidRPr="00883633">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
</w:rPr>
<w:t>Пример</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
</w:rPr>
<w:t
xml:space="preserve">
</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
<w:lang
w:val="en-US"></w:lang>
</w:rPr>
<w:t>VFP</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
</w:rPr>
<w:t>-</w:t>
</w:r>
<w:r
w:rsidRPr="00883633">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
</w:rPr>
<w:t
xml:space="preserve">таблицы
в виде таблицы в </w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
<w:lang
w:val="en-US"></w:lang>
</w:rPr>
<w:t>MS</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD"
w:rsidRPr="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
</w:rPr>
<w:t
xml:space="preserve">
</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
<w:lang
w:val="en-US"></w:lang>
</w:rPr>
<w:t>Word</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD"
w:rsidRPr="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
</w:rPr>
<w:t
xml:space="preserve">
2007</w:t>
</w:r>
<w:r
w:rsidR="00AF60CD">
<w:rPr>
<w:rFonts
w:asciiTheme="majorHAnsi"
w:hAnsiTheme="majorHAnsi"></w:rFonts>
</w:rPr>
<w:t>.</w:t>
</w:r>
</w:p>
<w:tbl>
<w:tblPr>
<w:tblStyle
w:val="1-1"></w:tblStyle>
<w:tblW
w:w="0"
w:type="auto"></w:tblW>
<w:tblLook
w:val="04A0"></w:tblLook>
</w:tblPr>
<w:tblGrid>
<w:gridCol
w:w="2392"></w:gridCol>
<w:gridCol
w:w="2393"></w:gridCol>
<w:gridCol
w:w="2393"></w:gridCol>
<w:gridCol
w:w="2393"></w:gridCol>
</w:tblGrid>
<w:tr
w:rsidR="00174F3F"
w:rsidTr="00174F3F">
<w:trPr>
<w:cnfStyle
w:val="100000000000"></w:cnfStyle>
</w:trPr>
<w:tc>
<w:tcPr>
<w:cnfStyle
w:val="001000000000"></w:cnfStyle>
<w:tcW
w:w="2392"
w:type="dxa"></w:tcW>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:proofErr
w:type="spellStart"></w:proofErr>
<w:r>
<w:rPr>
<w:lang
w:val="en-US"></w:lang>
</w:rPr>
<w:t>Фамилия</w:t>
</w:r>
<w:proofErr
w:type="spellEnd"></w:proofErr>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"></w:tcW>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="100000000000"></w:cnfStyle>
</w:pPr>
<w:r>
<w:t>Имя</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"></w:tcW>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="100000000000"></w:cnfStyle>
</w:pPr>
<w:r>
<w:t>День
рождения</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"></w:tcW>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="100000000000"></w:cnfStyle>
</w:pPr>
<w:r>
<w:t>Комментарий</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<xsl:for-each
select="//VFPData/employee">
<w:customXml
w:element="employee">
<w:tr
w:rsidR="00174F3F"
w:rsidTr="00174F3F">
<w:trPr>
<w:cnfStyle
w:val="000000100000"
/>
</w:trPr>
<w:customXml
w:element="lastname">
<w:tc>
<w:tcPr>
<w:cnfStyle
w:val="001000000000"
/>
<w:tcW
w:w="2392"
w:type="dxa"
/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:rPr>
<w:lang
w:val="en-US"
/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:lang
w:val="en-US"
/>
</w:rPr>
<w:t>
<xsl:value-of
select="lastname"
/>
</w:t>
</w:r>
</w:p>
</w:tc>
</w:customXml>
<w:customXml
w:element="firstname">
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"
/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="000000100000"
/>
<w:rPr>
<w:lang
w:val="en-US"
/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:lang
w:val="en-US"
/>
</w:rPr>
<w:t>
<xsl:value-of
select="firstname"
/>
</w:t>
</w:r>
</w:p>
</w:tc>
</w:customXml>
<w:customXml
w:element="birthdate">
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"
/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="000000100000"
/>
<w:rPr>
<w:lang
w:val="en-US"
/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:lang
w:val="ru-RU"
/>
</w:rPr>
<w:t>
<xsl:value-of
select="msxsl:format-date(birthdate,
'dd.MM.yyyy')" />
</w:t>
</w:r>
</w:p>
</w:tc>
</w:customXml>
<w:customXml
w:element="notes">
<w:tc>
<w:tcPr>
<w:tcW
w:w="2393"
w:type="dxa"
/>
</w:tcPr>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:cnfStyle
w:val="000000100000"
/>
<w:rPr>
<w:lang
w:val="en-US"
/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:lang
w:val="en-US"
/>
</w:rPr>
<w:t>
<xsl:choose>
<xsl:when
test="string-length(notes)<51">
<xsl:value-of
select="notes"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="concat(substring(notes,1,50),
substring-before(substring(notes,51), ' '), ' ...')"
/>
</xsl:otherwise>
</xsl:choose>
</w:t>
</w:r>
</w:p>
</w:tc>
</w:customXml>
</w:tr>
</w:customXml>
</xsl:for-each>
</w:tbl>
<w:p
w:rsidR="00174F3F"
w:rsidRDefault="00174F3F"
w:rsidP="00174F3F">
<w:pPr>
<w:rPr>
<w:lang
w:val="en-US"></w:lang>
</w:rPr>
</w:pPr>
</w:p>
</w:customXml>
<w:p
w:rsidR="00174F3F"
w:rsidRPr="00883633"
w:rsidRDefault="00174F3F"></w:p>
<w:sectPr
w:rsidR="00174F3F"
w:rsidRPr="00883633"
w:rsidSect="00EC3577">
<w:pgSz
w:w="11906"
w:h="16838"></w:pgSz>
<w:pgMar
w:top="1134"
w:right="850"
w:bottom="1134"
w:left="1701"
w:header="708"
w:footer="708"
w:gutter="0"></w:pgMar>
<w:cols
w:space="708"></w:cols>
<w:docGrid
w:linePitch="360"></w:docGrid>
</w:sectPr>
</w:body>
</w:document>
</xsl:template>
</xsl:stylesheet>
Файл: getXmlDocument.xslt
т.е. именно в подобный xslt-файл следовало бы
превратить наш исходный файл-шаблон document.xml путём
его редактирования. Здесь добавленные элементы слегка оттенены фоном и пояснения
к добавлениям следующие:
- создан стандартный для xslt-преобразований
корневой элемент <xsl:stylesheet ... >, которому
наряду с namespace-ами, обеспечивающими работу
xslt-преобразований, добавлены все
namespace-ы из элемента
<w:document ...> нашего файла-шаблона document.xml
- корневой элемент <w:document>...<w:document>
из document.xml погружён в
xslt-конструкцию <xsl:template match="/">...</xsl:template>
для обработки корневого элемента.
- перед групповым элементом <w:customXml
w:element="employee"> организован цикл по всем элементам
employee входных данных: <xsl:for-each
select="//VFPData/employee">...</xsl:for-each> файла
employee.xml
- а в каждый элемент
<w:tc>...</w:tc> столбца, точнее в конец вложенного в него элемента
<w:p>, сделана вставка элемента
<w:r>...</w:r> во вложенный элемент <w:t>...</w:t>
которого, осуществляется вставка соответствующих значений элементов данных
из employee.xml, возможно с небольшими
преобразованиями, специфичными для конкретного элемента данных. Имея ввиду,
что xslt-вычисления будут происходить внутри цикла
<xsl:for-each select="//VFPData/employee">...
Код же этого дополнительного xslt-преобразования, в
результате применений которого к файлу-шаблону document.xml,
был получен вышеприведённый результат следующий:
<?xml
version="1.0"
encoding="utf-8"?>
<!-- file: genXsltFromTemplate.xslt
-->
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
exclude-result-prefixes="xsi
msxsl msdata">
<xsl:output
version="1.0"
method="xml"
encoding="utf-8"
omit-xml-declaration="yes"
/>
<!-- Root element in xml-data
-->
<xsl:param
name="prmRootElement">
<xsl:text>VFPData</xsl:text>
</xsl:param>
<!-- Current encoding to replace in
output xml-PI -->
<xsl:param
name="prmEncoding">
<xsl:text>utf-8</xsl:text>
</xsl:param>
<xsl:template
match="/">
<xsl:text
disable-output-escaping="yes"><![CDATA[<?xml
version="1.0" encoding="]]></xsl:text>
<xsl:value-of
select="$prmEncoding"
/>
<xsl:text
disable-output-escaping="yes"><![CDATA["
standalone="yes"?>]]>
</xsl:text>
<!-- Generate open <xsl:stylesheet... &
<xsl:template... elements into output -->
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:stylesheet
version="1.0"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:msxsl="urn:schemas-microsoft-com:xslt"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:o="urn:schemas-microsoft-com:office:office"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:v="urn:schemas-microsoft-com:vml"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:w10="urn:schemas-microsoft-com:office:word"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[
exclude-result-prefixes="xsi msxsl msdata">]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:output
version="1.0" method="xml" encoding="utf-8" standalone="yes" />]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:template
match="/">]]></xsl:text>
<!-- Open w:document element
-->
<w:document
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
<!-- ... continue parsing for
children's
of w:document element ... -->
<xsl:apply-templates
select="/./w:document/*"
/>
<!-- Close w:document element
-->
</w:document>
<!-- Generate close ... </xsl:template>
& ...</xsl:stylesheet> elements into output
-->
<xsl:text
disable-output-escaping="yes"><![CDATA[</xsl:template>]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[</xsl:stylesheet>]]></xsl:text>
</xsl:template>
<!-- Parsing any current input...
-->
<xsl:template
match="@*|node()">
<!-- Get current name from input
-->
<xsl:variable
name="varName">
<xsl:value-of
select="name()"
/>
</xsl:variable>
<xsl:choose>
<xsl:when
test="string-length($varName)>0">
<xsl:choose>
<xsl:when
test="$varName='w:customXml'">
<!-- Parsing
<w:customXml...>...</w:customXml> -->
<xsl:call-template
name="genCustomXml">
<xsl:with-param
name="prmElementName"
select="@w:element"
/>
<xsl:with-param
name="prmChildren"
select="*"
/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- Parsing any other...
-->
<!-- ... copy current element into
output-->
<xsl:element
name="{$varName}">
<xsl:for-each
select="@*">
<xsl:attribute
name="{name()}">
<xsl:value-of
select="."
/>
</xsl:attribute>
</xsl:for-each>
<!-- ... continue parsing for child &
as child ... -->
<xsl:apply-templates
select="child::*"
/>
<xsl:value-of
select="text()"
/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates
select="@*|node()"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Generate w:customXml element
-->
<xsl:template
name="genCustomXml">
<xsl:param
name="prmElementName"/>
<xsl:param
name="prmChildren"/>
<!-- Get current name of element from
input -->
<xsl:variable
name="varName">
<xsl:value-of
select="name()"
/>
</xsl:variable>
<xsl:choose>
<xsl:when
test="$prmElementName=$prmRootElement">
<!-- Case w:customXml element is root
-->
<!-- ... copy current element
(w:customXml) -->
<xsl:element
name="{$varName}">
<xsl:for-each
select="@*">
<xsl:attribute
name="{name()}">
<xsl:value-of
select="."
/>
</xsl:attribute>
</xsl:for-each>
<!-- Continue parsing for child...
-->
<xsl:apply-templates
select="msxsl:node-set($prmChildren)/."
/>
</xsl:element>
</xsl:when>
<xsl:when
test="count(msxsl:node-set($prmChildren)/.)=0">
<!-- Case w:customXml element with
empty childrens -->
<!-- ... copy current element
(w:customXml) -->
<xsl:element
name="{$varName}">
<xsl:for-each
select="@*">
<xsl:attribute
name="{name()}">
<xsl:value-of
select="."
/>
</xsl:attribute>
</xsl:for-each>
<!-- Generate <w:r>...</w:r> as child
-->
<xsl:call-template
name="genRunElement">
<xsl:with-param
name="prmElementName"
select="$prmElementName"
/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:when
test="name(msxsl:node-set($prmChildren)/.)='w:tc'">
<!-- Case w:customXml element with
<w:tc>...</w:tc> as first child -->
<!-- ... copy current element
(w:customXml) -->
<xsl:element
name="{$varName}">
<xsl:for-each
select="@*">
<xsl:attribute
name="{name()}">
<xsl:value-of
select="."
/>
</xsl:attribute>
</xsl:for-each>
<!-- Generate <w:tc>...</w:tc> as child
-->
<w:tc>
<!-- Scan all children elements in
current <w:tc>...</w:tc> in input -->
<xsl:for-each
select="msxsl:node-set($prmChildren)/./*">
<xsl:choose>
<!-- ... in case <w:p>...</w:p>
-->
<xsl:when
test="name()='w:p'">
<!-- Copy current element (w:p)
-->
<xsl:element
name="{name()}">
<xsl:for-each
select="@*">
<xsl:attribute
name="{name()}">
<xsl:value-of
select="."
/>
</xsl:attribute>
</xsl:for-each>
<!-- ... copy all current child
contents as child -->
<!--<xsl:apply-templates
select="child::*" /> -->
<xsl:copy-of
select="child::*"
/>
<!-- ... insert <w:t>...</w:t> as child
-->
<xsl:call-template
name="genRunElement">
<xsl:with-param
name="prmElementName"
select="$prmElementName"
/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<!-- ... for any other case...
-->
<xsl:otherwise>
<!-- Copy all current contents into
output -->
<!--<xsl:apply-templates select="*" />
-->
<xsl:copy>
<xsl:copy-of
select="*"
/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</w:tc>
</xsl:element>
</xsl:when>
<xsl:when
test="name(msxsl:node-set($prmChildren)/.)='w:tr'">
<!-- Case w:customXml element with
<w:tr>...</w:tr> as first child -->
<!-- ... generate <xsl:for-each ...
-->
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:for-each
select="//]]></xsl:text>
<xsl:value-of
select="$prmRootElement"
/>
<xsl:text
disable-output-escaping="yes"><![CDATA[/]]></xsl:text>
<xsl:value-of
select="$prmElementName"
/>
<xsl:text
disable-output-escaping="yes"><![CDATA[">]]></xsl:text>
<!-- ... copy current element
(w:customXml) -->
<xsl:element
name="{$varName}">
<xsl:for-each
select="@*">
<xsl:attribute
name="{name()}">
<xsl:value-of
select="."
/>
</xsl:attribute>
</xsl:for-each>
<!-- Select current <w:tr>...</w:tr> in
input -->
<xsl:for-each
select="msxsl:node-set($prmChildren)/.">
<!-- Copy <w:tr>...</w:tr> into output
-->
<xsl:element
name="{name()}">
<xsl:for-each
select="@*">
<xsl:attribute
name="{name()}">
<xsl:value-of
select="."
/>
</xsl:attribute>
</xsl:for-each>
<!-- Scan all children elements in
current <w:tr>...</w:tr> in input -->
<xsl:for-each
select="msxsl:node-set($prmChildren)/./*">
<xsl:choose>
<!-- ... in case not
<w:customXml>...</w:customXml> -->
<xsl:when
test="name()!='w:customXml'">
<!-- Copy all current contents into
output -->
<xsl:copy-of
select="."
/>
</xsl:when>
<!-- ... in case
<w:customXml>...</w:customXml> -->
<xsl:otherwise>
<!-- Continue parsing ...
-->
<xsl:apply-templates
select="."
/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
<!-- ... close </xsl:for-each>
-->
<xsl:text
disable-output-escaping="yes"><![CDATA[</xsl:for-each>]]></xsl:text>
</xsl:when>
<xsl:otherwise>
<!-- In any other case: insert your
code here ...
<xsl:text disable-output-escaping="yes"><![CDATA[<xsl:for-each
select="//]]></xsl:text>
<xsl:value-of select="$prmRootElement" />
<xsl:text disable-output-escaping="yes"><![CDATA[/]]></xsl:text>
<xsl:value-of select="$prmElementName" />
<xsl:text disable-output-escaping="yes"><![CDATA[">]]></xsl:text>
<xsl:apply-templates select="msxsl:node-set($prmChildren)/./*" />
<xsl:text
disable-output-escaping="yes"><![CDATA[</xsl:for-each>]]></xsl:text>
-->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Generate <w:r>... <w:r> element
-->
<xsl:template
name="genRunElement">
<xsl:param
name="prmElementName"
/>
<w:r>
<w:rPr>
<!--<w:cnfStyle w:val="000000100000" />-->
<xsl:choose>
<xsl:when
test="$prmElementName='birthdate'">
<w:lang
w:val="ru-RU"
/>
</xsl:when>
<xsl:otherwise>
<w:lang
w:val="en-US"
/>
</xsl:otherwise>
</xsl:choose>
</w:rPr>
<!-- Generate <w:t>... <w:t> element
-->
<xsl:call-template
name="genTextContent">
<xsl:with-param
name="prmElementName"
select="$prmElementName"
/>
</xsl:call-template>
</w:r>
</xsl:template>
<!-- Generate <w:t>... <w:t> element
-->
<xsl:template
name="genTextContent">
<xsl:param
name="prmElementName"
/>
<w:t>
<!-- Generate output according to name
of element -->
<xsl:choose>
<xsl:when
test="$prmElementName='notes'">
<!-- ... for long input string only
first 50 chars of string -->
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:choose>]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:when
test="string-length(notes)<51">]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:value-of
select="notes"/>]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[</xsl:when>]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:otherwise>]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:value-of
select="concat(substring(notes,1,50), substring-before(substring(notes,51), '
'), ' ...')" />]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[</xsl:otherwise>]]></xsl:text>
<xsl:text
disable-output-escaping="yes"><![CDATA[</xsl:choose>]]></xsl:text>
</xsl:when>
<xsl:when
test="$prmElementName='birthdate'">
<!-- ... convert to russian date format
-->
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:value-of
select="msxsl:format-date(birthdate, 'dd.MM.yyyy')" />]]></xsl:text>
</xsl:when>
<xsl:otherwise>
<!-- ... simple copy
-->
<xsl:text
disable-output-escaping="yes"><![CDATA[<xsl:value-of
select="]]></xsl:text>
<xsl:value-of
select="$prmElementName"/>
<xsl:text
disable-output-escaping="yes"><![CDATA["
/>]]></xsl:text>
</xsl:otherwise>
</xsl:choose>
</w:t>
</xsl:template>
</xsl:stylesheet>
Файл: genXsltFromTemplate.xslt
Так вот, если теперь применить xslt-преобразование,
определенное в файле getXmlDocument.xslt, к данным из
файла employee.xml, а полученный при этом результат, в
качестве файла document.xml, заменить в шаблоне
документа "Пример VFP-таблицы в виде MS Word 2007.docx" (точнее,
подменить файл ..\DOCX\Пример VFP-таблицы в виде MS Word 2007\word\document.xml на полученный
результат и
применить командный файл fromContentsToDocxUseWinRAR.cmd), то после
всех этих преобразований документ показан на картинке ниже:
Рис.12
Как видите, таблица с данными сформирована и наша цель достигнута! :-) Здесь
чтобы показать/скрыть элементы схемы в документе, установите/снимите выделенный
красным кружком в панели "Структура XML"
соответствующий флажок. Обратите внимание, что MS Word
предупреждает о неверном формате даты в поле birthdate,
хотя предлагаемый им формат для даты в точности совпадает с тем, который
находиться в документе. Полагаю, что проблема в том, что предупреждение
возникает из-за того, что дата в документе противоречит не формату даты в
документе, а именно формату даты в XSD-схеме, т.е.
не в YYYY-MM-DD формате, как требуют того xml-данные.
Наконец, в столбце "Комментарий" данные урезаны до длины примерно в 50 символов,
т.е. в полном соответствии с тем, как мы преобразовали данные по полю
note в xslt-преобразовании
getXmlDocument.xslt.
Таким образом, окончательный вид нашей исходной VFP-таблички
employee в MS Word документе
подобен следующему:
Рис.13
Показанный на Рис.13 результат был получен с помощью вот такого
C#-кода консольного приложения в MS
VS .NET 2005 (SP1VS2005):
#define RUS_LANG
#define TMP_OUT
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Xsl;
using System.IO;
using System.IO.Packaging;
using System.Diagnostics;
namespace xml2docx
{
class
xml2docx
{
const
string PATH_XML =
@"..\..\..\..\XML\";
const
string PATH_DOCX =
@"..\..\..\..\DOCX\";
const
string PATH_XSLT =
@"..\..\..\..\XSLT\";
const
string XML_DATA_FILE =
"employee.XML";
const
string XSLT_FILE =
"genXsltFromTemplate.xslt";
const
string URI_DOCUMENT_XML =
"/word/document.xml";
const
string USE_ENCODING =
"utf-8";
const
string XML_PI =
"<?xml version=\"1.0\" encoding=\""
+ USE_ENCODING + "\"
standalone=\"yes\"?>\r\n";
#if RUS_LANG
const
string TEMPLATE_DOCX =
"Пример VFP-таблицы в виде MS Word 2007.docx";
const
string ERR_DIR_NOTFOUND =
"Не найден каталог: ";
const
string ERR_FILE_NOTFOUND =
"Не найден файл: ";
const
string ERR_EMPTY_XML =
"Нет xml-данных для: ";
const
string ERR_EMPTY_XSLTRES =
"Результат xslt-преобразований пуст для: ";
const
string DOC_PROP_DESCRIPTION
= "Документ создан на основе шаблона и программно заполнен данными из xml-файла.";
const
string DOC_PROP_SUBJECT =
"Использование Open XML формата в MS Word 2007";
#else
const string TEMPLATE_DOCX = "Example VFP-table in MS Word 2007.docx";
const string ERR_DIR_NOTFOUND = "Not found directory: ";
const string ERR_FILE_NOTFOUND = "Not found file: ";
const string ERR_EMPTY_XML = "Empty xml-data for: ";
const string ERR_EMPTY_XSLTRES = "Empty result of xslt-transformation for: ";
const string DOC_PROP_DESCRIPTION = "The document is created on the basis of
template and is filled by data from a xml-file from code program.";
const string DOC_PROP_SUBJECT = "Use Open XML format in MS Word 2007";
#endif
static
void Main(string[]
args)
{
bool bStartAppWord =
false;
try
{
///////////////////////////////////////////////////////////////////////
// Check existence of external files
if (!Directory.Exists(PATH_XML))
{
throw (new
DirectoryNotFoundException(ERR_DIR_NOTFOUND
+ PATH_XML));
}
if (!Directory.Exists(PATH_DOCX))
{
throw (new
DirectoryNotFoundException(ERR_DIR_NOTFOUND
+ PATH_DOCX));
}
if (!Directory.Exists(PATH_XSLT))
{
throw (new
DirectoryNotFoundException(ERR_DIR_NOTFOUND
+ PATH_XSLT));
}
string
FileXmlData = PATH_XML + XML_DATA_FILE;
if (!File.Exists(FileXmlData))
{
throw (new
FileNotFoundException(ERR_FILE_NOTFOUND
+ FileXmlData));
}
string
FileTemplateDocx = PATH_DOCX + TEMPLATE_DOCX;
if (!File.Exists(FileTemplateDocx))
{
throw (new
FileNotFoundException(ERR_FILE_NOTFOUND
+ FileTemplateDocx));
}
string
FileGenXsltFromTemplate = PATH_XSLT + XSLT_FILE;
if (!File.Exists(FileTemplateDocx))
{
throw (new
FileNotFoundException(ERR_FILE_NOTFOUND
+ FileGenXsltFromTemplate));
}
///////////////////////////////////////////////////////////////////////
// Copy template to target folder
string
OutputFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
string
OutputFileDocx = OutputFolder + @"\_"
+ TEMPLATE_DOCX;
File.Copy(FileTemplateDocx,
OutputFileDocx, true);
if (!File.Exists(OutputFileDocx))
{
throw (new
FileNotFoundException(ERR_FILE_NOTFOUND
+ OutputFileDocx));
}
///////////////////////////////////////////////////////////////////////
// Load xml-data file and get name of
its root element
XmlDocument
xmlDocData = new
XmlDocument();
xmlDocData.Load(FileXmlData);
if
(xmlDocData.DocumentElement == null
|| xmlDocData.DocumentElement.ChildNodes.Count == 0)
{
throw (new
NullReferenceException(ERR_EMPTY_XML
+ FileXmlData));
}
string
rootElementName = xmlDocData.DocumentElement.Name;
///////////////////////////////////////////////////////////////////////
// Get package copy of template and
prepare for its processing
Encoding enc =
Encoding.GetEncoding(USE_ENCODING);
string strXmlDoc
= System.String.Empty;
string docxTitle
= System.String.Empty;
Uri uriWordDocXml
= new
Uri(URI_DOCUMENT_XML,
UriKind.Relative);
using (Package
package = Package.Open(OutputFileDocx,
FileMode.Open))
{
///////////////////////////////////////////////////////////////////////
// Get PackagePart of template for
URI_DOCUMENT_XML(/word/document.xml)
PackagePart
packagePart = package.GetPart(uriWordDocXml);
// ... and its stream in ReadWrite mode
using (Stream
ppStrmXmlDoc = packagePart.GetStream(FileMode.Open,
FileAccess.ReadWrite))
{
///////////////////////////////////////////////////////////////////////////////
// Prepare and execute the first
xslt-transformation
// with document.xml and by using
xslt-file FileGenXsltFromTemplate
XmlDocument
xmlDoc = new
XmlDocument();
xmlDoc.Load(ppStrmXmlDoc);
XsltArgumentList
xsltArgumentList = new
XsltArgumentList();
xsltArgumentList.AddParam("prmRootElement",
"",
rootElementName);
xsltArgumentList.AddParam("prmEncoding",
"", USE_ENCODING);
XslCompiledTransform
xslXsltDocTrans = new
XslCompiledTransform();
xslXsltDocTrans.Load(FileGenXsltFromTemplate);
string strXsltDoc
= System.String.Empty;
using (MemoryStream
msXsltDoc = new
MemoryStream())
{
using (XmlTextWriter
xtwXsltDoc = new
XmlTextWriter(msXsltDoc,
enc))
{
xslXsltDocTrans.Transform(xmlDoc.CreateNavigator(), xsltArgumentList,
xtwXsltDoc);
strXsltDoc = enc.GetString(msXsltDoc.GetBuffer(), 0, (int)msXsltDoc.Length);
xtwXsltDoc.Close();
}
msXsltDoc.Close();
}
int nPos =
strXsltDoc.IndexOf('<');
if (nPos == (-1))
{
throw (new
NullReferenceException(ERR_EMPTY_XSLTRES
+ FileGenXsltFromTemplate));
}
strXsltDoc = strXsltDoc.Substring(nPos);
nPos = strXsltDoc.IndexOf("<?xml");
if (nPos == (-1))
{
strXsltDoc = XML_PI + strXsltDoc;
}
///////////////////////////////////////////////////////////////////////////////
// Load result of first
xslt-transformation
XmlDocument
xmlDocXslt = new
XmlDocument();
xmlDocXslt.LoadXml(strXsltDoc);
#if TMP_OUT
xmlDocXslt.Save("getXmlDocument.xslt");
#endif
///////////////////////////////////////////////////////////////////////////////
// Prepare and execute the second
xslt-transformation
// with input xml-data-file and by
using first result of xslt-transformation
XslCompiledTransform
xslXmlDocTrans = new
XslCompiledTransform();
xslXmlDocTrans.Load(xmlDocXslt.CreateNavigator());
using (MemoryStream
msXmlDoc = new
MemoryStream())
{
using (XmlTextWriter
xtwXmlDoc = new
XmlTextWriter(msXmlDoc,
enc))
{
xslXmlDocTrans.Transform(xmlDocData.CreateNavigator(), xtwXmlDoc);
strXmlDoc = enc.GetString(msXmlDoc.GetBuffer(), 0, (int)msXmlDoc.Length);
xtwXmlDoc.Close();
}
msXmlDoc.Close();
}
nPos = strXmlDoc.IndexOf('<');
if (nPos == (-1))
{
throw (new
NullReferenceException(ERR_EMPTY_XSLTRES
+ FileXmlData));
}
strXmlDoc = strXmlDoc.Substring(nPos);
nPos = strXmlDoc.IndexOf("<?xml");
if (nPos == (-1))
{
strXmlDoc = XML_PI + strXmlDoc;
}
///////////////////////////////////////////////////////////////////////////////
// Load result of second
xslt-transformation as xml-document
XmlDocument
xmlDocOut = new
XmlDocument();
xmlDocOut.LoadXml(strXmlDoc);
#if TMP_OUT
xmlDocOut.Save("document.xml");
#endif
/////////////////////////////////////////////////////////////////////////////////
// Replace PackagePart-stream with
result of second xslt-transformation
ppStrmXmlDoc.Seek(0, SeekOrigin.Begin);
xmlDocOut.Save(ppStrmXmlDoc);
ppStrmXmlDoc.Flush();
ppStrmXmlDoc.Close();
////////////////////////////////////////////////////////////////////////////////////////
// Get the text of the first paragraph
of the document as title of the document
string ns_w =
xmlDocOut.DocumentElement.GetNamespaceOfPrefix("w");
XmlNamespaceManager
nsManager = new
XmlNamespaceManager(xmlDocOut.NameTable);
nsManager.AddNamespace("w",
ns_w);
XmlNode xnTitle =
xmlDocOut.DocumentElement.SelectSingleNode("//w:customXml[@w:element='"
+ rootElementName + "']/w:p",
nsManager);
if (xnTitle !=
null)
{
docxTitle = xnTitle.InnerText;
}
}
if
(docxTitle.Length > 0)
{
///////////////////////////////////////////////////////////////////////////////
// Replace title and some other
PackageProperties
package.PackageProperties.Title = docxTitle;
package.PackageProperties.Subject = DOC_PROP_SUBJECT;
package.PackageProperties.Description = DOC_PROP_DESCRIPTION;
}
package.Flush();
package.Close();
}
///////////////////////////////////////////////////////////////////////////////
// Open the new docx file in Word 2007
Process proc =
new
Process();
proc.EnableRaisingEvents = false;
proc.StartInfo.FileName = "winword";
proc.StartInfo.Arguments = "\""
+ OutputFileDocx + "\"";
proc.Start();
bStartAppWord = true;
}
catch (Exception
ex)
{
Console.WriteLine("***
Error: " + ex.ToString());
}
if (!bStartAppWord)
{
Console.WriteLine("Press
any key to continue...");
Console.ReadKey();
}
}
}
}
Файл: xml2docx.cs
Чтобы Вам самим создать подобное приложение в MS VS .NET
2005, проделайте следующее:
- создайте консольное C#-приложение,
- в
панели Solution Explorerв папку References
добавьте файл C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll
- переименуйте файл Program.cs на
xml2docx.cs
- замените только что переименованный файл вышеприведённым.
Обратите внимание на значения констант: PATH_XML, PATH_DOCX, PATH_XSLT в коде.
Они указывают на папки, являющиеся родительскими по отношению в парки
Solution Вашего C#-приложения.
Соответственно родительскими должны быть созданы папки: DOCX,
XML и XSLT, в которые должны
быть помещены файлы:
- DOCX\"Пример VFP-таблицы в виде MS Word 2007.docx"
- шаблон
документа;
- XML\employee.XML, employee.XSD - данные
VFP-таблицы в xml-формате;
- XSLT\genXsltFromTemplate.xslt -
предварительное xslt-преобразование для получения
xslt-преобразования заполнения части
docx-документа (document.xml)
данными;
соответственно. А после успешного выполнения скомпилированного
приложения xml2docx.exe, в текущей папке будут созданы
файлы: document.xml, getXmlDocument.xslt, а
заполненный данными документ "_Пример VFP-таблицы в виде MS Word 2007.docx" будет помещён в папку: C:\Documents and Settings\<ИмяПользователя>\Мои документы\
Используя технику, изложенную в статье Hosting the .NET Runtime in Visual FoxPro, by Rick Strahl , приведённый
выше C#-код можно переписать и для использования
из-под MS Visual FoxPro 8.0 (или выше). И вот каким он
у меня получился:
#INCLUDE
"SolWestWindEx.h"
#INCLUDE
"FrameworkWrap.h"
#DEFINE
RUS_LANG .T.
#DEFINE
TMP_OUT .T.
#DEFINE
CRLF
CHR(13)
+ CHR(10)
#DEFINE
SW_NORMAL 1
#DEFINE
PATH_XML "..\..\XML\"
#DEFINE
PATH_DOCX "..\..\DOCX\"
#DEFINE
PATH_XSLT "..\..\XSLT\"
#DEFINE
XML_DATA_FILE "employee.XML"
#DEFINE
XSLT_FILE "genXsltFromTemplate.xslt"
#DEFINE
XSLT_FILE_FIRST_RESULT "getXmlDocument.xslt"
#DEFINE
XML_FILE_SECOND_RESULT "document.xml"
#DEFINE
URI_DOCUMENT_XML "/word/document.xml"
#DEFINE
USE_ENCODING "utf-8"
#DEFINE
XML_PI '<?xml version="1.0" encoding="' +
USE_ENCODING + '" standalone="yes"?>' + CRLF
#DEFINE
MYDOC_FOLDER "MyDocuments"
#IF
RUS_LANG
#DEFINE
TEMPLATE_DOCX "Пример VFP-таблицы в виде MS
Word 2007.docx"
#DEFINE
ERR_VFP_VERSION "Извините, данный код
предназначен для VFP 8.0 (или выше)."
#DEFINE
ERR_LABEL_OUTPUT "*** Возникла ошибка: "
#DEFINE
ERR_DIR_NOTFOUND "Не найден каталог: "
#DEFINE
ERR_FILE_NOTFOUND "Не найден файл: "
#DEFINE
ERR_EMPTY_XML "Нет xml-данных для: "
#DEFINE
ERR_EMPTY_XSLTRES "Результат
xslt-преобразований пуст для: "
#DEFINE
DOC_PROP_DESCRIPTION "Документ создан на
основе шаблона и программно заполнен данными из xml-файла."
#DEFINE
DOC_PROP_SUBJECT "Использование Open XML
формата в MS Word 2007"
#DEFINE
MSG_SUCCESS_RESUT "Процесс успешно завершён!
Ожидание открытия полученного нового документа в MS Word 2007..."
#ELSE
#DEFINE
TEMPLATE_DOCX "Example VFP-table in MS
Word 2007.docx"
#DEFINE
ERR_VFP_VERSION "Sorry, this code for VFP 8.0
(or later)."
#DEFINE
ERR_LABEL_OUTPUT "*** Error appears: "
#DEFINE
ERR_DIR_NOTFOUND "Not found directory: "
#DEFINE
ERR_FILE_NOTFOUND "Not found file: "
#DEFINE
ERR_EMPTY_XML "Empty xml-data for: "
#DEFINE
ERR_EMPTY_XSLTRES "Empty result of
xslt-transformation for: "
#DEFINE
DOC_PROP_DESCRIPTION "The document is created
on the basis of template and is filled by data from a xml-file from code
program."
#DEFINE
DOC_PROP_SUBJECT "Use Open XML format in MS
Word 2007"
#DEFINE
MSG_SUCCESS_RESUT "Process is successfully
completed! Waiting of opening of the received new document in MS Word 2007..."
#ENDIF
CLEAR
SET DEFAULT TO (LEFT(SYS(16),
RAT("\",
SYS(16))))
IF VERSION(5) < 800
? ERR_VFP_VERSION
RETURN
.F.
ENDIF
PRIVATE poBridge
&& as DotNetBridge OF
FULLPATH("DotNetBridge.prg")
LOCAL loException
as Exception;
,lbError as
Boolean;
,bIsWordDoc as
Boolean
TRY
*///////////////////////////////////////////////////////////////////////
*// Check existence of external files
IF
!DIRECTORY(PATH_XML)
ERROR
101, ERR_DIR_NOTFOUND +
LOWER(FULLPATH(PATH_XML))
ENDIF
IF !DIRECTORY(PATH_DOCX)
ERROR
101, ERR_DIR_NOTFOUND +
LOWER(FULLPATH(PATH_DOCX))
ENDIF
IF !DIRECTORY(PATH_XSLT)
ERROR
101, ERR_DIR_NOTFOUND +
LOWER(FULLPATH(PATH_XSLT))
ENDIF
LOCAL lcFileXmlData
as String
lcFileXmlData =
LOWER(FULLPATH(PATH_XML
+ XML_DATA_FILE))
IF
!FILE(lcFileXmlData)
ERROR
101, ERR_FILE_NOTFOUND + lcFileXmlData
ENDIF
LOCAL lcFileTemplateDocx
as String
lcFileTemplateDocx =
LOWER(FULLPATH(PATH_DOCX))
+ TEMPLATE_DOCX
IF
!FILE(lcFileTemplateDocx)
ERROR
101, ERR_FILE_NOTFOUND + lcFileTemplateDocx
ENDIF
LOCAL lcFileGenXsltFromTemplate
as String
lcFileGenXsltFromTemplate =
LOWER(FULLPATH(PATH_XSLT))
+ XSLT_FILE
IF
!FILE(lcFileTemplateDocx)
ERROR
101, ERR_FILE_NOTFOUND +
lcFileGenXsltFromTemplate
ENDIF
*////////////////////////////////////////////////////////////////
*// Create DotNetBridge object
LOCAL
lcBridgeFileProc
as String
lcBridgeFileProc =
LOWER(FULLPATH(BRIDGE_PROC))
IF
!FILE(lcBridgeFileProc)
ERROR
101, lcBridgeFileProc
ENDIF
IF NOT (JUSTSTEM(lcBridgeFileProc)
$ LOWER(SET("Procedure")))
SET PROCEDURE TO
(lcBridgeFileProc)
ADDITIVE
ENDIF
poBridge =
CREATEOBJECT(BRIDGE_CLASS)
IF VARTYPE(poBridge)
# 'O'
ERROR
'Can not create ' + BRIDGE_CLASS + ' object.'
ENDIF
*////////////////////////////////////////////////////////////////
*// Set procedure to "FrameworkWrap.prg"
LOCAL
lcFrameworkWrapFileProc
as String
lcFrameworkWrapFileProc =
LOWER(FULLPATH("FrameworkWrap.prg"))
IF
!FILE(lcFrameworkWrapFileProc)
ERROR
101, lcFrameworkWrapFileProc
ENDIF
IF NOT (JUSTSTEM(lcFrameworkWrapFileProc)
$ LOWER(SET("Procedure")))
SET PROCEDURE TO
(lcFrameworkWrapFileProc)
ADDITIVE
ENDIF
*////////////////////////////////////////////////////////////////
*// LoadAssembly ASSEMBLY_SYSTEM
IF
!poBridge.LoadAssembly(ASSEMBLY_SYSTEM)
ERROR
poBridge.cErrorMsg
ENDIF
*///////////////////////////////////////////////////////////////////////
*// Copy docx-template to target folder
LOCAL
loWSH
as
WScript.Shell
loWSH =
CREATEOBJECT("WScript.Shell")
LOCAL
lcOutputFolder
as String
lcOutputFolder =
loWSH.SpecialFolders(MYDOC_FOLDER)
LOCAL lcOutputFileDocx
as String
lcOutputFileDocx = lcOutputFolder +
"\_" + TEMPLATE_DOCX
poBridge.InvokeStaticMethod("System.IO.File", "Copy", lcFileTemplateDocx,
lcOutputFileDocx, .T.)
IF
!EMPTY(poBridge.cErrorMsg)
ERROR
poBridge.cErrorMsg
ENDIF
*//////////////////////////////////////////////////////////////////////////////
*// Creation some objects from ASSEMBLY_SYSTEM, necessary during processing
LOCAL
loEnc
as
Encoding
OF FULLPATH("FrameworkWrap.prg")
loEnc = CREATEOBJECT("Encoding",
USE_ENCODING)
LOCAL
lnUriKindRelative
as Integer
lnUriKindRelative =
poBridge.GetStaticProperty("System.UriKind", "Relative")
IF EMPTY(lnUriKindRelative)
ERROR
poBridge.cErrorMsg
ENDIF
LOCAL loUri
as
Uri
OF FULLPATH("FrameworkWrap.prg")
loUri = CREATEOBJECT("Uri",
URI_DOCUMENT_XML, lnUriKindRelative)
LOCAL
lnFileModeOpen
as Integer
lnFileModeOpen =
poBridge.GetStaticProperty("System.IO.FileMode", "Open")
IF EMPTY(lnFileModeOpen)
ERROR
poBridge.cErrorMsg
ENDIF
LOCAL lnFileAccessReadWrite
as Integer
lnFileAccessReadWrite =
poBridge.GetStaticProperty("System.IO.FileAccess", "ReadWrite")
IF EMPTY(lnFileAccessReadWrite)
ERROR
poBridge.cErrorMsg
ENDIF
LOCAL loMstrXsltDoc
as
MemoryStream
OF FULLPATH("FrameworkWrap.prg")
loMstrXsltDoc =
CREATEOBJECT("MemoryStream")
LOCAL
loMstrXmlDoc
as
MemoryStream
OF FULLPATH("FrameworkWrap.prg")
loMstrXmlDoc =
CREATEOBJECT("MemoryStream")
*////////////////////////////////////////////////////////////////
*// LoadAssembly ASSEMBLY_WINDOWSBASE
IF
!poBridge.LoadAssembly(ASSEMBLY_WINDOWSBASE)
ERROR
(poBridge.cErrorMsg)
ENDIF
*///////////////////////////////////////////////////////////////////////
*// Get package copy of template and prepare for its processing
LOCAL
loPackage
as
Package
OF FULLPATH("FrameworkWrap.prg")
loPackage =
CREATEOBJECT("Package",
lcOutputFileDocx)
*-- Get PackagePart
of template for URI_DOCUMENT_XML(/word/document.xml)
LOCAL
loPackagePart
as
PackagePart
OF FULLPATH("FrameworkWrap.prg")
loPackagePart =
CREATEOBJECT("PackagePart";
,loPackage.GetPart(loUri.GetOwnerFwObject()))
*-- ... and its
stream in ReadWrite mode
LOCAL
loPpStrmXmlDoc
as
Stream
OF FULLPATH("FrameworkWrap.prg")
loPpStrmXmlDoc =
CREATEOBJECT("Stream";
,loPackagePart.GetStream(lnFileModeOpen, lnFileAccessReadWrite))
*////////////////////////////////////////////////////////////////
*// LoadAssembly ASSEMBLY_SYSTEM_XML
IF
!poBridge.LoadAssembly(ASSEMBLY_SYSTEM_XML)
ERROR
poBridge.cErrorMsg
ENDIF
*///////////////////////////////////////////////////////////////////////
*// Load xml-data file and get name of its root element
LOCAL
loXmlDocData
as
XmlDocument
OF FULLPATH("FrameworkWrap.prg")
loXmlDocData =
CREATEOBJECT("XmlDocument")
loXmlDocData.Load(lcFileXmlData)
LOCAL
loXmlDocDataDocumentElement
as
XmlElement
OF FULLPATH("FrameworkWrap.prg")
loXmlDocDataDocumentElement =
CREATEOBJECT("XmlElement",
loXmlDocData.DocumentElement)
LOCAL
lcRootElementName
as String
lcRootElementName =
loXmlDocDataDocumentElement.ElementName
*///////////////////////////////////////////////////////////////////////////////
*// Prepare and execute the first xslt-transformation
*// with document.xml and by using xslt-file FileGenXsltFromTemplate
LOCAL
lcStrXsltDoc
as String
LOCAL loXmlDoc
as
XmlDocument
OF FULLPATH("FrameworkWrap.prg")
loXmlDoc =
CREATEOBJECT("XmlDocument")
loXmlDoc.Load(loPpStrmXmlDoc.GetOwnerFwObject())
LOCAL
loXsltArgumentList
as
XsltArgumentList
OF FULLPATH("FrameworkWrap.prg")
loXsltArgumentList =
CREATEOBJECT("XsltArgumentList")
loXsltArgumentList.AddParam("prmRootElement", "", lcRootElementName)
loXsltArgumentList.AddParam("prmEncoding", "", USE_ENCODING)
LOCAL
loXtwXsltDoc
as
XmlTextWriter
OF FULLPATH("FrameworkWrap.prg")
loXtwXsltDoc =
CREATEOBJECT("XmlTextWriter";
,loMstrXsltDoc.GetOwnerFwObject(), loEnc.GetOwnerFwObject())
LOCAL
loXslXsltDocTrans
as
XslCompiledTransform
OF FULLPATH("FrameworkWrap.prg")
loXslXsltDocTrans =
CREATEOBJECT("XslCompiledTransform")
loXslXsltDocTrans.Load(lcFileGenXsltFromTemplate)
loXslXsltDocTrans.Transform(loXmlDoc.CreateNavigator();
,loXsltArgumentList.GetOwnerFwObject(), loXtwXsltDoc.GetOwnerFwObject())
lcStrXsltDoc = loEnc.GetString(loMstrXsltDoc.GetBuffer(), 0,
loMstrXsltDoc.Length)
LOCAL
lnPos
as Integer
lnPos =
ATC("<",
lcStrXsltDoc)
IF
lnPos = 0
ERROR
(ERR_EMPTY_XSLTRES +
lcFileGenXsltFromTemplate)
ENDIF
lcStrXsltDoc =
SUBSTRC(lcStrXsltDoc,
lnPos)
lnPos = ATC("<?xml",
lcStrXsltDoc)
IF
lnPos = 0
lcStrXsltDoc = XML_PI + lcStrXsltDoc
ENDIF
*///////////////////////////////////////////////////////////////////////////////
*// Load result of first xslt-transformation
LOCAL
loXmlDocXslt
as
XmlDocument
OF FULLPATH("FrameworkWrap.prg")
loXmlDocXslt =
CREATEOBJECT("XmlDocument")
loXmlDocXslt.LoadXml(lcStrXsltDoc)
#IF
TMP_OUT
loXmlDocXslt.Save(XSLT_FILE_FIRST_RESULT)
#ENDIF
*///////////////////////////////////////////////////////////////////////////////
*// Prepare and execute the second xslt-transformation
*// with input xml-data-file and by using first result of xslt-transformation
LOCAL
lcStrXmlDoc
as String
LOCAL loXslXmlDocTrans
as
XslCompiledTransform
OF FULLPATH("FrameworkWrap.prg")
loXslXmlDocTrans =
CREATEOBJECT("XslCompiledTransform")
loXslXmlDocTrans.Load(loXmlDocXslt.CreateNavigator())
LOCAL
loXtwXmlDoc
as
XmlTextWriter
OF FULLPATH("FrameworkWrap.prg")
loXtwXmlDoc =
CREATEOBJECT("XmlTextWriter";
,loMstrXmlDoc.GetOwnerFwObject(), loEnc.GetOwnerFwObject())
loXslXmlDocTrans.Transform(loXmlDocData.CreateNavigator(),
loXtwXmlDoc.GetOwnerFwObject())
lcStrXmlDoc = loEnc.GetString(loMstrXmlDoc.GetBuffer(), 0,
loMstrXmlDoc.Length)
lnPos = ATC("<",
lcStrXmlDoc)
IF
lnPos = 0
ERROR
(ERR_EMPTY_XSLTRES + lcFileXmlData)
ENDIF
lcStrXmlDoc =
SUBSTRC(lcStrXmlDoc,
lnPos)
lnPos = ATC("<?xml",
lcStrXmlDoc)
IF
lnPos = 0
lcStrXmlDoc = XML_PI + lcStrXmlDoc
ENDIF
*///////////////////////////////////////////////////////////////////////////////
*// Load result of second xslt-transformation as xml-document
LOCAL
loXmlDocOut
as
XmlDocument
OF FULLPATH("FrameworkWrap.prg")
loXmlDocOut =
CREATEOBJECT("XmlDocument")
loXmlDocOut.LoadXml(lcStrXmlDoc)
#IF
TMP_OUT
loXmlDocOut.Save(XML_FILE_SECOND_RESULT)
#ENDIF
*///////////////////////////////////////////////////////////////////////////////
*// Replace PackagePart-stream with result of second xslt-transformation
loPpStrmXmlDoc.Position
= 0
loXmlDocOut.Save(loPpStrmXmlDoc.GetOwnerFwObject())
loPpStrmXmlDoc.Flush()
loPpStrmXmlDoc.Close()
bIsWordDoc = .T.
CATCH TO loException
lbError = .T.
LOCAL
lcMsg
as String
lcMsg = "Error: " +
LTRIM(STR(loException.ErrorNo))
+ CRLF ;
+ "LineNo: " + LTRIM(STR(loException.LineNo))
+ CRLF ;
+ "Message: " + IIF(EMPTY(loException.Message),
'Unknown error', loException.Message)
? ERR_LABEL_OUTPUT
? lcMsg
FINALLY
loWSH =
NULL
IF TYPE('poBridge') = 'O' AND !ISNULL(poBridge)
*-- Remove all
objects, which keeping resources of memory
loXtwXmlDoc =
NULL
loXtwXsltDoc =
NULL
loPpStrmXmlDoc =
NULL
loMstrXsltDoc =
NULL
loMstrXmlDoc =
NULL
loPackage =
NULL
poBridge.Unload()
poBridge = NULL
ENDIF
ENDTRY
LOCAL lbResult
as
Boolean
IF bIsWordDoc
*///////////////////////////////////////////////////////////////////////////////
*// Open the new docx file in Word 2007
WAIT
MSG_SUCCESS_RESUT
WINDOW NOWAIT TIMEOUT
10
DECLARE INTEGER
ShellExecute
IN
shell32.dll
;
INTEGER hwnd,
;
STRING
@lsOperation, ;
STRING
@lsFile, ;
STRING
@lsParameters, ;
STRING
@lsDirectory, ;
INTEGER
liShowCmd
DECLARE INTEGER
GetDesktopWindow
IN
user32.dll
LOCAL lnRetCode
as Integer
lnRetCode = 0
lnRetCode = ShellExecute(GetDesktopWindow(), "open", lcOutputFileDocx, "",
"", SW_NORMAL)
CLEAR DLLS
IF lnRetCode > 32
lbResult = .T.
ELSE
ERROR "ShellExecute: errorcode = " +
LTRIM(STR(lnRetCode))
ENDIF
ENDIF
RETURN lbResult
Файл: xml2docx.prg из проекта FillDocxByUseFramework_20.pjx
При поверхностном сравнении кода приведённых выше файлов
xml2docx.prg и xml2docx.cs,
и если не принимать во внимание различия синтаксиса используемых языков, то может показаться, что
отличия в коде минимальны... :-) Однако, посмотрев внимательнее, можно легко
заметить, что никакого прямого обращения к классам MS
Framework конечно же нет, а используются классы, определённый в файле
FrameworkWrap.prg и образующие т.н. "покрытия"
классов из MS Framework.
Давайте теперь бегло заглянем в подкаталоге CommonTools в файл FrameworkWrap.prg
и там мы увидим, что базовым для любого MS Framework-класса
является вот такой класс:
*////////////////////////////////////////////////////////
*// baseFwWrap - base for any wrap to MS Framework class
DEFINE CLASS baseFwWrap
as Session
oFwObj =
NULL
cClassName = ""
PROCEDURE Init
IF !this.IsBridgeObject()
ERROR
"Not found instance of " + BRIDGE_CLASS + "
object"
ENDIF
IF !this.IsClassObject()
ERROR
"Not define "+
this.Name
+ ".cClassName property"
ENDIF
ENDPROC
PROCEDURE IsBridgeObject
RETURN
(TYPE('poBridge')
= 'O' AND !ISNULL(poBridge))
ENDPROC
PROCEDURE IsClassObject
RETURN
!EMPTY(this.cClassName)
ENDPROC
PROCEDURE IsFrameworkObject
RETURN
(VARTYPE(this.oFwObj)
= 'O')
ENDPROC
PROCEDURE ToString
IF
!this.IsBridgeObject()
ERROR
"Not found instance of " + BRIDGE_CLASS + "
object" ;
+ " in " + this.Name
+ '.toString()'
ENDIF
LOCAL lcRetVal
as String
lcRetVal = poBridge.InvokeMethod(this.oFwObj,
'ToString')
IF
!EMPTY(poBridge.cErrorMsg)
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.ToString()')
ENDIF
RETURN lcRetVal
ENDPROC
PROCEDURE GetOwnerFwObject
RETURN this.oFwObj
ENDPROC
PROCEDURE Destroy
this.oFwObj =
NULL
ENDPROC
*// Insert your code
here for implement any other PEMs for this class...
ENDDEFINE
Класс: baseFwWrap
Как видим, пока нет ничего интересного. В классе определены два свойства:
oFwObj и cClassName и судя
по названию метода GetOwnerFwObject(), возвращающего значение свойства
this.oFwObj, последнее и есть истинная ссылка на
экземпляр MS Framework-класса, которая разрушается в
событии Destroy() данного класса. Свойство
cClassName предназначено для хранения полного названия
Framework-класса. В классе имеется ряд самоочевидных предикатов,
контролирующих типы некоторых переменных/свойств, и один единственный сколько-то
интересный метод ToString(). В нём, после удачного
контроля на существование приватной переменной poBridge
(ссылки на экземпляр класса DotNetBridge, о чём ниже),
через последнюю вызывается метод InvokeMethod() для
объекта this.oFwObj и названия метода
ToString. Результат выполнения метода
poBridge.InvokeMethod() собственно и возвращается в
качестве результата выполнения метода ToString()
данного класса. По такой же схеме будут работать и любые другие методы/свойства
классов-покрытий из файла FrameworkWrap.prg, т.е.
после формирования значений ряда параметров, через ссылку
poBridge будет вызываться один из методов класса
DotNetBridge, в большинстве случаев, это будет одна из
разновидностей методов DotNetBridge.Invoke...().
Давайте на примерах кода нескольких классов, посмотрим как это реально делается.
Ну вот, посмотрим на код совершенно "стандартного" в этом смысле класса
System.Xml.Xsl.XsltArgumentList:
*///////////////////////////////////////////////////////////////
*// XsltArgumentList - wrap to System.Xml.Xsl.XsltArgumentList
DEFINE CLASS XsltArgumentList
as
baseFwWrap
cClassName = "System.Xml.Xsl.XsltArgumentList"
PROCEDURE Init
IF !DODEFAULT()
RETURN
.F.
ENDIF
this.oFwObj =
poBridge.CreateInstance(this.cClassName)
IF VARTYPE(this.oFwObj)
# 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Init()')
ENDIF
ENDPROC
PROCEDURE AddParam
LPARAMETERS
tcParamName, tcNamespaceUri, toParamValue
IF
!this.IsBridgeObject()
ERROR
"Not found instance of " + BRIDGE_CLASS + "
object" ;
+ " in " + this.Name
+ '.AddParam()'
ENDIF
poBridge.InvokeMethod(this.oFwObj,
'AddParam', tcParamName, tcNamespaceUri, toParamValue)
IF
!EMPTY(poBridge.cErrorMsg)
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.AddParam()')
ENDIF
ENDPROC
*// Insert your code
here for implement any other PEMs for this class...
ENDDEFINE
Класс: XsltArgumentList
Как видим, в событии Init():
- (в VFP событие Init() грубо говоря, подобно
конструктору класса в других языках, вызывается лишь однажды при создании
экземпляра объекта) делается явный вызов baseFwWrap.Init()
(через спец. VFP-функцию
DODEFAULT()), при неудаче, событие
Init() данного класса возвращает
.F., что в свою очередь для клиента означает, что
экземпляр объекта не создаётся и возникает ошибка создания объекта класса.
- Далее, делается попытка через метод
poBridge.CreateInstance() создать экземпляр соответствующего
MS Framework-класса, поместив ссылку на него в свойство
this.oFwObj. Если при этом свойство не получило в
качестве значения экземпляр объекта (VARTYPE(this.oFwObj)
# 'O'), то в свойстве poBridge.cErrorMsg
должны быть подробности о возникшей ошибке. В такой ситуации прекращаем
выполнение, выдав клиенту соответствующее сообщение об ошибке. Если ссылка
на экземпляр объекта получена, т.е. ошибок не возникло, то "ничего не
делаем". В этом случае, событие Init() вернёт
.T. и клиент получит ссылку на экземпляр объекта
данного класса.
В методе AddParam():
- Проверяется наличие переменной-ссылки poBridge,
при отсутствии выдаётся сообщение об ошибке и метод завершается.
- При наличии переменной ссылки poBridge,
делается попытка обращения к её методу InvokeMetod()
для объекта из свойства класса this.oFwObj,
названия метода AddParam и с пришедшими на входе к
данному методу тремя параметрами. Если по завершению выполнения метода
свойство poBridge.cErrorMsg окажется не пустым,
т.е. возникла ошибка, то формируем соответствующее сообщение об ошибке
клиенту.
Если посмотреть описание класса System.Xml.Xsl.XsltArgumentList в
MSDN, то обнаружим, что мной реализована только
небольшая часть того, что может делать этот класс. Применительно к решаемой мной
задачи написанного оказалось достаточно, а если в решаемой Вами задачи потребуется
большая функциональность, то что же?... Ну допишите соответствующий код в
данный класс так, чтобы он удовлетворил все Ваши потребности... :-)
Не следует думать, что всё так гладко, как это может показаться... :-) Ну мол что
достаточно выполнить рутинную работу по наполнению методов/свойств
классов-покрытий, переадресовав вызовы на соответствующие
poBridge.Invoke...(), и таким образом поиметь
доступ из-под VFP к любому требуемому для Вашей задачи подмножеству
классов библиотеки MS Framework. Не всё так уж
безоблачно на этом пути... :-(
Например, взяв код у Rick Strahl по указанной выше
ссылке, и при попытке использовать его в задаче, изложенной в Как получить таблицу данных из xlsx-файла у приложения Excel из MS Office 2007 используя структуру данных формата Open XML? я быстро наткнулся на проблему
вызова перегруженных конструкторов/методов, имеющих одинаковое количество
параметров, но отличающихся их типами. Чтобы попытаться устранить эту проблемы,
пришлось несколько дополнить код, взятый с первоисточника. Т.е. обратите
внимание, что код, прилагаемый к этой статье, не будет работать, если попытаться
использовать dll-файлы от Rick
Strahl, а нужны именно dll-ки с небольшими
добавлениям из файла xlsxtbl.zip со страницы Примеры кода и утилиты.
Давайте на примере кода из файла FrameworkWrap.prg посмотрим, как можно использовать эти дополнительные
возможности, так в определении класса XmlTextWriter видим:
*////////////////////////////////////////////////////////////////////////
*// XmlTextWriter - wrap to System.Xml.XmlTextWriter
DEFINE CLASS XmlTextWriter
as
baseFwWrap
cClassName = "System.Xml.XmlTextWriter"
PROCEDURE Init
LPARAMETERS tuArg1, tuArg2
IF
!DODEFAULT()
RETURN
.F.
ENDIF
LOCAL
lnCntArgs
as Integer
lnCntArgs =
PCOUNT()
DO CASE
CASE
lnCntArgs = 1
this.oFwObj
= poBridge.CreateInstance(this.cClassName,
tuArg1)
CASE
lnCntArgs = 2
LOCAL
loConstr
as Object,
lcArg1Type
as String
lcArg1Type = tuArg1.ToString()
loConstr = poBridge.GetConstructor(this.cClassName,
lcArg1Type, "System.Text.Encoding")
IF VARTYPE(loConstr)
# 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Init()')
ENDIF
this.oFwObj =
poBridge.CreateInstanceObject(loConstr, tuArg1, tuArg2)
OTHERWISE
ERROR
("Error number of args." + "
In " +
this.Name
+ '.Init()')
ENDCASE
IF VARTYPE(this.oFwObj)
# 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Init()')
ENDIF
ENDPROC
PROCEDURE Flush
IF
!this.IsBridgeObject()
ERROR
"Not found instance of " + BRIDGE_CLASS + "
object" ;
+ " in " + this.Name
+ '.Flush()'
ENDIF
poBridge.InvokeMethod(this.oFwObj,
'Flush')
IF
!EMPTY(poBridge.cErrorMsg)
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Flush()')
ENDIF
ENDPROC
PROCEDURE Close
IF !this.IsBridgeObject()
ERROR
"Not found instance of " + BRIDGE_CLASS + "
object" ;
+ " in " + this.Name
+ '.Close()'
ENDIF
poBridge.InvokeMethod(this.oFwObj,
'Close')
IF
!EMPTY(poBridge.cErrorMsg)
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Close()')
ENDIF
ENDPROC
PROCEDURE Destroy
IF !this.IsBridgeObject()
ERROR
"Not found instance of " + BRIDGE_CLASS + "
object" ;
+ " in " + this.Name
+ '.Destroy()'
ENDIF
poBridge.InvokeMethod(this.oFwObj,
'Close')
DODEFAULT()
ENDPROC
*// Insert your code
here for implement any other PEMs for this class...
ENDDEFINE
Здесь в событии Init() (аналоге перегруженного
конструктора в обычных языках программирования) с помощью дописанных мной
методов: GetConstructor() и CreateInstanceObject()
в классе DotNetBridge
устраняется
проблема: какой именно конструктор у MS Framework-класса
System.Xml.XmlTextWriter необходимо вызвать, в
зависимости от типов входных параметров. Так, в выделенном цветом фона участке
кода, с помощью VFP-функции
PCOUNT(), предварительно определяется действительное
количество входных параметров, использованных при создании экземпляра класса и
далее, в зависимости от их количества, производится:
- либо прямое обращение к
poBridge.CreateInstance(), если на входе был один
параметр, т.е. отсутствовала неоднозначность по типам параметров,
- либо производится предварительное получение типа первого параметра (в
переменную lcArg1Type), в
зависимости от этого, с помощью метода GetConstructor(), создаётся экземпляр объекта
System.Reflection.ConstructorInfo и только затем, с помощью
CreateInstanceObject(), создаётся экземпляр класса
для однозначно предопределённого конструктора, именно для этого случая
создания экземпляра объекта.
После таких пояснений, видимо понятным будет код класса
Uri:
*/////////////////////////////////////////////////////
*// Uri - wrap to System.Uri
DEFINE CLASS Uri
as
baseFwWrap
cClassName = "System.Uri"
PROCEDURE Init
LPARAMETERS tuUrl, tuArg2, tuArg3
IF
!DODEFAULT()
RETURN
.F.
ENDIF
LOCAL lnCntArgs
as Integer
lnCntArgs =
PCOUNT()
DO CASE
CASE lnCntArgs = 1
this.oFwObj
= poBridge.CreateInstance(this.oConstr,
tuUrl)
CASE
lnCntArgs = 2
LOCAL
lcTypeArg1
as String;
,lcTypeArg2 as
String;
,loConstr as Object
lcTypeArg1 =
VARTYPE(tuUrl)
lcTypeArg2 = VARTYPE(tuArg2)
DO CASE
CASE lcTypeArg1 = 'C' AND lcTypeArg2
= 'L'
loConstr = poBridge.GetConstructor(this.cClassName,
"System.String", "System.Boolean")
CASE
lcTypeArg1 = 'C' AND lcTypeArg2 = 'N'
loConstr = poBridge.GetConstructor(this.cClassName,
"System.String", "System.UriKind")
CASE
lcTypeArg1 = 'O' AND lcTypeArg2 = 'C'
loConstr = poBridge.GetConstructor(this.cClassName,
"System.Uri", "System.String")
CASE
lcTypeArg1 = 'O' AND lcTypeArg2 = 'O'
loConstr = poBridge.GetConstructor(this.cClassName,
"System.Uri", "System.Uri")
OTHERWISE
ERROR ("Error type of args. In " +
this.Name
+ '.Init()')
ENDCASE
IF VARTYPE(loConstr) # 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Init()')
ENDIF
this.oFwObj =
poBridge.CreateInstanceObject(loConstr, tuUrl, tuArg2)
CASE
lnCntArgs = 3
LOCAL
lcTypeArg1
as String;
,lcTypeArg2 as
String;
,lcTypeArg3 as
String
lcTypeArg1 =
VARTYPE(tuUrl)
lcTypeArg2 = VARTYPE(tuArg2)
lcTypeArg3 = VARTYPE(tuArg3)
IF
lcTypeArg1 = 'O' AND lcTypeArg2 = 'C' AND
lcTypeArg3 = 'L'
LOCAL
loConstr
as Object
loConstr = poBridge.GetConstructor(this.cClassName,
"System.Uri", "System.String", "System.Boolean")
IF VARTYPE(loConstr)
# 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Init()')
ENDIF
this.oFwObj =
poBridge.CreateInstanceObject(loConstr, tuUrl, tuArg2, tuArg3)
ELSE
ERROR ("Error type of args. In " +
this.Name
+ '.Init()')
ENDIF
ENDCASE
IF VARTYPE(this.oFwObj)
# 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Init()')
ENDIF
ENDPROC
PROCEDURE Destroy
DODEFAULT()
ENDPROC
*// Insert your code
here for implement any other PEMs for this class...
ENDDEFINE
Здесь выбор конструктора для MS Framework-класса
производится на основе анализа VFP-типов параметров, а
не типов Framework-классов для параметров, как это
было в примере выше.
Можно ещё в качестве примера использования мной дополненной функциональности
по вызову перегруженных методов привести сюда код класса PackagePart:
*/////////////////////////////////////////////////////
*// Package - wrap to System.IO.Packaging.PackagePart
DEFINE CLASS PackagePart
as
baseFwWrap
cClassName = "System.IO.Packaging.PackagePart"
oMethodGetStream2 =
NULL
PROCEDURE Init
LPARAMETERS toPackagePart
IF
!DODEFAULT()
RETURN
.F.
ENDIF
this.oFwObj = toPackagePart
IF VARTYPE(this.oFwObj)
# 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.Init()')
ENDIF
ENDPROC
PROCEDURE GetStream
LPARAMETERS
tnFileMode, tnFileAccess
IF
!this.IsBridgeObject()
ERROR
"Not found instance of " + BRIDGE_CLASS + "
object" ;
+ " in " + this.Name
+ '.GetStream()'
ENDIF
LOCAL loRetVal
as Object;
,lnCntArgs as
Integer
lnCntArgs =
PCOUNT()
DO CASE
CASE lnCntArgs = 0
loRetVal = poBridge.InvokeMethod(this.oFwObj,
"GetStream")
CASE
lnCntArgs = 1
loRetVal = poBridge.InvokeMethod(this.oFwObj,
"GetStream", tnFileMode)
CASE
lnCntArgs = 2
IF VARTYPE(this.oMethodGetStream2)
# 'O'
this.oMethodGetStream2
= poBridge.GetMethod(this.oFwObj,
"GetStream";
,"System.IO.FileMode", "System.IO.FileAccess")
IF VARTYPE(this.oMethodGetStream2)
# 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.GetStream()')
ENDIF
ENDIF
loRetVal =
poBridge.InvokeMethodObject(this.oFwObj,
this.oMethodGetStream2;
,tnFileMode, tnFileAccess)
OTHERWISE
ERROR ("Error number of args." + "
In " + this.Name
+ '.Init()')
ENDCASE
IF VARTYPE(loRetVal) # 'O'
ERROR
(poBridge.cErrorMsg + " In " +
this.Name
+ '.GetStream()')
ENDIF
RETURN loRetVal
ENDPROC
PROCEDURE Destroy
this.oMethodGetStream2
= NULL
DODEFAULT()
ENDPROC
*// Insert your code
here for implement any other PEMs for this class...
ENDDEFINE
Здесь в методе GetStream(), в случае двух параметров, использованы методы: GetMethod()
и InvokeMethodObject() из класса
DotNetBridge для получения объекта класса System.Reflection.MethodInfo и вызова метода,
соответствующего типам параметров.
Всё ли гладко на этом пути? Да я бы не сказал так... :-( Конечно, можно многое
сделать, но проблемы остаются. Например, я не смог вызвать метод
Stream.Seek() у объекта, полученного от
PackagePart.GetStream() как не старался... :-(
Пришлось переустанавливать указатель потока через его свойство
Stream.Position. Мне не удалось также воспользоваться методом
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
подозреваю, что для этого снова нужно править C#-код в
классе DotNetBredge... Но в целом, подход всё-таки
работает! :-)
Попробуем теперь решить обратную задачу: извлечь из MS
Word 2007 документа, содержащего таблицу с данными, сами эти табличные
данные в xml-формате. Для этого нам потребуется
XSLT-преобразование над данными в /word/document.xml,
позволившее бы нам это сделать. При этом надо иметь ввиду, что некоторые типы
данных, например тип даты, необходимо преобразовать из обычного представления
(того, что в документе) в
формат, удовлетворяющий требованиям xml. Для нашей
таблички данных я это проделал и вот какой код у меня при этом получился:
<?xml
version="1.0"
encoding="utf-8"?>
<!-- File: genXmlFromDocument.xslt
-->
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
exclude-result-prefixes="xsi
msxsl msdata xsd ve o r m v wp w10 w wne">
<xsl:output
method="xml"
version="1.0"
encoding="utf-8"
omit-xml-declaration="yes"
/>
<!-- Current encoding to replace in output
xml-PI -->
<xsl:param
name="prmEncoding">
<xsl:text>utf-8</xsl:text>
</xsl:param>
<!-- Schema name -->
<xsl:param
name="prmSchemaUri">
<xsl:text>Schema_Employee</xsl:text>
</xsl:param>
<!-- xsd:schema as first child element in
output -->
<xsl:param
name="prmDocXsd"
/>
<!-- Get attribute w:element form
request w:customXml element in input -->
<xsl:variable
name="varRoot">
<xsl:value-of
select="/./w:document/w:body//w:customXml[@w:uri=$prmSchemaUri]/@w:element"/>
</xsl:variable>
<xsl:template
match="/">
<xsl:text
disable-output-escaping="yes"><![CDATA[<?xml
version="1.0" encoding="]]></xsl:text>
<xsl:value-of
select="$prmEncoding"
/>
<xsl:text
disable-output-escaping="yes"><![CDATA["
standalone="yes"?>]]>
</xsl:text>
<xsl:choose>
<xsl:when
test="$varRoot">
<!-- Generate root element into output
-->
<xsl:element
name="{$varRoot}">
<xsl:if
test="$prmDocXsd">
<!-- Insert schema element into output
-->
<xsl:copy-of
select="$prmDocXsd"/>
</xsl:if>
<!-- ... continue parsing for childrens
of request w:customXml element ... -->
<xsl:apply-templates
select="/./w:document/w:body//w:customXml[@w:uri=$prmSchemaUri]/*"
/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<!-- Error: request element w:customXml
not found -->
<xsl:element
name="{concat($prmSchemaUri,
'_Not_Found')}" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Parsing any current input...
-->
<xsl:template
match="@*|node()">
<xsl:variable
name="varName">
<xsl:value-of
select="name()"
/>
</xsl:variable>
<xsl:choose>
<xsl:when
test="string-length($varName)>0">
<xsl:choose>
<xsl:when
test="$varName='w:customXml'">
<!-- Parsing
<w:customXml...>...</w:customXml> -->
<xsl:variable
name="varElementName">
<xsl:value-of
select="@w:element"
/>
</xsl:variable>
<xsl:variable
name="varChildName">
<xsl:value-of
select="name(child::*[1])"/>
</xsl:variable>
<!-- Open <{@w:element}...>...
-->
<xsl:element
name="{$varElementName}">
<xsl:choose>
<xsl:when
test="$varElementName=$varRoot
or $varChildName='w:tr'">
<!-- ... for any group: continue
parsing for child & as child ... -->
<xsl:apply-templates
select="@*|node()"
/>
</xsl:when>
<xsl:otherwise>
<!-- ... get xml-value for current
w:customXml element -->
<xsl:call-template
name="getXmlValue">
<xsl:with-param
name="prmElementName"
select="$varElementName"
/>
<xsl:with-param
name="prmValue"
select="normalize-space(string(child::*))"
/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<!-- Close ... </{@w:element}>
-->
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates
select="@*|node()"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates
select="@*|node()"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Generate value of w:customXml element
-->
<xsl:template
name="getXmlValue">
<xsl:param
name="prmElementName"
/>
<xsl:param
name="prmValue"
/>
<xsl:choose>
<xsl:when
test="$prmDocXsd">
<!-- Try get element type from xsd-schema...
-->
<xsl:variable
name="varType">
<xsl:choose>
<xsl:when
test="msxsl:node-set($prmDocXsd)/.//xsd:element[@name=$prmElementName]/@type">
<xsl:value-of
select="msxsl:node-set($prmDocXsd)/.//xsd:element[@name=$prmElementName]/@type"/>
</xsl:when>
<xsl:when
test="msxsl:node-set($prmDocXsd)/.//xsd:element[@name=$prmElementName]/xsd:simpleType/xsd:restriction/@base">
<xsl:value-of
select="msxsl:node-set($prmDocXsd)/.//xsd:element[@name=$prmElementName]/xsd:simpleType/xsd:restriction/@base"/>
</xsl:when>
<!-- ... insert your code here for any other
types... -->
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Convert current value according to
element type... -->
<xsl:choose>
<xsl:when
test="$varType='xsd:date'">
<!-- ... for date value: convert element value
to xml-date format into ouptut -->
<xsl:value-of
select="concat(substring-after(substring-after($prmValue,
'.'), '.'), '-', substring-before(substring-after($prmValue, '.'), '.'), '-',
substring-before($prmValue, '.'))" />
</xsl:when>
<!-- ... insert your code here for any other
types... -->
<xsl:otherwise>
<!-- ... for otherwise case: simple copy
current value into ouptut -->
<xsl:value-of
select="$prmValue"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<!-- ... for otherwise case: simple copy
current value into ouptut -->
<xsl:value-of
select="$prmValue"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Файл: genXmlFromDocument.xslt
Чтобы применить приведённое выше XSLT-преобразование
из файла genXmlFromDocument.xslt
к содержанию нашего MS Word 2007 документа, потребуется ряд
действий. Код консольного C#-приложения,
выполняющего такие действия, мог бы быть следующим:
#define RUS_LANG
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;
using System.Xml;
using System.Xml.Xsl;
using System.IO;
using System.IO.Packaging;
namespace docx2xml
{
class
docx2xml
{
const
string PATH_DOCX =
@"..\..\..\..\DOCX\";
const
string PATH_XSLT =
@"..\..\..\..\XSLT\";
const
string PATH_XML =
@"..\..\..\..\XML\";
const
string XSLT_FILE =
"genXmlFromDocument.xslt";
const
string XML_OUTPUT_FILE =
"documentData.xml";
const
string URI_SETTINGS_XML =
"/word/settings.xml";
const
string URI_DOCUMENT_XML =
"/word/document.xml";
const
string KEY_SCHEMA_LIBRARY =
@"Software\Microsoft\Schema Library";
const
string LOCATION_NAME_VALUE
= "Location";
const
string USE_ENCODING =
"utf-8";
const
string XML_PI =
"<?xml version=\"1.0\" encoding=\""
+ USE_ENCODING + "\"
standalone=\"yes\"?>\r\n";
#if RUS_LANG
const
string TEMPLATE_DOCX =
"Пример VFP-таблицы в виде MS Word 2007.docx";
const
string ERR_LABEL_OUTPUT =
"*** Возникла ошибка: ";
const
string ERR_DIR_NOTFOUND =
"Не найден каталог: ";
const
string ERR_FILE_NOTFOUND =
"Не найден файл: ";
const
string ERR_EMPTY_XML =
"Нет xml-данных для: ";
const
string ERR_EMPTY_XSLTRES =
"Результат xslt-преобразований пуст для: ";
const
string ERR_CALL_GENDOCX =
"Выполните xml2docx.ex для создания этого
документа.";
const
string MSG_PRESS_ANY_KEY =
"Нажмите любую клавишу для продолжения...";
const
string MSG_SUCCESS_RESUT =
"Процесс успешно завершён!";
#else
const string TEMPLATE_DOCX = "Example VFP-table in MS Word 2007.docx";
const string ERR_LABEL_OUTPUT = "*** Error appears: ";
const string ERR_DIR_NOTFOUND = "Not found directory: ";
const string ERR_FILE_NOTFOUND = "Not found file: ";
const string ERR_EMPTY_XML = "Empty xml-data for: ";
const string ERR_EMPTY_XSLTRES = "Empty result of xslt-transformation for: ";
const string ERR_CALL_GENDOCX = "Call xml2docx.ex to generating this
document.";
const string MSG_PRESS_ANY_KEY = "Press any key to continue...";
const string MSG_SUCCESS_RESUT = "Process was successfully completed!";
#endif
static
void Main(string[]
args)
{
RegistryKey
rkDocxAsValue = null;
RegistryKey
rkDocxAttachedSchema = null;
RegistryKey
rkSchemaLibrary = null;
RegistryKey
rkCurrentUser = null;
bool bIsResult =
false;
try
{
///////////////////////////////////////////////////////////////////////
// Check existence of external files
if (!Directory.Exists(PATH_XML))
{
throw (new
DirectoryNotFoundException(ERR_DIR_NOTFOUND
+ Path.GetFullPath(PATH_XML)));
}
if (!Directory.Exists(PATH_DOCX))
{
throw (new
DirectoryNotFoundException(ERR_DIR_NOTFOUND
+ Path.GetFullPath(PATH_DOCX)));
}
if (!Directory.Exists(PATH_XSLT))
{
throw (new
DirectoryNotFoundException(ERR_DIR_NOTFOUND
+ Path.GetFullPath(PATH_XSLT)));
}
///////////////////////////////////////////////////////////////////////
// Get docx-file from Docx folder
string DocxFolder
= Environment.GetFolderPath(Environment.SpecialFolder.Personal);
string FileDocx =
DocxFolder + @"\_" +
TEMPLATE_DOCX;
if (!File.Exists(FileDocx))
{
throw (new
FileNotFoundException(ERR_FILE_NOTFOUND
+ FileDocx + ". " +
ERR_CALL_GENDOCX));
}
string FileXslt =
Path.GetFullPath(PATH_XSLT
+ XSLT_FILE);
if (!File.Exists(FileXslt))
{
throw (new
FileNotFoundException(ERR_FILE_NOTFOUND
+ FileXslt));
}
Encoding enc =
Encoding.GetEncoding(USE_ENCODING);
Uri
uriWordSettingsXml = new
Uri(URI_SETTINGS_XML,
UriKind.Relative);
Uri
uriWordDocumentXml = new
Uri(URI_DOCUMENT_XML,
UriKind.Relative);
string strXmlDoc
= System.String.Empty;
using (Package
package = Package.Open(FileDocx,
FileMode.Open))
{
string
DocxAttachedSchema = System.String.Empty;
///////////////////////////////////////////////////////////////////////
// Get PackagePart of template for
URI_SETTINGS_XML(/word/settings.xml)
PackagePart
packagePart = package.GetPart(uriWordSettingsXml);
// ... and its stream in Read mode
using (Stream
ppStrmSettingsXml = packagePart.GetStream(FileMode.Open,
FileAccess.Read))
{
///////////////////////////////////////////////////////////////////////
// Load stream as xml-document
XmlDocument
xmlDocSettings = new
XmlDocument();
xmlDocSettings.Load(ppStrmSettingsXml);
XmlElement xeRoot
= xmlDocSettings.DocumentElement;
string nsp_w =
xeRoot.GetNamespaceOfPrefix("w");
XmlNamespaceManager
nsMngr = new
XmlNamespaceManager(xmlDocSettings.NameTable);
if (nsp_w !=
null && nsp_w.Length
> 0)
{
nsMngr.AddNamespace("w",
nsp_w);
}
///////////////////////////////////////////////////////////////////////
// Get AttachedSchema name
XmlNode
xnAttachedSchema = xeRoot.SelectSingleNode("w:attachedSchema",
nsMngr);
if (xnAttachedSchema
!= null &&
xnAttachedSchema.Attributes.Count > 0)
{
XmlAttribute
xaVal = xnAttachedSchema.Attributes["w:val"];
if (xaVal !=
null)
{
DocxAttachedSchema = xaVal.Value;
}
}
ppStrmSettingsXml.Close();
}
string
PathSchemaLocation = System.String.Empty;
if (DocxAttachedSchema.Length
> 0)
{
///////////////////////////////////////////////////////////////////////////////////////////
// Get Location for XSD-schema from
Microsoft Schema Library in system registry
rkCurrentUser = Registry.CurrentUser;
// Get RegistryKey: Microsoft Schema
Library
rkSchemaLibrary = rkCurrentUser.OpenSubKey(KEY_SCHEMA_LIBRARY,
false);
// Get RegistryKey: AttachedSchema by
name
rkDocxAttachedSchema = rkSchemaLibrary.OpenSubKey(DocxAttachedSchema,
false);
// Get RegistryKey: first child of
AttachedSchema
string[]
asSubKeys = rkDocxAttachedSchema.GetSubKeyNames();
rkDocxAsValue = rkDocxAttachedSchema.OpenSubKey(asSubKeys[0],
false);
// Get Location
PathSchemaLocation = (string)rkDocxAsValue.GetValue(LOCATION_NAME_VALUE);
/////////////////////////////////////
// Close all RegistryKeys
rkDocxAsValue.Close();
rkDocxAsValue = null;
rkDocxAttachedSchema.Close();
rkDocxAttachedSchema = null;
rkSchemaLibrary.Close();
rkSchemaLibrary = null;
rkCurrentUser.Close();
rkCurrentUser = null;
}
///////////////////////////////////////////////////////////////////////////////////////
// Get PackagePart of template for
URI_DOCUMENT_XML(/word/document.xml)
packagePart = package.GetPart(uriWordDocumentXml);
// ... and its stream in Read mode
using (Stream
ppStrmDocumentXml = packagePart.GetStream(FileMode.Open,
FileAccess.Read))
{
///////////////////////////////////////////////////////////////////////
// Load stream as xml-document
XmlDocument
xmlDocumentXml = new
XmlDocument();
xmlDocumentXml.Load(ppStrmDocumentXml);
///////////////////////////////////////////////////////////////////////////////////////
// Load XSD-schema from file SchemaLocation
(as info from system registry)
XmlDocument
xmlDocXsd = null;
if (File.Exists(PathSchemaLocation))
{
xmlDocXsd = new
XmlDocument();
xmlDocXsd.Load(PathSchemaLocation);
}
///////////////////////////////////////////////////////////////////////////////
// Prepare and execute the xslt-transformation
// with document.xml and by using xslt-file
FileXslt
XslCompiledTransform
xsltXmlTransform = new
XslCompiledTransform();
XsltArgumentList
xsltArgList = new
XsltArgumentList();
xsltArgList.AddParam("prmEncoding",
"", USE_ENCODING);
if (DocxAttachedSchema.Length
> 0)
{
xsltArgList.AddParam("prmSchemaUri",
"",
DocxAttachedSchema);
}
if (xmlDocXsd !=
null &&
xmlDocXsd.DocumentElement != null)
{
xsltArgList.AddParam("prmDocXsd",
"",
xmlDocXsd.DocumentElement.CreateNavigator());
}
xsltXmlTransform.Load(FileXslt);
using (MemoryStream
msXmlDoc = new
MemoryStream())
{
using (XmlTextWriter
xtwXmlDoc = new
XmlTextWriter(msXmlDoc,
enc))
{
xsltXmlTransform.Transform(xmlDocumentXml.CreateNavigator(), xsltArgList,
xtwXmlDoc);
strXmlDoc = enc.GetString(msXmlDoc.GetBuffer(), 0, (int)msXmlDoc.Length);
xtwXmlDoc.Close();
}
msXmlDoc.Close();
}
int nPos =
strXmlDoc.IndexOf('<');
if (nPos == (-1))
{
throw (new
NullReferenceException(ERR_EMPTY_XSLTRES
+ FileXslt));
}
strXmlDoc = strXmlDoc.Substring(nPos);
nPos = strXmlDoc.IndexOf("<?xml");
if (nPos == (-1))
{
strXmlDoc = XML_PI + strXmlDoc;
}
ppStrmDocumentXml.Close();
}
package.Close();
}
//////////////////////////////////////
// Save result
string
xmlOutputFile = Path.GetFullPath(PATH_XML
+ XML_OUTPUT_FILE);
if (strXmlDoc.Length
> 0)
{
if (File.Exists(xmlOutputFile))
{
File.Delete(xmlOutputFile);
}
XmlDocument
xmlDocOut = new
XmlDocument();
xmlDocOut.LoadXml(strXmlDoc);
xmlDocOut.Save(xmlOutputFile);
bIsResult = File.Exists(xmlOutputFile);
}
else
{
throw (new
FileNotFoundException(ERR_EMPTY_XML
+ xmlOutputFile));
}
}
catch (Exception
ex)
{
Console.WriteLine(ERR_LABEL_OUTPUT
+ ex.ToString());
}
/////////////////////////////////////
// Close all RegistryKeys
if (rkDocxAsValue
!= null)
{
rkDocxAsValue.Close();
rkDocxAsValue = null;
}
if (rkDocxAttachedSchema
!= null)
{
rkDocxAttachedSchema.Close();
rkDocxAttachedSchema = null;
}
if (rkSchemaLibrary
!= null)
{
rkSchemaLibrary.Close();
rkSchemaLibrary = null;
}
if (rkCurrentUser
!= null)
{
rkCurrentUser.Close();
rkCurrentUser = null;
}
if (bIsResult)
{
Console.WriteLine(MSG_SUCCESS_RESUT);
}
Console.WriteLine(MSG_PRESS_ANY_KEY);
Console.ReadKey();
}
}
}
Файл: docx2xml.cs
Здесь из части /word/settings.xml MS Word 2007 документа
выбирается значение атрибута w:val у элемента
w:attachedSchema, содержащее название
xsd-схемы в библиотеке схем для содержания данного документа
(в переменную DocxAttachedSchema). Далее,
в библиотеке схем под ключом HKEY_CURRENT_USER\Software\Microsoft\Schema Library\ в системном реестре
по названию схемы (у подключа: \Schema_Employee\0
в нашем случае) у значения Location определяется
место расположения файла xsd-схемы на локальном диске
(в переменную PathSchemaLocation). Затем выбирается
часть документа /word/document.xml, над которой
совершается xslt-преобразование из файла
genXmlFromDocument.xslt для получения
xml-данных содержания документа, а в качестве первого
дочернего элемента к корневому элементу добавляется корневой элемент из
xsd-файла-схемы, передаваемой как параметр
prmDocXsd, тем самым формируется так называемая
inline-схема внутри нашего xml-файла
результата. Наконец, если всё вышеописанное было выполнено без ошибок, то
формируется файл результата с названием: XML_OUTPUT_FILE(documentData.xml)
в подкаталоге XML.
Выполнив код из docx2xml.cs для нашего MS
Word 2007 документа в подкаталоге XML мной был
получен следующий результат:
<?xml
version="1.0"
encoding="utf-8"
standalone="yes"?>
<VFPData>
<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
mixed="true">
<xsd:choice
maxOccurs="unbounded">
<xsd:element
name="employee"
minOccurs="0"
maxOccurs="unbounded">
<xsd:complexType
mixed="true">
<xsd:sequence>
<xsd:element
name="lastname">
<xsd:simpleType>
<xsd:restriction
base="xsd:string">
<xsd:maxLength
value="20"
/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element
name="firstname">
<xsd:simpleType>
<xsd:restriction
base="xsd:string">
<xsd:maxLength
value="10"
/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element
name="birthdate"
type="xsd:date"
minOccurs="0"
/>
<xsd:element
name="notes"
minOccurs="0">
<xsd:simpleType>
<xsd:restriction
base="xsd:string">
<xsd:maxLength
value="2147483647"
/>
</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>
<employee>
<lastname>Buchanan</lastname>
<firstname>Steven</firstname>
<birthdate>1955-03-04</birthdate>
<notes>Steven
Buchanan graduated from St. Andrews University, ...</notes>
</employee>
<employee>
<lastname>Callahan</lastname>
<firstname>Laura</firstname>
<birthdate>1958-01-09</birthdate>
<notes>Laura
received a BA in psychology from the University ...</notes>
</employee>
<employee>
<lastname>Davolio</lastname>
<firstname>Nancy</firstname>
<birthdate>1968-12-08</birthdate>
<notes>Education
includes a BA in psychology from Colorado ...</notes>
</employee>
<employee>
<lastname>Dodsworth</lastname>
<firstname>Anne</firstname>
<birthdate>1969-07-02</birthdate>
<notes>Anne
has a BA degree in English from St. Lawrence College. ...</notes>
</employee>
<employee>
<lastname>Fuller</lastname>
<firstname>Andrew</firstname>
<birthdate>1952-02-19</birthdate>
<notes>Andrew
received his BTS commercial and a Ph.D. in international ...</notes>
</employee>
</VFPData>
Файл: documentData.xml
В подкаталог XML примеров кода я также поместил
VFP-код CreateVfpCursorFromXml.prg,
результат выполнения которого показан на рисунке ниже:
Рис.14
Как видим, наша цель оказалась достигнутой! :-) Нами получены данные
исходного MS Word 2007 документа в виде таблицы в
xml-формате, причём используя VFP-класс
XMLAdapter, такие xml-данные
легко преобразуются в соответствующий VFP-курсор.
Приведённый выше код из файла docx2xml.cs может быть переписан и для выполнения из-под
VFP. У меня это получилось так:
#DEFINE
PATH_COMMON_TOOLS "..\..\CommonTools\"
#INCLUDE
"..\..\CommonTools\SolWestWindEx.h"
#INCLUDE
"..\..\CommonTools\FrameworkWrap.h"
#DEFINE
RUS_LANG .T.
#DEFINE
TMP_OUT .T.
#DEFINE
CRLF
CHR(13)
+ CHR(10)
#DEFINE
SW_NORMAL 1
#DEFINE
PATH_XML "..\..\XML\"
#DEFINE
PATH_DOCX "..\..\DOCX\"
#DEFINE
PATH_XSLT "..\..\XSLT\"
#DEFINE
XSLT_FILE "genXmlFromDocument.xslt"
#DEFINE
XML_OUTPUT_FILE "documentData.xml"
#DEFINE
XSLT_FILE_FIRST_RESULT "getXmlDocument.xslt"
#DEFINE
XML_FILE_SECOND_RESULT "document.xml"
#DEFINE
URI_SETTINGS_XML "/word/settings.xml"
#DEFINE
URI_DOCUMENT_XML "/word/document.xml"
#DEFINE
KEY_SCHEMA_LIBRARY "Software\Microsoft\Schema
Library"
#DEFINE
LOCATION_NAME_VALUE "Location"
#DEFINE
USE_ENCODING "utf-8"
#DEFINE
XML_PI '<?xml version="1.0" encoding="' +
USE_ENCODING + '" standalone="yes"?>' + CRLF
#DEFINE
MYDOC_FOLDER "MyDocuments"
#IF
RUS_LANG
#DEFINE
TEMPLATE_DOCX "Пример VFP-таблицы в виде MS
Word 2007.docx"
#DEFINE
ERR_VFP_VERSION "Извините, данный код
предназначен для VFP 8.0 (или выше)."
#DEFINE
ERR_LABEL_OUTPUT "*** Возникла ошибка: "
#DEFINE
ERR_DIR_NOTFOUND "Не найден каталог: "
#DEFINE
ERR_FILE_NOTFOUND "Не найден файл: "
#DEFINE
ERR_EMPTY_XML "Нет xml-данных для: "
#DEFINE
ERR_EMPTY_XSLTRES "Результат
xslt-преобразований пуст для: "
#DEFINE
MSG_SUCCESS_RESUT "Процесс успешно завершён!"
#ELSE
#DEFINE
TEMPLATE_DOCX "Example VFP-table in MS
Word 2007.docx"
#DEFINE
ERR_VFP_VERSION "Sorry, this code for VFP 8.0
(or later)."
#DEFINE
ERR_LABEL_OUTPUT "*** Error appears: "
#DEFINE
ERR_DIR_NOTFOUND "Not found directory: "
#DEFINE
ERR_FILE_NOTFOUND "Not found file: "
#DEFINE
ERR_EMPTY_XML "Empty xml-data for: "
#DEFINE
ERR_EMPTY_XSLTRES "Empty result of
xslt-transformation for: "
#DEFINE
MSG_SUCCESS_RESUT "Process is successfully
completed!"
#ENDIF
CLEAR
SET DEFAULT TO (LEFT(SYS(16),
RAT("\",
SYS(16))))
IF VERSION(5) < 800
? ERR_VFP_VERSION
RETURN
.F.
ENDIF
PRIVATE poBridge
&& as DotNetBridge OF
..\..\CommonTools\DotNetBridge.prg && FULLPATH(PATH_COMMON_TOOLS +
"DotNetBridge.prg")
LOCAL loException
as Exception;
,lbError as
Boolean;
,lbResult as
Boolean
TRY
*///////////////////////////////////////////////////////////////////////
*// Check existence of external files
IF
!DIRECTORY(PATH_COMMON_TOOLS)
ERROR
(ERR_DIR_NOTFOUND +
LOWER(FULLPATH(PATH_COMMON_TOOLS)))
ENDIF
IF !DIRECTORY(PATH_XML)
ERROR
(ERR_DIR_NOTFOUND +
LOWER(FULLPATH(PATH_XML)))
ENDIF
IF !DIRECTORY(PATH_DOCX)
ERROR
(ERR_DIR_NOTFOUND +
LOWER(FULLPATH(PATH_DOCX)))
ENDIF
IF !DIRECTORY(PATH_XSLT)
ERROR
(ERR_DIR_NOTFOUND +
LOWER(FULLPATH(PATH_XSLT)))
ENDIF
LOCAL lcFileXslt
as String
lcFileXslt =
LOWER(FULLPATH(PATH_XSLT))
+ XSLT_FILE
IF
!FILE(lcFileXslt)
ERROR
101, lcFileXslt
ENDIF
*///////////////////////////////////////////////////////////////////////
*// Get docx-file from My Documents folder
LOCAL
loWSH
as
WScript.Shell
loWSH =
CREATEOBJECT("WScript.Shell")
LOCAL
lcDocxFolder
as String
lcDocxFolder =
loWSH.SpecialFolders(MYDOC_FOLDER)
LOCAL
lcFileDocx
as String
lcFileDocx = lcDocxFolder + "\_" +
TEMPLATE_DOCX
IF
!FILE(lcFileDocx)
ERROR
101, lcFileDocx
ENDIF
*////////////////////////////////////////////////////////////////
*// Create DotNetBridge object
LOCAL
lcBridgeFileProc
as String
lcBridgeFileProc =
LOWER(FULLPATH(PATH_COMMON_TOOLS
+ BRIDGE_PROC))
IF
!FILE(lcBridgeFileProc)
ERROR
101, lcBridgeFileProc
ENDIF
IF NOT (JUSTSTEM(lcBridgeFileProc)
$ LOWER(SET("Procedure")))
SET PROCEDURE TO
(lcBridgeFileProc)
ADDITIVE
ENDIF
poBridge =
CREATEOBJECT(BRIDGE_CLASS)
IF VARTYPE(poBridge)
# 'O'
ERROR
'Can not create ' + BRIDGE_CLASS + ' object.'
ENDIF
*////////////////////////////////////////////////////////////////
*// Set procedure to "FrameworkWrap.prg"
LOCAL
lcFrameworkWrapFileProc
as String
lcFrameworkWrapFileProc =
LOWER(FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg"))
IF
!FILE(lcFrameworkWrapFileProc)
ERROR
101, lcFrameworkWrapFileProc
ENDIF
IF NOT (JUSTSTEM(lcFrameworkWrapFileProc)
$ LOWER(SET("Procedure")))
SET PROCEDURE TO
(lcFrameworkWrapFileProc)
ADDITIVE
ENDIF
*////////////////////////////////////////////////////////////////
*// LoadAssembly ASSEMBLY_SYSTEM
IF
!poBridge.LoadAssembly(ASSEMBLY_SYSTEM)
ERROR
poBridge.cErrorMsg
ENDIF
*//////////////////////////////////////////////////////////////////////////////
*// Creation some objects from ASSEMBLY_SYSTEM, necessary during processing
LOCAL
loEnc
as
Encoding
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loEnc =
CREATEOBJECT("Encoding",
USE_ENCODING)
LOCAL
lnUriKindRelative
as Integer
lnUriKindRelative =
poBridge.GetStaticProperty("System.UriKind", "Relative")
IF EMPTY(lnUriKindRelative)
ERROR
poBridge.cErrorMsg
ENDIF
LOCAL loUriWordSettingsXml
as
Uri
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loUriWordSettingsXml =
CREATEOBJECT("Uri",
URI_SETTINGS_XML, lnUriKindRelative)
LOCAL
loUriWordDocumentXml
as
Uri
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loUriWordDocumentXml =
CREATEOBJECT("Uri",
URI_DOCUMENT_XML, lnUriKindRelative)
LOCAL
lnFileModeOpen
as Integer
lnFileModeOpen =
poBridge.GetStaticProperty("System.IO.FileMode", "Open")
IF EMPTY(lnFileModeOpen)
ERROR
poBridge.cErrorMsg
ENDIF
LOCAL lnFileAccessRead
as Integer
lnFileAccessRead =
poBridge.GetStaticProperty("System.IO.FileAccess", "Read")
IF EMPTY(lnFileAccessRead)
ERROR
poBridge.cErrorMsg
ENDIF
LOCAL loMstrXmlDoc
as
MemoryStream
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loMstrXmlDoc =
CREATEOBJECT("MemoryStream")
*////////////////////////////////////////////////////////////////
*// LoadAssembly ASSEMBLY_WINDOWSBASE
IF
!poBridge.LoadAssembly(ASSEMBLY_WINDOWSBASE)
lcErrMsg = loBridge.cErrorMsg
ERROR
(lcErrMsg)
ENDIF
*///////////////////////////////////////////////////////////////////////
*// Get package of FileDocx and prepare for its processing
LOCAL
loPackage
as
Package
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loPackage =
CREATEOBJECT("Package",
lcFileDocx)
*-- Get PackagePart
of FileDocx for URI_SETTINGS_XML(/word/settings.xml)
LOCAL
loPackagePartSettings
as
PackagePart
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loPackagePartSettings =
CREATEOBJECT("PackagePart";
,loPackage.GetPart(loUriWordSettingsXml.GetOwnerFwObject()))
*-- ... and its
stream in Read mode
LOCAL
loPpStrmSettingsXml
as
Stream
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loPpStrmSettingsXml =
CREATEOBJECT("Stream";
,loPackagePartSettings.GetStream(lnFileModeOpen, lnFileAccessRead))
*-- Get PackagePart
of FileDocx for URI_DOCUMENT_XML(/word/document.xml)
LOCAL
loPackagePartDocument
as
PackagePart
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loPackagePartDocument =
CREATEOBJECT("PackagePart";
,loPackage.GetPart(loUriWordDocumentXml.GetOwnerFwObject()))
*-- ... and its
stream in Read mode
LOCAL
loPpStrmDocumentXml
as
Stream
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loPpStrmDocumentXml =
CREATEOBJECT("Stream";
,loPackagePartDocument.GetStream(lnFileModeOpen, lnFileAccessRead))
*////////////////////////////////////////////////////////////////
*// LoadAssembly ASSEMBLY_SYSTEM_XML
IF
!poBridge.LoadAssembly(ASSEMBLY_SYSTEM_XML)
ERROR
poBridge.cErrorMsg
ENDIF
*///////////////////////////////////////////////////////////////////////
*// Load stream SettingsXml as xml document
LOCAL
loXmlDocSettings
as
XmlDocument
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXmlDocSettings =
CREATEOBJECT("XmlDocument")
loXmlDocSettings.Load(loPpStrmSettingsXml.GetOwnerFwObject())
LOCAL
loXmlDocSettingsRoot
as
XmlElement
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXmlDocSettingsRoot =
CREATEOBJECT("XmlElement",
loXmlDocSettings.DocumentElement)
LOCAL
lcNsp_w
as String
lcNsp_w =
loXmlDocSettingsRoot.GetNamespaceOfPrefix("w")
LOCAL
loNsMngr
as
XmlNamespaceManager
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loNsMngr =
CREATEOBJECT("XmlNamespaceManager",
loXmlDocSettings.NameTable)
loNsMngr.AddNamespace("w", lcNsp_w)
*///////////////////////////////////////////////////////////////////////
*// Get AttachedSchema name
LOCAL
loXnAttachedSchema
as
XmlNode
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXnAttachedSchema =
CREATEOBJECT("XmlNode";
,loXmlDocSettingsRoot.SelectSingleNode("w:attachedSchema/@w:val",
loNsMngr.getOwnerFwObject()))
LOCAL
lcDocxAttachedSchema
as String
lcDocxAttachedSchema =
loXnAttachedSchema.Value
*///////////////////////////////////////////////////////////////////////////////
*// Get Location for XSD-schema from Microsoft Schema Library in system
registry
LOCAL
lcPathSchemaLocation
as String
LOCAL loRkCurrentUser
as
RegistryKey
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loRkCurrentUser =
CREATEOBJECT("RegistryKey";
,poBridge.GetStaticProperty("Microsoft.Win32.Registry", "CurrentUser"))
LOCAL
loRkSchemaLibrary
as
RegistryKey
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loRkSchemaLibrary =
CREATEOBJECT("RegistryKey",
loRkCurrentUser.OpenSubKey(KEY_SCHEMA_LIBRARY))
LOCAL
loRkDocxAttachedSchema
as
RegistryKey
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loRkDocxAttachedSchema =
CREATEOBJECT("RegistryKey",
loRkSchemaLibrary.OpenSubKey(lcDocxAttachedSchema + "\0"))
lcPathSchemaLocation = loRkDocxAttachedSchema.GetValue(LOCATION_NAME_VALUE)
*///////////////////////////////////////////////////////////////////////////////
*// Load xsd-schema and get its root element
LOCAL
loXmlDocXsd
as
XmlDocument
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXmlDocXsd =
CREATEOBJECT("XmlDocument")
loXmlDocXsd.Load(lcPathSchemaLocation)
LOCAL
loXmlDocXsdDocumentElement
as
XmlElement
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXmlDocXsdDocumentElement =
CREATEOBJECT("XmlElement",
loXmlDocXsd.DocumentElement)
*///////////////////////////////////////////////////////////////////////////////
*// Prepare and execute the xslt-transformation
*// with document.xml and by using xslt-file genXmlFromDocument.xslt
LOCAL
lcStrXmlDoc
as String
LOCAL loXmlDoc
as
XmlDocument
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXmlDoc =
CREATEOBJECT("XmlDocument")
loXmlDoc.Load(loPpStrmDocumentXml.GetOwnerFwObject())
LOCAL
loXsltArgumentList
as
XsltArgumentList
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXsltArgumentList =
CREATEOBJECT("XsltArgumentList")
loXsltArgumentList.AddParam("prmEncoding", "", USE_ENCODING)
IF
!EMPTY(lcDocxAttachedSchema)
loXsltArgumentList.AddParam("prmSchemaUri", "", lcDocxAttachedSchema)
ENDIF
IF VARTYPE(loXmlDocXsdDocumentElement)
= 'O'
loXsltArgumentList.AddParam("prmDocXsd", "",
loXmlDocXsdDocumentElement.CreateNavigator())
ENDIF
LOCAL loXtwXmlDoc
as
XmlTextWriter
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXtwXmlDoc =
CREATEOBJECT("XmlTextWriter";
,loMstrXmlDoc.GetOwnerFwObject(), loEnc.GetOwnerFwObject())
LOCAL
loXslXmlDocTrans
as
XslCompiledTransform
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXslXmlDocTrans =
CREATEOBJECT("XslCompiledTransform")
loXslXmlDocTrans.Load(lcFileXslt)
loXslXmlDocTrans.Transform(loXmlDoc.CreateNavigator();
,loXsltArgumentList.GetOwnerFwObject(), loXtwXmlDoc.GetOwnerFwObject())
lcStrXmlDoc = loEnc.GetString(loMstrXmlDoc.GetBuffer(), 0,
loMstrXmlDoc.Length)
LOCAL
lnPos
as Integer
lnPos =
ATC("<",
lcStrXmlDoc)
IF
lnPos = 0
ERROR
(ERR_EMPTY_XSLTRES +
lcFileGenXsltFromTemplate)
ENDIF
lcStrXmlDoc =
SUBSTRC(lcStrXmlDoc,
lnPos)
lnPos = ATC("<?xml",
lcStrXmlDoc)
IF
lnPos = 0
lcStrXmlDoc = XML_PI + lcStrXmlDoc
ENDIF
*///////////////////////////////////////////////////////////////////////////////
*// Load & save result of xslt-transformation
LOCAL
loXmlDocXml
as
XmlDocument
OF
..\..\CommonTools\FrameworkWrap.prg
&& FULLPATH(PATH_COMMON_TOOLS
+ "FrameworkWrap.prg")
loXmlDocXml =
CREATEOBJECT("XmlDocument")
loXmlDocXml.LoadXml(lcStrXmlDoc)
loXmlDocXml.Save(FULLPATH(PATH_XML)
+ XML_OUTPUT_FILE)
lbResult = .T.
? MSG_SUCCESS_RESUT
CATCH TO loException
lbError = .T.
LOCAL
lcMsg
as String
lcMsg = "Error: " +
LTRIM(STR(loException.ErrorNo))
+ CRLF ;
+ "LineNo: " + LTRIM(STR(loException.LineNo))
+ CRLF ;
+ "Message: " + IIF(EMPTY(loException.Message),
'Unknown error', loException.Message)
? ERR_LABEL_OUTPUT
? lcMsg
FINALLY
loWSH =
NULL
IF TYPE('poBridge') = 'O' AND !ISNULL(poBridge)
*-- Remove all
objects, which keeping resources of memory
loRkDocxAttachedSchema =
NULL
loRkSchemaLibrary =
NULL
loRkCurrentUser =
NULL
loPpStrmSettingsXml =
NULL
loPpStrmDocumentXml =
NULL
loMstrXmlDoc =
NULL
loXtwXmlDoc =
NULL
loPackage =
NULL
poBridge.Unload()
poBridge = NULL
ENDIF
ENDTRY
RETURN lbResult
Файл: docx2xml.prg из проекта DocxToXmlByUseFramework_20.pjx
Не стал я здесь упрощать код для улучшения восприятия... как надеюсь
восприятие от этого не пострадало. :-) Обратите внимание: разрушение объектов,
удерживающих ресурсы, производится явно в блоке FINALLY.
Это потому, что в противном случае, мы не управляем последовательностью
уничтожения объектов, а такая последовательность может быть важна. Например,
уничтожение любых объектов-покрытий MS Framework-классов
должно выполняться до уничтожений переменной ссылки poBridge,
т.к. в событиях Destroy() соответствующих классов
именно используя её происходит освобождение соответствующих ресурсов.
В заключении коснёмся ещё одной темы: преобразование xml-данных
в html-представление, т.к. MS Word
2007 способен воспринимать такие данные. Если предположить, что на входе
у нас всегда будет "плоская таблица" с данными в xml-формате
(возможно в качестве первого элемента, имеющие элемент xsd-схемы),
то XSLT-преобразование таких данных в
html-формат могло бы выглядеть так:
<?xml
version="1.0"
encoding="utf-8"?>
<!-- file: XmlTableToHtm.xslt
-->
<xsl:stylesheet
version="1.0"
xmlns="http://www.w3.org/TR/html4"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="xsi
xsd xs msxsl xsl">
<xsl:output
method="html"
version="4.0"
encoding="utf-8"
indent="no"
doctype-public="-//W3C//DTD
HTML 4.0 Transitional//EN"
/>
<xsl:variable
name="prmTableLoc">
<xsl:text>таблица</xsl:text>
</xsl:variable>
<!-- Test: is the first data element as
element of xsd-schema -->
<xsl:variable
name="varIsSchema">
<xsl:choose>
<xsl:when
test="local-name(/./*/*[1])='schema'">
<xsl:text>true</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>false</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable
name="varFirstDataElement">
<xsl:choose>
<xsl:when
test="$varIsSchema='true'">
<xsl:text>2</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>1</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Get name of first data element as
table name -->
<xsl:variable
name="varTable">
<xsl:value-of
select="local-name(/./*/*[number($varFirstDataElement)])"
/>
</xsl:variable>
<!-- Get xsd-schema if exist
-->
<xsl:variable
name="varSchema">
<xsl:if
test="$varIsSchema='true'">
<xsl:copy-of
select="/./*/*[1]"
/>
</xsl:if>
</xsl:variable>
<!-- Parsing from root ...
-->
<xsl:template
match="/">
<html>
<head>
<xsl:text
disable-output-escaping="yes"><![CDATA[<meta
http-equiv="Content-Type" content="text/html; charset=utf-8">]]></xsl:text>
<title>
<xsl:value-of
select="$varTable"
/>
<xsl:text>
- </xsl:text>
<xsl:value-of
select="$prmTableLoc"
/>
</title>
<style
type="text/css">
body
{font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 8.5pt;}
h4
{font-family: Verdana, Arial, Helvetica, sans-serif;
color: darkblue;
font-size: 9.0pt;}
th
{font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 8.5pt;
color: darkblue;
background-color: WhiteSmoke;}
td
{font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 8.5pt;}
.even
{font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 8.5pt;
background-color: Azure;}
</style>
</head>
<body>
<h4
align="center">
<xsl:value-of
select="$varTable"
/>
<xsl:text>
- </xsl:text>
<xsl:value-of
select="$prmTableLoc"
/>
</h4>
<table
align="center"
border="1"
cellspacing="0"
cellpadding="2">
<!-- For each elements with
name=$varTable -->
<xsl:for-each
select="/./*/*[local-name()=$varTable]">
<xsl:if
test="position()=1">
<!-- In case first position: generate
table row with columns headers -->
<tr>
<!-- For each column...
-->
<xsl:for-each
select="*">
<th>
<!-- Select name of element
-->
<xsl:value-of
select="local-name()"
/>
</th>
</xsl:for-each>
</tr>
</xsl:if>
<!-- Define class to current row
-->
<xsl:variable
name="varClass">
<xsl:if
test="position()
mod 2">
<xsl:text>even</xsl:text>
</xsl:if>
</xsl:variable>
<!-- Generate table row with data...
-->
<tr>
<!-- For each column...
-->
<xsl:for-each
select="*">
<!-- Generate column value
-->
<xsl:call-template
name="getColValue">
<xsl:with-param
name="prmClass"
select="$varClass"
/>
<xsl:with-param
name="prmValue"
select="."
/>
</xsl:call-template>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<!-- Generate column value
(<td>...</td>) -->
<xsl:template
name="getColValue">
<xsl:param
name="prmClass"
/>
<xsl:param
name="prmValue"
/>
<td>
<xsl:attribute
name="vAlign">
<xsl:text>top</xsl:text>
</xsl:attribute>
<xsl:if
test="string-length($prmClass)>0">
<!-- Add class attribute if need...
-->
<xsl:attribute
name="class">
<xsl:value-of
select="$prmClass"
/>
</xsl:attribute>
</xsl:if>
<!-- Get name of current element
-->
<xsl:variable
name="varName">
<xsl:value-of
select="local-name()"
/>
</xsl:variable>
<!-- Try get type of current element
-->
<xsl:variable
name="varType">
<xsl:choose>
<xsl:when
test="$varIsSchema='true'">
<!-- ... try get type from xsd-schema
-->
<xsl:choose>
<xsl:when
test="msxsl:node-set($varSchema)/.//*[local-name()='element'
and @name=$varName]/@type">
<xsl:value-of
select="msxsl:node-set($varSchema)/.//*[local-name()='element'
and @name=$varName]/@type"
/>
</xsl:when>
<xsl:when
test="msxsl:node-set($varSchema)/.//*[local-name()='element'
and
@name=$varName]/*[local-name()='simpleType']/*[local-name()='restriction']/@base">
<xsl:value-of
select="msxsl:node-set($varSchema)/.//*[local-name()='element'
and
@name=$varName]/*[local-name()='simpleType']/*[local-name()='restriction']/@base"
/>
</xsl:when>
<!-- ... insert your code here for any
other types... -->
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<!-- ... otherwise try get type
according to name of current element -->
<xsl:choose>
<xsl:when
test="$varName='birthdate'">
<xsl:text>date</xsl:text>
</xsl:when>
<!-- ... insert your code here for any
other types... -->
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Remove prefix from element type
-->
<xsl:variable
name="varTypeValue">
<xsl:choose>
<xsl:when
test="contains($varType,
':')">
<xsl:value-of
select="substring-after($varType,
':')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="$varType"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if
test="$varTypeValue='decimal'
or $varTypeValue='int' or $varTypeValue='date'">
<!-- Add align attribute if need...
-->
<xsl:attribute
name="align">
<xsl:text>right</xsl:text>
</xsl:attribute>
</xsl:if>
<!-- Convert value according to type of
current element -->
<xsl:choose>
<xsl:when
test="$varTypeValue='date'">
<!-- ... convert to russian date format
-->
<xsl:value-of
select="msxsl:format-date($prmValue,
'dd.MM.yyyy')" />
</xsl:when>
<!-- ... insert your code here for any
other types... -->
<xsl:otherwise>
<!-- ... otherwise: simple copy of
current value -->
<xsl:value-of
disable-output-escaping="yes"
select="$prmValue"
/>
</xsl:otherwise>
</xsl:choose>
</td>
</xsl:template>
</xsl:stylesheet>
Файл: XmlTableToHtm.xslt
Чтобы попробовать применить это преобразование к данных из только что
полученного нами файла documentData.xml,
можно использовать утилиту командной строки msxsl.exe,
которую можно свободно и бесплатно загрузить по ссылке, приведённой в разделе Ссылки по теме.
Для этого создадим в подкаталоге XML командный файл:
msxsl.exe documentData.xml ../XSLT/XmlTableToHtm.xslt -o documentData.htm -u '4.0'
Файл: documentDataToHtm.cmd
Здесь над данными из файла documentData.xml
текущего каталога применяется преобразование XmlTableToHtm.xslt
из подкаталога XSLT, а результат сохраняется в виде файла
documentData.htm текущего
каталога, при этом используется MSXML версии 4.0.
После выполнения командного файла documentDataToHtm.cmd
просмотр в Internet Explorer полученного файла
documentData.htm у меня даёт вот такую картинку:
Рис.15
XSLT-преобразование над xml-данными
можно выполнить и средствами MS Word 2007, причём
полученный результат иметь в виде текущего документа. Это можно сделать либо из
командой строки, либо создав ярлык вот с такой командой:
"%PROGRAMFILES%\Microsoft Office\Office12\WINWORD.EXE" /p..\XSLT\XmlTableToHtm.xslt employee.XML
Команда в ярлыке: ShowByUseEmployeeToHtml
Свойства ярлыка ShowByUseEmployeeToHtml
Здесь преобразование ..\XSLT\XmlTableToHtm.xslt применяется над данными из файла
employee.XML текущего каталога. Двойной клик
мышки на этом ярлыке у меня приводит к открытию MS Word
2007 и отображению в нём вот такой картинки:
Рис.16
Выполнить заданное XSLT-преобразование над
xml-данными средствами MS Word
2007 можно и из самой оболочки Word-а, для
этого следует открыть xml-данные т.с. "в чистом виде"
(файл: ..\XML\documentData.xml в нашем случае),
а затем, через кнопку "Обзор..." в панели справа "XML
документ" открыть и файл ..\XSLT\XmlTableToHtm.xslt.
Примерно так, как показано на рисунке ниже:
Рис.17
Если всё Вами проделано удачно, а как надеюсь, что это действительно так :-),
то в конечном счёте у Вас должно получиться примерно то, что получилось и у меня:
Рис.18
XML-файл может содержать инструкцию типа:
<?xml-stylesheet
type="text/xsl"
href="..\XSLT\XmlTableToHtmForIE.xslt"?>
в которой атрибут href указывает на файл
xslt-преобразования, которое следует применить к
данным xml-файла во время отображения его в
Internet Explorer. Однако, имеется небольшая проблема
с версией XML-процессора, который будет при этом
использован. Например, у меня на компьютере это версия 3.0 (можно посмотреть под
ключом: HKEY_CLASSES_ROOT\Msxml2.DOMDocument\CurVer\ в
системном реестре.) Функциональные возможности версии 3.0 по отношению к версии
4.0, для которой написан файл XmlTableToHtm.xslt
несколько ограничены. Например, отсутствует функция msxsl:format-date(),
позволяющая преобразовать значение даты в требуемый формат. Поэтому
специально для демонстрации этой возможность в каталог XSLT
я подложил слегка подправленный файл XmlTableToHtmForIE.xslt,
где вместо функции msxsl:format-date() использовано
явное формирование даты, путём разбора значения даты в формате
xml на её составляющие: год, месяц, день и последующим формированием
строки в требуемом для отображения формате, а посмотреть результат можно открыв
файл documentDataForIE.xml в
Internet Explorer.
К сожалению xml-формат, в котором мы можем получить
данные из базы данных, достаточно разнообразен, чтобы утверждать, что
преобразование XmlTableToHtm.xslt способно
обработать данные любой "плоской таблицы", представленной в
xml-формате. Так, чтобы продемонстрировать наличие "других" форматов
(отличных от того, в котором находится файл documentData.xml), в файле
getXmlFromADODB.js мной написан код на JavaScript,
позволяющий сделать выборку с MS SQL Server через
SQLOLEDB Provider с помощью SQL-команды:
"SELECT TOP 5 lastname, firstname, birthdate, notes FROM employees ORDER BY lastname, firstname" из базы данных
Northwind, а результат при этом, сохраняемый в файл documentDataSql.xml
с помощью команды oRs.Save("documentDataSql.xml", adPersistXML), у меня получился следующим:
<xml
xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'
xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'
xmlns:rs='urn:schemas-microsoft-com:rowset'
xmlns:z='#RowsetSchema'>
<s:Schema
id='RowsetSchema'>
<s:ElementType
name='row'
content='eltOnly'>
<s:AttributeType
name='lastname'
rs:number='1'
rs:writeunknown='true'>
<s:datatype
dt:type='string'
dt:maxLength='20'
rs:maybenull='false'/>
</s:AttributeType>
<s:AttributeType
name='firstname'
rs:number='2'
rs:writeunknown='true'>
<s:datatype
dt:type='string'
dt:maxLength='10'
rs:maybenull='false'/>
</s:AttributeType>
<s:AttributeType
name='birthdate'
rs:number='3'
rs:nullable='true'
rs:writeunknown='true'>
<s:datatype
dt:type='dateTime'
rs:dbtype='timestamp'
dt:maxLength='16'
rs:scale='3'
rs:precision='23'
rs:fixedlength='true'/>
</s:AttributeType>
<s:AttributeType
name='notes'
rs:number='4'
rs:nullable='true'
rs:writeunknown='true'>
<s:datatype
dt:type='string'
dt:maxLength='1073741823'
rs:long='true'/>
</s:AttributeType>
<s:extends
type='rs:rowbase'/>
</s:ElementType>
</s:Schema>
<rs:data>
<z:row
lastname='Buchanan'
firstname='Steven'
birthdate='1955-03-04T00:00:00'
notes='Steven
Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in
1976. Upon joining the company as a sales representative in 1992, he spent 6
months in an orientation program at the Seattle office and then returned to his
permanent post in London. He was promoted to sales manager in March 1993. Mr.
Buchanan has completed the courses "Successful
Telemarketing"
and "International
Sales Management."
He is fluent in French.'/>
<z:row
lastname='Callahan'
firstname='Laura'
birthdate='1958-01-09T00:00:00'
notes='Laura
received a BA in psychology from the University of Washington. She has also
completed a course in business French. She reads and writes French.'/>
<z:row
lastname='Davolio'
firstname='Nancy'
birthdate='1948-12-08T00:00:00'
notes='Education
includes a BA in psychology from Colorado State University in 1970. She also
completed "The
Art of the Cold Call."
Nancy is a member of Toastmasters International.'/>
<z:row
lastname='Dodsworth'
firstname='Anne'
birthdate='1966-01-27T00:00:00'
notes='Anne
has a BA degree in English from St. Lawrence College. She is fluent in French
and German.'/>
<z:row
lastname='Fuller'
firstname='Andrew'
birthdate='1952-02-19T00:00:00'
notes='Andrew
received his BTS commercial in 1974 and a Ph.D. in international marketing from
the University of Dallas in 1981. He is fluent in French and Italian and reads
German. He joined the company as a sales representative, was promoted to sales
manager in January 1992 and to vice president of sales in March 1993. Andrew is
a member of the Sales Management Roundtable, the Seattle Chamber of Commerce,
and the Pacific Rim Importers Association.'/>
</rs:data>
</xml>
Файл: documentDataSql.xml
Взгляните как он устроен и сравните его с содержимым файла documentData.xml.
Как надеюсь, разница заметна? :-) Очевидно, что ранее приведённое здесь
xslt-преобразование
XmlTableToHtm.xslt не отработает корректно для
данных такого формата. Что же, придётся подправить... и после внесения
соответствующих изменений у меня получился во такой результат:
<?xml
version="1.0"
encoding="utf-8"?>
<!-- file: XmlTableToHtmForADODB.xslt
-->
<xsl:stylesheet
version="1.0"
xmlns="http://www.w3.org/TR/html4"
xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'
xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'
xmlns:rs='urn:schemas-microsoft-com:rowset'
xmlns:z='#RowsetSchema'
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="s
dt rs z msxsl xsl">
<xsl:output
method="html"
version="4.0"
encoding="utf-8"
indent="no"
doctype-public="-//W3C//DTD
HTML 4.0 Transitional//EN"
/>
<xsl:variable
name="varSchema">
<xsl:copy-of
select="/./*/*[local-name()='Schema']"
/>
</xsl:variable>
<xsl:template
match="/">
<html>
<head>
<xsl:text
disable-output-escaping="yes"><![CDATA[<meta
http-equiv="Content-Type" content="text/html; charset=utf-8">]]></xsl:text>
<style
type="text/css">
body
{font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 8.5pt;}
h4
{font-family: Verdana, Arial, Helvetica, sans-serif;
color: darkblue;
font-size: 9.0pt;}
th
{font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 8.5pt;
color: darkblue;
background-color: WhiteSmoke;}
td
{font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 8.5pt;}
.even
{font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 8.5pt;
background-color: Azure;}
</style>
</head>
<body>
<table
align="center"
border="1"
cellspacing="0"
cellpadding="2">
<!-- For each
row-element -->
<xsl:for-each
select="/./*/*[local-name()='data']/*[local-name()='row']">
<xsl:if
test="position()=1">
<!-- In case first position: generate
table row with columns headers -->
<tr>
<!-- For each column...
-->
<xsl:for-each
select="@*">
<th>
<!-- Select name of
attribute
-->
<xsl:value-of
select="local-name()"
/>
</th>
</xsl:for-each>
</tr>
</xsl:if>
<!-- Define class to current row
-->
<xsl:variable
name="varClass">
<xsl:if
test="position()
mod 2">
<xsl:text>even</xsl:text>
</xsl:if>
</xsl:variable>
<!-- Generate table row with data...
-->
<tr>
<!-- For each column...
-->
<xsl:for-each
select="@*">
<!-- Generate column value
-->
<xsl:call-template
name="getColValue">
<xsl:with-param
name="prmClass"
select="$varClass"
/>
<xsl:with-param
name="prmValue"
select="."
/>
</xsl:call-template>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<!-- Generate column value
(<td>...</td>) -->
<xsl:template
name="getColValue">
<xsl:param
name="prmClass"
/>
<xsl:param
name="prmValue"
/>
<td>
<xsl:attribute
name="vAlign">
<xsl:text>top</xsl:text>
</xsl:attribute>
<xsl:if
test="string-length($prmClass)>0">
<!-- Add class attribute if need...
-->
<xsl:attribute
name="class">
<xsl:value-of
select="$prmClass"
/>
</xsl:attribute>
</xsl:if>
<!-- Get name of current
attribute
-->
<xsl:variable
name="varName">
<xsl:value-of
select="local-name()"
/>
</xsl:variable>
<!-- Try get type of current
value
-->
<xsl:variable
name="varType">
<!-- ... try get type from schema
-->
<xsl:choose>
<xsl:when
test="msxsl:node-set($varSchema)/.//*[local-name()='AttributeType'
and @name=$varName]/*[local-name()='datatype']/@dt:type">
<xsl:value-of
select="msxsl:node-set($varSchema)/.//*[local-name()='AttributeType'
and @name=$varName]/*[local-name()='datatype']/@dt:type"
/>
</xsl:when>
<!-- ... insert your code here for any
other types... -->
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if
test="$varType='decimal'
or $varType='int' or $varType='dateTime'">
<!-- Add align attribute if need...
-->
<xsl:attribute
name="align">
<xsl:text>right</xsl:text>
</xsl:attribute>
</xsl:if>
<!-- Convert value according to type of
current value -->
<xsl:choose>
<xsl:when
test="$varType='dateTime'">
<!-- ... convert to russian date format
-->
<xsl:value-of
select="msxsl:format-date($prmValue,
'dd.MM.yyyy')" />
</xsl:when>
<!-- ... insert your code here for any
other types... -->
<xsl:otherwise>
<!-- ... otherwise: simple copy of
current value -->
<xsl:value-of
disable-output-escaping="yes"
select="$prmValue"
/>
</xsl:otherwise>
</xsl:choose>
</td>
</xsl:template>
</xsl:stylesheet>
Файл: XmlTableToHtmForADODB.xslt
Запуск преобразования
можно осуществить воспользовавшись командным файлом
documentDataToHtmForADODB.cmd,
а результат можно посмотреть в созданном при этом файле:
documentDataForADODB.htm.
На другой подход решения подобной задачи, можно посмотреть в сообщении К
использованию dbf2docx , где в тексте документа используются "элементы
управления содержимым" в MS Word Office 2007/2010, в то время как xml-данные встраиваются в документ в виде CustomXmlPart-части.
Код преобразования шаблона документа, содержащего одну таблицу с одной строкой
данных в xml-формате, преобразующего такой шаблон в
документ, содержащий множество строк, взятых из xml-файла
данных, написан на C# в MS VS .NET
2010(SP1) с использованием
Open XML
SDK 2.0 for Microsoft Office .
Код примеров, xml-данные и xsd-схемы
к ним помещены мной в файл docxtbl.zip на странице Примеры кода и утилиты.
Ниже краткое описание содержания подкаталогов в файле docxtbl.zip:
- VFP_CreateXmlXsd - VFP-код для генерации xml+xsd данных
в подкаталог \XML
- XML - employee.XML - исходные данные для MS Word 2007
документа, employee.XSD - соответствующая схема, используемая при создании
шаблона "Пример VFP-таблицы в виде MS Word 2007.docx" из подкаталога \DOCX.
Здесь также расположены файлы: documentData.xml, documentData.htm, documentDataForADODB.htm, documentDataForIE.xml, documentDataSql.xml, documentDataToHtm.cmd, documentDataToHtmForADODB.cmd, getXmlFromADODB.js
и ярлык ShowByUseEmployeeToHtml
- XSLT - содержит файлы:
genXmlFromDocument.xslt, genXsltFromTemplate.xslt, XmlTableToHtm.xslt, XmlTableToHtmForADODB.xslt, XmlTableToHtmForIE.xslt
- DOCX - шаблон MS Word 2007 документа ("Пример
VFP-таблицы в виде MS Word 2007.docx"), использующего схему из
\XML\employee.XSD. Здесь также расположены файлы: getContentsUseWinRAR.cmd, fromContentsToDocxUseWinRAR.cmd
- solVS_NET2005_Framework_20 -
C#-код приложений xml2docx
и docx2xml
- VFP_FillDocx - VFP-код для
наполнения шаблона DOCX\"Пример VFP-таблицы в виде MS Word 2007.docx"
данными из XML\employee.XML
- VFP_DocxToXml -
VFP-код для извлечения xml-данных
(в XML\documentData.xml)
из файла MS Word 2007 документа (DOCX\"Пример
VFP-таблицы в виде MS Word 2007.docx")
- CommonTools -
VFP-код для поддержки обращений из-под VFP к
MS Framework 2.0 библиотекам классов
- bin - ClrHost.dll, DotNetBridge.dll - см. назначение и
код на стр. Примеры кода и утилиты в
xlsxtbl.zip
- Краткое введение в XML.
- Office 2007 Open XML Format, Алексей Федоров.
- Microsoft Office 2007 Open XML. Часть 2, Алексей Федоров.
- Форматы Ecma Office Open XML: вопросы и ответы.
- Работа с XML.
- OpenXML Developer
- Hosting the .NET Runtime in Visual FoxPro, by Rick Strahl
- Building Word 2007 Documents Using Office Open XML Formats, by Erika Ehrli, Brian Jones
- Walkthrough: Word 2007 XML Format, by Erika Ehrli
- Introducing the Office (2007) Open XML File Formats, by Frank Rice
- How to: Manipulate Office Open XML Formats Documents, by Frank Rice
- Manipulating Word 2007 Files with the Open XML Object Model (Part 1 of 3), by Frank Rice
- How To Use Compressed (Zipped) Folders in Windows XP
- Convert an InfoPath 2007 form into a Word 2007 document using XSLT and C#, by S.Y.M. Wong-A-Ton
- Command Line Transformation Utility (msxsl.exe).
- MSXML 4.0 Service Pack 2 (Microsoft XML Core Services).
- Технология XSLT, Алексей Валиков
- Как получить таблицу данных из xlsx-файла у приложения Excel из MS Office 2007 используя структуру данных формата Open XML?
- Как получить dbf-таблицы из xls-файла при наличии групп в данных?.
- К
использованию dbf2docx
- Open XML
SDK 2.0 for Microsoft Office
- Примеры кода и утилиты - см. код и данные к этой статье в docxtbl.zip