XSLT и временные документы - PullRequest
4 голосов
/ 03 июля 2011

Я пытаюсь обработать XML-файл, который имеет несколько различных групп состояний, например

<root>
<childgroup>16</childgroup>
<setstate>init</setstate>
<child1>...</child1>
<child2>...</child2>
<setstate>process<setstate>
<child2>...</child2>
<child3>...</child3>
.....
<childgroup>17</childgroup>
...

Что мне нужно, так это получить что-то вроде </p> <pre><code><childgroup no="16"> <state statename="init"> <child1>...</child1> <child2>...</child2> </state> <state statename="process"> <child2>...</child2> <child3>...</child3> </state> </childgroup> <childgroup no="17"> ...

Я сделал простую часть, которая состоит в добавлении атрибута "chgrpno" и атрибута stateid ко всем дочерним элементам (он копирует все элементы, кроме дочерней группы и состояния, добавляя атрибут к этим двум.

<xsl:template match="/">
  <xsl:apply-templates mode="numb"/>
</xsl:template>

Это работает, и в результате все дочерние элементы имеют атрибуты, чтобы я мог перегруппировать их в следующем проходе, а состояния имеют номера, чтобы позже я мог сделать то же самое. Но пытаюсь последовать примеру М.Кея с «временными документами», когда я пытаюсь сделать

<xsl:variable name="nmb">
  <xsl:apply-templates mode="numb"/>
</xsl:variable>

<xsl:template match="/">
  <xsl:copy-of select="$nmb"/>
</xsl:template>

тогда он просто возвращает мне оригинал, и все изменения, которые я сделал в первом проходе, пропали. Так что я тут делаю не так?

Я использую XSLT 1.0, а не XSLT 2.0 явно.

(редактировать: конечно, я назвал переменную, забыл скопировать ее здесь).

Ответы [ 4 ]

3 голосов
/ 03 июля 2011

Вот пример того, как приблизиться к группировке с XSLT 1.0 за один шаг;таблица стилей

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

  <xsl:output indent="yes"/>

  <xsl:key name="k1" match="root/*[not(self::childgroup)]" 
    use="generate-id(preceding-sibling::childgroup[1])"/>

  <xsl:key name="k2" match="root/*[not(self::childgroup) and not(self::setstate)]"
    use="concat(generate-id(preceding-sibling::childgroup[1]), '|', generate-id(preceding-sibling::setstate[1]))"/>

  <xsl:template match="root">
    <xsl:copy>
      <xsl:apply-templates select="childgroup"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="childgroup">
    <childgroup no="{.}">
      <xsl:apply-templates select="key('k1', generate-id())[self::setstate]"/>
    </childgroup>
  </xsl:template>

  <xsl:template match="setstate">
    <state statename="{.}">
      <xsl:copy-of select="key('k2', concat(generate-id(preceding-sibling::childgroup[1]), '|', generate-id()))"/>
    </state>
  </xsl:template>

</xsl:stylesheet>

преобразует входной образец

<root>
  <childgroup>16</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
  <childgroup>17</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
</root>

в

<root>
   <childgroup no="16">
      <state statename="init">
         <child1>...</child1>
         <child2>...</child2>
      </state>
      <state statename="process">
         <child2>...</child2>
         <child3>...</child3>
      </state>
   </childgroup>
   <childgroup no="17">
      <state statename="init">
         <child1>...</child1>
         <child2>...</child2>
      </state>
      <state statename="process">
         <child2>...</child2>
         <child3>...</child3>
      </state>
   </childgroup>
</root>
2 голосов
/ 03 июля 2011

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

Эта переменная содержит RTF (Результат-Tree-фрагмент).RTF определены только в XSLT 1.0.Единственная операция, которая может быть выполнена с RTF - это <xsl:copy-of> или <xsl:value-of> или передача его в качестве параметра - все, что рассматривает RTF просто как строку.

По определению, любая попытка проникновения внутрьRTF с выражением XPath, содержащим в себе тесты местоположения, должен завершиться неудачей.

Обходной путь - использовать функцию расширения, обычно называемую xxx: node-set () (но Xalan использует имя "nodeset"«без тире), где префикс "xxx:" связан с конкретным, определенным реализацией XSLT-процессором.Как рекомендует Мартин Хоннен, для достижения некоторой степени переносимости следует пытаться использовать расширение EXSLT common: nodeset () всякий раз, когда это реализуется конкретным процессором XSLT.

ЗдесьПример двухпроходной обработки с XSLT 1.0 :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
 exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

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

 <xsl:template match="num[not(. mod 2)]"/>

 <xsl:template match="/">
  <xsl:variable name="vrtfPass1">
   <xsl:apply-templates/>
  </xsl:variable>

  <nums>
   <xsl:apply-templates mode="pass2"
        select="ext:node-set($vrtfPass1)/*"/>
  </nums>
 </xsl:template>

 <xsl:template match="num" mode="pass2">
 <x>
  <xsl:apply-templates/>
 </x>
 </xsl:template>
</xsl:stylesheet>

При применении к этому документу XML :

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

это преобразование в первомpass создает отфильтрованное дерево, в котором присутствуют только элементы num с нечетным значением.Затем второй проход переименовывает каждый элемент num в x.Окончательный результат:

<nums>
   <x>01</x>
   <x>03</x>
   <x>05</x>
   <x>07</x>
   <x>09</x>
</nums>
1 голос
/ 04 июля 2011

Другой ( однопроходный ) возможный подход ( не говоря о более простом ):

  • скопировать наборы узлов как переменные
  • использовать переменные ссылки для рекурсивного построения предикатов пересекающихся наборов узлов на соседних братьях и сестрах одной и той же группы

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes"/>
    <xsl:template match="text()"/>

    <xsl:template match="*" mode="childx">
        <xsl:copy-of select="."/>
    </xsl:template>

    <xsl:template match="childgroup">
        <xsl:variable name="fw" select="following-sibling::*"/>
        <childgroup no="{.}">
            <xsl:variable name="fwrw" 
                select="$fw[self::childgroup][1]
                /preceding-sibling::*"/>
            <xsl:apply-templates select="following-sibling::*[
                count( . | $fwrw ) = count( $fwrw ) 
                or count( $fwrw )=0]
                [self::setstate] " mode="setstate"/>
        </childgroup>
    </xsl:template>

    <xsl:template match="setstate" mode="setstate">
        <xsl:variable name="fw" select="following-sibling::*"/>
        <state name="{.}">
            <xsl:variable name="fwrw" 
                select="$fw[ self::setstate or self::childgroup ][1]/
                preceding-sibling::*"/>
            <xsl:apply-templates select="following-sibling::*[
                count( . | $fwrw) = count( $fwrw ) 
                or count($fwrw) = 0]" mode="childx"/>
        </state>
    </xsl:template>
