XML/XSLT: Борьба с форматированием

При работе с XML/XSLT существует неприятный момент: форматирование XML-документа влияет на его структуру. В этой статье мы рассмотрим несколько примеров "граблей" и путей их обхода.

Форматирование XML-элементов

Рассмотрим XML-документ:

<test>
  <item/>
  <item/>
  <item/>
</test>

Будет иметь следующую структуру:

TEXT-узлы в XML-документе

Довольно странно видеть вместо трёх потомков элемента test целых семь.

Этого можно избежать, вставив в XSLT-преобразование следующий код:

<xsl:strip-space elements="*"/>

Теперь приведённый выше XML будет восприниматься XSLT-процессором следующим образом:

XML-документ без пустых TEXT-элементов

Однако при этом стоит помнить, что так же будет воспринят и такой XML:

<test>
  <item/>
  <item> </item>
  <item><![CDATA[ ]]></item>
</test>

Если вам действительно нужен пробел, то я советую писать:

<item>&#160;</item>

xsl:strip-space имеет силу только над символами: пробел (#x20), возврат каретки (#xD), перевод строки (#xA), знак табуляции (#x9).

Форматирование CDATA

Рассмотрим следующий XML-документ:

<test>
  <item>
    <![CDATA[hello world]]>
  </item>
</test>

Применим к этому документу XSLT-преобразование:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="no" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="/">
    <text>
      <xsl:value-of select="//test/item"/>
    </text>
  </xsl:template>
</xsl:stylesheet>

Вы ожидаете, что на выходе будет:

<text>hello world</text>

Но на самом деле получится:

<text>
    hello world
  </text>

Неожиданно и неприятно. Правильным советом здесь является использовать функцию normalize-space в xsl:value-of

Преобразование будет выглядеть так:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">  
  <xsl:output method="xml" indent="no" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*"/>  
  
  <xsl:template match="/">
    <text>
      <xsl:value-of select="normalize-space(//test/item)"/>
    </text>
  </xsl:template>
</xsl:stylesheet>

Теперь мы получаем желаемый результат:

<text>hello world</text>

Однако, то же самое получится и для такого XML-документа:

<test>
  <item>
    <![CDATA[  hello     world]]>
  </item>
</test>

Функция normalize-space удалит все лишние пробельные символы в TEXT-элементе.

Объединение TEXT-элементов

У XML-элемента не может быть несколько подряд идущих TEXT-элементов. Подряд идущие CDATA и строки будут объединены в один TEXT-элемент. Например, рассмотрим XML-документ:

<test>
  <item><![CDATA[hello]]> <![CDATA[world]]>!</item>
</test>

Он будет иметь следующую структуру:

Слияние TEXT-элементов

Заключение

Форматирования CDATA в XML легче не допускать, чем с ним бороться. В противном случае стоит использовать описанные средства.

Статья подготовлена по материалам статьи: Tricky whitespace handling in XSLT