XSLT Convert attribute to element and copy value from an other elemen - xslt

I have XML data like this
<ABC version="1.0">
<XYZ>
<ROWSET ROWS="00001">
<ROWDEF>
<COLUMN ID="ACCT_ID" LEN="016" NULL="Y"/>
<COLUMN ID="AGNT_ID" LEN="004" NULL="Y"/>
<COLUMN ID="CUST_EXTR_ID" LEN="024" NULL="Y"/>
<COLUMN ID="PI_ID" LEN="019" NULL="Y"/>
<COLUMN ID="PRIN_ID" LEN="004" NULL="Y"/>
<COLUMN ID="SYS_ID" LEN="004" NULL="Y"/>
</ROWDEF>
<ROW>
<C>6369921501000060</C>
<C>0000</C>
<C>C13093102141063422034238</C>
<C>6369921501000060 </C>
<C>1500</C>
<C>9008</C>
</ROW>
<ROW>
<C>6369921501000061</C>
<C>0001</C>
<C>C13093102141063422034231</C>
<C>6369921501000060 </C>
<C>1501</C>
<C>9001</C>
</ROW>
</ROWSET>
</XYZ>
</ABC>
And I'm trying to convert this into
<?xml version="1.0" encoding="utf-8"?>
<ABC version="1.0">
<XYZ RC="0067">
<ROWSET ROWS="00001">
<ROWDEF>
<ACCT_ID>6369921501000060</ACCT_ID>
<AGNT_ID>0000</AGNT_ID>
<CUST_EXTR_ID>C13093102141063422034238</CUST_EXTR_ID>
<PI_ID>6369921501000060</PI_ID>
<PRIN_ID>1500</PRIN_ID>
<SYS_ID>9008</SYS_ID>
</ROWDEF>
<ROWDEF>
<ACCT_ID>6369921501000061</ACCT_ID>
<AGNT_ID>0001</AGNT_ID>
<CUST_EXTR_ID>C13093102141063422034231</CUST_EXTR_ID>
<PI_ID>6369921501000060</PI_ID>
<PRIN_ID>1501</PRIN_ID>
<SYS_ID>9001</SYS_ID>
</ROWDEF>
</ROWSET>
</XYZ>
</ABC>
I have looked around and tried few things but it is not working.
Can someone help me with this.
Below is my XSLT. Thanks in advance.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="COLUMN">
<xsl:element name="{#ID}">
<xsl:copy>
<xsl:apply-templates select="node()"></xsl:apply-templates>
</xsl:copy>
<xsl:apply-templates />
<!--<xsl:call-template name="value"></xsl:call-template>-->
</xsl:element>
</xsl:template>
<!--
<xsl:template match="ROW" name="value">
<xsl:copy>
<xsl:apply-templates select="node()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
-->
</xsl:stylesheet>

This stylesheet will mostly do what you want, but I cannot fathom how to generate the attribute for <XYZ RC="0067">.
For each ROWSET it comes across it saves the ROWDEF element in a variable, copies all attribute nodes, and then processes each ROW element. The position of each C element in the ROW is caclulated, and the COLUMN in the corresponding position within the stored ROWDEF element is used to fetch an element name from the ID attribute.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="ROWSET">
<xsl:variable name="columns" select="ROWDEF/COLUMN"/>
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:for-each select="ROW">
<ROWDEF>
<xsl:for-each select="C">
<xsl:variable name="pos" select="position()"/>
<xsl:element name="{$columns[$pos]/#ID}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</ROWDEF>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
output
<?xml version="1.0" encoding="utf-8"?>
<ABC version="1.0">
<XYZ>
<ROWSET ROWS="00001">
<ROWDEF>
<ACCT_ID>6369921501000060</ACCT_ID>
<AGNT_ID>0000</AGNT_ID>
<CUST_EXTR_ID>C13093102141063422034238</CUST_EXTR_ID>
<PI_ID>6369921501000060 </PI_ID>
<PRIN_ID>1500</PRIN_ID>
<SYS_ID>9008</SYS_ID>
</ROWDEF>
<ROWDEF>
<ACCT_ID>6369921501000061</ACCT_ID>
<AGNT_ID>0001</AGNT_ID>
<CUST_EXTR_ID>C13093102141063422034231</CUST_EXTR_ID>
<PI_ID>6369921501000060 </PI_ID>
<PRIN_ID>1501</PRIN_ID>
<SYS_ID>9001</SYS_ID>
</ROWDEF>
</ROWSET>
</XYZ>
</ABC>

