Input xml file:
<a>
<desc key="1" attr1="# {@key}" attr2="{@key} #" attr3="" title="{@attr2}">
<b>
<slot key="2" attr4="xf{../../../@key}" title="{../../../@key}gk {../../../@key}" >
<val key="3">
<fix key="4" title11="{@key} {../@key}" title="{../../@key} {@title11}" >
<c attr4="{../../@key} and 1" attr5 = "{@attr4} or {../@key}" />
</fix>
</val>
<numb key="5" title12="z{@key}z in {../@key}" title="{@title12} {slot/val/@key}" titlew="{@title} {fix/@title}" />
</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>
<desc key="1" attr1="# 1" attr2="1 #" attr3="" title="1 #">
<b>
<slot key="2" attr4="xf1" title="1gk 1" >
<val key="3">
<fix key="4" title11="4 4" title="3 4" >
<c attr4="4 and 1" attr5 = "4 and 1 or ">
</fix>
</val>
<numb key="5" title12="z5z in 5" title="z5z in 5 3" titlew="z5z in 5 3 3 4" />
</slot>
</b>
</desc>
</a>
Now this is the XSLT1.0 transformation file:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common" version="1.0">
<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:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Xml file after transformation:
<a>
<desc key="1" attr1="# 1" attr2="1 #" attr3="" title="1 #">
<b>
<slot key="2" attr4="xf" title="gk ">
<val key="3">
<fix key="4" title11="4 " title="4 4 ">
<c attr4=" and 1" attr5=" and 1 or " />
</fix>
</val>
<numb key="5" title12="z5z in " title="z5z in " titlew="z5z in " />
</slot>
</b>
</desc>
</a>
How to correctly generate the xpath part of an attribute's value?
CodePudding user response:
As pointed out, you need xsl:evaluate
in XSLT 3 or a similar extension for XSLT 2 or 1; furthermore, you need to find the {...}
"templates" to be evaluated and you need to implement a strategy to evaluate a "template" only if the referenced attribute values are already computed.
Using XSLT 3 I arrived at:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:mf="http://example.com/mf"
version="3.0">
<xsl:param name="avt-pattern" as="xs:string" expand-text="no">\{(.*?)\}</xsl:param>
<xsl:mode name="convert" on-no-match="shallow-copy"/>
<xsl:template mode="convert" match="@*[matches(., $avt-pattern)]">
<xsl:variable name="context" select=".."/>
<xsl:attribute name="{name()}">
<xsl:analyze-string select="." regex="{$avt-pattern}">
<xsl:matching-substring>
<xsl:variable name="evaluated-result" as="xs:string">
<xsl:evaluate xpath="regex-group(1)" context-item="$context"/>
</xsl:variable>
<xsl:value-of select="if (matches($evaluated-result, $avt-pattern)) then . else $evaluated-result"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:attribute>
</xsl:template>
<xsl:function name="mf:evaluate-attributes" as="document-node()">
<xsl:param name="input" as="node()"/>
<xsl:variable name="result" as="node()">
<xsl:apply-templates select="$input" mode="convert"/>
</xsl:variable>
<xsl:sequence
select="if ($result//@*[matches(., $avt-pattern)])
then mf:evaluate-attributes($result)
else $result"/>
</xsl:function>
<xsl:template match="/">
<xsl:sequence select="mf:evaluate-attributes(.)"/>
</xsl:template>
</xsl:stylesheet>
Online sample at https://xsltfiddle.liberty-development.net/jy6KM8D.
Doing this with XSLT 1.0 and Microsoft is possible with the help of the project https://www.nuget.org/packages/Mvp.Xml.NetStandard and then
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
exclude-result-prefixes="exslt regexp dyn2 mf"
xmlns:mf="http://example.com/mf"
xmlns:regexp="http://exslt.org/regular-expressions"
xmlns:dyn2="http://gotdotnet.com/exslt/dynamic"
version="1.0">
<xsl:param name="avt-pattern">\{(.*?)\}</xsl:param>
<xsl:template mode="convert" match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()" mode="convert"/>
</xsl:copy>
</xsl:template>
<xsl:template name="evaluate-avts">
<xsl:param name="attribute-value"/>
<xsl:param name="context"/>
<xsl:choose>
<xsl:when test="contains($attribute-value, '{')">
<xsl:value-of select="substring-before($attribute-value, '{')"/>
<xsl:variable name="remainder" select="substring-after($attribute-value, '{')"/>
<xsl:variable name="avt" select="substring-before($remainder, '}')"/>
<xsl:call-template name="evaluate-avt">
<xsl:with-param name="expression" select="$avt"/>
<xsl:with-param name="context" select="$context"/>
</xsl:call-template>
<xsl:call-template name="evaluate-avts">
<xsl:with-param name="attribute-value" select="substring-after($remainder, '}')"/>
<xsl:with-param name="context" select="$context"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$attribute-value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="evaluate-avt">
<xsl:param name="expression"/>
<xsl:param name="context"/>
<xsl:variable name="evaluated-result" select="dyn2:evaluate($context, $expression)"/>
<xsl:choose>
<xsl:when test="regexp:test($evaluated-result, $avt-pattern)">
<xsl:value-of select="concat('{', $expression, '}')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$evaluated-result"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template mode="convert" match="@*">
<xsl:choose>
<xsl:when test="regexp:test(., $avt-pattern)">
<xsl:variable name="context" select=".."/>
<xsl:attribute name="{name()}">
<xsl:call-template name="evaluate-avts">
<xsl:with-param name="attribute-value" select="."/>
<xsl:with-param name="context" select="$context"/>
</xsl:call-template>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template mode="evaluate-attributes" match="/">
<xsl:variable name="result-rtf">
<xsl:apply-templates select="." mode="convert"/>
</xsl:variable>
<xsl:variable name="result" select="exslt:node-set($result-rtf)"/>
<xsl:choose>
<xsl:when test="$result//@*[regexp:test(., $avt-pattern)]">
<xsl:apply-templates select="$result" mode="evaluate-attributes"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$result"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="." mode="evaluate-attributes"/>
</xsl:template>
</xsl:stylesheet>