вторник, 5 апреля 2016 г.

[VB.Net]Сравнение фрагментов XML-документа.

  1. Предварительные замечания
  2. Немного об инструментах
  3. Где взять XML-документ для изучения и отладки
  4. Подготовка преобразования

Предварительные замечания.

Офисные пакеты OpenOffice, LibreOffice, возможно другие, основанные на том же коде, имеют интересную функцию добавления фильтров экспорта и импорта документов. Фильтры представляют из себя преобразование XSLT, а в случае с фильтром импорта - к нему еще может прилагаться и шаблон документа. Добавив такое преобразование к программе, можно сохранять документы офиса в любом XML формате, а так же из любого XML документа, для которого существует фильтр импорта загружать данные, которые при этом будут представлены в нужном виде.

XSLT - язык преобразования XML-документов и для того, чтобы написать его код, требуется знание структуры исходного документа. Структура документа описана в спецификации
OASIS Open Office Specification - OpenDocument-v1.1.pdf. Объем спецификации составляет 738 страниц, да еще и на английском языке. Поэтому узнать из спецификации какие-то подробности о формате документа - задача вполне выполнимая, но изучить это все и писать на основе этого преобразования - задача крайне трудновыполнимая. Тем не менее, в общем и целом, структура документа понятна интуитивно и с такими документами можно работать, просто слегка изучив их структуру и посмотрев как в них оформлены отдельные элементы, способ отображения которых заранее известен.

Я взялся за эту работу и то, что буду выяснять в процессе исследования опишу в нескольких заметках. Я не ставлю перед собой цели написать фильтр для какого-то реального формата данных, просто изучаю и описываю.
У читателей предполагаются хотя бы базовые знания XSLT. Кроме того, не мешало бы прочитать по этой теме что-то что уже написано ранее другими авторами. Например вот неплохая статья.
Расширение возможностей редактирования документов в OpenOffice при помощи XSLT.
Сам я ее только просмотрел что называется "по диагонали", так что, возможно, что-то из написанного там, будет повторено здесь в том или ином виде.

Немного об инструментах

Я уже упомянул о том, что чтение дальнейшего текста предполагает хотя бы базовые знания XSLT, а человек, знакомый с этим форматом, наверняка знает какими инструментами надо пользоваться для написания преобразований и скорей всего у него есть какие-то свои предпочтения, так что просто напишу пару слов о том, что я сам использую.

Для начала надо решить какая версия XSLT будет использоваться. LibreOffice (который использую я) в диалоговом окне создания и изменения фильтра предлагает указать, следует ли использовать для данного фильтра XSLT 2.0, это обстоятельство наталкивает на мысль, что данная версия поддерживается пакетом, но не тут-то было. При попытке использовать вторую версию выпадает сообщение о том, что программа не может сохранить данные в файл. На самом деле она просто не может выполнить преобразование, такие же сообщения она показывает, если передать ей преобразование с ошибками. Проблема решается установкой расширения. Я нашел вот такое
xslt2-transformer — LibreOffice Extensions. В принципе работает корректно, правда после сохранения в окне программы остается заполненный наполовину прогрессбар внизу, показывающий, что сохранение зависло. Как это лечится - не знаю, но программа вроде после этого продолжает нормально работать. Кроме того, совсем не обязательно использовать встроенные инструменты преобразования, вместо этого можно сохранить документ в XML формате и преобразовывать любым доступным набором инструментов(правда это уже не то).

В качестве редактора я использую Visual Studio 2013, там есть встроенная поддержка XSLT 1, но нет поддержки XSLT 2 (насколько мне известно и не планируется). Но зато редактируется XSLT в XML-редакторе с поддержкой схем, так что можно скачать схему XSLT 2 здесь, добавить ее в студию, там надо немного отредактировать схему, поскольку она импортирует пару пространств имен, ссылаясь при этом на сетевые ресурсы. Из этих элементов импорта надо удалить атрибут schemaLocation
То есть вот этот фрагмент

<!--

The declaration of xml:space and xml:lang may need to be commented out because

of problems processing the schema using various tools

--><xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/><!--

    An XSLT stylesheet may contain an in-line schema within an xsl:import-schema element,

    so the Schema for schemas needs to be imported

--><xs:import namespace="http://www.w3.org/2001/XMLSchema" schemaLocation="http://www.w3.org/2001/XMLSchema.xsd"/>

Нужно отредактировать так, чтобы он выглядел вот так.

<!--

The declaration of xml:space and xml:lang may need to be commented out because

of problems processing the schema using various tools

-->     

<xs:import namespace="http://www.w3.org/XML/1998/namespace" />

 <!--

    An XSLT stylesheet may contain an in-line schema within an xsl:import-schema element,

    so the Schema for schemas needs to be imported

-->

 <xs:import namespace="http://www.w3.org/2001/XMLSchema" />

