Farolink
Farolink

Reputation: 57

XSLT - Replace a value in a variable and keep the result as Node Tree

I know that this question has surely been asked many time but it's a very specific answer that I would like today.

In my XSL, I've a variable that we will name "A" which contains a node tree and I would like to modify some specific value in this node tree.

<xsl:variable name="A">
  <node1>ABC</node1>
  <node2>DEF</node2>
  <node3>
    <node31>ABC</node31>
    <node32>DEF</node32>
  </node3>
</xsl:variable>

My goal for this exemple is to replace each value equals to "ABC" in this node tree wherever it is by "123", and store the result in another variable (As we can't update the value of a variable in XSLT).

Meaning that the result that I want is equal to :

<xsl:variable name="B">
  <node1>123</node1>
  <node2>DEF</node2>
  <node3>
    <node31>123</node31>
    <node32>DEF</node32>
  </node3>
</xsl:variable>

Moreover, the variable "B" MUST be considered a node tree too. It mean that I want the possibility to do B/node1 B/node2 etc...

I found cases but it was considering all the document at one time, but in my case I've filtered a result in a variable and would like to treat it apart.

Sorry that I can't provide the real cases but it's sensible data so I can't afford that.

XSLT 2.0 or 3.0 is allowed.

Thank you.

Upvotes: 0

Views: 315

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167696

Any transformation should be implemented with templates, use a separate mode to ensure that processing doesn't interfere with the rest of the code:

  <xsl:mode name="replace-value" on-no-match="shallow-copy"/>

  <xsl:param name="replacements" as="map(xs:string, xs:string)"
    select="map { 'ABC' : '123' }"/>

  <xsl:template match="text()[. = map:keys($replacements)]" mode="replace-value">
      <xsl:value-of select="$replacements(.)"/>
  </xsl:template>

  <xsl:variable name="A">
      <node1>ABC</node1>
      <node2>DEF</node2>
      <node3>
        <node31>ABC</node31>
        <node32>DEF</node32>
      </node3>
  </xsl:variable>

  <xsl:variable name="B">
      <xsl:apply-templates select="$A" mode="replace-value"/>
  </xsl:variable>

https://xsltfiddle.liberty-development.net/ejivdHu

If you want to use a parameter you can change on any apply-templates then use a tunnel parameter and move the check into the template of the node(s) you want to transform:

  <xsl:mode name="replace-value" on-no-match="shallow-copy"/>

  <xsl:param name="replacements" as="map(xs:string, xs:string)"
    select="map { 'ABC' : '123' }"/>

  <xsl:template match="text()" mode="replace-value">
      <xsl:param name="replacements" tunnel="yes"/>
      <xsl:value-of
         select="if (map:contains($replacements, .))
                 then $replacements(.)
                 else ."/>
  </xsl:template>

  <xsl:variable name="A">
      <node1>ABC</node1>
      <node2>DEF</node2>
      <node3>
        <node31>ABC</node31>
        <node32>DEF</node32>
      </node3>
  </xsl:variable>

  <xsl:variable name="B">
      <xsl:apply-templates select="$A" mode="replace-value">
          <xsl:with-param name="replacements" tunnel="yes" select="$replacements"/>
      </xsl:apply-templates>
  </xsl:variable>

  <xsl:variable name="C">
      <xsl:apply-templates select="$A" mode="replace-value">
          <xsl:with-param name="replacements" tunnel="yes" select="map { 'DEF' : 'xxx', 'ABC' : 'yyy' }"/>
      </xsl:apply-templates>
  </xsl:variable>

https://xsltfiddle.liberty-development.net/ejivdHu/1

Upvotes: 2

Related Questions