Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

replace a string with a string with xslt

Tags:

xml

xslt

xpath

i want to replace a string with another string. i found an example that does that but didnt seem to work. this is a sample data

<Addy>
  <Row>
  <LD>Dwelling, 1</D>
  <LN> East</LN>
  <L>1</L>
  <Tf>Abesinia Passage</Tf>
  </Row>

  <Row>
  <LD>Logde</LD>
  <LN>North </LN>
  <L>1</L>
  <Tf>Abesinia Passage</Tf>
  </Row>
</Addy>

i want to replace the following string in this manner.

 Dwelling = FLAT
 Lodge    =  SHOP

Below are the codes i used. it only deleted all the values in LD element.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:lookup="lookup">

<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>

<lookup:data>
    <LD code="Dwelling">FLAT</LD>
     <LD code="Lodge">SHOP</LD>

</lookup:data>

<xsl:variable name="lookup" select="document('')/*/lookup:data"/>

<xsl:template match="LD/text()">
    <xsl:value-of select="$lookup/LD[@code = current()]" />
</xsl:template>

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>
</xsl:stylesheet>

If applied to the input data above, it produces this:

   <Addy>
  <Row>
  <LD></LD>
  <LN> East</LN>
  <L>1</L>
  <Tf>Abesinia Passage</Tf>
  </Row>

  <Row>
  <LD></LD>
  <LN>North </LN>
  <L>1</L>
  <Tf>Abesinia Passage</Tf>
  </Row>
  </Addy>

expected result with appropriate codes should produce

   <Addy>
  <Row>
  <LD>FLAT,1</D>
  <LN> East</LN>
  <L>1</L>
  <Tf>Abesinia Passage</Tf>
  </Row>

  <Row>
  <LD>SHOP</LD>
  <LN>North </LN>
  <L>1</L>
  <Tf>Abesinia Passage</Tf>
  </Row>
  </Addy>  
like image 666
lee Avatar asked Dec 19 '25 15:12

lee


1 Answers

Here is an XSLT transformation for performing multiple replacements into a string -- no extension function needed:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <my:reps>
  <rep>
   <old>Dwelling</old>
   <new>FLAT</new>
  </rep>
  <rep>
   <old>Lodge</old>
   <new>SHOP</new>
  </rep>
 </my:reps>

 <xsl:variable name="vReps" select="document('')/*/my:reps/*"/>

 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="LD/text()" name="replace">
  <xsl:param name="pText" select="."/>

  <xsl:choose>
   <xsl:when test="not($vReps/old[contains($pText, .)])">
    <xsl:value-of select="$pText"/>
   </xsl:when>
   <xsl:otherwise>
       <xsl:call-template name="multiReplace">
        <xsl:with-param name="pText" select="$pText"/>
        <xsl:with-param name="pReps"
         select="$vReps[contains($pText, old)]"/>
       </xsl:call-template>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>

 <xsl:template name="multiReplace">
  <xsl:param name="pText"/>
  <xsl:param name="pReps"/>

  <xsl:choose>
      <xsl:when test="$pReps">
       <xsl:variable name="vRepResult">
         <xsl:call-template name="singleReplace">
           <xsl:with-param name="pText" select="$pText"/>
           <xsl:with-param name="pOld" select="$pReps[1]/old"/>
           <xsl:with-param name="pNew" select="$pReps[1]/new"/>
         </xsl:call-template>
       </xsl:variable>

       <xsl:call-template name="multiReplace">
        <xsl:with-param name="pText" select="$vRepResult"/>
        <xsl:with-param name="pReps" select="$pReps[position() >1]"/>
       </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
       <xsl:value-of select="$pText"/>
      </xsl:otherwise>
  </xsl:choose>
 </xsl:template>

 <xsl:template name="singleReplace">
  <xsl:param name="pText"/>
  <xsl:param name="pOld"/>
  <xsl:param name="pNew"/>

  <xsl:if test="$pText">
   <xsl:choose>
    <xsl:when test="not(contains($pText, $pOld))">
     <xsl:value-of select="$pText"/>
    </xsl:when>
    <xsl:otherwise>
     <xsl:value-of select="substring-before($pText, $pOld)"/>
     <xsl:value-of select="$pNew"/>
     <xsl:call-template name="singleReplace">
       <xsl:with-param name="pText" select="substring-after($pText, $pOld)"/>
       <xsl:with-param name="pOld" select="$pOld"/>
       <xsl:with-param name="pNew" select="$pNew"/>
     </xsl:call-template>
    </xsl:otherwise>
   </xsl:choose>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<Addy>
    <Row>
        <LD>Dwelling, 1</LD>
        <LN> East</LN>
        <L>1</L>
        <Tf>Abesinia Passage</Tf>
    </Row>
    <Row>
        <LD>Lodge</LD>
        <LN>North </LN>
        <L>1</L>
        <Tf>Abesinia Passage</Tf>
    </Row>
