XSLT transform a document with group element - xslt

I have a document like this:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<data attribute1="12" attribute2="1" attribute3="1">Director</data>
<data attribute1="12" attribute2="1" attribute3="5">James</data>
<data attribute1="12" attribute2="1" attribute3="8">Male</data>
<data attribute1="12" attribute2="1" attribute3="9">10-Dec-1965</data>
<data attribute1="12" attribute2="2" attribute3="18">James#gmail.com</data>
<data attribute1="12" attribute2="2" attribute3="1">Chief Account</data>
<data attribute1="12" attribute2="2" attribute3="5">Anna</data>
<data attribute1="12" attribute2="2" attribute3="8">Female</data>
<data attribute1="12" attribute2="1" attribute3="9">5-Aug-1980</data>
<data attribute1="12" attribute2="2" attribute3="18">Anna#gmail.com</data>
</document>
I want to transform it to this:
<Person>
<Title>Director</Title>
<FullName>James</FullName>
<Gender>Male</Gender>
<DateOfBirth>10-Dec-1965</DateOfBirth>
<EmailAddress>James#gmail.com</EmailAddress>
</Person>
<Person>
<Title>Chief Account</Title>
<FullName>Anna</FullName>
<Gender>Female</Gender>
<DateOfBirth>5-Aug-1980</DateOfBirth>
<EmailAddress>Anna#gmail.com</EmailAddress>
</Person>
I am using this xslt:
<xsl:for-each select="document/data[#attribute1=12]">
<Person>
<xsl:choose>
<xsl:when test="boolean(./#attribute3 = '1')">
<Title>
<xsl:value-of select="./."/>
</Title>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '5')">
<FullName>
<xsl:value-of select="./."/>
</FullName>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '8')">
<Gender>
<xsl:value-of select="./."/>
</Gender>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '9')">
<DateOfBirth>
<xsl:value-of select="./."/>
</DateOfBirth>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '18')">
<EmailAddress>
<xsl:value-of select="./."/>
</EmailAddress>
</xsl:when>
</xsl:choose>
</Person>
</xsl:for-each>
The problem is that I get the following output, where the <Person> tag is duplicated.
<Person>
<Title>Director</Title>
</Person>
<Person>
<FullName>James</FullName>
</Person>
<Person>
<Gender>Male</Gender>
</Person>
<Person>
<DateOfBirth>10-Dec-1965</DateOfBirth>
</Person>
<Person>
<EmailAddress>James#gmail.com</EmailAddress>
</Person>
<Person>
<Title>Chief Account</Title>
</Person>
<Person>
<FullName>Anna</FullName>
</Person>
<Person>
<Gender>Female</Gender>
</Person>
<Person>
<DateOfBirth>5-Aug-1980</DateOfBirth>
</Person>
<Person>
<EmailAddress>Anna#gmail.com</EmailAddress>
</Person>
Could anyone help me solving this problem? Thank you!

It looks like you wish to create a new Person element for each data element with an attribute3 value of 1. Rather than iterate over all data elements as you are currently doing, just select the elements with the relevant attribute
<xsl:apply-templates select="data[#attribute3='1']"/>
Then, you would have a template to output the Person element only for these data elements.
<xsl:template match="data[#attribute3='1']">
<Person>
<Title><xsl:value-of select="." /></Title>
<!-- Select other elements here -->
</Person>
</xsl:template>
Now, to get the other elements, another way to achieve this is to make use of a key. Effectively, you will group the data elements by the first most preceding data element with an attribute3 value of "1".
<xsl:key name="data"
match="data[#attribute3 != '1']"
use="generate-id(preceding-sibling::data[#attribute3 = '1'][1])" />
Then, to select the other elements, you can use this key, using the unique ID of the current data element as the lookup.
<xsl:apply-templates select="key('data', generate-id())" />
This would only select the data elements that make up that particular person element. You would then have template to match the other data elements for each possible attribute value:
Try this XSLT :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="data" match="data[#attribute3 != '1']" use="generate-id(preceding-sibling::data[#attribute3 = '1'][1])" />
<xsl:template match="document">
<xsl:apply-templates select="data[#attribute3='1']"/>
</xsl:template>
<xsl:template match="data[#attribute3='1']">
<Person>
<Title><xsl:value-of select="." /></Title>
<xsl:apply-templates select="key('data', generate-id())" />
</Person>
</xsl:template>
<xsl:template match="data[#attribute3='5']">
<FullName><xsl:value-of select="." /></FullName>
</xsl:template>
<xsl:template match="data[#attribute3='8']">
<Gender><xsl:value-of select="." /></Gender>
</xsl:template>
<xsl:template match="data[#attribute3='9']">
<DateOfBirth><xsl:value-of select="." /></DateOfBirth>
</xsl:template>
<xsl:template match="data[#attribute3='18']">
<EmailAddress><xsl:value-of select="." /></EmailAddress>
</xsl:template>
</xsl:stylesheet>

