查找XML节点集的最低公共祖先

查找XML节点集的最低公共祖先,xml,xslt,xpath,xslt-2.0,Xml,Xslt,Xpath,Xslt 2.0,我使用XSLT中的xsl:key结构构造了一个节点集。我想找到这个节点集中所有节点的最低共同祖先(LCA)——有什么想法吗 我知道Kaysian intersects和XPath的intersect函数,但这些函数似乎只用于查找一对元素的LCA:我事先不知道每个节点集中有多少项 我想知道是否有一个结合使用“every”和“intersect”表达式的解决方案,但我还没想到一个 提前感谢,, 汤姆我尝试了以下方法: <xsl:stylesheet xmlns:xsl="http://ww

我使用XSLT中的xsl:key结构构造了一个节点集。我想找到这个节点集中所有节点的最低共同祖先(LCA)——有什么想法吗

我知道Kaysian intersects和XPath的intersect函数,但这些函数似乎只用于查找一对元素的LCA:我事先不知道每个节点集中有多少项

我想知道是否有一个结合使用“every”和“intersect”表达式的解决方案,但我还没想到一个

提前感谢,, 汤姆

我尝试了以下方法:

<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="2.0">

  <xsl:output method="html" indent="yes"/>

  <xsl:function name="mf:lca" as="node()?">
    <xsl:param name="nodes" as="node()*"/>
    <xsl:variable name="all-ancestors" select="$nodes/ancestor::node()"/>
    <xsl:sequence
      select="$all-ancestors[every $n in $nodes satisfies exists($n/ancestor::node() intersect .)][last()]"/>
  </xsl:function>

  <xsl:template match="/">
    <xsl:sequence select="mf:lca(//foo)"/>
  </xsl:template>

</xsl:stylesheet>

用样品测试

<root>
  <anc1>
    <anc2>
      <foo/>
      <bar>
        <foo/>
      </bar>
      <bar>
        <baz>
          <foo/>
        </baz>
      </bar>
    </anc2>
  </anc1>
</root>


我得到了
anc2
元素,但我没有使用更复杂的设置进行测试,现在没有时间。也许您可以尝试使用示例数据并报告是否得到了所需的结果。

这里有一种自下而上的方法:

 <xsl:function name="my:lca" as="node()?">
  <xsl:param name="pSet" as="node()*"/>

  <xsl:sequence select=
   "if(not($pSet))
      then ()
      else
       if(not($pSet[2]))
         then $pSet[1]
         else
           if($pSet intersect $pSet/ancestor::node())
             then
               my:lca($pSet[not($pSet intersect ancestor::node())])
             else
               my:lca($pSet/..)
   "/>
 </xsl:function>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:my="my:my">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:variable name="vSet1" select=
      "//*[self::A.1.1 or self::A.2.1]"/>

    <xsl:variable name="vSet2" select=
      "//*[self::B.2.2.1 or self::B.1]"/>

    <xsl:variable name="vSet3" select=
      "$vSet1 | //B.2.2.2"/>

 <xsl:template match="/">
<!---->
     <xsl:sequence select="my:lca($vSet1)/name()"/>
     =========

     <xsl:sequence select="my:lca($vSet2)/name()"/>
     =========

     <xsl:sequence select="my:lca($vSet3)/name()"/>

 </xsl:template>

 <xsl:function name="my:lca" as="node()?">
  <xsl:param name="pSet" as="node()*"/>

  <xsl:sequence select=
   "if(not($pSet))
      then ()
      else
       if(not($pSet[2]))
         then $pSet[1]
         else
           if($pSet intersect $pSet/ancestor::node())
             then
               my:lca($pSet[not($pSet intersect ancestor::node())])
             else
               my:lca($pSet/..)
   "/>
 </xsl:function>
</xsl:stylesheet>
<t>
    <A>
        <A.1>
            <A.1.1/>
            <A.1.2/>
        </A.1>
        <A.2>
            <A.2.1/>
        </A.2>
        <A.3/>
    </A>
    <B>
        <B.1/>
        <B.2>
            <B.2.1/>
            <B.2.2>
                <B.2.2.1/>
                <B.2.2.2/>
            </B.2.2>
        </B.2>
    </B>
</t>
     A
     =========

     B
     =========

     t
