Print each value of list element using xsl - xslt

I wonder if there is a way to loop on each Info element and take each value of Value element and Set it in temp variable using xsl
<list>
<Info>
<Child>John</Child>
<Value>5</Value>
</Info>
<Info>
<Child>Jo</Child>
<Value>6</Value>
</Info>
<Info>
<Child>Amanda</Child>
<Value>7</Value>
</Info>
</list>

Related

How to populate a value based on an element in XSL 1.0

My source sample XML is as below
<QuoteData>
<Components>
<Component ServiceOfferingId="XX" StartDate="07/03/2016" EndDate="12/31/9999" SerialOrderNbr="xx" StopReasonCode="" IsBundle="N">
<ServiceItem ItemType="xx" Type="2072" ModelFeature="24E" ProductId="2072 24E" SerialOrderNbr="xx" Quantity="1" StartDate="07/03/2016" EndDate="12/31/9999" CustomerId="xx" ServiceLevelId="xx"/>
</Component>
<Component ServiceOfferingId="yy" StartDate="07/03/2016" EndDate="12/31/9999" SerialOrderNbr="yy" StopReasonCode="" IsBundle="N">
<ServiceItem ItemType="yy" Type="2072" ModelFeature="24C" ProductId="2072 24C" SerialOrderNbr="yy" Quantity="1" StartDate="07/03/2016" EndDate="12/31/9999" CustomerId="yy" ServiceLevelId="yy"/>
<ServiceItem ItemType="zz" Type="2072" ModelFeature="24E" ProductId="2072 24E" SerialOrderNbr="zz" Quantity="1" StartDate="07/03/2016" EndDate="12/31/9999" CustomerId="zz" ServiceLevelId="zz"/>
</Component>
</Components>
<Descriptions>
<ProductDescription Id="2072 24E" Description="Customer EXPANSION"/>
<ProductDescription Id="2072 24C" Description="Customer CONTROL"/>
</Descriptions>
</QuoteData>
As per requirement each ServiceItem should be converted into a Line. So, in the above case, 3 lines shoule be created.
In the Target, I should be able to populate Product Description based on the Product ID value. Currently I am unable to do that using XSL 1.0
Expected target XML should be like below:
<Payload>
<Header></Header>
<Line>
<SerialOrderNbr>xx</SerialOrderNbr>
<ModelFeature>24E</ModelFeature>
<ProductId>2072 24E</ProductId>
<ProductDescription>Customer EXPANSION</ProductDescription>
</Line>
<Line>
<SerialOrderNbr>xx</SerialOrderNbr>
<ModelFeature>24C</ModelFeature>
<ProductId>2072 24C</ProductId>
<ProductDescription>Customer CONTROL</ProductDescription>
</Line>
<Line>
<SerialOrderNbr>xx</SerialOrderNbr>
<ModelFeature>24E</ModelFeature>
<ProductId>2072 24E</ProductId>
<ProductDescription>Customer EXPANSION</ProductDescription>
</Line>
</Payload>
I tried using different functions to populate the target description, but I am either getting the first Description value in all the lines or no value at all is being populated. I am able to achieve the required functionality using a while loop and assign activity in Jdev 11g.
How can I do this in XSLT?
Using the following stylesheet is easily possible:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" />
<xsl:template match="/QuoteData">
<Payload>
<xsl:for-each select="//ServiceItem">
<line>
<SerialOrderNbr><xsl:value-of select="#SerialOrderNbr" /></SerialOrderNbr>
<ModelFeature><xsl:value-of select="#ModelFeature" /></ModelFeature>
<ProductId><xsl:value-of select="#ProductId" /></ProductId>
<ProductDescription><xsl:value-of select="//ProductDescription[#Id = current()/#ProductId]/#Description" /></ProductDescription>
</line>
</xsl:for-each>
</Payload>
</xsl:template>
</xsl:stylesheet>
Its output is:
<?xml version="1.0"?>
<Payload>
<line>
<SerialOrderNbr>xx</SerialOrderNbr>
<ModelFeature>24E</ModelFeature>
<ProductId>2072 24E</ProductId>
<ProductDescription>Customer EXPANSION</ProductDescription>
</line>
<line>
<SerialOrderNbr>yy</SerialOrderNbr>
<ModelFeature>24C</ModelFeature>
<ProductId>2072 24C</ProductId>
<ProductDescription>Customer CONTROL</ProductDescription>
</line>
<line>
<SerialOrderNbr>zz</SerialOrderNbr>
<ModelFeature>24E</ModelFeature>
<ProductId>2072 24E</ProductId>
<ProductDescription>Customer EXPANSION</ProductDescription>
</line>
</Payload>
This is not exactly what you claimed to expect as output, because your version of the desired output seems to be erroneous. In your output version all SerialOrderNbrs have 'xx' as value, but in your source XML they are different.

