【问题标题】:How do I prevent duplicates, in XSL?如何在 XSL 中防止重复?
【发布时间】:2010-04-19 18:20:15
【问题描述】:

如何防止重复条目进入列表,然后理想地对该列表进行排序?我正在做的是,当一个级别的信息丢失时,从它下面的一个级别获取信息,在上面的级别构建缺失的列表。目前,我有类似这样的 XML:

<c03 id="ref6488" level="file">
    <did>
        <unittitle>Clinic Building</unittitle>
        <unitdate era="ce" calendar="gregorian">1947</unitdate>
    </did>
    <c04 id="ref34582" level="file">
        <did>
            <container label="Box" type="Box">156</container>
            <container label="Folder" type="Folder">3</container>
        </did>
    </c04>
    <c04 id="ref6540" level="file">
        <did>
            <container label="Box" type="Box">156</container>
            <unittitle>Contact prints</unittitle>
        </did>
    </c04>
    <c04 id="ref6606" level="file">
        <did>
            <container label="Box" type="Box">154</container>
            <unittitle>Negatives</unittitle>
        </did>
    </c04>
</c03>

然后我应用以下 XSL:

<xsl:template match="c03/did">
    <xsl:choose>
        <xsl:when test="not(container)">
            <did>
                <!-- If no c03 container item is found, look in the c04 level for one -->
                <xsl:if test="../c04/did/container">

                    <!-- If a c04 container item is found, use the info to build a c03 version -->
                    <!-- Skip c03 container item, if still no c04 items found -->
                    <container label="Box" type="Box">

                        <!-- Build container list -->
                        <!-- Test for more than one item, and if so, list them, -->
                        <!-- separated by commas and a space -->
                        <xsl:for-each select="../c04/did">
                            <xsl:if test="position() &gt; 1">, </xsl:if>
                            <xsl:value-of select="container"/>
                        </xsl:for-each>
                    </container>                    
            </did>
        </xsl:when>

        <!-- If there is a c03 container item(s), list it normally -->
        <xsl:otherwise>
            <xsl:copy-of select="."/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

但我得到的“容器”结果是

<container label="Box" type="Box">156, 156, 154</container>

当我想要的时候

<container label="Box" type="Box">154, 156</container>

下面是我想要得到的完整结果:

<c03 id="ref6488" level="file">
    <did>
        <container label="Box" type="Box">154, 156</container>
        <unittitle>Clinic Building</unittitle>
        <unitdate era="ce" calendar="gregorian">1947</unitdate>
    </did>
    <c04 id="ref34582" level="file">
        <did>
            <container label="Box" type="Box">156</container>
            <container label="Folder" type="Folder">3</container>
        </did>
    </c04>
    <c04 id="ref6540" level="file">
        <did>
            <container label="Box" type="Box">156</container>
            <unittitle>Contact prints</unittitle>
        </did>
    </c04>
    <c04 id="ref6606" level="file">
        <did>
            <container label="Box" type="Box">154</container>
            <unittitle>Negatives</unittitle>
        </did>
    </c04>
</c03>

提前感谢您的帮助!

【问题讨论】:

  • 好问题 (+1)。请参阅我对 XSLT 1.0 解决方案的回答,该解决方案比当前选择的 XSLT 2.0 解决方案更短。 :)

标签: xslt duplicates


【解决方案1】:

这个问题不需要 XSLT 2.0 解决方案

这是一个 XSLT 1.0 解决方案,它比当前选择的 XSLT 2.0 解决方案更紧凑(35 行对 43 行):

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:key name="kBoxContainerByVal"
     match="container[@type='Box']" use="."/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="c03/did[not(container)]">
   <xsl:copy>

   <xsl:variable name="vContDistinctValues" select=
    "/*/*/*/container[@type='Box']
            [generate-id()
            =
             generate-id(key('kBoxContainerByVal', .)[1])
            ]
            "/>

    <container label="Box" type="Box">
      <xsl:for-each select="$vContDistinctValues">
        <xsl:sort data-type="number"/>

        <xsl:value-of select=
        "concat(., substring(', ', 1 + 2*(position() = last())))"/>
      </xsl:for-each>
    </container>
    <xsl:apply-templates/>
   </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

当对最初提供的 XML 文档应用此转换时,会产生正确的、想要的结果

<c03 id="ref6488" level="file">
   <did>
      <container label="Box" type="Box">156, 154</container>
      <unittitle>Clinic Building</unittitle>
      <unitdate era="ce" calendar="gregorian">1947</unitdate>
   </did>
   <c04 id="ref34582" level="file">
      <did>
         <container label="Box" type="Box">156</container>
         <container label="Folder" type="Folder">3</container>
      </did>
   </c04>
   <c04 id="ref6540" level="file">
      <did>
         <container label="Box" type="Box">156</container>
         <unittitle>Contact prints</unittitle>
      </did>
   </c04>
   <c04 id="ref6606" level="file">
      <did>
         <container label="Box" type="Box">154</container>
         <unittitle>Negatives</unittitle>
      </did>
   </c04>
