I. 简单的 XSLT 1.0 转换:
<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:key name="kEntryByChildren" match="entry" use="."/>
<xsl:template match=
"entry[not(generate-id() = generate-id(key('kEntryByChildren', .)[1]))]"/>
<xsl:template match="entry">
<tr>
<xsl:apply-templates/>
<td><xsl:value-of select="count(key('kEntryByChildren', .))"/></td>
</tr>
</xsl:template>
<xsl:template match="entry/*">
<td><xsl:value-of select="."/></td>
</xsl:template>
<xsl:template match="/*">
<table>
<xsl:apply-templates/>
</table>
</xsl:template>
</xsl:stylesheet>
当应用于提供的 XML 时(片段被包装到单个顶部元素以获得格式良好的 XML 文档):
<t>
<entry>
<attribute1>A</attribute1>
<attribute2>B</attribute2>
</entry>
<entry>
<attribute1>A</attribute1>
<attribute2>B</attribute2>
</entry>
<entry>
<attribute1>C</attribute1>
<attribute2>D</attribute2>
</entry>
<entry>
<attribute1>E</attribute1>
<attribute2>F</attribute2>
</entry>
</t>
产生想要的正确结果:
<table>
<tr>
<td>A</td>
<td>B</td>
<td>2</td>
</tr>
<tr>
<td>C</td>
<td>D</td>
<td>1</td>
</tr>
<tr>
<td>E</td>
<td>F</td>
<td>1</td>
</tr>
</table>
当应用于这个棘手的 XML 文档时(如果我们使用子值的简单串联,我们会错误地得出前三个entry
元素“相同”的结论):
<t>
<entry>
<attribute1>AB</attribute1>
<attribute2>C</attribute2>
</entry>
<entry>
<attribute1>A</attribute1>
<attribute2>BC</attribute2>
</entry>
<entry>
<attribute1>A</attribute1>
<attribute2>BC</attribute2>
</entry>
<entry>
<attribute1>C</attribute1>
<attribute2>D</attribute2>
</entry>
<entry>
<attribute1>E</attribute1>
<attribute2>F</attribute2>
</entry>
</t>
产生正确的结果:
<table>
<tr>
<td>AB</td>
<td>C</td>
<td>1</td>
</tr>
<tr>
<td>A</td>
<td>BC</td>
<td>2</td>
</tr>
<tr>
<td>C</td>
<td>D</td>
<td>1</td>
</tr>
<tr>
<td>E</td>
<td>F</td>
<td>1</td>
</tr>
</table>
说明:
正确使用孟池分组法。
请注意:
此解决方案不依赖于元素的名称和子元素的数量entry
,因此如果有两个以上的子元素,或者预先命名未知的不同数量的子元素,则可以应用此解决方案。
这里我们假设只有当相同的孩子具有相同的值时,所有孩子的字符串值的串联是相同的。
二、完整的 XSLT 1.0 解决方案:
如果无法保证上述假设 2.,这是一种可能的 XSLT 1.0 解决方案:
<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="entry">
<xsl:variable name="vChildrenFp">
<xsl:for-each select="*">
<xsl:value-of select="concat(., '+')"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vPrecedingSame">
<xsl:for-each select="preceding-sibling::entry">
<xsl:variable name="vthisFP">
<xsl:for-each select="*">
<xsl:value-of select="concat(., '+')"/>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$vthisFP = $vChildrenFp">1</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="not(string($vPrecedingSame))">
<xsl:variable name="vFollowingSame">
<xsl:for-each select="following-sibling::entry">
<xsl:variable name="vthisFP">
<xsl:for-each select="*">
<xsl:value-of select="concat(., '+')"/>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$vthisFP = $vChildrenFp">1</xsl:if>
</xsl:for-each>
</xsl:variable>
<tr>
<xsl:apply-templates/>
<td><xsl:value-of select="string-length($vFollowingSame)+1"/></td>
</tr>
</xsl:if>
</xsl:template>
<xsl:template match="entry/*">
<td><xsl:value-of select="."/></td>
</xsl:template>
<xsl:template match="/*">
<table>
<xsl:apply-templates/>
</table>
</xsl:template>
</xsl:stylesheet>
当应用于同一个 XML 文档(上图)时,会产生相同的正确结果:
<table>
<tr>
<td>A</td>
<td>B</td>
<td>2</td>
</tr>
<tr>
<td>C</td>
<td>D</td>
<td>1</td>
</tr>
<tr>
<td>E</td>
<td>F</td>
<td>1</td>
</tr>
</table>
说明:
对于每个entry
元素,我们为其子元素生成一个“指纹”(FP),entry
如果其前面的兄弟entry
元素都没有相同的子元素指纹,则处理该元素。
“相同”entry
元素的计数以类似的方式完成——对于entry
具有相同子 FP 值的任何后续兄弟元素,我们输出单个字符('1')。总计数是这样生成的字符串(“1”)的字符串长度加 1。
三、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="my xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pExoticString" select="'+'"/>
<xsl:template match="/*">
<table>
<xsl:for-each-group select="entry" group-by="my:fingerprint(.)">
<tr>
<xsl:apply-templates/>
<td><xsl:value-of select="count(current-group())"/></td>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
<xsl:template match="entry/*">
<td><xsl:value-of select="."/></td>
</xsl:template>
<xsl:function name="my:fingerprint" as="xs:string">
<xsl:param name="pParent" as="element()"/>
<xsl:sequence select="string-join($pParent/*, $pExoticString)"/>
</xsl:function>
</xsl:stylesheet>
这个简单的解决方案可以轻松处理复杂的情况。当应用于最后一个 XML 文档时,会产生所需的正确结果:
<table>
<tr>
<td>AB</td>
<td>C</td>
<td>1</td>
</tr>
<tr>
<td>A</td>
<td>BC</td>
<td>2</td>
</tr>
<tr>
<td>C</td>
<td>D</td>
<td>1</td>
</tr>
<tr>
<td>E</td>
<td>F</td>
<td>1</td>
</tr>
</table>
说明:
正确使用xsl:for-each-group
,xsl:function
和.current-group()
string-join()