Here is my suggestion based on the position of elements:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="size" select="5"/>
<xsl:template match="document">
<Persons>
<xsl:apply-templates select="data[position() mod $size = 1]" mode="group"/>
</Persons>
</xsl:template>
<xsl:template match="data" mode="group">
<Person>
<xsl:apply-templates select=". | following-sibling::data[position() < $size]"/>
</Person>
</xsl:template>
<xsl:template match="data[#attribute3 = '1']">
<Title>
<xsl:value-of select="."/>
</Title>
</xsl:template>
<xsl:template match="data[#attribute3 = '5']">
<FullName>
<xsl:value-of select="."/>
</FullName>
</xsl:template>
<xsl:template match="data[#attribute3 = '8']">
<Gender>
<xsl:value-of select="."/>
</Gender>
</xsl:template>
<xsl:template match="data[#attribute3 = '9']">
<DateOfBirth>
<xsl:value-of select="."/>
</DateOfBirth>
</xsl:template>
<xsl:template match="data[#attribute3 = '18']">
<EmailAddress>
<xsl:value-of select="."/>
</EmailAddress>
</xsl:template>
</xsl:stylesheet>

Related

How to improve/refactor this xslt?

Given the following xml :
<tree>
<val>0</val>
<tree>
<val>1</val>
<tree>
<val>3</val>
</tree>
<tree>
<val>4</val>
</tree>
</tree>
<tree>
<val>2</val>
<tree>
<val>5</val>
</tree>
<tree>
<val>6</val>
</tree>
</tree>
</tree>
I need to transform it into this xml:
<root>0
<frst>1
<leaf>3</leaf>
<leaf>4</leaf>
</frst>
<second>2
<leaf>5</leaf>
<leaf>6</leaf>
</second>
</root>
This is my attempt that gives the same result, I recently started learning XSLT i'm not sure what other option I have, can this be improved or done in another way ?
Thank you for your help
This is my attempt :
<?xml version='1.0' encoding='utf-8'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/tree">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="val">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="tree">
<xsl:choose>
<!-- IF HAS CHILDREN -->
<xsl:when test="child::tree">
<xsl:if test="(count(preceding-sibling::tree)+1) = 1">
<frst>
<xsl:apply-templates/>
</frst>
</xsl:if>
<xsl:if test="(count(preceding-sibling::tree)+1) = 2">
<second>
<xsl:apply-templates/>
</second>
</xsl:if>
</xsl:when>
<!-- ELSE IS A LEAF -->
<xsl:otherwise>
<leaf>
<xsl:apply-templates/>
</leaf>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
test test
I would think the conditions can be written in match patterns:
<xsl:template match="tree[tree][1]">
<frst>
<xsl:apply-templates/>
</frst>
</xsl:template>
<xsl:template match="tree[tree][2]">
<second>
<xsl:apply-templates/>
</second>
</xsl:template>
<xsl:template match="tree[not(tree)]">
<leaf>
<xsl:apply-templates/>
</leaf>
</xsl:template>
although I guess your current code doesn't process the third/fourth/fifth.. tree children so an additional <xsl:template match="tree[tree][position() > 2]"/> might be necessary.

Getting values after the key colon in xslt v2.0

