Input xml file:
<a key="777">
<desc key="1" attr1="# {../@key}" attr2="{@key} #" attr3="555" title="{@attr2}">
<b attr3="444">
<slot key="2" attr3="333" attr4="{../../@attr3}" title="{../../@attr3} {../@attr3}-{@attr3}" >
<val key="3">
<fix key="4" title11="{../../@key} {../@key}" title="{@key} {@title11}" >
<c></c>
</fix>
</val>
<numb key="5" title12="z{@key}z in {@key}" title="{@title12} {@key}" titlew="{@title} {@title12}">
</numb>
</slot>
</b>
</desc>
</a>
The curly brackets indicate the XPath to the attribute, instead of which you must substitute the value of the attribute along this XPath, for example, instead of {@key} and {../@key}, the value of the key attribute of the current node is substituted. The file after XSLT1.0 transformation should look like this:
<a key="777">
<desc key="1" attr1="# 777" attr2="1 #" attr3="555" title="1 #">
<b attr3="444">
<slot key="2" attr3="333" attr4="555" title="555 444-333">
<val key="3">
<fix key="4" title11="2 3" title="4 2 3">
<c />
</fix>
</val>
<numb key="5" title12="z5z in 5" title="z5z in 5 5" titlew="z5z in 5 5 z5z in 5" />
</slot>
</b>
</desc>
</a>
Now this is the XSLT1.0 transformation file:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{name()}">
<xsl:call-template name="expand">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:attribute>
</xsl:template>
<xsl:template name="expand">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '{@')">
<xsl:value-of select="substring-before($text, '{')"/>
<xsl:variable name="name" select="substring-before(substring-after($text, '{@'), '}')" />
<xsl:call-template name="expand">
<xsl:with-param name="text" select="../@*[name()=$name]"/>
</xsl:call-template>
<xsl:call-template name="expand">
<xsl:with-param name="text" select="substring-after($text, '}')"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($text, '{../@') ">
<xsl:value-of select="substring-before($text, '{')"/>
<xsl:variable name="name" select="substring-before(substring-after($text, '{../@'), '}')" />
<xsl:call-template name="expand">
<xsl:with-param name="text" select="../../@*[name()=$name]"/>
</xsl:call-template>
<xsl:call-template name="expand">
<xsl:with-param name="text" select="substring-after($text, '}')"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($text, '{../../@') ">
<xsl:value-of select="substring-before($text, '{')"/>
<xsl:variable name="name" select="substring-before(substring-after($text, '{../../@'), '}')" />
<xsl:call-template name="expand">
<xsl:with-param name="text" select="../../../@*[name()=$name]"/>
</xsl:call-template>
<xsl:call-template name="expand">
<xsl:with-param name="text" select="substring-after($text, '}')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Xml file after transformation:
<a key="777">
<desc key="1" attr1="# 777" attr2="1 #" attr3="555" title="1 #">
<b attr3="444">
<slot key="2" attr3="333" attr4="555" title="333 333-333">
<val key="3">
<fix key="4" title11="3 3" title="4 3 3">
<c />
</fix>
</val>
<numb key="5" title12="z5z in 5" title="z5z in 5 5" titlew="z5z in 5 5 z5z in 5" />
</slot>
</b>
</desc>
</a>
The error is that if the same attribute names are specified in the attribute value of the input file node, for example, in the Slot tag of the title attribute in curly braces three times @attr3, then the same values are substituted for the curly braces:
<slot key="2" ... title="333 333-333">
And it should be like this:
<slot key="2" ... title="555 444-333">
Where is the mistake?
CodePudding user response:
There is no truly good solution for this in pure XSLT 1.0.
If the path expressions in the input XML are limited to a number of predefined patterns (as they are in your example), you could get the expected result by:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{name()}">
<xsl:call-template name="expand">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:attribute>
</xsl:template>
<xsl:template name="expand">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '{') ">
<!-- text before reference -->
<xsl:value-of select="substring-before($text, '{')"/>
<!-- reference -->
<xsl:call-template name="evaluate">
<xsl:with-param name="path" select="substring-before(substring-after($text, '{'), '}')"/>
</xsl:call-template>
<!-- recursive call with text after reference -->
<xsl:call-template name="expand">
<xsl:with-param name="text" select="substring-after($text, '}')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="evaluate">
<xsl:param name="path"/>
<xsl:variable name="name" select="substring-after($path, '@')"/>
<xsl:call-template name="expand">
<xsl:with-param name="text">
<xsl:choose>
<xsl:when test="starts-with($path, '../../@')">
<xsl:value-of select="../../../@*[name()=$name]"/>
</xsl:when>
<xsl:when test="starts-with($path, '../@')">
<xsl:value-of select="../../@*[name()=$name]"/>
</xsl:when>
<xsl:when test="starts-with($path, '@')">
<xsl:value-of select="../@*[name()=$name]"/>
</xsl:when>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
A better solution would be to upgrade to a processor that supports XSLT 3.0 or some other mechanism for dynamic evaluation - as suggested in one of your previous questions.