Это преобразование XSLT 1.0 (абсолютно никаких ограничений):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kElemByName" match="Element"
use="@name"/>
<xsl:key name="klastTokenByName" match="@lastToken"
use="../@name"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Element/@name">
<xsl:attribute name="name">
<xsl:call-template name="init"/>
</xsl:attribute>
<xsl:attribute name="lastToken">
<xsl:call-template name="lastToken"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates mode="pass2"
select="ext:node-set($vrtfPass1)/*"/>
</xsl:template>
<xsl:template mode="pass2" match="Element"/>
<xsl:template mode="pass2" match=
"Element[generate-id()
=
generate-id(key('kElemByName',@name)[1])
]
">
<Element name="{@name}">
<xsl:for-each select=
"key('klastTokenByName',@name)">
<lastToken name="{.}"
childCount="{count(key('kElemByName',../@name)
[@lastToken=current()]
/Child
)
}"
/>
</xsl:for-each>
</Element>
</xsl:template>
<xsl:template name="lastToken">
<xsl:param name="pText" select="."/>
<xsl:param name="pDelim" select="'.'"/>
<xsl:variable name="vrtfTokens">
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select="$pText"/>
<xsl:with-param name="pDelim" select="$pDelim"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select=
"ext:node-set($vrtfTokens)/*[last()]"/>
</xsl:template>
<xsl:template name="init">
<xsl:param name="pText" select="."/>
<xsl:param name="pDelim" select="'.'"/>
<xsl:variable name="vLastToken">
<xsl:call-template name="lastToken">
<xsl:with-param name="pText" select="$pText"/>
<xsl:with-param name="pDelim" select="$pDelim"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select=
"substring($pText,
1,
string-length($pText)
- string-length($vLastToken)
- string-length($pDelim)
)
"/>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="pText"/>
<xsl:param name="pDelim" select="'.'"/>
<xsl:if test="string-length($pText)">
<token>
<xsl:value-of select=
"substring-before(concat($pText,$pDelim),
$pDelim)"/>
</token>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,$pDelim)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
при применении к предоставленному документу XML :
<Elements>
<Element name="A.B.C.x">
<Child>...</Child>
<Child>...</Child>
<Child>...</Child>
</Element>
<Element name="A.B.C.y">
<Child>...</Child>
<Child>...</Child>
</Element>
<Element name="A.D.E.y">
<Child>...</Child>
</Element>
<Element name="A.D.E.z">
<Child>...</Child>
<Child>...</Child>
<Child>...</Child>
</Element>
</Elements>
желаемый, правильный результат получается:
<Elements>
<Element name="A.B.C">
<lastToken name="x" childCount="3"/>
<lastToken name="y" childCount="2"/>
</Element>
<Element name="A.D.E">
<lastToken name="y" childCount="1"/>
<lastToken name="z" childCount="3"/>
</Element>
</Elements>