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.
Related
I'm trying to understand the usage of keys and can't get my example to work.
Starting with this XML:
<items>
<item>Blue</item>
<item>Green</item>
<item>Orange</item>
</items>
I want to get this output XML:
<items>
<item>PURPLE</item>
<item>BLACK</item>
<item>PINK</item>
</items>
I defined the mapping directly in a variable in the XSLT transformation:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="mappings">
<mapping orig="Blue" repla="PURPLE"/>
<mapping orig="Green" repla="BLACK"/>
<mapping orig="Orange" repla="PINK"/>
</xsl:variable>
<xsl:key name="mappingsKey" match="$mappings/mapping" use="#orig"/>
<xsl:template match="items">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:value-of select="key('mappingsKey',.)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I know I'm missing the instruction to tell which is the replacement value, but don't know how to define it.
Example available here: https://xsltfiddle.liberty-development.net/ejivdHp/1
Thanks.
The $mappings variable is in a different document than the processed XML. You need to point the key() function to there. And you also need to select the repla attribute:
<xsl:value-of select="key('mappingsKey', ., $mappings)/#repla"/>
Referencing the variable in the match pattern of the xsl:key element is meaningless.
https://xsltfiddle.liberty-development.net/ejivdHp/2
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 can i copy an entire xml as is in an Variable?
Below is the sample xml:
<?xml version="1.0" encoding="UTF-8"?>
<products author="Jesper">
<product id="p1">
<name>Delta</name>
<price>800</price>
<stock>4</stock>
</product>
</products>
I have tried below xslt but it is not working.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:variable name="reqMsg">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:variable>
<xsl:copy-of select="$reqMsg"/>
</xsl:template>
</xsl:stylesheet>
Regards,
Rahul
Your transformation fails because at a certain point, it tries to create a variable (result tree fragment) containing an attribute node. This is not allowed.
It's not really clear what you mean by "copying an entire XML to a variable". But you probably want to simply use the select attribute on the root node:
<xsl:variable name="reqMsg" select="/"/>
This will actually create variable with a node-set containing the root node of the document. Using this variable with xsl:copy-of will output the whole document.
<xsl:copy-of select="document('path/to/file.xml')" />
Or if you need it more than once, to avoid repeating the doc name:
<xsl:variable name="filepath" select="'path/to/file.xml'" />
…
<xsl:copy-of select="document($filepath)" />
The result of document() should be cached IIRC, so don't worry about calling it repeatedly.
My source XML looks:
<test>
<text1>Test</text1>
<text2>Test</text2>
<text2>Test</text2>
<section>
<text1>Test<bold>content</bold></text1>
<text1>Test</text1>
<text2>Test</text2>
<text2>Test</text2>
</section>
</test>
Want to extract the value of 6th node, based on the absolute number of the element (overall count). The absolute number of the element has been identified using <xsl:number level="any" from="/" count="*"/>.
The XPath expression /descendant::*[6] should give you the element you need.
<xsl:template match="/">
<xsl:copy-of select="/descendant::*[6]" />
</xsl:template>
outputs
<text1>Test<bold>content</bold></text1>
Note that this is an example of the difference between descendant:: and // - //*[6] would give you all elements that are the sixth child element of their respective parent, rather than simply the sixth element in the document in depth-first order.
This xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="allElements" select="//element()" />
<xsl:template match="/">
<output>
<xsl:value-of select="$allElements[6]" />
</output>
</xsl:template>
</xsl:stylesheet>
will result in
<?xml version="1.0" encoding="UTF-8"?>
<output>Testcontent</output>
I want to check if in my XML exists node that has type attribute containing string type_attachment_.
Is it a correct way to check it?
<xsl:if test="count(*[contains(#Type, 'type_attachment_')]) > 0">
something
</xsl:if>
I don't know how nested can this node be. It can be for example as simple as that:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"?>
<hello-world>
<greeter>
<dsdsds>An XSLT Programmer
<greeting type = 'type_attachment_'>Hello, World!
</greeting>
</dsdsds>
</greeter>
</hello-world>
but can also contain this node nested in different other elements.
Expressions that match existing nodes are truthy. Expressions that do not match any nodes are falsy.
Therefore, you don't need to count the set of nodes returned. Simply test to see if anything matches.
<xsl:if test="*[contains(#Type, 'type_attachment')]">
something
</xsl:if>
Find out an example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:param name="filt">
<filters>
<ritem type="type_attachment_" relateditemnumber="8901037"/>
<ritem relateditemnumber="8901038"/>
<ritem type="type_attachment_" relateditemnumber="8901039"/>
<ritem relateditemnumber="8901040"/>
</filters>
</xsl:param>
<xsl:template match="/">
<xsl:for-each select="$filt/filters/ritem[#type='type_attachment_']">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
<ritem type="type_attachment_" relateditemnumber="8901037"/>
<ritem type="type_attachment_" relateditemnumber="8901039"/>