Help with XSLT Transformation

I was hoping I could get a little assistance with an XSLT transform. I can't seem to get it right.
Here is a sample of the source xml document:
<?xml version="1.0" encoding="UTF-8"?>
<Locations>
<header>
<location>Location Field</location>
<job_function>Job Function Field</job_function>
<count>Count</count>
</header>
<data>
<location>2177</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>OPS</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>SLS</job_function>
<count>5</count>
</data>
<data>
<location>2179</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2179</location>
<job_function>SEC</job_function>
<count>1</count>
</data>
</Locations>
I want to transform it into the following format:
<Locations>
<data>
<PeopleSoftID>2177</PeopleSoftID>
<ADM>1</ADM>
<OPS>1</OPS>
<SLS>5</SLS>
<TotalCount>7</TotalCount>
</data>
<data>
<PeopleSoftID>2179</PeopleSoftID>
<ADM>1</ADM>
<SEC>1</SEC>
<TotalCount>2</TotalCount>
</data>
</Locations>
So basically, as you can see in the sample source document there are multiple elements that have the same value. In the destination document, there should now only be one record (<PeopleSoftID> element) per <location> element value in the source document. Since there were 3 <location> elements with the value of 2177, the destination document now has just 1 <PeopleSoftID> element that contains that value. The value of the <job_function> element in the source document becomes an element in the destination document. The value of that new element ends up being the sibling value of the <count> element from the source document. The <TotalCount> element in the destination document is the SUM of the values of all the new elements that are generated from the source <job_function> element.
I hope that explanation did not confuse anybody =).
I am a little new to XSLTs still so I am having trouble getting the logic right on this.
I can only use XSLT 1.0 too.
If I did not provide enough information let me know, and I will try to provide more as soon as I am able.
Thanks guys!
Read up on xsl:key and grouping with the Muenchian Method
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<!--Group the data elements by their location values -->
<xsl:key name="data-by-location" match="data" use="location" />
<xsl:template match="Locations">
<xsl:copy>
<!--Get a distinct list of location values,
using the Muenchian Method -->
<xsl:for-each
select="data[generate-id() =
generate-id(key('data-by-location', location)[1])]">
<xsl:copy>
<PeopleSoftID>
<xsl:value-of select="location"/>
</PeopleSoftID>
<!--For every data element matching this location... -->
<xsl:for-each select="key('data-by-location',location)">
<!--Create an element using the job_function
as the element name -->
<xsl:element name="{job_function}">
<!--The value of the count element
as the value of the generated element-->
<xsl:value-of select="count"/>
</xsl:element>
</xsl:for-each>
<TotalCount>
<!--calculate the sum of all the count element values
for this location -->
<xsl:value-of select="sum(key('data-by-location',
location)/count)"/>
</TotalCount>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Selecting the first and second node in XSLT

Given the following structure, how to copy the first and the second nodes with all their elements from the document based on the predicate in XSLT:
<list>
<slot>xx</slot>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
</list>
<list>
<slot>xx</slot>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
</list>
How to select the first and the second occurence of data (without the data element itself, only name, age) from the list, where the slot is equal to a different variable, i.e the first list has the slot=02, but I need the data from the second list, where the slot=01. But it does not really matter the order of the list by a slot as long as slot=$slotvariable.
I tried the following statement, but it did not produce any results:
<xsl:element name="{'Lastdata'}">
<xsl:copy-of select="list/data[position()=1 and slot = $slotvariable]" />
</xsl:element>
<xsl:element name="{'prevdata'}">
<xsl:copy-of select="list/data[position()=2 and slot = $slotvariable]" />
</xsl:element>
Any working suggestions would be appreciated
If I understood your question correctly, then:
<Lastdata>
<xsl:copy-of select="list[slot=$slotvariable]/data[1]/*" />
</Lastdata>
<prevdata>
<xsl:copy-of select="list[slot=$slotvariable]/data[2]/*" />
<prevdata>
Hints:
Don't use <xsl:element> unless you have a dynamic name based on an expression.
[1] is a shorthand for [position() = 1]
The following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="slot" select="'slot1'"/>
<xsl:template match="/lists/list">
<xsl:copy-of select="data[../slot=$slot][position()<3]/*"/>
</xsl:template>
</xsl:stylesheet>
Applied to this source:
<lists>
<list>
<slot>slot1</slot>
<data>
<name>George</name>
<age>7</age>
</data>
<data>
<name>Bob</name>
<age>22</age>
</data>
<data>
<name>James</name>
<age>77</age>
</data>
</list>
<list>
<slot>slot2</slot>
<data>
<name>Wendy</name>
<age>25</age>
</data>
</list>
</lists>
Produces the following result:
<name>George</name>
<age>7</age>
<name>Bob</name>
<age>22</age>

