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.