Можно ли сделать XSL-преобразование в динамически генерируемый XML? - PullRequest
2 голосов
/ 25 июня 2009

Допустим, у меня есть пустой XML-файл, например:

<root></root>

И я хочу добавить элемент в корневой каталог во время XSL-преобразования следующим образом:

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

  <xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes" />

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

  <xsl:template match="/">
    <xsl:copy>
      <xsl:element name="root">
        <xsl:element name="label">Some text</xsl:element>
      </xsl:element>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Преобразование файла XML дает мне:

<root>
  <label>Some text</label>
</root>

Однако я хотел бы изменить файл XSL, чтобы я мог преобразовать сгенерированный во время выполнения XML. По сути, я хотел бы, чтобы XSL подумал, что исходный XML-файл содержал динамически созданный XML, а затем сделал HTML-преобразование на этом.

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

  <xsl:output method="html" encoding="UTF-8" omit-xml-declaration="yes" />

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

  <xsl:template match="/">
    <xsl:copy>
      <xsl:element name="root">
        <xsl:element name="label">Some text</xsl:element>
      </xsl:element>
    </xsl:copy>
  </xsl:template>

  <!-- This part is new -->
  <xsl:template match="//label">
    <b>
      <xsl:value-of select="." />
    </b>
  </xsl:template>

</xsl:stylesheet>

В этом случае мой желаемый результат будет: <b>label</b>.

Возможно ли сделать это в одном файле XSL? Я неправильно смотрю на эту проблему?

Ответы [ 2 ]

3 голосов
/ 25 июня 2009

Для выполнения многопроходного преобразования есть три основных элемента.

  • <xsl:import>
  • <xsl:apply-imports>
  • node-set() функция расширения

В этом примере XML-документ последовательно проходит два преобразования. Таким образом, он пропускает содержимое через преобразование, которое удаляет узлы пространства имен, а затем принимает вывод из первого преобразования и передает его через второе преобразование, которое изменяет заголовок документа. В этом случае документ является документом XHTML. Второе преобразование написано так, что оно не может принять документ XHTML с определенным пространством имен.

Оригинальный документ XHTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>This is the old title</title>
  </head>
  <body>
    <p>This is some text.</p>
  </body>
</html>

После прохода 1 (удалить узлы пространства имен)

<html>
  <head>
    <title>This is the old title</title>
  </head>
  <body>
    <p>This is some text.</p>
  </body>
</html>

Результат (после прохождения 2)

Обратите внимание, что текст заголовка изменился.

<html>
  <head> 
    <title>This is the new title</title>
  </head>
  <body>
    <p>This is some text.</p>
  </body>
</html>

XSLT для пропуска 1

Это преобразование применяет шаблоны в этом файле к содержимому для удаления узлов пространства имен, но копирует остальную часть содержимого. Затем во время этапа 2 применяет шаблоны, определенные в импортированном XSLT, с помощью тега <xsl:apply-imports>. Шаблоны для прохода 2 импортируются с использованием тега <xsl:import>.

Результаты первого прохода сохраняются в переменной с именем "treefrag". Фрагмент дерева преобразуется в набор узлов с помощью функции расширения "node-set()". В этом примере используется синтаксический анализатор Microsoft XML 4.0, поэтому объявляется пространство имен urn:schemas-microsoft-com:xslt.

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
>

  <xsl:import href="pass2.xslt" />
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />

  <xsl:template match="/">
    <xsl:param name="pass">1</xsl:param>

    <xsl:choose>
      <xsl:when test="$pass=1">
        <xsl:variable name="treefrag">
          <xsl:apply-templates>
            <xsl:with-param name="pass" select="$pass" />
          </xsl:apply-templates>
        </xsl:variable>
        <xsl:variable name="doc" select="
          msxsl:node-set($treefrag)
        " />
        <xsl:apply-templates select="$doc">
          <xsl:with-param name="pass">2</xsl:with-param>
        </xsl:apply-templates>
      </xsl:when>
      <xsl:when test="$pass=2">
        <xsl:apply-imports />
      </xsl:when>
    </xsl:choose>

  </xsl:template>   

  <!-- identity template without namespace nodes -->
  <xsl:template match="*">
    <xsl:param name="pass">2</xsl:param>

    <xsl:choose>
      <xsl:when test="$pass=1">
        <xsl:element name="{name()}">
          <xsl:apply-templates select="@*|node()">
            <xsl:with-param name="pass" select="$pass" />
          </xsl:apply-templates>
        </xsl:element>
      </xsl:when>
      <xsl:when test="$pass=2">
        <xsl:apply-imports />
      </xsl:when>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="@*|text()|comment()|processing-instruction()">
    <xsl:param name="pass">2</xsl:param>

    <xsl:choose>
      <xsl:when test="$pass=1">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()">
            <xsl:with-param name="pass" select="$pass" />
          </xsl:apply-templates>
        </xsl:copy>
      </xsl:when>
      <xsl:when test="$pass=2">
        <xsl:apply-imports />
      </xsl:when>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

