Create new node based on id from another xml, loop - xslt

I would like to add a node based on , but the problem is the categories are in a separate file and moreover I would like to loop to find all based on .
Let me show an example:
category.xml:
<ROOT>
<GROUPITEM>
<G_ID>1368</G_ID>
<GROUP>Phone</GROUP>
<PARENT>0</PARENT>
</GROUPITEM>
<GROUPITEM>
<G_ID>1194</G_ID>
<GROUP>Apple</GROUP>
<PARENT>1368</PARENT>
</GROUPITEM>
<GROUPITEM>
<G_ID>1195</G_ID>
<GROUP>2019</GROUP>
<PARENT>1194</PARENT>
</GROUPITEM>
</ROOT>
item.xml:
<ROOT>
<SHOPITEM>
<PRODUCT_ID>96555</PRODUCT_ID>
<GROUP_ID>1195</GROUP_ID>
<PRODUCT_NAME>Apple iPhone 8 Plus</PRODUCT_NAME>
</SHOPITEM>
</ROOT>
Example output:
<ROOT>
<SHOPITEM>
<PRODUCT_ID>96555</PRODUCT_ID>
<GROUP_ID>1195</GROUP_ID>
<PRODUCT_NAME>Apple iPhone 8 Plus</PRODUCT_NAME>
<CATEGORY>Phone | Apple | 2019</CATEGORY>
</SHOPITEM>
</ROOT>
In simple words I am looking for a way to create node (in item.xml) and add here value ( from category.xml) based on (item.xml): search in category.xml for the same -> if found, add it -> search more based on (if success add separator + value) -> loop.

Try something like (untested):
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:key name="group" match="GROUPITEM" use="G_ID" />
<xsl:template match="SHOPITEM">
<xsl:copy>
<xsl:copy-of select="*"/>
<CATEGORY>
<xsl:apply-templates select="key('group', GROUP_ID, document('category.xml'))"/>
</CATEGORY>
</xsl:copy>
</xsl:template>
<xsl:template match="GROUPITEM">
<xsl:variable name="parent" select="key('group', PARENT)" />
<xsl:if test="$parent">
<xsl:apply-templates select="$parent"/>
<xsl:text> | </xsl:text>
</xsl:if>
<xsl:value-of select="GROUP"/>
</xsl:template>
</xsl:stylesheet>

Related

How to iterate each xml tag having different names and values ? How to start iterating from a particular tag ? in xslt 2.0

I have the below xml text,
<SUBSCRIBER>
<Anumber>639081000000</Anumber>
<FirstCallDate>20130430104419</FirstCallDate>
<SetyCode>TNT</SetyCode>
<Status>ACT</Status>
<RoamIndicator/>
<PreloadCode>P1</PreloadCode>
<CreationDate>20130116100037</CreationDate>
<PreActiveEndDate/>
<ActiveEndDate>20130804210541</ActiveEndDate>
<GraceEndDate>20140502210541</GraceEndDate>
<RetailerIndicator/>
<OnPeakAccountID>9100</OnPeakAccountID>
<OnPeakSmsExpDate>20130804210504</OnPeakSmsExpDate>
<UnivWalletAcc/>
<UnliSmsOnCtl>20130606211359</UnliSmsOnCtl>
<UnliSmsTriCtl/>
<UnliSmsGblCtl/>
<UnliMocOnCtl>20130606211359</UnliMocOnCtl>
<UnliMocTriCtl/>
<UnliMocGblCtl/>
<UnliSurfFbcCtl>20130606212353</UnliSurfFbcCtl>
</SUBSCRIBER>
How can I iterate/parse over each xml tag name and get the value ( I need the name of the tag and value in a different variables) ? And also, How can I start iterating from particular tag name ? Ex: I would like to start iterating UnivWalletAcc
Please advise.
So far, I have tried the following,
<xsl:template match="SUBSCRIBER">
<xsl:variable name="tagName">
<xsl:value-of select="/*/*/name()"/>
</xsl:variable>
<xsl:variable name="tagValue">
<xsl:value-of select="/*/*/text()"/>
</xsl:variable>
<xsl:value-of select="$tagName"/>
<xsl:value-of select="$tagValue"/>
</xsl:template>
As an alternative to Veenstra's solution, instead of the xsl:if in the SUBSCRIBER/* template, you can control the iteration in the apply-templates:
<xsl:template match="SUBSCRIBER">
<data>
<xsl:apply-templates select="UnivWalletAcc,
UnivWalletAcc/following-sibling::*" />
</data>
</xsl:template>
With the following XSLT you can loop through all childs of the node SUBSCRIBER:
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
<!-- Identity template that will loop over all nodes and attributes -->
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<!-- Template to match the root and create new root -->
<xsl:template match="SUBSCRIBER">
<data>
<xsl:apply-templates select="#*|node()" />
</data>
</xsl:template>
<!-- Template to loop over all childs of SUBSCRIBER node -->
<xsl:template match="SUBSCRIBER/*">
<!-- This will test if we have seen the UnivWalletAcc node already, if so, output something, otherwise, output nothing -->
<xsl:if test="preceding-sibling::UnivWalletAcc or self::UnivWalletAcc">
<tag>
<tagName><xsl:value-of select="name()" /></tagName>
<tagValue><xsl:value-of select="." /></tagValue>
</tag>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

