2013-08-30 68 views
2

給定一個深度嵌套的XML樹,我想找到某個元素。在那一點上,我想將X包裝在一個與更高元素處於同一級別的新元素中。然後我想從「某些」元素之後的點開始繼續使用原始樹的其餘部分。如何使用XSLT拆分嵌套結構?

例如,假設該輸入:

<root> 
    <branch att="yo"> 
     <div stuff="no"> 
      <ul> 
       <li>Item 1</li> 
       <li>Item 2</li> 
       <li>Item 3</li> 
      </ul> 
     </div>   
    </branch> 
</root> 

我想找到項目2在< UL>(易peasy)。我想在項目2之前插入一個新的分支級元素。然後我想繼續使用項目2(繼續祖先節點)。也就是說,我想這樣的輸出:

<root> 
    <branch att="yo"> 
     <div stuff="no"> 
      <ul> 
       <li>Item 1</li> 
      </ul> 
     </div> 
    </branch> 
    <branch> 
     <div> 
      <p>New branch here</p> 
     </div> 
    </branch> 
    <branch att="yo"> 
     <div stuff="no"> 
      <ul> 
       <li>Item 2</li> 
       <li>Item 3</li> 
      </ul> 
     </div>   
    </branch> 
</root> 

我有一個很難的問題開始使用此,爲了使廣義解。我認爲它會涉及多種模式或鍵以及處理祖先節點以查找節點名稱和屬性。任何幫助表示讚賞。

好吧,這是我迄今爲止。它部分地工作(例如,我複製了一些節點但沒有屬性;我複製了太多的節點,但這是我認爲的一個開始)。我的想法是,我需要一個遞歸函數。我從我關心的最遠祖先(分支)開始,對於每個最終後代是特定元素(li「item 2」)的每個子節點,我複製每個節點及其屬性,將其附加到前一個節點(即什麼newNewTree是)。並且我遞歸直到到達某個元素(li的第2項),此時我返回NewNewTree變量,我的意圖是將它作爲正確祖先元素的樹(沒有文本)。爲了使這項工作,我認爲我需要做的是通過一個身份模板覆蓋從該函數處理每個節點,複製該節點及其屬性。但是今天晚上太累了,無法嘗試。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
       xmlns:xs="http://www.w3.org/2001/XMLSchema" 
       xmlns:my="http://www.example.com" 
       exclude-result-prefixes="xs my" 
       version="2.0"> 
    <xsl:template match="@*|node()"> 
    <xsl:copy> 
     <xsl:apply-templates select="@*|node()"/> 
    </xsl:copy> 
    </xsl:template> 
    <xsl:template match="/"> 
    <xsl:sequence select="$origNodesNoText" /> 
    </xsl:template> 
    <xsl:variable name="ancestorElemName" select="'div'" /> 
    <xsl:variable name="origNodesNoText"> 
    <xsl:apply-templates select="/" mode="origNodesNoText"/> 
    </xsl:variable> 
    <xsl:template match="text()" mode="origNodesNoText"/> 
    <xsl:template match="li[.='Item 2']" mode="origNodesNoText"> 
    <xsl:variable name="newTree"> 
     <xsl:element name="{local-name(ancestor::*[local-name()=$ancestorElemName][1])}"> 
     <xsl:for-each select="ancestor::*[local-name()=$ancestorElemName][1]/attribute::*"> 
      <xsl:attribute name="{local-name()}" select="."/> 
     </xsl:for-each> 
     </xsl:element> 
    </xsl:variable> 
    <xsl:sequence select="my:split(ancestor::*[local-name()=$ancestorElemName][1],.,$newTree)" /> 
    </xsl:template> 

    <xsl:function name="my:split"> 
    <xsl:param name="ancestorElemNode" /> 
    <xsl:param name="callingNode" /> 
    <xsl:param name="newTree" /> 
    <xsl:message>Calling my split</xsl:message> 
    <xsl:choose> 
     <xsl:when test="$ancestorElemNode ne $callingNode"> 
      <xsl:message>Found a node to copy its name and attributes</xsl:message> 
      <xsl:variable name="newNewTree"> 
       <xsl:for-each select="$newTree/node()"> 
        <xsl:copy /> <!-- doesn't copy attribute nodes so not what we want --> 
       </xsl:for-each> 
       <xsl:element name="{local-name($ancestorElemNode)}"> 
        <xsl:for-each select="$ancestorElemNode/attribute::*"> 
         <xsl:attribute name="{local-name()}" select="."/> 
        </xsl:for-each> 
       </xsl:element>  
      </xsl:variable> 
      <xsl:sequence select="my:split($ancestorElemNode/child::*[descendant::*[. eq $callingNode]][1],$callingNode,$newNewTree)" /> 
     </xsl:when> 
     <xsl:otherwise> 
      <xsl:message>Found the end point</xsl:message> 
      <xsl:sequence select="$newTree" /> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:function> 

</xsl:stylesheet> 

回答

0

