Transforming the namespace child(sub) element using XSLT - xslt

XML
<?xml version="1.0" encoding="UTF-8"?>
<!-- Edited by XMLSpy -->
<h:catalog xmlns:h="http://www.w3.org/TR/html4/">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</h:catalog>
XSLT:
<?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" xmlns:h="http://www.w3.org/TR/html4/">
<xsl:template match="/h:catalog/cd">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="country"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
I am expecting below output from the above transformation, but i am not getting
*Need Your help to display in below format, Struggling a lot because of the namespace child element*
Expected Output:
<tr xmlns:h="http://www.w3.org/TR/html4/">
<td>Empire Burlesque</td>
<td>USA</td>
</tr>
This is just an example i gave my query is: How can i transform the child element, whose parent has the namespace? example:
<app:ApplicationRequest xmlns:app="http://*/applicationRequest">
<BusinessChannel>
<!-- SOME STUFF -->
</BusinessChannel>
</app:ApplicationRequest>.
I need to display the contents of BusinessChannel using
<xsl:template match="/app:ApplicationRequest/BusinessChannel">
But it is not working.

If you want to match an element disregarding its namespace, as described here you have to use local-name(), so in your case:
<xsl:template match="/app:ApplicationRequest/*[local-name()='BusinessChannel']">

Related

XSLT not parsing the XML when Namespace prefix is missing

