views:

53

answers:

2

Hello everybody,

This question is an extension of this one. I have many values, as seen below:

<myStuff>
    <elem1>asdf</elem1>
    <elem2>foo bar</elem2>
    <elem3>foo bar foo</elem3>
    <elem4>foofoo</elem4>
</myStuff>

I've been doing a copy-of over MOST the elems (the select in the copy-of is very specific) and then doing a find-and-replace on the resulting XML, but I'd like to combine the two. In most of the situations where I've applied this, I would replace anything that said <xsl:value-of select="whatever"> with <xsl:apply-templates select="whatever"> and use the below template:

<xsl:template match="*">
    <xsl:variable name="rep1">
        <xsl:choose>
            <xsl:when test='matches(., ".*foo.*")'>
                <xsl:value-of select='replace(., "foo", "qwerty")' />
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select='./text()' />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="rep2">
        <xsl:choose>
            <xsl:when test='matches(., ".*bar.*")'>
                <xsl:value-of select='replace($rep1, "bar", "myBar")' />
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select='$rep1' />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:value-of select="$rep2" />
</xsl:template>

I would like to use a similar template to replace the copy-of in my code (because I'm making the same replacements as in my other files, so I could use the same template), but I'm unsure of how to do this.

The output would look like this:

<myOtherStuff>
    <elem1>asdf</elem1>
    <elem2>qwerty myBar</elem2>
    <elem3>qwerty myBar qwerty</elem3>
    <elem4>qwertyqwerty</elem4>
</myOtherStuff>

All help is appreciated and thanks in advance!

+2  A: 

This transformation:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:my="my:my" >
 <xsl:output omit-xml-declaration="yes"/>

  <xsl:variable name="vReps" as="element()*">
   <rep target="foo" newval="qwerty"/>
   <rep target="bar" newval="myBar"/>
  </xsl:variable>

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

    <xsl:template match="text()">
      <xsl:sequence select="my:myMultiRep(., $vReps)"/>
    </xsl:template>

    <xsl:function name="my:myMultiRep" as="xs:string">
      <xsl:param name="pText" as="xs:string"/>
      <xsl:param name="pReps" as="element()*"/>

      <xsl:sequence select=
       "if(not($pReps))
         then $pText
         else my:myMultiRep(replace($pText, $pReps[1]/@target, $pReps[1]/@newval),
                            subsequence($pReps, 2)
                           )
       "/>
    </xsl:function>
</xsl:stylesheet>

when applied on the provided XML document:

<myStuff>
    <elem1>asdf</elem1>
    <elem2>foo bar</elem2>
    <elem3>foo bar foo</elem3>
    <elem4>foofoo</elem4>
</myStuff>

produces the wanted, correct result:

<myStuff>
    <elem1>asdf</elem1>
    <elem2>qwerty myBar</elem2>
    <elem3>qwerty myBar qwerty</elem3>
    <elem4>qwertyqwerty</elem4>
</myStuff>
Dimitre Novatchev
What's all the my:my stuff about?
adam_0
@Dimitre: +1 Good answer! I borrowed your variable ;)
Alejandro
@adam_0: User defined function must be under namespace.
Alejandro
@adam, @Alejandro: Even standard functions are under namespace, but there is no requirement to specify this via a prefix. No `<xsl:function>` name can be in "no namespace".
Dimitre Novatchev
@Dimitre: You are rigth. I think that is more proper statement.
Alejandro
@Alejandro: Yes, but my solution gives you way to specify which you want replaced in `corelation` -- `core` or `relation`. With your solution it will only become `kernellation`. With my solution it can become `codependency`. :)
Dimitre Novatchev
+2  A: 

With another approach, this stylesheet:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output omit-xml-declaration="yes"/>
    <xsl:variable name="vReps" as="element()*">
        <rep target="foo" newval="qwerty"/>
        <rep target="bar" newval="myBar"/>
    </xsl:variable>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="text()">
        <xsl:analyze-string select="." 
         regex="{string-join($vReps/concat('(',@target,')'),'|')}">
            <xsl:matching-substring>
                <xsl:value-of select="$vReps[@target=current()]/@newval"/>
            </xsl:matching-substring>
            <xsl:non-matching-substring>
                <xsl:value-of select="."/>
            </xsl:non-matching-substring>
        </xsl:analyze-string>
    </xsl:template>
</xsl:stylesheet>

Output:

<myStuff>
    <elem1>asdf</elem1>
    <elem2>qwerty myBar</elem2>
    <elem3>qwerty myBar qwerty</elem3>
    <elem4>qwertyqwerty</elem4>
</myStuff>

Also, this input (from comments):

<myStuff>
    <elem>asdfooasdf</elem>
</myStuff>

Output:

<myStuff>
    <elem>asdqwertyasdf</elem>
</myStuff>

Note: This RegExp union perform the replacement all at once. This may differ of sequencely fn:replace calls: suppose this replacement targets "A" -> "B" and "B" -> "C" on this string "AB", union replacement should output "BC" and sequencely calls output "CC".

Note 2: With regard to matching order, do note that RegExp union follows its own rules (see specs, more specific If two alternatives within the supplied $pattern both match at the same position in the $input string, then the match that is chosen is the first.) and sequencely fn:replace calls follows strictly user defined order.

Alejandro
I thought that this was working but then realized that the following case failed: `<elem>asdfooasdf</elem>`. No replacement was made. Can you reproduce this?
adam_0
@adam_0: It works. Tested.
Alejandro
Hmm.. I didn't change anything and now it works. Thanks!
adam_0
@adam_0: Your wellcome! Also, don't be afraid of declaring your own functions.
Alejandro
Good solution, Alejandro (+1). However, there is one fundamental difference b/n yours and mine solutions. Your solution performs the changes in the order of occurence of the targets in the input. Mine performs them in the order of the specifications in $vReps. The two processings can have different results, so I think my solution gives the user a little-bit more control.
Dimitre Novatchev
@Dimitre: You are right! I should say that before. Adding a note.
Alejandro
@Alejandro: A better example is: `corelation` and the results will differ if the first rule is `core` --> `kernel` and the second is `relation` --> `closeness` . If you reverse the order of rules, you'll get different results!
Dimitre Novatchev
@Dimitre: I see what you mean now. But that is the same for both answer. I think.
Alejandro
@Alejandro: Yes, but with my solution one can control the order of the rules. So, for example, the user could put the longest target in the first rule, or whatever they want. With your solution, the targets are replaced as they are encountered -- from left to right and the user cannot exercise any such control -- cannot vary the order in which rules are applied.
Dimitre Novatchev
@Dimitre: Oh! Sorry! Now I understand. Adding that to note, too.
Alejandro
@Alejandro: I think the main reason your solution was accepted is that the OP doesn't understand namespaces... :). Is this correct, @adam_0 ?
Dimitre Novatchev
@Dimitre: Ja! Well, it can be because of that, but also because of differences. Some times you don't want to replace already replaced text...
Alejandro