creating xslt 1.0 to group element - xslt

Could you please help me with grouping in xslt .
Input.xml :
<EDI>
<Header/>
<Data>
<ISA> <Identifier>123</Identifier>
</ISA>
<Function>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>USD</Code></CUR>
</Transaction>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>EUR</Code></CUR>
</Transaction>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>USD</Code></CUR>
</Transaction>
</Function>
</Data>
</EDI>
I want to group the input xml based on CUR/Code . I have created key and generateid also but the in my output xml it reflecting ISA field value also before Output.
Output xml :
<Output>
<Filename>USAFile</Filename> (If the current-group has Code USD)
<EDI>
<Data><ISA><Identifier>12</Identifier></ISA>
<Function>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>USD</Code></CUR>
</Transaction>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>USD</Code></CUR>
</Transaction>
</Function>
</Data>
</EDI>
</Output>
<Output>
<Filename>EURFile</Filename> (If the current-group has Code EUR)
<EDI>
<Data><ISA><Identifier>23</Identifier></ISA>
<Function>
<Transaction>
<BPR><Account> 123<Account></BPR>
<CUR><Code>EUR/Code></CUR>
</Transaction>
</Data>
</EDI>
</Output>
XSLT :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tib="http://www.tibco.com/bw/xslt/custom-functions" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:key name="groups" match="/EDI/Data/Function/Transaction" use="CUR/Code" />
<xsl:template match="Function">
<Function>
<xsl:for-each select="Transaction[generate-id() = generate-id(key('groups', CUR/Code)[1])]"/>
<xsl:for-each select="key('groups',CUR/Code)">
<Transaction>
<BR><Account><xsl:value-of select="BPR/Account"/></Account>
<CUR><Code><xsl:value-of select="CUR/Code"/></Code>
</Transaction>
</xsl:for-each>
</xsl:for-each>
</Function>
</xsl:template>
</xsl:stylesheet>

Firstly making your input XML well formed as below:
<?xml version="1.0" encoding="UTF-8"?>
<EDI>
<Header />
<Data>
<ISA>
<Identifier>123</Identifier>
</ISA>
<Function>
<Transaction>
<BPR>
<Account>
123
</Account>
</BPR>
<CUR>
<Code>USD</Code>
</CUR>
</Transaction>
<Transaction>
<BPR>
<Account>
123
</Account>
</BPR>
<CUR>
<Code>EUR</Code>
</CUR>
</Transaction>
<Transaction>
<BPR>
<Account>
123
</Account>
</BPR>
<CUR>
<Code>USD</Code>
</CUR>
</Transaction>
</Function>
</Data>
</EDI>
Using a bit different approach than what you tried (in case if it is okay with your case):
Note: In your output XML, you have shown two different values for <Identifier> in <ISA>. I guessed it as typo. Correct me if I am missing something.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="groups" match="/EDI/Data/Function/Transaction" use="CUR/Code" />
<xsl:template match="Function">
<xsl:apply-templates mode="group" />
</xsl:template>
<xsl:template match="Transaction[count(. | key('groups',CUR/Code)[1]) = 1]" mode="group">
<Output>
<Filename>
<xsl:choose>
<xsl:when test="CUR/Code = 'USD'">
<xsl:value-of select="'USAFile'" />
</xsl:when>
<xsl:when test="CUR/Code = 'EUR'">
<xsl:value-of select="'EURFile'" />
</xsl:when>
</xsl:choose>
</Filename>
<EDI>
<Data>
<ISA>
<Identifier>
<xsl:value-of select="../preceding-sibling::*[1]" />
</Identifier>
</ISA>
<Function>
<Transaction>
<xsl:copy-of select="key('groups',CUR/Code)" />
</Transaction>
</Function>
</Data>
</EDI>
</Output>
</xsl:template>
<xsl:template match="ISA" />
<!-- remove nodes other than through the key -->
<xsl:template match="node()" mode="group" />
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/94rmq63

Related

I need to change an xml attribute value conditionaly from an external xml using Xsl

