How to order self-referencing xml - sorting

I have a list of order lines with each one product on them. The products in may form a self-referencing hierarchy. I need to order the lines in such a way that all products that have no parent or whose parent is missing from the order are at the top, followed by their children. No child may be above its parent in the end result.
So how can i order the following xml:
<order>
<line><product code="3" parent="1"/></line>
<line><product code="2" parent="1"/></line>
<line><product code="6" parent="X"/></line>
<line><product code="1" /></line>
<line><product code="4" parent="2"/></line>
</order>
Into this:
<order>
<line><product code="6" parent="X"/></line>
<line><product code="1" /></line>
<line><product code="2" parent="1"/></line>
<line><product code="3" parent="1"/></line>
<line><product code="4" parent="2"/></line>
</order>
Note that the order within a specific level is not important, as long as the child node follows at some point after it's parent.
I have a solution which works for hierarchies that do not exceed a predefined depth:
<order>
<xsl:variable name="level-0"
select="/order/line[ not(product/#parent=../line/product/#code) ]"/>
<xsl:for-each select="$level-0">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:variable name="level-1"
select="/order/line[ product/#parent=$level-0/product/#code ]"/>
<xsl:for-each select="$level-1">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:variable name="level-2"
select="/order/line[ product/#parent=$level-1/product/#code ]"/>
<xsl:for-each select="$level-2">
<xsl:copy-of select="."/>
</xsl:for-each>
</order>
The above sample xslt will work for hierarchies with a maximum depth of 3 levels and is easily extended to more, but how can i generalize this and have the xslt sort arbitrary levels of depth correctly?

To start with, you could define a couple of keys to help you look up the line elements by either their code or parent attribute
<xsl:key name="products-by-parent" match="line" use="product/#parent" />
<xsl:key name="products-by-code" match="line" use="product/#code" />
You would start off by selecting the line elements with no parent, using a key to do this check:
<xsl:apply-templates select="line[not(key('products-by-code', product/#parent))]"/>
Then, within the template that matches the line element, you would just copy the element, and then select its "children" like so, using the other key
<xsl:apply-templates select="key('products-by-parent', product/#code)"/>
This would be a recursive call, so it would recursively look for its children until no more are found.
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="products-by-parent" match="line" use="product/#parent"/>
<xsl:key name="products-by-code" match="line" use="product/#code"/>
<xsl:template match="order">
<xsl:copy>
<xsl:apply-templates select="line[not(key('products-by-code', product/#parent))]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="line">
<xsl:call-template name="identity"/>
<xsl:apply-templates select="key('products-by-parent', product/#code)"/>
</xsl:template>
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Do note the use of the XSLT identity transform to copy the existing nodes in the XML.

Very interesting problem. I would do this in two passes: first, nest the elements according to their hierarchy. Then output the elements, sorted by the count of their ancestors.
XSLT 1.0 (+ EXSLT node-set() function):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="product-by-code" match="product" use="#code" />
<!-- first pass -->
<xsl:variable name="nested">
<xsl:apply-templates select="/order/line/product[not(key('product-by-code', #parent))]" mode="nest"/>
</xsl:variable>
<xsl:template match="product" mode="nest">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="../../line/product[#parent=current()/#code]" mode="nest"/>
</xsl:copy>
</xsl:template>
<!-- output -->
<xsl:template match="/order">
<xsl:copy>
<xsl:for-each select="exsl:node-set($nested)//product">
<xsl:sort select="count(ancestor::*)" data-type="number" order="ascending"/>
<line><product><xsl:copy-of select="#*"/></product></line>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your input, the result is:
<?xml version="1.0" encoding="UTF-8"?>
<order>
<line>
<product code="6" parent="X"/>
</line>
<line>
<product code="1"/>
</line>
<line>
<product code="3" parent="1"/>
</line>
<line>
<product code="2" parent="1"/>
</line>
<line>
<product code="4" parent="2"/>
</line>
</order>
This still leaves the issue of the existing/missing parent X - I will try to address that later.

Related

How to get nested nodes from XML to CSV via XSLT

