【问题标题】:Need XSLT transform to remove duplicate elements - sorted by an attribute需要 XSLT 转换以删除重复元素 - 按属性排序
【发布时间】:2012-02-19 20:00:06
【问题描述】:

我有一段糟糕的 XML 需要通过 BizTalk 进行处理,并且我已设法将其规范化为下面的示例。我不是 XSLT 忍者,但在 web 和 VS2010 调试器之间,我可以找到绕过 XSL 的方法。

我现在需要一点 XSLT 来“清除”重复的元素,只保留最新的元素,这由 ValidFromDate 属性中的日期决定。

ValidFromDate 属性属于 XSD:Date 类型。

<SomeData>
  <A ValidFromDate="2011-12-01">A_1</A>
  <A ValidFromDate="2012-01-19">A_2</A>
  <B CalidFromDate="2011-12-03">B_1</B>
  <B ValidFromDate="2012-01-17">B_2</B>
  <B ValidFromDate="2012-01-19">B_3</B>
  <C ValidFromDate="2012-01-20">C_1</C>
  <C ValidFromDate="2011-01-20">C_2</C>
</SomeData>

转换后我只想保留这些行:

<SomeData>
  <A ValidFromDate="2012-01-19">A_2</A>
  <B ValidFromDate="2012-01-19">B_3</B>
  <C ValidFromDate="2012-01-20">C_1</C>
</SomeData>

关于我如何将 XSL 组合在一起的任何线索?我已经在网上寻找解决方案,并尝试了很多聪明的 XSL 排序脚本,但我觉得没有一个能把我引向正确的方向。

【问题讨论】:

  • 另外...因为这将从 BizTalk 映射中调用,因此通过 .NET 我仅限于 XSLT 1.0 ...
  • 可能是C_1 而不是C_2
  • 是的,当然……谢谢。在我的任务中编辑了这个。
  • 首先......很多非常棒的解决方案。通过阅读所有这些,我的 XSLT mojo 变得更好。我没有时间尝试所有这些,除了我选择的解决方案之外,还有其他解决方案可以解决问题。

标签: xml xslt xpath biztalk


【解决方案1】:

比@lwburk 更简单、更短的 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:key name="kName" match="*/*" use="name()"/>

 <xsl:template match="/">
  <xsl:apply-templates select=
   "*/*[generate-id()
       =
        generate-id(key('kName', name())[1])
       ]
   "/>
 </xsl:template>

 <xsl:template match="*/*">
  <xsl:for-each select="key('kName', name())">
   <xsl:sort select="@ValidFromDate" order="descending"/>
   <xsl:if test="position() = 1">
    <xsl:copy-of select="."/>
   </xsl:if>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于提供的 XML 文档时

<SomeData>
    <A ValidFromDate="2011-12-01">A_1</A>
    <A ValidFromDate="2012-01-19">A_2</A>
    <B CalidFromDate="2011-12-03">B_1</B>
    <B ValidFromDate="2012-01-17">B_2</B>
    <B ValidFromDate="2012-01-19">B_3</B>
    <C ValidFromDate="2012-01-20">C_1</C>
    <C ValidFromDate="2011-01-20">C_2</C>
</SomeData>

产生想要的正确结果

<A ValidFromDate="2012-01-19">A_2</A>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>

