Программирование

Извлечение CDATA в XSLT: решение проблемы с text()[2]

Пошаговое руководство по извлечению содержимого CDATA из XML-элементов в XSLT. Объяснение проблемы с text()[2] и альтернативные методы решения. Узнайте, почему CDATA не сохраняется в модели XDM.

4 ответа 2 просмотра

Как с помощью XSLT извлечь содержимое CDATA из XML-элемента, который содержит текст, затем CDATA и снова текст? Например, для структуры text1text2 выражение /root/example/text()[2] возвращает пустую строку вместо ожидаемого значения CDATA.

В XSLT невозможно напрямую извлечь содержимое CDATA-секций, так как при парсинге XML в модель данных XDM (XML Data Model) границы CDATA-секций теряются, а весь текст объединяется в последовательность текстовых узлов. Для решения этой проблемы необходимо использовать косвенные методы, такие как обработка всего текстового содержимого элемента и последующее разделение его на части или использование XSLT 2.0+ с функциями типа analyze-string.


Содержание


Основы XSLT и работа с CDATA

При работе с XML и XSLT важно понимать фундаментальное различие между тем, как данные представлены в исходном XML-документе и как они обрабатываются в модели данных XDM (XML Data Model), используемой XPath и XSLT. CDATA (Character Data) - это специальный синтаксис в XML, предназначенный для включения текста, который может содержать символы, которые иначе были бы интерпретированы как разметка.

В модели данных XDM, однако, CDATA-секции не сохраняются как отдельные сущности. Вместо этого все текстовое содержимое, независимо от того, было ли оно в CDATA-секции или в обычном тексте, объединяется в последовательность текстовых узлов. Это фундаментальное ограничение XSLT, которое часто вызывает путаницу у разработчиков.

Для XML-структуры:

xml
<root>
 <example>text1<![CDATA[---]]>text2</example>
</root>

В модели данных XDM элемент example содержит всего один текстовый узел со значением “text1—text2”, а не три отдельных узла, как мог бы ожидать разработчик. Это означает, что стандартные методы доступа к CDATA-секциям напрямую невозможны.

Почему /root/example/text()[2] возвращает пустую строку

Выражение /root/example/text()[2] возвращает пустую строку именно потому, что в модели данных XDM элемент example содержит только один текстовый узел, а не несколько. Давайте разберем, почему это происходит:

Когда XML-документ парсится, все текстовое содержимое внутри элемента, включая текст вне тегов и содержимое CDATA-секций, объединяется в единый текстовый узел. Для нашей структуры:

xml
<example>text1<![CDATA[---]]>text2</example>

В дереве XDM создается только один текстовый узел со значением “text1—text2”. Поэтому:

  • /root/example/text()[1] вернет “text1—text2”
  • /root/example/text()[2] вернет пустую строку, потому что второго текстового узла просто не существует

Это важное понимание модели данных XDM, которое лежит в основе всех операций XSLT и XPath. Разработчики, привыкшие к DOM-модели в других языках программирования, часто ожидают, что CDATA-секции будут представлены как отдельные узлы, но в XDM это не так.

Если вам нужен доступ к CDATA-секциям, вам придется использовать косвенные методы, которые мы рассмотрим в следующих разделах.

Модель данных XDM и CDATA-секции

Модель данных XDM (XML Data Model) - это абстрактная модель, определяющая, как XML-документ представляется в памяти для обработки XSLT и XPath. Ключевой аспект этой модели заключается в том, что она не сохраняет все особенности исходного XML-документа.

Как указано в официальной документации W3C: “Features of a source XML document that are not represented in the XDM tree will have no effect on the operation of an XSLT stylesheet. Examples of such features are entity references, CDATA sections, character references…”

Это означает, что:

  1. CDATA-секции не сохраняются как отдельные узлы - они просто становятся частью текстового содержимого
  2. Границы CDATA теряются - XSLT не может определить, где начиналась и заканчивалась CDATA-секция
  3. Текстовое содержимое объединяется - все текст, независимо от его источника (обычный текст или CDATA), объединяется в последовательность текстовых узлов

