问题
I am trying to use xslt to perform an xml to xml transform, such that each element is given a GUID, pulled in sequential order from an external xml document.
Source xml:
<?xml version="1.0"?>
<dataSets>
<data name="foo"/>
<data name="bar"/>
...
</dataSets>
List of IDs:
<?xml version="1.0"?>
<ids>
<id>some-GUID</id>
<id>another-GUID</id>
...
</ids>
Desired output:
<?xml version="1.0"?>
<dataSets>
<data name="foo" id="some-GUID"/>
<data name="bar" id="another-GUID"/>
...
</dataSets>
But I'm just getting the same, first ID each time:
<?xml version="1.0"?>
<dataSets>
<data name="foo" id="some-GUID"/>
<data name="bar" id="some-GUID"/>
...
</dataSets>
So far this is the xsl I've got:
<?xml version="1.0"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/|node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="dataSets">
<xsl:for-each select="data">
<xsl:variable name="c">
<xsl:number value="count(preceding-sibling::*|self::*)"/>
</xsl:variable>
<xsl:copy>
<xsl:attribute name="id">
<xsl:value-of select="document('idList.xml')/ids/id[$c]"/>
</xsl:attribute>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
</xsl:transform>
I've tried adding <xsl:attribute name="num"><xsl:value-of select="$c"/></xsl:attribute>
to the xsl to see what the variable is each time it iterates and it starts at 1 and increments each time through the for-each
just as I'd expect so I've no idea why it's not working.
Any help would be appreciated.
回答1:
How about:
XSLT 1.0
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="data">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:variable name="i" select="count(preceding::data) + 1" />
<xsl:attribute name="id">
<xsl:value-of select="document('idList.xml')/ids/id[$i]"/>
</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note:
If all
data
elements are siblings under the samedataSets
parent, you can expedite this by counting only the preceding siblings:<xsl:variable name="i" select="count(preceding-sibling::data) + 1" />
There are probably better ways to assign GUIDs to nodes.
Addditional explanation:
The reason why your attempt does not work is a combination of two factors:
First, the way you define your variable:
<xsl:variable name="c">
<xsl:number value="count(preceding-sibling::*|self::*)"/>
</xsl:variable>
results in the variable being a result tree fragment. The variable contains a single root node, and the string generated by xsl:number
is a child of that node.
Next, you try to use the variable as a numeric predicate in the expression:
<xsl:value-of select="document('idList.xml')/ids/id[$c]"/>
However, since the variable is not a number, it is evaluated as a boolean - and being non-empty it returns true, causing all id
nodes to pass the test (and, of course, in XSLT 1.0 xsl:value
will only return the value of the first one of these).
How to fix it:
There are three ways you can fix this problem:
Do not use the variable as a numeric predicate. Instead, put it in an expression that compares it to the position explicitly (as suggested in the answer by @potame):
<xsl:value-of select="document('idList.xml')/ids/id[position()=$c]"/>
Convert it to number before using it as a predicate:
<xsl:value-of select="document('idList.xml')/ids/id[number($c)]"/>
Eliminate the problem right from the start by defining the variable using the
select
attribute, which will cause the variable to become a number to begin with:<xsl:variable name="c" select="count(preceding-sibling::*|self::*)" />
See also:
http://www.w3.org/TR/xslt/#variable-values
http://www.w3.org/TR/xpath/#predicates
回答2:
You just need to slighly change the instruction to retrieve element at the desired index, by the use of position()
:
<xsl:value-of select="document('idList.xml')/ids/id[position() = $c]"/>
I suggest you to use a variable to avoid parsing the document several times:
<xsl:template match="dataSets">
<xsl:variable name="idList" select="document('idList.xml')/ids"/>
<xsl:for-each select="data">
<xsl:variable name="c">
<xsl:number value="count(preceding-sibling::*|self::*)"/>
</xsl:variable>
<xsl:copy>
<xsl:attribute name="id">
<xsl:value-of select="$idList/id[position() = $c]"/>
</xsl:attribute>
<xsl:apply-templates select="node()|@*"/>
<xsl:value-of select="$c" />
</xsl:copy>
</xsl:for-each>
</xsl:template>
来源:https://stackoverflow.com/questions/29823166/xslt-1-0-extract-sequential-infromation-from-external-document