Transform XML with multiple XSL files

javascript, xml, xslt

Solution

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

Problem

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

Original source

Related problems