xsl:sort with apply-templates not sorting - xslt

I have quite a large XSL document for an assignment that does a number of things. It is nearly complete but I missed a requirement that it has to be sorted and I cannot get it working. Here is a SSCCE of what is happening.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Root Document -->
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="staff">
<xsl:sort select="member/last_name" />
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="member">
<xsl:value-of select="first_name" /> <xsl:value-of select="last_name" /> <br/>
</xsl:template>
</xsl:stylesheet>
The XML file looks like this
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="sort.xsl"?>
<staff>
<member>
<first_name>Joe</first_name>
<last_name>Blogs</last_name>
</member>
<member>
<first_name>John</first_name>
<last_name>Smith</last_name>
</member>
<member>
<first_name>Steven</first_name>
<last_name>Adams</last_name>
</member>
</staff>
I was expecting the staff members to be listed by last name but they are not getting sorted. Please bear in mind that I am very inexperienced at XSLT.

<xsl:apply-templates select="staff">
<xsl:sort select="member/last_name" />
</xsl:apply-templates>
selects the staff elements and sorts them, but there is only one staff element, so this is a no-op.
Change to
<xsl:apply-templates select="staff/member">
<xsl:sort select="last_name" />
</xsl:apply-templates>
then that selects all the member elements and sorts them.

what is missing is a staff matching template or change the matching template to member like in this one:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Root Document -->
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="staff/member">
<xsl:sort select="last_name" />
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="member">
<xsl:value-of select="first_name" /> <xsl:value-of select="last_name" /> <br/>
</xsl:template>
</xsl:stylesheet>

Related

xslt: trying to understand conditional inside value-of

I'm expecting only Hola to appear:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="helloworld.xslt"?>
<greetings>
<greeting id="1">
Hello World!
</greeting>
<greeting id="2">
Hola!
</greeting>
</greetings>
However, both greetings appear.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html"/>
<xsl:template match="greetings">
<xsl:apply-templates select="greeting"/>
</xsl:template>
<xsl:template match="greeting">
<html>
<body>
<h1>
<xsl:value-of select="#id[.>1]"/>
</h1>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
It is not so much the conditional that is causing a problem, but the fact your statement is selecting the attribute, and so the xsl:value-of will output the attribute (but only if the value is greater than 1)
What you need to do is move the conditional to your xsl:apply-templates, and then do <xsl:value-of select="." /> to get your "Hola" value
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="greetings">
<xsl:apply-templates select="greeting[#id > 1]"/>
</xsl:template>
<xsl:template match="greeting">
<html>
<body>
<h1>
<xsl:value-of select="."/>
</h1>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Using XPATHs from file and adding relevant attribute

May be my XSL approach is wrong? please correct me the way to handle this situation
I want to grab XPATHs and Attrs from a mapping file, then use XPATH to match, and apply attributes to XML.
Here is my 3 inputs files:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map xpath="//title" class="title" others="moreToCome" />
<map xpath="//subtitle" class="subtitle" others="moreToCome" />
<map xpath="//p" class="p" others="moreToCome" />
</mappings>
Source.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title>title text</title>
<subtitle>subtitle text</subtitle>
<p>subtitle text</p>
</root>
StyleMapping.xsl
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="fMappings" select="document('mappings.xml')" />
<xsl:variable name="xpath"><xsl:text>justToMakeItGlobal</xsl:text></xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
<!-- loop thru map in mappings.xml -->
<xsl:for-each select="$fMappings//mappings/map">
<xsl:call-template name="dyn">
<xsl:with-param name="xpath" select="#xpath" />
<xsl:with-param name="class" select="#class" />
<xsl:with-param name="others" select="#others" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="$xpath" mode="dyn">
<xsl:param name="xpath"/>
<xsl:param name="class"/>
<xsl:param name="others"/>
<xsl:attribute name="class"><xsl:value-of select="$class" /></xsl:attribute>
<xsl:attribute name="others"><xsl:value-of select="$others" /></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is what is planning to do, but i'm not getting the correct way of doing it in XSLT:
1. Read mappings.xml file
2. Loop thru each map tag
3. grab xpath and attr's
4. apply template match/select with above xpath
5. add attr's to above selected nodes
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title class="title" others="moreToCome">title text</title>
<subtitle class="subtitle" others="moreToCome">subtitle text</subtitle>
<p class="p" others="moreToCome">subtitle text</p>
</root>
I don't think you can have a template with a calculated match pattern. Let me suggest a different approach:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map elem="title" class="Title" others="moreToCome1" />
<map elem="subtitle" class="Subtitle" others="moreToCome2" />
<map elem="p" class="P" others="moreToCome3" />
</mappings>
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="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:variable name="mappings" select="document('mappings.xml')/mappings" />
<xsl:template match="*">
<xsl:variable name="elem" select="name()" />
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="$elem = $mappings/map/#elem">
<xsl:attribute name="class">
<xsl:value-of select="$mappings/map[#elem=$elem]/#class"/>
</xsl:attribute>
<xsl:attribute name="others">
<xsl:value-of select="$mappings/map[#elem=$elem]/#others"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Testing with the following source XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title original="yes">title text</title>
<subtitle>subtitle text</subtitle>
<p>para text</p>
<nomatch attr="test">another text</nomatch>
</root>
results in:
<?xml version="1.0" encoding="utf-8"?>
<root>
<title original="yes" class="Title" others="moreToCome1">title text</title>
<subtitle class="Subtitle" others="moreToCome2">subtitle text</subtitle>
<p class="P" others="moreToCome3">para text</p>
<nomatch attr="test">another text</nomatch>
</root>

