XSLT not parsing the XML when Namespace prefix is missing - xslt

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.

Related

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..

Transforming the namespace child(sub) element using 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']">

XPath expression to select all XML child nodes except a specific list?

Here's the sample data:
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<customField1>Whatever</customField1>
<customField2>Whatever</customField2>
<customField3>Whatever</customField3>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<customField1>Whatever</customField1>
<customField2>Whatever</customField2>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<customField1>Whatever</customField1>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
</catalog>
Say I want to select everything except the price and year elements. I would expect to write something like the below, which obviously doesn't work.
<?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>
<xsl:for-each select="//cd/* except (//cd/price|//cd/year)">
Current node: <xsl:value-of select="current()"/>
<br />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Please help me find a way to exclude certain child elements.
<xsl:for-each select="//cd/*[not(self::price or self::year)]">
But actually this is bad and unnecessarily complicated. Better:
<xsl:template match="catalog">
<html>
<body>
<xsl:apply-templates select="cd/*" />
</body>
</html>
</xsl:template>
<!-- this is an empty template to mute any unwanted elements -->
<xsl:template match="cd/price | cd/year" />
<!-- this is to output wanted elements -->
<xsl:template match="cd/*">
<xsl:text>Current node: </xsl:text>
<xsl:value-of select="."/>
<br />
</xsl:template>
Avoid <xsl:for-each>. Almost all of the time it is the wrong tool and should be substituted by <xsl:apply-templates> and <xsl:template>.
The above works because of match expression specificity. match="cd/price | cd/year" is more specific than match="cd/*", so it is the preferred template for cd/price or cd/year elements. Don't try to exclude nodes, let them come and handle them by discarding them.
I'd start experimenting with something like
"//cd/*[(name() != 'price') and (name() != 'year')]"
Or you just do normal recursive template matching with <xsl:apply-templates/>, and then have empty templates for <price/> and <year/> elements:
<xsl:template match="price"/>
<xsl:template match="year"/>

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.