How to check if string is contained in a list? - xslt

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)"/>

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 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']">

Passing xml as parameter to xslt and replacing the xml [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
XSLT for replace attribute in XML element if it exists in another XML?
I have two XML:
First XML:
<FirstXML>
<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>
</FirstXML>
Second XML:
<SecondXML>
<catalog>
<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>
</SecondXML>
I want to Transform my First XML using XSLT. The cd node of first xml should be replaced with cd node of second xml.
Resulting XML from XSLT transformation:
<FirstXML>
<catalog>
<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>
</FirstXML>
Please help me with XSLT for this. I think we would need to pass the Second XML as a parameter to the XSLT, I am trying to do that. I am very new to XSLT so might not be coding it correctly.
Any inputs on how we can do this would be helpful. Thanks in Advance.
I think we would need to pass the Second XML as a parameter to the
XSLT.
This is possible, but not necessary at all.
A more convenient way is to pass to the transformation just the filepath to the document and use the XSLT document() function for parsing and accessing it:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pDocLink" select=
"'file:///c:/temp/delete/SecondXml.xml'"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="document($pDocLink)/*/*[1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided "first" document:
<FirstXML>
<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>
</FirstXML>
and the "second" document is placed at: c:\temp\delete\SecondXml.xml :
<SecondXML>
<catalog>
<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>
</SecondXML>
the wanted, correct result is produced:
<FirstXML>
<catalog>
<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>
</FirstXML>
There is a document function, use it to work with several input sources.
The description of your problem is too vague to make more detailed recommendations. Try to search some tutorials, e.g. http://www.ibm.com/developerworks/xml/library/x-tipcombxslt/
You should be looking at the document() function. Passing an XML tree in as a parameter works is some XML systems, not others.
If you have a variable such as
<xsl:variable name="source" select="document('filename.xml')//catalog"/>
then accessing the variable $source would give you the root of your CD branch
EDIT
Just for clarity of my answer to Dimitre Novatchev, and as I said you could do it, although in 90% of times you would want to use the document function, it is possible to pass a node fragment in through to a stylesheet from a C# application with AddParam, and could be used when you are generating that data from your own application, and don't need to write it (or where it isn't practical).
So for a stylesheet such as:
<?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="xml" indent="yes"/>
<xsl:param name="bonnie_tyler"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cd[ancestor::FirstXML]">
<xsl:copy>
<xsl:apply-templates select="msxsl:node-set($bonnie_tyler)//cd/*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
the First and Second XML as per your original question, you could use the pass the nodes in such as:
try
{
XslCompiledTransform xt = new XslCompiledTransform();
Assembly ass = Assembly.GetEntryAssembly();
Stream strm = ass.GetManifestResourceStream("ConsoleApplication1.testparam.xslt");
XmlTextReader xmr = new XmlTextReader(strm);
xt.Load(xmr);
xmr.Close();
XmlDocument originalDocument = new XmlDocument();
strm = ass.GetManifestResourceStream("ConsoleApplication1.FirstXML.xml");
originalDocument.Load(strm);
XmlDocument replacementNode = new XmlDocument();
strm = ass.GetManifestResourceStream("ConsoleApplication1.SecondXML.xml");
replacementNode.Load(strm);
XsltArgumentList arg = new XsltArgumentList();
arg.AddParam("bonnie_tyler", "", replacementNode);
StringBuilder htmlOutput = new StringBuilder();
XmlWriter writer = XmlWriter.Create(htmlOutput);
xt.Transform(originalDocument, arg, writer);
Console.Out.Write(htmlOutput.ToString());
}
which would give you your desired output (although obviously you wouldn't be loading the files from disk as here)

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"/>