XSLT:检查Element是否先前遍历过

时间:2011-12-22 14:13:45

标签: xml xslt xpath xslt-2.0

我正在尝试使用XSLT从大型XML渲染一些数据。 XML数据实际上是一种图形数据而不是分层数据。和元素是相互关联的,因此最终可能会有一个循环引用(但关系类型却不同)。

我试图遍历一个元素的关系并访问每个相关元素,依此类推。通过这种方式,有时我会达到一个我已经遍历过的元素。在这种情况下,我应该停止进一步遍历,否则我将在一个循环中运行。

我的问题是我无法存储已遍历的元素列表,每次开始遍历元素时都会查找,这样如果元素在查找中,我就可以停止遍历。

简单地说,我想将元素保存在查找表中,并在遍历时将每个元素添加到它中。

这有解决方法吗?

2 个答案:

答案 0 :(得分:7)

递归模板可以传递自身参数,这些参数包含“先前”处理的节点的节点集和要处理的节点队列。这是一个与修改状态变量等效的函数式编程。

示例输入:

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

如上所述,即使图表包含循环,也不会处理两次节点。

答案 1 :(得分: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>