views:

72

answers:

3

Basically, what I'm doing right now is running an XSLT, then opening the result in Visual Studio and doing a find and replace for one word - for this example, I want to change all instances of "bar" to "myBar". All instances of "bar" may be assumed to be in text elements like so:

<someElement>bar.whatever</someElement>

This would be transformed to:

<someElement>myBar.whatever</someElement>

But the caveat to this is that I'm also running other transformations, such as renaming or moving the element. Is there any way that I can combine these two operations (the transform and the find and replace) into one XSLT? Is this possible for multiple find-and-replaces?

All help is appreciated and thanks in advance!

Edit: From comments

I should have specified that I AM indeed using XSLT 2.0. I'm reading that article and trying to figure out how I would use replace()

+1  A: 

EDIT: Now that has been clarified that the OP wants every occurrence of 'bar' replaced by 'myBar',this XSLT 1.0 stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="text()" name="text">
        <xsl:param name="pString" select="."/>
        <xsl:choose>
            <xsl:when test="contains($pString,'bar')">
                <xsl:value-of 
                select="concat(substring-before($pString,'bar'),'myBar')"/>
                <xsl:call-template name="text">
                    <xsl:with-param name="pString" 
                    select="substring-after($pString,'bar')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$pString"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

With input:

<someElement>barbarian</someElement>

Output:

<someElement>myBarmyBarian</someElement>

Now, XSLT 2.0 stylesheet:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="text()">
        <xsl:value-of select="replace(.,'bar','myBar')"/>
    </xsl:template>
</xsl:stylesheet>

Note: text nodes pattern matching.

Alejandro
+1  A: 

XSLT 1.0 doesn't have robust text search-and-replace. You can gin something up that uses contains, substring-before, and substring-after, but you have to use a recursive template to handle the case where the string you're trying to fix has multiple occurrences of the substring.

This works, assuming that your transform that moves and renames elements is a variant of the identity transform:

<xsl:template match="text()">
  <xsl:call-template name="replace">
    <xsl:with-param name="text" select="."/>
  </xsl:call-template>
</xsl:template>

<xsl:template name="replace">
  <xsl:param name="text"/>
  <xsl:choose>
    <xsl:when test="contains($text, 'bar')">
      <xsl:call-template name="replace">
        <xsl:with-param name="text" select="concat(
                        substring-before($text, 'bar'), 
                        'myBar',
                        substring-after($text, 'bar'))"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$text"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Note that anywhere you're copying the value of an element using value-of, you need to be using apply-templates; change this:

<xsl:template match="someElement">
   <renamedElement>
      <xsl:value-of select="."/>
   <renamedElement>
</xsl:template>

into this:

<xsl:template match="someElement">
   <renamedElement>
      <xsl:apply-templates select="text()"/>
   <renamedElement>
</xsl:template>

Doing multiple replacements is a bit trickier. You have to extend the replace template to take a searchFor and replaceWith arguments, which is easy enough, and then do this in the text() template:

<xsl:variable name="pass1">
   <xsl:call-template name="replace">
      <xsl:with-param name="text" select="."/>
      <xsl:with-param name="searchFor">bar</xsl:with-param>
      <xsl:with-param name="replaceWith">myBar</xsl:with-param>
   </xsl:call-template>
</xsl:variable>

<xsl:variable name="pass2">
   <xsl:call-template name="replace">
      <xsl:with-param name="text" select="."/>
      <xsl:with-param name="searchFor">bar</xsl:with-param>
      <xsl:with-param name="replaceWith">myBar</xsl:with-param>
   </xsl:call-template>
</xsl:variable>

<xsl:value-of select="$pass2"/>

In XSLT 2.0, which supports using regular expressions on text nodes, this is a lot easier. You still create a template that matches text(), but it just has a call to replace. See this article for plenty of information, if you're fortunate enough to be able to use XSLT 2.0.

Robert Rossney
@Robert Rossney: I think you are adding some complexity where there isn't. If the OP is going to elaborate more about the needed matching, then you could say how much complex the XSLT 1.0 solution would be. Plus, why are you not recommending just to match the text node?
Alejandro
The OP said that he wants to replace "all instances of 'bar'", not "any instance of 'bar' that appears at the start of a text node." If you can write a simpler XSLT 1.0 template that can change `barbarian` to `myBarmyBarian` I'd very much like to see it. I don't understand your second question.
Robert Rossney
@Robert Rossney: The OP has also provided input sample an desired output. ;) I could easily rewrite my answer to address your `barbarian` to `myBarmyBarian` transformation just adding a few instructions to my answer. Complexity would emerge only if text nodes are going to become huge, because the need to use DVC to avoid stackoverflow. For second question explanation you could see my answer...
Alejandro
Well, let me clarify: I'd very much like to see a non-recursive XSLT 1.0 template that can replace *n* occurrences of a substring in a text node. If you can do this by adding a few instructions to the template in your answer, I will be very impressed.
Robert Rossney
I should have specified that I AM indeed using XSLT 2.0. I'm reading that article and trying to figure out how I would use replace()
adam_0
@adam_0: Then you should clarify what you want to replace: any occurrences of "bar" or just starting "bar".
Alejandro
@Robert Rossney: declarative paradigm lives by recursion!
Alejandro
@Alejandro, I want to replace all occurrences of "bar".
adam_0
@adam_0: So "barbarian" become "myBarmyBarian" as Robert Rossney suggest?
Alejandro
@Alejandro, precisely.
adam_0
A: 

You can use the Identity Transform pattern as suggested by alejandro and to the replacement with exslt str:replace

If your processor does not support this, you can find an implementation as an XSLT template on the same page.

To enable exslt, just use the appropriate namespace in your stylesheet as described here

Some additional pointers: You can read more about the pattern here: and here.

Using the first template only will copy the source to the destination unchanged. Then you can simply add additional templates for each element you want to do more specific formatting, in your case one that matches "text()", I guess that does the replacement.

wwerner