3

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>  
0

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

Comments

0

The problem with the existing code is that this line

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

will only emit anything if there is an LD element whose text is equal to the whole text of the context node. So the predicate needs to use contains() instead of =.

Using XSLT 2.0, you can change this template as follows:

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

If you can't use XSLT 2.0, you could use the EXSLT str:replace() instead of XSLT 2.0's version.

This assumes that the code attribute values don't contain any special characters like ., $, etc. that would be interpreted specially in a regular expression.

It also assumes that no more than one code will appear in any LD/text() node.

4 Comments

@ LarsH unfortunately am using XSLT 1.0. can you illustrate this in 1.0?
@lee, if you look at the page I linked to, exslt.org/str/functions/replace/index.html, there is a link at the bottom to an XSLT 1.0 implementation of a str:replace template. Near the top of the first page is example code for calling the template.
is there another way in doing this with 1.0?
@lee: What processor are you using? Is there a reason why you can't use EXSLT or the implementation I linked to above?
0

LarsH's solution is a good one. Try to use EXSLT if it is supported. If not supported and your XSLT engine is Microsoft, then this XSLT 1.0 style-sheet...

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:l="http://stackoverflow.com/questions/12360735"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  exclude-result-prefixes="xsl l msxsl" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:variable name="lookup">
  <l:map pattern="Dwelling" replacement="FLAT" />
  <l:map pattern="Lodge"    replacement="SHOP" />
</xsl:variable>  

<xsl:template match="LD/text()">
  <xsl:choose>
    <xsl:when test="contains(.,msxsl:node-set($lookup)/l:map/@pattern)">
      <xsl:variable name="hay-stack" select="." />
      <xsl:for-each select="(msxsl:node-set($lookup)/l:map[contains($hay-stack,@pattern)])[1]">
        <xsl:value-of select="concat(
          substring-before($hay-stack,@pattern),
          @replacement,
          substring-after($hay-stack,@pattern))" />
      </xsl:for-each>  
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="." />
    </xsl:otherwise>  
  </xsl:choose>  
</xsl:template>

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

...when applied to this input...

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

... yields...

<Addy>
  <Row>
    <LD>FLAT, 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>

If it is not Microsoft, and you cannot use EXSLT, then use this style-sheet...

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:l="http://stackoverflow.com/questions/12360735"
  exclude-result-prefixes="xsl l" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:variable name="lookup">
  <l:map pattern="Dwelling" replacement="FLAT" />
  <l:map pattern="Lodge"    replacement="SHOP" />
</xsl:variable>  

<xsl:template match="LD/text()">
  <xsl:choose>
    <xsl:when test="contains(.,document('')/*/xsl:variable[@name="lookup"]/l:map/@pattern)">
      <xsl:variable name="hay-stack" select="." />
      <xsl:for-each select="(document('')/*/xsl:variable[@name="lookup"]/l:map[contains($hay-stack,@pattern)])[1]">
        <xsl:value-of select="concat(
          substring-before($hay-stack,@pattern),
          @replacement,
          substring-after($hay-stack,@pattern))" />
      </xsl:for-each>  
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="." />
    </xsl:otherwise>  
  </xsl:choose>  
</xsl:template>

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.