</c03>

更新:

我没有注意到集装箱编号必须按顺序排列的要求。现在解决方案反映了这一点。

【讨论】:

  • 您的解决方案未对问题中要求的列表进行排序。通过在xsl:for-each 循环中添加&lt;xsl:sort/&gt; 可以轻松解决问题。
  • @markusk:谢谢,我通常早上很困。在这种情况下&lt;xsl:sort/&gt; 也需要data-type="number"
【解决方案2】:

试试下面的代码:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output indent="yes"></xsl:output>

<xsl:template match="node() | @*">
  <xsl:copy>
    <xsl:apply-templates select="node() | @*"/>
  </xsl:copy>
</xsl:template>

  <xsl:template match="c03/did">
    <xsl:choose>
      <xsl:when test="not(container)">
        <did>
          <!-- If no c03 container item is found, look in the c04 level for one -->
          <xsl:if test="../c04/did/container">
            <xsl:variable name="foo" select="../c04/did/container[@type='Box']/text()"/>
            <!-- If a c04 container item is found, use the info to build a c03 version -->
            <!-- Skip c03 container item, if still no c04 items found -->
            <container label="Box" type="Box">

              <!-- Build container list -->
              <!-- Test for more than one item, and if so, list them, -->
              <!-- separated by commas and a space -->
              <xsl:for-each select="distinct-values($foo)">
                <xsl:sort />
                <xsl:if test="position() &gt; 1">, </xsl:if>
                <xsl:value-of select="." />
              </xsl:for-each>
            </container>
            <xsl:apply-templates select="*" />
          </xsl:if>
        </did>
      </xsl:when>

      <!-- If there is a c03 container item(s), list it normally -->
      <xsl:otherwise>
        <xsl:copy-of select="."/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

它看起来很像你想要的输出:

<?xml version="1.0" encoding="UTF-8"?>
<c03 id="ref6488" level="file">
  <did>
      <container label="Box" type="Box">154, 156</container>
      <unittitle>Clinic Building</unittitle>
      <unitdate era="ce" calendar="gregorian">1947</unitdate>
   </did>
  <c04 id="ref34582" level="file">
      <did>
         <container label="Box" type="Box">156</container>
         <container label="Folder" type="Folder">3</container>
      </did>
  </c04>
  <c04 id="ref6540" level="file">
      <did>
         <container label="Box" type="Box">156</container>
         <unittitle>Contact prints</unittitle>
      </did>
  </c04>
  <c04 id="ref6606" level="file">
      <did>
         <container label="Box" type="Box">154</container>
         <unittitle>Negatives</unittitle>
      </did>
  </c04>
</c03>

诀窍是同时使用&lt;xsl:sort&gt;distinct-values()。请参阅 Michael Key 的(恕我直言)好书“XSLT 2.0 and XPATH 2.0”

【讨论】:

  • 我使用的是 XSLT2,所以我采用了这个解决方案,它工作得很好。唯一的事情是,我不得不注释掉 \ 出于某种原因,它正在复制“unittitle”节点。非常感谢!
  • 我同意你对 Michael Kay 的书的高度评价。不幸的是,很少有人/组织切换到 XSLT 2.0。
【解决方案3】:

尝试在 xslt 中使用 Key 组,这里有一篇关于 Muenchian 方法的文章,它应该有助于消除重复项。 http://www.jenitennison.com/xslt/grouping/muenchian.html

【讨论】:

  • Jeni 的文章很好地解释了 Meunchian 方法,适合那些难以思考的人(比如我)!
【解决方案4】:

一个稍短的 XSLT 2.0 版本,结合了其他答案的方法。请注意,排序是按字母顺序排列的,因此如果找到标签“54”和“156”,输出将是“156, 54”。如果需要数字排序,请使用&lt;xsl:sort select="number(.)"/&gt; 而不是&lt;xsl:sort/&gt;

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/> 
    <xsl:strip-space elements="*"/>

    <xsl:template match="node()|@*"> 
        <xsl:copy> 
            <xsl:apply-templates select="node()|@*"/> 
        </xsl:copy> 
    </xsl:template> 

    <xsl:template match="c03/did[not(container)]">
        <xsl:variable name="containers" 
                      select="../c04/did/container[@label='Box'][text()]"/>
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:if test="$containers">
                <container label="Box" type="Box">
                    <xsl:for-each select="distinct-values($containers)">
                        <xsl:sort/>
                        <xsl:if test="position() != 1">, </xsl:if>
                        <xsl:value-of select="."/>
                    </xsl:for-each>
                </container> 
            </xsl:if>
            <xsl:apply-templates select="node()"/> 
        </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet>