XSLT, XPath unique child nodes only problem where non unique nodes are not selected at all

<root>
<parent>
<child>
<name>John</name>
</child>
<child>
<name>Ben</name>
</child>
</parent>
<parent>
<child>
<name>John</name>
</child>
<child>
<name>Mark</name>
</child>
<child>
<name>Luke</name>
</child>
</parent>
</root>
I want unique child nodes only i.e. only one child node if there is more than one with the same name.
Such as:
John Ben Mark Luke
I have tried:
<xsl:for-each select="parent">
<xsl:for-each select="child[name != preceding::name]">
<xsl:value-of select="name"/>
</xsl:for-each>
</xsl:for-each>
But I get:
Ben Mark Luke
?!
Your problem is that you are using the != operator for comparison between a value and a node-set.
This is wrong -- always avoid using the != operator and always use the not() function and the = operator when one of the operands in the comparison is a node-set.
Below is a correct solution:
<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="/*">
<xsl:for-each select="parent">
<xsl:for-each select="child[not(name = preceding::name)]">
<xsl:value-of select="concat(name, ' ')"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<root>
<parent>
<child>
<name>John</name>
</child>
<child>
<name>Ben</name>
</child>
</parent>
<parent>
<child>
<name>John</name>
</child>
<child>
<name>Mark</name>
</child>
<child>
<name>Luke</name>
</child>
</parent>
</root>
the wanted, correct result is produced:
John Ben Mark Luke
Explanation: Here is how the W3C XPath 1.0 spec defines the semantics of the != operator:
"If one object to be compared is a
node-set and the other is a string,
then the comparison will be true if
and only if there is a node in the
node-set such that the result of
performing the comparison on the
string-value of the node and the other
string is true."
This means that
's' != node-set
is always true if there is even only one node in node-set that isn't equal to 's'.
This isn't the semantics that is wanted.
On the other side,
not('s' = node-set())
is true only only if there isn't a node in node-set that is equal to 's'.
This is exactly the wanted comparison.
Do note: The grouping technique you have chosen is O(N^2) and should only be used on very small sets of values to be dedupped. If efficiency is needed, by all means use the Muenchian method for grouping (discussing or demo-ing this falls outside the scope of this question).

XSLT - Problem with foreach and incremented attribute name

I'm using XSLT and would like to transform this:
<attr>
<header name="UpdateInformation1">
<detail name="info">blah</detail>
</header>
<header name="UpdateInformation2">
<detail name="info">blah2</detail>
</header>
...other headers with different names...
</attr>
To this:
<UpdateInformation>
<info>blah</info>
</UpdateInformation>
<UpdateInformation>
<info>blah2</info>
</UpdateInformation>
...
I've been trying to do this using a foreach, but I'm not having much success. Heres what I currently have, but wildcards don't work in this type of context:
* WRONG *
<xsl:for-each select="attr/header[#name='UpdateInformation*']">
<UpdateInformation>
<Info>
<xsl:value-of select="detail[#name='info']"/>
</info>
</UpdateInformation>
</xsl:for-each>
* WRONG *
Any suggestions? Thanks!
Use something like this:
<xsl:for-each select="attr/header[starts-with(#name, 'UpdateInformation')]">
<UpdateInformation>
<Info>
<xsl:value-of select="detail[#name='info']"/>
</info>
</UpdateInformation>
</xsl:for-each>
EDITED: Corrected XPath expression per comments (below).
Doing this with the xsl:for-each element:
<xsl:for-each select="header[starts-with(#name, 'UpdateInformation')]">
<UpdateInformation>
<Info>
<xsl:value-of select="detail"/>
</info>
</UpdateInformation>
</xsl:for-each>
Using a xsl:template would be a better way to do this in xslt, as this is the strength of it:
<xsl:template match="header[starts-with(#name, 'UpdateInformation')]">
<UpdateInformation>
<Info>
<xsl:value-of select="detail"/>
</info>
</UpdateInformation>
</xsl:template>