あなたは 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>