XPATH to display based on conditional value - xslt

<chapter>
<concept>
<title>*********************</title>
.
.
</concept>
<sections>
<title>*******************</title>
</chapter>
In the above structure I would like to retrieve text from <concept><title> or <sections><title>. i.e. using one xpath condition i need the value with below conditions.
1) if <concept><title> is not appeared then <sections><title>. vice verso also..
2) Both the title are available there then I want to consider nearst node value. i.e. in above structure "<sections><title>" is latest node.

You want the "nearest" (first) of the two:
(/*/*[self::concept or self::sections]/title)[1]
If you want the "latest" (last) of them, use:
(/*/*[self::concept or self::sections]/title)[last()]
XSLT - based verification:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select=
"(/*/*[self::concept or self::sections]/title)[1]"/>
===============
<xsl:copy-of select=
"(/*/*[self::concept or self::sections]/title)[last()]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<chapter>
<concept>
<title>*********Title 1************</title>
.
.
</concept>
<sections>
<title>**********Title 2*********</title>
</sections>
</chapter>
the two XPath expressions are evaluated and their results are copied to the output:
<title>*********Title 1************</title>
===============
<title>**********Title 2*********</title>

So in other words, you just want the last concept/title or sections/title that occurs under chapter? This XPath should do it (assuming the current context is chapter:
(concept/title | sections/title)[last()]

Related

XSLT - Get String between commas

How can I get the value 'four' in XSLT?
<root>
<entry>(one,two,three,four,five,six)</entry>
</root>
Thanks in advance.
You didn't specify the XSLT version, so I assume version 2.0.
I also assume that word four is only a "marker", stating from which place
take the result string (between the 3rd and 4th comma).
To get the fragment you want, you can:
Use tokenize function to "cut" the whole content of entry
into pieces, using a comma as the cutting pattern.
Take the fourth element of the result array.
This expression can be used e.g. in a template matching entry.
So the example script can look like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="entry">
<xsl:copy>
<xsl:value-of select="tokenize(., ',')[4]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
For your input XML it gives:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<entry>four</entry>
</root>

How to select unique child nodes of all siblings in XSLT 1

I'm looking for the best way to get all unique (no duplicates) nested nodes of all sibling nodes. The node I'm am interested in is "Gases". The sibling nodes are "Content". My simplified XML:
<Collection>
<Content>
<Html>
<root>
<Gases>NO2</Gases>
<Gases>CH4</Gases>
<Gases>O2</Gases>
</root>
</Html>
</Content>
<Content>
<Html>
<root>
<Gases>NO2</Gases>
<Gases>CH4</Gases>
<Gases>CO</Gases>
<Gases>LEL</Gases>
<Gases>NH3</Gases>
</root>
</Html>
</Content>
</Collection>
Desired result: NO2 CH4 O2 CO LEL NH3
I'm new to XSLT so any help would be much appreciated. I've been trying to use XPATH, similar to here, but with no luck.
This XSLT stylesheet will produce the desired output. Note that it relies on there being no duplicate Gases element inside a single Content element.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<!-- Match Gases elements whose value does not appear in a Gases element inside a previous
Content element. -->
<xsl:template match="//Gases[not(. = ancestor::Content/preceding-sibling::Content//Gases)]">
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:template>
<!-- Need to override the built-in template for text nodes, otherwise they will still get
printed out. -->
<xsl:template match="text()"/>
</xsl:stylesheet>

Filter data from XSLT

I have a XSL file in which I am creating a field like this:
<ServiceText>
<xsl:value-of select="concat(Yrs,'-',Mos,'-',Days,'-',Hrs)" />
</ServiceText>
The values of 'Yrs,'-',Mos,'-',Days,'-',Hrs , I am receiving from a Web service response and assiging it to the XSL directly. I cannot do any modification to the data in code for these fields, because that is how the ocde is. All data manipulation is on the xslt.
I want to do a data filtering on xslt as follows:
if value of yrs =-1 then yrs=""
if value of mos =-1 then mos=""
if value of Days =-1 then Days=""
if value of Hrs =-1 then Hrs=""
How can I do it on the XSL file?
XSLT 2.0:
<xsl:template match="/root">
<ServiceText>
<xsl:value-of select="string-join((Yrs, Mos, Days, Hrs)[.!=-1],'-')" />
</ServiceText>
</xsl:template>
See link for a Working example
In XSLT 1.0 use something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*/*[not(.= -1)]">
<xsl:value-of select="concat(., substring('-', 2 - not(self::Hrs)))"/>
</xsl:template>
<xsl:template match="*/*[. = -1]"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<Yrs>-1</Yrs>
<Mos>7</Mos>
<Days>15</Days>
<Hrs>3</Hrs>
</t>
the wanted result is produced:
7-15-3
Do Note:
It seems that there is an assumption that the "-1" values form a contiguous group (right to left in the sequence Yrs, Mos,Days).
If this assumption is violated, it would be impossible to understand what is the missing part in 2013-10-8 -- is it the months or the days ?

XSLT how to remove unwanted output

I have an xsl stylesheet giving just about what is needed, except there are values outputted outside the tags, . Is there a way to remove them? The scenario is that the desired output is a total invoice amount for invoices that appear more than once. Each time the xslt is executed the parameter p1 contains the InvoiceNumber to total. The code below shows that parameter, p1, hardcoded to '351510'.
<?xml version="1.0" encoding="utf-8"?>
<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:template match="/Invoices/Invoice[InvoiceNumber=351510][1]/InvoiceNumber">
<xsl:copy>
<xsl:apply-templates select="/Invoices/Invoice[InvoiceNumber=351510][1]/InvoiceAmount"/>
</xsl:copy>
</xsl:template>
<xsl:param name="tempvar"/>
<xsl:template name="InvTotal" match="/Invoices/Invoice[InvoiceNumber=351510][1]/InvoiceNumber">
<xsl:variable name="p1" select="351510" />
<xsl:if test="/Invoices/Invoice/InvoiceNumber[. = $p1]">
<!--<xsl:if test="$test = $p1" >-->
<InvoiceAmount>
<xsl:value-of select="sum(../../Invoice[InvoiceNumber=351510]/InvoiceAmount)"/>
</InvoiceAmount>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Here is the input:
<Invoices>
- <Invoice>
<InvoiceNumber>351510</InvoiceNumber>
<InvoiceAmount>137.00</InvoiceAmount>
</Invoice>
- <Invoice>
<InvoiceNumber>351510</InvoiceNumber>
<InvoiceAmount>363.00</InvoiceAmount>
</Invoice>
- <Invoice>
<InvoiceNumber>351511</InvoiceNumber>
<InvoiceAmount>239.50</InvoiceAmount>
</Invoice>
</Invoices>
Here is the output:
<InvoiceAmount>500</InvoiceAmount>137.00351510363.00351511239.50
Here is desired output:
<InvoiceAmount>500</InvoiceAmount>
Also, thank you goes to lwburk who got me this far.
Adding
<xsl:template match="text()"/>
should help.
I do not get the same results as you posted (only 351510137.00351510363.00351511239.50, all the text nodes), and I do not know the purpose of tempvar (unused).
Since it appears that all you need is the sum of InvoiceAmount values for a specific InvoiceNumber, just keep it simple and ignore everything else:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="invoiceNumber"/>
<xsl:template match="/">
<InvoiceAmount>
<xsl:value-of select="sum(/Invoices/Invoice[InvoiceNumber=$invoiceNumber]/InvoiceAmount)"/>
</InvoiceAmount>
</xsl:template>
</xsl:stylesheet>
You can pass the InvoiceNumber to process via the parameter invoiceNumber, or you can hardcode it if you like (see version 1).
Note: should you prefer a number format like e.g. #.00 (fixed decimals) for the sum, then you can also use the format-number(…) function.
This transformation:
<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="pNum" select="351510"/>
<xsl:key name="kInvAmmtByNumber" match="InvoiceAmount"
use="../InvoiceNumber"/>
<xsl:variable name="vInvoiceAmounts" select=
"key('kInvAmmtByNumber', $pNum)"/>
<xsl:variable name="vIdInvAmount1" select=
"generate-id($vInvoiceAmounts[1])"/>
<xsl:template match="InvoiceAmount">
<xsl:if test="generate-id() = $vIdInvAmount1">
<InvoiceAmount>
<xsl:value-of select="sum($vInvoiceAmounts)"/>
</InvoiceAmount>
</xsl:if>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML file:
<Invoices>
<Invoice>
<InvoiceNumber>351510</InvoiceNumber>
<InvoiceAmount>137.50</InvoiceAmount>
</Invoice>
<Invoice>
<InvoiceNumber>351510</InvoiceNumber>
<InvoiceAmount>362.50</InvoiceAmount>
</Invoice>
<Invoice>
<InvoiceNumber>351511</InvoiceNumber>
<InvoiceAmount>239.50</InvoiceAmount>
</Invoice>
</Invoices>
produces exactly the wanted, correct result:
<InvoiceAmount>500</InvoiceAmount>
Explanation:
The wanted invoice number is passed to the transformation as the value of the external/global parameter $pNum .
We use a key that indexes all InvoiceAmount elements by their corresponding InvoiceNumber values.
Using that key we define the variable $vInvoiceAmounts that contains the node-set of all InvoiceAmount elements the value of whose corresponding InvoiceNumber element is the same as the value of the external parameter $pNum.
We also define a variable ($vIdInvAmount1) that contains a unique Id of the first such InvoiceAmount element.
There is a template that matches any InvoiceAmount element. It checks if the matched element is the first of the elements contained in the node-set $vInvoiceAmounts. If so, a InvoiceAmount element is generated with a single text-node child, whose value is the sum of all InvoiceAmount elements contained in $vInvoiceAmounts. Otherwise nothing is done.
Finally, there is a second template that matches any text node and does nothing (deletes it in the output), effectively overriding the unwanted side effect of the default XSLT processing -- the outputting of unwanted text.

I want to select all texts (including node names) under a node

I currently have a xml file like this:
<aaa>
<b>I am a <i>boy</i></b>.
</aaa>
How can I get the exact string as: <b>I am a <i>boy</i></b>.? Thanks.
You have to tell XSLT that you want to copy elements through as well. That can be done with an additional rule. Note that I use custom select clauses on my apply-templates elements to select attributes as well as all node-type objects. Also note that the rule for aaa takes precedence, and does not copy the aaa element itself to the output.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="aaa">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
<aaa>
<b>I am a <i>boy</i></b>.
</aaa>
How can I get the exact string as:
<b>I am a <i>boy</i></b>.?
The easiest/shortest way to do this in your case is to output the result of the following XPath expression:
/*/node()
This means: "Select all nodes that are children of the top element."
Of course, there are some white-space-only text nodes that we don't want selected, but XSLT can take care of this, so the XPath expression is just as simple as shown above.
Now, to get the result with an XSLT transformation, we use the following:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:copy-of select="/*/node()"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document, the wanted result is produced:
<b>I am a <i>boy</i></b>.
Do note:
The use of the <xsl:copy-of> xslt instruction (not <xsl:value-of>), which copies nodes, not string values.
The use of the <xsl:strip-space elements="*"/> XSLT instruction, directing the XSLT processor to ignore any white-space-only text node in the XML document.