1 (изменено: Rumata, 2013-07-15 10:55:19)

Тема: XML/XSL: Обработка всех текстовых узлов всех тегов

Решил выделить вопрос коллеги Serge Yolkin и свой же ответ в отдельную тему. Взято отсюда HTA: XML Viewer.

Serge Yolkin пишет:

А не подскажете ли, как с помощью xslt преобразования можно обрабатывать текстовые ноды по отдельности? Ну, для примера: из xml

<root>
  <tag1>
    текст1
    <tag2>
      текст2
      <tag3/>
      текст3
    </tag2>
  </tag1>
  текст4
</root>

получить html

<p>текст1</p>
<p>текст2</p>
<p>текст3</p>
<p>текст4</p>

причём, в общем случае (без привязки к именам тэгов и структуре дерева).

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


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

<xsl:template match="//*/text()">
<xsl:if test="normalize-space() != ''">
<p><xsl:value-of select="normalize-space()" /></p>
</xsl:if>
</xsl:template>

</xsl:stylesheet>

Или более короткий вариант:


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

<xsl:template match="//*/text()">
<p><xsl:value-of select="normalize-space()" /></p>
</xsl:template>

</xsl:stylesheet>

Если же необходимо чтобы каждый новый тег <p> распологался на отдельной строке (как показано в запросе):


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

<xsl:template match="//*/text()">
<xsl:text><![CDATA[
]]></xsl:text>
<p><xsl:value-of select="normalize-space()" /></p>
</xsl:template>

</xsl:stylesheet>
( 2 * b ) || ! ( 2 * b )

2 (изменено: Serge Yolkin, 2013-07-15 15:58:55)

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Опа! Слишком упростил пример. Правда, текущая проблема всё же решена, но общего понимания нет (просветления - тоже). Хотя срочность решения теперь ниже, всё же очень хочется разобраться: можно ли средствами xsl работать с дочерними текстовыми нодами каждого тега по отдельности? Новый пример:
xml

<root>
  текст1
  <tag1>
    текст2
    <tag2>
      текст3
      <tag3/>
      текст4
    </tag2>
    текст5
  </tag1>
  текст6
  <tag4>
    текст7
    <tag5/>
    текст8
  </tag4>
  текст9
</root>

html

<p>root</p>
<p>текст1</p>
<p>текст6</p>
<p>текст9</p>
<p>tag1</p>
<p>текст2</p>
<p>текст5</p>
<p>tag2</p>
<p>текст3</p>
<p>текст4</p>
<p>tag4</p>
<p>текст7</p>
<p>текст8</p>

Собственно, как обойти ноды, даже вместе с атрибутами каждой, понятно из сценария коллеги mozers. А вот работать так же с текстовыми узлами у меня не получается.
[Off]
декларативность xsl вызывает когнитивный диссонанс в моих императивных мозгах.
[/Off]

3

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Ссылки по теме
XML DOM
XSLT
XPath

можно ли средствами xsl работать с дочерними текстовыми нодами каждого тега по отдельности?

Можно. В сообщении 74 (HTA: XML Viewer) содержится описание моей версии программы, которая отображает любые xml документы в виде динамического дерева тегов. Если есть желание разбираться...

Serge Yolkin
Решение показать пошагово, с комментариями?

( 2 * b ) || ! ( 2 * b )

4

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

За ссылки спасибо, но мой английский не настолько хорош... Пока ковыряю вот отсюда. Но не хватает волшебного пенделя. Т.е., то что читаю, вроде понимаю, а вот пойти дальше рассмотренных примеров не получается...

Пример из сообщения №74 разбирал, но как выводить текстовые ноды в порядке их появления в XML я понял, не получается вывести их сразу после имени тэга и до дочерних узлов, даже если они идут после их закрывающих тэгов. Т.е., в приведённом примере (пост 2 в этой теме) текст1, 6 и 9 должны идти подряд, несмотря на то, что текст6 упомянут после поддерева tag1, а 9 - ещё дальше

На счёт пошагово и с комментариями - сделал стойку и навострил уши.

Возможно, задача действительно специфична, но она иллюстрирует точку моего (текущего) непонимания. Разберусь, пойду дальше - появятся новые.
Тэг <p> на выходе выбран потому, что в 3 раза короче <div>. Оборачивать в нужный html я уже немного научился, поэтому выходная конструкция особого значения не имеет.
Да, и на счёт в отдельной строке - не стОит: читаемость хуже, а отформатировать я могу при необходимости.