I have XML like below:
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header>
<MessageId>{70BF3A9B-9111-48D8-93B4-C6232E74307F}</MessageId>
<Action>http://tempuri.org/example/find</Action>
</Header>
<Body>
<MessageParts>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.02" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<pain.001.001.02>
<GrpHdr>
<MsgId>AB01029407</MsgId>
<CreDtTm>2020-05-07T11:23:08</CreDtTm>
<NbOfTxs>2</NbOfTxs>
<CtrlSum>4598</CtrlSum>
<Grpg>MIXD</Grpg>
<InitgPty>
<Nm>MY COMPANY Ltd1</Nm>
<Id>
<OrgId>
<TaxIdNb>GB 823825133</TaxIdNb>
</OrgId>
</Id>
</InitgPty>
</GrpHdr>
<PmtInf>
<PmtInfId>AB01029407</PmtInfId>
<PmtMtd>TRF</PmtMtd>
<PmtTpInf>
<SvcLvl>
<Cd>SEPA</Cd>
</SvcLvl>
</PmtTpInf>
<Dbtr>
<Nm>MY COMPANY Ltd</Nm>
<PstlAdr>
<AdrLine>Address Line 1</AdrLine>
<AdrLine>Address Line 2</AdrLine>
<Ctry>CB</Ctry>
</PstlAdr>
</Dbtr>
<DbtrAcct>
<Id>
<IBAN>98</IBAN>
</Id>
</DbtrAcct>
<DbtrAgt>
<FinInstnId>
<BIC>ABC123</BIC>
</FinInstnId>
</DbtrAgt>
<ChrgBr>SLEV</ChrgBr>
<CdtTrfTxInf>
<PmtId>
<EndToEndId>Not-Provided</EndToEndId>
</PmtId>
<Amt>
<InstdAmt Ccy="CAD">2198.00</InstdAmt>
</Amt>
<CdtrAgt>
<FinInstnId>
<BIC>SWIFT01</BIC>
</FinInstnId>
</CdtrAgt>
<Cdtr>
<Nm>Creditor Name</Nm>
<PstlAdr>
<AdrLine>tests</AdrLine>
<AdrLine>Chicago</AdrLine>
<Ctry>US</Ctry>
</PstlAdr>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>98</IBAN>
</Id>
</CdtrAcct>
<RmtInf>
<Ustrd>1345</Ustrd>
</RmtInf>
</CdtTrfTxInf>
<CdtTrfTxInf>
<PmtId>
<EndToEndId>Not-Provided</EndToEndId>
</PmtId>
<Amt>
<InstdAmt Ccy="EUR">2400.00</InstdAmt>
</Amt>
<CdtrAgt>
<FinInstnId>
<BIC>SWIFT01</BIC>
</FinInstnId>
</CdtrAgt>
<Cdtr>
<Nm>Creditor Name1</Nm>
<PstlAdr>
<AdrLine>tests</AdrLine>
<AdrLine>Chicago</AdrLine>
<Ctry>US</Ctry>
</PstlAdr>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>98</IBAN>
</Id>
</CdtrAcct>
<RmtInf>
<Ustrd>123456765</Ustrd>
</RmtInf>
</CdtTrfTxInf>
</PmtInf>
</pain.001.001.02>
</Document>
</MessageParts>
</Body>
</Envelope>
I have XSLT like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns1="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:ns2="urn:iso:std:iso:20022:tech:xsd:pain.001.001.02"
version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates
select="ns1:Envelope/ns1:Body//ns2:pain.001.001.02//ns2:GrpHdr"/>
</xsl:template>
<xsl:template match="ns2:GrpHdr">
<xsl:value-of select="ns2:CreDtTm"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="ns2:NbOfTxs"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="ns2:CtrlSum"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="ns2:Grpg"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="ns2:InitgPty/Nm"/>
<xsl:text>
</xsl:text> <!-- Line Return -->
</xsl:template>
</xsl:stylesheet>
With this XSLT I am getting only one set..but not able to go beyond one group of elements. Output i got is:
2020-05-07T11:23:08,2,4598,MIXD,
This looks correct only. But i wanted almost all specific nodes. I could not able to get the inner nested elements from a template.
The desired output is:
2020-05-07T11:23:08,2,4598,MIXD,MY COMPANY Ltd1,GB 823825133,AB01029407,TRF,SEPA,MY COMPANY Ltd,Address Line 1,Address Line 2,CB,98,ABC123,SLEV,Not-Provided,2198.00,SWIFT01,Creditor Name,tests,Chicago,US,98,1345
2020-05-07T11:23:08,2,4598,MIXD,MY COMPANY Ltd1,GB 823825133,AB01029407,TRF,SEPA,MY COMPANY Ltd,Address Line 1,Address Line 2,CB,98,ABC123,SLEV,Not-Provided,2400.00,SWIFT01,Creditor Name1,tests,Chicago,US,98,123456765
I am newer to XSLT. Can anyone help with this ?
Thanks in advance.
Try this as your starting point:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:ns2="urn:iso:std:iso:20022:tech:xsd:pain.001.001.02">
<xsl:output method="text"/>
<xsl:template match="/ns1:Envelope">
<!-- data from header -->
<xsl:variable name="header" select="ns1:Body/ns1:MessageParts/ns2:Document/ns2:pain.001.001.02/ns2:GrpHdr" />
<xsl:value-of select="$header/ns2:CreDtTm"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="$header/ns2:NbOfTxs"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="$header/ns2:CtrlSum"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="$header/ns2:Grpg"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="$header/ns2:InitgPty/ns2:Nm"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="$header/ns2:InitgPty/ns2:Id/ns2:OrgId/ns2:TaxIdNb"/>
<xsl:text>,</xsl:text>
<!-- data from pmt -->
<xsl:variable name="pmt" select="ns1:Body/ns1:MessageParts/ns2:Document/ns2:pain.001.001.02/ns2:PmtInf" />
<xsl:value-of select="$pmt/ns2:PmtMtd"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="$pmt/ns2:Dbtr/ns2:Nm"/>
<xsl:text>,</xsl:text>
<!-- CONTINUE HERE -->
</xsl:template>
</xsl:stylesheet>
Note that this assumes there is only one record in the input XML and therefore only one row in the output CSV. Your XML is structured in a way that allows multiple nodes of the same kind at various level of the hierarchy. If you want to reflect this in your CSV, you need to decide which node will represent a record and adjust the stylesheet so that it creates a separate row for each instance of such node - see an example here: https://stackoverflow.com/a/55311500/3016153

