这个通用的 XSLT 2.0 转换:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="xs my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<t>
<xsl:sequence select="my:grouping(*)"/>
</t>
</xsl:template>
<xsl:function name="my:grouping" as="node()*">
<xsl:param name="pElems" as="element()*"/>
<xsl:if test="$pElems">
<xsl:for-each-group select="$pElems" group-by="my:signature(.)">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:sequence select="my:grouping(current-group()/*)"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:if>
</xsl:function>
<xsl:function name="my:signature" as="xs:string">
<xsl:param name="pElem" as="element()"/>
<xsl:variable name="vsignAttribs" as="xs:string*">
<xsl:for-each select="$pElem/@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(), '=', .,'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:sequence select=
"concat(name($pElem), '|', string-join($vsignAttribs, ''))"/>
</xsl:function>
</xsl:stylesheet>
当应用于提供的 XML 时(包装到单个顶部元素中以成为格式良好的 XML 文档):
<t>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
</Ratings>
</TopLevel>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
</Ratings>
</TopLevel>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
</Ratings>
</TopLevel>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
产生想要的正确结果:
<t>
<TopLevel CodeTL="Something">
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
说明:
执行的分组在函数中实现my:grouping()
并且是递归的。
顶部元素在其级别上是单一的,除了自身的浅拷贝之外不需要任何其他分组。然后在这个浅拷贝的主体内部,较低级别的分组由函数执行my:grouping()
。
该函数my:grouping()
有一个参数,它是上一级组中所有元素的所有子元素。它返回当前级别的所有组。
作为参数传递给函数的元素序列根据它们的签名进行分组——元素名称与其属性的所有名称-值对及其对应值的串联,并使用适当的分隔符将它们分开。元素的签名由函数生成my:signature()
。
二、通用 XSLT 1.0 解决方案:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:my="my:my" exclude-result-prefixes="my ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="/*"/>
</xsl:variable>
<xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)"/>
<xsl:template match="/">
<xsl:apply-templates select="$vPass1/*" mode="pass2"/>
</xsl:template>
<xsl:template match="/*" mode="pass2">
<xsl:copy>
<xsl:call-template name="my:grouping">
<xsl:with-param name="pElems" select="*"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="my:grouping">
<xsl:param name="pElems" select="/.."/>
<xsl:if test="$pElems">
<xsl:for-each select="$pElems">
<xsl:variable name="vPos" select="position()"/>
<xsl:if test=
"not(current()/@my:sign
= $pElems[not(position() >= $vPos)]/@my:sign
)">
<xsl:element name="{name()}">
<xsl:copy-of select="namespace::*[not(. = 'my:my')]"/>
<xsl:copy-of select="@*[not(name()='my:sign')]"/>
<xsl:call-template name="my:grouping">
<xsl:with-param name="pElems" select=
"$pElems[@my:sign = current()/@my:sign]/*"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/*">
<xsl:variable name="vSignature">
<xsl:call-template name="signature"/>
</xsl:variable>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="my:sign">
<xsl:value-of select="$vSignature"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template name="signature">
<xsl:variable name="vsignAttribs">
<xsl:for-each select="@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(), '=', .,'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select=
"concat(name(), '|', $vsignAttribs)"/>
</xsl:template>
</xsl:stylesheet>
当此转换应用于同一个 XML 文档(如上)时,再次产生相同的正确结果:
<t>
<TopLevel>
<Ratings>
<Rating CodeA="ABC" Start="1-1-2012" End="1-2-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="ABC" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
</Rating>
<Rating CodeA="XYZ" Start="1-2-2012" End="1-3-2012">
<RatingByNumber Code="X" Rating="10" Number="1"/>
<RatingByNumber Code="X" Rating="19" Number="2"/>
<RatingByNumber Code="X" Rating="30" Number="3"/>
<RatingByNumber Code="X" Rating="39" Number="4"/>
</Rating>
</Ratings>
</TopLevel>
</t>
说明:
这是一个两遍转换。
在每个元素的第一遍中,计算一个签名,它成为一个新属性的值my:sign
。
使用与 XSLT 2.0 解决方案相同的递归分组算法。