您希望以下列方式處理輸入的節點。首先,讓我們定義我們經常需要參考的節點的兩個術語:

  • 第二個列表項是我們的目標元素;我們將它稱爲舷窗。
  • 包圍靶心的分支元素需要拆分;我們將其稱爲香蕉。

現在我們可以在輸入樹中根據輸出樹的對應關係定義幾類節點。

  • Banana以外的所有東西都被複制,如同一個標識轉換;它對應於輸出中具有相同類型的節點,具有相同的名稱,並且有一個例外,它具有類似的屬性和子級序列。例外是香蕉的父親:它的子代序列包含兩個對應於香蕉的元素,以及它們之間的第三個元素(這裏命名爲branch)。
  • 作爲Bullseye的祖先和香蕉的後代(或香蕉本身)的每個元素都對應於輸出樹中的兩個元素,具有相似的名稱和屬性。
  • 以文件順序在Bullseye之前的香蕉的每一個後裔,不是Bullseye的祖先,也不是Bullseye的祖先的屬性,被複制一次到輸出,如身份轉換,在第一個副本中的香蕉。
  • 以文檔順序跟隨Bullseye並且不是Bullseye的祖先並且不是Bullseye的祖先的屬性的香蕉的每個後代都被複制一次到輸出中,如在身份轉換中一樣,在第二副本中的香蕉。 Bullseye本身也是。

我會這樣做一個近身份變換。我們需要對香蕉及其後代進行特殊處理。其中一些需要複製兩次,有些只需複製一次,但最簡單的做法是僅處理整個子樹兩次。

樣式表作爲一個整體可能是這個樣子:

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

    <xsl:output method="xml" indent="yes"/> 

    <xsl:variable name="Bullseye" select="//li[.='Item 2']"/> 

    <!--* Default identity transform *--> 
    <xsl:template match="comment()"> 
    <xsl:comment><xsl:value-of select="."/></xsl:comment> 
    </xsl:template> 

    <xsl:template match="processing-instruction()"> 
    <xsl:variable name="pitarget" select="name()"/> 
    <xsl:processing-instruction name="{$pitarget}"> 
     <xsl:value-of select="."/> 
    </xsl:processing-instruction> 
    </xsl:template> 

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

    <!--* Banana *--> 
    <xsl:template match="branch[descendant::* intersect $Bullseye]">  
    <xsl:copy> 
     <xsl:apply-templates select="@*|node()" mode="pre-bullseye"/> 
    </xsl:copy> 
    <xsl:call-template name="insertion"/> 
    <xsl:copy> 
     <xsl:apply-templates select="@*|node()" mode="post-bullseye"/> 
    </xsl:copy> 
    </xsl:template> 

    <!--* first pass over subtree rooted at Banana *--> 
    <xsl:template match="comment()" mode="pre-bullseye"> 
    <xsl:apply-templates select="."/> 
    </xsl:template> 
    <xsl:template match="processing-instruction()" mode="pre-bullseye"> 
    <xsl:apply-templates select="."/> 
    </xsl:template> 
    <xsl:template match="@*|*|text()" mode="pre-bullseye"> 
    <xsl:choose> 
     <xsl:when test="descendant::* intersect $Bullseye"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*"/> 
      <xsl:apply-templates select="node()" mode="pre-bullseye"/> 
     </xsl:copy> 
     </xsl:when> 
     <xsl:when test=". &lt;&lt; $Bullseye"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
     </xsl:when> 
     <xsl:when test=". is $Bullseye"/>  
     <xsl:when test=". >> $Bullseye"/> 
     <xsl:otherwise> 
     <xsl:message terminate="yes" 
      >Unexpected case in mode pre-bullseye, dying now.</xsl:message> 
     </xsl:otherwise> 
    </xsl:choose> 

    </xsl:template> 

    <!--* Second pass over subtree rooted at Banana *--> 
    <xsl:template match="comment()" mode="post-bullseye"> 
    <xsl:apply-templates select="."/> 
    </xsl:template> 
    <xsl:template match="processing-instruction()" mode="post-bullseye"> 
    <xsl:apply-templates select="."/> 
    </xsl:template> 
    <xsl:template match="@*|*|text()" mode="post-bullseye"> 
    <xsl:choose> 
     <xsl:when test="descendant::* intersect $Bullseye"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*"/> 
      <xsl:apply-templates select="node()" mode="post-bullseye"/> 
     </xsl:copy> 
     </xsl:when> 
     <xsl:when test=". &lt;&lt; $Bullseye"/> 

     <xsl:when test=". is $Bullseye"> 
     <xsl:copy-of select="."/> 
     </xsl:when> 
     <xsl:when test=". >> $Bullseye"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:message terminate="yes" 
     >Unexpected case in post-bullseye, dying now.</xsl:message> 
     </xsl:otherwise> 
    </xsl:choose> 

    </xsl:template> 

    <xsl:template name="insertion"> 
    <branch> 
     <div> 
     <p>New branch here.</p> 
     </div> 
    </branch> 
    </xsl:template> 

