how to select preceding nodes within a specific parent - xslt-1.0

Given the following xml
<Root>
<Employee>
<service>
<Record>xxx</Record>
<Record>yyy</Record>
</service>
<service>
<Record>xxx</Record>
<Record>yyy</Record>
<Record>zzz</Record>
</service>
</Employee>
<Employee>
<service>
<Record>xxx</Record>
<Record>yyy</Record>
</service>
<service>
<Record>xxx</Record>
<Record>yyy</Record>
<Record>zzz</Record>
</service>
</Employee>
</Root>
Using XSLT1.0, while transforming the xml for each <Employee> the <Record> field containing 'xxx','yyy','zzz' should occur only once in the result
<Root>
<Employee>
<Service>
<Record>xxx</Record>
<Record>yyy</Record>
<Record>zzz</Record>
</Service>
</Employee>
<Employee>
<Service>
<Record>xxx</Record>
<Record>yyy</Record>
<Record>zzz</Record>
<Service>
</Employee>
</Root>
In a for-each loop of Employee I tired using <xsl:if test='not(preceding::./service/Record=$record)'>. The test works fine for the first <Employee> taking the <Record> for 'xxx','yyy','zzz' only once. When the iteration goes to the next <Employee> the test condition checks for the <Record>values in the first<Employee> also and it finds preceding nodes already exisitng with the values'xxx','yyy','zzz', hence i am not able to get the records for the second <Employee>.
How to get the <Record>s in the second <Employee> . Any help is much appreciated.
Thanks

This transformation uses the Muenchian method for grouping:
<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:key name="kEmpRecordByVal" match="Employee/service/Record"
use="concat(generate-id(../..), '+', .)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Employee">
<Employee>
<xsl:apply-templates select=
"service/Record
[generate-id()
=
generate-id(key('kEmpRecordByVal',
concat(generate-id(../..), '+', .)
)[1]
)
]
"/>
</Employee>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (the provided one with different values for the second Employee to aid readability):
<Root>
<Employee>
<service>
<Record>xxx</Record>
<Record>yyy</Record>
</service>
<service>
<Record>xxx</Record>
<Record>yyy</Record>
<Record>zzz</Record>
</service>
</Employee>
<Employee>
<service>
<Record>aaa</Record>
<Record>bbb</Record>
</service>
<service>
<Record>aaa</Record>
<Record>bbb</Record>
<Record>ccc</Record>
</service>
</Employee>
</Root>
the wanted, correct result is produced:
<Root>
<Employee>
<Record>xxx</Record>
<Record>yyy</Record>
<Record>zzz</Record>
</Employee>
<Employee>
<Record>aaa</Record>
<Record>bbb</Record>
<Record>ccc</Record>
</Employee>
</Root>

Related

Concatenate XML in BPEL 2.0

Need your help for a requirement in BPEL 2.0. I have a collection in the below format
<FilesCollection>
<Files>
<transactionid>
<status>
<filename>
<Files>
<FilesCollection>
I would be getting several such collections while traversing through a ForEach loop.
Once I have exited the loop , I need to concatenate all the collections so that finally I get something as below
<FilesCollection>
<Files>
<transactionid>
<status>
<filename>
<Files>
<Files>
<transactionid>
<status>
<filename>
<Files>
<Files>
<transactionid>
<status>
<filename>
<Files>
<FilesCollection>
Please note that the number of FilesCollection element and the number of Files element appearing within it would be dynamic.
Please help me with this.
Thanks
Arijit
As i understand you have multiple FilesCollection in XML document and you want to wrap inside one then you need to do something like this:
Note: Suppose your root element is root in source XML
<?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"
exclude-result-prefixes="xs" version="1.0">
<xsl:template match="root">
<root>
<FilesCollection>
<xsl:copy-of select="FilesCollection/node()"/>
</FilesCollection>
</root>
</xsl:template>
</xsl:stylesheet>

How to get Xpath Value in Hive