</Addy>

the wanted, correct result is produced:

<Addy>
   <Row>
      <LD>FLAT, 1</LD>
      <LN> East</LN>
      <L>1</L>
      <Tf>Abesinia Passage</Tf>
   </Row>
   <Row>
      <LD>SHOP</LD>
      <LN>North </LN>
      <L>1</L>
      <Tf>Abesinia Passage</Tf>
   </Row>
</Addy>

Important:

This solution is complete and correct. Sean's is rather superficial.

Compare the results by the two solutions, when applied on the following XML document:

<Addy>
    <Row>
        <LD>Dwelling, Lodge, 1</LD>
        <LN> East</LN>
        <L>1</L>
        <Tf>Abesinia Passage</Tf>
    </Row>
    <Row>
        <LD>Lodge, Dwelling</LD>
        <LN>North </LN>
        <L>1</L>
        <Tf>Abesinia Passage</Tf>
    </Row>
</Addy>

Sean's solution producces incorrect replacements:

<Addy>
   <Row>
      <LD>FLAT, Lodge, 1</LD>
      <LN> East</LN>
      <L>1</L>
      <Tf>Abesinia Passage</Tf>
   </Row>
   <Row>
      <LD>Lodge, FLAT</LD>
      <LN>North </LN>
      <L>1</L>
      <Tf>Abesinia Passage</Tf>
   </Row>
</Addy>

The current, correct solution from this answer, produces the correct replacements:

<Addy>
   <Row>
      <LD>FLAT, SHOP, 1</LD>
      <LN> East</LN>
      <L>1</L>
      <Tf>Abesinia Passage</Tf>
   </Row>
   <Row>
      <LD>SHOP, FLAT</LD>
      <LN>North </LN>
      <L>1</L>
      <Tf>Abesinia Passage</Tf>
   </Row>
</Addy>

Explanation:

  1. The identity rule copies "as is" every matching node for which it is selected for executiom.

  2. It is overriden by a single template matching any text-node child of any LD element -- the nodes in which replacements must be done.

  3. This template checks if the matched text node contais any of the old (string values), as specified in the global inline my:reps element. For convenience all my:reps/rep elements have been selected in a global variable named $vReps and are referenced off this variable. If none of these strings are contained in the current node, then it is copied to the output.

  4. If there is at least one $vReps/old element whose string value is contained in the currently matched text node, then we must do replacements. We call a template with name "multiReplace" that performs all replacements in the current text node. We pass to this template as parameters the current text node and a nodeset of all $vReps/rep elements the string value of whose old child is contained in the current text node -- these are all the replacements to be made.

  5. The multiReplace template calls a template named singleReplace to do the first replacement and captures the result in a variable named $vRepResult. This contains the result of replacing in $pText all occurences of (the string value of) $pReps[1]/old with the string value of $pReps[1]/new. Then the multiReplace template calls itself recursively with the result of the replacements so far passed as the $pText parameter, and the node-set of replacements to be made from which the first replacement is excluded -- as the $pReps parameter. The "stop-condition" for this recursion is when the $pReps parameter becomes the empty node-set.

  6. The singleReplace template does what it name says -- it replaces in the string contained in its $pText parameter any substring equal to the $pOld parameter with the string contained in the pNew parameter. The number of replacements may be greater than one, but all of them are for a single replacement specification ==> thus the name singleReplace. The replacements are again done in a recursive manner with stop condition when $pText is non-empty and still contains $pOld.

like image 117
Dimitre Novatchev Avatar answered Dec 21 '25 07:12

Dimitre Novatchev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!