【问题标题】:XSLT: Check if Element traversed earlierXSLT:检查元素是否更早遍历
【发布时间】:2011-12-22 14:13:45
【问题描述】:

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

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

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

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

有解决办法吗?

【问题讨论】:

  • 您能否更具体地了解您的输入内容?为什么不能像lists.xml.org/archives/xml-dev/201110/msg00030.html 那样将遍历过的元素存储在参数中?
  • XSLT 是一种声明性语言,而不是程序性语言。因此没有“更早”、“已经”或其他与时间相关的概念的概念。您需要从功能角度重新考虑流程。
  • 样本输入、所需输出和样式表尝试都可以让您更轻松地向您展示适合您情况的解决方案。

标签: xml xslt xpath xslt-2.0


【解决方案1】:

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

示例输入:

<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 以获得良好的解决方案。与此同时,我独立地为我的答案添加了一个类似的 XSLT 1.0 解决方案。
  • @Dmitre:我查看了您在列表存档中引用的解决方案。我发现很难理解(主要是屏幕格式,以及将应用于此问题的部分与其他上下文分开的挑战),所以我决定编写一个实现 - 这也是我之前解决过的问题.很高兴您在此处发布了单独的解决方案。
  • Lars,我在您和我的解决方案中都发现了一个小问题,并在我的回答中纠正了这个问题。要重现,只需将此图形节点添加到您的 XML:` `,然后运行转换。如您所见,节点e 没有被遍历并包含在输出中。
  • @Dimitre:如果起始节点是a,那么我认为e不应该被遍历......为什么会这样呢?只有从起始节点通过边(在我的图中是定向的)到达的节点才会被遍历。如果我们想遍历每个节点,我们可以只遍历所有节点的集合,而不考虑连通性,这将是一个更简单的问题。如果我们想将边缘视为无向(双向),我们需要像您一样扩展我们的选择以扩大队列(vnewNodes)。
  • 我的理解是,无论从“起始节点”开始,OP 都需要遍历整个图,并且属于该图的任何顶点都有资格作为“起始节点”。在一般情况下,我们不能简单地选择所有节点,因为节点可能代表几个独立的图。您最初的解决方案是找到从起始节点可达的所有节点,这与确定图中的所有节点不同。当然,只有 OP 可以澄清他想要什么,但是这两个任务都很有用,都应该准确命名。
【解决方案2】:

这在 XSLT 1.0 中并不难做到,请参阅我 2004 年对更具体的图遍历问题的回答:

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

这是一个完整的 XSLT 1.0 有向图遍历解决方案,假设有向链接有特定的 XML 表示(因为您忘记向我们展示源 XML 文档...): p>

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

【讨论】:

  • 非常感谢。看来我从您的回答中得到了解决方案。我会在测试后更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-16
  • 2021-11-10
  • 2020-02-21
  • 2011-04-14
  • 1970-01-01
相关资源
最近更新 更多