Selecting the first and second node in XSLT - 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>

Related

xslt transformation of a xml based on the value in a node variable incremental xml

i have a java based application entering data into a table with columns by name fieldName and its fieldValue am trying to re group for another use
in an array format i have tried using for-each to get the substring after the customer and before : but its not working that way
INPUT:
<Data>
<fieldName>customer0:fname</fieldName>
<fieldValue>fremont</fieldValue>
</Data>
<Data>
<fieldName>customer0:mname<</fieldName>
<fieldValue>u</fieldValue>
</Data>
<Data>
<fieldName>customer0:Lname<</fieldName>
<fieldValue>usa</fieldValue>
</Data>
<Data>
<fieldName>customer1:fname</fieldName>
<fieldValue>Hyd</fieldValue>
</Data>
<Data>
<fieldName>customer1:mname<</fieldName>
<fieldValue>M</fieldValue>
</Data>
<Data>
<fieldName>customer1:Lname<</fieldName>
<fieldValue>india</fieldValue>
</Data>
OUTPUT:
I am trying to convert this into below format
<responsexml>
<ResponseList>
<firstname>fremont</firstname>
<middlename>u</middlename>
<lastname>usa</lastname>
</ResponseList>
<ResponseList>
<firstname>hyd</firstname>
<middlename>M</middlename>
<lastname>india</lastname>
</ResponseList>
</responsexml>
The following XSL applied to the following XML works.
First I do a grouping on the customer (key function). I am considering that everything that comes before ":" is the customer number.
After, for each instance inside the grouping, I test the fieldName to check if it is the first, last or middle name (all options) and then I add the value to the right node.
XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:key name="Customer" match="Data" use="substring-before(fieldName, ':')" />
<xsl:template match="root">
<responsexml>
<xsl:for-each select="Data[generate-id() = generate-id(key('Customer', substring-before(fieldName, ':'))[1])]">
<ResponseList>
<xsl:for-each select="key('Customer', substring-before(fieldName, ':'))">
<xsl:choose>
<xsl:when test="substring-after(fieldName, ':') = 'fname'">
<firstname>
<xsl:value-of select="fieldValue"/>
</firstname>
</xsl:when>
<xsl:when test="substring-after(fieldName, ':') = 'mname'">
<middlename>
<xsl:value-of select="fieldValue"/>
</middlename>
</xsl:when>
<xsl:when test="substring-after(fieldName, ':') = 'Lname'">
<lastname>
<xsl:value-of select="fieldValue"/>
</lastname>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</ResponseList>
</xsl:for-each>
</responsexml>
</xsl:template>
</xsl:stylesheet>
XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
<Data>
<fieldName>customer0:fname</fieldName>
<fieldValue>fremont</fieldValue>
</Data>
<Data>
<fieldName>customer0:mname</fieldName>
<fieldValue>u</fieldValue>
</Data>
<Data>
<fieldName>customer0:Lname</fieldName>
<fieldValue>usa</fieldValue>
</Data>
<Data>
<fieldName>customer1:fname</fieldName>
<fieldValue>Hyd</fieldValue>
</Data>
<Data>
<fieldName>customer1:mname</fieldName>
<fieldValue>M</fieldValue>
</Data>
<Data>
<fieldName>customer1:Lname</fieldName>
<fieldValue>india</fieldValue>
</Data>
</root>

XSL for-each filter with subcontext from other main-node

