2010-04-19 87 views
7

如何防止重複條目進入列表,然後理想地對列表進行排序?我在做什麼,是什麼時候缺少某一級別的信息,從低於它的級別獲取信息,到上面的級別構建缺失的列表。目前,我有類似XML這樣:如何防止XSL中的重複項?

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

在此先感謝您的幫助!

+0

很好的問題(+1)的separator屬性。查看我對XSLT 1.0解決方案的回答,該解決方案比當前選定的XSLT 2.0解決方案更短。 :) – 2010-04-20 13:26:48

回答

1

試試下面的代碼:

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

的技巧是使用<xsl:sort>distinct-values()在一起。見邁克爾關鍵的(恕我直言)偉大的書 「XSLT 2.0和XPath 2.0」

+0

我使用的是XSLT2,所以我使用了這個解決方案,它工作得很好。唯一的問題是,我必須註釋掉\ 出於某種原因,它重複了「單元標題」節點。非常感謝! – LOlliffe 2010-04-19 23:34:55

+0

我分享你對邁克爾凱的書的高度評價。不幸的是,很少有人/組織轉向使用XSLT 2.0。 – 2010-05-01 16:57:04

0

以下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,否則這是要走的路。

2

對於這個問題,沒有必要使用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> 

更新:

我沒有注意到容器號碼必須出現排序的要求。現在解決方案反映了這一點

+0

您的解決方案不會對問題中要求的列表進行排序。通過在'xsl:for-each'循環中添加''輕鬆解決。 – markusk 2010-04-20 15:37:24

+0

@markusk:謝謝,我通常在早上很困。在這種情況下,''也需要'data-type =「number」'。 – 2010-04-20 16:05:47

1

稍微縮短XSLT 2.0版本,結合其他答案的方法。請注意,排序是按字母順序排列的,因此如果找到標籤「54」和「156」,則輸出將是「156,54」。如果需要數字排序,請使用<xsl:sort select="number(.)"/>而不是<xsl:sort/>

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

+1。這仍然是最短的XSLT 2.0解決方案! :) – 2010-04-20 19:42:48

1

一個真正的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. 使用的類型避免了需要指定的data-type<xsl:sort/>

  2. 使用<xsl:value-of/>

+1

+1很好完成。但是,我們知道'c03'元素是根嗎?海報只是說輸入是「相似的」,所以我對相對XPath(即'../ c04/container',或者'../*/ container')感覺稍微舒適一點,而不是絕對的( '/ */*/*/container')。這樣,即使'c03'元素出現在文檔結構的更下方,樣式表也可以工作。 – markusk 2010-04-20 19:07:39

+0

@markusk再次好評,感謝upvote!是的,我們目睹了OP如何不斷改變他們問題的定義。有時我很想成爲算命先生,但這本身也有風險。另外,XSLT代碼與實際的XML越來越不直接相關,因此更難以理解。這就是爲什麼在這種情況下,我通常寧願保持儘可能接近發佈的XML。我寫了很多最常用的XSLT代碼,例如XPath Visualizer/FXSL,但這裏的目的是儘可能多地具體/有用。 :) – 2010-04-20 19:41:03