I have the following 2 xml files,
Mapping.xml
<xml>
<map ordinal="0" reverse="xxx" forward="ThisIsXxx" />
<map ordinal="0" reverse="yyy" forward="ThisIsYyy" />
<map ordinal="0" reverse="zzz" forward="thisIsZzz" />
<map ordinal="0" reverse="xx1" forward="ThisIsXx1Info" />
<map ordinal="0" reverse="yy1" forward="ThisIsYy1Info" />
<map ordinal="0" reverse="zz1" forward="ThisIsZz1Info" />
</xml>
and an input xml file with a series of elements with some of them having a 'name' attribute
second.xml
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<Children>
<child name="xxx">
<info name="xx1">
</info>
</child>
<child name="yyy">
<info name="yy1">
</info>
</child>
<child name="zzz">
<info name="zz1">
</info>
</child>
</Children> <!-- Added by edit -->
</xml>
What I need is if direction is 'forward' then the second.xml files #name to be set to the value from #forward from the Mapping.xml such as the following,
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<Children>
<child name="ThisIsXxx">
<info name="ThisIsXx1Info">
</info>
</child>
<child name="ThisIsZzz">
<info name="ThisIsYy1Info">
</info>
</child>
<child name="ThisIsYyy">
<info name="ThisIsZz1Info">
</info>
</child>
</xml>
Xslt file I have is setting all #name="" but not getting any values from the external file..
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<!-- get the mapping db -->
<xsl:variable name="mapDb" select="document('Mapping.xml')" />
<xsl:variable name="mapFwd" select="forward" />
<!-- for all #name find the #mapdb/*/#reverse == #name and set #name=#mapDb/*/forward -->
<xsl:template match="*/#name">
<xsl:choose>
<xsl:when test="$mapFwd='forward'">
<xsl:attribute name="name">
<xsl:value-of select="$mapDb/xml/map[#reverse='{#name}']/#forward"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="name">
<xsl:value-of select="$mapDb/xml/map[#forward='{#name}']/#reverse"/>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Copy Everything unchanged-->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I would appreciate some help.
I think you want to use current() e.g. $mapDb/xml/map[#reverse=current()] in your comparisons in the template matching the #name attribute and of course the variable should be initialized with <xsl:variable name="mapFwd" select="'forward'"/>.

Insert list of elements inside XML tags

I am trying to obtain a list of all the elements with values that aren't in the (Line 1, Line2), and then insert them into the tags similar to the test.
Right now I can retrieve all the elements, but I'm having trouble restricting this to just my desired values. And then I'm unsure how to match and do a for each on elements outside my match criteria. Any advice would be greatly appreciated!
Given the Following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>test</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
Current XSL for getting elements
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:for-each select="node()[text() != '']">
<xsl:value-of select="local-name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
My WIP xml for inserting the result xml tags is below. I'm unsure how to insert the results of the above xsl into this,
<xsl:template match="Element">
<xsl:copy-of select="."/>
<Element>Value1</Element>
</xsl:template>
And ultimate desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>Identifier</Element>
<Element>Gender</Element>
<Element>Title</Element>
<Element>Name</Element>
<Element>AddressType</Element>
<Element>Line1</Element>
<Element>Suburb</Element>
<Element>State</Element>
<Element>PostCode</Element>
<Element>Country</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
I would change the current template to use mode attribute, so it is only used in specific cases, rather than matching all elements. You should also change it to output elements, not text, like so:
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
Then you can call it like this....
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
Try this XSLT. Note the use of the identity template to copy all other existing elements unchanged
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Convert string value as XML tag name

Below is my requirement. Can we do this using XSLT? I want to convert value of AttributeName as tag under policy and corresponding AttributeValue as value.
Input :
<Policy>
<Attributes>
<AttributeName>is_policy_loan</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
<Attributes>
<AttributeName>is_policy_owners</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
<Attributes>
<AttributeName>is_policy_twoyears</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
</Policy>
Output :
<Policy>
<is_policy_loan>Yes</is_policy_loan>
<is_policy_owners>Yes</is_policy_owners>
<is_policy_twoyears>Yes</is_policy_twoyears>
</Policy>
The following xsl file will do the job:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- create the <AttributeName>AttributeValue</..> nodes -->
<xsl:template match="//Attributes">
<xsl:variable name="name" select="AttributeName" />
<xsl:element name="{$name}">
<xsl:value-of select="AttributeValue" />
</xsl:element>
</xsl:template>
<!-- wrap nodes in a `Policy` node -->
<xsl:template match="/">
<Policy>
<xsl:apply-templates/>
</Policy>
</xsl:template>
</xsl:stylesheet>
The way i would do,
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="yes" />
<xsl:template match="Policy">
<xsl:element name="Policy">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Attributes">
<xsl:variable name="name" select="AttributeName" />
<xsl:element name="{$name}">
<xsl:value-of select="AttributeValue" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
output will be,
<Policy>
<is_policy_loan>Yes</is_policy_loan>
<is_policy_owners>Yes</is_policy_owners>
<is_policy_twoyears>Yes</is_policy_twoyears>
</Policy>

Get distinct values from xml

My sample xml looks below: I need to get the distinct states from xml. I am using xslt 1.0 in vs 2010 editor.
<?xml version="1.0" encoding="utf-8" ?>
<states>
<node>
<value>2</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>NJ</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
</states>
My xslt looks like below:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:user="urn:my-scripts">
<xsl:output method="text" indent="yes"/>
<xsl:key name="st" match="//states/node/state" use="." />
<xsl:variable name="disst">
<xsl:for-each select="//states/node[contains(value,1)]/state[generate-id()=generate-id(key('st',.)[1])]" >
<xsl:choose>
<xsl:when test="(position() != 1)">
<xsl:value-of select="concat(', ',.)" disable-output-escaping="yes"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/" >
<xsl:value-of disable-output-escaping="yes" select="$disst"/>
</xsl:template>
</xsl:stylesheet>
Output: DE,NJ,NY
My above xml looks good for the above test xml.
If I change the xml as below:
<?xml version="1.0" encoding="utf-8" ?>
<states>
<node>
<value>2</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>NJ</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
</states>
It in not picking the state DE. Can any one suggest the suitable solution.Thanks in advance.
I need to find out the distinct states from the xml.
The problem here is your use of a predicate in your Muenchian grouping XPath:
[contains(value,1)]
This will often make Muenchian grouping fail to find all of the available distinct values. Instead, you should add the predicate to the key:
<xsl:key name="st" match="//states/node[contains(value, 1)]/state" use="." />
Alternatively, you can apply the predicate inside the grouping statement:
<xsl:apply-templates
select="//states/node
/state[generate-id() =
generate-id(key('st',.)[contains(../value, 1)][1])]" />
Full XSLT (with some improvements):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:user="urn:my-scripts">
<xsl:output method="text" indent="yes"/>
<xsl:key name="st" match="//states/node/state" use="." />
<xsl:variable name="a" select="1" />
<xsl:variable name="disst">
<xsl:apply-templates
select="//states/node
/state[generate-id() =
generate-id(key('st',.)[contains(../value, $a)][1])]" />
</xsl:variable>
<xsl:template match="state">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select ="." disable-output-escaping="yes" />
</xsl:template>
<xsl:template match="/" >
<xsl:value-of disable-output-escaping="yes" select="$disst"/>
</xsl:template>
</xsl:stylesheet>
Result when run on your sample XML:
DE,NJ,NY

How do I combine or merge grouped nodes?

Using the XSL:
<?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="2.0">
<xsl:output method="xml"/>
<xsl:template match="/">
<records>
<record>
<!-- Group record by bigID, for further processing -->
<xsl:for-each-group select="records/record" group-by="bigID">
<xsl:sort select="bigID"/>
<xsl:for-each select="current-group()">
<!-- Create new combined record -->
<bigID>
<!-- <xsl:value-of select="."/> -->
<xsl:for-each select=".">
<xsl:value-of select="bigID"/>
</xsl:for-each>
</bigID>
<text>
<xsl:value-of select="text"/>
</text>
</xsl:for-each>
</xsl:for-each-group>
</record>
</records>
</xsl:template>
</xsl:stylesheet>
I'm trying to change:
<?xml version="1.0" encoding="UTF-8"?>
<records>
<record>
<bigID>123</bigID>
<text>Contains text for 123</text>
<bigID>456</bigID>
<text>Some 456 text</text>
<bigID>123</bigID>
<text>More 123 text</text>
<bigID>123</bigID>
<text>Yet more 123 text</text>
</record>
</records>
into:
<?xml version="1.0" encoding="UTF-8"?>
<records>
<record>
<bigID>123
<text>Contains text for 123</text>
<text>More 123 text</text>
<text>Yet more 123 text</text>
</bigID>
<bigID>456
<text>Some 456 text</text>
</bigID>
</record>
</records>
Right now, I'm just listing the grouped <bigID>s, individually. I'm missing the step after grouping, where I combine the grouped <bigID> nodes. My suspicion is that I need to use the "key" function somehow, but I'm not sure.
Thanks for any help.
Here is the wanted XSLT 2.0 transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kTextforId" match="text"
use="preceding-sibling::bigID[1]"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record">
<record>
<xsl:for-each-group select="bigID" group-by=".">
<bigID>
<xsl:sequence select="current-grouping-key()"/>
<xsl:copy-of select=
"key('kTextforId', current-grouping-key())"/>
</bigID>
</xsl:for-each-group>
</record>
</xsl:template>
</xsl:stylesheet>
When performed on the provided XML document, the wanted result is produced.
In XSLT 2.0 you don't need keys for grouping.
Since you are just copying the text elements in the group, the inner for-each can be removed.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<records>
<record>
<xsl:for-each-group select="records/record/bigID" group-by=".">
<xsl:sort select="." data-type="number" />
<bigID>
<xsl:value-of select="." />
<xsl:copy-of select="current-group()/following-sibling::text[1]" />
</bigID>
</xsl:for-each-group>
</record>
</records>
</xsl:template>
</xsl:stylesheet>
If you instead wanted to output the bigID elements followed by their text elements, then my loop would be replaced by the following.
<xsl:for-each-group select="records/record/bigID" group-by=".">
<xsl:sort select="." data-type="number" />
<xsl:copy-of select="." />
<xsl:copy-of select="current-group()/following-sibling::text[1]" />
</xsl:for-each-group>