Constructing variable with template in XSLT and then applying xpath

I am using following xslt
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.com/services/test/test/v1">
<xsl:output method="xml" encoding="UTF-8"
omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:variable name="mytree">
<xsl:call-template name="myvariable">
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="count($mytree/foos/foo) > 1">
<xsl:copy-of select="$mytree"/>
</xsl:when>
<xsl:otherwise>
<error>test</error>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="myvariable">
<foos>
<foo>bar1</foo>
<foo>bar2</foo>
<foo>bar3</foo>
<foo>bar4</foo>
</foos>
</xsl:template>
</xsl:stylesheet>
When i use above xslt it should be following output
<foos xmlns="http://www.test.com/services/test/test/v1">
<foo>bar1</foo>
<foo>bar2</foo>
<foo>bar3</foo>
<foo>bar4</foo>
</foos>
but it is
<error xmlns="http://www.test.com/services/test/test/v1">test</error>
when i remove the xmlns="http://www.test.com/services/test/test/v1" output is proper. Not sure what is happening?
Well, with any XML, whether constructed inside of your XSLT or read from a source, if you have elements in a certain namespace, then, to select them with XPath in XSLT, in XSLT 2 you have two options, either use xpath-default-namespace="http://www.test.com/services/test/test/v1" (e.g. <xsl:when test="count($mytree/foos/foo) > 1" xpath-default-namespace="http://www.test.com/services/test/test/v1">) or bind the namespace to a prefix (e.g. <xsl:when xmlns:v1="http://www.test.com/services/test/test/v1" test="count($mytree/v1:foos/v1:foo) > 1">).
You can use these approaches on an ancestor element, for instance the root element of the stylesheet, if it does not interfere with other selections you want to make.
You have to specify qualified element names in your XPath expression to address the foos and foo elements in your default namespace http://www.test.com/services/test/test/v1:
Register the default namespace once more with a namespace prefix (e.g. myns): xmlns:myns="http://www.test.com/services/test/test/v1"
Use that namepace prefix in your XPath expressions to address nodes in that namespace (e.g. myns:foos/myns:foo).
Add exclude-result-prefixes="myns" to suppress the myns prefix in your result document.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.com/services/test/test/v1"
xmlns:myns="http://www.test.com/services/test/test/v1"
exclude-result-prefixes="myns">
…
<xsl:template match="/">
…
<xsl:choose>
<xsl:when test="count($mytree/myns:foos/myns:foo) > 1">
<xsl:copy-of select="$mytree"/>
</xsl:when>
<xsl:otherwise>
<error>test</error>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
…
</xsl:stylesheet>
see XSLT Fiddle
If you only had an XSLT 1.0 processor at hand, you would need the EXSLT node-set function to access the $mytree variables from the result tree:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.com/services/test/test/v1"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
xmlns:myns="http://www.test.com/services/test/test/v1"
exclude-result-prefixes="myns">
…
<xsl:template match="/">
…
<xsl:choose>
<xsl:when test="count(exsl:node-set($mytree)/myns:foos/myns:foo) > 1">
<xsl:copy-of select="$mytree"/>
</xsl:when>
<xsl:otherwise>
<error>test</error>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
…
</xsl:stylesheet>
Use code for remove namespace
<xsl:template match="#*[namespace-uri() = 'http://www.test.com/services/test/test/v1']"/>

