XSLT Adding in a for-each loop - xslt

Is there a way to create a for-each loop in XSLT that will add in the value of each node it loops through?
For Example, is there a way to create a loop to add together the price nodes to get a total price?
<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>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
<cd>
<title>Still got the blues</title>
<artist>Gary Moore</artist>
<country>UK</country>
<company>Virgin records</company>
<price>10.20</price>
<year>1990</year>
</cd>
Here is the current code I am trying to use to extract the price and hold it as a variable:
<xsl:template name="Amount-Calc">
<xsl:value-of select="sum(//cd[country = 'UK'])"/>>
<xsl:variable name="FullPrice" select="price" />
<xsl:variable name="dollars" select="substring($FullPrice,1,2)" />
<xsl:variable name="cents" select="substring($FullPrice,3,2)" />
</xsl:template>
I am not sure how to store the full price as a variable
My Expected output is:
<price=20.10/>
However when I do my calculation I am getting <price=NaN/>

Is there a way to create a for-each loop in XSLT that will add in the
value of each node it loops through?
It is neither possible, nor necessary.
It's not possible, because xsl:for-each is not a loop.
It's not necessary, because you can sum the nodes using an expression like sum(cd/price)from the context of the parent node (missing from your example).
Added:
Given a well-formed XML input:
<root>
<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>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
<cd>
<title>Still got the blues</title>
<artist>Gary Moore</artist>
<country>UK</country>
<company>Virgin records</company>
<price>10.20</price>
<year>1990</year>
</cd>
</root>
the following stylesheet:
<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:template match="/root">
<total-price>
<xsl:value-of select="format-number(sum(cd[country = 'UK']/price), '0.00')"/>
</total-price>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="UTF-8"?>
<total-price>20.10</total-price>

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 merge multiple xml files with tinyxml

I have multiple xml files that are structurally identical, looking like this:
firstFile.xml
<?xml version="1.0" encoding="UTF-8" ?>
<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/>
<YEAR/>
</CD>
</CATALOG>
secondFile.xml
<?xml version="1.0" encoding="UTF-8" ?>
<CD>
<TITLE>The dock of the bay</TITLE>
<ARTIST>Otis Redding</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Atlantic</COMPANY>
<PRICE>7.90</PRICE>
<YEAR>1987</YEAR>
</CD>
<CD>
<TITLE>Picture book</TITLE>
<ARTIST>Simply Red</ARTIST>
<COUNTRY>EU</COUNTRY>
<COMPANY>Elektra</COMPANY>
<PRICE>7.20</PRICE>
<YEAR>1985</YEAR>
</CD>
<CD>
<TITLE>Red</TITLE>
<ARTIST>The Communards</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>London</COMPANY>
<PRICE>7.80</PRICE>
<YEAR>1987</YEAR>
</CD>
</CATALOG>
My program uses TinyXML to load each of these files individually, without any problems. I am also able to perform all the other requirements of my program, using TinyXML
However, one of the goals of the program is to combine all such files, and then save it as a single file, that should look like this:
combined.xml
<?xml version="1.0" encoding="UTF-8" ?>
<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/>
<YEAR/>
</CD>
<CD>
<TITLE>The dock of the bay</TITLE>
<ARTIST>Otis Redding</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Atlantic</COMPANY>
<PRICE>7.90</PRICE>
<YEAR>1987</YEAR>
</CD>
<CD>
<TITLE>Picture book</TITLE>
<ARTIST>Simply Red</ARTIST>
<COUNTRY>EU</COUNTRY>
<COMPANY>Elektra</COMPANY>
<PRICE>7.20</PRICE>
<YEAR>1985</YEAR>
</CD>
<CD>
<TITLE>Red</TITLE>
<ARTIST>The Communards</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>London</COMPANY>
<PRICE>7.80</PRICE>
<YEAR>1987</YEAR>
</CD>
</CATALOG>
A bit of googling revealed some discussions on "deep copy". But I couldn't get a relevant sample code. Hope some good hack around here, can share some.
Thanks in advance.

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

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