You are viewing a plain text version of this content. The canonical link for it is here.
Posted to j-users@xalan.apache.org by rabii mouali <mo...@andil.fr> on 2002/06/14 13:48:06 UTC

XSLT function problem

Hi,

Perhaps this is not a place for asking this kind of question, but i didn't
found the response anywhere.

I use xalan to transform xml file to html using xsl.

The output contain some javascript, as my xml language is french, there are
conflict between javascript and &apos; who must be \&apos;.

I try in my xsl to use translate function, but there are problem : i have an
error that there are an simple quot who's not closed.

How can i remplace an &apos; in input to \&apos; in output.

Thanks



Re: XSLT function problem

Posted by Peter Davis <pe...@pdavis.cx>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Friday 14 June 2002 04:48, rabii mouali wrote:
> How can i remplace an &apos; in input to \&apos; in output.

In Javascript, &apos; is only one of several characters that will cause 
problems, and must be escaped with a backslash '\'.  Here is a complete list 
of the escaped characters:

* backslash '\\' (&#92;)
* single quote '\'' (&apos;)
* double quote '\"' (&quot;)
* line-feed '\n' (&#10;)
* carriage-return '\r' (&#13;)
* tab '\t' (&9;)

The following characters should be escaped, but are illegal in XML, and so 
cannot cause a problem when using XSLT:

* form-feed '\f' (&#12;)
* backspace '\b' (&#8;)
* vertical-tab '\v' (&#11;)

As you have discovered, the translate() function cannot be used to do this, 
since it can only translate single characters.  So, you must use a recursive 
template to search for each appearance of the desired character.

The process goes like this: say you have a string:

"Hello. Goodbye."

and you want to replace every "." (period) with an '!' (exclamation point).  
The first step is to find the first period, and output the part of the string 
that comes before that.  The XSLT contains() and substring-before() functions 
are useful for this purpose.  So after the first step, you are left with 
this:

Output: "Hello"
Remaining: ". Goodbye."

Now you output the character that will replace the period:

Output: "Hello!"
Remaining: ". Goodbye."

Next, you get the part of the string that comes after the first period:

Remaining: " Goodbye."

and repeat the process:

Output: "Hello! Goodbye"
Remaining: "."

Output: "Hello! Goodbye!"
Remaining: "."

Remaining: "" (no more remaining, so stop)

This basically functions like a loop, but XSLT has no while() or for() loops.  
You have to use a recursive template -- a template that calls itself, just 
like a function that calls itself in Javascript.  The whole thing looks like 
this (for brevity, the 'xsl:' prefix has been removed from each element):

<template name="replace-string">
  <!-- search for this: -->
  <param name="search" select="string(.)"/>

  <!-- and replace it with this: -->
  <param name="replace" select="string(.)"/>

  <!-- here is the original string: -->
  <param name="string" select="string(.)"/>
  
  <choose>
    <when test="not(contains($string, $search))">
      <!-- if there are no more appearances of $search in the
        $string, output the rest of the string and stop. -->
      <value-of select="$string"/>
    </when>
    <otherwise>
      <!-- output the part of the $string that is before the
        first appearance of $search. -->
      <value-of select="substring-before($string, $search)"/>
      
      <!-- output the replacement $replace.  -->
      <value-of select="$replace"/>

      <!-- repeat the process, using the part of $string that
        comes after the first appearance of $search. -->
      <call-template name="replace-string">
        <with-param name="search" select="$search"/>
        <with-param name="replace" select="$replace"/>
        <with-param name="string" select="substring-after($string, $search)"/>
      </call-template>
    </otherwise>
  </choose>
</template>


The one limitation is that the above template can only search-and-replace one 
string at a time, but in order to escape Javascript, you have to do it for 
each of the six characters that are illegal (single-quote, backslash, 
double-quote, line-feed, carriage-return, and tab).  I've done this before, 
and here is what I came up with:

  <template name="escape-javascript">
    <param name="string" select="string(.)"/>

    <choose>
      <when test="function-available('text-utils:escape')">
        <value-of select="text-utils:escape($string)"/>
      </when>

      <otherwise>
        <!-- replace all characters not matching SingleStringCharacter
        or DoubleStringCharacter according to ECMA262.  Note: not all
        characters that should be escaped are legal XML characters:
        "\a", "\b", "\v", and "\f" are not escaped. -->
        <call-template name="replace-string">
          <with-param name="search">'</with-param>
          <with-param name="replace">\'</with-param>
          <with-param name="string">
            <call-template name="replace-string">
              <with-param name="search">"</with-param>
              <with-param name="replace">\"</with-param>
              <with-param name="string">
                <call-template name="replace-string">
                  <with-param name="search">
                    <text>#9;</text>
                  </with-param>
                  <with-param name="replace">\t</with-param>
                  <with-param name="string">
                    <call-template name="replace-string">
                      <with-param name="search">
                        <text>&#10;</text>
                      </with-param>
                      <with-param name="replace">\n</with-param>
                      <with-param name="string">
                        <call-template name="replace-string">
                          <with-param name="search">
                            <text>&#13;</text>
                          </with-param>
                          <with-param name="replace">\r</with-param>
                          <with-param name="string">
                            <call-template name="replace-string">
                              <!-- remember to do backslash first -->
                              <with-param name="search">\</with-param>
                              <with-param name="replace">\\</with-param>
                              <with-param name="string" select="$string">
                              </with-param>
                            </call-template>
                          </with-param>
                        </call-template>
                      </with-param>
                    </call-template>
                  </with-param>
                </call-template>
              </with-param>
            </call-template>
          </with-param>
        </call-template>
      </otherwise>
    </choose>
  </template>

Let me know if that needs clarification.

To use the above template, you just call it like this:

<xsl:call-template name="escape-javascript">
  <xsl:with-param name="string">The 'string' to escape.</xsl:with-param>
</xsl:call-template>

Hope that helps!

- -- 
Peter Davis
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.7 (GNU/Linux)

iD8DBQE9ClFmNSZCJx7tYycRAlZfAJ9mkJxk+lp8JEss0iR+PEjjacculwCeMJLS
2e93Dp7gn+KyDSOC0vM1Z2k=
=c/Gd
-----END PGP SIGNATURE-----