5 (изменено: Rumata, 2013-07-16 12:47:39)

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Не надо в стойку . Было время я вообще ничего не понимал в словоохотливости xslt. Сейчас тоже есть темные для меня моменты. А что знаю - тем делюсь.

Начну, пожалуй, с того, что xsl описывает каким должен получится результирующий документ. В нашем случае (второй запрос) надо показать имя тега и все текстовые элементы тега.

Основа преобразований - шаблон, набор правил, которые применяются к подходящему элементу.
1.


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

<!-- Чтобы преобразовать входной файл, надо связать xml-элементы и шаблоны -->
<xsl:template match="*"><!-- * - все теги, то есть шаблон будет применен к любым тегам -->
<p><xsl:value-of select="name()" /></p><!-- выбрать имя тега и отобразить -->
</xsl:template>

</xsl:stylesheet>

Это работает, но только для одно, корневого тега. XML-документ состоит из одного корневого тега и правило применеяется только к нему. Чтобы заработало для всех тегов, надо применить аналогичное правило для всех вложенных тегов:
2.


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

<xsl:template match="*">
<p><xsl:value-of select="name()" /></p>
<xsl:apply-templates select="*" /><!-- применить ко всем вложенным тегам -->
</xsl:template>

</xsl:stylesheet>

Теперь все теги видны. Но надо еще получить все текстовые ноды. Аналогично, опишем шаблон и привяжем его к текстовым узлам:
3.


<xsl:template match="text()"><!-- только для текстовых узлов -->
<p><xsl:value-of select="normalize-space()" /></p><!-- удалить пробелы вокруг текста -->
</xsl:template>

Простое решение не будет работать, потому что шаблон выбирает только первый текстовый узел из тега - оставляя другие узлы за бортом
4.


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

<xsl:template match="*">
<p><xsl:value-of select="name()" /></p>
<p><xsl:value-of select="text()" /></p>
</xsl:template>

</xsl:stylesheet>

Поэтому объединим шаблоны из примеров 2 и 3 и добавим форматирование:
5.


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

<!-- формат выходных данных - html-документ, начиная каждый тег с новой строки -->
<xsl:output method="html" version="1.0" indent="yes" />

<xsl:template match="*">
<p><xsl:value-of select="name()" /></p>
<xsl:apply-templates select="text()" />
<xsl:apply-templates select="*" />
</xsl:template>

<xsl:template match="text()">
<p><xsl:value-of select="normalize-space()" /></p>
</xsl:template>

</xsl:stylesheet>

Почти работает. Почти - потому что показаны пустые теги, не содержащие теста. Подредактируем правило и рассмотрим окончательный вариант:
6.


<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" indent="yes" />

<xsl:template match="*[text()]"><!-- соответствует любому тегу с непустым текстовым узлом -->
<p><xsl:value-of select="name()" /></p>
<xsl:apply-templates select="text()" />
<xsl:apply-templates select="*" />
</xsl:template>

<xsl:template match="text()">
<p><xsl:value-of select="normalize-space()" /></p>
</xsl:template>

</xsl:stylesheet>
( 2 * b ) || ! ( 2 * b )

