master stylesheet sharing in XSLT - xslt

i would like to create a master template in XSLT, which could be stored in a separate file. Every other Page stylesheets share it, with xsl:import.
master.xslt
<xsl:template match="Page">
<html>
<head>
</head>
<body>
<call-template name="Content"/>
</body>
</html>
</xsl:template>
<xsl:stylesheet>
page.xslt
<xsl:stylesheet>
<xsl:import href="master.xslt"/>
<xsl:template match="/">
<apply-templates match="Page"/>
</xsl:template>
<xsl:template name="Content">
... apply something page-specific
</xsl:template>
</xsl:stylesheet>
page.xml
<Page>
... something page-specific
</Page>
Can i improve this solution?
i cannot start from master stylesheet, because i will need xsl:import everything.
i dont want master.xslt contain references on each particular page.
Another decision (which is against the xslt spirit) maybe such:
master.xslt
<xsl:template name="masterHead">
<html>
<head>
</head>
<body>
</xsl:template>
<xsl:template name=masterEnd>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
page.xslt
<xsl:stylesheet>
<xsl:import href="master.xslt"/>
<xsl:template match="/">
<call-template name=masterHead>
... apply something page-specific
<call-template name=masterEnd/>
</xsl:template>
</xsl:stylesheet>
we don't need any general root <Page> element.

Using <xsl:import> is the right design decision. This is exactly the main use-case this XSLT directive was intended for.
One can go further even more -- lookup for the <xsl:apply-imports> directive, and in addition to how an imported stylesheet can apply templates about whose actions and meaning it absolutely doesn't know anything. The latter is called Higher-Order-Functions and is implemented in XSLT with the FXSL library (written entirely in XSLT).

