XSLT and temporary documents

2019-06-27 10:56发布

I am trying to process an xml file which has several different state groups like

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

What I need is actually get something like

<childgroup no="16">
  <state statename="init">
    <child1>...</child1>
    <child2>...</child2>
  </state>
  <state statename="process">
    <child2>...</child2>
    <child3>...</child3>
  </state>
</childgroup>
<childgroup no="17">
...

I've done simple part which is going and adding "chgrpno" attribute and stateid attribute to all childs (it makes copy-of of all elements but childgroup and state, adding the attribute to those two.

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

This works and in the result all childs have attribute so I could regroup them in the next pass and states have numbers so I could later make same thing. But trying to follow the example of M.Kay with "temporary documents" when I try to do

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

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

then it just returns the original to me, and all changes which I made in the first pass are gone. So what am I doing wrong here?

I use XSLT 1.0, not XSLT 2.0 explicitly.

(edit: surely I named the variable, forgot to copy it here).

标签: xml xslt
4条回答
仙女界的扛把子
2楼-- · 2019-06-27 11:21

The key thing in multi-pass processing with XSLT 1.0 is that the variable that contains the result of the first pass doesn't actually contain an XML document (tree).

This variable contais an RTF (Result-Tree-Fragment). RTFs are defined only in XSLT 1.0. THe only operation that can be done with an RTF is <xsl:copy-of> or <xsl:value-of> or passing it as parameter -- anything that treats the RTF just as a string.

By definition, any attempt to the innards of an RTF with an XPath expression having location tests in it -- must fail.

The workaround from this is to use an extension function, usually named xxx:node-set() (but Xalan uses the name "nodeset" no dash), where the "xxx:" prefix is bound to a specific, implementation-defined XSLT processor. As Martin Honnen recommends, for the purpose of achieving some degree of portability, one should try to use the EXSLT common:nodeset() extension, whenever this is implemented by the particular XSLT processor.

Here is an example of two-pass processing with 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>

When applied on this XML document:

<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>

this transformation in the first pass produces a filtered tree in which only the num elements with odd value are present. Then the second pass renames every num element to x. The final result is:

<nums>
   <x>01</x>
   <x>03</x>
   <x>05</x>
   <x>07</x>
   <x>09</x>
</nums>
查看更多
beautiful°
3楼-- · 2019-06-27 11:28

Another (single-pass) possible approach (not saying the simpler one) is:

  • copy the node-sets as variables
  • use variable references to build the predicates of intersecting nodes-sets on adjacent siblings of the same group recursively

<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>

When applied on the following 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>

Produces:

<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"/>
查看更多
我欲成王,谁敢阻挡
4楼-- · 2019-06-27 11:35

You should name your variable, then you can copy it e.g.

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

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

With XSLT 1.0, if you don't want to copy the variable content with copy-of and instead want to process it with apply-templates then you need an extension function like exsl:node-set e.g.

<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>
查看更多
混吃等死
5楼-- · 2019-06-27 11:37

Here is an example how to approach the grouping with XSLT 1.0 in one step; the stylesheet

<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>

transforms the input sample

<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>

into

<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>
查看更多
登录 后发表回答