我正在处理一个简化的XML文件,如下所示:
<resources>
<resource id="a">
<dependency idref="b"/>
<!-- some other stuff -->
</resource>
<resource id="b">
<!-- some other stuff -->
</resource>
</resources>
XSLT样式表必须处理我们感兴趣的特定资源,我称之为 root 资源,以及所有递归依赖项。依赖关系是其他资源,由id
属性唯一标识。
资源是否被处理两次并不重要,尽管最好只处理一次所需的资源。处理资源的顺序也无关紧要。
仅 root 资源及其递归依赖关系的处理非常重要。我们不能只处理所有资源并完成它。
一个天真的实现如下:
<xsl:key name="resource-id" match="resource" use="@id"/>
<xsl:template match="resource">
<!-- do whatever is required to process the resource. -->
<!-- then handle any dependencies -->
<xsl:apply-templates select="key('resource-id', dependency/@idref)"/>
</xsl:template>
此实现适用于上述示例以及许多实际情况。它的缺点是它经常处理同一个资源不止一次,但如上所述,这并不是非常重要。
问题是有时资源具有循环依赖性:
<resources>
<resource id="a">
<dependency idref="b"/>
<dependency idref="d"/>
</resource>
<resource id="b">
<dependency idref="c"/>
</resource>
<resource id="c">
<dependency idref="a"/>
</resource>
<resource id="d"/>
</resources>
如果您使用天真的实现来处理此示例,并且首先处理 a , b 或 c ,您将获得无限递归
不幸的是,我无法控制输入数据,在任何情况下,循环依赖都是完全有效的,并且是相关规范所允许的。
我已经提出了各种部分解决方案,但在所有情况下都没有。
理想的解决方案是防止节点被多次处理的一般方法,但我不认为这是可能的。事实上,我怀疑整个问题是不可能解决的。
如果有帮助,我可以使用大部分EXSLT(包括功能)。如果有必要,我还可以使用任意数量的其他XSLT脚本预处理输入,但最好不要对输出中不会产生的资源进行过多的预处理。
我不能做的是切换到用另一种语言处理它(至少没有大量的重新设计)。我也不能使用XSLT 2.0。
有什么想法吗?
答案 0 :(得分:3)
这是一个简单的解决方案:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRootResourceId" select="'a'"/>
<xsl:key name="kResById" match="resource" use="@id"/>
<xsl:template match="/">
<resourceProcessing root="{$pRootResourceId}">
<xsl:apply-templates select=
"key('kResById', $pRootResourceId)"/>
</resourceProcessing>
</xsl:template>
<xsl:template match="resource">
<xsl:param name="pVisited" select="'|'"/>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select=
"key('kResById',
dependency/@idref
[not(contains($pVisited, concat('|', ., '|')))])">
<xsl:with-param name="pVisited"
select="concat($pVisited, @id, '|')"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
应用于提供的XML文档:
<resources>
<resource id="a">
<dependency idref="b"/>
<dependency idref="d"/>
</resource>
<resource id="b">
<dependency idref="c"/>
</resource>
<resource id="c">
<dependency idref="a"/>
</resource>
<resource id="d"/>
</resources>
产生了想要的正确结果:
<resourceProcessing root="a">
<resource id="a">
<resource id="b">
<resource id="c"/>
</resource>
<resource id="d"/>
</resource>
</resourceProcessing>
主要思路很简单:维护已访问资源的ID列表,只有在新资源的ID不在列表中时才允许处理。 “处理”用于演示目的,并输出请求包装其所依赖的所有其他请求(递归)。
另请注意每个request
仅处理一次。
多年前,我为图遍历问题提供了类似的解决方案 - 可以在xml-dev组档案中找到 - here 。 :)
答案 1 :(得分:2)
只是为了好玩,另一种解决方案(遵循Dimitre),但增加了一个带有访问节点的节点集。我发布了两个样式表,一个是节点集逻辑,另一个是节点集比较,因为你必须对大型XML输入进行更快的测试。
所以,这个样式表:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRootResourceId" select="'a'"/>
<xsl:key name="kResById" match="resource" use="@id"/>
<xsl:template match="/" name="resource">
<xsl:param name="pVisited" select="key('kResById', $pRootResourceId)"/>
<xsl:param name="pNew" select="key('kResById',$pVisited/dependency/@idref)"/>
<xsl:choose>
<xsl:when test="$pNew">
<xsl:call-template name="resource">
<xsl:with-param name="pVisited" select="$pVisited|$pNew"/>
<xsl:with-param name="pNew" select="key('kResById',
$pNew/dependency/@idref)[not(@id=($pVisited|$pNew)/@id)]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<result>
<xsl:copy-of select="$pVisited"/>
</result>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
这个样式表:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRootResourceId" select="'a'"/>
<xsl:key name="kResById" match="resource" use="@id"/>
<xsl:template match="/" name="resource">
<xsl:param name="pVisited" select="key('kResById', $pRootResourceId)"/>
<xsl:param name="pNew" select="key('kResById', $pVisited/dependency/@idref)"/>
<xsl:variable name="vAll" select="$pVisited|$pNew"/>
<xsl:choose>
<xsl:when test="$pNew">
<xsl:call-template name="resource">
<xsl:with-param name="pVisited" select="$vAll"/>
<xsl:with-param name="pNew" select="key('kResById',
$pNew/dependency/@idref)[count(.|$vAll)>count($vAll)]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<result>
<xsl:copy-of select="$pVisited"/>
</result>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
两个输出:
(首先输入)
<result>
<resource id="a">
<dependency idref="b" />
<!-- some other stuff -->
</resource>
<resource id="b">
<!-- some other stuff -->
</resource>
</result>
(使用上次输入)
<result>
<resource id="a">
<dependency idref="b" />
<dependency idref="d" />
</resource>
<resource id="b">
<dependency idref="c" />
</resource>
<resource id="c">
<dependency idref="a" />
</resource>
<resource id="d" />
</result>