我有一个源xml文件,如下所示:
<transactions>
<transaction>
<number>12</number>
</transaction>
<transaction>
<number>12</number>
</transaction>
<transaction>
<number>13</number>
</transaction>
<transaction>
<number>13</number>
</transaction>
<transaction>
<number>14</number>
</transaction>
<transaction>
<number>14</number>
</transaction>
<transaction>
<number>14</number>
</transaction>
</transactions>
想要使用 XSLT 生成响应。想要基于<number>
元素生成<line>
元素。例如:对于每个相同的<number>
都希望生成序列号。目标文件必须生成如下:
<transactions>
<transaction>
<number>12</number>
<line>1</line>
</transaction>
<transaction>
<number>12</number>
<line>2</line>
</transaction>
<transaction>
<number>13</number>
<line>1</line>
</transaction>
<transaction>
<number>13</number>
<line>2</line>
</transaction>
<transaction>
<number>14</number>
<line>1</line>
</transaction>
<transaction>
<number>14</number>
<line>2</line>
</transaction>
<transaction>
<number>14</number>
<line>3</line>
</transaction>
</transactions>
使用for-each-group
按number
对transaction
元素进行分组,然后将current-group()
推送到apply-templates
,然后在模板中transaction
您可以使用position()
的值填充line
元素:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="transactions">
<xsl:copy>
<xsl:for-each-group select="transaction" group-by="number">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="transaction">
<xsl:copy>
<xsl:apply-templates select="@* , node()"/>
<line>
<xsl:value-of select="position()"/>
</line>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYjm
上面会在结果中将transaction
具有相同number
值的元素组合在一起,如果不需要,那么在 XSLT 3 中,另一种选择是使用累加器通过number
值来记录transaction
元素,并在模板中输出累加器值以供transaction
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy" use-accumulators="trans-count"/>
<xsl:accumulator name="trans-count" as="map(xs:integer, xs:integer)" initial-value="map{}">
<xsl:accumulator-rule match="transaction"
select="let $number := xs:integer(number)
return if (map:contains($value, $number))
then map:put($value, $number, $value($number) + 1)
else map:put($value, $number, 1)"/>
</xsl:accumulator>
<xsl:template match="transaction">
<xsl:copy>
<xsl:apply-templates select="@* , node()"/>
<line>
<xsl:value-of select="accumulator-before('trans-count')(xs:integer(number))"/>
</line>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYjm/1
两个完整示例都使用 XSLT 3<xsl:mode on-no-match="shallow-copy"/>
方式将标识转换声明为转换的基础,对于 XSLT 2 处理器,您需要将其拼写为
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
相反。
对于使用键和 Muenchian 分组的 XSLT 1 解决方案,您可以使用
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="trans-group"
match="transaction" use="number"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="transactions">
<xsl:copy>
<xsl:for-each select="transaction[generate-id() = generate-id(key('trans-group', number)[1])]">
<xsl:apply-templates select="key('trans-group', number)"/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="transaction">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
<line>
<xsl:value-of select="position()"/>
</line>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYjm/4
这是一个简短而简单的 XSLT 1.0 解决方案 -- 只有 15 行(适用于 XSLT 2.0 或 3.0 处理器,因为 XSLT 是向后兼容的):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="number">
<xsl:call-template name="identity"/>
<line><xsl:value-of select="count(../preceding-sibling::*[number = current()]) + 1"/></line>
</xsl:template>
</xsl:stylesheet>
在提供的 XML 文档上应用此转换时:
<transactions>
<transaction>
<number>12</number>
</transaction>
<transaction>
<number>12</number>
</transaction>
<transaction>
<number>13</number>
</transaction>
<transaction>
<number>13</number>
</transaction>
<transaction>
<number>14</number>
</transaction>
<transaction>
<number>14</number>
</transaction>
<transaction>
<number>14</number>
</transaction>
</transactions>
生成所需的正确结果:
<transactions>
<transaction>
<number>12</number>
<line>1</line>
</transaction>
<transaction>
<number>12</number>
<line>2</line>
</transaction>
<transaction>
<number>13</number>
<line>1</line>
</transaction>
<transaction>
<number>13</number>
<line>2</line>
</transaction>
<transaction>
<number>14</number>
<line>1</line>
</transaction>
<transaction>
<number>14</number>
<line>2</line>
</transaction>
<transaction>
<number>14</number>
<line>3</line>
</transaction>
</transactions>