6

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Rumata, у Вас талант доходчиво объяснять труднодоходящее.
Вот почти тот же пример (изменения минимальны), комментарии добавил так, как понял. Правильно?

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

  <xsl:output method="html" version="1.0" indent="yes" />
  <!-- формат выходных данных - html-документ, начиная каждый тег с новой строки -->

  <xsl:template match="/">
  <!-- для корня - чтобы 1 раз -->
    <style>
      .sn{color:navy;font-weight:bold}
      .sa{color:crimson;}
      .st{color:blue;}
      ul{list-style-type:none;margin:0;}
      li,ul ul{margin:3px 0 0 10px;}
    </style>
    <!-- прописали в начале -->
    <xsl:apply-templates />
    <!-- и корень же - на дальнейшую обработку -->
    <br />
    <!-- один раз в конце -->
  </xsl:template>

  <xsl:template match="*">
    <!-- для каждого узла -->
    <ul>
      <span class="sn">&lt;<xsl:value-of select="name()" />&gt;</span>
      <!-- сразу выводим имя узла -->
      <xsl:apply-templates select="@*" />
      <!-- на дальнейшую обработку: все атрибуты -->
      <xsl:apply-templates select="text()" />
      <!-- на дальнейшую обработку: все текстовые ноды -->
      <xsl:apply-templates select="*" />
      <!-- на дальнейшую обработку: все дочерние узлы - рекурсия -->
    </ul>
    <!-- результат обработки вернётся туда, откуда был вызван, т.е. -->
    <!-- окажется между тэгами <ul> и </ul>, и это рекурсивно -->
  </xsl:template>

  <xsl:template match="@*">
    <!-- для каждого атрибута -->
    <li>
      <span class="sa"><xsl:value-of select="name()" />=“</span>
      <!-- выводим имя= -->
      <span class="sv"><xsl:value-of select="." /></span>
      <!-- выводим значение -->
      <span class="sa">”</span>
    </li>
  </xsl:template>

  <xsl:template match="text()[normalize-space()]">
    <!-- для каждой непустой текстовой ноды -->
    <li>
      <span class="st">[Текст] “</span>
      <span class="sv"><xsl:value-of select="normalize-space()" /></span>
      <!-- точка - аргумент normalize-space(.) - означает текущий -->
      <!-- контент, поэтому можно без неё - по умолчанию будет он же -->
      <span class="st">”</span>
    </li>
    <!-- выводим [Текст] значение -->
  </xsl:template>

  <!-- в общем, больше ничего не делаем (пока) -->

</xsl:stylesheet>

7

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Serge Yolkin пишет:

Rumata, у Вас талант доходчиво объяснять труднодоходящее.

Спасибо. Значит мои преподавательские способности еще не утеряны

Serge Yolkin пишет:

Вот почти тот же пример (изменения минимальны), комментарии добавил так, как понял. Правильно?

Я не проверял Ваш пример. Судя по коду - все верно. Если у Вас работает все верно на любом наборе входных данных, значит правильно.

Это работает?

<xsl:template match="text()[normalize-space()]">
( 2 * b ) || ! ( 2 * b )

8

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Да, normalize-space() в качестве фильтра работает. А вопрос "Правильно?" относился не к коду, а к комментариям... Сам пример не заработать не мог: я двигался от Вашего кода беспроигрышным методом (одно изменение - проверка, если что - откат).

9

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Все верно. : )

( 2 * b ) || ! ( 2 * b )

10

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Забавно: обрадовался, было, тому, что XSL понимает такие конструкции, как &lt; и тут же налетел на &ldquo;... С ним XSL вообще считается сплошной ошибкой.

11 (изменено: Rumata, 2013-07-19 23:18:39)

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

DTD (wiki)
DTD

XML не знают, что делать с большинством сущностей, потому что они не определены. Используйте коды в форме &#число; или опишите их в своем xslt-файле:

<!ENTITY &ldquo; "&#8220;">
( 2 * b ) || ! ( 2 * b )

12

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Кавычки и прочие символы можно вставлять как символы, благо UTF-8 позволяет (например, из Таблицы символов). Я про то, что аналогия не сработала...

13

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Вы можете вставить в любой XML файл как сами символы, так и указать их код, в виде &#число;. А комбинации вида &что-то; - это сущности, которые должны быть определены где-то заранее. Есть предопределенный набор сущностей вида &lt;. Поэтому Вы можете их использовать - остальные надо определять самим.

( 2 * b ) || ! ( 2 * b )

14

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Нашел здесь.

15

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Поигрался со свойством indent элемента xsl:output и ничего не понял. Похоже, в микрософтовой реализации оно игнорируется. Для методов text и html переноса на новую строку нет ни при "yes", ни при "no", для xml - вообще непонятно, часть тэгов переносится и при "no", а с отступами - беда и при "yes".

Пытался с помощью xsl получить исходный xml, но в отформатированном виде, независимо от исходного форматирования. С наскоку не получилось. Видимо, надо пересоздавать узлы (xsl:element), назначая им их же атрибуты. Пока отложил. Пробовал сделать то же методом text - похоже, ещё сложнее (отступы съедаются).

