I have question about XSLT1.0. The task is to write out in HTML all books written by given authors only by using XSL templates.
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book author="herbert">
<name>Dune</name>
</book>
<book author="herbert">
<name>Chapterhouse: Dune</name>
</book>
<book author="pullman">
<name>Lyras's Oxford</name>
</book>
<book author="pratchett">
<name>I Shall Wear Midnight</name>
</book>
<book author="pratchett">
<name>Going Postal</name>
</book>
<author id="pratchett"><name>Terry Pratchett</name></author>
<author id="herbert"><name>Frank Herbert</name></author>
<author id="pullman"><name>Philip Pullman</name></author>
</books>
So far I have this solution.
<?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/>
<body>
<table border="1">
<xsl:apply-templates select="//author"/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="author">
<tr>
<td>
<xsl:value-of select="name/text()"/>
</td>
<td>
<xsl:value-of select="#id"/>
</td>
<td>
<xsl:apply-templates select="/books/book[#id=#author]"/>
--previous XPath does not work properly, it should choose only those books that are written by the given author (that this template matches)
</td>
</tr>
</xsl:template>
<xsl:template match="book">
<xsl:value-of select="name/text()"/>
</xsl:template>
</xsl:stylesheet>
There is a problem though, explained in the comment.
Thank you Martin and Marzipan - it works now. There is one more thing. What if I wanted to have the book titles for each authors separated by commas? I propose this solution, but is there more elegant way to achieve this?
...
<xsl:apply-templates select="/books/book[current()/#id=#author][not(position()=last())]" mode="notLast"/>
<xsl:apply-templates select="/books/book[current()/#id=#author][last()]"/>
</td>
</tr>
</xsl:template>
<xsl:template match="book">
<xsl:value-of select="name/text()"/>
</xsl:template>
<xsl:template match="book" mode="notLast">
<xsl:value-of select="name/text()"/>
<xsl:text> , </xsl:text>
</xsl:template>
</xsl:stylesheet>
I just realized my question has been already answered by Marzipan. Quesion is solved then.
You want <xsl:apply-templates select="/books/book[current()/#id=#author]"/> or better yet a key (as a child of the xsl:stylesheet element):
<xsl:key name="book-by-author" match="books/book" use="#author"/>
and then <xsl:apply-templates select="key('book-by-author', #id)"/>.
The problem is this line:
<xsl:apply-templates select="/books/book[#id=#author]"/>
Understand that, when you enter a predicate, the context changes to the nodes matched by the preceding XPath selector. The context is no longer the node matched by the template it is within.
To get round this, commit the author's ID to a variable, then use that inside your predicate instead.
<xsl:variable name='author_id' select='#id' />
...
[#author=$author_id]
[EDIT - or, as Martin pointed out in his answer, use current() to force the context]
You can run it here:
http://www.xmlplayground.com/10FRcA
Related
I want to get the first heading (h1) before a table in a docx.
I can get all headings with:
<xsl:template match="w:p[w:pPr/w:pStyle[#w:val='berschrift1']]">
<p>
<context>
<xsl:value-of select="." />
</context>
</p>
</xsl:template>
and I can also get all tables
<xsl:template match="w:tbl">
<p>
<table>
<xsl:value-of select="." />
</table>
</p>
</xsl:template>
But unfortunetly the processor does not accept
<xsl:template match="w:tbl/preceding-sibling::w:p[w:pPr/w:pStyle[#w:val='berschrift1']]">
<p>
<table>
<xsl:value-of select="." />
</table>
</p>
</xsl:template>
Here is a reduced XML file extracted from a docx: http://pastebin.com/KbUyzRVv
I want something like that as a result:
<context>Let’s get it on</context> <- my heading
<table>data</table>
<context>Let’s get it on</context> <- my heading
<table>data</table>
<context>We’re in the middle of something</context> <- my heading
<table>data</table>
Thanks to Daniel Haley I was able to find a solution for that problem. I'll post it here, so it is independend of the pastebin I postet below.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:v="urn:schemas-microsoft-com:vml" exclude-result-prefixes="xsl w v">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="w:tbl">
<context>
<xsl:value-of select="(preceding-sibling::w:p[w:pPr/w:pStyle[#w:val = 'berschrift1']])[last()]"/>
</context>
<table>
<xsl:value-of select="."/>
</table>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Hard to answer without a Minimal, Complete, and Verifiable example, but try this:
<xsl:template match="w:tbl">
<p>
<table>
<xsl:value-of select="(preceding::w:p[w:pPr/w:pStyle[#w:val='berschrift1']])[last()]"/>
</table>
</p>
</xsl:template>
Assuming you can use XSLT 2.0 (and most people can, nowadays), I find a useful technique here is to have a global variable that selects all the relevant nodes:
<xsl:variable name="special"
select="//w:tbl/preceding-sibling::w:p[w:pPr/w:pStyle[#w:val='berschrift1']][1]"/>
and then use this variable in a template rule:
<xsl:template match="w:p[. intersect $special]"/>
In XSLT 3.0 you can reduce this to
<xsl:template match="$special"/>
XML
<books>
<book title="XML Today" author="David Perry" release="2016"/>
<book title="XML and Microsoft" author="David Perry" release="2015"/>
<book title="XML Productivity" author="Jim Kim" release="2015"/>
</books>
The following XSL code iterates through all books by David Perry.
XSL
<xsl:key name="title-search" match="book" use="#author"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each select="key('title-search', 'David Perry')">
<DIV>
<xsl:value-of select="#title"/>
</DIV>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
HTML output
<HTML>
<BODY>
<DIV>XML Today</DIV>
<DIV>XML and Microsoft</DIV>
</BODY>
</HTML>
Now I would like to iterate not only through all books by David Perry but through all books by any author.
How would a corresponding outer loop look like?
Or in other words: How do I iterate through all values of my title-search key.
The output should be something like this:
<HTML>
<BODY>
<H1>David Perry</H1>
<DIV>XML Today</DIV>
<DIV>XML and Microsoft</DIV>
<H1>Jim Kim</H1>
<DIV>XML Productivity</DIV>
</BODY>
</HTML>
This should do the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="title-search" match="book" use="#author"/>
<xsl:template match="/books">
<HTML>
<BODY>
<xsl:apply-templates select="book" />
</BODY>
</HTML>
</xsl:template>
<xsl:template match="book">
<xsl:variable name="author" select="#author" />
<xsl:if test="generate-id(.) = generate-id(key('title-search', $author)[1])">
<H1><xsl:value-of select="#author" /></H1>
<xsl:apply-templates select="//book[#author = $author]" mode="titles"/>
</xsl:if>
</xsl:template>
<xsl:template match="book" mode="titles">
<DIV>
<xsl:value-of select="#title"/>
</DIV>
</xsl:template>
</xsl:stylesheet>
It uses a technique called Muenchian grouping. Each element in an XML document implicitly has a unique ID assigned to it by the XSLT processor (it can also be explicitly assigned with the id attribute in the document itself). This part:
generate-id(.) = generate-id(key('title-search', $author)[1])
basically tests if the ID of the current book element is the same as that of the first book element with the same author. The variable $author is taken from the current book, The key is used to look up the <book> elements with that same author, the [1] predicate takes the first one. As a result, the <H1> is only generated for the first occurrence of that specific author, and in that same if element we're then applying the template for listing the books of that author. The mode is used to avoid a clash between these templates. There's no doubt a solution that doesn't use modes, but this works too. You could also do a lot of this with <xsl:for-each> but I made separate templates because XSLT is declarative and works best when treating it as such.
Grouping is a lot easier in XSLT 2, but when stuck with XSLT 1, the Muenchian grouping technique often provides a solution once you grok it.
Try this:
XSLT2:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each-group select="books/book" group-by="#author">
<H1><xsl:value-of select="current-grouping-key()"/></H1>
<xsl:for-each select="current-group()">
<DIV><xsl:value-of select="#title"/></DIV>
</xsl:for-each>
</xsl:for-each-group>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
I've the below Sample XMLs.
XML1
<root num="1">
<abc></abc>
<cde></cde>
<def></def>
</root>
XML2
<root num="2">
<xyz></xyz>
<cft></cft>
<vft></vft>
</root>
XML3
<root num="3">
<dfg></dfg>
<mnb></mnb>
<gft></gft>
<root>
And i have 3 different XSLTs, each corresponding to XML.
I want to achieve the below.
Make a single XSLT and call the template based on the root number. something like the below.
<xsl:if test="root[#num="1"]>
<!--Call the template matching root 1-->
</xsl:if>
<xsl:if test="root[#num="2"]>
<!--Call the template matching root 2-->
</xsl:if>
<xsl:if test="root[#num="3"]>
<!--Call the template matching root 3-->
</xsl:if>
I just want to put all the XSLTs in a single XSLT, please let me know how can i do this.
Thanks
You can use element.
The element contains rules to apply when a specified node is matched.
The match attribute is used to associate the template with an XML element. The match attribute can also be used to define a template for a whole branch of the XML document (i.e. match="/" defines the whole document).
Note: is a top-level element.
Example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="cd">
<p>
<xsl:apply-templates select="title"/>
<xsl:apply-templates select="artist"/>
</p>
</xsl:template>
<xsl:template match="title">
Title: <span style="color:#ff0000">
<xsl:value-of select="."/></span>
<br />
</xsl:template>
<xsl:template match="artist">
Artist: <span style="color:#00ff00">
<xsl:value-of select="."/></span>
<br />
</xsl:template>
</xsl:stylesheet>
This was taken from http://www.w3schools.com/xsl/el_template.asp
I have the template
<xsl:template match="node" mode="some_mode">
<xsl:value-of select="child1" />
<xsl:value-of select="child2" />
<xsl:value-of select="child3" />
</xsl:template>
I want to apply the template so it would select all the nodes specified in the template in one case like this
<xsl:apply-templates select="node" mode="some_node" /> <!-- select all inside the node tag -->
and in another case I want to limit the output and for example not to select <child1> or <child2> nodes. Can I do it with a variable or a param? Or do I have to write another template from scratch?
<xsl:apply-templates select="node" mode="some_node" /> <!-- select only some tags from the node tag -->
In other word I will use this templates several times and I want to contorl the output when applying. I can define the variable, but the documentation says that I can't change the value of a variable once it was defined. Probably the param will wrok but I'm not good with it.
I'm afraid that's not possible without modification of the template you have.
You could think about storing template output in a variable and then processing it to remove child1 and child2 values. But I guess you would be unable to guess which part of your output is coming from child1 and/or child2.
Or you can develop another template that performs that alternative action.
EDIT: Another idea:
Maybe it is possible to apply filtering to get rid of child1 and child2 before applying your template (that would be a sort of multi-pass XSL tranformation).
Use the xsl:if instruction inside your template to branch.
Example:
<xsl:template match="node">
<xsl:value-of select="child1" />
<xsl:if test="price > 10">
<xsl:value-of select="child2" />
<xsl:value-of select="child3" />
</xsl:if>
</xsl:template>
I was hoping for a simple example in the question. As of the face-of-it one is not forthcoming, I'll make up a simple example here, which emodies your question, and show you how the xsl:if instruction is the perfect answer.
Let's say you have a music collection like so ...
<?xml version="1.0"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
And you want to output a html table of your CD's listing title and price. Normally you want both title and price, but on some conditions (say price is less than $10), you want to suppress the output of price, and just have some static text in its place, like "cheaper than 10.00".
So your expected output should be:
<?xml version="1.0" encoding="utf-8"?>
<table>
<tr>
<td>Empire Burlesque</td>
<td>cheaper than 10.00</td>
</tr>
<tr>
<td>Hide your heart</td>
<td>9.90</td>
</tr>
</table>
The style-sheet to produce this transformation could be:
<?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" indent="yes"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="//cd"/>
</table>
</xsl:template>
<xsl:template match="cd">
<tr>
<td><xsl:value-of select="title"/></td>
<xsl:if test="price < 10">
<td><xsl:value-of select="price"/></td>
</xsl:if>
<xsl:if test="not(price < 10)">
<td>cheaper than 10.00</td>
</xsl:if>
</tr>
</xsl:template>
</xsl:stylesheet>
And there you have the perfect answer - how to apply the whole template or just parts of it depending on a condition. In this case the condition is that the price is less than $10.
I recommend using separate templates with different match patterns and/or in different modes. This is to be preferred to using conditionals and thus writing messy and potentially buggy code:
<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:apply-templates mode="allChildren"/>
==========
<xsl:apply-templates mode="child3Only"/>
</xsl:template>
<xsl:template match="node" mode="allChildren">
<xsl:copy-of select="*"/>
</xsl:template>
<xsl:template match="node" mode="child3Only">
<xsl:copy-of select="child3"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following document (similar to the one you used in a previous question):
<document>
<node>
<child1>Child 1</child1>
<child2>Child 2</child2>
<child3>Child 3</child3>
</node>
<anotherNode />
</document>
the wanted result is produced:
<child1>Child 1</child1><child2>Child 2</child2><child3>Child 3</child3>
==========
<child3>Child 3</child3>
I'm using Altova's command-line xml processor on Windows to process a Help & Manual xml file. Help & Manual is help authoring software.
I'm extracting the text content from it using the following xslt. Specifically, I'm having an issue with the final para rule:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:strip-space elements="*" />
<xsl:template match="para[#styleclass='Heading1']">
<xsl:text>====== </xsl:text>
<xsl:value-of select="." />
<xsl:text> ======
</xsl:text>
</xsl:template>
<xsl:template match="para[#styleclass='Heading2']">
<xsl:text>===== </xsl:text>
<xsl:value-of select="." />
<xsl:text> =====
</xsl:text>
</xsl:template>
<xsl:template match="para">
<xsl:value-of select="." />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="toggle">
<xsl:text>**</xsl:text>
<xsl:apply-templates />
<xsl:text>**
</xsl:text>
</xsl:template>
<xsl:template match="title" />
<xsl:template match="topic">
<xsl:apply-templates select="body" />
</xsl:template>
<xsl:template match="body">
<xsl:text>Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
</xsl:text>
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
I've run into an issue with the extraction of text from certain paragraph elements. Take for example this xml:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="../helpproject.xsl" ?>
<topic template="Default" lasteditedby="tlilley" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../helpproject.xsd">
<title translate="true">New Installs</title>
<keywords>
<keyword translate="true">Regional and Language Options</keyword>
</keywords>
<body>
<header>
<para styleclass="Heading1"><text styleclass="Heading1" translate="true">New Installs</text></para>
</header>
<para styleclass="Normal"><table rowcount="1" colcount="2" style="width:100%; cell-padding:6px; cell-spacing:0px; page-break-inside:auto; border-width:1px; border-spacing:0px; cell-border-width:0px; border-color:#000000; border-style:solid; background-color:#fffff0; head-row-background-color:none; alt-row-background-color:none;">
<tr style="vertical-align:top">
<td style="vertical-align:middle; width:96px; height:103px;">
<para styleclass="Normal" style="text-align:center;"><image src="books.png" scale="100.00%" styleclass="Image Caption"></image></para>
</td>
<td style="vertical-align:middle; width:1189px; height:103px;">
<para styleclass="Callouts"><text styleclass="Callouts" style="font-weight:bold;" translate="true">Documentation Convention</text></para>
<para styleclass="Callouts"><text styleclass="Callouts" translate="true">To make the examples concrete, we refer to the </text><var styleclass="Callouts">Add2Exchange</var><text styleclass="Callouts" translate="true"> Service Account as "zAdd2Exchange" throughout this document. If your Service Account name is different, substitute that value for "zAdd2Exchange" in all commands and examples. If you have named your account according to the recommended "zAdd2Exchange", then you may cut and paste any given commands as is.</text></para>
</td>
</tr>
</table></para>
</body>
</topic>
When the xslt is run on that paragraph, it pulls the text out but does so at the top paragraph element. The transform is supposed to add a pair of newlines to all extracted paragraphs, but doesn't have a chance to do so on the embedded <para> elements because the text is extracted at the parent para element.
Note that I don't care about the table tags, I just want to strip those.
Is there a way to construct the para rule so that it properly extracts the directly-owned text of a para element, as well as the text of any children para's, such that each extracted chunk gets the rule's newlines in the output text?
I think I've found the answer. Instead of value-of with the last para rule, I'm using apply-templates instead and that seems to catch them all.