XSLT для прохода 2

Это преобразование просто изменяет содержимое тега TITLE, может копировать все остальное содержимое.

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

  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

  <xsl:template match="title">
    <title>This is the new title</title>
  </xsl:template>

  <!-- identity template -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet> 

Источник статьи: «XSLT: многопроходные преобразования»

0 голосов
/ 25 июня 2009

Технически правильный ответ Дуга, и он поставил меня на правильный путь поиска функции расширения набора узлов (спасибо!). Разница лишь в том, что я использую процессор Xalan-java вместо Microsoft. Вот рабочий (чрезвычайно простой) пример того, чего я пытался достичь:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:xalan="http://xml.apache.org/xalan" exclude-result-prefixes="xalan">
<xsl:output method="html" encoding="UTF-8" omit-xml-declaration="yes"/>

<xsl:template match="label">
    <b><xsl:value-of select="." /></b>
</xsl:template>

<xsl:template name="someTemplate">
    <root>
        <label>hey there</label>
    </root>
</xsl:template>

<xsl:template match="/">
    <xsl:variable name="gen">
        <xsl:call-template name="someTemplate" />
    </xsl:variable>

    <xsl:apply-templates select="xalan:nodeset($gen)//label" />
</xsl:template>

</xsl:stylesheet>

Основная идея состоит в том, что вы создаете переменную (здесь называемую «gen»), которая вызывает шаблон («someTemplate»), который создает некоторый xml. Затем это xml-дерево обрабатывается функцией xalan: nodeset. Здесь я запускаю apply-templates и обрабатываю все поля меток, которые совпадают с моим шаблоном меток и создает метку, выделенную жирным шрифтом.

Причина, по которой я все это делаю, заключается в том, что я работаю над веб-приложением, в котором весь контент на каждой странице определяется в XML, а затем обрабатывается с использованием XSL. Это прекрасно работает, пока вы не захотите заполнить части страницы результатами вызова AJAX, потому что тогда вам нужно либо создать новый html внутри функции javascript из возвращенного XML (грязный и плохой), либо передать XML-ответ AJAX через XSL-преобразование на стороне сервера и просто вставьте результат xhtml на страницу.

В моей ситуации, поскольку я описываю каждую веб-страницу в XML, я создал простые, общие элементы, которые имеют «типы». Мои элементы - это «блок», то есть любой блок контента. Блок может иметь подузлы, такие как «кнопка» (которая является ссылкой или кнопкой), «список» (который состоит из нескольких блоков), «изображение» или «вход». Детали схемы не имеют значения; Я просто хотел показать, что моя XML-схема очень проста и универсальна.

С другой стороны, ответ AJAX XML, который я получаю для обновления / создания страницы, создается сторонней организацией, которая не использует мою схему. Напротив, каждый предоставляемый ими сервис возвращает очень специфический XML для каждого уникального запроса. По этой причине мне нужен способ преобразовать их очень специфический XML в мой очень общий XML, чтобы я мог преобразовать его, используя один набор шаблонов XSL.

Вот пример одного из шаблонов xsl, которые я сейчас использую, который преобразует ответ AJAX и преобразует их конкретный XML в мой общий XML.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:xalan="http://xml.apache.org/xalan" exclude-result-prefixes="xalan">
<xsl:output method="html" encoding="UTF-8" omit-xml-declaration="yes"/>

<!-- base.xsl includes a generic template that matches all blocks and routes
        to the needed template based on the block type -->
<xsl:include href="base.xsl" />

<xsl:template name="templateForAJAXResponse">
    <block>
        <type>list</type>
        <list>
            <type>itemBrowser</type>
            <xsl:for-each select="/response/items/item">
                <block>
                    <type>button</type>
                    <button>
                        <type>imageButton</type>
                        <href>something.action</href>
                        <src><xsl:value-of select="src" /></src>
                        <title><xsl:value-of select="name" /></title>
                    </button>
                </block>
            </xsl:for-each>
        </list>
    </block>
</xsl:template>

<xsl:template match="/">
    <xsl:variable name="gen">
        <xsl:call-template name="templateForAJAXResponse" />
    </xsl:variable>

    <div>
        <xsl:apply-templates select="xalan:nodeset($gen)/block" />
    </div>
</xsl:template>

</xsl:stylesheet>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...