2011-12-22 99 views
2

我想使用XSLT呈現大型XML中的某些數據。 XML數據實際上是一種圖形數據,而不是分層的。和元素是相互關聯的,因此最終可能有一個循環引用(但是關係類型不同)。XSLT:檢查前面遍歷的元素

我試圖遍歷從一個元素的關係和訪問每個相關的元素等等。這樣,有時我達到了我已經穿過的一個元素。在這種情況下,我應該停止進一步運行,否則我將在一個循環中運行。

我的問題是,我無法存儲已經遍歷的元素列表,並且每次開始遍歷一個元素時都會查找,以便在查找元素時停止遍歷。

簡而言之,我想將元素保留在查找表中,並在遍歷時向其中添加每個元素。

有沒有解決方案?

+1

你可以更具體地瞭解你的輸入看起來如何?爲什麼不能將已遍歷的元素存儲在參數中,如http://lists.xml.org/archives/xml-dev/201110/msg00030.html中所做的那樣? –

+1

XSLT是一種聲明性語言,而不是程序性語言。因此沒有「早期」或「已經」或其他時間相關概念的概念。你需要重新思考這個過程的功能性。 –

+0

樣本輸入,所需的輸出以及樣式表的嘗試都會讓您更容易地向您展示適用於您的情況的解決方案。 – LarsH

回答

6

遞歸模板可以傳遞自己的參數,該參數保存「之前」處理的節點的節點集和要處理的節點的隊列。這是一個修改狀態變量的函數式編程。

樣品輸入:

<graph startNode="a"> 
    <graphNode id="a"> 
     <edge target="b" /> 
     <edge target="c" /> 
    </graphNode> 
    <graphNode id="b"> 
     <edge target="c" /> 
    </graphNode> 
    <graphNode id="c"> 
     <edge target="d" /> 
    </graphNode> 
    <graphNode id="d"> 
     <edge target="a" /> 
     <edge target="b" /> 
    </graphNode> 
</graph> 

XSL 2.0樣式表:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0"> 

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

    <xsl:key name="graphNodeByID" match="graphNode" use="@id" /> 

    <xsl:template match="/graph"> 
     <results> 
      <xsl:apply-templates select="key('graphNodeByID', @startNode)" 
        mode="process"/>    
     </results> 
    </xsl:template> 

    <xsl:template match="graphNode" mode="process"> 
     <xsl:param name="already-processed" select="/.." /> 
     <xsl:param name="queue" select="/.." /> 

     <!-- do stuff with context node ... --> 
     <processing node="{@id}" /> 

     <!-- Add connected nodes to queue, excluding those already processed. --> 
     <xsl:variable name="new-queue" 
       select="($queue | key('graphNodeByID', edge/@target)) 
         except ($already-processed | .)" /> 

     <!-- recur on next node in queue. --> 
     <xsl:apply-templates select="$new-queue[1]" mode="process"> 
      <xsl:with-param name="already-processed" 
          select="$already-processed | ." /> 
      <xsl:with-param name="queue" select="$new-queue" /> 
     </xsl:apply-templates> 
    </xsl:template> 

</xsl:stylesheet> 

輸出(測試):

<results> 
    <processing node="a"/> 
    <processing node="b"/> 
    <processing node="c"/> 
    <processing node="d"/> 
</results> 

作爲指定,沒有節點被處理兩次,即使圖包含循環。

+0

+1爲一個好的解決方案。在此期間,我獨立地爲我的答案添加了類似的XSLT 1.0解決方案。 –

+0

@Dmitre:我曾看過列表歸檔中引用的解決方案。我發現很難遵循(主要是屏幕格式化,以及將應用於此問題的部分與其他上下文分離的挑戰),所以我決定寫一個實現 - 這也是我以前解決的一個問題。很高興你在此發佈一個單獨的解決方案。 – LarsH

+2

拉爾斯,我在你的和我的解決方案中發現了一個小問題,並在我的答案中糾正了這個問題。到REPRO,只是該圖中節點添加到XML:'\t \t \t <邊緣目標= 「A」/> \t '然後運行轉換。正如你將會看到的那樣,節點'e'沒有被遍歷並且被包含在輸出中。 –

3

這是不難XSLT 1.0做的,看我2004年的回答更具體的圖遍歷問題:

http://lists.xml.org/archives/xml-dev/200401/msg00444.html

下面是一個完整的XSLT 1.0有向圖遍歷解決方案,假設向鏈路特定的XML表示(如你忘了展現給我們的源XML文檔......):

<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="kNodeById" match="*" use="@id"/> 

<xsl:template match="/"> 
    <xsl:call-template name="gTraverse"> 
    <xsl:with-param name="pNode" select="/*/a"/> 
    </xsl:call-template> 
</xsl:template> 

<xsl:template name="gTraverse"> 
    <xsl:param name="pNode"/> 
    <xsl:param name="pVisited" select="/.."/> 
    <xsl:param name="pMustVisit" select="/.."/> 

    <xsl:variable name="vnewVisited" select= 
    "$pVisited | $pNode"/> 

    <xsl:variable name="vnewNodes" select= 
    "key('kNodeById', 
     ($pNode/linkTo 
     | 
      /*/*[linkTo=$pNode/@id])/@id 
     ) 
      [not(@id = $vnewVisited/@id)] 
    "/> 

    <xsl:variable name="vnewMustVisit" select= 
    "$pMustVisit[count(.|$pNode) > 1] | $vnewNodes"/> 

    <xsl:choose> 
    <xsl:when test="not($vnewMustVisit)"> 
    <xsl:copy-of select="$vnewVisited"/> 
    </xsl:when> 
    <xsl:otherwise> 
     <xsl:call-template name="gTraverse"> 
     <xsl:with-param name="pNode" select= 
     "$vnewMustVisit[1]"/> 
     <xsl:with-param name="pVisited" select="$vnewVisited"/> 
     <xsl:with-param name="pMustVisit" select= 
     "$vnewMustVisit[position() > 1]"/> 
     </xsl:call-template> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 
</xsl:stylesheet> 

當施加於下面的XML文檔這種轉變,表示有5個頂點向圖:

<graph> 
<a id ="1"> 
    <linkTo>2</linkTo> 
    <linkTo>5</linkTo> 
</a> 
<b id ="2"> 
    <linkTo>3</linkTo> 
    <linkTo>5</linkTo> 
</b> 
<c id ="3"> 
    <linkTo>1</linkTo> 
    <linkTo>4</linkTo> 
</c> 
<d id ="4"> 
    <linkTo>1</linkTo> 
</d> 
<e id ="5"> 
    <linkTo>3</linkTo> 
    <linkTo>4</linkTo> 
</e> 
<f id ="6"> 
    <linkTo>1</linkTo> 
</f> 
</graph> 

正確的結果(曲線圖的所有節點),產生

<a id="1"> 
    <linkTo>2</linkTo> 
    <linkTo>5</linkTo> 
</a> 
<b id="2"> 
    <linkTo>3</linkTo> 
    <linkTo>5</linkTo> 
</b> 
<c id="3"> 
    <linkTo>1</linkTo> 
    <linkTo>4</linkTo> 
</c> 
<d id="4"> 
    <linkTo>1</linkTo> 
</d> 
<e id="5"> 
    <linkTo>3</linkTo> 
    <linkTo>4</linkTo> 
</e> 
<f id="6"> 
    <linkTo>1</linkTo> 
</f> 
+0

非常感謝。看來我從你的答案中得到了解決方案。一旦我測試,我會更新。 – Kangkan

+0

@Kangkan:不客氣。 –