How to get multiple views (html) using XSLT on 1 XML file - html

I have 1 big XML file with all data I need.
What I need is something like this:
1 page = Overview. on this page a table is shown. Each row starts with a hyperlink to a detail page.
I am looking for a way to do it with XML, XSLT and HTML only. No server side processing.
Any way to achieve this?
Right now the XML has the XSLT to use for the overview specified in the header:
<?xml-stylesheet type="text/xsl" href="overview.xslt"?>
If I cannot do it with multiple XSLT files, is there a way to read the querystring from the url in XSLT?

I see two options:
Let the XSLT create one big HTML and hide all the sections except the one that you'd like to show.
Use JavaScript to initiate different transforms, replacing the body of the current HTML with the body of the HTML that was returned by the transformation. You could either use one stylesheet that takes a different route depending on the value of a <xsl:parameter> that needs to be supplied for every transform, or you use different stylesheets.
Let's assume you have the following XML (lets call it text.xml):
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<myXml>
<chapter id="c1">
<heading>Heading 1</heading>
<content>This is text of chapter one.</content>
</chapter>
<chapter id="c2">
<heading>Heading 2</heading>
<content>This is text of chapter two.</content>
</chapter>
</myXml>
Then, for suggestion 1, you could do:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
<title></title>
<script type="text/javascript">
function showView(id) {
document.getElementById("dynamicStyle").innerHTML = "#" + id + "{display:inline}";
}
</script>
<style type="text/css">
.view {display:none};
</style>
<style type="text/css" id="dynamicStyle">
#overview{display:inline}
</style>
</head>
<body>
<div class="view overview" id="overview">
<h1>Overview</h1>
<xsl:apply-templates select="myXml/chapter" mode="overview"/>
</div>
<xsl:apply-templates select="myXml/chapter" mode="detail"/>
</body>
</html>
</xsl:template>
<xsl:template match="chapter" mode="overview">
<div><xsl:value-of select="heading"/></div>
</xsl:template>
<xsl:template match="chapter" mode="detail">
<div class="view detail" id="{#id}">
<div>Back to overview</div>
<xsl:apply-templates mode="detail"/>
</div>
</xsl:template>
<xsl:template match="heading" mode="detail">
<h1><xsl:value-of select="."/></h1>
</xsl:template>
<xsl:template match="content" mode="detail">
<div><xsl:value-of select="."/></div>
</xsl:template>
</xsl:stylesheet>
The key is creating separate divs for each view and toggling between them by letting JavaScript change the CSS.
Method 2 might look like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:param name="view" select="'overview'"/>
<xsl:template match="/">
<html>
<head>
<title></title>
<script type="text/javascript">
function showView(id) {
document.documentElement.replaceChild(transform(id).body, document.body);
}
function loadXML(fileName,mime) {
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open("GET",fileName,false);
if(mime) xmlHttpRequest.overrideMimeType(mime);
xmlHttpRequest.send("");
return xmlHttpRequest.responseXML;
}
function transform(view) {
var xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(loadXML('test.xsl','application/xslt+xml'));
xsltProcessor.setParameter(null,'view',view);
return xsltProcessor.transformToDocument(loadXML('test.xml'),document);
}
</script>
</head>
<body>
<xsl:choose>
<xsl:when test="$view = 'overview'">
<div>
<h1>Overview</h1>
<xsl:apply-templates select="myXml/chapter" mode="overview"/>
</div>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="myXml/chapter[#id = $view]" mode="detail"/>
</xsl:otherwise>
</xsl:choose>
</body>
</html>
</xsl:template>
<xsl:template match="chapter" mode="overview">
<div><xsl:value-of select="heading"/></div>
</xsl:template>
<xsl:template match="chapter" mode="detail">
<div>
<div>Back to overview</div>
<xsl:apply-templates mode="detail"/>
</div>
</xsl:template>
<xsl:template match="heading" mode="detail">
<h1><xsl:value-of select="."/></h1>
</xsl:template>
<xsl:template match="content" mode="detail">
<div><xsl:value-of select="."/></div>
</xsl:template>
</xsl:stylesheet>
The key here is loading the stylesheet and the XML using JavaScript and using the JavaScript object XSLTProcessor to do a transform, then replace the body of the document. In this example, I use one stylesheet with different parameters, but you could also load different stylesheet. You'd have to adjust the transform() function accordingly, replacing test.xsl with a variable that needs to be supplied somehow.