I wanted to get the value of Hindi & English as an arry from the below xml using XPATH in Hive.
<employees>
<employee>
<name>Ranjith</name>
<language emp:langCode="HI">Hindi</language>
<city emp:country="india">Delhi</city>
<employee>
<employee>
<name>John</name>
<language emp:langCode="EN">English</language>
<city emp:country="america">Sunnyvale</city>
<employee>
</employees>
Can anyone help??
I have tried few options, but not works.
Generic xpath would be:
/employees/employee/language
But your xml has some errors in it. Here is the updated xml that works:
<employees>
<employee>
<name>Ranjith</name>
<language langCode="HI">Hindi</language>
<city country="india">Delhi</city>
</employee>
<employee>
<name>John</name>
<language langCode="EN">English</language>
<city country="america">Sunnyvale</city>
</employee>
</employees>
And here is the result using the shown xpath
Element='<language langCode="HI">Hindi</language>'
Element='<language langCode="EN">English</language>'

XSL v.1, Muenchian Grouping, Summing Line Items per Invoice, Call-Template

I'm trying to sum the aggregate of line item amounts in a group with multiple groups. That is, there are many Invoices with many Line Items per Invoice. I need to sum the Amounts of Line Items across each Invoice.
I've searched across various posts here on Stack, as well as various forums, and haven't been able to decipher the code to be able to sum values with a Muenchian Grouping approach. If there is already a solution for this, please point me in the right direction.
The grouping occurs on the recordId attribute.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<query>
<results total="6">
<result recordId="15918960" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$20.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918960" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$30.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918960" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$40.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918961" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$20.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918961" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$30.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918962" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$29.74]]></LI_Amount_display>
</column>
</columns>
</result>
</results>
</query>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:datetime="http://exslt.org/dates-and-times"
xmlns:exsl="http://exslt.org/common"
xmlns:fn="http://www.w3.org/2013/xpath-functions"
exclude-result-prefixes="datetime">
<xsl:output method="text" encoding="UTF-8" indent="no"/>
<xsl:key name="recordID" match="result" use="#recordId"/><!-- Define a key to use for grouping the results -->
<!-- Might be needed for nulls
<xsl:template match="/results/result/columns/column/LI_Amount_display[not(text()[normalize-space()])]">
<xsl:element name='LI_Amount_display' value="0.00"></xsl:element>
</xsl:template> -->
<xsl:template match="/">
<xsl:call-template name="fixTheWidth" >
<!-- This parameter is a Id for each group of records based on the result/#recordId attribute. This groups all records to the record ID-->
<xsl:with-param name="resultIndex" select="//results/result[generate-id(.) = generate-id(key('recordID', #recordId)[1])]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="fixTheWidth" match="/results">
<xsl:param name="resultIndex" /> <!-- A unique index based on grouping the records on the recordID -->
<!-- MORE CODE HERE USING $resultIndex has been redacted for simplicity-->
<xsl:for-each select="$resultIndex" >
<xsl:text> BEGIN | </xsl:text>
<xsl:value-of select="number(translate(substring(key('recordID',#recordId)/columns/column/LI_Amount_display,2),',',''))"></xsl:value-of>
<!-- <xsl:value-of select="sum(number(translate(substring(key('recordID',#recordId)/columns/column/LI_Amount_display,2),',','')))"></xsl:value-of>-->
</xsl:for-each>
<xsl:text> | END </xsl:text>
<!-- MORE CODE HERE USING $resultIndex has been redacted for simplicity-->
</xsl:template>
</xsl:stylesheet>
I realize that using the "call-template" strategy is not necessarily the "best" approach, but its what I've come to such that I can create a fixed width flat file and not have to refactor the entire script. If there's a way to accomplish this otherwise, I'm all ears.
You cannot sum nodes that aren't numbers. A value of $20.74 is a string, not a number. You must first convert the values to numbers by removing the currency symbol (and any other non-numeric characters, if they are allowed in the input), then proceed to group the resulting nodes and sum the groups.
Here's an example:
XSLT 1.0
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:key name="amt" match="amount" use="#id"/>
<xsl:template match="/query">
<xsl:variable name="amounts">
<xsl:for-each select="results/result">
<amount id="{#recordId}"><xsl:value-of select="translate(columns/column/LI_Amount_display, '$', '')"/></amount>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="amount-set" select="exsl:node-set($amounts)/amount" />
<xsl:for-each select="$amount-set[generate-id() = generate-id(key('amt', #id)[1])]" >
<xsl:text> BEGIN | </xsl:text>
<xsl:value-of select="sum(key('amt', #id))"/>
</xsl:for-each>
<xsl:text> | END </xsl:text>
</xsl:template>
</xsl:stylesheet>
Applied to your example input, the result is:
BEGIN | 92.22 BEGIN | 51.48 BEGIN | 29.74 | END
I couldn't understand the strategy of calling a template and "create a fixed width flat file". In any case, it seems unrelated to the question at hand. You might post a separate question, if necessary.

