XSLT – creating a numbered attribute based on the value of another attribute - xslt

From a document such as the following list:
<list>
<city ref="Paris">Paris</city>
<city ref="Rome">Rome</city>
<city ref="NYC">New York</city>
<city ref="Lisboa">Lisboa</city>
<city ref="Lisboa">Lisbon</city>
<city ref="Lisboa">Lisbonne</city>
<city ref="NYC">The Big Apple</city>
</list>
I would like to obtain a copy of this list, with an added numeric attribute derived from the #ref attribute (ideally in alphabetical order), for an output like:
<list>
<city ref="Paris" id="3">Paris</city>
<city ref="Rome" id="4">Rome</city>
<city ref="NYC" id="2">New York</city>
<city ref="Lisboa" id="1">Lisboa</city>
<city ref="Lisboa" id="1">Lisbon</city>
<city ref="Lisboa" id="1">Lisbonne</city>
<city ref="NYC" id="2">The Big Apple</city>
</list>
I suppose there is a way to use <xsl:key> to number a sorted list of my #ref attributes, but am not fluent enough to get there.
Many thanks in advance.

With XSLT 3.0 (as supported by Saxon 9.7) it is as easy as
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:variable name="cities" select="sort(distinct-values(/list/city/#ref))"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="city/#ref">
<xsl:copy/>
<xsl:attribute name="id" select="index-of($cities, .)"/>
</xsl:template>
</xsl:stylesheet>
With XSLT 2.0 we can use
<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:variable name="cities" as="xs:string*">
<xsl:perform-sort select="distinct-values(/list/city/#ref)">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="city/#ref">
<xsl:copy/>
<xsl:attribute name="id" select="index-of($cities, .)"/>
</xsl:template>
</xsl:stylesheet>
Finally with XSLT 1.0 the above "translates" into
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl"
version="1.0">
<xsl:key name="city" match="city" use="#ref"/>
<xsl:variable name="cities-rtf">
<xsl:for-each select="/list/city[generate-id() = generate-id(key('city', #ref)[1])]">
<xsl:sort select="#ref"/>
<city id="{position()}" ref="{#ref}"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cities" select="exsl:node-set($cities-rtf)/city"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="city/#ref">
<xsl:copy/>
<xsl:attribute name="id">
<xsl:value-of select="$cities[#ref = current()]/#id"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>

Related

xsl to move node under precibling parent based on matching of parent attribute

