使用XSLT / XPath查找有向无环图(DAG)最小元素(顶点)?

时间:2009-05-09 19:28:10

标签: xslt xpath graph-theory directed-acyclic-graphs build-system

我有一个编码的XML文件 代表directed acyclic graph (DAG)partial order。此类图表对于指定依赖项和查找critical paths等内容非常有用。对于好奇,我当前的应用程序是指定build system的组件依赖项,因此顶点是组件和边指定编译时依赖项。这是一个简单的例子:

<?xml version="1.0"?>
<dag>
    <vertex name="A">
        <directed-edge-to vertex="C"/>
    </vertex>
    <vertex name="B">
        <directed-edge-to vertex="C"/>
        <directed-edge-to vertex="D"/>
    </vertex>
    <vertex name="C">
        <directed-edge-to vertex="E"/>
    </vertex>
    <vertex name="D">
        <directed-edge-to vertex="E"/>
    </vertex>
    <vertex name="E">
        <directed-edge-to vertex="G"/>
    </vertex>
    <vertex name="F">
        <directed-edge-to vertex="G"/>
    </vertex>
    <vertex name="G"/>
</dag>

此DAG可能如下所示:


(来源:iparelan.com

我想应用生成另一个XML的XSLT stylesheet 仅包含与部分订单的minimal elements对应的顶点的文档。也就是说,那些没有传入边的顶点。示例图的最小顶点集是{A, B, F}。对于我的构建依赖项应用程序,找到这个集合是有价值的,因为我知道如果我构建了这个集合的成员,那么我的项目中的所有内容都将被构建。

这是我当前的样式表解决方案(我使用Apache Ant的xslt任务在Java上运行Xalan。一个关键的观察是,任何directed-edge-to元素都不会引用最小顶点:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xalan="http://xml.apache.org/xslt"
                exclude-result-prefixes="xalan">
    <xsl:output method="xml" indent="yes" xalan:indent-amount="4"/>

    <xsl:template match="dag">
        <minimal-vertices>
            <xsl:for-each select="//vertex">
                <xsl:if test="not(//vertex/directed-edge-to[@vertex=current()/@name])">
                    <minimal-vertex name="{@name}"/>
                </xsl:if>
            </xsl:for-each>
        </minimal-vertices>
    </xsl:template>
</xsl:stylesheet>

应用此样式表会产生以下输出(我认为这是正确的):

<?xml version="1.0" encoding="UTF-8"?>
<minimal-vertices>
    <minimal-vertex name="A"/>
    <minimal-vertex name="B"/>
    <minimal-vertex name="F"/>
</minimal-vertices>

问题是,我对这个解决方案并不完全满意。 我想知道是否有办法将select的{​​{1}}和for-each的{​​{1}}与XPath语法结合起来。 < / p>

我想写一些类似的东西:

test

但是这不符合我的要求,因为if函数不引用外部<xsl:for-each select="//vertex[not(//vertex/directed-edge-to[@vertex=current()/@name])]"> 表达式选择的节点。

因此,我的解决方案使用XPath 1.0XSLT 1.0语法,但我也可以使用XPath 2.0XSLT 2.0语法。

如果你愿意,这是Ant构建脚本:

current()

//vertex目标生成Graphviz Dot language代码以呈现图表。这是<?xml version="1.0"?> <project name="minimal-dag" default="default"> <target name="default"> <xslt in="dag.xml" out="minimal-vertices.xml" style="find-minimal-vertices.xsl"/> </target> <target name="dot"> <xslt in="dag.xml" out="dag.dot" style="xml-to-dot.xsl"/> </target> </project>

dot

2 个答案:

答案 0 :(得分:8)

您可以在=运算符上利用XPath的隐式存在量化:

<xsl:for-each select="//vertex[not(@name = //vertex/directed-edge-to/@vertex)]">

当您使用六个比较运算符中的任何一个时(=!=<<=>>=)要比较节点集,如果节点集中的任何节点满足条件,则表达式将返回true。将一个节点集与另一个节点集进行比较时,如果第一个节点集中的任何节点在与第二个节点集中的任何节点进行比较时满足条件,则表达式返回true。 XPath 2.0引入了六个不执行此存在量化的新运算符(eqneltlegt和{{1} })。但在您的情况下,您将需要使用“ge”来获得存在量化。

当然,请注意,您仍然希望使用=功能。大多数情况下,避免使用not()运算符是件好事。如果您在此使用它而不是!=,那么如果有任何not()属性不等于@vertex值,则返回true,这不是您的意图。 (如果任一节点集为空,那么它将返回false,因为与空节点集的比较总是返回false。)

如果你想使用@name,那么你必须做一些类似的工作:从迭代中分离出条件,这样你就可以绑定eq。但是在XPath 2.0中,您可以在表达式中执行此操作:

current()

当您的条件不是简单的相等比较时(因此无法使用“<xsl:for-each select="for $v in //vertex return $v[not(//directed-edge-to[@vertex eq $v/@name])]"> ”进行存在量化),这非常有用。例如:=

XPath 2.0还有一种执行存在量化的明确方法。除了上面的starts-with(@vertex, $v/@name)表达式,我们可以写下这个:

for

除了“<xsl:for-each select="//vertex[not(some $e in //directed-edge-to satisfies @name eq $e/@vertex)]"> ”语法之外,XPath 2.0还提供了相应的“some”语法,用于执行通用量化。

您可以使用更模块化(功能强大)的模板规则,而不是使用every

for-each

同样,在这种情况下,我们依赖于<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <minimal-vertices> <xsl:apply-templates/> </minimal-vertices> </xsl:template> <!-- Copy vertex elements that have no arrows pointing to them --> <xsl:template match="vertex[not(@name = //directed-edge-to/@vertex)]"> <minimal-vertex name="{@name}"/> </xsl:template> </xsl:stylesheet> 的存在量化。

XSLT 1.0禁止在模式中使用=函数,即在current()属性中,但XSLT 2.0允许它。在这种情况下,match指的是当前匹配的节点。所以在XSLT 2.0中,我们也可以编写它(不必使用current()表达式):

for

请注意,此模式与您尝试在<xsl:template match="vertex[not(//directed-edge-to[@vertex eq current()/@name])]"> 中使用的表达式基本相同,但是在for-each中它不能执行您想要的操作时, >在模式中做你想做的事情(因为for-each绑定的是不同的。)

最后,我将添加一个在某些方面简化逻辑的变体(删除current())。这也可以追溯到使用XSLT 1.0:

not()

如果您不喜欢输出的空格,请为文本节点添加一个空规则,这样它们就会被剥离(覆盖文本节点的默认规则,即复制它们):

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="/">
    <minimal-vertices>
      <xsl:apply-templates/>
    </minimal-vertices>
  </xsl:template>

  <!-- By default, copy vertex elements -->
  <xsl:template match="vertex">
    <minimal-vertex name="{@name}"/>
  </xsl:template>

  <!-- But strip out vertices with incoming arrows -->
  <xsl:template match="vertex[@name = //directed-edge-to/@vertex]"/>

</xsl:stylesheet>

或者您可以更灵活地将模板应用于哪些节点:

<xsl:template match="text()"/>

您采取的方法部分取决于品味,部分取决于样式表和预期数据的更广泛背景(输入结构可能会有多大差异等)。

我知道我超越了你的要求,但我希望你至少发现这很有意思。 : - )

答案 1 :(得分:5)

一个这样的XPath 1.0表达式是

<强> /*/vertex[not(@name = /*/vertex/directed-edge-to/@vertex)]

