2011-12-22 70 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:不客气。 –