</xsl:stylesheet>

При применении к следующему XML:

<root>
  <childgroup>16</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
  <childgroup>17</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
  <childgroup>18</childgroup>
  <childgroup>19</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
  <childgroup>20</childgroup>
</root>

Производит:

<childgroup no="16">
   <state name="init">
      <child1>...</child1>
      <child2>...</child2>
   </state>
   <state name="process">
      <child2>...</child2>
      <child3>...</child3>
   </state>
</childgroup>
<childgroup no="17">
   <state name="init">
      <child1>...</child1>
      <child2>...</child2>
   </state>
   <state name="process">
      <child2>...</child2>
      <child3>...</child3>
   </state>
</childgroup>
<childgroup no="18"/>
<childgroup no="19">
   <state name="init">
      <child1>...</child1>
      <child2>...</child2>
   </state>
   <state name="process">
      <child2>...</child2>
      <child3>...</child3>
   </state>
</childgroup>
<childgroup no="20"/>
0 голосов
/ 03 июля 2011

Вы должны назвать свою переменную, затем вы можете скопировать ее, например,

<xsl:variable name="rtf1">
  <xsl:apply-templates mode="numb"/>
</xsl:variable>

<xsl:template match="/">
  <xsl:copy-of select="$rtf1"/>
</xsl:template>

С XSLT 1.0, если вы не хотите копировать содержимое переменной с помощью copy-of и вместо этого хотите обработать ее с помощьюapply-templates тогда вам нужна функция расширения, такая как exsl: node-set например,

<xsl:variable name="rtf1">
  <xsl:apply-templates mode="numb"/>
</xsl:variable>

<xsl:template match="/">
  <xsl:apply-templates select="exsl:node-set($rtf1)/node()"/>
</xsl:template>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...