Related

XSLT element selection based on Choose not working

I have the following XML:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Matches>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Period>Nov-22</Period>
</Row>
</Matches>
</Row>
</Document>
I want to use XSLT to return the nested /Matches/Row/Period when the Document/Row/Period is undefined (as it is in the second Row of the XML)
So I have the following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://exampleincludednamespace.com/"
exclude-result-prefixes="ns">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<Document>
<xsl:for-each select="/Document/Row">
<xsl:variable name="period" select="/Period" />
<xsl:choose>
<xsl:when test="$period = null">
<xsl:copy>
<xsl:copy-of select="KEY | /Matches/Row/Period" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="KEY | Period" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Document>
</xsl:template>
</xsl:stylesheet>
But it returns the following output:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
</Row>
</Document>
(Note how it is not returning the nested /Matches/Row/Period in the second /Row.
I expect to get the following output:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Period>Nov-22</Period>
</Row>
</Document>
What am I doing wrong?
undefined or null are not checked in XSLT/XPath using expression = null, you would rather use (for node-sets) <xsl:when test="expression"> e.g. <xsl:when test="Period"> that there is at least one Period child element for the context node or test="not(Period)" to check there is no Period child.
In the end I would suggest to use template matching based on the identity transformation template and put any conditions into match pattern (predicates), but that is a different issue.
Got it working with this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://exampleincludednamespace.com/"
exclude-result-prefixes="ns">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<Document>
<xsl:for-each select="/Document/Row">
<xsl:choose>
<xsl:when test="not(Period)">
<xsl:copy>
<xsl:copy-of select="KEY | Matches/Row/Period" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="KEY | Period" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Document>
</xsl:template>
</xsl:stylesheet>
Thanks to #MartinHonnen's guidance.
I believe it could be simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/Document">
<xsl:copy>
<xsl:for-each select="Row">
<xsl:copy>
<xsl:copy-of select="KEY | descendant::Period[1]" />
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT for-each namespace

I'm using a for-each in my XSLT template.
This is my example input XML:
<products>
<data>
<label_1>some_label1</label_1>
<label_2>some_label2</label_2>
<values>
<a>a</a>
<b>b</b>
</values>
</data>
<data>
<label_1>some_label1</label_1>
<label_2>some_label2</label_2>
<values>
<c>c</c>
<d>d</d>
</values>
</data>
</products>
Now based on my template:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http:/example.com/ns">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:variable name="values" select="values" />
<xsl:for-each select="$values">
<xsl:apply-templates select="#*|node()" />
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>
I get only <values></values> and that is ok for me.
That's my output:
<products>
<data>
<a>a</a>
<b>b</b>
</data>
<data>
<c>c</c>
<d>d</d>
</data>
</products>
What i need in my output is namespace like this:
<products>
<data>
<ns:a>a</ns:a>
<ns:b>b</ns:b>
</data>
<data>
<ns:c>c</ns:c>
<ns:d>d</ns:d>
</data>
</products>
So what i understand is "each element of values is applied by template". How can I add namespace ?
You can get output similar to what you show (albeit well-formed) by using:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="data">
<xsl:copy>
<xsl:apply-templates select="values/*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="values/*">
<xsl:element name="ns:{local-name()}" namespace="http:/example.com/ns">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Or, if you prefer:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http:/example.com/ns">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/products">
<products>
<xsl:for-each select="data">
<xsl:copy>
<xsl:for-each select="values/*">
<xsl:element name="ns:{local-name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
Replace http:/example.com/ns with your own namespace URI.
Credits
This answer follows the technique used in this SO answer to a similar problem.
Solution
Add namespace information to all descendants of specific elements. Augment the stylesheet by a template matching this set of nodes:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://my.ns.uri"
>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:variable name="values" select="values" />
<xsl:for-each select="$values">
<xsl:apply-templates select="#*|node()" />
</xsl:for-each>
</data>
</xsl:template>
<!--
Added template.
-->
<xsl:template match="data//*">
<xsl:element name="ns:{name()}" namespace="http://my.ns.uri">
<xsl:for-each select=".">
<xsl:apply-templates select="#*|node()" />
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Remove xml namespace while transformation using xslt

I am transforming XML using XSLT and facing issue while namespace removal. If I remove xmlns it works fine.
Issue 1. It doesn't delete namespace from transformed XML
Issue 2. Doesn't implements other template I have in transformation.
My Input XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Catalog xmlns="http://example.com">
<Books>
<book1>Wise1 Otherwise</book1>
<book2>Great Expectations</book2>
</Books>
<library>
<Name> Forsyth </Name>
<city> Cumming </city>
</library>
</Catalog>
Expected Result
<?xml version="1.0" encoding="UTF-8"?>
<Import>
<Books>
<book1>Wise1 Otherwise</book1>
<book2>Great Expectations</book2>
</Books>
<elab>
<Name> Forsyth </Name>
<city> Cumming </city>
</elab>
</Import>
XSL
<?xml version="1.0" encoding="UTF-8"?>
<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:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#xmlns">
<xsl:element name="{local-name()}" namespace="http://example.com">
<xsl:apply-templates select="node() | #*"/>
</xsl:element>
</xsl:template>
<xsl:template match="library">
<elab>
<xsl:apply-templates />
</elab>
</xsl:template>
<xsl:template match="Catalog">
<Import>
<xsl:apply-templates />
</Import>
</xsl:template>
</xsl:stylesheet>
I think you are missing the namespace declaration in the XSLT templates to match elements:
This is my try:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://example.com">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="ns:library">
<elab>
<xsl:apply-templates />
</elab>
</xsl:template>
<xsl:template match="ns:Catalog">
<Import>
<xsl:apply-templates />
</Import>
</xsl:template>
</xsl:stylesheet>

how to skip elements in xslt based on position()

Here is my xslt:
This one works, but only if hardcode '1,2,'
<xsl:template match="row[contains('1,2,',concat(position(),','))]"
working xslt:
<xsl:stylesheet version="1.0" exclude-result-prefixes="msxsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="positions"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row[contains('1,2,',concat(position(),','))]" name="skiprow"/>
</xsl:stylesheet>
But I want to pass the position values as parameter. But it doesn't work.
I checked the value of the parameter by adding a line, the parameter is good.
<xsl:value-of select="$positions"/>
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
Non-working xslt:
<xsl:stylesheet version="1.0" exclude-result-prefixes="msxsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="positions"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row[contains('$positions',concat(position(),','))]" name="skiprow"/>
</xsl:stylesheet>
Sample Xml:
<root>
<row>
<column1>7004275</column1>
<column2>NUVCFDK</column2>
</row>
<row>
<column1>1001459</column1>
<column2>CAN</column2>
<column3>12</column3>
<column4>646.80</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
<row>
<column1>1001461</column1>
<column2>CAN</column2>
<column3>1</column3>
<column4>9.50</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
</root>
Non-working xslt:
<xsl:template match="row[contains('$positions',concat(position(),','))]" name="skiprow"/>
In XSLT 1.0 a match pattern is forbidden to contain a variable/parameter reference.
Use instead:
<xsl:template match="row>
<xsl:if test=
"not(contains($vPositions, concat(position(),',')))">
<xsl:call-template name="identity"/>
<xsl:if>
</xsl:template>
The complete transformation becomes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="vPositions" select="'1,2,'"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row">
<xsl:if test=
"not(contains($vPositions, concat(position(),',')))">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
and when this transformation is applied on the provided XML document:
<root>
<row>
<column1>7004275</column1>
<column2>NUVCFDK</column2>
</row>
<row>
<column1>1001459</column1>
<column2>CAN</column2>
<column3>12</column3>
<column4>646.80</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
<row>
<column1>1001461</column1>
<column2>CAN</column2>
<column3>1</column3>
<column4>9.50</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
</root>
the wanted, correct result is produced (rows 1 and 2 "deleted"):
<root>
<row>
<column1>1001461</column1>
<column2>CAN</column2>
<column3>1</column3>
<column4>9.50</column4>
<column5>23-06-2009</column5>
<column6>31-12-2009</column6>
<column7/>
</row>
</root>
Do note, however, that your conditions aren't strong enough -- a parameter value of "11,13," will delete four rows -- 1, 11, 3 and 13.
A good condition to use is:
not(contains($vPositions, concat(',',position(),',')))
That means that the parameter must start and end with the comma character.
Here is the complete, corrected transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="vPositions" select="',1,2,'"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row">
<xsl:if test=
"not(contains($vPositions, concat(',',position(),',')))">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

How to prevent line-wrapping by XSLT?

I have some XML like this:
<root>
<do-not-sort>
<z/>
<y/>
</do-not-sort>
<sortable>
<e f="fog" h="bat" j="cat">
<n n="p"/>
<m n="p"/>
</e>
<d b="fop" c="bar" k="cab">
<m o="p"/>
<m n="p"/>
</d>
</sortable>
</root>
I want to sort the children of the "sortable" element by their textual representation, to end up with this:
<root>
<do-not-sort>
<z/>
<y/>
</do-not-sort>
<sortable>
<d b="fop" c="bar" k="cab">
<m n="p"/>
<m o="p"/>
</d>
<e f="fog" h="bat" j="cat">
<m n="p"/>
<n n="p"/>
</e>
</sortable>
</root>
I am currently doing this by applying the following XSLT template:
<?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" indent="yes" encoding="UTF-8" />
<xsl:template match="#* | /">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:value-of select="normalize-space(text()[1])" />
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="sortable//*">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:value-of select="normalize-space(text()[1])" />
<xsl:apply-templates select="*">
<xsl:sort data-type="text" select="local-name()" />
<xsl:sort data-type="text" select="#*" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The sorting works correctly, but if the sorted elements have a lot of attributes, the later attributes each wrap onto a new line, for example:
<sortable>
<this is="an" element="with" a="lot" of="attributes"
and="the"
excess="ones"
each="wrap"
onto="their"
own="line"/>
How do I keep all these attributes on the same line, i.e.
<sortable>
<this is="an" element="with" a="lot" of="attributes" and="the" excess="ones" each="wrap" onto="their" own="line"/>
How do I keep all these attributes on
the same line
In your code, replace:
<xsl:output method="xml" indent="yes" encoding="UTF-8" />
with
<xsl:output method="xml" encoding="UTF-8" />
Of course, this will produce the complete output on a single line! At the moment of writing this XSLT 2.0 still doesn't have a finer grained control over the serialization of the output.
In case you need some elements indented and some not, then you will have to provide your own post-processing (and this post-processing may be easier to write with something different from XSLT).
Update:
Actually, using Saxon 9.1.07 and
<xsl:output method="html" encoding="UTF-8" />
where the complete transformation is:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" />
<xsl:template match="#* | /">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:value-of select="normalize-space(text()[1])" />
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="sortable//*">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:value-of select="normalize-space(text()[1])" />
<xsl:apply-templates select="*">
<xsl:sort data-type="text" select="local-name()" />
<xsl:sort data-type="text" select="#*" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
and the source XML document is:
<root>
<do-not-sort>
<z></z>
<y></y>
</do-not-sort>
<sortable>
<this is="an" element="with" a="lot" of="attributes" and="the" excess="ones" each="wrap" onto="their" own="line"></this>
<e f="fog" h="bat" j="cat"></e>
<d b="fop" c="bar" k="cab"></d>
<d b="foo" c="baz" k="cap"></d>
</sortable>
</root>
I get the output with the wanted indentation:
<root>
<do-not-sort>
<z></z>
<y></y>
</do-not-sort>
<sortable>
<this is="an" element="with" a="lot" of="attributes" and="the" excess="ones" each="wrap" onto="their" own="line"></this>
<e f="fog" h="bat" j="cat"></e>
<d b="fop" c="bar" k="cab"></d>
<d b="foo" c="baz" k="cap"></d>
</sortable>
</root>
If you are using Saxon, and want to avoid everything being on one line, you can use the 'line-length' attribute, like this:
<xsl:output xmlns:saxon="http://saxon.sf.net/" indent="yes" saxon:line-length="2000"/
However, be aware that this only works on the Saxon PE (Professional) edition. See here for details:
http://www.saxonica.com/products/products.xml
If you are using the HE (Home) version, you will get an error like this:
Transformation failed: Requested feature (custom serialization {http://saxon.sf.net/}line-length) requires Saxon-PE