8

我有一个 XML 文件,它对表示偏序的有向无环图 (DAG)进行编码 。这样的图对于指定依赖关系和查找关键路径等事情很有用。出于好奇,我当前的应用程序是为构建系统指定组件依赖项,因此顶点是组件,边指定编译时依赖项。这是一个简单的例子:

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

我想应用一个XSLT 样式表来生成另一个 XML 文档,该文档只包含与偏序的最小元素相对应的顶点。也就是说,那些没有传入边的顶点。示例图的最小顶点集是{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>

问题是,我对这个解决方案并不完全满意。我想知道是否有一种方法可以将selectof thefor-eachtestof theif与 XPath 语法结合起来。

我想写一些类似的东西:

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

但这并不符合我的要求,因为该current()函数没有引用外部//vertex表达式选择的节点。

到目前为止,我的解决方案使用XPath 1.0XSLT 1.0语法,尽管我也对XPath 2.0XSLT 2.0语法持开放态度。

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

<?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目标生成用于渲染图形的Graphviz Dot 语言 代码。这里是xml-to-dot.xsl

<?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="text"/>

    <xsl:template match="dag">
        digraph {
        rankdir="BT";
        node [style="filled", fillcolor="cyan", fontname="Helvetica"];
        <xsl:apply-templates select="//directed-edge-to"/>
        }
    </xsl:template>

    <xsl:template match="directed-edge-to">
        <xsl:value-of select="concat(ancestor::vertex/@name, '->', @vertex, ';')"/>
    </xsl:template>
</xsl:stylesheet>
4

2 回答 2

8

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

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

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

当然请注意,您仍然希望像以前not()一样使用该功能。大多数时候,最好避开!=操作员。如果您在这里使用它而不是not(),那么如果有任何@vertex不等于该@name值的属性,它将返回 true,这不是您的意图。(如果任一节点集为空,则返回 false,因为与空节点集的比较总是返回 false。)

如果你想eq改用,那么你必须做一些像你一样的事情:将条件从迭代中分离出来,这样你就可以绑定current(). 但在 XPath 2.0 中,您可以在表达式中执行此操作:

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

当您的条件不是简单的相等比较(因此不能使用“ =”进行存在量化)时,这很有用。例如:starts-with(@vertex, $v/@name)

XPath 2.0 还具有执行存在量化的显式方法。代替for上面的表达式,我们可以这样写:

<xsl:for-each select="//vertex[not(some $e in //directed-edge-to
                                   satisfies @name eq $e/@vertex)]">

除了 " some" 语法之外,XPath 2.0 还提供了相应的 " 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()在模式中(即在match属性中)使用函数,但 XSLT 2.0 允许这样做。在这种情况下,current()指的是当前正在匹配的节点。所以在 XSLT 2.0 中,我们也可以这样写(不必使用for表达式):

<xsl:template match="vertex[not(//directed-edge-to[@vertex eq current()/@name])]">

请注意,此模式本质上与您尝试在 in 中使用的表达式相同for-each,但是虽然它没有执行您想要的 in for-each,但它确实执行了您想要在模式中执行的操作(因为current()绑定到的内容不同)。

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

<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()"/>

或者,您可以在将模板应用到的节点上更具选择性:

<xsl:apply-templates select="/dag/vertex"/>

您采用哪种方法部分取决于品味,部分取决于样式表的更广泛上下文和预期数据(输入结构可能有多少变化等)。

我知道我远远超出了你的要求,但我希望你至少觉得这很有趣。:-)

于 2009-05-10T10:38:00.023 回答
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 提供了遍历完整(可能是循环)图的解决方案

于 2009-05-10T13:51:25.757 回答