I need to define a xslt to combine couple of elements with separators for custom defined xsd types

I have couple of custom types which have 2, 3 or 4 childs. So wherever I get these childs I need to combine them into a single element which is the parent tag itself in the output XML. I tried but could not do due to lack of experience with xslt. Can any one help.
My input XML.
<PERSON>
<ID>194</ID>
<NAME>IKHAJA</NAME>
<DETAILS>
<NUMBER>100</NUMBER>
<Description />
<NUMBER01 />
<NUMBER02>Test</NUMBER02>
</DETAILS>
<STATUS>
<NUMBER>ACTIVE</NUMBER>
<Description>ACTIVE</Description>
<NUMBER01 />
<NUMBER02>ACTIVE</NUMBER02>
</STATUS>
<employer>
<ID>123456</ID>
<FNAME>EMPLOYER F NAME</FNAME>
<LNAME>EMPLOYER L NAME</LNAME>
</employer>
<PERSON_OFF>
<TYPE>
<NUMBER>41</NUMBER>
<Description>AMPLIFIERS</Description>
<NUMBER01>77</NUMBER01>
<NUMBER02 />
</TYPE>
<REPORT>
<NUMBER />
<Description />
<NUMBER01 />
<NUMBER02 />
</REPORT>
<SERIAL>111</SERIAL>
<ADDITIONAL_DESC>TEST</ADDITIONAL_DESC>
<KEY>5</KEY>
<CREATED_BY>Test Guy</CREATED_BY>
<CREATED_ON>2013-03-13T10:03:00</CREATED_ON>
<PERSON_OFF_ONE>
<BULK>
<NUMBER>98078</NUMBER>
<Description>BULK</Description>
<NUMBER01 />
<NUMBER02>8563</NUMBER02>
</BULK>
<CHECKED>Y</CHECKED>
</PERSON_OFF_ONE>
</PERSON_OFF>
</PERSON>
And my output XML should be like this:
<PERSON>
<ID>194</ID>
<NAME>IKHAJA</NAME>
<DETAILS>100;;;Test</DETAILS>
<STATUS>ACTIVE;ACTIVE;;ACTIVE</STATUS>
<employer>123456:EMPLOYER F NAME,EMPLOYER L NAME</employer>
<PERSON_OFF>
<TYPE>41;AMPLIFIERS;77;</TYPE>
<REPORT>;;;</REPORT>
<SERIAL>111</SERIAL>
<ADDITIONAL_DESC>TEST</ADDITIONAL_DESC>
<KEY>5</KEY>
<CREATED_BY>Test Guy</CREATED_BY>
<CREATED_ON>2013-03-13T10:03:00</CREATED_ON>
<PERSON_OFF_ONE>
<BULK>98078;BULK;;8563</BULK>
<CHECKED>Y</CHECKED>
</PERSON_OFF_ONE>
</PERSON_OFF>
</PERSON>
If you observe here details, status, bulk etc. are my custom types which have child nodes NUMBER, Description, NUMBER01, NUMBRER02. and I need to combine them with a separator ";" if they are empty or null just I will have ";;;" in my destination column as shown in REPORT field.
Also I have some fields of employer type like employer with childs ID, FNAME and LNAME and I should combine them as ID: FNAME, LNAME as shown in employer field.
I think if I know handling one custom type, I can handle the other types easily.
Can you please help? I already spent whole day on this and I need to do this very badly ASAP.
OP's comment to the answer by JLRishe:
It works if I list all of my custom types but, here I have so many
fields that are of my custom types. Instead of listing out all those
like "DETAILS | STATUS | TYPE | REPORT | BULK", is there any way to
merge fields of these.
This can be done easily, using the fact that all these possibly hundreds of parent elements have one of four children.
A minor adjustment to JLRishe's solution works with unlimited number of parent names:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[NUMBER|Description|NUMBER01|NUMBER02]">
<xsl:copy>
<xsl:apply-templates select="*" mode="delimit" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[position() > 1]" mode="delimit">
<xsl:value-of select="concat(';', .)"/>
</xsl:template>
<xsl:template match="employer">
<xsl:copy>
<xsl:apply-templates select="*" mode="idlist" />
</xsl:copy>
</xsl:template>
<xsl:template match="ID" mode="idlist">
<xsl:value-of select="concat(., ':')" />
</xsl:template>
<xsl:template match="*[not(self::ID)][position() > 1]" mode="idlist">
<xsl:value-of select="concat(',', .)" />
</xsl:template>
</xsl:stylesheet>
As you see, this transformation doesn't mention at all any parent names such as DETAILS, STATUS, TYPE, REPORT, BULK, ..., etc.
and when applied on the provided XML document, produces the wanted, correct result:
<PERSON>
<ID>194</ID>
<NAME>IKHAJA</NAME>
<DETAILS>100;;;Test</DETAILS>
<STATUS>ACTIVE;ACTIVE;;ACTIVE</STATUS>
<employer>123456:EMPLOYER F NAME,EMPLOYER L NAME</employer>
<PERSON_OFF>
<TYPE>41;AMPLIFIERS;77;</TYPE>
<REPORT>;;;</REPORT>
<SERIAL>111</SERIAL>
<ADDITIONAL_DESC>TEST</ADDITIONAL_DESC>
<KEY>5</KEY>
<CREATED_BY>Test Guy</CREATED_BY>
<CREATED_ON>2013-03-13T10:03:00</CREATED_ON>
<PERSON_OFF_ONE>
<BULK>98078;BULK;;8563</BULK>
<CHECKED>Y</CHECKED>
</PERSON_OFF_ONE>
</PERSON_OFF>
</PERSON>
Please give this a try:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="DETAILS | STATUS | TYPE | REPORT | BULK">
<xsl:copy>
<xsl:apply-templates select="*" mode="delimit" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[position() > 1]" mode="delimit">
<xsl:value-of select="concat(';', .)"/>
</xsl:template>
<xsl:template match="employer">
<xsl:copy>
<xsl:apply-templates select="*" mode="idlist" />
</xsl:copy>
</xsl:template>
<xsl:template match="ID" mode="idlist">
<xsl:value-of select="concat(., ':')" />
</xsl:template>
<xsl:template match="*[not(self::ID)][position() > 1]" mode="idlist">
<xsl:value-of select="concat(',', .)" />
</xsl:template>
</xsl:stylesheet>
When run on your sample input, the result is:
<PERSON>
<ID>194</ID>
<NAME>IKHAJA</NAME>
<DETAILS>100;;;Test</DETAILS>
<STATUS>ACTIVE;ACTIVE;;ACTIVE</STATUS>
<employer>123456:EMPLOYER F NAME,EMPLOYER L NAME</employer>
<PERSON_OFF>
<TYPE>41;AMPLIFIERS;77;</TYPE>
<REPORT>;;;</REPORT>
<SERIAL>111</SERIAL>
<ADDITIONAL_DESC>TEST</ADDITIONAL_DESC>
<KEY>5</KEY>
<CREATED_BY>Test Guy</CREATED_BY>
<CREATED_ON>2013-03-13T10:03:00</CREATED_ON>
<PERSON_OFF_ONE>
<BULK>98078;BULK;;8563</BULK>
<CHECKED>Y</CHECKED>
</PERSON_OFF_ONE>
</PERSON_OFF>
</PERSON>