Abbreviating text with whitespace with XSLT

I want to extract short lemmas out of text for some explanatory notes. That is, if the text is too long it should output only the first and the last word. This works:
<?xml version="1.0" encoding="UTF-8"?>
<lemma>
<a><b>I</b> can what I can and <b><c>what</c></b> I can't I can</a>
</lemma>
when this xslt is applied
<?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"
version="2.0">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<!-- Identity template : copy all text nodes, elements and attributes -->
<xsl:template match="#*|node()">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="lemma">
<xsl:value-of select="."/>
<xsl:choose>
<xsl:when test="string-length(normalize-space(a)) > 20">
<xsl:value-of select="tokenize(a,' ')[1]"/>
<xsl:text> […] </xsl:text>
<xsl:value-of select="tokenize(a,' ')[last()]"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="a"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
produces the desired output:
I can what I can and what I can't I can
I […] can
Unfortunately whenever two child elements are immediately adjacent the space in between is coded as child-node named „space“. The above solution doesn't work with:
<lemma>
<a><b>I</b><space/><b>can</b> what I can and what I can't I can</a>
</lemma>
I tried to have the single space-special character processed before, but that doesn't work (and I know why), I just don't know how to do it better. It would work with two XLST-runs, I suppose.
<?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"
version="2.0">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<!-- Identity template : copy all text nodes, elements and attributes -->
<xsl:template match="#*|node()">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="space">
 
</xsl:template>
<xsl:template match="lemma">
<xsl:apply-templates select="space"/>
<xsl:value-of select="."/>
<xsl:choose>
<xsl:when test="string-length(normalize-space(a)) > 20">
<xsl:value-of select="tokenize(a,' ')[1]"/>
<xsl:text> […] </xsl:text>
<xsl:value-of select="tokenize(a,' ')[last()]"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="a"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output:
Ican what I can and what I can't I can
Ican […] can
You could do an xsl:apply-templates to process a and save it in a variable...
XML Input
<doc>
<lemma>
<a><b>I</b> can what I can and <b><c>what</c></b> I can't I can</a>
</lemma>
<lemma>
<a><b>I</b><space/><b>can</b> what I can and what I can't I can</a>
</lemma>
</doc>
XSLT 2.0
<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()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="space">
<xsl:text> </xsl:text>
</xsl:template>
<xsl:template match="lemma">
<xsl:variable name="a">
<xsl:apply-templates select="a"/>
</xsl:variable>
<xsl:variable name="norm" select="normalize-space($a)"/>
<xsl:variable name="tokens" select="tokenize($norm,'\s')"/>
<xsl:copy>
<result>
<xsl:value-of select="$norm"/>
</result>
<result>
<xsl:value-of select="
if (string-length($norm) > 20) then
concat($tokens[1],' […] ', $tokens[last()])
else $norm"/>
</result>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XML Output
<doc>
<lemma>
<result>I can what I can and what I can't I can</result>
<result>I […] can</result>
</lemma>
<lemma>
<result>I can what I can and what I can't I can</result>
<result>I […] can</result>
</lemma>
</doc>

How to eliminate all <TAG/> and all attribute="" by XSLT?