В Visual Studio есть собственные версии импортируемых схем и студия о них знает, так что указывать их местоположение не нужно. А вот если используются другие редакторы, то, возможно, придется скачать документы для импорта и указать их реальное положение в системе. Приятная новость в том, что для этой схемы студия сохраняет такую же подсветку синтаксиса, как и для XSLT 1, то есть собственно элементы XSLT подсвечиваются другим цветом, нежели элементы других пространств имен. Кроме того не происходит никакого конфликта схем за счет того, что указывается версия в самом документе и, в зависимости от версии, будет выбрана схема(здесь надо сказать, что в свойствах документа надо отключить схему для первой версии, иначе конфликт все-таки будет). К неприятным новостям относится то, что при использовании этой схемы студия не контролирует корректность XPath-выражений, использования в них необъявленных переменных и т. д. То есть для студии это просто XML-документ.

Для преобразования XSLT 2 использую The SAXON XSLT and XQuery Processor. Есть бесплатная редакция Saxon-HE 9.6, ее вполне достаточно. Использовать утилиту командной строки предельно просто Saxon Getting Started. Я просто написал cmd - файл и запускаю, когда надо просмотреть результат. Можно запускать прямо из командной строки, можно использовать подключение внешних инструментов, такая возможность есть во многих редакторах кода и IDE. Тут уж кому что больше нравится.

Где взять XML-документ для изучения и отладки.

Документ, естественно, надо создать в том приложении, для которого и будет создаваться фильтр. Но документ сохраненный в формате .dot - это бинарный файл, а не XML, и что же делать? На самом деле этот файл - всего лишь zip-архив. Ему можно поменять расширение на .zip и просмотреть содержимое с помощью любого архиватора или даже проводника Windows. В составе пакета будет насколько разных документов, наиболее интересный из которых - content.xml. Но тут вся проблема в том, что если идти по этому пути, то документ, с которого будет писаться преобразование, будет не совсем тем документом, который на самом деле используют приложения OpenOffice. То есть содержимое у него будет тем же, но в силу того, что оно там разделено на несколько файлов, такой документ может не содержать всей необходимой для обработки информации.

Выходом может быть использование другого формата для сохранения. В программах "офиса" есть возможность сохранить документ именно в одном XML-файле. Для программы Writer, с которой мы пока и будем работать, при сохранении документа надо выбрать тип файла "Flat Document ODF Text Document (.fodt)(*.fodt)" и сохранить. Это XML-документ, его можно сразу использовать для преобразования, но мы будем его использовать в основном для изучения.

Для отладки фильтра этот документ подходит только в том случае, если результатом преобразования будет XML или, к примеру, HTML. А вот если надо получить строго отформатированный текст, то может случиться так, что во время отладки будет все нормально, а когда фильтр будет установлен, то вдруг окажется, что весь текст сохраняется в одну строку. Причина здесь в форматировании документа при сохранении. В .fodt документ при сохранении вставляются переносы строк, отступы и прочие пробельные литеры, которые не имеют значения с точки зрения логики разбора XML, поскольку там обычно строки нормализуются, а вот при генерации текста все эти лишние текстовые узлы могут "выйти боком". Проблема решаема, но если отладка будет производиться не на таком документе, который используется самой программой, то никогда нельзя быть уверенным в том, что результат будет тем же, что и во время отладки. И как бы в этом случае не пришлось переписывать кучу кода.

Так где же взять образец документа-источника? Ответ на этот вопрос заложен в самой теме поста. Нужно просто создать фильтр, который будет выполнять тождественное преобразование документа, но только без форматирования.

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="no"/>

    <xsl:template match="@* | node()">

        <xsl:copy>

            <xsl:apply-templates select="@* | node()"/>

        </xsl:copy>

    </xsl:template>

</xsl:stylesheet>

Для фильтров лучше выделить отдельную папку, поскольку они не копируются программой в какой-то специальный каталог, а там просто сохраняется адрес фильтра. Для добавления надо зайти в мену программы
Сервис -> Настройка фильтров XML...
В диалоговом окне нажимаем кнопку Создать. Появляется новое окно для ввода параметров нового фильтра.

В поле "Имя фильтра" надо ввести имя, которое будет отображаться в списке фильтров, в нашем случае это может быть имя "Тождественное преобразование".
В списка "Приложение" обязательно надо выбрать программу, для которой этот фильтр будет доступен. Иначе он не будет доступен нигде, а OpenOffice его не даст и удалить тоже. Например при попытке создания нового фильтра с таким же именем он может сообщить, что фильтр с таким именем уже существует. Кстати такое бывает и после удаления фильтра и по-моему не лечится.
В поле "Имя типа файла" надо ввести текст, который будет отображаться в диалоговом окне сохранения файла, в списке типов файлов.
Ну и в поле "Расширение файла" надо ввести расширение имени файла, это может быть как существующий формат, так и собственный. В нашем случае можно оставить xml
На вкладке "Преобразование" нам надо будет только ввести путь к файлу нашего преобразования в поле "XSLT для экспорта".

