XSLT accessing child element to retrieve parent element - xslt

Here is my following xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>price</th>
</tr>
<xsl:for-each select="library/book/authors/author[first='Harry'and last='Potter'] ">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="price"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Below is my XSL stylesheet
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>price</th>
</tr>
<xsl:for-each select="library/book/authors/author[first='Harry'and last='Potter'] ">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="price"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I am unable to display title and price as they are the parent element and I'm giving condition to my child element. And I need to display information regarding my parents element.

You can get access to a parent node in your XPath expression using "../". For example:
<xsl:value-of select="../../title"/>
<xsl:value-of select="../../price"/>

In this case I'd be inclined to suggest that you use a predicate so the for-each operates over the book elements instead of the author ones:
<xsl:for-each select="library/book[authors/author[first='Harry'and last='Potter']] ">
This says "select all the book elements that have at least one author named Harry Potter".

Related

Using XSLT how can I get my output to repeat instead of only returning the first instance?

I'm working on an XSLT script to output an HTML table containing data from an XML file but my resulting document is only giving me the first set when I need each set.
This is my XML:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE map PUBLIC "-//KPE//DTD DITA KPE Map//EN" "kpe-map.dtd" []>
<map>
<title><ph conref="../../titles/sec_s63_title_l1.dita#sec_s63_title_l1/topic_title"/></title>
<topicref href="../questions/sec_question_00260_1.dita">
<topicsubject keyref="sec_s63_los_1"/>
</topicref>
<topicref href="../questions/sec_question_00260_2.dita">
<topicsubject keyref="sec_s63_los_1"/>
</topicref>
<topicref href="../questions/sec_question_00260_3.dita">
<topicsubject keyref="sec_s63_los_1"/>
</topicref>
</map>
This is my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="/">
<html>
<body>
<h2></h2>
<table border="1">
<tr>
<td><xsl:value-of select="//topicref/#href"/></td>
<td><xsl:value-of select="//topicref/topicsubject/#keyref"/></td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This is the output I'm getting:
<html>
<body>
<h2></h2>
<table border="1">
<tr>
<td>../questions/sec_question_00260_1.dita</td>
<td>sec_s63_los_1</td>
</tr>
</table>
</body>
</html>
This is what I'm trying to get:
<html>
<body>
<h2></h2>
<table border="1">
<tr>
<td>../questions/sec_question_00260_1.dita</td>
<td>sec_s63_los_1</td>
</tr>
<tr>
<td>../questions/sec_question_00260_2.dita</td>
<td>sec_s63_los_1</td>
</tr>
<tr>
<td>../questions/sec_question_00260_3.dita</td>
<td>sec_s63_los_1</td>
</tr>
</table>
</body>
</html>
Where is my script off? Thanks in advance for any help!
I think you want something along the lines of
<xsl:template match="/">
<html>
<body>
<h2></h2>
<table border="1">
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="topicref">
<tr>
<td><xsl:value-of select="#href"/></td>
<td><xsl:value-of select="topicsubject/#keyref"/></td>
</tr>
</xsl:template>
Try it this way:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/map">
<html>
<body>
<h2></h2>
<table border="1">
<xsl:for-each select="topicref">
<tr>
<td><xsl:value-of select="#href"/></td>
<td><xsl:value-of select="topicsubject/#keyref"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
See the Repetition section in XSLT specification.

XSLT to show a unique object name

