0

I'm using XSLT to transform an XML into HTML on the client side (Chrome browser).

I'm trying to add <script> HTML tag to the XSLT but it seems that the code in it is never evaluated on the generated HTML, although I've specified defer.
On the other hand, onclick event itself runs OK.

Here is an example of the XSLT which demonstrates the issue:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="html"
            encoding="UTF-8"
            indent="no"/>

<xsl:template match="/">
<html>
<head>

<script type="text/javascript" defer="defer">
<xsl:text>
<![CDATA[
function test(){
  window.alert('Test');
}
 ]]>
 </xsl:text>
</script>


</head>

<body>
  <button onclick="window.alert('Test')">This works</button>      
  <br/>
  <button onclick="test()">This does not work</button>      
</body>
</html>
</xsl:template>
</xsl:stylesheet>

The XML file does not matter is this example. You can try the above example on W3Schools online XSLT transformation

In this example, clicking on This does not work yields an error: Uncaught ReferenceError: test is not defined.

What am I doing wrong here?

Update

The problem only happens when I'm performing the XSLT transformation itself in javascript. Here is the piece of code that is doing that in my case:

var processor = new XSLTProcessor(),
  htmlResult;

processor.importStylesheet(xsl);
htmlResult = processor.transformToFragment(xhr.responseXML, document);
document.getElementById("result").appendChild(htmlResult);

Update

I also need the following to work correctly when they appear in the XSLT file:

  • Loading external javascript files using script element:
    <script type="text/javascript" src="/somelibrary.js" />
  • Bare <script> elements with javascript code in them, that call functions which are declared in an external javascript file, loaded by an earlier script element.
Amir Gonnen
  • 3,525
  • 4
  • 32
  • 61
  • not sure - works in firefox, not in edge or chrome – Jaromanda X Nov 16 '15 at 08:42
  • I have tried to reproduce the problem, the XML document is http://home.arcor.de/martin.honnen/xslt/test2015111601.xml, your XSLT is http://home.arcor.de/martin.honnen/xslt/test2015111601.xsl, Chrome 46 on Windows 10 shows an alert box for both buttons, no script error is shown in the console. Don't use the w3schools test bed to run XSLT. – Martin Honnen Nov 16 '15 at 10:12
  • @MartinHonnen It is reproduced on my local sever. W3Schools was only given as an example. I will try to give another example, with some other online tool other than W3Schools. – Amir Gonnen Nov 16 '15 at 10:26
  • Can you reproduce the problem with http://home.arcor.de/martin.honnen/xslt/test2015111601.xml? Which version of Chrome on which platform is that? – Martin Honnen Nov 16 '15 at 13:26
  • @MartinHonnen no, as you said, in [your test](http://home.arcor.de/martin.honnen/xslt/test2015111601.xml) the problem is not reproduced. I suspect it may be related to the fact that the XML is transformed **in javascript** (both on W3Schools and maybe also in my local reproducible case). I'm still trying to figure this out. – Amir Gonnen Nov 16 '15 at 13:38
  • 1
    It seems to be a known problem with Chrome: https://code.google.com/p/chromium/issues/detail?id=266305, they don't seem to make any effort to fix it. The bug reporter says that doing `document.getElementById("result").appendChild(document.importNode(htmlResult, true))` instead of the direct `appendChild` fixes the problem with Chrome, I have not tested that. – Martin Honnen Nov 16 '15 at 17:36

2 Answers2

0

As far as I have researched, this is a known bug in Chrome reported in 2013 in https://code.google.com/p/chromium/issues/detail?id=266305. It does not look as any effort has been taken to fix it. However, the bug reporter also found a workaround, instead of using appendChild or insertBefore or similar to insert the fragment returned by transformToFragment directly into the document, if you call importNode first on the fragment, the script elements are then executed when you insert the imported fragment into the document.

I have written a test case http://home.arcor.de/martin.honnen/xslt/test2015111606.html which works fine for me a current version of Chrome on Windows, it uses

  var proc = new XSLTProcessor();
  proc.importStylesheet(xsltDoc);
  targetElement.appendChild(targetElement.ownerDocument.importNode(proc.transformToFragment(xmlDoc, targetElement.ownerDocument), true));

However, do note that Microsoft Edge neither with the normal approach of calling appendChild on the fragment nor with the above Chrome workaround executes the script element's code.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • `importNode` approach didn't work for me when the XSLT contained references to external javascript resources: ``. The embedded javascript code could not use `library.js`. – Amir Gonnen Nov 17 '15 at 11:49
  • In http://home.arcor.de/martin.honnen/xslt/test2015111701.html the script loads and transforms an XSLT stylesheet creating an HTML `script` with an external source file and that is loaded and executed fine by Chrome. – Martin Honnen Nov 17 '15 at 12:17
  • And in http://home.arcor.de/martin.honnen/xslt/test2015111702.html Chrome runs the external script where one function `foo` is declared and one function call is done, and the `input` button created also is able to call the button `foo` successfully. I have not tried to use `defer`, as I am not sure why that would be needed. – Martin Honnen Nov 17 '15 at 12:24
  • Your example include `script` element and an `input` element that uses that external script in its `onclick` action. However, In my case, I have a **bare `script` element in the XSL file that explicitly calls a function which is declared in an external .js file** (Indeed this part does not appear on my original question, I will revise it). This part is missing from your example and this is what's not working well with `importNode`. (while having the same function call on an action attribute such as `onclick` works well) – Amir Gonnen Nov 17 '15 at 14:50
0

Possible solution: (inspired from this answer)

                function insertAndExecute(id, htmlResult) {                
                    document.getElementById(id).appendChild(htmlResult);
                    var scripts = document.getElementById(id).getElementsByTagName("script");
                    var deferreds = [];

                    // First load all external scripts
                    for (var i = 0; i < scripts.length; i++) {                  
                        if (scripts[i].src != "") {
                            deferreds.push($.getScript(scripts[i].src));
                        }
                    }

                    // Execute other (inline) scripts after all external scripts are loaded                 
                    $.when.apply(null, deferreds).done(function() {
                        for (var i = 0; i < scripts.length; i++) {                  
                            if (!scripts[i].src) {
                                $.globalEval(scripts[i].innerText || scripts[i].textContent);
                            }
                        }
                    })
                }


var processor = new XSLTProcessor(),
                            htmlResult;

processor.importStylesheet(xsl);
htmlResult = processor.transformToFragment(xhr.responseXML, document);                      

// The htmlResult may contain javascript code. Scan and run it
insertAndExecute("result", htmlResult); 
Community
  • 1
  • 1
Amir Gonnen
  • 3,525
  • 4
  • 32
  • 61