I've seen many related topics in the internet about getting the value after the colon or keys. So, what I did is, I applied all the possible solution provided by the xslt expert. But, upon combining the codes, I can't get the exact structure of the output. Here is my sample input file:
<Data>
:A:00001
:B:Sample Data Only B
:C:082917
:D2:Sample Data Only D1/090909
:E2:Sample Data Only E1/121212
:E2:Sample Data only E2/232323
:D2:Sample Data Only D2/343434
:E2:Sample Data Only E1/454545
:F:092917
:A:00002
:B:Sample Data Only BB
:C:053017
:D2:Sample Data Only DD2/565656
:E2:Sample Data Only EE1/676767
:F:063017
</Data>
For :C: and :F:, they belong to the same level group <AccountPeriod>, but in my output it created another level group of <AccountPeriod> for :C: and :F:. For every occurrence of :D2:, it should create <Detail> group, and for each occurrence of :E:, it should create <Group> group inside <Trans> group. But, in my current output, it only gets all the value of :D: inside the <Group> elements instead of :E:. And lastly, my current output didn't iterate even if I have 2 occurrence of :A:. The tokenize(.,'\n\n') didn't work in my code.
Here is my XSLT code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<xsl:for-each select="tokenize(., '\n\n')">
<Data>
<xsl:analyze-string select="." regex=":([0-9A-Za-z]+):(.*)\n">
<xsl:matching-substring>
<xsl:if test="regex-group(1)='A'">
<Line>
<xsl:value-of select="regex-group(2)"/>
</Line>
</xsl:if>
<xsl:if test="regex-group(1)='B'">
<Number>
<xsl:value-of select="regex-group(2)"/>
</Number>
</xsl:if>
<xsl:variable name="SDate">
<xsl:if test="regex-group(1)='C'">
<xsl:value-of select="regex-group(2)"/>
</xsl:if>
</xsl:variable>
<xsl:variable name="EDate">
<xsl:if test="regex-group(1)='F'">
<xsl:value-of select="regex-group(2)"/>
</xsl:if>
</xsl:variable>
<xsl:if test="not(normalize-space($SDate)='') or not(normalize-space($EDate)='')">
<AccountPeriod>
<StartDate>
<xsl:value-of select="$SDate"/>
</StartDate>
<EndDate>
<xsl:value-of select="$EDate"/>
</EndDate>
</AccountPeriod>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
<xsl:variable name="temp" as="element()*">
<xsl:analyze-string select="." regex="^:([D2][E2]):(.*)$" flags="m">
<xsl:matching-substring>
<xsl:element name="Detail{regex-group(1)}">
<xsl:value-of select="regex-group(2)"/>
</xsl:element>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:for-each-group select="$temp" group-starting-with="DetailD">
<Detail>
<Seq>
<xsl:value-of select="substring-after(current-group()[1],'/')"/>
</Seq>
<Ext>
<xsl:value-of select="substring-before(current-group()[1],'/')"/>
</Ext>
<Trans>
<xsl:for-each select="current-group()[position() ge 2]">
<Group>
<AcctNo>
<xsl:value-of select="substring-after(.,'/')"/>
</AcctNo>
<Desc>
<xsl:value-of select="substring-before(.,'/')"/>
</Desc>
</Group>
</xsl:for-each>
</Trans>
</Detail>
</xsl:for-each-group>
</Data>
</xsl:for-each>
</xsl:template>
Current Output:
<Data>
<Line>00001</Line>
<Number>Sample Data Only B</Number>
<AccountPeriod>
<StartDate>082917</StartDate>
<EndDate/>
</AccountPeriod>
<AccountPeriod>
<StartDate/>
<EndDate>092917</EndDate>
</AccountPeriod>
<Line>00002</Line>
<Number>Sample Data Only BB</Number>
<AccountPeriod>
<StartDate>053017</StartDate>
<EndDate/>
</AccountPeriod>
<AccountPeriod>
<StartDate/>
<EndDate>063017</EndDate>
</AccountPeriod>
<Detail>
<Seq>090909</Seq>
<Ext>Sample Data Only D1</Ext>
<Trans>
<Group>
<DetailD2>Sample Data Only D2/343434</DetailD2>
</Group>
<Group>
<DetailD2>Sample Data Only DD2/565656</DetailD2>
</Group>
</Trans>
</Detail>
</Data>
Expected output:
<Record>
<Data>
<Line>00001</Line>
<Number>Sample Data Only B</Number>
<AccountPeriod>
<StartDate>082917</StartDate>
<EndDate>092917</EndDate>
</AccountPeriod>
<Detail>
<Seq>090909</Seq>
<Ext>Sample Data Only D1</Ext>
<Trans>
<Group>
<AcctNo>121212</AcctNo>
<Desc>Sample Data Only E1</Desc>
</Group>
<Group>
<AcctNo>232323</AcctNo>
<Desc>Sample Data Only E2</Desc>
</Group>
</Trans>
</Detail>
<Detail>
<Seq>343434</Seq>
<Ext>Sample Data Only D2</Ext>
<Trans>
<Group>
<AcctNo>454545</AcctNo>
<Desc>Sample Data Only E1</Desc>
</Group>
</Trans>
</Detail>
</Data>
<Data>
<Line>00002</Line>
<Number>Sample Data Only BB</Number>
<AccountPeriod>
<StartDate>053017</StartDate>
<EndDate>063017</EndDate>
</AccountPeriod>
<Detail>
<Seq>565656</Seq>
<Ext>Sample Data Only DD2</Ext>
<Trans>
<Group>
<AcctNo>676767</AcctNo>
<Desc>Sample Data Only EE1</Desc>
</Group>
</Trans>
</Detail>
</Data>
Your feedback is much appreciated. Thanks!