Related

call-template returns "Supplied element name is a zero-length string"

I am preparing to modularize my code since I need to build a XHTML doc about 2000 lines of code. If I run the "value-of" directly in the last template I get out the correct result. It fails when running "call-template".
JSON:
<data>
{
"main": {
"head-content": "myTitle2",
"body-content": {
"Pencils": 43
}
}
}
</data>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
expand-text="yes"
>
<xsl:output method="xml" indent="yes"/>
<!-- Block all data that has no user defined template -->
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)/*"/>
</xsl:template>
<!-- Module: Body content -->
<xsl:template name="body" match="*[#key='body-content']//*[#key and not(*)]">
<xsl:element name="{#key}">{.}</xsl:element>
</xsl:template>
<!-- Module: Head content -->
<xsl:template name="head" match="*[#key='head']//*[#key and not(*)]">
<xsl:element name="{#key}">{.}</xsl:element>
</xsl:template>
<xsl:template match="*:map[not(#key)]">
<head>
<title>
<!-- Works -->
<!-- <xsl:value-of select="//*[#key='head-content']"/> -->
<!-- Does not work -->
<xsl:call-template name="head"/>
</title>
</head>
<body>
<!-- Works -->
<!-- <xsl:value-of select="//*[#key='body-content']"/> -->
<!-- Does not work -->
<xsl:call-template name="body"/>
</body>
</xsl:template>
</xsl:transform>
Result
Blank, with errror:
Error on line 27 column 34 of principal.xsl: XTDE0820 Supplied element name is a zero-length string
Wanted result:
<?xml version="1.0" encoding="UTF-8"?>
<head>
<title>myTitle2</title>
</head>
<body>43</body>

How to disable text escape on xsl:copy-of

I build a mechanism that takes all the <script /> tags and put them at the end of the page.
It works good except that Ampersant & characters are encode to & even those in JavaScript code which is not what I want.
How can I solve this?
XML
<?xml version="1.0" encoding="UTF-8"?>
<root></root>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="*">
<xsl:variable name="body">
<xsl:apply-templates select="." mode="body"></xsl:apply-templates>
</xsl:variable>
<xsl:apply-templates select="$body" mode="no-script" />
<xsl:copy-of select="$body//script" xpath-default-namespace="http://www.w3.org/1999/xhtml" />
</xsl:template>
<xsl:template match="script" mode="no-script">
</xsl:template>
<xsl:template match="*[not(self::script)] | #* |comment()" mode="no-script">
<xsl:if test="name() != 'script'">
<xsl:copy xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:apply-templates select="node()[not(self::script)] | #*" mode="no-script" />
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="body">
<script type="text/javascript">
// Ampersand <xsl:text disable-output-escaping="yes"><![CDATA[&]]></xsl:text>
var a = 'a';
</script>
<div>Hello World</div>
</xsl:template>
</xsl:stylesheet>
It outputs:
<div xmlns="http://www.w3.org/1999/xhtml">Hello World</div>
<script xmlns="http://www.w3.org/1999/xhtml" type="text/javascript">
// Ampersand &
var a = 'a';
</script>
I tried and it works but I wunder if there is a way to keep the <script> tags inside the variable $body.
<script type="text/javascript">
<xsl:value-of select="$body//script" xpath-default-namespace="http://www.w3.org/1999/xhtml" disable-output-escaping='yes'/>
</script>
The HTML output method in XSLT 2.0+ should not perform escaping for the text within a script element.
However, there's a difference between 2.0 and 3.0. In 2.0, this only applies to a script element in no namespace. In 3.0, provided you're outputting HTML5, it also applies to a script element in the XHTML namespace.

for-each-group text paragraph - xslt 2.0

I am looking for a solution to group text based on the title h1. I tried this with for-each-group, starts-with ="h1". The problem is that the h1 is not on the same level as the rest of the elements (div/h1).
Input html:
<!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>test</title>
</head>
<body>
<div>
<h1><b>TRAIN</b></h1>
</div>
<p>text</p>
<p>In this field there is text</p>
<div>
<h1><b>nr1</b><b>CAR</b></h1>
</div>
<h2><b>1.</b><b>nr2</b><b>area</b></h2>
<p>infos about cars</p>
<p><b>more and</b>more infos about cars</p>
</body>
</html>
What I have so far is:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:output omit-xml-declaration="yes" method="xhtml" version="1.0" encoding="UTF-8"
indent="yes"/>
<xsl:template match="head"/>
<xsl:template match="body">
<xsl:for-each-group select = "*" group-starting-with = "h1">
<output>
<xsl:apply-templates select="current-group()"/>
</output>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
But the output is not working the way I want. I would like to have two output-blocks as this example output:
<html>
<output>
<div><h1><b>TRAIN</b></h1></div>
<p>text</p>
<p>In this field there is text</p>
</output>
<output>
<div><h1><b>nr1</b><b>CAR</b></h1></div>
<h2>
<b>1.</b>
<b>nr2</b>
<b>area</b>
</h2>
<p>infos about cars</p>
<p><b>more and</b>more infos about cars</p>
</output>
Thanks for any help!
You could use the descendant-or-self axis, to group starting on elements which have h1 as a descendant (or are h1 elements themselves)
<xsl:for-each-group select="*" group-starting-with="*[descendant-or-self::h1]">
Also note that in your XSLT you have used xpath-default-namespace, but your input XML does not use that namespace, so as it stands your body template in your XSLT won't match the input. Either you need to add the default namespace to your input, or remove the xpath-default-namespace from your XSLT.
How about:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/html">
<xsl:copy>
<xsl:for-each-group select="body/*" group-starting-with="div[h1]">
<output>
<xsl:copy-of select="current-group()"/>
</output>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT handling text with tag in between

I have the following XML: <a>Text with <b>stuff</b> here</a>
With my code:
<xsl:template match="*[local-name() = 'a'][namespace-uri()=namespace-uri(.)]">
<xsl:value-of select="normalize-space(text()) "/><xsl:text> </xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[local-name() = 'b'][namespace-uri()=namespace-uri(.)]">
<xsl:value-of select="normalize-space(text())"/><xsl:text> </xsl:text>
<xsl:apply-templates/>
</xsl:template>
I only get the result:
Text with stuff
What I want is:
Text with stuff here.
So how do I handle the remaining text after the <b/> element?
Why is this so complicated? If this is really your XML input:
<a>Text with <b>stuff</b> here</a>
then the following stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:value-of select="a" />
</xsl:template>
</xsl:stylesheet>
will return the requested* result:
Text with stuff here
--
(*) except for the period at the end, which is not present in the input.

Vertical output XSL and XML

I am working on a simple dictionary in XML, and now I'm trying to output some words vertical, but they all come out on a line without spaces.
This is some of the XML file
<thesaurus>
<dictionary>
<language>English</language>
<word type="1">word 1</word>
<word type="2">word 2</word>
<word type="3">word 3</word>
<word type="4">word 4</word>
<word type="5">word 5</word>
<word type="6">word 6</word>
</dictionary>
</thesaurus>
This is my first "almost" solution
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="//word">
<xsl:sort order="ascending"/>
</xsl:apply-templates>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
That solution only prints out all the word like this
AgentsColorFoundationsGrainPartialPogotypePretendSilentStrollTender
My second try is something like this
<xsl:for-each select="thesaurus">
<h1> <xsl:value-of select="//word"/></h1>
</xsl:for-each>
In that way I could style the words and they will print vertical, but the thing is that only the first of the words is printing. =/
Would be great with a hint :)
Thanks
Use this template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="*/*/word">
<xsl:sort order="ascending"/>
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="word">
<xsl:value-of select="."/>
<br/>
</xsl:template>
</xsl:stylesheet>
Output:
<html>
<body>word 1<br />word 2<br />word 3<br />word 4<br />word 5<br />word 6<br /></body>
</html>