tags:

views:

74

answers:

3

Hello, I have XML that looks like this:

<ROW ref="0005631" type="04" line="1" value="Australia"/>
<ROW ref="0005631" type="00" line="1" value="John"/>
<ROW ref="0005631" type="02" line="1" value="Builder"/>
<ROW ref="0005631" type="01" line="1" value="Smith"/>

I need a solution in XSL to format it to look this this:

John Smith Builder Australia

Any help would be great. I have lots and lots of varying data, many different types and lines which are all mixed up so I don't want to hardcode any of it.

A: 

Did you mean, you want to get elements from the same line, then display it according to type number? I think xsl:sort is what you need

phunehehe
I don't think it's as simple as just sort. I have hundreds of references, and references have lots of lines. So I need to be able to somehow link the line number to the reference and output all the items have belong to that line. All the data is in a random order too, so I just can't iterate through the data.
Ben
@Ben, then maybe you ought to: A. give a larger sample of your XML; B. Explain more clearly what your requirements are;C. Give a larger example of expected output; andD. Show what your xsl looks like so far.
Jonathan Fingland
+2  A: 

To use xsl:sort on your data you need to have a parent node which you did not provide in your example code but would have to have one in order for your XML document to be valid. Assuming that the Parent node is <TABLE /> your input would be.

<TABLE>
    <ROW ref="0005631" type="04" line="1" value="Australia"/>
    <ROW ref="0005631" type="00" line="1" value="John"/>
    <ROW ref="0005631" type="02" line="1" value="Builder"/>
    <ROW ref="0005631" type="01" line="1" value="Smith"/>
</TABLE>

You would then be able to use the following XSLT to achieve the desired results assuming that you are sorting solely based on the value of the type column.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

  <xsl:output method="text"/>

  <xsl:template match="TABLE">
    <xsl:apply-templates>
      <xsl:sort select="@type" data-type="number"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="ROW">
    <xsl:apply-templates select="@value"/>
    <xsl:text>&#x20;</xsl:text>
  </xsl:template>

</xsl:stylesheet>

If you wanted to sort based on ref, type, line as opposed to simply type you could use the following for the TABLE template.

  <xsl:template match="TABLE">
    <xsl:apply-templates>
      <xsl:sort select="@ref" data-type="number"/>
      <xsl:sort select="@type" data-type="number"/>
      <xsl:sort select="@line" data-type="number"/>
    </xsl:apply-templates>
  </xsl:template>

I couldn't determine exactly what sort order your wanted from your question, but any alternatives should be straight forward given these two examples.

Blake Taylor
A: 

In XSLT 1.0:

<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:output method="text" encoding="utf-8" />

  <!-- index ROWs by their @ref -->
  <xsl:key name="kRowByRef"        match="ROW" use="@ref" />
  <!-- index ROWs by their @ref and @line -->
  <xsl:key name="kRowByRefAndLine" match="ROW" use="concat(@ref, ',', @line)" />

  <xsl:template match="/*">
    <!-- 1) rows are processed with "ORDER BY @ref" -->
    <xsl:apply-templates select="ROW" mode="ref-group">
      <xsl:sort select="@ref"  data-type="number" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="ROW" mode="ref-group">
    <!-- 2) rows are grouped by @ref -->
    <xsl:variable name="thisGroup" select="
      key('kRowByRef', @ref)
    " />

    <xsl:if test="generate-id() = generate-id($thisGroup[1])">
      <!-- 2.1) for the first item in the group, 
                nodes are processed with "ORDER BY @line" -->
      <xsl:apply-templates select="$thisGroup" mode="line-group">
        <xsl:sort select="@line" data-type="number" />
      </xsl:apply-templates>
      <!-- use a line as record separator -->
      <xsl:text>----------------------------&#10;</xsl:text>
    </xsl:if>
  </xsl:template>

  <xsl:template match="ROW" mode="line-group">
    <!-- 3) rows are grouped by @ref, @line -->
    <xsl:variable name="thisGroup" select="
      key('kRowByRefAndLine', concat(@ref, ',', @line))
    " />

    <xsl:if test="generate-id() = generate-id($thisGroup[1])">
      <!-- 3.1) for the first item in the group, 
                nodes are processed with "ORDER BY @type" -->
      <xsl:apply-templates select="$thisGroup" mode="line">
        <xsl:sort select="@type" data-type="number" />
      </xsl:apply-templates>
    </xsl:if>
  </xsl:template>

  <xsl:template match="ROW" mode="line">
    <!-- 4) rows are printed out & appended with space or newline -->
    <xsl:value-of select="@value" />
    <xsl:choose>
      <xsl:when test="position() = last()">
        <xsl:text>&#10;</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text> </xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