Для XML:

xml
<root>
 <example>text1<![CDATA[---]]>text2</example>
</root>

В дереве XDM будет следующая структура:

  • Узел элемента root
  • Узел элемента example
  • Текстовый узел: “text1—text2”

В этой структуре нет отдельного узла для CDATA-секции “—”. Это фундаментальное ограничение модели данных XDM, которое необходимо понимать при работе с XSLT.

Для более сложных сценариев, где требуется обработка CDATA-секций, можно рассмотреть следующие подходы:

  1. Предварительная обработка XML - с помощью скриптов или других инструментов перед передачей в XSLT
  2. Использование XSLT 2.0+ - с расширенными возможностями обработки строк
  3. Постобработка результата - когда XSLT возвращает весь текст, и его можно разделить уже после преобразования

Эти методы мы рассмотрим в следующих разделах.

Альтернативные методы извлечения CDATA в XSLT

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

1. Использование функции string() и последующий анализ

Самый простой метод - получить все текстовое содержимое элемента с помощью функции string() и затем проанализировать его для извлечения нужных частей. Для нашего примера:

xml
<xsl:template match="example">
 <xsl:variable name="all-text" select="string()"/>
 <!-- Анализ строки и извлечение нужных частей -->
</xsl:template>

2. Использование XSLT 2.0+ с analyze-string

В XSLT 2.0 и выше доступна функция analyze-string, которая позволяет использовать регулярные выражения для сложного анализа строк:

xml
<xsl:template match="example">
 <xsl:analyze-string select="string()" regex="text1(---)text2">
 <xsl:matching-substring>
 <CDATA-content><xsl:value-of select="regex-group(1)"/></CDATA-content>
 </xsl:matching-substring>
 </xsl:analyze-string>
</xsl:template>

3. Использование шаблонов для разделения текста

Можно создать шаблон, который разделяет текст на основе известных шаблонов:

xml
<xsl:template match="example">
 <xsl:variable name="all-text" select="string()"/>
 <xsl:variable name="first-part" select="substring-before($all-text, '---')"/>
 <xsl:variable name="second-part" select="substring-after($all-text, '---')"/>
 
 <CDATA-extraction>
 <first-text><xsl:value-of select="$first-part"/></first-text>
 <CDATA-value><xsl:value-of select="'---'"/></CDATA-value>
 <last-text><xsl:value-of select="$second-part"/></last-text>
 </CDATA-extraction>
</xsl:template>

4. Использование расширений процессора XSLT

Некоторые процессоры XSLT предоставляют расширения для доступа к исходному XML, включая CDATA-секции. Например, в Saxon (один из самых популярных процессоров XSLT) можно использовать:

xml
<xsl:template match="example">
 <xsl:value-of select="saxon:parse('&lt;root>&lt;example>' 
 &lt;![CDATA[---]]>&lt;/example>&lt;/root>')/example/text()[2]"/>
</xsl:template>

5. Предварительная обработка XML

Если структура CDATA-секций важна для вашего приложения, рассмотрите возможность предварительной обработки XML с помощью скриптов (Python, JavaScript и т.д.), которые преобразуют CDATA-секции в элементы:

xml
<!-- Исходный XML -->
<example>text1<![CDATA[---]]>text2</example>

<!-- После обработки скриптом -->
<example>
 <text>text1</text>
 <cdata>---</cdata>
 <text>text2</text>
</example>

Затем в XSLT можно легко обращаться к содержимому:

xml
<xsl:template match="example">
 <CDATA-value><xsl:value-of select="cdata"/></CDATA-value>
</xsl:template>

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

Практические примеры работы с CDATA в XSLT

Рассмотрим несколько практических примеров, демонстрирующих различные подходы к работе с CDATA-секциями в XSLT-преобразованиях.

Пример 1: Простое извлечение CDATA-содержимого

Для XML-структуры:

xml
<root>
 <example>text1<![CDATA[---]]>text2</example>
