Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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).

like image 269
Phyxx Avatar asked Nov 20 '12 05:11

Phyxx


2 Answers

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).

like image 170
javabeangrinder Avatar answered Oct 12 '22 01:10

javabeangrinder


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.

like image 26
Enigmadan Avatar answered Oct 12 '22 02:10

Enigmadan