4

我正在处理一个简化的 XML 文件,看起来像这样:

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

如果您使用朴素的实现来处理此示例,并且从处理abc开始,您将获得无限递归。

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

我提出了各种部分解决方案,但没有任何一种方法适用于所有情况。

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

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

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

有任何想法吗?

4

2 回答 2

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 组档案中找到——这里。:)

于 2010-08-04T03:47:08.180 回答
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>
于 2010-08-04T14:45:27.297 回答