Getting values after the key colon in xslt v2.0 - xslt

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!

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.

Using correct regex in xslt 2.0

I've been stuck using the correct regex. I need to tokenize each data per keys along with there values. In my example below,
Sample File:
<Rec>
<Data>/CHG1/EUR1000,00/EXCH/0,10/CPRP/Payment Code 1</Data>
<Data>/CHG1/EUR1000,00/EXCH/0,10/CPRP/Payment Code 1</Data>
<Data>/CHG3/EUR3000,00/PURP//CD/Payment Code 3</Data>
<Data>/CHG5/EUR5000,00/PURP//PRTRY/Payment Code 5</Data>
<Data>/ORIG//CSID/EUR7000,00/BENM//ID/Payment Code 7</Data>
</Rec>
The keys with '//' in the middle is considered as 1 key. I need to generate the output like this:
<Data>
<Group>
<Token>/CHG1/EUR1000,00</Token>
<Token>/EXCH/0,10</Token>
<Token>/CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token>/CHG3/EUR3000,00</Token>
<Token>/PURP//CD/Payment Code 3</Token>
</Group>
<Group>
<Token>/CHG5/EUR5000,00</Token>
<Token>/PURP//PRTRY/Payment Code 5</Token>
</Group>
<Group>
<Token>/ORIG//CSID/EUR7000,00</Token>
<Token>/BENM//ID/Payment Code 7</Token>
</Group>
</Data>
But, my generated output is like this:
<Data>
<Group>
<Token>/CHG1/</Token>
<Token>/EXCH/</Token>
<Token>/CPRP/</Token>
</Group>
<Group>
<Token>/CHG3/</Token>
<Token>/PURP//CD/</Token>
</Group>
<Group>
<Token>/CHG5/</Token>
<Token>/PURP//PRTRY/</Token>
</Group>
<Group>
<Token>/ORIG//CSID/</Token>
<Token>/BENM//ID/</Token>
</Group>
</Data>
Here is my XSLT that I've been using:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()[boolean(normalize-space())]|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<xsl:for-each select=".">
<Group>
<xsl:analyze-string select="." regex="\/[A-Z]+[0-9]?\/(\/(CD|PRTRY|MARF|ID|CSID|NAME|RID)\/)?">
<xsl:matching-substring>
<xsl:variable name="val" select="."/>
<Token>
<xsl:value-of select="$val"/>
</Token>
</xsl:matching-substring>
</xsl:analyze-string>
</Group>
</xsl:for-each>
</xsl:template>
There is something missing in my regex expression. Can anyone help me figure out? Thank you for your feedback.
I can't make heads or tails of your expected output. I suspect the correct output should be actually something like:
<?xml version="1.0" encoding="UTF-8"?>
<Rec>
<Group>
<Token>CHG1/EUR1000,00</Token>
<Token>EXCH/0,10</Token>
<Token>CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token>CHG1/EUR1000,00</Token>
<Token>EXCH/0,10</Token>
<Token>CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token>CHG3/EUR3000,00</Token>
<Token>PURP/</Token>
<Token>CD/Payment Code 3</Token>
</Group>
<Group>
<Token>CHG5/EUR5000,00</Token>
<Token>PURP/</Token>
<Token>PRTRY/Payment Code 5</Token>
</Group>
<Group>
<Token>ORIG/</Token>
<Token>CSID/EUR7000,00</Token>
<Token>BENM/</Token>
<Token>ID/Payment Code 7</Token>
</Group>
</Rec>
If so, I would suggest you change your approach and try:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<xsl:variable name="t" select="tokenize(., '/')" />
<Group>
<xsl:for-each select="$t[position() mod 2 = 0]">
<xsl:variable name="i" select="index-of($t, .)"/>
<Token>
<xsl:value-of select="." />
<xsl:text>/</xsl:text>
<xsl:value-of select="$t[$i + 1]" />
</Token>
</xsl:for-each>
</Group>
</xsl:template>
</xsl:stylesheet>
Demo: http://xsltransform.net/93dEHGr
Or even simpler:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<Group>
<xsl:for-each-group select="tokenize(., '/')" group-by="position() idiv 2">
<Token>
<xsl:value-of select="current-group()" separator="/"/>
</Token>
</xsl:for-each-group>
</Group>
</xsl:template>
</xsl:stylesheet>
Demo: http://xsltransform.net/93dEHGr/2
Added:
Actually, the keys with 2 '//' in the middle is considered as 1 key.
Well, then let's take the // strings out of the game before doing the tokenizing, and restore them at the end:
<xsl:template match="Data">
<Group>
<xsl:for-each-group select="tokenize(replace(., '//', '§§§'), '/')" group-by="position() idiv 2">
<Token>
<xsl:value-of select="replace(string-join(current-group(), '/'), '§§§', '//')" />
</Token>
</xsl:for-each-group>
</Group>
</xsl:template>
Result
<?xml version="1.0" encoding="UTF-8"?>
<Rec>
<Group>
<Token/>
<Token>CHG1/EUR1000,00</Token>
<Token>EXCH/0,10</Token>
<Token>CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token/>
<Token>CHG1/EUR1000,00</Token>
<Token>EXCH/0,10</Token>
<Token>CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token/>
<Token>CHG3/EUR3000,00</Token>
<Token>PURP//CD/Payment Code 3</Token>
</Group>
<Group>
<Token/>
<Token>CHG5/EUR5000,00</Token>
<Token>PURP//PRTRY/Payment Code 5</Token>
</Group>
<Group>
<Token/>
<Token>ORIG//CSID/EUR7000,00</Token>
<Token>BENM//ID/Payment Code 7</Token>
</Group>
</Rec>
http://xsltransform.net/93dEHGr/4
You can try this with tokenize in xslt 2.0
<xsl:template match="Rec">
<Data>
<xsl:apply-templates/>
</Data>
</xsl:template>
<xsl:template match="Data">
<Group>
<xsl:for-each select="tokenize(., '0/')">
<Token>
<xsl:if test="position() ne 1">
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:value-of select="."/>
<xsl:if test="position() ne last()">
<xsl:text>0</xsl:text>
</xsl:if>
</Token>
</xsl:for-each>
</Group>
</xsl:template>

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 transform a document with group element

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>

