我正在尝试使用XSLT从大型XML渲染一些数据。 XML数据实际上是一种图形数据而不是分层数据。和元素是相互关联的,因此最终可能会有一个循环引用(但关系类型却不同)。
我试图遍历一个元素的关系并访问每个相关元素,依此类推。通过这种方式,有时我会达到一个我已经遍历过的元素。在这种情况下,我应该停止进一步遍历,否则我将在一个循环中运行。
我的问题是我无法存储已遍历的元素列表,每次开始遍历元素时都会查找,这样如果元素在查找中,我就可以停止遍历。
简单地说,我想将元素保存在查找表中,并在遍历时将每个元素添加到它中。
这有解决方法吗?
答案 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>