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-данными. Нами будет проделано следующее:

 

Получение xml+xsd-файлов по данным из 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 2007, использующего xsd-схемы данных.

Далее, создадим на основе данных этих двух файлов таблицу в MS Word из MS Office 2007. Ниже шаг за шагом и т.с. в картинках показано, как это можно сделать. Но прежде всего, если у Вас на "Панели быстрого доступа" отсутствует вкладка "Разработчик", то проделайте действия, изображённые на Рис.1:

xlsx1.jpg
Рис.1

т.е. отметьте пункт "Показывать вкладку "Разработчик" на ленте" в диалоге "Параметры Excel" и подтвердите изменения, нажав кнопку "Ok" в правой нижней части окна этого диалога.

Следующим действием загрузим файл-схему employee.XSD в MS Word 2007, для чего открыв новый Word-документ, выполним следующее:

docx2.jpg
Рис.2

В возникшем при этом диалоге "Параметры схемы", определите поля: URI и Псевдоним так, как показано на картинке ниже и подтвердите установку схемы.

docx3.jpg
Рис.3

После удачной установки схемы в правой части окна у Вас должна появиться панель "Структура XML" подобная также изображенная на картинке ниже. Введите текст в качестве заголовка всего документа, выделите его весь мышкой и примените к нему всю схему, ткнув мышкой в нижней части панели "Структура XML" в текст "VFPData {Schema Employee from VFP}" подобно тому, как это сделано на картинке ниже:

docx4.jpg
Рис.4

В возникшем при этом диалоге "Применить ко всему документу?" подтвердите выбором кнопки "Применить ко всему документу". После чего у Вас должно получиться нечто похожее представленному на рисунке ниже:

docx5.jpg
Рис.5

Перемесив позицию курсора к началу конечного элемента VFPData, нажмите мышкой на элемент employee в нижней части панели "Структура XML". Поле чего Вы должны получить то, что показано на рисунке ниже:

docx6.jpg
Рис.6

Последовательно выбирая мышкой подчинённые элементы: lastname, firstname, birthdate, notes у элемента employee, в нижней части панели "Структура XML" и после ввода очередного элемента, перемещая текущую позицию в документе к началу конечного элемента employee, Вы должны получить подобное тому, что изображено на картинке ниже:

docx7.jpg
Рис.7

Следующим шагом, мы должны создать таблицу таким образом, чтобы элемент employee с вложенными в него элементами разместился бы в первой строке создаваемой нами таблицы, а каждый из вложенных элементом попал бы в отведённый ему столбец таблицы. Строку заголовков столбцов таблицы при этом, следует поместить сразу после заголовка документа, но перед элементом employee. Проделав это, Вы должны получить подобное тому, что показано на картинке ниже:

docx8.jpg
Рис.8

К этому же результату можно придти, если сначала создать таблицу с заголовками столбцов и одной пустой строкой в качестве её данных, а затем, выделив всю строку данных в таблице, связать с ней групповой элемент employee. Далее, последовательно смещаясь по столбцам, связать с каждым из них элементы, принадлежащие групповому элементу employee: lastname, firstname, birthdate и birthdate.

Чтобы придать полученной таблице презентабельный вид, можно воспользоваться одним из стилей таблиц, например таким:

docx9.jpg
Рис.9

Наконец, полученный результат сохраняем как MS Word 2007 документ:

docx10.jpg
Рис.10

Следующим шагом нам нужно извлечь xml-данные в формате Open XML у полученного документа. Это можно сделать

@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:

docx11.jpg
Рис.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, я наблюдаю следующую картинку:

docx20.jpg

Как видим, здесь всё в полом соответствии с нашими желаниями. Таким образом, если у нас имеется несколько таблиц с разными xsd-схемами, то мы вполне можем создавать docx-шаблон, содержащий их программно, используя шаблон genTable из файла genXsltTableTemplate.xslt. Только нужно иметь ввиду, что

 

Построение XSLT-преобразования для заполнения шаблона MS Word 2007 документа данными из xml-файла.

Сколько-то разобравшись с содержимым файла document.xml, каковы же будут наши дальнейшие действия? Наша цель на данном этапе: это на основе приведённого выше document.xml, создать файл xslt-преобразования, которое получив бы на входе данные из файла employee.xml, возвратило бы в качестве результата преобразований xml-документ, подобный файлу document.xml, но уже не с пустой строкой в данных у таблицы, а с заполненными всеми строками данных таблицы, взяв их из файла employee.xml. Это можно сделать

Такая работа мной проделана и ниже 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)&lt;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-преобразования, в результате применений которого к файлу-шаблону 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"?>]]>&#xD;&#xA;</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)&lt;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), то после всех этих преобразований документ показан на картинке ниже:

docx12.jpg
Рис.12

Как видите, таблица с данными сформирована и наша цель достигнута! :-) Здесь чтобы показать/скрыть элементы схемы в документе, установите/снимите выделенный красным кружком в панели "Структура XML" соответствующий флажок. Обратите внимание, что MS Word предупреждает о неверном формате даты в поле birthdate, хотя предлагаемый им формат для даты в точности совпадает с тем, который находиться в документе. Полагаю, что проблема в том, что предупреждение возникает из-за того, что дата в документе противоречит не формату даты в документе, а именно формату даты в XSD-схеме, т.е. не в YYYY-MM-DD формате, как требуют того xml-данные. Наконец, в столбце "Комментарий" данные урезаны до длины примерно в 50 символов, т.е. в полном соответствии с тем, как мы преобразовали данные по полю note в xslt-преобразовании getXmlDocument.xslt.