<xsl:stylesheet version="2.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:my="my:my">
        <xsl:output omit-xml-declaration="yes" indent="yes"/>

        <xsl:variable name="vSet1" select=
          "//*[self::A.1.1 or self::A.2.1]"/>

        <xsl:variable name="vSet2" select=
          "//*[self::B.2.2.1 or self::B.1]"/>

        <xsl:variable name="vSet3" select=
          "$vSet1 | //B.2.2.2"/>

     <xsl:template match="/">
         <xsl:sequence select="my:lca($vSet1)/name()"/>
         =========

         <xsl:sequence select="my:lca($vSet2)/name()"/>
         =========

         <xsl:sequence select="my:lca($vSet3)/name()"/>

     </xsl:template>

     <xsl:function name="my:lca" as="node()?">
      <xsl:param name="pSet" as="node()*"/>

      <xsl:sequence select=
       "if(not($pSet))
          then ()
          else
           if(not($pSet[2]))
             then $pSet[1]
             else
              for $n1 in $pSet[1],
                  $n2 in $pSet[last()]
               return my:lca2nodes($n1, $n2)
       "/>
     </xsl:function>

     <xsl:function name="my:lca2nodes" as="node()?">
      <xsl:param name="pN1" as="node()"/>
      <xsl:param name="pN2" as="node()"/>

      <xsl:variable name="n1" select=
       "($pN1 | $pN2)
                    [count(ancestor-or-self::node())
                    eq
                     min(($pN1 | $pN2)/count(ancestor-or-self::node()))
                    ]
                     [1]"/>

      <xsl:variable name="n2" select="($pN1 | $pN2) except $n1"/>

      <xsl:sequence select=
       "$n1/ancestor-or-self::node()
                 [exists(. intersect $n2/ancestor-or-self::node())]
                     [1]"/>
     </xsl:function>
</xsl:stylesheet>
 A
 =========

 B
 =========

 t
更新:我有我认为可能是最有效的算法

其思想是,一个节点集的LCA与该节点集中两个节点的LCA相同:“最左侧”和“最右侧”节点。这是正确的证据留给读者作为练习:)

这里是一个完整的XSLT 2.0实现

 <xsl:function name="my:lca" as="node()?">
  <xsl:param name="pSet" as="node()*"/>

  <xsl:sequence select=
   "if(not($pSet))
      then ()
      else
       if(not($pSet[2]))
         then $pSet[1]
         else
           if($pSet intersect $pSet/ancestor::node())
             then
               my:lca($pSet[not($pSet intersect ancestor::node())])
             else
               my:lca($pSet/..)
   "/>
 </xsl:function>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:my="my:my">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:variable name="vSet1" select=
      "//*[self::A.1.1 or self::A.2.1]"/>

    <xsl:variable name="vSet2" select=
      "//*[self::B.2.2.1 or self::B.1]"/>

    <xsl:variable name="vSet3" select=
      "$vSet1 | //B.2.2.2"/>

 <xsl:template match="/">
<!---->
     <xsl:sequence select="my:lca($vSet1)/name()"/>
     =========

     <xsl:sequence select="my:lca($vSet2)/name()"/>
     =========

     <xsl:sequence select="my:lca($vSet3)/name()"/>

 </xsl:template>

 <xsl:function name="my:lca" as="node()?">
  <xsl:param name="pSet" as="node()*"/>

  <xsl:sequence select=
   "if(not($pSet))
      then ()
      else
       if(not($pSet[2]))
         then $pSet[1]
         else
           if($pSet intersect $pSet/ancestor::node())
             then
               my:lca($pSet[not($pSet intersect ancestor::node())])
             else
               my:lca($pSet/..)
   "/>
 </xsl:function>
</xsl:stylesheet>
<t>
    <A>
        <A.1>
            <A.1.1/>
            <A.1.2/>
        </A.1>
        <A.2>
            <A.2.1/>
        </A.2>
        <A.3/>
    </A>
    <B>
        <B.1/>
        <B.2>
            <B.2.1/>
            <B.2.2>
                <B.2.2.1/>
                <B.2.2.2/>
            </B.2.2>
        </B.2>
    </B>
</t>
     A
     =========

     B
     =========

     t
