Wrong attribute value transformation?

Viewed 46

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?

1 Answers

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.

Related