Жмем "ОК", закрываем диалог фильтров. Далее, если документ уже загружен, открываем меню
Файл -> Экспорт, выбираем в диалоговом окне путь, имя и тип файла (тот, который мы указали при создании фильтра). Сохраняем.

Пара слов о содержимом файла. Естественно он может иметь произвольное содержимое, но лучше придерживаться некоторых правил, чтобы проще было анализировать. В файле должны содержаться те элементы, которые будут обрабатываться, изложенные в понятной форме. Ну например: содержимое текста курсивом, будет просто словом "курсив" на отдельной строке; текст, выделенный жирным будет просто словом "жирный" и т. д. Желательно даже, чтобы большинство примеров состояли из одного слова. Его потом легче искать. Причем не только визуально, но и программно. Допустим нужно найти жирный текст, чтобы получить информацию о нем, можно написать преобразование, а искать узел будет легко по содержимому. Вот такое выражение видимо найдет этот текст
//text()[. = 'Жирный']
Дальше от этого узла можно уже начинать поиск информации о нем. Если текст будет длинным, то вполне возможно, что программа разделит его на несколько узлов с одинаковым форматированием, на отображение это не влияет, но, используя прием, показанный выше, такой текст уже не найдешь. С другой стороны иногда наоборот надо посмотреть, как разделяется текст. Но об этом уже позже. Пример документа я приложу к записи, но нужен он только для того, чтобы понимать то, о чем буду писать, поскольку буду ссылаться именно на этот документ. А так он может быть любым.

Подготовка преобразования.

Поскольку дело мы будем иметь с гибридным документом, включающим довольно большое количество пространств имен, нам придется с этими пространствами работать. Кроме того, желательно сохранить неизменными префиксы пространств имен. Для того, чтобы иметь доступ к этим пространствам надо объявить их все в корневом элементе преобразования. То есть надо их просто скопировать туда. Если мы не хотим, чтобы они попали в результирующий документ, то корневому элементу преобразования еще надо будет добавить атрибут
exclude-result-prefixes="#all"
Или можно через пробел перечислить префиксы, которые мы хотим исключить.
Для XSLT 2 нужно добавить пространство имен, делающее доступными функции XPath 2,
xmlns:fn="http://www.w3.org/2005/xpath-functions"
Кроме того, можно объявить несколько собственных пространств имен, для собственных функций. Я добавил такое
xmlns:my="urn:my-functions"
Ну и версию, XSLT тоже надо указать правильно. Таким образом в результате у нас для XSLT 2 получится вот такая заготовка преобразования.

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="urn:my-functions"

                xmlns:fn="http://www.w3.org/2005/xpath-functions"

                 exclude-result-prefixes="#all"

xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"

xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"

xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"

xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"

xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"

xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"

xmlns:xlink="http://www.w3.org/1999/xlink"

xmlns:dc="http://purl.org/dc/elements/1.1/"

xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"

xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"

xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"

xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"

xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"

xmlns:math="http://www.w3.org/1998/Math/MathML"

xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"

xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"

xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"

xmlns:ooo="http://openoffice.org/2004/office"

xmlns:ooow="http://openoffice.org/2004/writer"

xmlns:oooc="http://openoffice.org/2004/calc"

xmlns:dom="http://www.w3.org/2001/xml-events"

xmlns:xforms="http://www.w3.org/2002/xforms"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:rpt="http://openoffice.org/2005/report"

xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"

xmlns:xhtml="http://www.w3.org/1999/xhtml"

xmlns:grddl="http://www.w3.org/2003/g/data-view#"

xmlns:officeooo="http://openoffice.org/2009/office"

xmlns:tableooo="http://openoffice.org/2009/table"

xmlns:drawooo="http://openoffice.org/2010/draw"

xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"

xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"

xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0"

xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"

xmlns:css3t="http://www.w3.org/TR/css3-text/"

office:version="1.2"

office:mimetype="application/vnd.oasis.opendocument.text">

    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/">

    </xsl:template>

</xsl:stylesheet>

Теперь все готово. В следующей части начнем изучать документ.

PS
Во вложении пример документа в формате fodt. Документ первоначально создавался в Word, потом был сохранен в odt, а потом уже с помощью LibreOffice в текущий формат. Выкладываю просто для примера, поскольку изучать буду именно его.
PPS
Напоминаю, что при создании фильтра в программе, в диалоговом окне добавления XSLT надо установить флажок, чтобы использовалась именно вторая версия языка, ну и расширение предварительно надо установить.

Комментариев нет :

Отправить комментарий