views:

43

answers:

1

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"&gt;

  <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"/>
+2  A: 

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"&gt;

  <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>
Dimitre Novatchev
Hmm, when I use Saxon 9.1.0.8, I get the error "XTTE1020: A sequence of more than one item is not allowed as the @select attribute of xsl:sort".
Andrew Swan
Same with Saxon 9.1.0.7. I ran it from Java - what about you?
Andrew Swan
@Andrew-Swan: If this is so, you are not using exactly the same stylesheet as you provided in your question. Or do you think I am imagining this?
Dimitre Novatchev
@Andrew-Swan: I have updated my answer and it now contains the complete transformation and the source XML document -- you have just to run the transformation as is and you'll get the wanted results -- same as reported in my answer.
Dimitre Novatchev
Thanks for your help Dimitre, using method="html" fixed the wrapping. But I do have to use Saxon 8.7, not 9.x, otherwise I get the XTTE1020 error described above - unless you think I am imagining this... ;-) By the way, the template correctly sorts all descendants of the "sortable" element except for its direct children; would you know how to fix this?
Andrew Swan