【讨论】:

    【解决方案5】:

    真正的 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"
      exclude-result-prefixes="xs"
    >
      <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
      <xsl:template match="node()|@*">
        <xsl:copy>
          <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="c03/did[not(container)]">
        <xsl:copy>
          <xsl:copy-of select="@*"/>
    
          <xsl:variable name="vContDistinctValues" as="xs:integer*">
            <xsl:perform-sort select=
              "distinct-values(/*/*/*/container[@type='Box']/text()/xs:integer(.))">
              <xsl:sort/>
            </xsl:perform-sort>
          </xsl:variable>
    
          <xsl:if test="$vContDistinctValues">
            <container label="Box" type="Box">
              <xsl:value-of select="$vContDistinctValues" separator=","/>
            </container>
          </xsl:if>
          <xsl:apply-templates/>
        </xsl:copy>
      </xsl:template>
    </xsl:stylesheet>
    

    请注意:

    1. 类型的使用避免了在&lt;xsl:sort/&gt;中指定data-type的需要。

    2. &lt;xsl:value-of/&gt;separator属性的使用

    【讨论】:

    • +1 做得很好。但是,我们知道c03 元素将是根吗?发帖人只说输入是“相似的”,所以我觉得相对 XPaths(即../c04/container,或者../*/container)而不是绝对的(/*/*/*/container)稍微舒服一些。这样,即使 c03 元素出现在文档结构的更下方,样式表也能正常工作。
    • @markusk 再次发表好评论,感谢您的支持!是的,我们见证了 OP 如何不断改变他们对问题的定义。有时我很想充当算命先生,但这本身也有风险。此外,XSLT 代码变得越来越不直接与实际的 XML 相关,因此更难以理解。这就是为什么在这种情况下,我通常更愿意尽可能接近已发布的 XML。我已经多次编写了最通用的 XSLT 代码,例如XPath Visualizer/FXSL,但这里的目的是尽可能具体/有用。 :)
    【解决方案6】:

    以下 XSLT 1.0 转换可以满足您的需求

    <xsl:stylesheet 
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    > 
      <xsl:output encoding="utf-8" />
    
      <!-- key to index containers by these three distinct qualities: 
           1: their ancestor <c??> node (represented as its unique ID)
           2: their @type attribute value
           3: their node value (i.e. their text) -->
      <xsl:key 
        name  = "kContainer" 
        match = "container"
        use   = "concat(generate-id(../../..), '|', @type, '|', .)"
      />
    
      <!-- identity template to copy everything as is by default -->
      <xsl:template match="node()|@*">
        <xsl:copy>
          <xsl:apply-templates select="node()|@*" />
        </xsl:copy>
      </xsl:template>
    
      <!-- special template for <did>s without a <container> child -->
      <xsl:template match="did[not(container)]">
        <xsl:copy>
          <xsl:copy-of select="@*" />
          <container label="Box" type="Box">
            <!-- from subordinate <container>s of type Box, use the ones
                 that are *the first* to have that certain combination 
                 of the three distinct qualities mentioned above -->
            <xsl:apply-templates mode="list-values" select="
              ../*/did/container[@type='Box'][
                generate-id()
                =
                generate-id(
                  key(
                    'kContainer', 
                    concat(generate-id(../../..), '|', @type, '|', .)
                  )[1]
                )
              ]
            ">
              <!-- sort them by their node value -->
              <xsl:sort select="." data-type="number" />
            </xsl:apply-templates>
          </container>
          <xsl:apply-templates select="node()" />
        </xsl:copy>
      </xsl:template>
    
      <!-- generic template to make list of values from any node-set -->
      <xsl:template match="*" mode="list-values">
        <xsl:value-of select="." />
        <xsl:if test="position() &lt; last()">
          <xsl:text>, </xsl:text>
        </xsl:if>
      </xsl:template>
    
    </xsl:stylesheet>
    

    返回

    <c03 id="ref6488" level="file">
      <did>
        <container label="Box" type="Box">154, 156</container>
        <unittitle>Clinic Building</unittitle>
        <unitdate era="ce" calendar="gregorian">1947</unitdate>
      </did>
      <c04 id="ref34582" level="file">
        <did>
          <container label="Box" type="Box">156</container>
          <container label="Folder" type="Folder">3</container>
        </did>
      </c04>
      <c04 id="ref6540" level="file">
        <did>
          <container label="Box" type="Box">156</container>
          <unittitle>Contact prints</unittitle>
        </did>
      </c04>
      <c04 id="ref6606" level="file">
        <did>
          <container label="Box" type="Box">154</container>
          <unittitle>Negatives</unittitle>
        </did>
      </c04>
    </c03>
    

    generate-id() = generate-id(key(...)[1]) 部分是所谓的 Muenchian 分组。除非您可以使用 XSLT 2.0,否则这是可行的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-06
      • 1970-01-01
      • 2022-07-12
      • 2021-01-27
      • 2020-04-08
      相关资源
      最近更新 更多