Q:

In XSLT, Replace one class value when there are multiple values

I'm working with an XSL transformation that's being applied to an HTML document. Relevant HTML example:
<div class="row home-row">
    <p>Hello</p>
</div>

I select items that match this with the following XSL (which is working nicely):
<xsl:template match="//*[contains(concat(' ', normalize-space(@class), ' '), ' row ')]" priority="1">

Then, if a condition I've earlier checked matches, I replace "row" with "row-fluid" like this:
<xsl:variable name="original-row-class" select="string(@class)" />
<xsl:variable name="row-class">
                  <xsl:call-template name="replace">
                        <xsl:with-param name="text" select="$original-row-class" />    
                        <xsl:with-param name="replace" select="' row '" />
                        <xsl:with-param name="by" select="' row-fluid '" />
                    </xsl:call-template>
                </xsl:variable>
                <xsl:copy>
                    <xsl:attribute name="class"><xsl:value-of select="$row-class" /></xsl:attribute>
                    <xsl:apply-templates select="@*[local-name() != 'class']|node()[local-name() != 'class']"/>
                </xsl:copy>

This also works fairly well, except that it replaces all mentions of "row" with "row-fluid" even if they are inside "home-row". What I'd like to do is change the "row" classes to "row-fluid", but ignore the "home-row" classes while doing so. Is this possible?
I should mention that I am locked into XSLT 1.0.
Update: adding the replace template below (should've thought of that earlier!):
<xsl:template name="replace">
    <xsl:param name="text" />
    <xsl:param name="replace" />
    <xsl:param name="by" />
    <xsl:choose>
      <xsl:when test="contains($text, $replace)">
        <xsl:value-of select="substring-before($text,$replace)" />
        <xsl:value-of select="$by" />
        <xsl:call-template name="replace">
          <xsl:with-param name="text" select="substring-after($text,$replace)" />
          <xsl:with-param name="replace" select="$replace" />
          <xsl:with-param name="by" select="$by" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text" />
      </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Thanks,
Jonathan

A:

I think you have to split the class string into words with an recurcive template call.
e.g. And compare / replase each word.
OK and here is a solution based on the recursive approach.
        
        <xsl:template match="test" >
            <xsl:variable name ="new_class">
                <xsl:call-template name="replace_words">
                    <xsl:with-param name="replace" select="'xx'"/>
                    <xsl:with-param name="by" select="'yyy'"/>
                    <xsl:with-param name="words" select="'xx xx-a xx-b xx xx'"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:value-of select="$new_class"/>

        </xsl:template>

        <xsl:template name="replace_words">
            <xsl:param name="replace"/>
            <xsl:param name="by"/>
            <xsl:param name="words"/>
            <xsl:choose>
                <xsl:when test="contains($words,' ')">
                    <!-- try replace first word-->
                    <xsl:call-template name="replace_words">
                        <xsl:with-param name="replace" select="$replace"/>
                        <xsl:with-param name="by" select="$by"/>
                        <xsl:with-param name="words" select="substring-before($words,' ')"/>
                    </xsl:call-template>
                    <!-- dlimeter -->
                    <xsl:text> </xsl:text>
                    <xsl:call-template name="replace_words">
                        <xsl:with-param name="replace" select="$replace"/>
                        <xsl:with-param name="by" select="$by"/>
                        <xsl:with-param name="words" select="substring-after($words,' ')"/>
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:choose>
                        <xsl:when test="$replace = $words">
                            <xsl:value-of select="$by"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:value-of select="$words"/>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    </xsl:stylesheet>

A:

Just a few touches to your original transformation and it now works in the wanted way -- no extension functions are needed:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <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="*[contains(concat(' ', @class, ' '), ' row ')]">
   <xsl:variable name="original-row-class" select="string(@class)" />
   <xsl:variable name="row-class">
     <xsl:call-template name="replace">
        <xsl:with-param name="text" select=
        "concat(' ', $original-row-class, ' ')" />
        <xsl:with-param name="replace" select="' row '" />
        <xsl:with-param name="by" select="' row-fluid '" />
     </xsl:call-template>
   </xsl:variable>
   <xsl:copy>
     <xsl:attribute name="class">
      <xsl:value-of select="normalize-space($row-class)" />
     </xsl:attribute>
     <xsl:apply-templates select=
      "@*[local-name() != 'class']|node()"/>
   </xsl:copy>
 </xsl:template>

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

When applied on the provided XML document:
<div class="row home-row">
    <p>Hello</p>
</div>

the wanted, correct result is produced:
<div class="row-fluid home-row">
    <p>Hello</p>
</div>