<xsl:stylesheet version="2.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:my="my:my">
        <xsl:output omit-xml-declaration="yes" indent="yes"/>

        <xsl:variable name="vSet1" select=
          "//*[self::A.1.1 or self::A.2.1]"/>

        <xsl:variable name="vSet2" select=
          "//*[self::B.2.2.1 or self::B.1]"/>

        <xsl:variable name="vSet3" select=
          "$vSet1 | //B.2.2.2"/>

     <xsl:template match="/">
         <xsl:sequence select="my:lca($vSet1)/name()"/>
         =========

         <xsl:sequence select="my:lca($vSet2)/name()"/>
         =========

         <xsl:sequence select="my:lca($vSet3)/name()"/>

     </xsl:template>

     <xsl:function name="my:lca" as="node()?">
      <xsl:param name="pSet" as="node()*"/>

      <xsl:sequence select=
       "if(not($pSet))
          then ()
          else
           if(not($pSet[2]))
             then $pSet[1]
             else
              for $n1 in $pSet[1],
                  $n2 in $pSet[last()]
               return my:lca2nodes($n1, $n2)
       "/>
     </xsl:function>

     <xsl:function name="my:lca2nodes" as="node()?">
      <xsl:param name="pN1" as="node()"/>
      <xsl:param name="pN2" as="node()"/>

      <xsl:variable name="n1" select=
       "($pN1 | $pN2)
                    [count(ancestor-or-self::node())
                    eq
                     min(($pN1 | $pN2)/count(ancestor-or-self::node()))
                    ]
                     [1]"/>

      <xsl:variable name="n2" select="($pN1 | $pN2) except $n1"/>

      <xsl:sequence select=
       "$n1/ancestor-or-self::node()
                 [exists(. intersect $n2/ancestor-or-self::node())]
                     [1]"/>
     </xsl:function>
</xsl:stylesheet>
 A
 =========

 B
 =========

 t

Martin的解决方案会起作用,但我认为在某些情况下可能会非常昂贵,需要大量消除重复项。我倾向于使用一种方法,找到两个节点的LCA,然后递归地使用它,基于LCA(x,y,z)=LCA(LCA(x,y,z)[我留给读者证明的理论…]

现在,通过查看序列x/祖先或self::node()和y/祖先或self::node(),将两个序列截断为较短的长度,然后查找两个序列中的最后一个节点,可以相当有效地找到LCA(x,y):在XQuery表示法中:

( let $ax := $x/ancestor-or-self::node()
  let $ay := $y/ancestor-or-self::node()
  let $len := min((count($ax), count($ay))
  for $i in reverse($len to 1) 
  where $ax[$i] is $ay[$i]
  return $ax[$i]
)[1]

如果有人想知道这里的大局,我将把一本书中的脚注从结尾的一块移到正文中引用的最低层次。嗨,迈克尔,谢谢你花时间看这个。我不确定如何在这个场景中应用您的答案,因为我不知道节点集中会有多少个节点(实际上在绝大多数情况下,只有一个),因此我不确定如何在该节点集中的成对节点之间递归(如果有的话)。也为问题中Kaysian的拼写错误道歉@迈克尔·凯:你可能对一种更快的算法感兴趣——我用我认为是LCA最佳算法的方法更新了我的答案。这看起来很棒,尽管我认为我还没有弄清楚为什么它是[last()]而不是[1]-如果您直接使用$nodes/祖先::*而不是$all祖先,可能会有所不同?这个答案的好处是,它是纯XPath-可能会在QA测试中派上用场,即使我在XSLT.Martin中使用Dimitre的解决方案,你可能对更快的算法感兴趣——我用我认为是LCA的最佳算法更新了我的答案。在我看来,Martin的代码也会起作用,但这将更好地扩展,并且更容易被未来的同事阅读。非常感谢,我现在就去测试它@山仁:不客气。我用稍微改变的解决方案编辑了我的答案(不再使用
后代::
轴),这可能会更有效,因为祖先集是“线性的”,而desendents集可能是“二次的”@yamahito:我用我认为可能是最快的算法之一更新了我的答案——只比较了两个节点。对于大量节点,它的执行速度比我以前的算法和Martin算法快得多。观察到一组节点的LCA与文档顺序中第一个和最后一个节点的LCA相同,这确实非常强大(我希望我知道如何证明它…)。然而,我认为我计算两个节点的LCA的功能在许多实现上可能比Dimitre的更好——尽管找到答案的唯一方法是测量它。我认为Dimitre的代码还假设$pSet是按文档顺序排列的:要强制这样做,可能需要形成
/$pSet
.hmm。。。即使我从键生成节点集,这仍然会强制文档顺序吗?