How to use parameter or variable values as node name?

I am trying to use the value of a parameter or variable as a node name inside a value-of select but so far failed..
So my XML is as below.
<Data>
<Name>John Smith</Name>
<Date>28112012</Date>
<Phone>iphone</Phone>
<Car>BMW</Car>
</Data>
And my incomplete xslt looks like below.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
exclude-result-prefixes="#all">
<xsl:param name="nodename" select="'Name'"/>
<xsl:template match="/Data">
<Output>
<xsl:value-of select="{$nodename}"/>
</Output>
</xsl:template>
</xsl:stylesheet>
Ideally I want the out put to be
<Output>John Smith</Output>
Is there any way I can do this using XSLT?
I want to be able to select appropriate node based on a users choice.
Thanks
SK
A wild guess, let me know if it works:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" exclude-result-prefixes="#all">
<xsl:param name="nodename" select="'Name'"/>
<xsl:template match="/Data">
<Output>
<xsl:value-of select="//*[name()=$nodename]" />
</Output>
</xsl:template>
</xsl:stylesheet>

XSLT-1.0 can a variable be used to access to other nodes?

With a simple XML like this
<value>
<num>
<accession>111</accession>
<sequence>AAA</sequence>
<score>4000</score>
</num>
</value>
I want to know if it is possible to access to a particular node from a node previously stored in a variable. The XSLT code is very short and explains better what I want to say
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/value/num">
<xsl:variable name="node">
<xsl:copy-of select="current()"/>
</xsl:variable>
<root>
<xsl:copy-of select="$node"/>
</root>
</xsl:template>
</xsl:stylesheet>
So I store the node in the variable "node". Then I can print the contents of the node with $node.
(EDIT) XML output
<root>
<num>
<accession>111</accession>
<sequence>AAA</sequence>
<score>4000</score>
</num>
</root>
What I want to do is to print the contents of a sub-node, like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/value/num">
<xsl:variable name="node">
<xsl:copy-of select="current()"/>
</xsl:variable>
<root>
<xsl:copy-of select="$node/accession"/>
</root>
</xsl:template>
</xsl:stylesheet>
But it gives an error (Component returned failure code: 0x80600008 [nsIXSLTProcessor.transformToFragment]) (check here)
(EDIT) The XML that I would want is
<root>
<accession>111</accession>
</root>
NOTE: The question is not how can I get this output. The question is how, using a variable as in the XSLT provided, can I get this output.
(EDIT:SOLVED)
Actually it is possible, but as pointed out in the comments, the value of a variable has to be assigned with the "select" attribute if a node-set is required. So this code was not working since the variable had a tree fragment instead of a node-set stored in it (read more information here)
Thanks!
Try this:
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/value">
<root>
<xsl:for-each select="num">
<xsl:variable name="node" select="current()" />
<xsl:copy-of select="$node/accession" />
</xsl:for-each>
</root>
</xsl:template>
</xsl:transform>
Note that I used xsl:transform instead of xsl:stylesheet. Also, consider using version 2.0 instead of 1.0 if you have a compliant processor, it adds a lot of useful features.
I still don't see your need for a variable, though.