I am writing my first XSLT code to convert XML file.
Below is the sample code just to explain the issue here.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="urn.abc.xyz" xmlns:ns2="abc:cde">
<xsl:template match="/catalog">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Artist</th>
<th>Price</th>
</tr>
<xsl:for-each select="catalog/cd">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="artist"/></td>
<td><xsl:value-of select="price"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
above code works well when all the namespace is having prefix in input XML. but fails to work when namespace prefix is missing in XML , i.e., execution just shows xml content without tag names.
Input XML
<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="urn.abc.xyz" xmlns:ns2="abc:cde">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
output for above XML:
Title Artist Price
with prefix:
<catalog xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:abc="urn.abc.xyz" xmlns:ns2="abc:cde">
without prefix
<catalog xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="urn.abc.xyz" xmlns:ns2="abc:cde">
if i run the XSL without namespace prefix (abc)
then output looks like below
Empire Burlesque Bob Dylan USA Columbia 10.90 1985
can anyone help why my xsl is not working when namespace prefix is empty? and what changes needed in my xsl so that it will work even though namespace prefix is missing.
A declaration of a namespace without a prefix defines a default namespace, and it places all unprefixed elements within the scope of such declaration in that namespace.
In order to address such elements in your stylesheet, you must declare the same namespace, assign it a prefix and use that prefix for all unprefixed elements in the source XML.
For example, if the input XML is:
<catalog xmlns="urn.abc.xyz">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
then your stylesheet needs to be:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:abc="urn.abc.xyz"
exclude-result-prefixes="abc">
<xsl:template match="/abc:catalog">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Artist</th>
<th>Price</th>
</tr>
<xsl:for-each select="abc:cd">
<tr>
<td><xsl:value-of select="abc:title"/></td>
<td><xsl:value-of select="abc:artist"/></td>
<td><xsl:value-of select="abc:price"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
(you can use any prefix you want).
However, this stylesheet is NOT suitable for processing an input whose elements are in no-namespace:
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
It is technically possible to construct a stylesheet that would ignore the namespace and process both XMLs by using only the local-names of the elements. However, this is not recommended unless absolutely necessary. The assumption is that namespaces are there for a reason, and ignoring them may lead to incorrect results.
Note also that namespace declarations that do use a prefix, such as xmlns:ns2="abc:cde" in your example, are applied only to nodes that have the same prefix. In the absence of such nodes, such declaration has no effect and is entirely redundant (and irrelevant to your question).
In XSLT 1.0, if the elements in your source document are in a namespace, you must use namespace prefixes in the XPath expressions in your XSLT.
In your source document you declare "urn.abc.xyz" as the default namespace URI. (Incidentally, "urn.abc.xyz" is not really a proper namespace URI; it should be an absolute URI, including a URI scheme, e.g. "urn:abc.xyz" or something, but that's not your problem here).
<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="urn.abc.xyz">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
(NB I removed the unused XSLT and "ns2" namespace declarations since they weren't used in the file anywhere).
In your XSLT you have a template with an XPath match expression = /catalog, but because catalog has no namespace prefix, it would only match a catalog element in no namespace. To match a catalog element in the urn.abc.xyz namespace you would need to first associate a namespace prefix (which could be anything, e.g. my-namespace) with the urn.abc.xyz namespace URI, and then use the XPath expression "/my-namespace:catalog" in your template's match attribute. To bind the urn.abc.xyz namespace URI to a prefix my-namespace, add a namespace declaration to the stylesheet element: xmlns:my-namespace="urn.abc.xyz"
Then within the template, your xsl:for-each statement would refer to the child elements using the expression my-namespace:cd, and similarly you'd need to use my-namespace:title, etc. to refer to the child elements of the cd element.

How to check if string is contained in a list?

XSLT Code:
<?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>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Artist</th>
</tr>
<xsl:for-each select="catalog/cd[artist='Bob Dylan']">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="artist"/></td>
</tr>
</xsl:for-each>
**<xsl:value-of select="sum(catalog/cd[artist='Bob Dylan' and extra[not(contains(tests/test,'CD'))]]/price)"/>**
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
XML Code:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<extra>
<tests>
<test>CDEF</test>
</tests>
<tests>
<test>QWER</test>
</tests>
<tests>
<test>UIOP</test>
</tests>
</extra>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Red</title>
<artist>Bob Dylan</artist>
<country>UK</country>
<extra>
<tests>
<test>CDEF</test>
</tests>
<tests>
<test>QWER</test>
</tests>
<tests>
<test>UIOP</test>
</tests>
</extra>
<company>London</company>
<price>7.80</price>
<year>1987</year>
</cd>
<cd>
<title>Unchain my heart</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<extra>
<tests>
<test>ABXY</test>
</tests>
<tests>
<test>QWER</test>
</tests>
<tests>
<test>CDOP</test>
</tests>
</extra>
<company>EMI</company>
<price>8.5</price>
<year>1987</year>
</cd>
</catalog>
In the XSLT, I want to take the sum of all that have artists as Bob Dylan, and I want to make sure that I only retrieve test that don't contain CD in their string.
Based on the result, I am taking 8.5. From what I found, my query ignores values from 2-n. What do I need to do to account for them?
I've tried extra[not(contains(tests/test,'CD'))] not(contains(extra/tests/test))], extra/tests[not(contains(tests,CD))]
I've been grasping at straws, so I'd love to have someone enlighten me. Thanks in advance!
In your original XSLT you have this in your expression...
extra[not(contains(tests/test,'CD'))]
contains is a string function, which takes 2 strings as arguments. If you pass it a node, it will convert that to a string first. In this case though, you are passing a node-set. In XSLT 1.0, it will convert only the first node in that to a string which is why it doesn't pick up your last cd. (In XSLT 1.0, passing in a node-set consisting of more than one node throws an error).
You need to do this, so that contains is applied as a condition to each test:
<xsl:value-of select="sum(catalog/cd[artist='Bob Dylan' and extra[not(tests/test[contains(., 'CD')])]]/price)"/>
Or you could slightly simplify it to this...
<xsl:value-of select="sum(catalog/cd[artist='Bob Dylan' and not(extra/tests/test[contains(., 'CD')])]/price)"/>

How to delete 10 records out of 20 records and display the remaining 10 records in an xslt?

I'm a fresher in xslt coding..I'm able to delete a particular record using remove() operation but I'm unable to delete 10 records out of 20 records at a time and display the remaining 10 records...Can anyone please give me an example of performing this operation..Thanks in advance.
Actually I'm doing project in dataower, I'm giving soap request and I want to delete 10 records from the response using xslt code..
Here is my input request...
<soapenv:Envelope>
<soapenv:Body>
<IntegrationEventWS_GetEvents_Input>
<EventCount>10</EventCount>
<QueueName>Opportunity Partner</QueueName></IntegrationEventWS_GetEvents_Input>
</soapenv:Body>
</soapenv:Envelope>
Here is my sample response...
<SOAP-ENV:Envelope>
<SOAP-ENV:Body>
<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>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Like this in the response I have 20 records...and I want to delete 10 out of 20 records at a time..for this I have to write xslt code..
Here is my xslt code...I tried like this..
<?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>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Artist</th>
<th>id</th>
</tr>
<xsl:for-each select="catalog/cd">
<xsl: remove select="artist"/>
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="artist"/></td>
<td><xsl:value-of select="id"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
and I used remove() to delete the entire records..

apply the whole template or template parts depending on the condition

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>

Change font in XML using XSLT

I'm new to XSLT. I'm trying to change the font size of a specific text in XML file using XSLT. For eg- I have the CDCatalog.xml file with following data.
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="cdcat.xsl"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist><SmallText>Bob Dylan</SmallText><LineBreak/>*</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</catalog>
and the cdCat.XSL file is-
<?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="html" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:include href="cdCatalog.xsl" /> <!-- I added this -->
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th align="left">Title</th>
<th align="left">Artist</th>
</tr>
<xsl:for-each select="catalog/cd">
<tr>
<td>
<xsl:value-of select="title" />
</td>
<td>
<xsl:value-of select="artist" />
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I added a new xsl file cdCatalog.XSL file with following details-
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="LineBreak">
<br/>
</xsl:template>
<xsl:template match="Superscript">
<sup>
<xsl:value-of select="."/>
</sup>
</xsl:template>
<xsl:template match="SmallText">
<font size="1">
<xsl:value-of select="."/>
</font>
</xsl:template>
</xsl:stylesheet>
and included this file in the CDCat.xsl file.and added the tags - <smallText>, <LineBreak> in the CdCatalog.xml file. now when I open the xml file i dont see the LineBreak nor the font size difference. Can anyone please suggest if I'm missing something.
Thanks in advance
Sai
You need to use apply-templates to indicate where your template matches should take effect.
XML says nothing about presentation, that's the whole point. It's a data format.
If you want your XSLT to output to something where presentation matters I suggest you transform to HTML and get let HTML/CSS handle the styling.
Having seen your actual code now (hint: use the formatting when creating questions) don't use the font tag. What you want semantically and in practice is just headers <h1>, <h2>, <h3> etc, and I'd still suggest you add a CSS link in there. Oh and <xsl:output method="html" />
In-between these two opening tags:
<html>
<body>
...I'd place a link to a style sheet that defines the font sizes. Alternatively (and useful if you want a self contained HTML file to email around) you could put a style block there instead.