XLS select="$my_parameter" will not evaluate as parsed XML

Here is a variable that returns 12, which is what I expect:
<xsl:variable name="MM">
<xsl:value-of select="../BIRTH_MONTH"/>
</xsl:variable>
I want to base the select clause on a parameter. I figure something like this:
<!-- $which_date has the value "BIRTH" -->
<xsl:variable name="MM">
<xsl:value-of select="concat('../', $which_date, '_MONTH')"/>
</xsl:variable>
The above returns a value of ../BIRTH_MONTH.
I thought the problem might be with concat(), but below is a variant that also returns the un-evaluated result of ../BIRTH_MONTH:
<!-- $which_date has the value "../BIRTH_MONTH" -->
<xsl:variable name="MM">
<xsl:value-of select="$which_date"/>
</xsl:variable>
(Insert history here of dozens of attempts based on tweaks with quotation marks, braces, etc...)
How can I use $which_date in an expression that can be evaluated?
<xsl:variable name="MM">
<xsl:value-of select="concat('../', $which_date, '_MONTH')"/>
</xsl:variable>
The above returns a value of ../BIRTH_MONTH.
You want:
<xsl:variable name="MM" select="../*[name()=concat($which_date, '_MONTH')]"/>
Here is a complete transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="which_date" select="'BIRTH'"/>
<xsl:template match="x">
<xsl:variable name="MM" select=
"../*[name()=concat($which_date, '_MONTH')]"/>
<xsl:value-of select="$MM"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document (none was provided with the question):
<t>
<x>1</x>
<BIRTH_MONTH>12</BIRTH_MONTH>
</t>
the wanted, correct result is produced:
12
Update: Based on your other similar question, we see that you want a parameterized solution.
Here is one possible parameterized solution:
<xsl:stylesheet version="2.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="pdateComponents" select="'|BIRTH_MONTH|BIRTH_DAY|BIRTH_YEAR|'"/>
<xsl:param name="poutputDateName" select="'BIRTH_DATE'"/>
<xsl:template match="/*">
<xsl:apply-templates select=
"*[*[contains($pdateComponents, concat('|',name(),'|'))]]" mode="dateHolder"/>
</xsl:template>
<xsl:template match="*" mode="dateHolder">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:element name="{$poutputDateName}">
<xsl:apply-templates select=
"*[contains($pdateComponents, concat('|',name(),'|'))]" mode="date">
<xsl:sort select="substring-before($pdateComponents, concat('|',name(),'|'))"/>
</xsl:apply-templates>
</xsl:element>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="date">
<xsl:value-of select=
"concat(substring('/', 1 + (position() = 1)), normalize-space())"/>
</xsl:template>
</xsl:stylesheet>
When this XSLT 1.0 transformation is applied on the following XML document:
<RECORDS>
<PERSON name="A">
<BIRTH_YEAR> 1943 </BIRTH_YEAR>
<BIRTH_MONTH> 04 </BIRTH_MONTH>
<BIRTH_DAY> 01 </BIRTH_DAY>
</PERSON>
<PERSON name="B">
<BIRTH_YEAR> 1957 </BIRTH_YEAR>
<BIRTH_MONTH> 08 </BIRTH_MONTH>
<BIRTH_DAY> 29 </BIRTH_DAY>
</PERSON>
<PERSON name="C">
<BIRTH_YEAR> 1802 </BIRTH_YEAR>
<BIRTH_MONTH> 12 </BIRTH_MONTH>
<BIRTH_DAY> 14 </BIRTH_DAY>
</PERSON>
<PERSON name="D">
<BIRTH_YEAR> 2015 </BIRTH_YEAR>
<BIRTH_MONTH> 04 </BIRTH_MONTH>
<BIRTH_DAY> 30 </BIRTH_DAY>
</PERSON>
</RECORDS>
the result is:
<PERSON name="A">
<BIRTH_DATE>04/01/1943</BIRTH_DATE>
</PERSON>
<PERSON name="B">
<BIRTH_DATE>08/29/1957</BIRTH_DATE>
</PERSON>
<PERSON name="C">
<BIRTH_DATE>12/14/1802</BIRTH_DATE>
</PERSON>
<PERSON name="D">
<BIRTH_DATE>04/30/2015</BIRTH_DATE>
</PERSON>
Do note:
The names of the date components are provided in a global parameter to the transformation -- they are not statically known.
The name of the output element to contain the date is also provided as the value of another global parameter to the transformation.
Even the order of the date components, using which to construct the date, is provided in the first component!
Thus, the above supplied parameter results in American dates output.
But if we provide this parameter:
<xsl:param name="pdateComponents" select="'|BIRTH_DAY|BIRTH_MONTH|BIRTH_YEAR|'"/>
then the result of the transformation contains dates in European format:
<PERSON name="A">
<BIRTH_DATE>01/04/1943</BIRTH_DATE>
</PERSON>
<PERSON name="B">
<BIRTH_DATE>29/08/1957</BIRTH_DATE>
</PERSON>
<PERSON name="C">
<BIRTH_DATE>14/12/1802</BIRTH_DATE>
</PERSON>
<PERSON name="D">
<BIRTH_DATE>30/04/2015</BIRTH_DATE>
</PERSON>

