如何用xsl计算有向无环图中child节点的数量

How to count the number of child nodes in a directed acyclic graph with xsl

我简化了表示有向无环图的 xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<items>
  <item id="1">
  </item>
  <item id="2">
    <parent idref="1" />
  </item>
  <item id="3">
    <parent idref="1" />
  </item>
  <item id="4">
    <parent idref="3" />
  </item>
  <item id="5">
    <parent idref="4" />
  </item>
  <item id="6">
    <parent idref="3" />
  </item>
  <item id="7">
    <parent idref="4" />
  </item>
  <item id="8">
    <parent idref="4" />
  </item>
  <item id="9">
    <parent idref="4" />
  </item>
  <item id="10">
    <parent idref="6" />
  </item>
</items>

此表示允许无限深度(我不知道数学术语)。每个项目都有一个且只有一个 parent,除了根项目没有 parent。每个项目可以有 0 到任意数量的 child 个项目。每个项目都由它的 id 标识,它是一个任意标签。

用圆点表示法可视化图形可能更容易。在我的文件中,更容易看到项目 3 有两个 children 节点,项目 4 有 4:

digraph
{
    1 -> {2, 3}
    3 -> {4, 6}
    4 -> {5, 7, 8, 9}
    6 -> 10
}

计算第3项的child仁数是:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
    <xsl:value-of select="count(items/item/parent[@idref='3'])" />
</xsl:template>

</xsl:stylesheet>

xslt 样式表输出 2,这是正确的。如果我把 3 改成 4,那么输出就是 4,这也是正确的。

问题: 我想统计一个特定项目的所有children项目。

对于第 3 项,正确答案是 7(第 4、6、5、7、8、9、10 项)。对于第 1 项,答案是 9。对于第 7 项,答案是 0。我怀疑答案涉及递归,我已经成功地使用它来构造树的一部分,但没有传递值或计算总和。

对于以下 cross-references 我建议设置键,然后确实要解决问题,递归是解决方案的(另一个)关键,因此在 XSLT 2 或 3 中,您可以使用递归函数:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="xs mf"
    version="3.0">

  <xsl:param name="start-id">7</xsl:param>

  <xsl:key name="id" match="item" use="@id"/>
  <xsl:key name="child" match="item" use="parent/@idref"/>

  <xsl:function name="mf:descendants" as="element(item)*">
      <xsl:param name="item" as="element(item)*"/>
      <xsl:sequence 
        select="let $children := key('child', $item/@id, root($item))
                return ($children | $children!mf:descendants(.))"/>
  </xsl:function>

  <xsl:template match="/">
      <xsl:value-of select="count(mf:descendants(key('id', $start-id)))"/>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/bdxtpH/0 and https://xsltfiddle.liberty-development.net/bdxtpH/1 and https://xsltfiddle.liberty-development.net/bdxtpH/2 是一些例子。

对于 XSLT 2,您将使用本地 xsl:variable 而不是上面使用的 let $children

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="xs mf">

  <xsl:param name="start-id">1</xsl:param>

  <xsl:key name="id" match="item" use="@id"/>
  <xsl:key name="child" match="item" use="parent/@idref"/>

  <xsl:function name="mf:descendants" as="element(item)*">
      <xsl:param name="item" as="element(item)*"/>
      <xsl:variable name="children" select="key('child', $item/@id, root($item))"/>
      <xsl:sequence 
        select="$children | $children/mf:descendants(.)"/>
  </xsl:function>

  <xsl:template match="/">
      <xsl:value-of select="count(mf:descendants(key('id', $start-id)))"/>
  </xsl:template>

</xsl:transform>

http://xsltransform.hikmatu.com/pPgCcou

对于 XSLT 1,您可以使用一种略有不同的方法,使用递归命名模板收集后代,直到找不到更多子代,然后输出计数:

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

    <xsl:param name="start-id">7</xsl:param>

    <xsl:key name="id" match="item" use="@id"/>
    <xsl:key name="child" match="item" use="parent/@idref"/>

    <xsl:template name="count-descendants">
        <xsl:param name="descendants" select="/.."/>
        <xsl:param name="level"/>
        <xsl:variable name="children" select="key('child', $level/@id)"/>
        <xsl:choose>
            <xsl:when test="not($children)">
                <xsl:value-of select="count($descendants)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="count-descendants">
                    <xsl:with-param name="descendants" select="$descendants | $children"/>
                    <xsl:with-param name="level" select="$children"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="/">
        <xsl:variable name="start-item" select="key('id', $start-id)"/>
        <xsl:call-template name="count-descendants">
            <xsl:with-param name="level" select="$start-item"/>
        </xsl:call-template>
    </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/3NzcBsK/0https://xsltfiddle.liberty-development.net/3NzcBsK/1https://xsltfiddle.liberty-development.net/3NzcBsK/2 有示例数据。

为了多样化,这里有另一种方法:

<xsl:param name="start-id">7</xsl:param>

<xsl:key name="id" match="item" use="@id"/>

<xsl:function name="f:has-ancestor" as="xs:boolean">
  <xsl:param name="d" as="item"/>
  <xsl:param name="a" as="item"/>
  <xsl:sequence select="exists($d) and ($a is $d or 
       f:has-ancestor(key('id', $d/parent/@idref), $a))"/>
</xsl:function>

然后

select="count(//item[f:has-ancestor(., key('id', $start-id))])"/>

(当然,它们中哪一个更快取决于你的树的灌木丛。)