I'm trying to handle a xml converted from a pdf to another xml file in some format. First I want to move / group some text / node together based on the geometry of the text but failed to do so. The following is my input & what I wanted:
input xml:
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5.</Text>
</Box>
<Box llx="81.84" lly="560.64" urx="194.39" ury="571.68">
<Text>Equipment list</Text>
</Box>
<Box llx="257.40" lly="560.64" urx="265.36" ury="571.68">
<Text>C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
<Page>
same structure as above...
</Page>
</Pages>
Output xml:
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5. Equipment list C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
<Page>
same structure as above...
</Page>
</Pages>
What i have:
<xsl:template match="#*|node()" name = "identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Box">
<xsl:choose>
<xsl:when test="#ury = following-sibling::Box/#ury">
<xsl:call-template name="identity"/>
<xsl:apply-templates select ="#*"/>
<xsl:copy-of select="following-sibling::Box/Text"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
1.It doesn't copy the wanted nodes 2. i don't know how to exclude the following nodes. I hope someone can help me on this. Many thanks in advance.
I tried the following to exclude the duplicates but it doesn't copy what i want anyways:
<xsl:template match="Box[#ury != preceding-sibling::Box/#ury]/Text">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
This is a case of muenchian grouping in which you need to group the nodes based on certain common criteria and process them to provide an output.
Based on the version of XSLT being used, the solution differs for XSLT 1.0 and XSLT 2.0
XSLT 1.0
Version 1.0 uses a <xsl:key> to group the elements based on common criteria. In this case, the grouping is being done based on the value of attribute #ury so we define a key
<xsl:key name="groupingKey" match="Box" use="#ury" />
Using this key, the templates are grouped together for processing.
<xsl:template match="Box[generate-id() = generate-id(key('groupingKey', #ury)[1])]">
Finally within the grouped elements, a loop is run over the <Text> elements to concatenate its values.
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="key('groupingKey', #ury)/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
Below is the complete XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="groupingKey" match="Box" use="#ury" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Box[generate-id() = generate-id(key('groupingKey', #ury)[1])]">
<xsl:copy>
<xsl:apply-templates select="#*" />
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="key('groupingKey', #ury)/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
</xsl:copy>
</xsl:template>
<xsl:template match="Box" />
</xsl:stylesheet>
XSLT 2.0
Version 2.0 is advanced and provides a simpler approach as compared to XSLT 1.0. The <xsl:for-each-group> and group-by feature can be used to group the elements together.
<xsl:for-each-group select="Box" group-by="#ury">
Below is the complete XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Page">
<xsl:copy>
<xsl:apply-templates select="PAGENUMBER" />
<xsl:for-each-group select="Box" group-by="#ury">
<xsl:copy>
<xsl:apply-templates select="#*" />
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="current-group()/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Both the XSLT provide the required output
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5. Equipment list C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
</Pages>

XSLT Merging two nodes into one

I'm looking to merge two nodes (or more) into one. I've worked with xslt for a little while, but mainly doing pretty simple stuff. I've done much searching, but solutions have been over my head so I haven't been able to adapt to my own problem. Closest thing I've found is an answer by Martin Honnen using a function he built called "eliminate-deep-equal-duplicates".
My problem is I can have two or more <Coverage> nodes that have "CoverageCd=ADDRL" and I need to combine these nodes only, no other Coverage nodes with other CoverageCd values. So I want to merge the ADDRL nodes but keep the unique "Addr" child node for all ADDRL iterations.
One other caveat is that I need to have the count of merged ADDRL nodes and place in the "OptionValue" element. So In my example where I have two ADDRL Coverage nodes my OptionValue needs to be 2. My xslt currently almost gives me what I need, but duplicates the MiscParty/GeneralPartyInfo which I don't want. And while I have the variable AddrlCount that gives me the correct value to place in my OptionValue,
I'm not quite sure how to incorporate that into the current xslt. I know my main problem is that I'm not exactly sure what the "eliminate-deep-equal-duplicates" function is doing. Any help anyone could provide would be greatly appreciated.
Input XML
<ACORD>
<InsuranceSvcRq>
<HomePolicyQuoteInqRq>
<HomeLineBusiness>
<Dwell LocationRef="000b3c6b-264f-83b7-1b80-006a3ce1f40e">
<PolicyTypeCd>06</PolicyTypeCd>
<PurchaseDt>2011-05-10</PurchaseDt>
<Construction>
<ConstructionCd>F</ConstructionCd>
<com.ormutual_recontype>Standard</com.ormutual_recontype>
<YearBuilt>1988</YearBuilt>
<BldgArea>
<NumUnits>1200</NumUnits>
<UnitMeasurementCd>Square Foot</UnitMeasurementCd>
</BldgArea>
</Construction>
<Coverage>
<CoverageCd>MEDPM</CoverageCd>
<Limit>
<FormatInteger>1000</FormatInteger>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>LAC</CoverageCd>
<Limit>
<FormatInteger>50000</FormatInteger>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>ADDRL</CoverageCd>
<Option>
<OptionTypeCd>Num1</OptionTypeCd>
<OptionValue>1</OptionValue>
</Option>
<MiscParty>
<GeneralPartyInfo>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<Addr1>9325 SW CAMILLE TER</Addr1>
<City>PORTLAND</City>
<StateProvCd>OR</StateProvCd>
<PostalCode>97223</PostalCode>
<County>WASHINGTON</County>
</Addr>
</GeneralPartyInfo>
</MiscParty>
</Coverage>
<Coverage>
<CoverageCd>ADDRL</CoverageCd>
<Option>
<OptionTypeCd>Num1</OptionTypeCd>
<OptionValue>1</OptionValue>
</Option>
<MiscParty>
<GeneralPartyInfo>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<Addr1>2222 ANDREW AVE NW</Addr1>
<City>SALEM</City>
<StateProvCd>OR</StateProvCd>
<PostalCode>97304</PostalCode>
<County>POLK</County>
</Addr>
</GeneralPartyInfo>
</MiscParty>
</Coverage>
</Dwell>
</HomeLineBusiness>
</HomePolicyQuoteInqRq>
</InsuranceSvcRq>
</ACORD>
Desired Output
<ACORD>
<InsuranceSvcRq>
<HomePolicyQuoteInqRq>
<HomeLineBusiness>
<Dwell LocationRef="000b3c6b-264f-83b7-1b80-006a3ce1f40e">
<PolicyTypeCd>06</PolicyTypeCd>
<PurchaseDt>2011-05-10</PurchaseDt>
<Construction>
<ConstructionCd>F</ConstructionCd>
<com.ormutual_recontype>Standard</com.ormutual_recontype>
<YearBuilt>1988</YearBuilt>
<BldgArea>
<NumUnits>1200</NumUnits>
<UnitMeasurementCd>Square Foot</UnitMeasurementCd>
</BldgArea>
</Construction>
<Coverage>
<CoverageCd>MEDPM</CoverageCd>
<Limit>
<FormatInteger>1000</FormatInteger>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>LAC</CoverageCd>
<Limit>
<FormatInteger>50000</FormatInteger>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>ADDRL</CoverageCd>
<Option>
<OptionTypeCd>Num1</OptionTypeCd>
<OptionValue>2</OptionValue>
</Option>
<MiscParty>
<GeneralPartyInfo>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<Addr1>9325 SW CAMILLE TER</Addr1>
<City>PORTLAND</City>
<StateProvCd>OR</StateProvCd>
<PostalCode>97223</PostalCode>
<County>WASHINGTON</County>
</Addr>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<Addr1>2222 ANDREW AVE NW</Addr1>
<City>SALEM</City>
<StateProvCd>OR</StateProvCd>
<PostalCode>97304</PostalCode>
<County>POLK</County>
</Addr>
</GeneralPartyInfo>
</MiscParty>
</Coverage>
</Dwell>
</HomeLineBusiness>
</HomePolicyQuoteInqRq>
</InsuranceSvcRq>
</ACORD>
My attempt at the xslt
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:functx="http://www.functx.com" xmlns:mf="http://example.com/mf" exclude-result-prefixes="xs functx mf">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:function name="functx:index-of-node" as="xs:integer*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="nodeToFind" as="node()"/>
<xsl:sequence select=" for $seq in (1 to count($nodes)) return $seq[$nodes[$seq] is $nodeToFind] "/>
</xsl:function>
<xsl:function name="mf:eliminate-deep-equal-duplicates" as="node()*">
<xsl:param name="nodes"/>
<xsl:sequence select="for $node in $nodes return $node[not(some $preceding-node in $nodes[position() lt functx:index-of-node($nodes, $node)] satisfies deep-equal($node, $preceding-node))]"/>
</xsl:function>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:variable name="AddrlCount" select="count(ACORD/InsuranceSvcRq/HomePolicyQuoteInqRq/HomeLineBusiness/Dwell/Coverage[./CoverageCd='ADDRL'])"/>
<xsl:template match="ACORD/InsuranceSvcRq/HomePolicyQuoteInqRq/HomeLineBusiness/Dwell">
<xsl:copy>
<xsl:for-each-group select="Coverage" group-by="CoverageCd">
<xsl:copy>
<xsl:apply-templates select="Coverage[./CoverageCd='ADDRL'],
mf:eliminate-deep-equal-duplicates(current-group()/(* except (Addr))),
Addr"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You are grouping Coverage elements by CoverageCd values
<xsl:for-each-group select="Coverage" group-by="CoverageCd">
So, at this point you are positioned on a Coverage element (ones with the first occurrence of a CoverageCd value). But you then do this...
<xsl:apply-templates select="Coverage[./CoverageCd='ADDRL'],
This means you are looking for a Coverage element that is a child of a covering Coverage element, which would return element. Similarly, where you do this....
mf:eliminate-deep-equal-duplicates(current-group()/(* except (Addr)))
current-group() returns all Coverage elements with the same code, none of which have Addr elements, so this selects all elements.
What you can possibly do, is just replace the statement with this:
<xsl:apply-templates select="mf:eliminate-deep-equal-duplicates(current-group()/*)" />
Then you can have templates for matching elements you wish to change.
Try this XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:functx="http://www.functx.com" xmlns:mf="http://example.com/mf" exclude-result-prefixes="xs functx mf">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:function name="functx:index-of-node" as="xs:integer*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="nodeToFind" as="node()"/>
<xsl:sequence select=" for $seq in (1 to count($nodes)) return $seq[$nodes[$seq] is $nodeToFind] "/>
</xsl:function>
<xsl:function name="mf:eliminate-deep-equal-duplicates" as="node()*">
<xsl:param name="nodes"/>
<xsl:sequence select="for $node in $nodes return $node[not(some $preceding-node in $nodes[position() lt functx:index-of-node($nodes, $node)] satisfies deep-equal($node, $preceding-node))]"/>
</xsl:function>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ACORD/InsuranceSvcRq/HomePolicyQuoteInqRq/HomeLineBusiness/Dwell">
<xsl:copy>
<xsl:apply-templates select="#*|node() except Coverage" />
<xsl:for-each-group select="Coverage" group-by="CoverageCd">
<xsl:copy>
<xsl:apply-templates select="mf:eliminate-deep-equal-duplicates(current-group()/*)" />
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="OptionValue">
<xsl:copy>
<xsl:value-of select="count(mf:eliminate-deep-equal-duplicates(current-group()/MiscParty/GeneralPartyInfo/Addr))" />
</xsl:copy>
</xsl:template>
<xsl:template match="GeneralPartyInfo">
<xsl:copy>
<xsl:apply-templates select="mf:eliminate-deep-equal-duplicates(current-group()/MiscParty/GeneralPartyInfo/Addr)" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
To be honest, I don't think you even need the mf:eliminate-deep-equal-duplicates in this particular case. This XSLT also produces the result you need
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:functx="http://www.functx.com" xmlns:mf="http://example.com/mf" exclude-result-prefixes="xs functx mf">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ACORD/InsuranceSvcRq/HomePolicyQuoteInqRq/HomeLineBusiness/Dwell">
<xsl:copy>
<xsl:apply-templates select="#*|node() except Coverage" />
<xsl:for-each-group select="Coverage" group-by="CoverageCd">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="OptionValue">
<xsl:copy>
<xsl:value-of select="count(current-group()/MiscParty/GeneralPartyInfo/Addr)" />
</xsl:copy>
</xsl:template>
<xsl:template match="GeneralPartyInfo">
<xsl:copy>
<xsl:apply-templates select="current-group()/MiscParty/GeneralPartyInfo/Addr" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Sort the element content (serno) when it is grouped by other two elements (surname, firstname)

Please suggest how to 'Sort the element content (serno) in ascending oder, when it is grouped by other two elements (surname, firstname)'. Grouping is happening properly but unable to sort the numeral values of 'serno' element content. (XSLT2)
XML:
<!DOCTYPE index [<!ENTITY Racute "Ŕ"><!ENTITY racute "ŕ">]>
<index>
<cmindexnote>This index is cumulative for volume 65</cmindexnote>
<issue>v65n</issue>
<author><lastname>Rudra</lastname><firstname>TP</firstname>
<refserial><serno>14</serno><serno>24</serno></refserial></author>
<author><lastname>Rudra</lastname><firstname>TP</firstname>
<refserial><serno>4</serno></refserial></author>
<author><lastname>Rudra</lastname><firstname>AP</firstname>
<refserial><serno>14</serno></refserial></author>
<author><lastname>Rudramuni</lastname><firstname>CP</firstname>
<refserial><serno>14</serno></refserial></author>
<author><lastname>Rudramuni</lastname><firstname>AP</firstname>
<refserial><serno>24</serno></refserial></author>
<author><lastname>Rud&racute;amuni</lastname><firstname>AP</firstname>
<refserial><serno>24</serno></refserial></author>
<author><lastname>Rudramuni</lastname><firstname>AP</firstname>
<refserial><serno>224</serno></refserial></author>
<author><lastname>Rudramuni</lastname><firstname>TP</firstname>
<refserial><serno>10</serno></refserial></author>
<author><lastname>Rudramuni</lastname><firstname>AP</firstname>
<refserial><serno>9</serno></refserial></author>
<author><lastname>Rudresh</lastname><firstname>TP</firstname>
<refserial><serno>11</serno></refserial></author>
<author><lastname>&Racute;udramuni</lastname><firstname>TP</firstname>
<refserial><serno>19</serno></refserial></author>
</index>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="no" use-character-maps="chars"/>
<xsl:character-map name="chars">
<xsl:output-character character="Ŕ" string="&Racute;"/>
<xsl:output-character character="ŕ" string="&racute;"/>
</xsl:character-map>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>
</xsl:template>
<xsl:template match="index">
<cmindexnote><xsl:value-of select="cmindexnote"/></cmindexnote>
<issue><xsl:value-of select="issue"/></issue>
<xsl:for-each-group select="author" group-by="concat(lastname, firstname)">
<xsl:sort select="lastname" collation="http://saxon.sf.net/collation?lang=en&ignore-modifiers=yes"/>
<xsl:sort select="current-grouping-key()" collation="http://saxon.sf.net/collation?lang=en&ignore-modifiers=yes"/>
<!--xsl:sort select="number(../serno)"/-->
<author>
<lastname><xsl:value-of select="*[1]"/></lastname>
<firstname><xsl:value-of select="*[2]"/></firstname>
<refserial>
<xsl:for-each-group select="current-group()" group-by="refserial">
<xsl:apply-templates select="current-group()">
<xsl:sort select="number(serno)"/>
</xsl:apply-templates>
</xsl:for-each-group>
</refserial>
</author>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="author">
<xsl:apply-templates select="node() except (lastname, firstname)"/>
</xsl:template>
<xsl:template match="refserial">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Required OutPut:
<?xml version="1.0" encoding="UTF-8"?><cmindexnote>This index is cumulative for volume 65</cmindexnote><issue>v65n</issue>
<author><lastname>Rudra</lastname><firstname>AP</firstname>
<refserial><serno>14</serno></refserial></author>
<author><lastname>Rudra</lastname><firstname>TP</firstname>
<refserial><serno>4</serno><serno>14</serno><serno>24</serno></refserial></author>
<author><lastname>Rudramuni</lastname><firstname>AP</firstname>
<refserial><serno>9</serno><serno>24</serno><serno>224</serno></refserial></author>
<author><lastname>Rud&racute;amuni</lastname><firstname>AP</firstname>
<refserial><serno>24</serno></refserial></author>
<author><lastname>Rudramuni</lastname><firstname>CP</firstname>
<refserial><serno>14</serno></refserial></author>
<author><lastname>Rudramuni</lastname><firstname>TP</firstname>
<refserial><serno>10</serno></refserial></author>
<author><lastname>&Racute;udramuni</lastname><firstname>TP</firstname>
<refserial><serno>19</serno></refserial></author>
<author><lastname>Rudresh</lastname><firstname>TP</firstname>
<refserial><serno>11</serno></refserial></author>
Does this work for you?
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" use-character-maps="chars"/>
<xsl:character-map name="chars">
<xsl:output-character character="Ŕ" string="&Racute;"/>
<xsl:output-character character="ŕ" string="&racute;"/>
</xsl:character-map>
<xsl:template match="/index">
<xsl:copy-of select="cmindexnote | issue"/>
<xsl:for-each-group select="author" group-by="concat(lastname, firstname)">
<xsl:sort select="lastname" collation="http://saxon.sf.net/collation?lang=en&ignore-modifiers=yes"/>
<xsl:sort select="firstname" collation="http://saxon.sf.net/collation?lang=en&ignore-modifiers=yes"/>
<author>
<xsl:copy-of select="lastname | firstname"/>
<refserial>
<xsl:for-each select="current-group()/refserial/serno">
<xsl:sort select="." data-type="number" order="ascending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</refserial>
</author>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

Transform Exception. Adding Attribute after Sub-element

I have this XML:
<root>
<tab name="Detail">
<section name="mysection">
<items level="1">
<Idx_name>9</Idx_name>
<Type>mytype</Type>
<item name="myname">
<Grams um="(g)">9,0</Grams>
<Pre-infusion>Max</Pre-infusion>
</item>
<Std._Mode>On</Std._Mode>
<Price>100</Price>
</items>
</section>
</tab>
</root>
and this XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items/*">
<xsl:choose>
<xsl:when test="not(name()='item')">
<xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Now, what I want is:
<root>
<tab name="Detail">
<section name="mysection">
<items level="1" Idx_name="9" Type="mytype" Std._Mode="On" Price="100">
<item name="myname">9,0Max</item>
</items>
</section>
</tab>
</root>
I obtain the error: "An attribute cannot be added after a child"
Unluckily, I can not change the order of elements in the node items of my original XML
How can I do it ?
Thanks
Ivan
Make sure you process the elements first you want to transform into attributes e.g. with XSLT 2.0 where you can process sequences which have a order you can simply do
<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="items">
<xsl:copy>
<xsl:apply-templates select="#*, * except item, item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items/*[not(self::item)]">
<xsl:attribute name="{name()}" select="."/>
</xsl:template>
<xsl:template match="items/item">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT 1.0 you would need to spell out several apply-templates in the order you want.

Adding a namespace to just one element

How do I only add a namespace to the root element?
My XML:
<Envelope>
<from>
<contents />
</from>
</Envelope>
My desired output:
<Envelope xmlns:tns="Foo">
<from>
<contents />
</from>
</Envelope>
I can only get "xmlns='Foo'" using this, not "xmlns:tns=..":
<xsl:element name="{local-name()}" namespace="Foo" >
<xsl:copy-of select="attribute::*"/>
<xsl:apply-templates />
</xsl:element>
Here is a complete transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tns="Foo">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:element name="{name()}">
<xsl:copy-of select=
"document('')/*/namespace::*[name()='tns']"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Envelope>
<from>
<contents />
</from>
</Envelope>
the wanted, correct result is produced:
<Envelope xmlns:tns="Foo">
<from>
<contents/>
</from>
</tns:Envelope>