Multiply nodes value using XSLT - xslt

I need to get a value which is coming from two different nodes in the same XML file. For instance, my xml:
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>50</value>
</asset>
<exchangeRates>
<USD>
<USD>1</USD>
<EUR>0.73</EUR>
</USD>
</exchangeRates>
and I want to get equivalent of 50 Dollars in Euro.
I tried :
<xsl:value-of select="(Asset/value * /exchangeRates[node() = curr]/curr_wanted)"/>
But it didn't work. Also I have to use XSLT 1.0. How can I get that value in Euro?

I did not test it very much but for input like
<?xml version="1.0" encoding="UTF-8"?>
<root>
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>50</value>
</asset>
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>25</value>
</asset>
<exchangeRates>
<USD>
<USD>1</USD>
<EUR>0.73</EUR>
</USD>
</exchangeRates>
</root>
something like following could work
for $asset in /root/asset, $rate in /root/exchangeRates
return $asset/value*$rate/*[name() = $asset/curr]/*[name() = $asset/curr_wanted]
But it will work only in xpath 2.0 and it also depends on the whole input xml (like if there might exist more asset elements, more exchangeRates elements, etc.).
Edit: In xslt 1.0 you could use xsl:variable to store some information and prevent them from context changes during xpath evaluation. Look for example at following template
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="text" />
<!-- Store "exchangeRates" in a global variable-->
<xsl:variable name="rates" select="/root/exchangeRates" />
<xsl:template match="/root">
<xsl:apply-templates select="asset" />
</xsl:template>
<xsl:template match="asset">
<!-- Store necessary values into local variables -->
<xsl:variable name="currentValue" select="value" />
<xsl:variable name="currentCurrency" select="curr" />
<xsl:variable name="wantedCurrency" select="curr_wanted" />
<xsl:variable name="rate" select="$rates/*[name() = $currentCurrency]/*[name() = $wantedCurrency]" />
<!-- Some text to visualize results -->
<xsl:value-of select="$currentValue" />
<xsl:text> </xsl:text>
<xsl:value-of select="$currentCurrency" />
<xsl:text> = </xsl:text>
<!-- using variable to prevent context changes during xpath evaluation -->
<xsl:value-of select="$currentValue * $rate" />
<!-- Some text to visualize results -->
<xsl:text> </xsl:text>
<xsl:value-of select="$wantedCurrency" />
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
which produces following output for input xml above.
50 USD = 36.5 EUR
25 USD = 18.25 EUR

Related

XSL - Sum of computed amounts

I just need some help on basic feature of XSL.
I would like to display a sum of amounts previously computed. But I do not know how to do it.
For information the XSL must work with XSLT 1.0, technical limitation on my side.
For instance here is my xml.
<A>
<amount>10</amount>
<rate>4</rate>
</A>
<A>
<amount>-21</amount>
<rate>2</rate>
</A>
<B>
<amount>8</amount>
<rate>1</rate>
</B>
<C>
<amount>7</amount>
<rate>32</rate>
</C>
and I would like to display the sum of each amount multiplied by each associated rate within a Total element.
<Total value="230">
<PositiveTotal>
272
</PositiveTotal>
<NegativeTotal>
-42
</NegativeTotal>
</Total>
I have no idea how to do it.
Thanks in advance
Regards,
One of possible multiple solutions. It will give you an idea, how to solve this.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<xsl:variable name="positiveTotal">
<xsl:call-template name="sum">
<xsl:with-param name="items" select="*[not(starts-with(amount,'-') or starts-with(rate, '-'))]"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="negativTotal">
<xsl:call-template name="sum">
<xsl:with-param name="items" select="*[starts-with(amount,'-') or starts-with(rate, '-')]"/>
</xsl:call-template>
</xsl:variable>
<Total value="{$positiveTotal + $negativTotal}">
<PositivTotal>
<xsl:value-of select="format-number($positiveTotal, '0')"/>
</PositivTotal>
<NegativeTotal>
<xsl:value-of select="format-number($negativTotal, '0')"/>
</NegativeTotal>
</Total>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="items" />
<xsl:param name="total" select="0" />
<xsl:choose>
<xsl:when test="not($items)">
<xsl:value-of select="$total"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sum">
<xsl:with-param name="items" select="$items[position() > 1]" />
<xsl:with-param name="total"
select="$total + ($items[1]/amount * $items[1]/rate)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
!! Change match="root" to your root-node! Given source-xml is not valid.
There are already many sum-questions! See the Related Box on your right side of screen.
This question has been asked many times and following the links to similar searches on SO should give you lots of ideas.
Computing the sum of computed values in XSLT 2.0 is trivial, but in XSLT 1.0 it isn't easy because there's no such data type in its data model as a set of numbers (sum() only works over a set of nodes). Possible solutions include:
(a) a recursive template call which supplies the running total as a parameter to the template, adds the next value, then calls the template again to process the rest of the list with the new running total
(b) a multiphase transformation where the computed values are placed in XML nodes during one phase, and summed using the sum() function in a second phase
(c) use of the FXSL library which uses xsl:apply-templates to simulate higher-order functions and then provides a fold mechanism which can be specialised to implement summation.
(d) calling out to extension functions in a procedural programming language
(e) upgrading to XSLT 2.0.
I would suggest you do it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<!-- first pass -->
<xsl:variable name="summands-rtf">
<xsl:for-each select="*">
<value>
<xsl:value-of select="amount * rate" />
</value>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="summands" select="exsl:node-set($summands-rtf)/value" />
<!-- output -->
<Total value="{sum($summands)}">
<PositiveTotal>
<xsl:value-of select="sum($summands[. > 0])" />
</PositiveTotal>
<NegativeTotal>
<xsl:value-of select="sum($summands[. < 0])" />
</NegativeTotal>
</Total>
</xsl:template>
</xsl:stylesheet>
When applied to a well-formed XML input (with a single root element):
XML
<root>
<A>
<amount>10</amount>
<rate>4</rate>
</A>
<A>
<amount>-21</amount>
<rate>2</rate>
</A>
<B>
<amount>8</amount>
<rate>1</rate>
</B>
<C>
<amount>7</amount>
<rate>32</rate>
</C>
</root>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<Total value="230">
<PositiveTotal>272</PositiveTotal>
<NegativeTotal>-42</NegativeTotal>
</Total>

XSLT Appending incremented value to existing attribute value

For my input XML, I have written the XSLT , But I cannot make the XSLT to generate the <mynewtag> correctly. Please help.
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
Expected Output:
<?xml version="1.0" encoding="UTF-8"?>
<newbooks>
<newbook>
<mynewtag id="book1" />
<title>charithram</title>
<author>sarika</author>
</newbook>
<newbook>
<mynewtag id="book2" />
<title>doublebell</title>
<author>psudarsanan</author>
</newbook>
</newbooks>
XSLT that I tried: [I understand the syntax is incorrect for <mynewtag> ]. But I don't know to fix it to get the desired output.
<?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="xml" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:template match="/">
<newbooks>
<xsl:for-each select="books/child::*">
<newbook>
<mynewtag id="book<xsl:number value='position()' format='1' />" />
<title>
<xsl:value-of select="title" />
</title>
<author>
<xsl:value-of select="author" />
</author>
</newbook>
</xsl:for-each>
</newbooks>
</xsl:template>
</xsl:stylesheet>
Trying in Online XSLT transformer , http://www.freeformatter.com/xsl-transformer.html
I tried assigning the position to a variable, but still I face the same problem of not knowing how to append it with the attribute value book .
<xsl:variable name="cnt">
<xsl:number value='position()' format='1' />
</xsl:variable>
<xsl:value-of select = "$cnt" />
Note: If I remove the <xsl:number value='position()' format='1' /> from XSL, then the syntax is correct, but then I won't be able to generate book1 book2 etc as <mynewtag> attribute values.
Please help.
Added: <mynewtag> is a required element. It is like any other XML element like <title> that is required in output. It is not an element just to hold the attribute id. Sorry if there is a confusion on this.
Adding Solution here, from the answers obtained to summarize:
<mynewtag>
<xsl:attribute name="id">
<xsl:text>book</xsl:text>
<xsl:number value='position()'/>
</xsl:attribute>
</mynewtag>
or shortly:
<mynewtag id="book{position()}" />"
or
<newbook>
<xsl:variable name="cnt">
<xsl:number value='position()' format='1' />
</xsl:variable>
<mynewtag id="book{$cnt}" />
..........
Also the attribute value templates that IanRoberts mentioned.
You can't place a tag inside another tag. Try either:
<mynewtag>
<xsl:attribute name="id">
<xsl:text>book</xsl:text>
<xsl:number value='position()'/>
</xsl:attribute>
</mynewtag>
or shortly:
<mynewtag id="book{position()}" />"
ADDED:
Not directly related to your question, but I believe the id attribute should be applied to the parent <newbook> element, instead of creating an artificial child element to hold it.

XSLT 2.0 to 1.0

Lots of help here to convert XSLT 1.0 to 2.0, but I need to go the other way!
OK, I have an XSL file working in XSLT 2.0: I need to convert this file to XSLT 1.0. Right now, I know the transformation is getting hung up on "result-document" but I'm not sure how to fix this. And, I'm not sure if my other info will translate to 1.0: will my value-of select= statements still work? help! (thanks)!
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:result-document href="output.xml" method="xml">
<playlist>
<xsl:for-each select="//dict[preceding-sibling::*[1]='Tracks']/dict">
<Track>
<Artist>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Artist']"/>
</Artist>
<Album>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Album']"/>
</Album>
<Songname>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Name']"/>
</Songname>
<TrackID>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Track ID']"/>
</TrackID>
<TagID>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Tag ID']"/>
</TagID>
</Track>
</xsl:for-each>
<xsl:for-each select="//dict[preceding-sibling::*[1]='Upload Information']">
<Description>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Playlist Description']"/>
</Description>
<Title>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Playlist Title']"/>
</Title>
<Username>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Username']"/>
</Username>
<Token>
<xsl:value-of select="child::*[preceding-sibling::*[1] = 'Token']"/>
</Token>
</xsl:for-each>
</playlist>
</xsl:result-document>
</xsl:template>
Well considering that result-document is new in XSLT 2.0 and that XSLT 1.0 without processor specific extension does not allow creating of multiple result documents at all it is in general not possible to translate stylesheets making use of result-document to XSLT 1.0.
However in your snippet the result-document instruction is inside the template matching the single / document node so you could simply remove it and use
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<playlist>
<xsl:for-each select="//dict[preceding-sibling::*[1]='Tracks']/dict">
<Track>
<Artist>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Artist']"/>
</Artist>
<Album>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Album']"/>
</Album>
<Songname>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Name']"/>
</Songname>
<TrackID>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Track ID']"/>
</TrackID>
<TagID>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Tag ID']"/>
</TagID>
</Track>
</xsl:for-each>
<xsl:for-each select="//dict[preceding-sibling::*[1]='Upload Information']">
<Description>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Playlist Description']"/>
</Description>
<Title>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Playlist Title']"/>
</Title>
<Username>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Username']"/>
</Username>
<Token>
<xsl:value-of select="child::*[preceding-sibling::*[1] = 'Token']"/>
</Token>
</xsl:for-each>
</playlist>
</xsl:template>
Of course you would then need to ensure you run your XSLT processor with the right arguments to write its result to output.xml.
As for value-of, check whether any of the used expressions selects multiple nodes in your input XML documents. But you only need to check if the stylesheet had once version="2.0" and was run that way with an XSLT 2.0 processor. Your posted snippet has version="1.0", that way even an XSLT 2.0 processor would run it in backwards compatible mode where value-of semantics (output string value of first selected node) is used.
So the value-of only need adaption if with version="2.0" they output multiple values and you want to preserve that with version="1.0". You need to use for-each then e.g. by replacing
<xsl:value-of select="child::*[preceding-sibling::*[1] = 'Token']"/>
with
<xsl:for-each select="child::*[preceding-sibling::*[1] = 'Token']">
<xsl:if test="position() > 1"><xsl:text> </xsl:text></xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
The only problem with your code with xslt-1.0 would be the <xsl:result-document href="output.xml" method="xml">. This is not supported for xslt-1.0
One possible solution could be to remove this (xsl:result-document) and redirect the "stdout" output to a file. This depends on the tools you are using.
Also some xlst processor support some extensions. For example wiht xsltproc you can replace
xsl:result-document with xsl:document.

XSLT: Replace string with Abbreviations

I would like to know how to replace the string with the abbreviations.
My XML looks like below
<concept reltype="CONTAINS" name="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" type="NUM">
<code meaning="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" value="18074-5" schema="LN" />
<measurement value="5.7585187646">
<code value="cm" schema="UCUM" />
</measurement>
<content>
<concept reltype="HAS ACQ CONTEXT" name="Image Mode" type="CODE">
<code meaning="Image Mode" value="G-0373" schema="SRT" />
<code meaning="2D mode" value="G-03A2" schema="SRT" />
</concept>
</content>
</concept>
and I am selecting some value from the xml like,
<xsl:value-of select="concept/measurement/code/#value"/>
Now what I want is, I have to replace cm with centimeter. I have so many words like this. I would like to have a xml for abbreviations and replace from them.
I saw one similar example here.
Using a Map in XSL for expanding abbreviations
But it replaces node text, but I have text as attribute. Also, it would be better for me If I can find and replace when I select text using xsl:valueof select instead of having a separate xsl:template. Please help. I am new to xslt.
I have created XSLT v "1.1". For abbreviations I have created XML file as you have mentioned:
Abbreviation.xml:
<Abbreviations>
<Abbreviation>
<Short>cm</Short>
<Full>centimeter</Full>
</Abbreviation>
<Abbreviation>
<Short>m</Short>
<Full>meter</Full>
</Abbreviation>
</Abbreviations>
XSLT:
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" />
<xsl:param name="AbbreviationDoc" select="document('Abbreviation.xml')"/>
<xsl:template match="/">
<xsl:call-template name="Convert">
<xsl:with-param name="present" select="concept/measurement/code/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="Convert">
<xsl:param name="present"/>
<xsl:choose>
<xsl:when test="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]">
<xsl:value-of select="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]/Full"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$present"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
INPUT:
as you have given <xsl:value-of select="concept/measurement/code/#value"/>
OUTPUT:
centimeter
You just need to enhance this Abbreviation.xml to keep short and full value of abbreviation and call 'Convert' template with passing current value to get desired output.
Here a little shorter version:
- with abbreviations in xslt file
- make use of apply-templates with mode to make usage shorter.
But with xslt 1.0 node-set extension is required.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="abbreviations_txt">
<abbreviation abbrev="cm" >centimeter</abbreviation>
<abbreviation abbrev="m" >meter</abbreviation>
</xsl:variable>
<xsl:variable name="abbreviations" select="exsl:node-set($abbreviations_txt)" />
<xsl:template match="/">
<xsl:apply-templates select="concept/measurement/code/#value" mode="abbrev_to_text"/>
</xsl:template>
<xsl:template match="* | #*" mode="abbrev_to_text">
<xsl:variable name="abbrev" select="." />
<xsl:variable name="long_text" select="$abbreviations//abbreviation[#abbrev = $abbrev]/text()" />
<xsl:value-of select="$long_text"/>
<xsl:if test="not ($long_text)">
<xsl:value-of select="$abbrev"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

XSLT - how to apply a template to every node of the type?

I am quite new to xsl and functional programming, so I'll be grateful for help on this one:
I have a template that transforms some xml and provides an output. The problem is that there are many elements of type xs:date, all in different contexts, that must be localized. I use a concatenation of substrings of these xs:dates to produce a localized date pattern strings.
As you can guess this causes a lot of copy-paste "substring-this and substring-that". How can I write a template that will automatically transform all the elements of type xs:date to localized strings preserving all the context-aware transformations?
My xsl is something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/">
...
<input value="{substring(/select/a/date 9,2)}.{substring(/select/a/date, 6,2)}.{substring(/select/a/date 1,4)}">
...
<!-- assume that following examples are also with substrings -->
<div><xsl:value-of select="different-path/to_date"/></div>
...
<table>
<tr><td><xsl:value-of select="path/to/another/date"/></td></tr>
</table>
<apply-templates/>
</xsl:template>
<xsl:template match="something else">
<!-- more dates here -->
</xsl:template>
</xsl:stylesheet>
I hope I managed to make my question clear =)
UPD: Here is an example of xml:
<REQUEST>
<header>
<... />
<ref>
<ref_date type="xs:date">1970-01-01</ref_date>
</ref>
</header>
<general>
<.../>
<info>
<.../>
<date type="xs:date">1970-01-01</date>
<ExpireDate type="xs:date">1970-01-01</ExpireDate>
<RealDate type="xs:date">1970-01-01</RealDate>
<templateDetails>template details</templateDetails>
<effectiveDate type="xs:date">1970-01-01</effectiveDate>
</info>
<party>
<.../>
<date type="xs:date">1970-01-01</date>
</party>
<!-- many other parts of such kind -->
</general>
</REQUEST>
As for the output, there are many different options. The main thing is that these values must be set as a value of different html objects, such as tables, input fields and so on. You can see an example in the xsl listing.
P.S. I'm using xsl 1.0.
If you did a schema-aware XSLT 2.0 transformation, you wouldn't need all those type='xs:date' attributes: defining it in the schema as a date would be enough. You could then match the attributes with
<xsl:template match="attribute(*, xs:date)">
What you could do is add a template to match any element which has an #type attribute of 'xs:date', and do you substring manipulation in there
<xsl:template match="*[#type='xs:date']">
<xsl:value-of select="translate(., '-', '/')" />
</xsl:template>
In this case I am just replacing the hyphens by slashes as an example.
Then, instead of using xsl:value-of....
<div><xsl:value-of select="different-path/to_date"/></div>
You could use xsl:apply-templates
<div><xsl:apply-templates select="different-path/to_date"/></div>
Consider this XSLT as an example
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[#type='xs:date']">
<xsl:copy>
<xsl:value-of select="translate(., '-', '/')" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In this case, all this XSLT is doing is copying the XML document as-is, but changing the date elements.
If you wanted to use the date template for other elements, or values, you could also make it a named-template, like so
<xsl:template match="*[#type='xs:date']" name="date">
<xsl:param name="date" select="." />
<xsl:value-of select="translate($date, '-', '/')" />
</xsl:template>
This would allow you to also call it much like a function. For example, to format a data and add as an attribute you could do the following:
<input>
<xsl:attribute name="value">
<xsl:call-template name="date">
<xsl:with-param name="date" select="/select/a/date" />
</xsl:call-template>
</xsl:attribute>
</input>