In a xsl:stylesheet I have this "identity like" transform, to eliminate comments, empty (terminal) tags and empty attributes... But the second xsl:when not works
<xsl:template match="node()">
<xsl:choose>
<xsl:when test="name()='p' and not(./*) and not(normalize-space(.))"></xsl:when>
<xsl:when test="not(name()='img') and not(name()='br') and not(./*) and not(text())"
></xsl:when> <!-- this line NOT WORKS -->
<xsl:otherwise><xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*">
<xsl:choose>
<xsl:when test="not(normalize-space(.))"></xsl:when>
<xsl:otherwise><xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="comment()"></xsl:template>
Whow to express condition to empty tags in this context?
PS: the "empty rules" are explained here, I try to use it, but not see why not working.
An empty element is an element with no child nodes.
Template match priority is your friend ... the following should be the kind of identity stylesheet that meets your description plus what I think you are doing with image and break elements.
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<!--toss these-->
<xsl:template match="comment() |
*[not(node())] |
#*[not(normalize-space())]"/>
<!--preserve these-->
<xsl:template match="img|br" priority="1">
<xsl:call-template name="identity"/>
</xsl:template>
<!--preserve everything else-->
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Modify an XML node using [MS] XSLT Script

I would like to select a node and modify its attributes and child-nodes using an
xsl:script function. In addition, templates matching child-nodes of that node should
STILL perform their job (after script is done processing the node).
Can it be done using XSLT?
Can you please provide an example / skeleton for such a transformation?
Yes, it can be done. I don't seem to see what the problem is because the XML (or whatever output) of an XSL script is buffered independently from its input.
This is illustrated in the following example whereby a simple XSL script copies an input XML document mostly as-is, changing a few things:
the root element name and attribute
flattening by removing the element from the hierarchy
dropping the results/date element
rename the item's 'source' attribute 'origin'
change the item's 'level' attribute value
rename the FirstName and LastName elements of the item elements
Sample input
<?xml version="1.0" encoding="ISO-8859-1"?>
<MyRoot version="1.2">
<results>
<info>Alpha Bravo</info>
<author>Employee No 321</author>
<date/>
<item source="www" level="6" cost="33">
<FirstName>Jack</FirstName>
<LastName>Frost</LastName>
<Date>1998-10-30</Date>
<Organization>Lemon growers association</Organization>
</item>
<item source="db-11" level="1" cost="65" qry="routine 21">
<FirstName>Mike</FirstName>
<LastName>Black</LastName>
<Date>2006-10-30</Date>
<Organization>Ford Motor Company</Organization>
</item>
</results>
</MyRoot>
Output produced
<?xml version="1.0" encoding="utf-16"?>
<MyNewRoot version="0.1">
<author>Employee No 321</author>
<info>Alpha Bravo</info>
<item cost="33" origin="www" level="77">
<GivenName>Jack</GivenName>
<FamilyName>Frost</FamilyName>
<Date>1998-10-30</Date>
<Organization>Lemon growers association</Organization>
</item>
<item cost="65" qry="routine 21" origin="db-11" level="77">
<GivenName>Mike</GivenName>
<FamilyName>Black</FamilyName>
<Date>2006-10-30</Date>
<Organization>Ford Motor Company</Organization>
</item>
</MyNewRoot>
XSL script
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#default">
<xsl:template match="MyRoot">
<xsl:call-template name="MainTemplate">
</xsl:call-template>
</xsl:template>
<xsl:template name="MainTemplate">
<MyNewRoot version="0.1">
<xsl:copy-of select="results/author" />
<xsl:copy-of select="results/info" />
<xsl:for-each select="results/item">
<xsl:call-template name="FixItemElement"/>
</xsl:for-each>
</MyNewRoot>
</xsl:template>
<xsl:template name="FixItemElement">
<xsl:copy>
<xsl:copy-of select="#*[not(name()='source' or name()='level')]" />
<xsl:attribute name="origin">
<xsl:value-of select="#source"/>
</xsl:attribute>
<xsl:attribute name="level">
<xsl:value-of select="77"/>
</xsl:attribute>
<xsl:for-each select="descendant::*">
<xsl:choose>
<xsl:when test="local-name(.) = 'FirstName'">
<GivenName>
<xsl:value-of select="."/>
</GivenName>
</xsl:when>
<xsl:when test="local-name(.) = 'LastName'">
<FamilyName>
<xsl:value-of select="."/>
</FamilyName>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:copy>
</xsl:template>

Resources