可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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).
回答1:
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>
回答2:
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>
回答3:
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:
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>