</root>

XSLT-преобразование с использованием XSLT 2.0+:

xml
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
 <result>
 <xsl:for-each select="//example">
 <xsl:variable name="all-text" select="string()"/>
 <xsl:analyze-string select="$all-text" regex="^([^*]+)([^*]+)([^*]+)$">
 <xsl:matching-substring>
 <CDATA-extraction>
 <first-part><xsl:value-of select="regex-group(1)"/></first-part>
 <CDATA-value><xsl:value-of select="regex-group(2)"/></CDATA-value>
 <last-part><xsl:value-of select="regex-group(3)"/></last-part>
 </CDATA-extraction>
 </xsl:matching-substring>
 </xsl:analyze-string>
 </xsl:for-each>
 </result>
 </xsl:template>
</xsl:stylesheet>

Пример 2: Обработка нескольких CDATA-секций

Для более сложной структуры с несколькими CDATA-секциями:

xml
<root>
 <example>text1<![CDATA[---]]>text2<![CDATA[+++]]>text3</example>
</root>

XSLT-преобразование:

xml
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
 <result>
 <xsl:for-each select="//example">
 <xsl:variable name="all-text" select="string()"/>
 <xsl:analyze-string select="$all-text" regex="^([^*]+)([^*]+)([^*]+)([^*]+)([^*]+)$">
 <xsl:matching-substring>
 <CDATA-extraction>
 <part1><xsl:value-of select="regex-group(1)"/></part1>
 <CDATA1><xsl:value-of select="regex-group(2)"/></CDATA1>
 <part2><xsl:value-of select="regex-group(3)"/></part2>
 <CDATA2><xsl:value-of select="regex-group(4)"/></CDATA2>
 <part3><xsl:value-of select="regex-group(5)"/></part3>
 </CDATA-extraction>
 </xsl:matching-substring>
 </xsl:analyze-string>
 </xsl:for-each>
 </result>
 </xsl:template>
</xsl:stylesheet>

Пример 3: Использование XSLT 1.0 без analyze-string

Для процессоров, поддерживающих только XSLT 1.0:

xml
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
 <result>
 <xsl:for-each select="//example">
 <xsl:variable name="all-text" select="."/>
 <xsl:variable name="before-cdata" select="substring-before($all-text, '---')"/>
 <xsl:variable name="after-cdata" select="substring-after($all-text, '---')"/>
 
 <CDATA-extraction>
 <first-text><xsl:value-of select="$before-cdata"/></first-text>
 <CDATA-value><xsl:value-of select="'---'"/></CDATA-value>
 <last-text><xsl:value-of select="$after-cdata"/></last-text>
 </CDATA-extraction>
 </xsl:for-each>
 </result>
 </xsl:template>
</xsl:stylesheet>

Пример 4: Преобразование CDATA в элементы

Если вы хотите преобразовать CDATA-секции в отдельные элементы в выходном XML:

xml
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="example">
 <example-processed>
 <xsl:variable name="all-text" select="."/>
 <xsl:variable name="before-cdata" select="substring-before($all-text, '---')"/>
 <xsl:variable name="after-cdata" select="substring-after($all-text, '---')"/>
 
 <text-before><xsl:value-of select="$before-cdata"/></text-before>
 <cdata-section><xsl:value-of select="'---'"/></cdata-section>
 <text-after><xsl:value-of select="$after-cdata"/></text-after>
 </example-processed>
 </xsl:template>
 
 <xsl:template match="/">
 <root>
 <xsl:apply-templates select="//example"/>
 </root>
 </xsl:template>
</xsl:stylesheet>

Пример 5: Обработка переменной длины CDATA

Для случаев, когда содержимое CDATA-секций переменной длины:

