Вот подход снизу вверх:
<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>
Когда это преобразование применяется на следующий документ XML:
<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
Update: У меня есть то, что я думаю, что, вероятно, наиболее эффективный алгоритм.
Идея состоит в том, что LCA набора узлов совпадает с LCA только двух узлов этого набора узлов: «самые левые» и «самые правые». Доказательство того, что это правильно оставляется в качестве упражнения для читателя :)
Вот полный XSLT 2.0 реализация:
<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>
когда это преобразование выполняется на том же самом документе XML (выше), то же правильный результат получается, но гораздо быстрее - особенно если размер набора узлов большой:
A
=========
B
=========
t
Если кто-то хочет знать больше картину здесь, я двигаюсь сносок в книге из одного комка в конец до самого низкого уровня, с которого они указаны в тексте. –