使用 XSLT 3 的(稍微改变的)输入
<RS>
<R>
<C0>A</C0>
<C1>B</C1>
<C2>C</C2>
<C3>D</C3>
<C4>1</C4>
</R>
<R>
<C0>A</C0>
<C1>B</C1>
<C2>C</C2>
<C3>E</C3>
<C4>2</C4>
</R>
<R>
<C0>A</C0>
<C1>B</C1>
<C2>F</C2>
<C4>3</C4>
</R>
</RS>
可以通过递归分组进行转换,如
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:function name="mf:group" as="node()*">
<xsl:param name="rows" as="element(R)*"/>
<xsl:param name="index" as="xs:integer"/>
<xsl:for-each-group select="$rows" group-by="*[$index]">
<xsl:try>
<xsl:element name="{current-grouping-key()}">
<xsl:sequence select="mf:group(current-group(), $index + 1)"/>
</xsl:element>
<xsl:catch>
<xsl:value-of select="current-grouping-key()"/>
</xsl:catch>
</xsl:try>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="RS">
<xsl:sequence select="mf:group(R, 1)"/>
</xsl:template>
<xsl:output method="xml" indent="yes"/>
</xsl:stylesheet>
进入
<A>
<B>
<C>
<D>1</D>
<E>2</E>
</C>
<F>3</F>
</B>
</A>
替代输入结构也可以通过 XSLT 3 中的递归分组进行转换,这次使用映射作为中间数据结构:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:function name="mf:group" as="node()*">
<xsl:param name="rows" as="map(*)*"/>
<xsl:param name="index" as="xs:integer"/>
<xsl:for-each-group select="$rows" group-by="?columns[$index]">
<xsl:element name="{current-grouping-key()}">
<xsl:sequence select="if (exists(?columns[$index + 1])) then mf:group(current-group(), $index + 1) else ?value"/>
</xsl:element>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="RS">
<xsl:sequence select="mf:group(R ! map { 'columns' : tokenize(C0, '->'), 'value' : data(C1) }, 1)"/>
</xsl:template>
<xsl:output method="xml" indent="yes"/>
</xsl:stylesheet>
使用地图的算法可以通过使用来适应您的第一个输入结构
<xsl:function name="mf:group" as="node()*">
<xsl:param name="rows" as="map(*)*"/>
<xsl:param name="index" as="xs:integer"/>
<xsl:for-each-group select="$rows" group-by="?columns[$index]">
<xsl:element name="{current-grouping-key()}">
<xsl:sequence select="if (exists(?columns[$index + 1])) then mf:group(current-group(), $index + 1) else ?value"/>
</xsl:element>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="RS">
<xsl:sequence select="mf:group(R ! map { 'columns' : *[. castable as xs:NCName], 'value' : data(*[last()]) }, 1)"/>
</xsl:template>