XSLT Saxon - Getting Latest node value

I am trying to find the last element in my input xml, which looks like
<ROOT>
<Items>
<Item>
<FieldTag>AMOUNT</FieldTag>
</Item>
<Item>
<FieldTag>CAT_TYPE</FieldTag>
</Item>
<Item>
<FieldTag>NUMBER</FieldTag>
</Item>
</Items>
<getResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MAIN>
<ArrayItem>
<NUMBER>123456789</NUMBER>
<CTRY>GB</CTRY>
<NAME>TEST NAME</NAME>
<RS>
<ArrayOfRSItem>
<DATE_1>2014-12-12T10:14:02-05:00</DATE_1>
<DATE_2>2014-12-12T10:13:53-05:00</DATE_2>
<AMOUNT>11111</AMOUNT>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-13T17:16:19-05:00</DATE_1>
<DATE_2>2014-12-13T16:33:07-05:00</DATE_2>
<AMOUNT>22222</AMOUNT>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-12T10:14:02-05:00</DATE_1>
<DATE_2>2014-12-12T10:13:53-05:00</DATE_2>
<CAT_TYPE>10000</CAT_TYPE>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-13T17:16:19-05:00</DATE_1>
<DATE_2>2014-12-13T16:33:07-05:00</DATE_2>
<CAT_TYPE>20000</CAT_TYPE>
</ArrayOfRSItem>
</RS>
</ArrayItem>
</MAIN>
</getResponse>
</ROOT>
and I use following XSLT file for transformation
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:urn="urn:iso:std:iso:20022:tech:xsd:pain.001.001.02"
xmlns:func="http://www.test.com/"
exclude-result-prefixes="urn func" xmlns:saxon="http://saxon.sf.net/"
extension-element-prefixes="saxon">
<xsl:output method ="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:if test="count(/ROOT/getResponse/MAIN/ArrayItem) > 0" >
<Data>
<xsl:for-each select="/ROOT/getResponse/MAIN/ArrayItem" >
<xsl:element name="NodeItem">
<xsl:variable name="TempNo" select="NUMBER"/>
<xsl:element name="Number">
<xsl:value-of select="NUMBER"/>
</xsl:element>
<xsl:element name="Country">
<xsl:value-of select="CTRY"/>
</xsl:element>
<xsl:element name="Name">
<xsl:value-of select="NAME"/>
</xsl:element>
<xsl:element name="DataChanges">
<xsl:for-each select="./RS/ArrayOfRSItem/*" >
<xsl:if test="name()!='DATE_1' and name()!='DATE_2' ">
<xsl:variable name="ChangedNodeName" select="name()"></xsl:variable>
<xsl:for-each select="/ROOT/Items/Item">
<xsl:variable name="DisplayNodeName" select="FieldTag"></xsl:variable>
<xsl:if test="$DisplayNodeName = $ChangedNodeName">
<xsl:element name="FieldItem">
<xsl:attribute name="Tag">
<xsl:value-of select="FieldTag"/>
</xsl:attribute>
<xsl:variable name="NodeFullText" select="concat('../../getResponse/MAIN/ArrayItem[NUMBER=',$TempNo,']/RS/ArrayOfRSItem/',$DisplayNodeName)"/>
<xsl:attribute name="Value">
<xsl:value-of select="saxon:evaluate($NodeFullText)"/>
</xsl:attribute>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:for-each>
</Data>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
to get the desired output in the form of
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="22222"/>
<FieldItem Tag="CAT_TYPE" Value="20000"/>
</DataChanges>
</NodeItem>
</Data>
But I am getting the output xml as
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="11111 22222"/>
<FieldItem Tag="AMOUNT" Value="11111 22222"/>
<FieldItem Tag="CAT_TYPE" Value="10000 20000"/>
<FieldItem Tag="CAT_TYPE" Value="10000 20000"/>
</DataChanges>
</NodeItem>
</Data>
I need to get the latest AMOUNT entry and CAT_TYPE. Any help or guidance would be much appreciated. Thanks in advance.
Couldn't this be (much) simpler?
XSLT 1.0 (or 2.0)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<Data>
<xsl:apply-templates select="ROOT/getResponse/MAIN/ArrayItem"/>
</Data>
</xsl:template>
<xsl:template match="ArrayItem">
<NodeItem>
<Number><xsl:value-of select="NUMBER"/></Number>
<Country><xsl:value-of select="CTRY"/></Country>
<Name><xsl:value-of select="NAME"/></Name>
<DataChanges>
<xsl:apply-templates select="RS/ArrayOfRSItem/AMOUNT">
<xsl:sort select="../DATE_1" data-type="text" order="descending"/>
</xsl:apply-templates>
<xsl:apply-templates select="RS/ArrayOfRSItem/CAT_TYPE">
<xsl:sort select="../DATE_1" data-type="text" order="descending"/>
</xsl:apply-templates>
</DataChanges>
</NodeItem>
</xsl:template>
<xsl:template match="AMOUNT | CAT_TYPE">
<xsl:if test="position() = 1">
<FieldItem Tag="{local-name()}" Value="{.}"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Result, when applied to your example input:
<?xml version="1.0" encoding="utf-8"?>
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="22222"/>
<FieldItem Tag="CAT_TYPE" Value="20000"/>
</DataChanges>
</NodeItem>
</Data>