For this completely unordered input:

<data>
  <ROW ref="0005631" type="04" line="1" value="Australia"/>
  <ROW ref="0005632" type="00" line="1" value="Jack"/>
  <ROW ref="0005631" type="00" line="1" value="John"/>
  <ROW ref="0005631" type="01" line="1" value="Smith"/>
  <ROW ref="0005632" type="04" line="2" value="Whiskey"/>
  <ROW ref="0005632" type="02" line="1" value="Tennessee"/>
  <ROW ref="0005631" type="02" line="1" value="Builder"/>
  <ROW ref="0005632" type="01" line="1" value="Daniel's"/>
</data>

I get:

John Smith Builder Australia
----------------------------
Jack Daniel's Tennessee
Whiskey
----------------------------


Explanation

What happens here is a staged grouping and sorting, so that the eventual result is grouped and ordered by @ref, @line, and @type.

All of the grouping is Muenchian grouping. The template #1 processes all <ROW> elements in @ref order:

<ROW ref="0005631" type="04" line="1" value="Australia"/>
<ROW ref="0005631" type="00" line="1" value="John"/>
<ROW ref="0005631" type="01" line="1" value="Smith"/>
<ROW ref="0005631" type="02" line="1" value="Builder"/>
<ROW ref="0005632" type="00" line="1" value="Jack"/>
<ROW ref="0005632" type="04" line="2" value="Whiskey"/>
<ROW ref="0005632" type="02" line="1" value="Tennessee"/>
<ROW ref="0005632" type="01" line="1" value="Daniel's"/>

Template #2 processes the first of each @ref group only:

<ROW ref="0005631" type="04" line="1" value="Australia"/>
<ROW ref="0005632" type="00" line="1" value="Jack"/>

handing the entire group ($thisGroup) over to template #3 in two steps, in @line order:

<ROW ref="0005631" type="04" line="1" value="Australia"/>
<ROW ref="0005631" type="00" line="1" value="John"/>
<ROW ref="0005631" type="01" line="1" value="Smith"/>
<ROW ref="0005631" type="02" line="1" value="Builder"/>

<ROW ref="0005632" type="00" line="1" value="Jack"/>
<ROW ref="0005632" type="02" line="1" value="Tennessee"/>
<ROW ref="0005632" type="01" line="1" value="Daniel's"/>
<ROW ref="0005632" type="04" line="2" value="Whiskey"/>

Template #3 takes the first of each @line group:

<ROW ref="0005631" type="04" line="1" value="Australia"/>
<ROW ref="0005632" type="00" line="1" value="Jack"/>
<ROW ref="0005632" type="04" line="2" value="Whiskey"/>

and processes them, handing the entire group over to template #4 in three steps, in @type order:

<ROW ref="0005631" type="00" line="1" value="John"/>
<ROW ref="0005631" type="01" line="1" value="Smith"/>
<ROW ref="0005631" type="02" line="1" value="Builder"/>
<ROW ref="0005631" type="04" line="1" value="Australia"/>

<ROW ref="0005632" type="00" line="1" value="Jack"/>
<ROW ref="0005632" type="01" line="1" value="Daniel's"/>
<ROW ref="0005632" type="02" line="1" value="Tennessee"/>

<ROW ref="0005632" type="04" line="2" value="Whiskey"/>

Template #4 prints them out, appending spaces or newlines where appropriate.

Tomalak
Very good, thank you.I was able to modify your code to do exactly what I needed.
Ben