简体   繁体   中英

Transform XML with multiple XSL files

I have some XML that I'd like to transform into HTML using a number of XSL files. These XSL files are all related through xsl:import and xsl:include statements, and all required to complete the transform.

I know the XSL works, because using the <?xml-stylesheet type="text/xsl" href="transform.xsl"?> directive in a pre-created XML file opened by the browser displays the output that I want. The problem is that I want to be able to replicate this functionality on dynamically generated XML.

There are two ways that I can see that this can possibly be done, but both seem to have limitations that I have not been able to work around.

The first solution is to use Javascript to transform the XML. From what I can tell, this will require the XSLTProcessor object to load multiple XSL files, but Chrome (and probably other browsers) don't support xsl:import very well - http://code.google.com/p/chromium/issues/detail?id=8441

I also looked at writing the XML to an iFrame or new window, but the <?xml-stylesheet type="text/xsl" href="transform.xsl"?> directives are commented out in the resulting window. Actually anything written into a new window is HTML anyway - I have not found a way to write XML into a new window.

So how can I get a browser window to display the result of an XML file transformed with a set of XSL files?

UPDATE

So here is the results of my research into this problem.

Possible workaround : compile a tool like xsltproc into JavaScript using emscripten. I have actually done this - see https://github.com/mcasperson/xsltproc.js

Problem : it is incredible slow in firefox (what takes 5 seconds in Chrome takes 30+ in firefox), and you can't run the code in a Chrome Web Worker - https://code.google.com/p/chromium/issues/detail?id=252492

Possible workaround : don't use XSL at all, but display the XML using CSS style sheets.

Problem : until browsers start implementing the css attr(atrributename, url) function, there is no way to treat a file reference in an XML attribute as anything other than a string, which makes it impossible to display images.

Possible workaround : Merge all the XSL files into a single style sheet

Problem : This is somewhat possible (see Merge multiple xslt stylesheets ), but xsl:import and xsl:include have particular semantics that don't carry across when simply substituting a files contents in place of a xsl:import or xsl:include statement. For large XSL transforms broken up over multiple files, this solution would require a lot of manual work.

Possible workaround : Write out the contents of the XML into an iframe or new window.

Problem : It is not possible to write XML into a new window or iframe. The contents written into these elements is always assumed to be HTML, and inserted into a HTML->BODY element.

Possible workaround : Create a server side service that takes XML and then returns that XML with the XSL stylesheet directive. The service URL can then be used as a src attribute for an iframe or new window.

Problem : The service would have to be a GET end point, which means the XML to be returned would have to be included as a query parameter, which means you'll eventually run into issues with the length of the URL.

Possible workaround : Use a javascript XSL library like Saxonica CE .

Problem : This may actually work (I haven't tried it), but Saxonica CE is no open source (which is a requirement of our project).

If you want a Browser only solution I would do it like this:

Static xml

Make a simple static xml that contains only the call to the xsl. This is the xml that is opened in the browser - always. This xml file could contain property settings to control the flow or nothing at all as this example.

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="cartoon2html.xsl"?>
<xml/>

Dynamic plain xml

Generate the dynamic XML in your favourite way using a defined name - in my case cartoons.xml.

<?xml version="1.0" encoding="utf-8"?>
<cartoons>
    <cartoon name="Donald Duck" publisher="Walt Disney" />
    <cartoon name="Mickey Mouse" publisher="Walt Disney" />
    <cartoon name="Batman" publisher="DC Comics" />
    <cartoon name="Superman" publisher="DC Comics" />
    <cartoon name="Iron Man" publisher="Marvel Comics" />
    <cartoon name="Spider-Man" publisher="Marvel Comics" />
</cartoons>

XSLT with document load

Use document loan in the xslt to reference the generated dynamic xml. By using select in the first apply-templates all other templates will work as intended.

Take a close look at the variable reference at top and the further down in the code. This is where the magic is performed.

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

    <xsl:variable name="cartoons" select="document('cartoons.xml')/cartoons" />

    <xsl:template match="/">
        <html>
            <head>
                <title>Cartoons</title>
                <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
            </head>
            <body>
                <xsl:apply-templates select="$cartoons" />
            </body>
        </html>
    </xsl:template>

    <xsl:template match="cartoons">
        <table>
            <xsl:apply-templates />
        </table>
    </xsl:template>

    <xsl:template match="cartoon">
        <tr>
            <td><xsl:value-of select="@name" /></td>
            <td><xsl:value-of select="@publisher" /></td>
        </tr>
    </xsl:template>

</xsl:stylesheet>

You could save these three files into a directory of choice and open the static xml file in firefox. (Chrome and perhaps Safari has to have the file served through a web server to perform the transformation).

I would recommend using jquery to import and style the XML.

Something like this would allow you to import the XML whenever a function is called (a function linked to a keypress or a refresh button or even a timer.)

$.ajax({
    type: "GET",
    url: "FILENAME.xml",
    dataType: "xml",
    success: function(xml) {
        $(xml).find('site').each(function(){ //finds parent node
            var id = $(this).attr('id'); //id of parent node
            var title= $(this).find('title').text();  //finds title node within parent node
            var url = $(this).find('url').text(); //finds URL node within parent node
            var description = $(this).find('descr').text(); //etc...
            var img = $(this).find('img').text(); //etc...

            // Creates div with id of parent node (for individual styling)
            $('<div id="link_'+id+'">')
                .addClass('add a div class')
            //sets css of div
                .css({set css})
            // Sets the inner HTML of this XML allocated div to hyperlinked 'title' with 'description' and an 'img'
                .html('<a href="'+url+'">'+title+'</a>'+description+'<img src="'+img+'">')                  
            // Append the newly created element to body
                .appendTo('#holder');
    }
    }
})

And the XML would look something like this:

<site id="0">
 <url>http://blah.com</url>
 <img>imgs/image1.png</img>
 <description>this is a description</description>
 <title>Title</title>
</site>

<site id="1">
 <url>http://filler.com</url>
 <img>imgs/image2.jpg</img>
 <description>this is another description</description>
 <title>Title 2</title>
</site>

Of course instead of importing to a div you could import the XML to a table or any other type of HTML element.

I had the same problem just now. My solution was to propagate the includes directly into the XML. This code was tested under Chrome, Firefox and IE 10,9,8 and 7.

function propegateIncludes(dname,xml, isExplorer) {
    var preTag = isExplorer ? "xsl:" : "";

    var TAG_STYLESHEET = preTag + "stylesheet", TAG_INCLUDE = preTag
        + "include", TAG_TEMPLATE = preTag + "template";

    var stylesheets = xml.getElementsByTagName(TAG_STYLESHEET);
    if (stylesheets.length == 0) {
        return;
    }

    var includes = xml.getElementsByTagName(TAG_INCLUDE);
    if (includes.length == 0) {
        return;
    }

    var stylesheet = stylesheets[0];
    var path = dname.substring(0, dname.lastIndexOf('/'));

    for ( var i = 0; i < includes.length; i++) {
        var args = includes[i].attributes;
        var includeXml = null;

        for ( var a = 0; a < args.length; a++) {
            if (args[a].nodeName == "href") {
                includeXml = loadXMLDoc(path + "/" + args[a].nodeValue);
                break;
            }
        }

        stylesheet.removeChild(includes[i]);

        if (includeXml != null) {
            var templates = includeXml.getElementsByTagName(TAG_TEMPLATE);

            for (var t = 0; t < templates.length; ++t) {
                stylesheet.appendChild(templates[t].cloneNode(true));
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM