How to use the Javascript FileReader API for XSLT transformations — no local server required

The Javascript FileReader API provides a way to load files, such as XML files, without the use of the Fetch() API, XMLHttpRequest () or a local server — could this be a benefit for the development of small offline HTML tools?

XSLT for the Modern Web
6 min readDec 14, 2024

If you’ve been following along with the posts in the XSLT for the Modern Web you’ve seen that XSLT transformations can be executed in the browser view a web server (without Javascript) or with the use of various Javascript API’s —the Fetch() API or XMLHttpRequest or via libraries such as jQuery (using XMLHttpRequest behind the scenes). All these Javascript API’s require the use of a server or at the very least a local server — i.e localhost.

I’ve covered these methods of loading XML files in previous posts:

The Javascript FileReader API does not require that files are accessed via a server, this means that we have another method to load XML and the API is very simple to use! Great.

(Note that the FileReader doesn’t source the file from a local filesystem filepath i.e — /Documents/myfile.xml — instead the files need to loaded via <input type=”file”> or via DataTransfer object as part of a Drag and Drop operation.)

XSLT for the Modern Web

A simple HTML5 example

Below is a simple example of using a simple HTML5 form with two <input type=”file”> — one for loading the XSLT stylesheet, a second for an XML document. The XSLT transformation is then outputted into a <textarea>.

Disclaimer: This example is not suggested at all for production, as this is far too simple for online use (it doesn’t even have a submit button!) and any online use would need to involve a lot of error checking! — but the point here is that if you were making a offline tool which takes a XSLT stylesheet and a single XML document — using FileReader like this avoids the need for a server. You could easily extend this example with a button that resets the form, a button that opens a new tab with the output in this or maybe a way to download the output into a file.

(This example supposes that the XSLT transformation would result in a HTML output, but would not include the topmost elements of the output <!DOCTYPE html><head> section etc. This is because XSLT cannot output <!DOCTYPE html> and so cannot make a HTML5 document so it is best to that the output HTML is from the <body> downwards).

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<title>XSLT FileReader Example</title>
</head>
<body>
<main>
<div class="container">
<div class="row">
<h1>HTML5 XSLT FileReader</h1>
<p>An example of loading XSLT using FileReader</p>
</div>
<form class="row">
<div class="mb-3">
<label for="xslt" class="form-label">XSLT</label>
<input class="form-control" type="file" id="xslt" name="xslt">
</div>
<div class="mb-3">
<label for="xml" class="form-label">XML</label>
<input class="form-control" type="file" id="xml">
</div>
<div class="mb-3">
<div class="form-floating">
<textarea class="form-control" placeholder="Output here...." id="output" style="height: 400px"></textarea>
<label for="output">XSLT output</label>
</div>
</div>
</form>

<script type="text/javascript">
(function(){

function documentLoaded(){

if(!('XSLTProcessor' in window && 'DOMParser' in window)){
console.log('XSLTProcessor does not appear to be available in this browser. Please try another.');
return;
}

const xsltProcessor = new XSLTProcessor();
const parser = new DOMParser();

//output
const output = document.getElementById("output");

//handle xsltfile
const xsltfile = document.getElementById("xslt");

//handle xmlfile
const xmlfile = document.getElementById("xml");

const HTML5Doctype = ["<!DOCTYPE html><html lang=\"en\">", "</html>"];
const HTML5Head = ["<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65\" crossorigin=\"anonymous\"></head>"];

xsltfile.addEventListener("change", function(){
const file = this.files[0];

if(file.type !== "application/xslt+xml"){
console.log("file is not an XSLT file");
return;
}

const reader = new FileReader();
reader.readAsText(file);

//load
reader.addEventListener("load", function(){
let xsl = parser.parseFromString(reader.result, "application/xml");
xsltProcessor.importStylesheet(xsl);
}, false);

//error
reader.addEventListener("error", function(){
console.log("error");
}, false);

}, false);


xmlfile.addEventListener("change", function(){

const file = this.files[0];

if(file.type !== "text/xml"){
console.log("file is not an XML file");
return;
}

const reader = new FileReader();
reader.readAsText(file);

//load
reader.addEventListener("load", function(){
const xml = parser.parseFromString(reader.result, "application/xml");

const fragment = xsltProcessor.transformToFragment(xml, document);
output.innerHTML = HTML5Doctype[0] + HTML5Head[0] + fragment.firstChild.innerHTML + HTML5Doctype[1];

}, false);

//error
reader.addEventListener("error", function(){
console.log("error");
}, false);

}, false);

}

window.addEventListener("load", documentLoaded, false);
}) ();
</script>

</div>
</main>
</body>
</html>

The key sections to review are within the event handlers — a file can be loaded with a few very simple lines. I’ve added a few comments here but the usage of the API is really simple with event handlers.

xsltfile.addEventListener("change", function(){

//here is the handle to the file which is accessed via the <input type="file">
const file = this.files[0];

if(file.type !== "application/xslt+xml"){
console.log("file is not an XSLT file");
return;
}

//here is the instance to FileReader API
const reader = new FileReader();

//read the file as text
reader.readAsText(file);

//load
reader.addEventListener("load", function(){

//parse the file - the parser here is DOMParser API
let xsl = parser.parseFromString(reader.result, "application/xml");

//xls is a documentFragment, which can then be passed to xsltProcessor.importStylesheet
xsltProcessor.importStylesheet(xsl);
}, false);

//error
reader.addEventListener("error", function(){
console.log("error");
}, false);

}, false);

XSLT

Here is the XSLT stylesheet I used purely as an example to test the XSLT transformation — all it does is generate a list of links to content, and a series of sections, using XPath generate-id to establish a unique link between.

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

<xsl:output method="html"/>

<xsl:template match="/">
<body>
<ul>
<xsl:apply-templates select="history/period/person" mode="link"/>
</ul>
<xsl:apply-templates select="history/period/person" mode="section"/>
</body>
</xsl:template>

<xsl:template match="person" mode="link">
<li>
<a href="#{generate-id(current())}">
<xsl:value-of select="@name"/>
</a>
</li>
</xsl:template>

<xsl:template match="person" mode="section">
<section>
<a id="{generate-id(current())}">
<p>
<xsl:value-of select="."/>
</p>
</a>
</section>
</xsl:template>

</xsl:stylesheet>

XML

Here is the simple XML document used with this example — again, purely as an example.

<?xml version="1.0" encoding="UTF-8"?>
<history>
<period from="27BCE" to="476CE" name="Roman Empire">
<person name="Octavian" knownas="Augustus" focusdate="27-14CE">
...
</person>
<person name="Tiberius" role="Emperor" focusdate="14-37 BCE">

</person>
<person name="Caligula" role="Emperor" focusdate="41-54 BCE">
...
</person>
<person name="Nero" role="Emperor" focusdate="54-68 BCE">
...
</person>
<person name="Vespasian" role="Emperor" focusdate="69-79 BCE">
...
</person>
<person name="Titus" role="Emperor" focusdate="79-81 BCE">
...
</person>
<person name="Domitian" role="Emperor" focusdate="81-96 BCE">
...
</person>
<person name="Marcus Cocceius Nerva" role="Emperor" focusdate="96-98 BCE">
...
</person>
<person name="Trajan" role="Emperor" focusdate="98-117 BCE">
...
</person>
<person name="Hadrian" role="Emperor" focusdate="117-138 BCE">
...
</person>
<person name="Antonius Pius" role="Emperor" focusdate="138-161 BCE">
...
</person>
<person name="Marcus Aurelius" role="Emperor">
...
</person>
<person name="Commodus" role="Emperor" focusdate="180-192 BCE">
...
</person>
<person name="Septimus Severus" role="Emperor" focusdate="193- BCE">
...
</person>
<person name="Diocletian" role="Emperor" focusdate="284-305 BCE">
...
</person>
<person name="Constantine" role="Emperor" focusdate="312-337 BCE">
...
</person>
<person name="Theodosius I" role="Emperor" focusdate="379-395 BCE">
...
</person>
</period>
</history>

HTML output

Now here’s the HTML output of this example.

HTML output

Want to learn more?

There’s lots to learn about using XSLT in the Modern Web — I started this series as I am interested in the idea that older website technology can still be relevant many years beyond its inception. XML technology still has many uses!

--

--

XSLT for the Modern Web
XSLT for the Modern Web

Written by XSLT for the Modern Web

Re-learning XSLT in the context of the Modern Web - occassional writer, reader, Digital Humanities enthusiast All text is 100% non-AI - learn, explore, reuse

No responses yet