</xsl:stylesheet> 

當您提供的輸入運行,這個樣式表產生以下輸出。

<?xml version="1.0" encoding="UTF-8"?> 
<root> 
    <branch att="yo"> 
     <div stuff="no"> 
      <ul> 
       <li>Item 1</li> 
       </ul> 
     </div> 
    </branch> 
    <branch> 
     <div> 
     <p>New branch here.</p> 
     </div> 
    </branch> 
    <branch> 
     <div stuff="no"> 
     <ul> 
      <li>Item 2</li> 
       <li>Item 3</li> 
      </ul> 
     </div>   
    </branch> 
</root> 
+0

W¯¯流。謝謝。那個好漂亮。只有一個問題,分支上的@att不會在香蕉後複製。我做了一個改變,它使它起作用,我不確定這個改變的其他後果是什麼。 (我不熟悉<<構造)。以下是我必須做的: – user5923

+0

在此模板中: 在選擇,我已經當到本作空:的 user5923

+0

我還沒有發現負面效應的變化了xsl:when元素我提到。先生非常感謝您。 – user5923

0

這是另一種選擇。基本上你:

  • 定義目標元素的值(參數target)。
  • 標識目標元素(密鑰branch)並應用該元素的模板。 (如果有多個具有該值的元素,則它首次出現分支。)
  • 從頂部開始爲前面的同胞應用模板。 備註:「top」是/*/*。如果根元素有多個子元素,則此樣式表需要一些調整。
  • 將模板應用到目標元素。我們通過一個參數(branch),所以我們知道這是插入新分支的地方。
  • 再次將模板應用到目標元素(不包含branch參數)以及以下兄弟。

XML輸入

<root> 
    <branch att="yo"> 
     <div stuff="no"> 
      <ul> 
       <li>Item 1</li> 
       <li>Item 2</li> 
       <li>Item 3</li> 
      </ul> 
     </div>   
    </branch> 
</root> 

XSLT 2.0

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

    <xsl:param name="target" select="'Item 2'"/> 
    <xsl:key name="branch" match="*[.=$target]" use="."/> 

    <xsl:template match="/*"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|(//*[key('branch',.)])[1]"/> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="*"> 
     <xsl:variable name="precID" select="for $id in preceding-sibling::* return generate-id($id)"/> 
     <xsl:variable name="follID" select="for $id in (self::*,following-sibling::*) return generate-id($id)"/> 
     <xsl:apply-templates select="/*/*" mode="ident"> 
      <xsl:with-param name="restricted2IDs" select="$precID" tunnel="yes"/>   
     </xsl:apply-templates> 
     <xsl:apply-templates select="/*/*" mode="ident"> 
      <xsl:with-param name="restricted2IDs" select="generate-id(current())" tunnel="yes"/> 
      <xsl:with-param name="branch" select="';-)'" tunnel="yes"/> 
     </xsl:apply-templates> 
     <xsl:apply-templates select="/*/*" mode="ident"> 
      <xsl:with-param name="restricted2IDs" select="$follID" tunnel="yes"/>   
     </xsl:apply-templates>  
    </xsl:template> 

    <xsl:template match="@*|node()" mode="ident"> 
     <xsl:param name="restricted2IDs" tunnel="yes"/> 
     <xsl:param name="branch" tunnel="yes"/> 
      <xsl:choose> 
       <xsl:when test="*[generate-id()=$restricted2IDs]"> 
        <xsl:choose> 
         <xsl:when test="$branch"> 
          <p>New branch here</p> 
         </xsl:when> 
         <xsl:otherwise> 
          <xsl:copy> 
           <xsl:apply-templates select="@*|node()[generate-id()=$restricted2IDs]" mode="ident"/>              
          </xsl:copy>       
         </xsl:otherwise> 
        </xsl:choose> 
       </xsl:when> 
       <xsl:when test="descendant::*[generate-id()=$restricted2IDs]"> 
        <xsl:copy> 
         <!--If you want the attributes in the new branch, remove the "if" and just use "@*|node".--> 
         <xsl:apply-templates select="if ($branch) then node() else @*|node()" mode="ident"/>       
        </xsl:copy> 
       </xsl:when> 
       <xsl:otherwise> 
        <xsl:copy> 
         <xsl:apply-templates select="@*|node()" mode="ident"/> 
        </xsl:copy> 
       </xsl:otherwise> 
      </xsl:choose> 
    </xsl:template> 

</xsl:stylesheet> 

XML輸出

<root> 
    <branch att="yo"> 
     <div stuff="no"> 
     <ul> 
      <li>Item 1</li> 
     </ul> 
     </div> 
    </branch> 
    <branch> 
     <div> 
     <p>New branch here</p> 
     </div> 
    </branch> 
    <branch att="yo"> 
     <div stuff="no"> 
     <ul> 
      <li>Item 2</li> 
      <li>Item 3</li> 
     </ul> 
     </div> 
    </branch> 
</root> 
+0

謝謝,丹尼爾。你的回答是信息。 – user5923