2010-08-03 73 views
4

循环依赖我处理的是,一个简单的XML文件,看起来像这样:处理与XSLT

<resources> 
    <resource id="a"> 
    <dependency idref="b"/> 
    <!-- some other stuff --> 
    </resource> 
    <resource id="b"> 
    <!-- some other stuff --> 
    </resource> 
</resources> 

XSLT样式表必须处理特定的资源,我们感兴趣的,我会打电话资源以及所有递归依赖关系。依赖项是其他资源,由它们的id属性唯一标识。

资源是否被处理两次并不重要,但最好只处理每个所需资源一次。它也无所谓什么顺序资源被处理。

重要的是,只有资源和递归依赖性处理。我们不能仅仅处理所有的资源并且完成它。

一个天真的执行情况如下:

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

如果使用幼稚的做法来处理这个例子中,你通过处理开始bÇ,你获得无限递归。

不幸的是,我无法控制输入数据,在任何情况下,循环依赖都是完全有效的,并且被相关规范所允许。

我已经想出了各种部分解决方案,但没有任何工作在任何情况下。

理想的解决方案是防止节点被多次处理的一般方法,但我认为这是不可能的。事实上,我怀疑这个问题是无法解决的。

如果有帮助,我可以使用大部分EXSLT(包括函数)。如有必要,我还可以使用任意数量的其他XSLT脚本预处理输入,但最好不要对输出中不会执行的资源进行过多的预处理。

我不能做的是切换到另一种语言处理(至少不是没有实质性的重新设计)。我也不能使用XSLT 2.0。

任何想法?

+0

+1对于一个写得很好的问题。祝您在XSLT 1.0中找到解决方案。如果你在这里没有得到答案,你可以在http://www.mulberrytech.com/xsl/xsl-list – 2010-08-04 02:13:53

+0

试试XSL-LIST好问题(+1)。查看我的答案以获得完整而简单的解决方案。 :) – 2010-08-04 03:47:44

+1

@吉姆加里森:好像丹尼尔好运:) :) – 2010-08-04 03:52:50

回答

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。 :)

+0

辉煌!谢谢。只要我问了一个XSLT问题,我有一种感觉,你会回答它:)。从外观上看,应该可以将访问过的资源列表保存为节点集而不是字符串,这可能会更清晰一些。感谢您指点我正确的方向。 – 2010-08-04 10:19:16

+0

@ Daniel-Cassidy:我不建议维护pVisited变量中的节点,因为每次重新复制时,必须添加新节点比简单地串联字符串要慢得多。 在XPath 2.0中,预先挂起或附加到序列可以被XSLT处理器优化(例如Saxon优化附加),并且这可以用来将此算法从O(N^2)改进为O(N)。 – 2010-08-04 12:31:56

+0

够公平的。顺便说一下,您的解决方案并不能保证每个“资源”只处理一次,例如__a__依赖于__b__和__c__,而__b__依赖于__c__。在这种情况下,__c__会被处理两次。但是,正如我所说,这对我来说不是问题;你的解决方案应该防止周期,这是所有需要的。 – 2010-08-04 13:04:53

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>