xml
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="/">
 <result>
 <xsl:for-each select="//example">
 <xsl:variable name="all-text" select="string()"/>
 <!-- Разделяем на части по фиксированным шаблонам -->
 <xsl:analyze-string select="$all-text" regex="^([^*]+)([^*]*?)([^*]+)$">
 <xsl:matching-substring>
 <CDATA-extraction>
 <first-part><xsl:value-of select="regex-group(1)"/></first-part>
 <CDATA-content><xsl:value-of select="regex-group(2)"/></CDATA-content>
 <last-part><xsl:value-of select="regex-group(3)"/></last-part>
 </CDATA-extraction>
 </xsl:matching-substring>
 </xsl:analyze-string>
 </xsl:for-each>
 </result>
 </xsl:template>
</xsl:stylesheet>

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

Инструменты и отладка XSLT преобразований

При работе с CDATA-секциями в XSLT преобразованиях правильные инструменты и подходы к отладке могут значительно упростить процесс. Рассмотрим наиболее полезные инструменты и техники.

Инструменты для разработки XSLT

  1. Saxon - один из самых мощных процессоров XSLT, поддерживающий версии 1.0, 2.0 и 3.0. Предоставляет расширенные возможности для отладки и работы с CDATA.

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

  3. XMLSpy - еще один мощный редактор XML с поддержкой XSLT, который помогает визуализировать результаты преобразований.

  4. XSLT Online - веб-сервисы для быстрой проверки XSLT-преобразований без установки дополнительного ПО.

Техники отладки XSLT

  1. Вывод промежуточных результатов - используйте временные шаблоны для вывода значений переменных на разных этапах преобразования:
xml
<xsl:template match="example">
 <xsl:variable name="all-text" select="string()"/>
 
 <!-- Отладочная информация -->
 <debug-info>
 <all-text><xsl:value-of select="$all-text"/></all-text>
 </debug-info>
 
 <!-- Основная логика преобразования -->
</xsl:template>
  1. Использование xsl:message - вывод отладочных сообщений в консоль:
xml
<xsl:template match="example">
 <xsl:variable name="all-text" select="string()"/>
 <xsl:message>Обработка элемента example, весь текст: <xsl:value-of select="$all-text"/></xsl:message>
</xsl:template>
  1. Визуализация модели данных XDM - создайте шаблон, который выводит структуру XDM-дерева:
xml
<xsl:template name="dump-xdm">
 <xsl:param name="node" select="."/>
 <xsl:for-each select="$node/node()">
 <node type="{name()}" value="{.}"/>
 <xsl:if test="node()">
 <xsl:call-template name="dump-xdm">
 <xsl:with-param name="node" select="."/>
 </xsl:call-template>
 </xsl:if>
 </xsl:for-each>
</xsl:template>

Оптимизация производительности XSLT

При работе с большими XML-документами и сложными преобразованиями важно учитывать производительность:

  1. Использование ключей - для эффективного поиска элементов:
xml
<xsl:key name="examples" match="example" use="."/>
  1. Минимизация вычислений - кэшируйте результаты повторяющихся вычислений в переменных:
xml
<xsl:variable name="all-examples" select="//example"/>
<xsl:for-each select="$all-examples">
 <!-- Вместо повторного вычисления //example -->
</xsl:for-each>
  1. Правильное использование шаблонов - избегайте избыточных шаблонов и рекурсивных вызовов.

Интеграция с другими технологиями

  1. Использование JavaScript в XSLT - для сложных операций с текстом можно использовать расширения:
xml
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:js="http://example.com/javascript">
 <xsl:template match="example">
 <xsl:variable name="all-text" select="string()"/>
 <xsl:variable name="cdata-content" select="js:extractCdata($all-text)"/>
 </xsl:template>
</xsl:stylesheet>
  1. Постобработка результатов - когда XSLT завершает свою работу, можно использовать другие инструменты для дополнительной обработки результата.

Правильный выбор инструментов и подходов к отладке может значительно упростить работу с CDATA-секциями в XSLT и помочь быстрее находить и исправлять ошибки в преобразованиях.