XSLT 1.0 to iterate over several XML elements with comma delimited values

I have an XML document structured as follows
<items>
<item>
<name>item1</name>
<attributes>a,b,c,d</attributes>
</item>
<item>
<name>item2</name>
<attributes>c,d,e</attributes>
</item>
</items>
For each unique attribute value (delimited by commas) I need to list all item names associated with that value like so:
a : item1
b : item1
c : item1, item2
d : item1, item2
e : item2
My initial plan was to use a template to parse the attributes into Attribute nodes, surrounding each with appropriate tags, and then separating out the unique values with an XPATH expression like
Attribute[not(.=following::Attribute)]
but since the result of the template isn't a node-set that ever goes through an XML parser, I can't traverse it. I also tried exslt's node-set() function only to realize it does not allow me to traverse the individual Attribute nodes either.
At this point I'm at a loss for a simple way to do this and would really appreciate any help or ideas on how to proceed. Thanks!
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kAtrByVal" match="attr" use="."/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<groups>
<xsl:apply-templates/>
</groups>
</xsl:variable>
<xsl:variable name="vPass1"
select="ext:node-set($vrtfPass1)"/>
<xsl:apply-templates select="$vPass1/*"/>
</xsl:template>
<xsl:template match="item">
<group name="{name}">
<xsl:apply-templates select="attributes"/>
</group>
</xsl:template>
<xsl:template match="attributes" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<xsl:variable name="vText" select=
"concat($pText,',')"/>
<attr>
<xsl:value-of select="substring-before($vText,',')"/>
</attr>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match=
"attr[generate-id()
=
generate-id(key('kAtrByVal',.)[1])
]
">
<xsl:value-of select="concat('
',.,': ')"/>
<xsl:for-each select="key('kAtrByVal',.)">
<xsl:value-of select="../#name"/>
<xsl:if test="not(position()=last())">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<items>
<item>
<name>item1</name>
<attributes>a,b,c,d</attributes>
</item>
<item>
<name>item2</name>
<attributes>c,d,e</attributes>
</item>
</items>
produces the wanted, correct result:
a: item1
b: item1
c: item1, item2
d: item1, item2
e: item2
Explanation:
Pass1: tokenization and end result:
<groups>
<group name="item1">
<attr>a</attr>
<attr>b</attr>
<attr>c</attr>
<attr>d</attr>
</group>
<group name="item2">
<attr>c</attr>
<attr>d</attr>
<attr>e</attr>
</group>
</groups>
.2. Pass2 takes the result of Pass1 (converted to a nodeset using the extension function ext:node-set()) as input, performs Muenchian grouping and produces the final, wanted result.
My first thought is to make two passes. First, tokenize the attributes elements using a (slightly) modified version of #Alejandro's answer to this previous question:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<items>
<xsl:apply-templates/>
</items>
</xsl:template>
<xsl:template match="item">
<item name="{name}">
<xsl:apply-templates select="attributes"/>
</item>
</xsl:template>
<xsl:template match="attributes" name="tokenize">
<xsl:param name="text" select="."/>
<xsl:param name="separator" select="','"/>
<xsl:choose>
<xsl:when test="not(contains($text, $separator))">
<val>
<xsl:value-of select="normalize-space($text)"/>
</val>
</xsl:when>
<xsl:otherwise>
<val>
<xsl:value-of select="normalize-space(
substring-before($text, $separator))"/>
</val>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after(
$text, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Which produces:
<items>
<item name="item1">
<val>a</val>
<val>b</val>
<val>c</val>
<val>d</val>
</item>
<item name="item2">
<val>c</val>
<val>d</val>
<val>e</val>
</item>
</items>
Then apply the following stylesheet to that output:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="byVal" match="val" use="." />
<xsl:template match="val[generate-id() =
generate-id(key('byVal', .)[1])]">
<xsl:value-of select="." />
<xsl:text> : </xsl:text>
<xsl:apply-templates select="key('byVal', .)" mode="group" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="val" mode="group">
<xsl:value-of select="../#name" />
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="val" />
</xsl:stylesheet>
Producing:
a : item1
b : item1
c : item1, item2
d : item1, item2
e : item2
Doing this in one stylesheet would require more thought (or an extension function).