i've already a new problem.
I have to merge lists via xsl, problem on this is that the key in the lookup-list must be concatinated by 2 values.
The lists can be relative large with thousands,ten-thousands in some cases even more of entries in both lists. In advance of large sizes of this lists, i have to look on performance and memory. It could be that this will later implemented in an web-service-client, so it must run quick and resource-saving.
Merging the exisiting Elements in List1 and List2 is done and was not complicated, but now i have to check both lists on non-exisiting elements in other list.
I tried to negate the for-each select statement but failed and it is presumably the wrong way.
InputXML-example
<ROOT>
<getObjectListResponse>
<item>
<Key>1111111:aaaa</Key>
<someOhterData>Text</someOhterData>
</item>
<item>
<Key>2222222:bbbb</Key>
<someOhterData>Text</someOhterData>
</item>
<item>
<Key>3333333:aaaa</Key>
<someOhterData>Text</someOhterData>
</item>
</getObjectListResponse>
<LookupList>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
</LookupList>
</ROOT>
The first part, find the existing parts in both lists is already done.
The second part is to find non-existing parts in List 1 to List 2 and List 2 to List 1.
I wanna like to do this in for-each, so you get only non-exisiting entries from List1 which does not exists in List2.
My Problem ist to lookup in for-each context with an concatinated key, from all DATA in LookupList.
<xsl:for-each select="/*/getObjectListResponse/item[Key/text() != /*/LookupList/DATA/*[concat(KeyPart1,'/',KeyPart2)]]">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:for-each select="/*/getObjectListResponse/item[Key/text() != /*/LookupList/DATA/[concat(KeyPart1,'/',KeyPart2)]]">
<xsl:copy-of select="."/>
</xsl:for-each>
But everything i tried fails, with no results or wrong result.
How can this be done?
I tried this and some others, but nothing will work.
Thanks in advance
I would use keys for the cross-reference, here is an XSLT 3.0 (as supported by Saxon 9.8 all editions or Altova XMLSpy/Raptor) stylesheet as obviously one sample is a good use case for a composite key:
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:output indent="yes"/>
<xsl:key name="data" match="DATA" composite="true" use="KeyPart1, KeyPart2"/>
<xsl:key name="item" match="item" use="Key"/>
<xsl:template match="ROOT">
<xsl:copy>
<items-not-in-data>
<xsl:copy-of select="getObjectListResponse/item[not(key('data', (substring-before(Key, ':'), substring-after(Key, ':'))))]"/>
</items-not-in-data>
<data-not-in-items>
<xsl:copy-of select="LookupList/DATA[not(key('item', concat(KeyPart1, ':', KeyPart2)))]"/>
</data-not-in-items>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For your sample data I get
<ROOT>
<items-not-in-data/>
<data-not-in-items>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
</data-not-in-items>
</ROOT>
Of course XSLT 3.0 and a composite key is not mandatory, you could as well use XSLT 2.0 and use a single key value concat(KeyPart1, KeyPart2).

Filter and Group in XSLT

I am trying to filter data and then group using XSLT. Here is my XML
<?xml version="1.0" encoding="UTF-8"?>
<AccumulatedOutput>
<root>
<Header>
<Add>true</Add>
<Name>Subscriber</Name>
<Value>SAC</Value>
</Header>
</root>
<root>
<Header>
<Add>true</Add>
<Name>System</Name>
<Value>CBP</Value>
</Header>
</root>
<root>
<Header>
<Add>false</Add>
<Name>Subscriber</Name>
<Value>SAC</Value>
</Header>
</root>
</AccumulatedOutput>
What I want to do is that group based on Header/Name and but remove the group in which Header/Add is false.
So in above example I there will be two groups created (one for Name=Subscriber and other for Name=System) but since the first group(with Name=Subscriber) contains Add=false , I want to ignore that and my output should only have one node in it , like below
<?xml version = "1.0" encoding = "UTF-8"?>
<root>
<Header>
<Name>System</Name>
<Value>CBP</Value>
<Add>true</Add>
</Header>
</root>
I tried using group by method but I can't figure out a way to filter it.
It will be a great help if someone can give me some pointers
Thanks
In XSLT 2.0, you could do:
<xsl:template match="/AccumulatedOutput">
<root>
<xsl:for-each-group select="root/Header" group-by="Name">
<xsl:if test="not(current-group()/Add='false')">
<xsl:copy-of select="current-group()"/>
</xsl:if>
</xsl:for-each-group>
</root>
</xsl:template>
No explicit XSLT conditional instructions:
<xsl:template match="/*">
<root>
<xsl:for-each-group group-by="Name" select=
"root/Header[for $n in string(Name)
return every $h in /*/root/Header[Name eq $n]
satisfies not($h/Add eq 'false')]">
<xsl:sequence select="current-group()"/>
</xsl:for-each-group>
</root>
</xsl:template>

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>

Finding unique nodes with xslt

I have an xml document that contains some "Item" elements with ids. I want to make a list of the unique Item ids. The Item elements are not in a list though - they can be at any depth within the xml document - for example:
<Node>
<Node>
<Item id="1"/>
<Item id="2"/>
</Node>
<Node>
<Item id="1"/>
<Node>
<Item id="3"/>
</Node>
</Node>
<Item id="2"/>
</Node>
I would like the output 1,2,3 (or a similar representation). If this can be done with a single xpath then even better!
I have seen examples of this for lists of sibling elements, but not for a general xml tree structure. I'm also restricted to using xslt 1.0 methods. Thanks!
Selecting all unique items with a single XPath expression (without indexing, beware of performance issues):
//Item[not(#id = preceding::Item/#id)]
Try this (using Muenchian grouping):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item-id" match="Item" use="#id" />
<xsl:template match="/Node">
<xsl:for-each select="//Item[count(. | key('item-id', #id)[1]) = 1]">
<xsl:value-of select="#id" />,
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Not sure if this is what you mean, but just in case.
In the html
<xsl:apply-templates select="item"/>
The template.
<xsl:template match="id">
<p>
<xsl:value-of select="#id"/> -
<xsl:value-of select="."/>
</p>
</xsl:template>