您还没有说 XSLT 的版本。这是一个 XSLT 2.0 解决方案。XSLT 1.0 是可能的,但工作量很大。
这个 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:so="http://stackoverflow.com/questions/18432225"
exclude-result-prefixes="xsl xs so">
<xsl:output encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:function name="so:option-set-value" as="xs:string">
<xsl:param name="OptionData" as="element(OptionData)" />
<xsl:variable name="options">
<xsl:for-each select="$OptionData/Option">
<xsl:sort select="@Name"/>
<xsl:value-of select="concat(@Name,'=',@Value,';')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$options" />
</xsl:function>
<xsl:template match="/*">
<xsl:copy>
<xsl:for-each-group select="SOSData" group-by="so:option-set-value( OptionData)">
<SOSData>
<xsl:apply-templates select="current-group()[1]/OptionData" />
<xsl:apply-templates select="current-group()/VariantItem" />
</SOSData>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
...当应用于本文档时...
<ConfigurationSetDataExtract>
<SOSData>
<OptionData>
<Option Name="Controller Type" Value="Controller Type 1"/>
<Option Name="Spindle Type" Value="Single"/>
<Option Name="With Tailstock" Value="True"/>
</OptionData>
<VariantItem ItemID="000039"/>
</SOSData>
<SOSData>
<OptionData>
<Option Name="Integer1" Value="27"/>
<Option Name="Logical 1" Value="True"/>
<Option Name="Real 1" Value="56"/>
<Option Name="String 1" Value="Test"/>
</OptionData>
<VariantItem ItemID="000042"/>
</SOSData>
<SOSData>
<OptionData>
<Option Name="Controller Type" Value="Controller Type 1"/>
<Option Name="Spindle Type" Value="Four"/>
<Option Name="With Tailstock" Value="False"/>
</OptionData>
<VariantItem ItemID="000040"/>
</SOSData>
<SOSData>
<OptionData>
<Option Name="Controller Type" Value="Controller Type 1"/>
<Option Name="Spindle Type" Value="Single"/>
<Option Name="With Tailstock" Value="True"/>
</OptionData>
<VariantItem ItemID="000041"/>
</SOSData>
</ConfigurationSetDataExtract>
...将产生输出...
<ConfigurationSetDataExtract>
<SOSData>
<OptionData>
<Option Name="Controller Type" Value="Controller Type 1"/>
<Option Name="Spindle Type" Value="Single"/>
<Option Name="With Tailstock" Value="True"/>
</OptionData>
<VariantItem ItemID="000039"/>
<VariantItem ItemID="000041"/>
</SOSData>
<SOSData>
<OptionData>
<Option Name="Integer1" Value="27"/>
<Option Name="Logical 1" Value="True"/>
<Option Name="Real 1" Value="56"/>
<Option Name="String 1" Value="Test"/>
</OptionData>
<VariantItem ItemID="000042"/>
</SOSData>
<SOSData>
<OptionData>
<Option Name="Controller Type" Value="Controller Type 1"/>
<Option Name="Spindle Type" Value="Four"/>
<Option Name="With Tailstock" Value="False"/>
</OptionData>
<VariantItem ItemID="000040"/>
</SOSData>
</ConfigurationSetDataExtract>
笔记
如果您可以保证所有选项的名称始终保持相同的顺序,则此样式表可以大大简化。让我们知道是否是这种情况。
另一个 XSLT 2.0 解决方案。
尽管在规模上效率不高,但这种替代的 XSLT 2.0 解决方案应该会获得美观的标记。它在功能上等同于第一个给定的解决方案。
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="xsl fn">
<xsl:output encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:for-each-group select="SOSData" group-by="
fn:string-join(
for $z in (
for $i in (0 to count(OptionData/Option) - 1) return
for $x in OptionData/Option return
if ( $i = count(
for $y in OptionData/Option return
if ($y/@Name lt $x/@Name)
then $y
else ()
)
)
then $x
else ()
) return concat($z/@Name,'=',$z/@Value),';')">
<SOSData>
<xsl:apply-templates select="current-group()[1]/OptionData ,
current-group()/VariantItem" />
</SOSData>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XPath 2 排序技术来自杰出的Pavel Hlousek。谢谢帕维尔。
XSLT 1.0 解决方案
这是等效的 XSLT 1.0 解决方案...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:esl="http://exslt.org/common"
xmlns:so="http://stackoverflow.com/questions/18432225"
exclude-result-prefixes="so xsl esl ">
<xsl:output encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="option-set" match="SOSData" use="@so:key" />
<xsl:template match="@*|node()" mode="phase-1">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="phase-1" />
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="phase-2">
<xsl:element name="{name()}">
<xsl:apply-templates select="@*|node()" mode="phase-2" />
</xsl:element>
</xsl:template>
<xsl:template match="@*|processing-instruction()|comment()" mode="phase-2">
<xsl:copy />
</xsl:template>
<xsl:template match="SOSData" mode="phase-1">
<xsl:copy>
<xsl:variable name="options">
<xsl:for-each select="OptionData/Option">
<xsl:sort select="@Name"/>
<xsl:value-of select="concat(@Name,'=',@Value,';')" />
</xsl:for-each>
</xsl:variable>
<xsl:attribute name="so:key"><xsl:value-of select="$options" /></xsl:attribute>
<xsl:apply-templates select="@*|node()" mode="phase-1" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:variable name="phase-1-result">
<xsl:apply-templates select="." mode="phase-1" />
</xsl:variable>
<xsl:apply-templates select="esl:node-set($phase-1-result)/*" mode="phase-2" />
</xsl:template>
<xsl:template match="*[SOSData]" mode="phase-2">
<xsl:copy>
<xsl:apply-templates select="@* | node()[not(self::SOSData)]" mode="phase-2" />
<xsl:for-each select="SOSData[ generate-id() =
generate-id( key('option-set', @so:key)[1])]">
<xsl:apply-templates select="." mode="phase-2" />
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="SOSData" mode="phase-2">
<SOSData>
<xsl:apply-templates select="@*|node()|(key('option-set', @so:key)/VariantItem)" mode="phase-2" />
</SOSData>
</xsl:template>
<xsl:template match="@so:*" mode="phase-2" />
</xsl:stylesheet>