Storing into a variable and displaying the unique entries using XSL

In that, I want to display only the unique fruit entries in it. Here is the XML tag what I'm using for parsing
<main>
<local id="1" type="Primary">
-<summary Date="23-02-12">
-<fruit>apple</fruit>
-<fruit>Orange</fruit>
</summary>
</local>
<local id="2" type="Primary">
-<summary Date="23-02-12">
-<fruit>apple</fruit>
-<fruit>mango</fruit>
</summary>
</local>
</main>
The expected result should be in the below format
<fruit>apple</fruit>
<fruit>Orange</fruit>
<fruit>Mango</fruit>
Here are the code snippet what I'm trying to use
<xsl:for-each select="main/local">
<xsl:for-each select="symbol/fruit">
<xsl:copy-of select="."/>
<xsl:copy-of select="fruit[not(.=$fruit)]"/>
</xsl:for-each>
</xsl:for-each>
But I'm not getting any output display for this, Can you please help me how can I remove this duplicate redundancy from here.? Thank You in advance
To do this in XSLT1.0 you can make use of a technique called 'Meunchian' grouping. First you define a key to 'look-up' the fruit elements based on the value
<xsl:key name="fruit" match="fruit" use="." />
Then, to get the unique fruit names, you match fruit elements that happen to be the first fruit element in the key (and to check two nodes are the same the generate-id() method is used)
<xsl:apply-templates
select="//fruit[generate-id() = generate-id(key('fruit', .)[1])]" />
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="fruit" match="fruit" use="." />
<xsl:template match="/">
<xsl:apply-templates
select="//fruit[generate-id() = generate-id(key('fruit', .)[1])]" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output:
<fruit>apple</fruit>
<fruit>Orange</fruit>
<fruit>mango</fruit>