【讨论】:

    【解决方案2】:

    不依赖输入顺序的 XLST 2.0 解决方案。

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
        <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
        <xsl:template match="/">
            <SomeData>
                <xsl:for-each-group select="/SomeData/*" group-by="name()">
                        <xsl:for-each select="current-group()">
                            <xsl:sort select="number(substring(attribute(),1,4))" order="descending" data-type="number"/> <!-- year-->
                            <xsl:sort select="number(substring(attribute(),6,2))" order="descending" data-type="number"/> <!-- month-->
                            <xsl:sort select="number(substring(attribute(),9,2))" order="descending" data-type="number"/> <!-- date-->
                            <xsl:if test="position()=1">
                                    <xsl:sequence select="."/>
                            </xsl:if>
                        </xsl:for-each>
                </xsl:for-each-group>
            </SomeData>
    </xsl:template>
    </xsl:stylesheet>
    

    【讨论】:

    • 是的 .. XSLT 2.0 使执行此操作变得更简单,但 Microsoft 还没有着手实施……不过还是谢谢。
    【解决方案3】:

    基于@ValidFromDate 订单:

    XSLT:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:key name="k" match="*" use="name()"/>
    
      <xsl:template match="SomeData">
        <xsl:copy>
          <xsl:apply-templates select="*[generate-id() = 
                               generate-id(key('k', name()))]"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="*">
        <xsl:apply-templates select="key('k', name())" mode="a">
          <xsl:sort select="@ValidFromDate" order="descending"/>
        </xsl:apply-templates>
      </xsl:template>
    
      <xsl:template match="*" mode="a">
        <xsl:if test="position() = 1">
          <xsl:copy-of select="."/>
        </xsl:if>
      </xsl:template>
    
    </xsl:stylesheet>
    

    适用于:

    <SomeData>
      <A ValidFromDate="2011-12-01">A_1</A>
      <A ValidFromDate="2012-01-19">A_2</A>
      <B CalidFromDate="2011-12-03">B_1</B>
      <B ValidFromDate="2012-01-17">B_2</B>
      <B ValidFromDate="2012-01-19">B_3</B>
      <C ValidFromDate="2012-01-20">C_1</C>
      <C ValidFromDate="2011-01-20">C_2</C>
    </SomeData>
    

    产生:

    <SomeData>
      <A ValidFromDate="2012-01-19">A_2</A>
      <B ValidFromDate="2012-01-19">B_3</B>
      <C ValidFromDate="2012-01-20">C_1</C>
    </SomeData>
    

    【讨论】:

      【解决方案4】:

      以下样式表会产生正确的结果,而不依赖于输入顺序:

      <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
          <xsl:output omit-xml-declaration="yes" indent="yes"/>
          <xsl:key name="byName" match="/SomeData/*" use="name()"/>
          <xsl:template match="@*|node()">
              <xsl:copy>
                  <xsl:apply-templates select="@*|node()"/>
              </xsl:copy>
          </xsl:template>
          <xsl:template match="SomeData">
              <xsl:copy>
                  <xsl:apply-templates select="@*"/>
                  <xsl:for-each select="*[generate-id()=
                                          generate-id(key('byName', name())[1])]">
                      <xsl:apply-templates select="key('byName', name())" mode="out">
                          <xsl:sort select="translate(@ValidFromDate, '-', '')" 
                                    data-type="number" order="descending"/>
                      </xsl:apply-templates>
                  </xsl:for-each>
              </xsl:copy>
          </xsl:template>
          <xsl:template match="SomeData/*" mode="out">
              <xsl:if test="position()=1">
                  <xsl:apply-templates select="."/>
              </xsl:if>
          </xsl:template>
      </xsl:stylesheet>
      

      输出:

      <SomeData>
         <A ValidFromDate="2012-01-19">A_2</A>
         <B ValidFromDate="2012-01-19">B_3</B>
         <C ValidFromDate="2012-01-20">C_1</C>
      </SomeData>
      

      请注意,结果与您列出的所需输出略有不同,因为C_1 实际上是最新的C 元素(即输入不是 已排序)。通过依赖初始排序顺序(并盲目遵循列出的预期输出),现有答案实际上是不正确的。

      说明:

      • xsl:key 将所有/SomeData/* 分组name()
      • 外层for-each选择每组的第一项
      • 然后将模板应用于该组的所有成员,这些成员按@ValidFromDate 排序
      • 一个额外的模板处理从每个排序组中挑选出第一个元素
      • 身份转换模板负责其余的工作

      【讨论】:

      • 嗨 lwburk ...感谢您指出 C_1 的事情。我坏。解决方案虽然效果很好。我选择了 Dimitre 的解决方案,因为它更短且更易于概览,因此对于那些必须对此进行维护的人来说,它是维护的。
      【解决方案5】:

      基于Pawel's answer,我做了如下修改,结果一样:

      <xsl:template match="/SomeData">
        <xsl:copy>
          <xsl:copy-of select="*[generate-id() = generate-id(key('element-key', name())[last()])]"/>
        </xsl:copy>
      </xsl:template>
      

      如果他们每次都产生相同的结果,我喜欢这个,因为它更干净一些。

      【讨论】:

        【解决方案6】:

        使用 Xslt 1.0 解决此问题的最佳解决方案是使用 Muenchian 分组。 (鉴于元素已经按 ValidFromDate 属性排序)下面的样式表应该可以解决问题:

        <?xml version="1.0" encoding="utf-8"?>
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
          <xsl:output method="xml" indent="yes"/>
        
          <xsl:key name="element-key" match="/SomeData/*" use="name()" />
        
          <xsl:template match="/SomeData">
            <xsl:copy>
              <xsl:for-each select="*[generate-id() = generate-id(key('element-key', name()))]">
                <xsl:copy-of select="(. | following-sibling::*[name() = name(current())])[last()]" />
              </xsl:for-each>
            </xsl:copy>
          </xsl:template>
        
        </xsl:stylesheet>
        

        这是我对您的示例 Xml 运行它时得到的结果:

        <?xml version="1.0" encoding="utf-8"?>
        <SomeData>
          <A ValidFromDate="2012-01-19">A_2</A>
          <B ValidFromDate="2012-01-19">B_3</B>
          <C ValidFromDate="2011-01-20">C_2</C>
        </SomeData>
        

        【讨论】:

        • 您好,我对您的回答进行了修改。考虑到 OP 的源 XML,两者都产生相同的输出,但我不知道是否存在更细微的差异。
        • 不应该有任何差异。你的版本更干净!谢谢!
        • @lwburk:是的,谢谢你指出这一点。我会深入看看你的答案。
        • @Pawel:实际上,元素没有排序——因此这个解决方案是不正确的。
        猜你喜欢
        • 1970-01-01
        • 2020-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多