Источники

  1. W3C XSLT 1.0 Specification — Базовая спецификация XSLT 1.0 и модель данных XDM: https://www.w3.org/TR/xslt-10/
  2. W3C XSLT 2.0 Specification — Спецификация XSLT 2.0 с расширенными возможностями обработки строк: https://www.w3.org/TR/xslt20/
  3. W3C XSLT 3.0 Specification — Современная спецификация XSLT 3.0 с функцией analyze-string: https://www.w3.org/TR/xslt-30/
  4. Saxonica Documentation — Документация по процессору XSLT Saxon с примерами работы с CDATA: https://www.saxonica.com/
  5. Oxygen XML Editor — Профессиональный инструмент для разработки XSLT с поддержкой отладки: https://www.oxygenxml.com/
  6. XMLSpy Documentation — Документация по редактору XML/XSLT: https://www.altova.com/xmlspy/
  7. XSLT Online Services — Веб-сервисы для тестирования XSLT преобразований: https://www.freeformatter.com/xslt-transformer.html

Заключение

Извлечение содержимого CDATA-секций в XSLT представляет собой интересную задачу, связанную с особенностями модели данных XDM. Как мы выяснили, прямое обращение к CDATA-секциям через /root/example/text()[2] невозможно, потому что в модели данных XDM все текстовое содержимое объединяется в единый текстовый узел.

Для решения этой проблемы существуют несколько подходов, от простых методов с использованием функций substring-before и substring-after до сложных решений с analyze-string в XSLT 2.0+. Выбор конкретного метода зависит от версии XSLT, которую вы используете, и сложности ваших данных.

Ключевым моментом в работе с CDATA в XSLT является понимание того, что границы CDATA-секций не сохраняются в модели данных XDM. Это фундаментальное ограничение, которое необходимо учитывать при разработке XSLT-преобразований.

Для сложных сценариев можно рассмотреть предварительную обработку XML-документа для преобразования CDATA-секций в отдельные элементы, что значительно упростит последующую обработку в XSLT.

Использование правильных инструментов и подходов к отладке, таких как Saxon, Oxygen XML Editor или XSLT Online, может значительно упростить разработку и отладку XSLT-преобразований для работы с CDATA-секциями.

J

В XSLT 1.0 невозможно извлечь содержимое CDATA отдельно от обычного текста, так как при парсинге XML различие между CDATA и обычным текстом теряется. CDATA-секции обрабатываются как обычные текстовые узлы в модели данных XML. Выражение /root/example/text()[2] не возвращает содержимое CDATA, потому что в DOM содержимое элемента example представляет собой один текстовый узел, содержащий всю строку “text1—text2”, а не несколько отдельных текстовых узлов. Для получения всего текстового содержимого элемента используйте /root/example, но выделить именно часть, которая была в CDATA, невозможно средствами XSLT 1.0.

M

В XSLT невозможно напрямую извлечь содержимое CDATA, так как при преобразовании XML в модель данных XDM (XPath Data Model) разделы CDATA преобразуются в обычные текстовые узлы. Как указано в документе: “Features of a source XML document that are not represented in the XDM tree will have no effect on the operation of an XSLT stylesheet. Examples of such features are entity references, CDATA sections, character references…”. Это означает, что границы CDATA-секций не сохраняются в дереве XDM. Все текстовое содержимое, независимо от того, было ли оно в CDATA или обычном тексте, объединяется в последовательность текстовых узлов.

M

В XSLT невозможно напрямую извлекать содержимое CDATA-секций, так как при парсинге XML в модель данных XDM (XML Data Model) CDATA-секции преобразуются в текстовые узлы без сохранения информации о их исходном формате. Как указано в документе: “конструкции, которые могут существовать в исходном XML-документе, но не входят в область действия модели данных, не могут обрабатываться stylesheet и не могут гарантированно оставаться неизменными в выходных данных преобразования. К таким конструкциям относятся границы CDATA-секций, использование сущностей и объявление DOCTYPE”. Это объясняет, почему выражение /root/example/text()[2] возвращает пустую строку - CDATA-секция не сохраняется как отдельный текстовый узел, а объединяется с обычным текстом.

Авторы
Проверено модерацией
НейроОтветы
Модерация