How to get distinct/unique attributes in xsl variable

<xsl:variable name="Rows" select=" .. some stmt .." />
<xsl:for-each select="$Rows">
<xsl:value-of select="#ATTRNAME"/>
</xsl:for-each>
Would like to know how to find 'Rows' with unique/distinct attribute 'ATTRNAME' [ in XSLT 1.0 ].
Grouping in XSLT 1.0 is done using xsl:key. The following prints only the unique elements of the root element:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes"/>
<xsl:key name="attrByVal" match="/*/#*" use="."/>
<xsl:template match="/">
<xsl:apply-templates select="/*/#*"/>
</xsl:template>
<xsl:template match="#*[generate-id()=generate-id(key('attrByVal', .)[1])]">
<xsl:value-of select="concat(name(), ': ', ., '
')"/>
</xsl:template>
<xsl:template match="#*"/>
</xsl:stylesheet>
Explanation: First, we group all attributes of the root element by value:
<xsl:key name="attrByVal" match="/*/#*" use="."/>
Then, create a template that matches only the first element for each key in the group:
<xsl:template match="#*[generate-id()=generate-id(key('attrByVal', .)[1])]">
And ignore all the others:
<xsl:template match="#*"/>
Example input:
<root one="apple" two="apple" three="orange" four="apple"/>
Output:
one: apple
three: orange
XSLT 2.0 solution :
<xsl:for-each-group select="$Rows" group-by="#ATTRNAME">
<!-- Do something with the rows in the group. These rows
can be accessed by the current-group() function. -->
</xsl:for-each-group>

XSLT: Add namespace to root element

I need to change namespaces in the root element as follows:
input document:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd"
xmlns:ns2="http://www.w3.org/1999/xlink" xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
desired output:
<foo audience="external" xsi:schemaLocation="urn:isbn:1-931666-22-9
http://www.loc.gov/ead/ead.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="urn:isbn:1-931666-22-9">
I was trying to do it as I copy over the whole document and before I give any other transformation instructions, but the following doesn't work:
<xsl:template match="* | processing-instruction() | comment()">
<xsl:copy copy-namespaces="no">
<xsl:for-each select=".">
<xsl:attribute name="audience" select="'external'"/>
<xsl:namespace name="xlink" select="'http://www.w3.org/1999/xlink'"/>
</xsl:for-each>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
Thanks for any advice!
XSLT 2.0 isn't necessary to solve this problem.
Here is an XSLT 1.0 solution, which works equally well as XSLT 2.0 (just change the version attribute to 2.0):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink"
exclude-result-prefixes="xlink"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select=
"namespace::*
[not(name()='ns2')
and
not(name()='')
]"/>
<xsl:copy-of select=
"document('')/*/namespace::*[name()='xlink']"/>
<xsl:copy-of select="#*"/>
<xsl:attribute name="audience">external</xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on this XML document:
<foo
xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd"
xmlns:ns2="http://www.w3.org/1999/xlink"
xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
the wanted result is produced:
<foo xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd"
audience="external"/>
You should really be using the "identity template" for this, and you should always have it on hand. Create an XSLT with that template, call it "identity.xslt", then into the current XSLT. Assume the prefix "bad" for the namespace you want to replace, and "good" for the one you want to replace it with, then all you need is a template like this (I'm at work, so forgive the formatting; I'll get back to this when I'm at home): ... If that doesn't work in XSLT 1.0, use a match expression like "*[namespace-uri() = 'urn:bad-namespace'", and follow Dimitre's instructions for creating a new element programmatically. Within , you really need to just apply-template recursively...but really, read up on the identity template.