This question already has answers here:
Sort XML to XML using XSLT
(2 answers)
Closed 8 years ago.
I am working on XML transformation and before I start transform XML I have sort only child elements. My current XSL file sort out child element and even parent element which I don't want to sort
Please see below
My XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Catalog>
<libraries>
<library3>
<Name> COBB </Name>
<city> Marietta </city>
</library3>
<library1>
<Name> COBB </Name>
<city> Marietta </city>
</library1>
<library4>
<Name> COBB </Name>
<city> Marietta </city>
</library4>
<library2>
<Name> COBB </Name>
<city> Marietta </city>
</library2>
</libraries>
<Books>
<Book1>
<Name>Wise Otherwise</Name>
<author>Great Expectations</author>
</Book1>
<Book3>
<Name>Wise Otherwise</Name>
<author>Great Expectations</author>
</Book3>
<Book6>
<Name>Wise Otherwise</Name>
<author>Great Expectations</author>
</Book6>
<Book2>
<Name>Wise Otherwise</Name>
<author>Great Expectations</author>
</Book2>
</Books>
</Catalog>
Desire Output
<?xml version="1.0" encoding="UTF-8"?>
<Catalog>
<libraries>
<library1>
<city> Marietta </city>
<Name> COBB </Name>
</library1>
<library2>
<city> Marietta </city>
<Name> COBB </Name>
</library2>
<library3>
<city> Marietta </city>
<Name> COBB </Name>
</library3>
<library4>
<city> Marietta </city>
<Name> COBB </Name>
</library4>
</libraries>
<Books>
<Book1>
<author>Great Expectations</author>
<Name>Wise Otherwise</Name>
</Book1>
<Book2>
<author>Great Expectations</author>
<Name>Wise Otherwise</Name>
</Book2>
<Book3>
<author>Great Expectations</author>
<Name>Wise Otherwise</Name>
</Book3>
<Book6>
<author>Great Expectations</author>
<Name>Wise Otherwise</Name>
</Book6>
</Books>
</Catalog>
My XSL
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Catalog/Books">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="Catalog/libraries">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Catalog/Books">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="Catalog/libraries">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
Related
Here is my source XML which I'm trying to transform based on values of <City>
<?xml version="1.0" encoding="UTF-8"?>
<Workers>
<Worker>
<EmpID>12345</EmpID>
<City>NYC</City>
<Allowance>
<Type>Meal</Type>
<Amount>150</Amount>
</Allowance>
<Allowance>
<Type>Gym</Type>
<Amount>200</Amount>
</Allowance>
</Worker>
<Worker>
<EmpID>56789</EmpID>
<City>SFO</City>
<Base>
<BaseType>General</BaseType>
<BaseAmount>1000</BaseAmount>
</Base>
</Worker>
<Worker>
<EmpID>18978</EmpID>
<City>LAX</City>
<Base>
<BaseType>General</BaseType>
<BaseAmount>3000</BaseAmount>
</Base>
</Worker>
</Workers>
I'm attempting to transform as below. Transformation doesn't need be to applied if the value of <City> is either NYC or SFO
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
<m:GetQuotationResponse xmlns:m = "http://www.example.com">
<m:Worker>12345</m:Worker>
<m:Location>NYC</m:Location>
<m:Expense>
<m:ExpenseType>Meal</m:ExpenseType>
<m:Amount>150</m:Amount>
</m:Expense>
<m:Expense>
<m:ExpenseType>Gym</m:ExpenseType>
<m:Amount>200</m:Amount>
</m:Expense>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
<m:GetQuotationResponse xmlns:m = "http://www.example.com">
<m:Worker>56789</m:Worker>
<m:Location>SFO</m:Location>
<m:Expense>
<m:ExpenseType>General</m:ExpenseType>
<m:Amount>1000</m:Amount>
</m:Expense>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</Root>
This is my attempt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
xmlns:m = "http://www.example.com"
exclude-result-prefixes="xs m SOAP-ENV"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Worker[City='NYC']">
<Root>
<SOAP-ENV:Body xmlns:m = "http://www.example.com">
<m:GetQuotationResponse>
<m:Worker><xsl:value-of select="EmpID"/></m:Worker>
<m:Location><xsl:value-of select="City"/></m:Location>
<xsl:for-each select="Allowance">
<m:Expense>
<m:ExpenseType><xsl:value-of select="Type"/></m:ExpenseType>
<m:Amount><xsl:value-of select="Amount"/></m:Amount>
</m:Expense>
</xsl:for-each>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</Root>
</xsl:template>
<xsl:template match="Worker[City='SFO']">
<Root>
<SOAP-ENV:Body xmlns:m = "http://www.example.com">
<m:GetQuotationResponse>
<m:Worker><xsl:value-of select="EmpID"/></m:Worker>
<m:Location><xsl:value-of select="City"/></m:Location>
<xsl:for-each select="Base">
<m:Expense>
<m:ExpenseType><xsl:value-of select="BaseType"/></m:ExpenseType>
<m:Amount><xsl:value-of select="BaseAmount"/></m:Amount>
</m:Expense>
</xsl:for-each>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</Root>
</xsl:template>
</xsl:stylesheet>
I have two issues(or more)
Couldn't get <Root> as the Parent node
Templates which are not matching values of <City> also returned. I wanted to match only <xsl:template match="Worker[City='NYC']"> or <xsl:template match="Worker[City='SFO']">
Current Output
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
<m:GetQuotationResponse xmlns:m="http://www.example.com">
<m:Worker>12345</m:Worker>
<m:Location>NYC</m:Location>
<m:Expense>
<m:ExpenseType>Meal</m:ExpenseType>
<m:Amount>150</m:Amount>
</m:Expense>
<m:Expense>
<m:ExpenseType>Gym</m:ExpenseType>
<m:Amount>200</m:Amount>
</m:Expense>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</Root>
<Root>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
<m:GetQuotationResponse xmlns:m="http://www.example.com">
<m:Worker>56789</m:Worker>
<m:Location>SFO</m:Location>
<m:Expense>
<m:ExpenseType>General</m:ExpenseType>
<m:Amount>1000</m:Amount>
</m:Expense>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</Root>
18978
LAX
General
3000
Any help is appreciated to get this working using xslt 2.0 or xslt 3.0. Thank you
I'm posting the first solution that Martin Honnen has suggested.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
xmlns:m = "http://www.example.com"
exclude-result-prefixes="xs m SOAP-ENV"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Workers">
<Root>
<xsl:apply-templates/>
</Root>
</xsl:template>
<xsl:template match="Worker[City='NYC']">
<SOAP-ENV:Body xmlns:m = "http://www.example.com">
<m:GetQuotationResponse>
<m:Worker><xsl:value-of select="EmpID"/></m:Worker>
<m:Location><xsl:value-of select="City"/></m:Location>
<xsl:for-each select="Allowance">
<m:Expense>
<m:ExpenseType><xsl:value-of select="Type"/></m:ExpenseType>
<m:Amount><xsl:value-of select="Amount"/></m:Amount>
</m:Expense>
</xsl:for-each>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</xsl:template>
<xsl:template match="Worker[City='SFO']">
<SOAP-ENV:Body xmlns:m = "http://www.example.com">
<m:GetQuotationResponse>
<m:Worker><xsl:value-of select="EmpID"/></m:Worker>
<m:Location><xsl:value-of select="City"/></m:Location>
<xsl:for-each select="Base">
<m:Expense>
<m:ExpenseType><xsl:value-of select="BaseType"/></m:ExpenseType>
<m:Amount><xsl:value-of select="BaseAmount"/></m:Amount>
</m:Expense>
</xsl:for-each>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</xsl:template>
<xsl:template match="Worker[not(City = ('SFO', 'NYC'))]"/>
</xsl:stylesheet>
Here is what alternate solution returns. I'm not sure how this solution can be modified to get expected output since City='SFO' has different sibling node <Base> than City='NYC'
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
<m:GetQuotationResponse xmlns:m="http://www.example.com">
<m:Worker>12345</m:Worker>
<m:Location>NYC</m:Location>
<m:Expense>
<m:ExpenseType>Meal</m:ExpenseType>
<m:Amount>150</m:Amount>
</m:Expense>
<m:Expense>
<m:ExpenseType>Gym</m:ExpenseType>
<m:Amount>200</m:Amount>
</m:Expense>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
<m:GetQuotationResponse xmlns:m="http://www.example.com">
<m:Worker>56789</m:Worker>
<m:Location>SFO</m:Location>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</Root>
Following part is missing from the output
<m:Expense>
<m:ExpenseType>General</m:ExpenseType>
<m:Amount>1000</m:Amount>
</m:Expense>
Alternate solution suggested by #Martin Honnen with some minor tweaks also provide desired output.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
xmlns:m = "http://www.example.com"
exclude-result-prefixes="xs m SOAP-ENV"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Workers">
<Root>
<xsl:apply-templates select="Worker[City = ('SFO', 'NYC')]"/>
</Root>
</xsl:template>
<xsl:template match="Worker">
<SOAP-ENV:Body xmlns:m = "http://www.example.com">
<m:GetQuotationResponse>
<m:Worker><xsl:value-of select="EmpID"/></m:Worker>
<m:Location><xsl:value-of select="City"/></m:Location>
<xsl:for-each select="Allowance">
<m:Expense>
<m:ExpenseType><xsl:value-of select="Type"/></m:ExpenseType>
<m:Amount><xsl:value-of select="Amount"/></m:Amount>
</m:Expense>
</xsl:for-each>
<xsl:for-each select="Base"> <!-- This part was included to get desired output without having to use more than one templates -->
<m:Expense>
<m:ExpenseType><xsl:value-of select="BaseType"/></m:ExpenseType>
<m:Amount><xsl:value-of select="BaseAmount"/></m:Amount>
</m:Expense>
</xsl:for-each>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</xsl:template>
</xsl:stylesheet>
Start with a template
<xsl:template match="Workers">
<Root>
<xsl:apply-templates/>
</Root>
</xsl:template>
then map your selected Workers to a SOAP body with a single template using e.g. <xsl:template match="Worker[City = ('SFO', 'NYC')]"> or <xsl:template match="Worker[City = 'SFO'] | Worker[City = 'NYC']">, if you prefer.
For other Workers, set up an empty template e.g. <xsl:template match="Worker[not(City = ('SFO', 'NYC'))]"/>.
As an alternative, you can of course just use a template matching Worker to map to a SOAP body and make your desired selection in the apply-templates of the first template I have show, i.e. change that to <xsl:apply-templates select="Worker[City = ('SFO', 'NYC')]"/>, that way you also ensure that only the wanted Workers are processed e.g.
<xsl:template match="Workers">
<Root>
<xsl:apply-templates select="Worker[City = ('SFO', 'NYC')]"/>
</Root>
</xsl:template>
<xsl:template match="Worker">
<SOAP-ENV:Body xmlns:m = "http://www.example.com">
<m:GetQuotationResponse>
<m:Worker><xsl:value-of select="EmpID"/></m:Worker>
<m:Location><xsl:value-of select="City"/></m:Location>
<xsl:for-each select="Allowance">
<m:Expense>
<m:ExpenseType><xsl:value-of select="Type"/></m:ExpenseType>
<m:Amount><xsl:value-of select="Amount"/></m:Amount>
</m:Expense>
</xsl:for-each>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</xsl:template>
INPUT XML:
<overline-start id="tie1" specific-use="tie-bar"/>PtCl<sub>2</sub>(P((CH<sub>2</sub>)<sub><italic toggle="yes">n</italic></sub>)<sub>3</sub><overline-end rid="tie1"/>
EXPECTED XML:
<overline id="tie1" specific-use="tie-bar">PtCl<sub>2</sub>(P((CH<sub>2</sub>)<sub><italic toggle="yes">n</italic></sub>)<sub>3</sub></overline>
MY XSLT 2.0 Code:
<xsl:template match="overline-start">
<xsl:for-each-group select="self::overline-start" group-adjacent="self::overline-start[following-sibling::overline-end]">
<xsl:for-each select="current-group()">
<overline>
<xsl:apply-templates select="#*"/>
<xsl:copy-of select="current-group()"/>
</overline>
</xsl:for-each>
As you mentioned the requirement in comments, I tried it #Martin Honnen 's way:
Assuming input as:
<?xml version="1.0" encoding="UTF-8"?>
<p>
<overline-start id="tie1" specific-use="tie-bar"/>PtCl<sub>2</sub>(P((CH<sub>2</sub>)<sub><italic toggle="yes">n</italic></sub>)<sub>3</sub><overline-end rid="tie1"/>
</p>
A 2.0 solution can be:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="no" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<overline>
<xsl:for-each-group select="* | text()" group-starting-with="overline-start">
<xsl:for-each-group select="current-group()" group-ending-with="overline-end">
<xsl:apply-templates select="#*" />
<xsl:sequence select="(current-group() except .) [position() != last()]" />
</xsl:for-each-group>
</xsl:for-each-group>
</overline>
</xsl:template>
</xsl:stylesheet>
http://xsltfiddle.liberty-development.net/bnnZW8/1
How to apply identity transformation and grouping at the same time
<items>
<user>
<id>8788989</id>
<firstname>test</firstname>
<lastname>user</lastname>
</user>
<info>test xml</info>
<fromdate><fromdate>
<todate></todate>
<item id="123" name="Java">
<price>1</price>
<description></description>
</item>
<item id="123" name="Java and XML">
<price>2</price>
<description></description>
</item>
<item id="234" name="python">
<price>3</price>
<description></description>
</item>
<item id="234" name="scala">
<price>3</price>
<description></description>
</item>
</items>
I want output as
<items>
<user>
<id>8788989</id>
<firstname>test</firstname>
<lastname>user</lastname>
</user>
<info>test xml</info>
<fromdate><fromdate>
<todate></todate>
<group>
<item id="123" name="Java">
<price>1</price>
<description></description>
</item>
<item id="123" name="Java and XML">
<price>2</price>
<description></description>
</item>
</group>
<group>
<item id="234" name="python">
<price>3</price>
<description></description>
</item>
<item id="234" name="scala">
<price>3</price>
<description></description>
</item>
</group>
</items>
Grouping is done on item/#id
You can group like this:
<?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 indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items">
<items>
<xsl:apply-templates select="* except item"/>
<xsl:for-each-group select="item" group-by="#id">
<group>
<xsl:apply-templates select="../item[#id = current()/#id]"/>
</group>
</xsl:for-each-group>
</items>
</xsl:template>
</xsl:stylesheet>
UPDATED ANSWER:
<?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 indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items">
<items>
<xsl:apply-templates select="* except item"/>
<xsl:for-each-group select="item" group-by="#id">
<group>
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:for-each-group>
</items>
</xsl:template>
</xsl:stylesheet>
This is input xml input.xml
<root>
<bodytext>
<remotelink refptid="HKBL1.0001.lohk.CAP65">some text</remotelink>
<remotelink refptid="HKBL1.0001.lohk.CAP199999">some text</remotelink>
</bodytext>
</root>
This is Prop.xml
<?xml version="1.0" encoding="utf-8"?>
<properties>
<code dpsi="0BZG" docid="asdww">HKBL1.0001.lohk.CAP65</code>
<code dpsi="0BZH" docid="navin">HKBL1.0002.aohk.CAP383</code>
<code no="3">345</code>
</properties>
This is desired output
<root>
<bodytext>
<remotelink refptid="HKBL1.0001.lohk.CAP65" dpsi="0BZG" docid="asdww">some text</remotelink>
<remotelink refptid="HKBL1.0001.lohk.CAP199999">some text</remotelink>
</bodytext>
</root>
IF prop.xml code/text matches remotelink/#refptid than copy attribute of prop.xml to remotelink otherwise no changes in remotelink.
This is the XSLT I have written. So far I am not getting result for unmatched condition:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xlsx="http://www.stylusstudio.com/XSLT/XLSX" xmlns:spml="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:saxon="http://saxon.sf.net/" version="2.0">
<xsl:template match="#*|node()" name="root">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="remotelink[#service='DOC-ID']" name="t-remote">
<xsl:variable name="refptid" select="./#refpt"/>
<xsl:variable name="path" select="doc('file:/C:/Users/DalalNS/Desktop/xslt/prop.xml')"/>
<xsl:for-each select="$path/properties/code">
<xsl:choose>
<xsl:when test="./text()=$refptid">
<xsl:element name="remotelink">
<xsl:attribute name="DOC-ID" select="./#docid"/>
<xsl:attribute name="dpsi" select="./#dpsi"/>
<xsl:attribute name="refpt" select="$refptid"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:if test="./#docrefid"/>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="remotelink[#service='DOC-ID']"> will never never be triggered, there's no service attribute on the <remotelink> element. Moreover, you don't retrieve the correct attribute (refpt instead of refptid)
This XSL transformation should do the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xlsx="http://www.stylusstudio.com/XSLT/XLSX" xmlns:spml="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:saxon="http://saxon.sf.net/" version="2.0">
<xsl:variable name="props.doc" select="doc('file:/C:/Users/DalalNS/Desktop/xslt/prop.xml')/properties" />
<xsl:template match="#*|node()" name="root">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="remotelink" name="t-remote">
<xsl:variable name="refptid" select="./#refptid"/>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:if test="$props.doc/code[. = $refptid]">
<xsl:apply-templates select="$props.doc/code[. = $refptid]/#*"/>
</xsl:if>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This is the result of the transformation:
<?xml version="1.0" encoding="UTF-8"?><root>
<bodytext>
<remotelink refptid="HKBL1.0001.lohk.CAP65" dpsi="0BZG" docid="asdww">some text</remotelink>
<remotelink refptid="HKBL1.0001.lohk.CAP199999">some text</remotelink>
</bodytext>
</root>
I would simply define a global parameter or variable <xsl:variable name="path" select="doc('file:/C:/Users/DalalNS/Desktop/xslt/prop.xml')"/>, then set up a key <xsl:key name="prop" match="code" use="."/>, and then use that in a template
<xsl:template match="remotelink[key('prop', #refptid, $path)]">
<xsl:copy>
<xsl:copy-of select="key('prop', #refptid, $path)/#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
that, together with your first template, should suffice.
Move elements from one place to another using XSLT 2 (saxon8)
Hi all please help me to produce the following output using XSLT 2.0.
I have this:
<UL>
<ITEM>
aaa
<NL>iii
<ITEM1>111</ITEM1>
<ITEM2>222</ITEM2>
</NL>
</ITEM>
<ITEM>
bbb
<NL>vvv
<ITEM1>333</ITEM1>
<ITEM2>444</ITEM2>
</NL>
</ITEM>
</UL>
I need to produce this
<UL>
<ITEM>
aaa
<ITEM1>111</ITEM1>
</ITEM>
<ITEM>
bbb
<ITEM1>333</ITEM1>
</ITEM>
<NEW>
<NL>iii
<ITEM2>222</ITEM2>
</NL>
<NL>vvv
<ITEM2>444</ITEM2>
</NL>
</NEW>
</UL>
I am trying to get the output using mode option as mentioned below:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="NL"/>
<xsl:template match="ITEM2"/>
<xsl:template match="UL">
<xsl:copy>
<xsl:apply-templates/>
<NEW>
<xsl:apply-templates select="descendant::NL" mode="move"/>
</NEW>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM" mode="move">
<xsl:copy>
<xsl:apply-templates/>
<xsl:apply-templates select="descendant::ITEM2" mode="move1"/>
</xsl:copy>
</xsl:template>
<xsl:template match="NL" mode="move">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM2" mode="move1">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Thanks and regards
Bala
Here is an XSLT 2.0 stylesheet that creates the structure you want, although white space is different, as it is a problem with mixed content to produce the exact output:
<?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:template match="UL">
<xsl:copy>
<xsl:for-each-group select="ITEM" group-by="NL/*/node-name(.)">
<xsl:choose>
<xsl:when test="position() eq 1">
<xsl:for-each select="current-group()">
<ITEM>
<xsl:apply-templates select="text() | NL/*[node-name(.) eq current-grouping-key()]"/>
</ITEM>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<NEW>
<xsl:for-each select="current-group()">
<NL>
<xsl:apply-templates select="NL/(text() | *[node-name(.) eq current-grouping-key()])"/>
</NL>
</xsl:for-each>
</NEW>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="NL/*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I get the result
<UL><ITEM>
aaa
<ITEM1>111</ITEM1>
</ITEM><ITEM>
bbb
<ITEM1>333</ITEM1>
</ITEM><NEW><NL>iii
<ITEM2>222</ITEM2>
</NL><NL>vvv
<ITEM2>444</ITEM2>
</NL></NEW></UL>
When I add instructions to strip white space from the input and indent the output, as in
<?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:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="UL">
<xsl:copy>
<xsl:for-each-group select="ITEM" group-by="NL/*/node-name(.)">
<xsl:choose>
<xsl:when test="position() eq 1">
<xsl:for-each select="current-group()">
<ITEM>
<xsl:apply-templates select="text() | NL/*[node-name(.) eq current-grouping-key()]"/>
</ITEM>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<NEW>
<xsl:for-each select="current-group()">
<NL>
<xsl:apply-templates select="NL/(text() | *[node-name(.) eq current-grouping-key()])"/>
</NL>
</xsl:for-each>
</NEW>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="NL/*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
the result is
<UL>
<ITEM>
aaa
<ITEM1>111</ITEM1>
</ITEM>
<ITEM>
bbb
<ITEM1>333</ITEM1>
</ITEM>
<NEW>
<NL>iii
<ITEM2>222</ITEM2>
</NL>
<NL>vvv
<ITEM2>444</ITEM2>
</NL>
</NEW>
</UL>
As an alternative, if we know there are just ITEM1 and ITEM2 we want to split, here is stylesheet using modes:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()" mode="#all">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="NL">
<xsl:apply-templates select="ITEM1"/>
</xsl:template>
<xsl:template match="ITEM2"/>
<xsl:template match="UL">
<xsl:copy>
<xsl:apply-templates/>
<NEW>
<xsl:apply-templates select="descendant::NL" mode="move"/>
</NEW>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM1" mode="move"/>
</xsl:stylesheet>