My XML input:
<result>
<objects name="wf1">
<object>
<qname>wf</qname>
<object_name>Nr 1</object_name>
<person_name>Anton</person_name>
</object>
<object>
<qname>wf</qname>
<object_name>Nr 2</object_name>
<person_name>Ben</person_name>
</object>
</objects>
<objects name="wf2">
<object>
<qname>wf</qname>
<object_name>Nr 2</object_name>
<person_name>Chris</person_name>
</object>
<object>
<qname>wf</qname>
<object_name>Nr 3</object_name>
<person_name>Dirk</person_name>
</object>
</objects>
</result>
Things to note
There are two blocks with information: wf1 and wf2
Both have an object with object_name = 'Nr 2'
All objects have qname = 'wf'
My XSLT:
<xsl:template match="/">
<xsl:call-template name="output_html" />
</xsl:template>
<xsl:template name="output_html">
<html>
<body >
<table>
<tr>
<th>Object name</th>
<th>Person name</th>
</tr>
<xsl:apply-templates select="result/objects/object[qname='wf']" />
</table>
</body>
</html>
</xsl:template>
<xsl:template match="object[*]">
<tr align="center" valign="top">
<td><xsl:value-of select="object_name"/></td>
<td><xsl:value-of select="person_name"/></td>
</tr>
</xsl:template>
Output:
Object name
Person name
Nr 1
Anton
Nr 2
Ben
Nr 2
Chris
Nr 3
Dirk
My wish:
I would like to see unique object names. If an object with a certain name appears in both wf1 and wf2 then only the one from wf2 should be shown.
So the desired output would be:
Object name
Person name
Nr 1
Anton
Nr 2
Chris
Nr 3
Dirk
The information "Ben" gets lost. That is fine.
Does anybody have ideas about how to achieve that in XSLT 1.0?
In XSLT 1.0, it would be best to adapt the Muenchian method to the current problem:
XSLT 1.0
<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:key name="obj-by-name" match="object" use="object_name" />
<xsl:template match="/result">
<html>
<body >
<table>
<!-- header -->
<tr>
<th>Object name</th>
<th>Person name</th>
</tr>
<!-- for each distinct object_name -->
<xsl:for-each select="objects/object[count(. | key('obj-by-name', object_name)[1]) = 1]">
<tr>
<td>
<xsl:value-of select="object_name"/>
</td>
<td>
<!-- sort the group with wf2 on top -->
<xsl:for-each select="key('obj-by-name', object_name)">
<xsl:sort select="number(../#name='wf2')" data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:value-of select="person_name"/>
</xsl:if>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
If <objects name="wf2"> will always come after <objects name="wf1">, then you can shorten this to:
<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:key name="obj-by-name" match="object" use="object_name" />
<xsl:template match="/result">
<html>
<body >
<table>
<!-- header -->
<tr>
<th>Object name</th>
<th>Person name</th>
</tr>
<!-- for each distinct object_name -->
<xsl:for-each select="objects/object[count(. | key('obj-by-name', object_name)[1]) = 1]">
<tr>
<td>
<xsl:value-of select="object_name"/>
</td>
<td>
<xsl:value-of select="key('obj-by-name', object_name)[last()]/person_name"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

how locate an element in source xml file dynamically in a loop

if we have the following xml input:
<root xmlns:ns="http://www.blabla">
<ns:element1 attribute="attr1">10</ns:element1>
<ns:element1 attribute="attr2">20</ns:element1>
<ns:element2 attribute="attr1">30</ns:element1>
<ns:element2 attribute="attr2">40</ns:element1>
</root>
how can we locate each element inside a for-each in xslt?
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Column 1</th>
<th style="text-align:left">Column 2</th>
</tr>
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<tr>
<td>
<xsl:value-of select="???what should be here???[lower-case(#attribute)='attr0']"/>
</td>
<td>
<xsl:value-of select="???same question???[lower-case(#attribute)='attr1']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
Please note that I want to follow this procedure (if possible) as the input is very dynamic and we don't always get all elements in every row.
I appreciate your help.
The expression you want is this...
<xsl:value-of select="*[local-name() = $name][lower-case(#attribute)='attr1']"/>
... Except this will fail with an error along the lines of Required item type of context item for the child axis is node(); supplied expression (.) has item type xs:integer, due to the code being executed in the context of the xsl:for-each on atomic values. To get around this, you will need to save a reference to the child elements in a variable before the xsl:for-each.
Try this XSLT
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Title</th>
</tr>
<xsl:variable name="children" select="*" />
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<tr>
<td>
<xsl:value-of select="$children[local-name() = $name][lower-case(#attribute)='attr1']"/>
</td>
<td>
<xsl:value-of select="$children[local-name() = $name][lower-case(#attribute)='attr2']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Or slightly better, to reduce code duplication...
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Title</th>
</tr>
<xsl:variable name="children" select="*" />
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<xsl:variable name="element" select="$children[local-name() = $name]"/>
<tr>
<td>
<xsl:value-of select="$element[lower-case(#attribute)='attr1']"/>
</td>
<td>
<xsl:value-of select="$element[lower-case(#attribute)='attr2']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Note, I would really consider changing your input XML if you have control over it. Numbering elements using the element name makes it harder to manipulate. Ideally you would do this instead...
<ns:element num="1" attribute="attr1">10</ns:element>
If you dont have control try to take count of the elements and run the loop as per count like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://www.blabla">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Column 1</th>
<th style="text-align:left">Column 2</th>
</tr>
<xsl:variable name="all-element" select="count(//*:element)"/>
<xsl:for-each select="*[concat('ns:element', (1 to $all-element))]">
<xsl:message select="."/>
<tr>
<td>
<xsl:value-of select="#attribute"/>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

