2010-04-19 14 views
7

Làm cách nào để ngăn các mục trùng lặp vào danh sách, và sau đó lý tưởng, sắp xếp danh sách đó? Những gì tôi đang làm, là khi thông tin ở một cấp bị thiếu, lấy thông tin từ một cấp độ bên dưới nó, để xây dựng danh sách còn thiếu, ở cấp trên. Hiện nay, tôi có XML tương tự như sau:Làm cách nào để ngăn chặn các bản sao, trong 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> 

Sau đó tôi áp dụng XSL sau:

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

Nhưng tôi nhận được "container" kết quả của

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

khi những gì Tôi muốn là

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

Be thấp là kết quả đầy đủ mà tôi đang cố gắng đạt được:

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

Cảm ơn bạn đã giúp đỡ!

+0

Câu hỏi Tốt (+1). Xem câu trả lời của tôi cho một giải pháp XSLT 1.0, ngắn hơn là giải pháp XSLT 2.0 hiện đang được chọn. :) –

Trả lời

1

Hãy thử đoạn mã sau:

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

Nó trông khá nhiều như đầu ra bạn muốn:

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

Bí quyết là sử dụng <xsl:sort>distinct-values() với nhau. Xem (IMHO) cuốn sách tuyệt vời từ Michael Key "XSLT 2.0 và XPath 2.0"

+0

Tôi đang sử dụng XSLT2, vì vậy tôi đã đi với giải pháp này, và nó đã làm việc tuyệt vời. Điều duy nhất, là tôi đã phải bình luận ra \ Vì lý do nào đó, nó đã nhân bản các nút "unittitle". Cám ơn rất nhiều! – LOlliffe

+0

Tôi chia sẻ ý kiến ​​của bạn về cuốn sách của Michael Kay. Thật không may, quá ít người/tổ chức đã chuyển sang XSLT 2.0. –

1

thử sử dụng Nhóm khóa trong xslt, đây là bài viết về phương pháp Muenchian sẽ giúp loại bỏ trùng lặp. http://www.jenitennison.com/xslt/grouping/muenchian.html

+0

Bài viết của Jeni giải thích phương pháp Meunchian rất độc đáo cho khó nghĩ (như tôi)! –

0

sau Việc chuyển đổi XSLT 1.0 làm những gì bạn đang tìm kiếm

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

Returns

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

Phần generate-id() = generate-id(key(...)[1]) được gọi là nhóm Muenchian. Trừ khi bạn có thể sử dụng XSLT 2.0, đây là cách để đi.

2

Không cần giải pháp XSLT 2.0 cho vấn đề này.

Dưới đây là một giải pháp XSLT 1.0, đó là nhỏ gọn hơn so với XSLT được chọn hiện thời 2.0 giải pháp (35 dòng so với 43 dòng):

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

Khi chuyển đổi này được áp dụng trên được cung cấp ban đầu tài liệu XML, đúng, kết quả muốn được sản xuất:

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

cập nhật:

Tôi không nhận thấy yêu cầu rằng số vùng chứa phải được sắp xếp. Bây giờ giải pháp phản ánh điều này.

+0

Giải pháp của bạn không sắp xếp danh sách được yêu cầu trong câu hỏi. Dễ dàng khắc phục bằng cách thêm '' vào trong vòng lặp 'xsl: for-each'. – markusk

+0

@markusk: Cảm ơn, tôi thường buồn ngủ sớm vào buổi sáng. '' cũng cần 'data-type =" number "' trong trường hợp này. –

1

Phiên bản XSLT 2.0 hơi ngắn hơn, kết hợp các cách tiếp cận từ các câu trả lời khác. Lưu ý rằng sắp xếp theo thứ tự bảng chữ cái, để nếu các nhãn "54" và "156" được tìm thấy, đầu ra sẽ là "156, 54". Nếu cần loại số, hãy sử dụng <xsl:sort select="number(.)"/> thay vì <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. Đây vẫn là giải pháp XSLT 2.0 ngắn nhất! :) –

1

Một thực sự XSLT giải pháp 2.0, cũng khá ngắn:

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

Do lưu ý:

  1. Việc sử dụng các loại tránh sự cần thiết để xác định data-type trong <xsl:sort/>.

  2. Việc sử dụng các thuộc tính của separator<xsl:value-of/>

+1

+1 Được thực hiện tốt. Tuy nhiên, chúng ta có biết rằng phần tử 'c03' sẽ là gốc không? Các poster chỉ nói rằng đầu vào sẽ là "tương tự", vì vậy tôi cảm thấy hơi thoải mái hơn với XPath tương đối (ví dụ, '../ c04/container', hoặc có lẽ' ../*/ container') thay vì những cái tuyệt đối ('/ */*/*/container'). Bằng cách đó, biểu định kiểu hoạt động ngay cả khi phần tử 'c03' xuất hiện sâu hơn trong cấu trúc tài liệu. – markusk

+0

@markusk Nice bình luận một lần nữa, và cảm ơn cho upvote! Có, chúng tôi chứng kiến ​​làm thế nào OPs liên tục thay đổi định nghĩa của vấn đề của họ. Đôi khi tôi bị cám dỗ để hành động như một thầy bói, nhưng điều này cũng có một rủi ro. Ngoài ra, mã XSLT ngày càng trở nên không liên quan trực tiếp đến XML thực tế và do đó khó hiểu hơn. Đây là lý do tại sao, trong những trường hợp như thế này, tôi thường thích giữ nguyên XML càng tốt. Tôi đã viết nhiều lần mã XSLT chung nhất, ví dụ: XPath Visualizer/FXSL, nhưng mục đích ở đây là càng cụ thể/hữu ích, càng tốt. :) –

Các vấn đề liên quan