Home > front end >  xpath part of attribute value?
xpath part of attribute value?

Time:04-14

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>
  • Related