Home > Back-end >  recursive attribute calculation?
recursive attribute calculation?

Time:02-25

XML input:

<a>
  <desc key="1" attr1="# {@key}" attr2="{@key} #"  attr3="" title="{@attr2}">
    <b>
      <slot key="2" attr4="{@key}" title="{@attr4} {@attr4}" >
        <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>

I have this XML transformation code XSLT1.0:

<?xml version="1.0" encoding="utf-8"?>
 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  
  <xsl:template match="*/@*">
    <xsl:attribute name="{name()}">
        <xsl:call-template name="replace">
          <xsl:with-param name="str" select="."/>
          <xsl:with-param name="find" select="'{@key}'"/>
          <xsl:with-param name="replace" select="../@key"/>
        </xsl:call-template>
    </xsl:attribute>
  </xsl:template>
  
    <!--Identity template-->
  <xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template name="replace">
    <xsl:param name="str"/>
    <xsl:param name="find"/>
    <xsl:param name="replace"/>
    <xsl:choose>
      <xsl:when test="contains($str, $find)">
        <xsl:variable name="prefix" select="substring-before($str, $find)"/>
        <xsl:variable name="suffix">
          <xsl:call-template name="replace">
            <xsl:with-param name="str" select="substring-after($str, $find)"/>
            <xsl:with-param name="find" select="$find"/>
            <xsl:with-param name="replace" select="$replace"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:value-of select="concat($prefix, $replace, $suffix)"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$str"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

This code finds any occurrence of '{@key}' in tag attribute values. If the tag has an attribute specified after the '@' character, i.e. in this case 'key', then the string '{@key}' is replaced by the value of the 'key' attribute. Then the XML file after transformation will look like this:

<a>
  <desc key="1" attr1="# 1" attr2="1 #" attr3="" title="{@attr2}">
    <b>
      <slot key="2" attr4="2" title="{@attr4} {@attr4}">
        <val key="3">
          <fix key="4" title11="4 4" title="4 {@title11}">
            <c />
          </fix>
        </val>
        <numb key="5" title12="z5z in 5" title="{@title12} 5" titlew="{@title} {@title12}">
        </numb>
      </slot>
    </b>
  </desc>
</a>

But I need to replace not only the string '{@key}' with the value of the 'key' attribute, but a more general rule: if the tag has an attribute 'attr', then any occurrence of the string '{@attr}' in the values tag attributes was replaced with the value of the 'attr' attribute. Is there a way to do this using XSLT 1.0 so that after transformation the XML file looks like this:

<a>
  <desc key="1" attr1="# 1" attr2="1 #"  attr3="" title="1 #">
    <b>
      <slot key="2" attr4="2" title="2 2" >
        <val key="3">
          <fix key="4" title11="4 4" title="4 4 4" >
            <c></c>
          </fix>
        </val>
        <numb key="5" title12="z5z in 5" title="z5z in 5 5" titlew="z5z in 5 5 z5z in 5">
        </numb>
      </slot>
    </b>
  </desc>
</a>

CodePudding user response:

If it can be assumed that an attribute will never contain the {} characters other than to house a reference to another attribute of the same parent element, then you could do something like:

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, '{')"/>
            <!-- recursive call with the expanded reference-->
            <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>            
            <!-- recursive call with rest of the text -->
            <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>

Caveat: not tested very thoroughly.

  • Related