xslt following group

Old Source XML:
<Employees>
<Person>
<FirstName>Joy</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joyce</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joe</FirstName>
<IsManager>Y</IsManager>
</Person>...
</Employees>
New Source XML:
<Employees>
<Person>
<FirstName>Joy</FirstName>
<DetailsArray>
<Details1>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details1>
<Details2>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details2>
</DetailsArray>
</Person>
<Person>
<FirstName>Joyce</FirstName>
<DetailsArray>
<Details1>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details1>
<Details2>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details2>
</DetailsArray>
</Person>
<Person>
<FirstName>Joe</FirstName>
<DetailsArray>
<Details1>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details1>
<Details2>
<IsManager>Y</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details2>
</DetailsArray>
</Person>...
</Employees>
output should be:
<Names>
<Name num='1'>Joe</Name>
<Name num='2'>Joy</Name>
<Name num='3'>Joyce</Name>
....
</Names>
This source XML has some adjustments when compared to previous XML. Here the new condition is "The person may be linked to 2projects or 2tasks", so that i need the output to start from the person with IsManager='Y' even if IsManager is 'y' in Details2 tag of DetailsArray. The output should not have duplications of Names. For suppose if we sort The names will be duplicated..
Thanks for the Previous answers..
EDIT. As lwburk points out, the original solution of this answer just sorts the nodes by IsManager.
Here is a solution that finds the first manager, prints it out, then cycles through the remaining people (cycling back to the beginning, if needed).
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Employees">
<xsl:variable name="position" select="count(Person) - count(Person/IsManager[. = 'Y'][1]/../following-sibling::*)" />
<xsl:call-template name="person">
<xsl:with-param name="name" select="Person/IsManager[. = 'Y'][1]/../FirstName" />
<xsl:with-param name="position" select="'1'" />
</xsl:call-template>
<xsl:for-each select="Person[position() > $position]">
<xsl:call-template name="person" />
</xsl:for-each>
<xsl:for-each select="Person[position() < $position]">
<xsl:call-template name="person" />
</xsl:for-each>
</xsl:template>
<xsl:template name="person">
<xsl:param name="name" select="FirstName" />
<xsl:param name="position" select="position() + 1" />
<Name>
<xsl:attribute name="num"><xsl:value-of select="$position" /></xsl:attribute>
<xsl:value-of select="$name" />
</Name>
</xsl:template>
</xsl:stylesheet>
Old answer.
I'm not sure about your question, but I think you want to get all the names starting from the person with IsManager = Y. You can use <xsl:sort> by the IsManager value. Don't forget to specify "descending" in the attribute "order" (otherwise, the person with IsManager = Y will be the last one).
I wrote an example that works with your input data:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Employees">
<xsl:for-each select="Person">
<xsl:sort select="IsManager" order="descending" />
<Name>
<xsl:attribute name="num">
<xsl:value-of select="position()" />
</xsl:attribute>
<xsl:value-of select="FirstName" />
</Name>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This short and simple transformation (no modes, no variables, and only three templates):
<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="/">
<Names>
<xsl:apply-templates select="*/Person[IsManager='Y'][1]"/>
</Names>
</xsl:template>
<xsl:template match="Person[IsManager='Y']">
<xsl:apply-templates select=
"FirstName |../Person[not(generate-id()=generate-id(current()))]
/FirstName
">
<xsl:sort select=
"generate-id(..) = generate-id(/*/*[IsManager = 'Y'][1])"
order="descending"/>
<xsl:sort select=
"boolean(../preceding-sibling::Person[IsManager='Y'])"
order="descending"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="FirstName">
<Name num="{position()}"><xsl:value-of select="."/></Name>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML (the same one as provided by #lwburk):
<Employees>
<Person>
<FirstName>Joy</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joyce</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joe</FirstName>
<IsManager>Y</IsManager>
</Person>
<Person>
<FirstName>Professor X</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Songey</FirstName>
<IsManager>Y</IsManager>
</Person>
</Employees>
produces the wanted, correct result:
<Names>
<Name num="1">Joe</Name>
<Name num="2">Professor X</Name>
<Name num="3">Songey</Name>
<Name num="4">Joy</Name>
<Name num="5">Joyce</Name>
</Names>
Explanation:
This is a typical case of sorting using multiple keys.
The highest priority sorting criteria is whether the Person parent is the first manager.
The second priority sorting criteria is whether the parent Person is following a manager.
We use the fact that when sorting booleans false() comes before true(), therefore we are processing the sorted nodelist in descending order.
It sounds like you're trying to start at the the first manager and then processes all Person elements in order, cycling back around to the beginning to get all elements before the partition element.
The following stylesheet achieves the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="Employees/Person"/>
</xsl:template>
<xsl:template match="Person[IsManager='Y'][1]">
<Name num="1">
<xsl:apply-templates select="FirstName"/>
</Name>
<!-- partition -->
<xsl:apply-templates select="following-sibling::Person" mode="after"/>
<xsl:apply-templates select="../Person" mode="before">
<xsl:with-param name="pos" select="last() - position() + 1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Person" mode="after">
<Name num="{position() + 1}">
<xsl:apply-templates select="FirstName"/>
</Name>
</xsl:template>
<xsl:template match="Person[not(IsManager='Y') and
not(preceding-sibling::Person[IsManager='Y'])]" mode="before">
<xsl:param name="pos" select="0"/>
<Name num="{position() + $pos}">
<xsl:apply-templates select="FirstName"/>
</Name>
</xsl:template>
<xsl:template match="Person"/>
<xsl:template match="Person" mode="before"/>
</xsl:stylesheet>
Note: 1) This solution requires there be at least one manager present in the source; 2) This might not be a very efficient solution because it requires multiple passes and uses preceding-sibling to test group membership (for elements before the partition element).
Example input:
<Employees>
<Person>
<FirstName>Joy</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joyce</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joe</FirstName>
<IsManager>Y</IsManager>
</Person>
<Person>
<FirstName>Professor X</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Songey</FirstName>
<IsManager>Y</IsManager>
</Person>
</Employees>
Output:
<Name num="1">Joe</Name>
<Name num="2">Professor X</Name>
<Name num="3">Songey</Name>
<Name num="4">Joy</Name>
<Name num="5">Joyce</Name>