然后将其放入类似

的XSLT样式表中
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="/">
      <minimal-vertices>
          <xsl:for-each select=
          "/*/vertex[not(@name = /*/vertex/directed-edge-to/@vertex)]"
          >
           <minimal-vertex name="{@name}"/>
          </xsl:for-each>
      </minimal-vertices>
    </xsl:template>
</xsl:stylesheet>

将此样式表应用于最初提供的XML文档

<dag>
    <vertex name="A">
        <directed-edge-to vertex="C"/>
    </vertex>
    <vertex name="B">
        <directed-edge-to vertex="C"/>
        <directed-edge-to vertex="D"/>
    </vertex>
    <vertex name="C">
        <directed-edge-to vertex="E"/>
    </vertex>
    <vertex name="D">
        <directed-edge-to vertex="E"/>
    </vertex>
    <vertex name="E">
        <directed-edge-to vertex="G"/>
    </vertex>
    <vertex name="F">
        <directed-edge-to vertex="G"/>
    </vertex>
    <vertex name="G"/>
</dag>

生成了想要的结果

<minimal-vertices>
  <minimal-vertex name="A" />
  <minimal-vertex name="B" />
  <minimal-vertex name="F" />
</minimal-vertices>

请注意 XSLT here中提供了遍历完整(可能是循环)图表的解决方案。