6

I am defining a JavaScript variable from XSLT and am getting an error due to an unescaped string. I understand that I need to replace this to ', but I'm unsure how to do that in XSLT 1.0.

XSLT example:

var currentComment = '<xsl:value-of select="root/Reviews/Review/Comment" />';

Rendered javascript with unescaped single quote:

var currentComment = 'Let's test a string.',
// Causing an error ------^
BenR
  • 2,791
  • 3
  • 26
  • 36
  • 1
    You will need to replace the `'` with `\'`, not with `'` (and also every backslash with double-backslash) - you're escaping it from the JavaScript parser, not the HTML parser. Take a look at [this answer](http://stackoverflow.com/a/1069157/592139) for a possible XSLT 1.0 solution. – Ian Roberts Jan 11 '13 at 22:59

3 Answers3

7

As Ian Roberts pointed out in his comment, you need to account for backslashes and not just apostrophes. Dimitre's answer can be modified as follows to account for this:

<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="/">
    <xsl:text>var currentComment = '</xsl:text>
    <xsl:apply-templates select="root/value" mode="escape" />
    <xsl:text>'&#xA;</xsl:text>

    <!-- Example with placing the escaped value in a variable first -->
    <xsl:variable name="escapedOther">
      <xsl:apply-templates select="root/otherValue" mode="escape" />
    </xsl:variable>
    <xsl:value-of select='concat("var otherComment = &apos;", $escapedOther, "&apos;")' />
  </xsl:template>


  <xsl:template match="@* | node()" mode="escape">
    <!-- Escape the apostrophes second -->
    <xsl:call-template name="replace">
      <xsl:with-param name="pTarget" select='"&apos;"' />
      <xsl:with-param name="pReplacement" select='"\&apos;"'/>
      <xsl:with-param name="pText">
        <!-- Escape the backslashes first, and then pass that result directly into the next template -->
        <xsl:call-template name="replace">
          <xsl:with-param name="pTarget" select="'\'" />
          <xsl:with-param name="pReplacement" select="'\\'" />
          <xsl:with-param name="pText" select="." />
        </xsl:call-template>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="replace">
    <xsl:param name="pText"/>
    <xsl:param name="pTarget" select='"&apos;"'/>
    <xsl:param name="pReplacement" select="'\&quot;'"/>

    <xsl:if test="$pText">
      <xsl:value-of select='substring-before(concat($pText,$pTarget),$pTarget)'/>
      <xsl:if test='contains($pText, $pTarget)'>
        <xsl:value-of select='$pReplacement'/>
      </xsl:if>

      <xsl:call-template name="replace">
        <xsl:with-param name="pText" select='substring-after($pText, $pTarget)'/>
        <xsl:with-param name="pTarget" select="$pTarget"/>
        <xsl:with-param name="pReplacement" select="$pReplacement"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Any time you need to escape something, you can just use apply-templates on it with mode escape. For this input XML:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <value>Let's test a string that refers to something like the C:\ drive's \Program Files Directory.</value>
  <otherValue>This value has a bunch of apostrophes '''' and backslashes \\\\ in it</otherValue>
</root>

This produces:

var currentComment = 'Let\'s test a string that refers to something like the C:\\ drive\'s \\Program Files Directory.'
var otherComment = 'This value has a bunch of apostrophes \'\'\'\' and backslashes \\\\\\\\'
JLRishe
  • 99,490
  • 19
  • 131
  • 169
3

This transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:variable name="vSomething">Let's test a string.</xsl:variable>

 <xsl:template match="/">
  <xsl:text>var currentComment = '</xsl:text>
  <xsl:call-template name="replace">
   <xsl:with-param name="pText" select="$vSomething"/>
  </xsl:call-template>'
 </xsl:template>

 <xsl:template name="replace">
  <xsl:param name="pText"/>
  <xsl:param name="pTarget" select='"&apos;"'/>
  <xsl:param name="pReplacement" select='"\&apos;"'/>

  <xsl:if test="$pText">
   <xsl:value-of select='substring-before(concat($pText,$pTarget),$pTarget)'/>
   <xsl:if test='contains($pText, $pTarget)'>
     <xsl:value-of select="$pReplacement"/>
   </xsl:if>

   <xsl:call-template name="replace">
    <xsl:with-param name="pText" select='substring-after($pText, $pTarget)'/>
    <xsl:with-param name="pTarget" select="$pTarget"/>
    <xsl:with-param name="pReplacement" select="$pReplacement"/>
   </xsl:call-template>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

when applied on any XML document (not used), produces the wanted, correct result:

var currentComment = 'Let\'s test a string.'
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • You've gone to the trouble of defining the parameters `$pTarget` and `$pReplacement` presumably with the intent of making the `replace` template dynamic, but then you don't actually use them where it matters. – JLRishe Jan 12 '13 at 15:27
  • @JLRishe, Yes, this is *by design* :) Note that I have given these parameters suitable *default values* . So, the result is that I can omit them in the outermost call of the template in this particular case. – Dimitre Novatchev Jan 12 '13 at 15:37
  • I'm not referring to omitting them from the template call - I'm referring to not using them at all in these lines: ``, `\'`, ``. If someone were to actually pass in values for those parameters, it would still just perform `'` -> `\'` replacement. – JLRishe Jan 12 '13 at 15:44
  • And this is a minor point, but you've specified `'\"'` as the default value for the pReplacement parameter. If the parameter values were being used, this would result in apostrophes being replaced with `\"`. – JLRishe Jan 12 '13 at 15:47
  • Cool. +1 for a nice generic solution. – JLRishe Jan 12 '13 at 19:14
  • Thank you Dimitre for the straight forward template. Much appreciated. – BenR Jan 14 '13 at 19:54
0

This is just an implementation of the JLRische-Novachev solution I did to accomplish my xml. Maybe it can be usefull:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <value msg="1- Let's test a string that refers to something like the C:\ drive's \Program     Files Directory." num="1"/>
  <value msg="2- Let's test a string that refers to something like the C:\ drive's \Program Files Directory." num="2"/>  
</root>

xsl:

 ...
 <xsl:template match="@* | node()" mode="escape">

    <!-- Escape the apostrophes second -->
    <xsl:call-template name="replace">
      <xsl:with-param name="pTarget" select='"&apos;"' />
      <xsl:with-param name="pReplacement" select='"\&apos;"'/>
      <xsl:with-param name="pText">
        <!-- Escape the backslashes first, and then pass that result directly into the next     template -->
        <xsl:call-template name="replace">
          <xsl:with-param name="pTarget" select="'\'" />
          <xsl:with-param name="pReplacement" select="'\\'" />
          <xsl:with-param name="pText" select="./@msg" />
        </xsl:call-template>
      </xsl:with-param>
    </xsl:call-template>

<xsl:text>, Number: </xsl:text><xsl:value-of select="./@num"/><xsl:text>&#xA;</xsl:text>

...

Output:

1- Let\'s test a string that refers to something like the C:\\ drive\'s \\Program Files Directory., Number: 1
2- Let\'s test a string that refers to something like the C:\\ drive\'s \\Program Files Directory., Number: 2
Duccio Fabbri
  • 998
  • 1
  • 10
  • 21