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:
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/>
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With