That looks about right to me... very common to what I have used in the past (although I've often used <xsl:include/>, but either should work). The main change I might make is to make the match more explicit (at least in the master xslt) - i.e.
<xsl:template match="/Page"> <!-- leading slash -->
so it won't accidentally match Page elements at other locations (for example, data-paging, like <Page Index="3" Size="20"/>).
One other common thing I do is to add a "*" match that uses xsl:message to throw an error if I don't have a more-specific match for a node. This makes it more obvious when you have a typo, etc.

I'm actually glad to have found this example as I've been looking for verification that this is actually the correct approach to a master/slave template setup.
However the examples provided did not work out of the box on tomcat - so just to help others who only knows how to copy paste here are a working tomcat set of master / slave files.
Master.xsl :
<?xml version="1.0" encoding="iso-8859-1" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="iso-8859-15" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" indent="no"/>
<!-- http://stackoverflow.com/questions/646878/master-stylesheet-sharing-in-xslt -->
<xsl:template match="ms247">
<html>
<head>
<title>test</title>
</head>
<body>
<div style="border: 1px solid black; width: 200px; float: left; margin: 10px; padding: 5px;">
<xsl:call-template name="left"/>
</div>
<div style="border: 1px solid black; width: 200px; float: left; margin: 10px; padding: 5px;">
<xsl:call-template name="content"/>
</div>
<div style="border: 1px solid black; width: 200px; float: left; margin: 10px; padding: 5px;">
<xsl:call-template name="right"/>
</div>
</body>
</html>
</xsl:template>
<xsl:template name="content">
<span style="color: red">Content template is empty - overrule in page template.</span>
</xsl:template>
<xsl:template name="left">
<span style="color: red">Left template is empty - overrule in page template.</span>
</xsl:template>
<xsl:template name="right">
<span style="color: red">Right template is empty - overrule in page template.</span>
</xsl:template>
</xsl:stylesheet>
And slave.xsl:
<?xml version="1.0" encoding="iso-8859-1" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="master.xsl"/>
<xsl:template name="content">
... apply something page-specific
</xsl:template>
<xsl:template name="right">
And we have RIGHT content!
<!-- Execute matching template which is NOT triggered automatically -->
<xsl:apply-templates select="params/param"/>
</xsl:template>
<!-- And we do not define any left template -->
<!-- Example -->
<xsl:template match="ms247/params/param">
Paramters on page: <xsl:value-of select="#name"/><br/>
</xsl:template>
</xsl:stylesheet>
Hope this can help others - do not be shy to drop me a note.

Related

How do I excluding nodes from XLST

Sorry, this is a really novice question. My real problem involves translating HTML to Open Office XML, but this illustrates the issue I am seeing. I want to make sure the "b" node is ignored in my processing, i.e. not get the 123 at the end of the results output.
XML:
<?xml version="1.0"?><?xml-stylesheet type="text/xsl"?>
<a>
<hello-world>
<greeter>An XSLT Programmer</greeter>
<greeting>Hello, World!</greeting>
</hello-world>
<b>123
</b>
</a>
XSLT:
<?xml version="1.0"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="hello-world">
<HTML><HEAD><TITLE></TITLE></HEAD><BODY><H1>
<xsl:value-of select="greeting"/>
</H1>
<xsl:apply-templates select="greeter"/>
</BODY></HTML>
</xsl:template>
<xsl:template match="greeter">
<DIV>from <I><xsl:value-of select="."/></I></DIV>
</xsl:template>
</xsl:stylesheet>
Results:
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<TITLE></TITLE>
</HEAD>
<BODY>
<H1>Hello, World!</H1>
<DIV>from <I>An XSLT Programmer</I></DIV>
</BODY>
</HTML>
123
I see you added an answer, but thought I could add more information.
Your first attempt was good, you could have simply added another template to ignore the "b" nodes.
<xsl:template match="b"/>
What was happening is that the XSLT built-in template rules include by default a template that copies the text of any node that's not explicitely matched by your templates.
Reference: docstore.mik.ua/orelly/xml/xmlnut/ch08_07.htm
See the output of your transformation with the added template: https://xsltfiddle.liberty-development.net/aixRus
The issue was that I wasn't selecting the whole document to begin with. If I change the xslt as follows, it then works
<?xml version="1.0"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/*">
<xsl:apply-templates select='hello-world'/>
</xsl:template>
<xsl:template match="hello-world">
<HTML><HEAD><TITLE></TITLE></HEAD><BODY><H1>
<xsl:value-of select="greeting"/>
</H1>
<xsl:apply-templates select="greeter"/>
</BODY></HTML>
</xsl:template>
<xsl:template match="greeter">
<DIV>from <I><xsl:value-of select="."/></I></DIV>
</xsl:template>
</xsl:stylesheet>

Get the name of the Grandparent of a node

I'm trying to transform an XML file and trying to test if a node's grandparent has a certain name but i don't know how to get to the grandparent name. this is the example I'm working on. what i tried to do is to get the parent and then find the parent of the parent but it didn't work.
thanks.
<?xml version="1.0" encoding="UTF-8"?>
<breakfast_menu>
<food_XX>
<food_X>
<food>
<name>Belgian Waffles</name>
</food>
</food_X>
</food_XX>
<food_XX1>
<food_X>
<food>
<name>Strawberry Belgian Waffles</name>
</food>
</food_X>
</food_XX1>
</breakfast_menu>
and this is the xsl code:
<?xml version="1.0" encoding="UTF-8"?>
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<xsl:for-each select=".//food">
<xsl:variable name="parentName" select="parent::food_X" />
<xsl:value-of select="name($parentName)" />
<xsl:if test="name(parent::$parentName)='food_XX1'">
<div style="background-color:teal;color:white;padding:4px">
<span style="font-weight:bold"><xsl:value-of select="name"/> - </span>
</div>
</xsl:if>
</xsl:for-each>
</body>
</html>
Even better, use template rules.
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<xsl:apply-templates select=".//food">
</body>
</html>
</xsl:template>
<xsl:template match="food"/>
<xsl:template match="food_XX1/*/food">
<div style="background-color:teal;color:white;padding:4px">
<span style="font-weight:bold"><xsl:value-of select="name"/> - </span>
</div>
</xsl:template>
</xsl:transform>
To find the parent name, you can just do this...
<xsl:if test="name(../..)='food_XX1'">
And for grandparents....
<xsl:if test="name(../..)='food_XX1'">
Note that, given your current XSLT, if the intention is just to output food nodes under food_XX1 you could re-write it as this....
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<xsl:for-each select=".//food_XX1/*/food">
<div style="background-color:teal;color:white;padding:4px">
<span style="font-weight:bold"><xsl:value-of select="name"/> - </span>
</div>
</xsl:for-each>
</body>
</html>
The parent:: axis selects the parent of the context node.
In your example, the context node is the food element being processed inside the xsl:for-each. parent::food_X is the equivalent of ./parent::food_X
When you selected parent::food_X, it is attempting to select an element named food_X that is the parent of the context node (in this case, it is the food element). Then, when attempting to select the grandparent with parent::$parentName, was essentially saying, "give me the parent of this food element if it is a food_X element", rather than asking for the parent of food_X.
In order to find the parent of the $parentName, you need to use a step from $parentName, so that it looks for the parent of $parentName, and not the parent of the food element:
<xsl:if test="name($parentName/parent::*)='food_XX1'">
You could simplify your test further. Rather than selecting any parent element and then testing it's name, select the parent element food_XXX1:
<xsl:if test="$parentName/parent::food_XX1">
Even more simple, you could eliminate the $parentName variable from your XSLT and just use this:
<xsl:if test="parent::food_X/parent::food_XX1">

using XSLT to display image series with captions in XHTML

I'm trying to display a series of images each with its own caption using XSLT. I've coded the images and the captions by nesting <img> and then <figcaption> within but the resultant html does not display as intended (the captions are not lining up with corresponding images). Is there a way to nest <xsl: for-each> for the captions within the images? Here's the XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="html"/>
<xsl:template match="letter">
<html>
<head>
<style type="text/css">
#wrapper {min-height: 100%;}
#figcaption {
text-align: left;
}
#main {
padding-top: 15px;;
width: 1200px;
}
</style>
</head>
<body>
<div id="wrapper">
<div id="images">
<figure>
<xsl:if test="image">
<xsl:for-each select="image/#xlink:href">
<img>
<xsl:attribute name="src">
<xsl:value-of select="."/>
</xsl:attribute>
</img>
</xsl:for-each>
</xsl:if>
<xsl:if test="image/#label">
<xsl:for-each select="image/#label">
<figcaption><xsl:value-of select="."/></figcaption>
</xsl:for-each>
</xsl:if>
</figure>
</div>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Here's the corresponding XML:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="XSLT.xsl"?>
<letter xmlns:xlink="http://www.w3.org/1999/xlink">
<image label="page 1" xlink:href="http://tinyurl.com/nu7zmhc"/>
<image label="page 2" xlink:href="http://tinyurl.com/pysyztr"/>
<title>Letter from Shawn Schuyler</title>
<date>1963-06-30</date>
<language>English</language>
<creator>
<firstName>William</firstName>
<lastName>Schultz</lastName>
<street>Unites States Disciplinary Barracks</street>
<city>Fort Leavenworth</city>
<state abbr="KS">Kansas</state>
</creator>
</letter>
My desired output in html is basically this for each image:
<figure>
<img src='image.jpg'/>
<figcaption>Caption</figcaption>
</figure>
Or simply:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink"
exclude-result-prefixes="xlink">
<xsl:template match="/letter">
<html>
<head>
<style type="text/css">
#wrapper {min-height: 100%;}
#figcaption {
text-align: left;
}
#main {
padding-top: 15px;;
width: 1200px;
}
</style>
</head>
<body>
<div id="wrapper">
<div id="images">
<xsl:for-each select="image">
<figure>
<img src='{#xlink:href}'/>
<figcaption>
<xsl:value-of select="#label"/>
</figcaption>
</figure>
</xsl:for-each>
</div>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Note:
There's nothing wrong with using xsl:for-each, especially in a
simple case like this;
There is something wrong with using xsl:element when you can use a literal result element. And while XSLT is naturally verbose, using the attribute value template can reduce the code (quite significanltly, as you can see in this case).
Try this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="html" indent="yes" />
<xsl:template match="letter">
<html>
<head>
<style type="text/css">
#wrapper {min-height: 100%;}
#figcaption {
text-align: left;
}
#main {
padding-top: 15px;;
width: 1200px;
}
</style>
</head>
<body>
<div id="wrapper">
<div id="images">
<xsl:apply-templates select="./image"></xsl:apply-templates>
</div>
</div>
</body>
</html>
</xsl:template>
<xsl:template match="letter/image">
<xsl:element name="figure">
<xsl:element name="img">
<xsl:attribute name="src">
<xsl:value-of select="./#xlink:href"/>
</xsl:attribute>
</xsl:element>
<xsl:apply-templates select="./#label"></xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="letter/image/#label">
<xsl:element name="figcaption">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
xsl:apply-templates says where anything matching the pattern specified in select should be put (with the dot showing the current element's context).
xsl:template is matched against the source document based on the path given in match. Any hits are processed in parallel, then later stitched together based on where the apply-templates elements indicate.
NB: depending on your XSLT engine having output="html" may have different effects on your img element. In HTML5 the img element is defined as not requiring a close tag (or being self-closing), so the engine won't close that tag. Arguments about whether that inconsistency is a good choice or not can be found throughout the net.
Ref: Are (non-void) self-closing tags valid in HTML5?
A good article on this alternate approach to for-each can be found here: http://gregbee.ch/blog/using-xsl-for-each-is-almost-always-wrong
You'll find with XLST that once the concept of a template clicks your code will become way shorter are simpler to maintain.

Get the title of html document using xslt

I am unable to get the value of head/title element as follow.
xslt:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:h="http://www.w3.org/1999/xhtml"
version="2.0" >
<xsl:output method="text" indent="yes"/>
<xsl:template match="h:title">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
.AlignLeft { text-align: left; }
.AlignCenter { text-align: center; }
.AlignRight { text-align: right; }
</style>
<title>Doc title</title>
</head>
<body>
body
</body>
</html>
java -jar saxon9he.jar -xsl:style.xsl -s:test.html
output:
.AlignLeft { text-align: left; }
.AlignCenter { text-align: center; }
.AlignRight { text-align: right; }
Doc title
body
expected output:
Doc title
The behaviour you are experiencing is because of XSLT's built-in template rules. These are apply when XSLT is looking for a template to match a node, but one doesn't appear in your XSLT.
At the moment, the XSLT will look for templates matching style and body but find none, and so the built-in templates apply and will ultimately output text.
What you could do, is explicitly tell XSLT to select only the title like so:
<xsl:template match="/">
<xsl:apply-templates select="/h:html/h:head/h:title"/>
</xsl:template>
Alternatively, add a template that overrides the built-in template that stops any text being output:
<xsl:template match="text()" />
Adding either of these templates to your existing XSLT should work.

Modify "on the fly" the portal column content tag's class

I need to modify "on the fly" a class name in portal-column-content tag, this is the html code rendered:
<div id="portal-column-content" class="cell width-9 position-1:4">
I want to replace only "width-9" with "width-12".
Any advice?
Thank's
Vito
Since you ask for advise, here is some:
Do not use a css class to signify anything concrete, let it signify intent.
The concrete implementation of the intent comes in the css. For instance, do not create a class named width-9, rather create one named portal-column-content. You can then make portal-column-content be width:9px, width:12em or whatever.
Doing a string-replace like this is not really a thing you would do with xslt.
Even though you could. Depending on your setup there are other, better ways.
If you can't/won't follow any of the above advise, try
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#class">
<xsl:attribute name="class">
<xsl:value-of select="substring-before(.,'width-9')"/>width-12<xsl:value-of select="substring-after(.,'width-9')"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div[#id='portal-column-content']/#class">
<xsl:attribute name="class">
<xsl:value-of select=
"substring-before(concat(.,'width-9'), 'width-9')"/>
<xsl:value-of select="'width-12'"/>
<xsl:value-of select="substring-after(., 'width-9')"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when applied on the following sample XML document:
<html>
<div id="a" class="a"/>
<div id="b" class="b"/>
<div id="c" class="cell width-9 position-1:4"/>
<div id="portal-column-content" class="cell width-9 position-1:4"/>
<div id="d" class="d"/>
<div id="e" class="cell width-9 position-1:4"/>
</html>
produces the wanted, correct result (only replaced is the 'width-9' substring of the class attribute of any div that has id attribute with string value 'portal-column-content':
<html>
<div id="a" class="a"></div>
<div id="b" class="b"></div>
<div id="c" class="cell width-9 position-1:4"></div>
<div id="portal-column-content" class="cell width-12 position-1:4"></div>
<div id="d" class="d"></div>
<div id="e" class="cell width-9 position-1:4"></div>
</html>
Do note:
Only replaced is the 'width-9' substring of the class attribute of any div that has id attribute with string value 'portal-column-content'. Other div elements that have a different id attribute aren't affected.
The transformation correctly works with class attributes, whose string value doesn't contain 'width-9' -- compare with the other answer, whose XSLT solution in such case completely replaces the string value of the class attribute with 'width-12'.
Since you've tagged this with "diazo", the simplest solution is probably to use diazo rules. Just use a replace, before or after rule to copy content children of #portal-column-content to the theme children of a correctly classed #portal-column-content.
Use an "if" expression if it's to be done selectively.