How to return text of node without child nodes text

I have xml like:
<item id="1">
<items>
<item id="2">Text2</item>
<item id="3">Text3</item>
</items>Text1
</item>
How to return text of <item id="1">('Text1')?
<xsl:value-of select="item/text()"/> returns nothing.
My XSLT is:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="item"/>
</body>
</html>
</xsl:template>
<xsl:template match="item">
<xsl:value-of select="text()"/>
</xsl:template>
</xsl:stylesheet>
I dont know what else to type to commit my edits
How to return text of <item id="1">('Text1')? <xsl:value-of
select="item/text()"/> returns nothing.
The item element has more than one text-node children and the first of them happens to be a all-whitespace one -- this is why you get "nothing".
One way to test if the string value of a node isn't all-whitespace is by using the normalize-space() function.
In a single Xpath expression, you want this:
/*/text()[normalize-space()][1]
Here is a complete transformation the result of which is the desired text node:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:copy-of select="text()[normalize-space()][1]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<item id="1">
<items>
<item id="2">Text2</item>
<item id="3">Text3</item>
</items>Text1
</item>
the wanted, correct result is produced:
Text1
This should generally work:
<xsl:apply-templates select="item/text()" />
Incorporated into your XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item_key" match="item" use="."/>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<html>
<body>
<ul>
<xsl:apply-templates select="item"/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="item">
<li>
<xsl:apply-templates select="text()"/>
</li>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, the result is:
<html>
<body>
<ul>
<li>Text1
</li>
</ul>
</body>
</html>
Alternatively, this should work as well:
<xsl:copy-of select="item/text()" />

Empty/blank namespace declarations being generated within result-document

I have written a package for converting XMLs to ePubs. Everything works fine, except some cases, where blank namespace (xmlns="") nodes are being written to the result-documents. Prior to transformation, I prepared temporary variables for holding main-segments(i.e., meta, body etc.) and finally copied the nodes(using xsl:copy-of[#copy-namespaces='no'] instruction) to result-document. I also have used #exclude-result-prefixes='ns_list_sep_by_space' within xsl:transform element and still not able to get desired result.
oXygen IDE shows a message in pop-up saying:
When using xsl:copy-of the new elements will also have namespace nodes copied from the original element node, unless they are excluded by specifying copy-namespaces="no". If this attribute is omitted, or takes the value yes, then all the namespace nodes of the original element are copied to the new element. If it takes the value no, then none of the namespace nodes are copied: however, namespace nodes will still be created in the result tree as required by the namespace fixup process.
Here is some more details of my problem:
Main stylesheet:
main.xsl:main caller
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:cylian="local-ns-for-extension-functions"
exclude-result-prefixes="xs xd cylian"
version="2.0">
<xsl:import href="modules/core.xsl"/>
<xsl:variable name="base" select="base-uri()" as="xs:anyURI"/>
<xsl:template match="/">
<xsl:call-template name="procA"/>
</xsl:template>
</xsl:transform>
Main stylesheet:
core.xsl: core processing unit
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:cylian="local-ns-for-extension-functions"
exclude-result-prefixes="xs xd cylian"
version="2.0">
<xsl:import href="sub1.xsl"/>
<xsl:import href="sub2.xsl"/>
<!--and more-->
<!-- variable to hold intermediate results for stage1 -->
<xsl:variable name="stage1">
<cylianz>
<xsl:copy-of select="$a" copy-namespaces="no"/>
<xsl:copy-of select="$b" copy-namespaces="no"/>
<!--and more-->
</cylianz>
</xsl:variable>
<!-- variable to hold intermediate results for stage2 -->
<xsl:variable name="stage2">
<cylianz>
<xsl:for-each select="$stage1//cylian">
<xsl:sort select="#pos"/>
<xsl:sequence select="."/>
</xsl:for-each>
</cylianz>
</xsl:variable>
<xsl:template name="procA">
<xsl:for-each select="$stage2//cylian">
<xsl:result-document href="{concat($outdir,#href)}" format="general">
<xsl:call-template name="procB">
<xsl:with-param name="context" select="."/>
<xsl:with-param name="title">
<xsl:value-of select="$book_title"/>
</xsl:with-param>
</xsl:call-template>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template name="procB">
<xsl:param name="context"/>
<xsl:param name="title"/>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<xsl:call-template name="header">
<xsl:with-param name="title" select="$title"/>
</xsl:call-template>
</head>
<body>
<div id="root">
<xsl:apply-templates select="."/>
</div>
</body>
</html>
</xsl:template>
<!--
1/ other rules are shortened for clarity
2/ declaration «xmlns:cylian='local-ns-for-extension-functions'» has to retain, some parts of transformation uses some extension functions from that namespace
-->
</xsl:transform>
and here's the output:
a.html
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta xmlns="" http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title xmlns="">BookTitle</title>
<!--
2012.04.16 - 18:27:36 [XSLT processor: SAXON 9.1.0.5 from Saxonica]
-->
<link xmlns="" href="isbn.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<div id="root">
<div xmlns="" id="a1">
<!--...-->
</div>
</div>
</body>
</html>
I hope it would be easier to understand what's the problem is going on. All suggestions are welcome. Thanks in advance.
Well we need to see your code to be sure but I suspect you have e.g.
<xsl:template match="/">
<foo xmlns="http://example.com/ns">
<xsl:apply-templates/>
</foo>
</xsl:template>
<xsl:template match="whatever">
<bar/>
</xsl:template>
and then you get
<foo xmlns="http://example.com/ns">
<bar xmlns=""/>
</foo>
while you want
<foo xmlns="http://example.com/ns">
<bar/>
</foo>
To fix that make sure you move the default namespace declaration on the xsl:stylesheet element with e.g.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://example.com/ns">
version="1.0">
<xsl:template match="/">
<foo>
<xsl:apply-templates/>
</foo>
</xsl:template>
<xsl:template match="whatever">
<bar/>
</xsl:template>
</xsl:stylesheet>
that way it applies to all result elements created in different templates.
[edit]
Based on the samples you have provided now I think my suggestion is right, only with several files you need to make sure that all stylesheet modules you have put xmlns="http://www.w3.org/1999/xhtml" on the xsl:stylesheet respectively xsl:transform elements so that all result elements end up in the XHTML namespace.
[second edit]
I think you want
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:cylian="local-ns-for-extension-functions"
exclude-result-prefixes="xs xd cylian"
version="2.0">
<xsl:import href="sub1.xsl"/>
<xsl:import href="sub2.xsl"/>
<!--and more-->
<!-- variable to hold intermediate results for stage1 -->
<xsl:variable name="stage1" xmlns="">
<cylianz>
<xsl:copy-of select="$a" copy-namespaces="no"/>
<xsl:copy-of select="$b" copy-namespaces="no"/>
<!--and more-->
</cylianz>
</xsl:variable>
<!-- variable to hold intermediate results for stage2 -->
<xsl:variable name="stage2" xmlns="">
<cylianz>
<xsl:for-each select="$stage1//cylian">
<xsl:sort select="#pos"/>
<xsl:sequence select="."/>
</xsl:for-each>
</cylianz>
</xsl:variable>
<xsl:template name="procA">
<xsl:for-each select="$stage2//cylian">
<xsl:result-document href="{concat($outdir,#href)}" format="general">
<xsl:call-template name="procB">
<xsl:with-param name="context" select="."/>
<xsl:with-param name="title">
<xsl:value-of select="$book_title"/>
</xsl:with-param>
</xsl:call-template>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template name="procB">
<xsl:param name="context"/>
<xsl:param name="title"/>
<html >
<head>
<xsl:call-template name="header">
<xsl:with-param name="title" select="$title"/>
</xsl:call-template>
</head>
<body>
<div id="root">
<xsl:apply-templates select="."/>
</div>
</body>
</html>
</xsl:template>
</xsl:transform>
And then if you have any additional modules supposed to produce XHTML elements make sure you put xmlns="http://www.w3.org/1999/xhtml" on the root element of the module or if you need to create elements in other namespaces as well then on any template supposed to output XHTML.
There are two kinds of "unwanted" namespace declarations that might appear in your output: declarations that are unwanted because they are redundant noise (they declare namespace prefixes that aren't used), and declarations that are unwanted because they put the elements in a different namespace from the one intended.
In the first case, XSLT provides features such as exclude-result-prefixes and copy-namespaces='no' to get rid of the noise.
In the second case (which is where I think you are), the namespace declarations are a symptom of the fact that the stylesheet author created the elements in the wrong namespace in the first place, and the solution is to look at the code that created the elements, and fix it. For example you might have written a literal result element <foo> that creates an element in no namespace, when you intended <foo xmlns="something"/> to create it in some other namespace.
Let's have this XML document:
<x:t xmlns:x="some:x">
<a/>
<b/>
</x:t>
Here is code that is probably similar to yours:
<xsl:stylesheet version="2.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="/*">
<t xmlns="some:x">
<xsl:copy-of select="*" copy-namespaces="no"/>
</t>
</xsl:template>
</xsl:stylesheet>
and it produces this unwanted result:
<t xmlns="some:x">
<a xmlns=""/>
<b xmlns=""/>
</t>
Why is this result produced?
Because you are using <xsl:copy-of> the nodes are copied "as-is", so elements don't change the namespace they are in. The attribute copy-namespaces="no" only specifies that the namespace nodes belonging to this element will be skipped from copying -- not that the element will change its own namespace.
Solution:
When we want to change the namespace an element is in (in this case from "no namespace" to "some:x", we shouldn't copy this element node.
Instead we must create a new element from this one, and specify the new namespace in which the new element should be:
<xsl:stylesheet version="2.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="/*">
<t xmlns="some:x">
<xsl:apply-templates select="*"/>
</t>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}" namespace="some:x">
<xsl:apply-templates select="node() | #*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the wanted, correct result is produced:
<t xmlns="some:x">
<a/>
<b/>
</t>

xsl:element apply attributes

this question relates to
XSLT to generate html tags specified in XML
where I had an xml docment and used an xsl to generate html tags using
<xsl:element name="{Type}" >
the problem I have is that I want to specify some html attributes in my xml for example
<Page>
<ID>Site</ID>
<Object>
<ID>PostCode</ID>
<Type>div</Type>
<Attributes>
<Attribute name="class">TestStyle</Attribute>
<Attribute name="id">TestDiv</Attribute>
</Attributes>
<Class>display-label</Class>
<Value>PostCode</Value>
</Object>
</Page>
So does any one know how I could populate the xsl:element with the two attributes using xsl?
Thanks
Building from the stylesheet that I posted in the previous question, within the element declaration you can iterate through each of the Attributes/Attribute elements and construct the attributes for the element that you are constructing.
You are "standing" on the Object element node inside that for-loop, so you can then iterate over it's Attributes/Attribute elements like this:
<xsl:for-each select="Attributes/Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="current()"/></xsl:attribute>
</xsl:for-each>
Applied to your stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<head>
</head>
<body>
<xsl:for-each select="Page/Object">
<xsl:element name="{Type}" >
<xsl:for-each select="Attributes/Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="current()"/></xsl:attribute>
</xsl:for-each>
<xsl:value-of select="Value"/>
</xsl:element>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This is an alternative way to achieve the same output, but in a little more delcarative fashion, using apply-templates instead of for-each.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<head>
</head>
<body>
<xsl:apply-templates select="Page/Object" />
</body>
</html>
</xsl:template>
<xsl:template match="Object">
<xsl:element name="{Type}" >
<xsl:apply-templates select="Attributes/Attribute" />
<xsl:apply-templates select="Value" />
</xsl:element>
</xsl:template>
<xsl:template match="Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
You need to fix the Attributes element in your source sample, it's not closed.
You can use xsl:for-each or xsl:apply-templates with select="Attributes/Attribute", to invoke an xsl:attribute element that looks a bit like this:
<xsl:attribute name="{#name}"><xsl:value-of select="text()"/></xsl:attribute>
The thing you need to watch out for is that xsl:attribute must come before anything that adds children to the {Type} element.
<xsl:element name="Attribute">
<xsl:attribute name="class">TestStyle</xsl:attribute>
<xsl:attribute name="id">TestDiv</xsl:attribute>
</xsl:element>