检查XSL中的XML重复元素

时间:2013-08-25 18:23:38

标签: xslt

如何使用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文件有更多的数据,因此可能有更多类似的场景,因此需要是通用的。

1 个答案:

答案 0 :(得分: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>
相关问题