-2

如何使用 XSL 实现以下 XML 文件的转换(需要检查 <OptionData> 中的重复条目):

输入 XML:

<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>

输出 XML:

<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>

实际的 XML 文件有更多的数据,因此可能有更多类似的场景,因此需要通用。

4

1 回答 1

0

您还没有说 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>
于 2013-08-26T08:05:05.383 回答