XSLT Correct usage of child::* , Difference between using xsl:for-each and xsl:template in this scenario

I tried creating XSL transformation for the input xml of the below format.
With both <xsl:for-each /> and <xsl:template />
XML 1:
<books>
<book>
<title>charithram</title>
<author>sarika</author>
</book>
<book>
<title>doublebell</title>
<author>psudarsanan</author>
</book>
</books>
XSLT 1:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Title</th>
<th>Author</th>
</tr>
<xsl:for-each select="books/book">
<tr>
<td><xsl:value-of select="title" /></td>
<td><xsl:value-of select="author" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Or
XSLT 2:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Title</th>
<th>Author</th>
</tr>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="books/book">
<tr>
<td><xsl:value-of select="title" /></td>
<td><xsl:value-of select="author" /></td>
</tr>
</xsl:template>
</xsl:stylesheet
......
Now, if the XML is
XML 2:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
I am able to accomplish the result with usage of books/child::*
XSLT 3:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<th>Title</th>
<th>Author</th>
</tr>
<xsl:for-each select="books/child::*">
<tr>
<td><xsl:value-of select="title" /></td>
<td><xsl:value-of select="author" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Questions:
What is the difference between using <xsl:for-each/> and applying template in the above scenario? I do not see any difference. [XSLT 1 and XSLT 2]
Please validate if this is the correct usage. [using <xsl:for-each select="books/child::*"> to accomplish the result] [XSLT 3]
Update:
I have removed my third question and will post in a different thread.
1) In fact, you're right. xsl:for-each is a sort of "anonymous inline template". It's actually bad practice in many cases, since it tends to indicate that the stylesheet is being written procedurally rather than rule-driven... but it is occasionally the best way to express your logic. As with most programming languages, there's more than one way to solve most problems and it's up to the programmer to develop a sense of style to pick the best one.
2) As Ian Roberts said, "yes, that will work, but books/child::* can be shortened to books/* as child:: is the default axis". (He does deserve the credit for answering that one -- I was half-asleep and didn't get a Round Tuit.)

Parse through multiple nodes using xslt

Below is my input xml
<Record>
<Header>
<field1/>
</Header>
<Body>
<firstname>x1</firstname>
<lastname>y1</lastname>
<company>Test1</company>
<Body>
<Body>
<firstname>x2</firstname>
<lastname>y2</lastname>
<company></company>
<Body>
<Body>
<firstname>x3</firstname>
<lastname>y3</lastname>
<company>Test2</company>
<Body>
</Record>
I am trying to loop through body and check if the company value is blank,to output the corresponding first name and last name.This whole output,I am mapping to DATA on the target using xslt mapper.Can someone help me with the below code which is not working
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="Namespace">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="NAME" select="/RecordSet/Body"/>
<xsl:variable name="break"><br></xsl:variable>
<xsl:variable name="tableB"><table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0" ></xsl:variable>
<xsl:variable name="tableE"></table></xsl:variable>
<xsl:variable name="trB"><tr></xsl:variable>
<xsl:variable name="trE"></tr></xsl:variable>
<xsl:variable name="tdB"><td></xsl:variable>
<xsl:variable name="tdE"></td></xsl:variable>
<xsl:variable name="nbsp">&nbsp;</xsl:variable>
<xsl:variable name="thB"><tr BGCOLOR="#CCCCCC"></xsl:variable>
<xsl:template match="/">
<DATA>
<xsl:value-of select="$tableB"/>
<xsl:value-of select="$thB"/>
<xsl:value-of select="$tdB"/><B>FirstName</B>
<xsl:value-of select="$nbsp"/>
<xsl:value-of select="$tdE"/>
<xsl:value-of select="$tdB"/><B>LASTNAME </B>
<xsl:value-of select="$nbsp"/>
<xsl:value-of select="$tdE"/>
<xsl:value-of select="$trE"/>
<xsl:value-of select="$trB"/>
<xsl:for-each select="$NAME/Body[string-length(company) > 0]">
<xsl:value-of select="$tdB"/>
<xsl:value-of select="$MT_NAME/firstname"/>
<xsl:value-of select="$nbsp"/>
<xsl:value-of select="$tdE"/>
<xsl:value-of select="$tdB"/>
<xsl:value-of select="$MT_NAME/lastname"/>
<xsl:value-of select="$nbsp"/>
<xsl:value-of select="$tdE"/>
<xsl:if test="position() mod 2 = 0">
<xsl:value-of select="$trE"/>
</xsl:if>
</xsl:for-each>
<xsl:value-of select="$tableE"/>
</DATA>
The output should be
|FIRSTNAME|LASTNAME|
| X2 | Y2 |
The provided code doesn't produce HTML at all -- it produces strings -- one-dimensional text.
Also, AFAIK, DATA isn't an HTML element.
Also, the provided "XML" is severely malformed.
Here is an example how to produce an HTML table with XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<table>
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Company</td>
</tr>
</thead>
<xsl:apply-templates/>
</table>
</xsl:template>
<xsl:template match="Body">
<tr>
<xsl:apply-templates select="*"/>
</tr>
</xsl:template>
<xsl:template match="Body/*">
<td> </td>
</xsl:template>
<xsl:template match="Body/*[normalize-space()]">
<td><xsl:value-of select="."/></td>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (the provided severely malformed text -- corrected):
<Record>
<Header>
<field1/>
<Body>
<firstname>x1</firstname>
<lastname>y1</lastname>
<company>Test1</company>
</Body>
<Body>
<firstname>x2</firstname>
<lastname>y2</lastname>
<company></company>
</Body>
<Body>
<firstname>x3</firstname>
<lastname>y3</lastname>
<company>Test2</company>
</Body>
</Header>
</Record>
A meaningful and sensible HTML table is produced:
<table>
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Company</td>
</tr>
</thead>
<tr>
<td>x1</td>
<td>y1</td>
<td>Test1</td>
</tr>
<tr>
<td>x2</td>
<td>y2</td>
<td> </td>
</tr>
<tr>
<td>x3</td>
<td>y3</td>
<td>Test2</td>
</tr>
</table>
I have to take a guess at your requirements as they are not very well explained.
This XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<DATA>
<table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0">
<tr BGCOLOR="#CCCCCC">
<th>First name</th>
<th>Last name</th>
<th>Company</th>
</tr>
<xsl:apply-templates select="*/Body" />
</table>
</DATA>
</xsl:template>
<xsl:template match="Body">
<tr>
<td><xsl:value-of select="firstname" /></td>
<td><xsl:value-of select="lastname" /></td>
<td>
<xsl:choose>
<xsl:when test="company!=''" >
<xsl:value-of select="company" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(firstname,' ',lastname)" />
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
... when applied on this document...
<Record>
<Header>
<field1/>
</Header>
<Body>
<firstname>x1</firstname>
<lastname>y1</lastname>
<company>Test1</company>
</Body>
<Body>
<firstname>x2</firstname>
<lastname>y2</lastname>
<company/>
</Body>
<Body>
<firstname>x3</firstname>
<lastname>y3</lastname>
<company>Test2</company>
</Body>
</Record>
...will yield...
<DATA>
<table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0">
<tr BGCOLOR="#CCCCCC">
<th>First name</th>
<th>Last name</th>
<th>Company</th>
</tr>
<tr>
<td>x1</td>
<td>y1</td>
<td>Test1</td>
</tr>
<tr>
<td>x2</td>
<td>y2</td>
<td>x2 y2</td>
</tr>
<tr>
<td>x3</td>
<td>y3</td>
<td>Test2</td>
</tr>
</table>
</DATA>
Note
Notice that the middle record has a cell value of 'x2 y2' for Company as per stated requirements when the input Company is empty or missing.
Update
The input document is still badly malformed. In consideration of the OP's updated requirements, this XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<DATA>
<table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0">
<tr BGCOLOR="#CCCCCC">
<th>First name</th>
<th>Last name</th>
</tr>
<xsl:apply-templates select="*/Body[company='']" />
</table>
</DATA>
</xsl:template>
<xsl:template match="Body">
<tr>
<td><xsl:value-of select="firstname" /></td>
<td><xsl:value-of select="lastname" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>
...when applied to the same input document as before, yields...
<DATA>
<table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0">
<tr BGCOLOR="#CCCCCC">
<th>First name</th>
<th>Last name</th>
</tr>
<tr>
<td>x2</td>
<td>y2</td>
</tr>
</table>
</DATA>