如何用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/0 和
https://xsltfiddle.liberty-development.net/3NzcBsK/1 和
https://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))])"/>
(当然,它们中哪一个更快取决于你的树的灌木丛。)
我简化了表示有向无环图的 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/0 和 https://xsltfiddle.liberty-development.net/3NzcBsK/1 和 https://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))])"/>
(当然,它们中哪一个更快取决于你的树的灌木丛。)