11

I have the following code that applies a XSLT style

Test.Xml.xslTransform = function(xml, xsl) {
    try {
        // code for IE
        if (window.ActiveXObject) {
            ex = xml.transformNode(xsl);
            return ex;
        }
        // code for Mozilla, Firefox, Opera, etc.
        else if (document.implementation && document.implementation.createDocument) {
            xsltProcessor = new XSLTProcessor();
            xsltProcessor.importStylesheet(xsl);
            resultDocument = xsltProcessor.transformToFragment(xml, document);
            return resultDocument;
        }
    } catch (exception) {
        if (typeof (exception) == "object") {
            if (exception.message) {
                alert(exception.message);
            }
        } else {
            alert(exception);
        }
    }

The code is working in IE and firefox but not in Chrome and Safari. Any ideas why?

Update

ResultDocument = xsltProcessor.transformToFragment(xml, document);

The line above is returning null. No error is being thrown.

Update

The code is not working as the xslt file contains xsl:include. Need to find a way to get the include working I will paste progress here

Update

It has been recomended that I use the http://plugins.jquery.com/project/Transform/ plugin. I am trying to use the client side libary as the example of include works here (http://daersystems.com/jquery/transform/).

The code works in IE but still not in chrome.

Test.Xml.xslTransform = function(xml, xsl) {
        try {
                $("body").append("<div id='test' style='display:none;'></div>");
                var a = $("#test").transform({ xmlobj: xml, xslobj: xsl });
                return a.html();
        }
        catch (exception) {
            if (typeof (exception) == "object") {
                if (exception.message) {
                    alert(exception.message);
                }
            } else {
                alert(exception);
            }

        }
    }

xml and xsl are both objects being passed in.

Update

I tried changing the XSL file to being something very simple with no include and Chrome is still not applying the stylesheet and IE is. The XSL that is being brought in as an object is:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:rs="urn:schemas-microsoft-com:rowset"
    xmlns:z="#RowsetSchema"
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:spsoap="http://schemas.microsoft.com/sharepoint/soap/"
    >
    <xsl:output method="html"/>
    <xsl:template match="/">
        <h1>test</h1>
    </xsl:template>

</xsl:stylesheet>

Update

The end result that I want is for the xsl to be applied to the xml file. The xsl file has in it includes. I want the trasnfer to happen on the client ideally.

Updated Rupert could you update the question with the xml and how you're calling Test.Xml.xslTransform ?

I got the xml using ie8

<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SearchListItemsResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/"><SearchListItemsResult><listitems xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
                    <rs:data ItemCount="1">
                        <z:row ows_Title="Test" ows_FirstName="Test 4" ows_UniqueId="74;#{1A16CF3E-524D-4DEF-BE36-68A964CC24DF}" ows_FSObjType="74;#0" ows_MetaInfo="74;#" ows_ID="74" ows_owshiddenversion="10" ows_Created="2009-12-29 12:21:01" ows_FileRef="74;#Lists/My List Name/74_.000" ReadOnly="False" VerificationRequired="0"/>
                    </rs:data>
                </listitems></SearchListItemsResult></SearchListItemsResponse></soap:Body></soap:Envelope>

The code is being called as follows:

xsl = Test.Xml.loadXMLDoc("/_layouts/xsl/xsl.xslt");
var doc = Test.Xml.xslTransform(xData.responseXML, xsl);

xData is the xml returned by a web service.

Rupert
  • 1,296
  • 8
  • 24
  • 36
  • Please provide more information, such as the exception message. I recommend using Sarissa (open-source JavaScript for cross-browser XML/XSLT). – Doug Domeny Jan 11 '10 at 13:56
  • Perhaps the XSLT on the XML is returning no result because none of the templates in the XSLT match on the XML document. See my blog "Avoid common XSLT mistakes" (http://dev.ektron.com/blogs.aspx?id=22956), especially link "XSLT: namespaces" (http://dev.ektron.com/kb_article.aspx?id=481) – Doug Domeny Jan 11 '10 at 15:25
  • Looks like there is a problem using xsl:include in the xslt. Looking at ways round it at the moment. – Rupert Jan 11 '10 at 17:34
  • Rupert could you update the question with the xml and how you're calling `Test.Xml.xslTransform` ? – Martijn Laarman Jan 20 '10 at 13:42
  • Rupert it seems as though it has problems included when using objects. Could you confirm the fix I posted works for you ? – Martijn Laarman Jan 20 '10 at 15:27

2 Answers2

14

If your XSLT is using xsl:include you might receive weird unexplainable errors but always with the same end result: your transformation failing.

See this chromium bug report and please support it! http://code.google.com/p/chromium/issues/detail?id=8441

The bug is actually in webkit though. For more info here's another link which goes into more detail why it doesn't work.

The only way around this is to pre-process the stylesheet so that it injects the included stylesheets. Which is what a crossbrowser XSLT library like Sarissa will do for you automatically.

If your looking for jQuery solution:
http://plugins.jquery.com/project/Transform/ is a cross browser XSL plug-in. I've succesfully used this to get xsl:include working in the past without much hassle. You don't have to rewrite your xsl's this plugin will pre-process them for you. Definitely worth looking at as it's more lightweight then Sarissa.

UPDATE:

<html>
<head>
<script language="javascript" src="jquery-1.3.2.min.js"></script> 
<script language="javascript" src="jquery.transform.js"></script>  
<script type="text/javascript">
function loadXML(file)
{
    var xmlDoc = null;
    try //Internet Explorer
    {
        xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
        xmlDoc.async=false;
        xmlDoc.load(file);
    }
    catch(e)
    {
        try //Firefox, Mozilla, Opera, etc.
        {
            xmlDoc=document.implementation.createDocument("","",null);
            xmlDoc.async=false;
            xmlDoc.load(file);
        }
        catch(e)
        {
            try //Google Chrome
            {
                var xmlhttp = new window.XMLHttpRequest();
                xmlhttp.open("GET",file,false);
                xmlhttp.send(null);
                xmlDoc = xmlhttp.responseXML.documentElement;
            }
            catch(e)
            {
            error=e.message;
            }
        }
    }
    return xmlDoc;
}
function xslTransform(xmlObject, xslObject)
{
    try 
    {
        $("body").append("<div id='test'></div>");
        var a = $("#test").transform({ xmlobj: xmlObject, xslobj: xslObject });
    }
    catch (exception) 
    {
        if (typeof (exception) == "object" && exception.message) 
            alert(exception.message);
        else alert(exception);
    }
}
var xmlObject = loadXML("input.xml");
var xslObject = loadXML("transform.xsl");
$(document).ready(function()  
{
    xslTransform(xmlObject, xslObject);
});
</script>
</head>
<body>

</body>
</html>

This test html page works both in Chrome/FireFox/IE.

input.xml is just a simple xml file containing <root /> transform.xsl is the stripped down xsl you posted.

EDIT

It does however seem the $.transform has problems importing stylesheets from included files:

Here's how to fix this:

Locate

var safariimportincludefix = function(xObj,rootConfig) {

in jquery.transform.js and replace the entire function with this:

var safariimportincludefix = function(xObj,rootConfig) {
    var vals = $.merge($.makeArray(xObj.getElementsByTagName("import")),$.makeArray(xObj.getElementsByTagName("include")));

    for(var x=0;x<vals.length;x++) {
        var node = vals[x];
        $.ajax({
            passData : { node : node, xObj : xObj, rootConfig : rootConfig},
            dataType : "xml",
            async : false,
            url : replaceref(node.getAttribute("href"),rootConfig),
            success : function(xhr) {
                try {
                    var _ = this.passData;
                    xhr = safariimportincludefix(xhr,_.rootConfig);

                    var imports = $.merge(childNodes(xhr.getElementsByTagName("stylesheet")[0],"param"),childNodes(xhr.getElementsByTagName("stylesheet")[0],"template"));
                    var excistingNodes = [];
                    try 
                    {
                        var sheet = _.xObj;
                        var params = childNodes(sheet,"param");
                        var stylesheets = childNodes(sheet,"template");
                        existingNodes = $.merge(params,stylesheets);
                    }
                    catch(exception) 
                    {
                        var x = exception;
                    }
                    var existingNames = [];
                    var existingMatches = [];
                    for(var a=0;a<existingNodes.length;a++) {
                        if(existingNodes[a].getAttribute("name")) {
                            existingNames[existingNodes[a].getAttribute("name")] = true;
                        } else {
                            existingMatches[existingNodes[a].getAttribute("match")] = true;
                        }
                    }

                    var pn = _.node.parentNode;
                    for(var y=0;y<imports.length;y++) {
                        if(!existingNames[imports[y].getAttribute("name")] && !existingMatches[imports[y].getAttribute("match")]) {
                            var clonednode = _.xObj.ownerDocument.importNode(imports[y],true);
                            //pn.insertBefore(clonednode,_.xObj);
                            pn.insertBefore(clonednode,childNodes(_.xObj,"template")[0]);
                        }
                    }
                    pn.removeChild(_.node);
                } catch(ex) { 

                }
            }
        });
    }

    return xObj;
};

Now using the previously pasted test index.html use this for transform.xsl:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    >
        <xsl:include href="include.xsl" />
    <xsl:output method="html"/>
    <xsl:template match="/">
            <xsl:call-template name="giveMeAnIncludedHeader" />
    </xsl:template>
</xsl:stylesheet>

And this for include.xsl

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template name="giveMeAnIncludedHeader">
        <h1>Test</h1>
    </xsl:template>
</xsl:stylesheet>

With the previously posted fix in jquery.transform.js this will now insert the included <h1>Test</h1> on all the browsers.

You can see it in action here: http://www.mpdreamz.nl/xsltest

Martijn Laarman
  • 13,476
  • 44
  • 63
  • It seems to be the xsl:include issue that is causing the problem. I am looking at possibly trying to use Sarissa – Rupert Jan 11 '10 at 17:33
  • Updated with another library that aims to fix this. The jQuery plugin is easy to set up and you don't have to rewrite your xslt's which is a huge timesaver even if the pre-processing will add some latency. – Martijn Laarman Jan 11 '10 at 19:59
  • I think the problem I will have is that the xml file is being genereated on the fly so I don't think the transform function will work – Rupert Jan 12 '10 at 12:19
  • The examples page is misleading you can pass XML objects as well by passing `xmlobj` instead of `xml`. Have a look at their official page: http://daersystems.com/jquerytransform.asp. – Martijn Laarman Jan 12 '10 at 12:52
  • Sorry hoepfully the last questio. Do you use client or server? Also do you now if the xsl:include works with the xslobj? Working in IE without a problem – Rupert Jan 12 '10 at 13:35
  • Client side (this plugin is a jQuery plugin so thus always runs on the client side). `xsl:include` works perfectly in IE and Firefox. In webkit based browsers `xsl:include` is broken however. I've used this jQuery plugin with `xmlobj` to fix `xsl:include` errors i was having in Safari/Chrome and other webkit deratives. – Martijn Laarman Jan 12 '10 at 14:03
  • Ok I have done the following. Created index.html with code above. In the same folder I have placed jquery-1.3.2.min.js and the modified jquery.transform.js. I have created transform.xsl, include.xsl and input.xml. I navigate to index.html and it only seems to work in FireFox... – Rupert Jan 20 '10 at 17:34
  • How are you opening the index.html file ? It might not allow the HTTPRequest if your using the filesystem directly (i.e file:///* instead of http://localhost/). You can see an example here: http://www.mpdreamz.nl/xsltest/ which works on Chrome/FF/IE for me. – Martijn Laarman Jan 20 '10 at 20:53
  • The link you sent works. I need to get this woring on my code but you havesolved the problem. Thanks for all your help. – Rupert Jan 21 '10 at 09:05
5

This is not an answer to the original question but during my search over internet looking for a sample xslt transformation that works on chrome I found links to this thread many times. I was looking for a solution that doesn't use any open-source or third party libraries/plugins and works well with silverlight.

The problem with chrome and safari is the limitation that prevents loading xml files directly. The suggested workaround at http://www.mindlence.com/WP/?p=308 is to load the xml file via any other method and pass it as a string to the xslt processor.

With this approach I was able to perform xsl transformations in javascript and pass on the result to silverlight app via HTML Bridge.

moonlightdock
  • 1,358
  • 13
  • 14