16 (изменено: Serge Yolkin, 2013-08-04 06:52:32)

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Вроде, получилось. Хотя, возможно, не слишком изящно.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="*">
  <!-- для тэгов -->
    <xsl:param name="indent" select="'&#10;'"/>
    <!-- задали параметр — начальный отступ (сначала это перенос строки) -->

    <xsl:value-of select="$indent"/>
    <!-- применили отступ перед тэгом -->
    <xsl:copy>
    <!-- копируем содержимое тэга (сам тэг — автоматически?) -->
      <xsl:copy-of select="@*" />
      <!-- атрибуты — как есть -->

      <xsl:apply-templates>
      <!-- рекурсия -->
        <xsl:with-param name="indent" select="concat($indent, '    ')"/>
        <!-- вложенные тэги — с новым значением отступа (+«табуляция») -->
      </xsl:apply-templates>

      <xsl:choose>
        <xsl:when test="*">
        <!-- если были вложенные тэги -->
          <xsl:value-of select="$indent"/>
          <!-- подвинуть закрывающий тэг (здесь отступ не увеличен) -->
        </xsl:when>
        <xsl:otherwise>
        <!-- если не было — закрывающий в той же строке, что и открывающий -->
          <xsl:text></xsl:text>
          <!-- чушь, но иначе всё равно переносит строку -->
        </xsl:otherwise>
      </xsl:choose>

    </xsl:copy>
  </xsl:template>

  <xsl:template match="comment()|processing-instruction()">
  <!-- для комментариев и инструкций процессора -->
    <xsl:param name="indent" select="''"/>
    <!-- задаём пустой параметр, всё равно будет передан правильный (with-param) -->
    <xsl:value-of select="$indent"/>
    <!-- отступ -->
    <xsl:copy />
    <!-- просто копируем (с отступом) -->
  </xsl:template>

  <xsl:template match="text()">
  <!-- для текстовых узлов -->
    <xsl:param name="indent" select="''"/>
    <!-- задаём пустой (см. выше) -->
    <xsl:value-of select="$indent"/>
    <!-- отступ -->
    <xsl:if test="normalize-space()!=''">
    <!-- если не пустой -->
      <xsl:copy-of select="normalize-space()" />
      <!-- копируем содержимое с нормализованными прбелами -->
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Шаблон форматирует XML, хотя, строго говоря, "портит" текстовые ноды, вольно обращаясь с их пробелами... И  может добавить новые, например, если исходный файл был однострочником.

17

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Пожалуй, вопрос не в тему, но, может, название темы сменить на что-то более общее? Если нет - перенесу в отдельную...
И так. В начале XML есть инструкция <?xml version="1.0" encoding="..."?>. При загрузке (load) в MSXML2.DOMDocument эта инструкция сохраняется, включая encoding и его значение, хотя сам XML, насколько я понял, в xmlдомике находится в UTF-16 (кажется, LE). Проверил просто: сохранил xmlDoc не родным save(), а с помощью fso.textFile.write(xmlDoc.xml). Далее. У xsl:output тоже есть свойство encoding, которое задаёт выходную кодировку в том же формате. Вопрос:
Уважаемые коллеги, нельзя ли вставить значение свойства xsl:output encoding из инструкции исходного xml файла (чтобы выходная кодировка соответствовала входной)?

18

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Serge Yolkin пишет:

Шаблон форматирует XML, хотя, строго говоря, "портит" текстовые ноды, вольно обращаясь с их пробелами... И  может добавить новые, например, если исходный файл был однострочником.

Попробуйте посмотреть в сторону атрибута xml:space="preserve" в исходном XML или на элемент <xsl:preserve-space>.

Забыл пароль и потерял e-mail.

19 (изменено: Serge Yolkin, 2013-08-06 12:36:50)

Re: XML/XSL: Обработка всех текстовых узлов всех тегов

Не-не-не. Сохранить пробелы в текстовых нодах можно и проще, моя задача как раз была в получении "канонического" вида XML. Я просто об этом предупредил. Да и пробелы в них не только убираются, но и добавляются - отступ.

Поправлю сам себя: оказывается, свойство encoding сохраняется только в моей несчастной винде (пора переустанавливать). Ни в какой другой (даже у меня же на работе) не сохраняется. По всей видимости, получить исходную кодировку XML файла, после того, как он загружен в DOM, низзя. Прийдётся сначала парсить файл, как текст, а уж потом грузить...