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.
Related
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)"/>
I am trying to convert xml into pdf using xsl for some business requirement(can't share that code here). My requirement is to add a page break after completion of xml reading. Here we are reading that same xml two times and adding that result into a single pdf. For example , after reading the xml for the first time we get a pdf of 2 pages, So on reading the same xml second time , I want my result to start from third page of pdf and not on the second page. Any help would be appreciated.
Sample xml:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artit>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
</cd>
</catalog>
Say Using loop, I am reading this same xml twice and I want the result of each iteration of loop on separate pages.
Generate an fo:page-sequence (https://www.w3.org/TR/xsl11/#fo_page-sequence) for each catalog. By definition, each fo:page-sequence starts a new page.
Alternatively, the second time that you process the XML, add break-before="page" (https://www.w3.org/TR/xsl11/#break-before) or page-break-before="always" (https://www.w3.org/TR/xsl11/#page-break-before, and a shorthand for break-before="page") to the FO generated from the appropriate element.
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>
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)
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"/>