Таким образом, окончательный вид нашей исходной VFP-таблички employee в MS Word документе подобен следующему:

docx13.jpg
Рис.13

 

C#-код приложения для заполнения шаблона MS Word 2007 документа данными из xml-файла.

Показанный на Рис.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, проделайте следующее:

Обратите внимание на значения констант: PATH_XML, PATH_DOCX, PATH_XSLT в коде. Они указывают на папки, являющиеся родительскими по отношению в парки Solution Вашего C#-приложения. Соответственно родительскими должны быть созданы папки: DOCX, XML и XSLT, в которые должны быть помещены файлы:

соответственно. А после успешного выполнения скомпилированного приложения xml2docx.exe, в текущей папке будут созданы файлы: document.xml, getXmlDocument.xslt, а заполненный данными документ "_Пример VFP-таблицы в виде MS Word 2007.docx" будет помещён в папку: C:\Documents and Settings\<ИмяПользователя>\Мои документы\

 

VFP-код приложения для заполнения шаблона MS Word 2007 документа данными из xml-файла.

Используя технику, изложенную в статье Hosting the .NET Runtime in Visual FoxPro, by Rick Strahl external.gif usa.gif, приведённый выше 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():

В методе AddParam():

Если посмотреть описание класса 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(), предварительно определяется действительное количество входных параметров, использованных при создании экземпляра класса и далее, в зависимости от их количества, производится:

После таких пояснений, видимо понятным будет код класса 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... Но в целом, подход всё-таки работает! :-)

 

Построение XSLT-преобразования для извлечения xml-данных из таблицы в MS Word 2007 документе.

Попробуем теперь решить обратную задачу: извлечь из 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"?>]]>&#xD;&#xA;</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

 

C#-код приложения для извлечения xml-данных из таблицы в MS Word 2007 документе. top

Чтобы применить приведённое выше 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, результат выполнения которого показан на рисунке ниже:

docx14.jpg
Рис.14

Как видим, наша цель оказалась достигнутой! :-) Нами получены данные исходного MS Word 2007 документа в виде таблицы в xml-формате, причём используя VFP-класс XMLAdapter, такие xml-данные легко преобразуются в соответствующий VFP-курсор.

 

VFP-код приложения для извлечения xml-данных из таблицы в MS Word 2007 документе.

Приведённый выше код из файла 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() соответствующих классов именно используя её происходит освобождение соответствующих ресурсов.

 

XSLT-преобразование таблицы данных из xml-формата в html-представление.

В заключении коснёмся ещё одной темы: преобразование 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 у меня даёт вот такую картинку:

docx15.jpg
Рис.15

XSLT-преобразование над xml-данными можно выполнить и средствами MS Word 2007, причём полученный результат иметь в виде текущего документа. Это можно сделать либо из командой строки, либо создав ярлык вот с такой командой:

"%PROGRAMFILES%\Microsoft Office\Office12\WINWORD.EXE" /p..\XSLT\XmlTableToHtm.xslt employee.XML
Команда в ярлыке: ShowByUseEmployeeToHtml

docx19.jpg
Свойства ярлыка ShowByUseEmployeeToHtml

Здесь преобразование ..\XSLT\XmlTableToHtm.xslt применяется над данными из файла employee.XML текущего каталога. Двойной клик мышки на этом ярлыке у меня приводит к открытию MS Word 2007 и отображению в нём вот такой картинки:

docx16.jpg
Рис.16

Выполнить заданное XSLT-преобразование над xml-данными средствами MS Word 2007 можно и из самой оболочки Word-а, для этого следует открыть xml-данные т.с. "в чистом виде" (файл: ..\XML\documentData.xml в нашем случае), а затем, через кнопку "Обзор..." в панели справа "XML документ" открыть и файл ..\XSLT\XmlTableToHtm.xslt. Примерно так, как показано на рисунке ниже:

docx17.jpg
Рис.17

Если всё Вами проделано удачно, а как надеюсь, что это действительно так :-), то в конечном счёте у Вас должно получиться примерно то, что получилось и у меня:

docx18.jpg
Рис.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 &#x22;Successful Telemarketing&#x22; and &#x22;International Sales Management.&#x22; 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 &#x22;The Art of the Cold Call.&#x22; 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 external.gif, где в тексте документа используются "элементы управления содержимым" в MS Word Office 2007/2010, в то время как xml-данные встраиваются в документ в виде CustomXmlPart-части. Код преобразования шаблона документа, содержащего одну таблицу с одной строкой данных в xml-формате, преобразующего такой шаблон в документ, содержащий множество строк, взятых из xml-файла данных, написан на C# в MS VS .NET 2010(SP1) с использованием Open XML SDK 2.0 for Microsoft Office external.gif usa.gif.

Краткое описание кода примеров.

Код примеров, xml-данные и xsd-схемы к ним помещены мной в файл docxtbl.zip на странице Примеры кода и утилиты. Ниже краткое описание содержания подкаталогов в файле docxtbl.zip:

 

Ссылки по теме.


По вопросам, связанным с этим веб-узлом, обращайтесь по адресу:
© Michael Drozdov, 2000-2013. Все права защищены.
Последнее изменение страницы: Чт, 31 января 2013
 
Hosted by uCoz