We help IT Professionals succeed at work.

XSL to Select Unique and Sort

GJDuquette
GJDuquette asked
on
Hello Experts,

I am trying to create a XSL to give the output below:


Time            Judge      Case            Defendant

09:00 AM  EAM       CR04003107    BLOUGH, JOE
10:00 AM  EAM       CR04004200      BLOUGH, FRED



I need to select only Court=CR3 and unique cases base on the first ten characters of CaseNo.

Any suggestions on how to approach this would be appreciated.
 
XML DATA:

<?xml version="1.0" encoding="ISO-8859-1"?>
<CRDocket>
<Case>
<Time>09:00 AM</Time>
<Judge>EAM</Judge>
<Court>CR3</Court>
<CaseNo>CR04003107-01</CaseNo>
<Defendant>BLOUGH, JOE</Defendant>
</Case>
<Case>
<Time>09:00 AM</Time>
<Judge>EAM</Judge>
<Court>CR3</Court>
<CaseNo>CR04003107-02</CaseNo>
<Defendant>BLOUGH, JOE</Defendant>
</Case>
<Case>
<Time>09:00 AM</Time>
<Judge>NAT</Judge>
<Court>CR5</Court>
<CaseNo>CR11000475-01</CaseNo>
<Defendant>DOUGH, JANE</Defendant>
</Case>
<Case>
<Time>10:00 AM</Time>
<Judge>EAM</Judge>
<Court>CR3</Court>
<CaseNo>CR04004200-02</CaseNo>
<Defendant>BLOUGH, FRED</Defendant>
</Case>
</CRDocket>

Open in new window

Comment
Watch Question

Gertone (Geert Bormans)Information Architect
Top Expert 2006

Commented:
This will do that
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:key name="case" match="Case" use="substring(CaseNo, 1, 10)"/>
    <xsl:strip-space elements="*"/>
    <xsl:output method="html"/>
    <xsl:template match="CRDocket">
        <table>
            <tr>
                <th>Time</th>
                <th>Judege</th>
                <th>Case</th>
                <th>Defendant</th>
            </tr>
            <xsl:apply-templates select="Case[generate-id() = generate-id(key('case', substring(CaseNo, 1, 10))[1])]"/>
        </table>
    </xsl:template>
    <xsl:template match="Case">
        <tr>
            <td><xsl:value-of select="Time"/></td>
            <td><xsl:value-of select="Judge"/></td>
            <td><xsl:value-of select="substring(CaseNo, 1, 10)"/></td>
            <td><xsl:value-of select="Defendant"/></td>
        </tr>
    </xsl:template>
</xsl:stylesheet>

Open in new window

Commented:
<xsl:stylesheet version="1.0"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                exclude-result-prefixes="xsi xsl xsd">

   <xsl:output method="text" version="1.0" encoding="UTF-8" indent="no" omit-xml-declaration="yes"/>
  
  <xsl:key name="CaseNoKey" match="Case" use="substring(CaseNo,1,10)"/>
  
  <xsl:template match="/CRDocket/Case[Court!='CR3']"/>

  <xsl:template match="/CRDocket">
  	<xsl:text>Time       Judge   Case          Defendant</xsl:text>
  	<xsl:for-each select="Case[generate-id() = generate-id(key('CaseNoKey', substring(CaseNo, 1, 10))[1])][Court='CR3']">
	  	<xsl:text disable-output-escaping="yes">
</xsl:text>
	  	<xsl:value-of select="Time"/>
	  	<xsl:text disable-output-escaping="yes">    </xsl:text>
	  	<xsl:value-of select="Judge"/>
	  	<xsl:text disable-output-escaping="yes">    </xsl:text>
	  	<xsl:value-of select="substring(CaseNo,1,10)"/>
	  	<xsl:text disable-output-escaping="yes">    </xsl:text>
	  	<xsl:value-of select="Defendant"/>
  	</xsl:for-each>
  </xsl:template>
	-->
	
</xsl:stylesheet>

Open in new window

Gertone (Geert Bormans)Information Architect
Top Expert 2006

Commented:
for uniqyueness, I used the muenchian technique

http://www.jenitennison.com/xslt/grouping/muenchian.xml
Information Architect
Top Expert 2006
Commented:
I see that I missed one reuirement, the one about the Court = CR3

change this line
    <xsl:key name="case" match="Case" use="substring(CaseNo, 1, 10)"/>

into this
    <xsl:key name="case" match="Case[Court = 'CR3']" use="substring(CaseNo, 1, 10)"/>

Full code repeated below

It spits out a HTML table,
but it can easily be changed into text only

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:key name="case" match="Case[Court = 'CR3']" use="substring(CaseNo, 1, 10)"/>
    <xsl:strip-space elements="*"/>
    <xsl:output method="html"/>
    <xsl:template match="CRDocket">
        <table>
            <tr>
                <th>Time</th>
                <th>Judege</th>
                <th>Case</th>
                <th>Defendant</th>
            </tr>
            <xsl:apply-templates select="Case[generate-id() = generate-id(key('case', substring(CaseNo, 1, 10))[1])]"/>
        </table>
    </xsl:template>
    <xsl:template match="Case">
        <tr>
            <td><xsl:value-of select="Time"/></td>
            <td><xsl:value-of select="Judge"/></td>
            <td><xsl:value-of select="substring(CaseNo, 1, 10)"/></td>
            <td><xsl:value-of select="Defendant"/></td>
        </tr>
    </xsl:template>
</xsl:stylesheet>

Open in new window

Commented:
Whilst Gertone's revised answer is now correct I personally prefer to see all discriminators in the same line for ease of maintenance.  You could change Gertones first answer to

<xsl:apply-templates select="Case[generate-id() = generate-id(key('case', substring(CaseNo, 1, 10))[1])][Court='CR3']"/>

Open in new window


to achieve the same result whilst keeping the discrimantors together

Author

Commented:
Very nice! How can we add sorting by time and defendant?

Commented:
  	<xsl:for-each select="Case[generate-id() = generate-id(key('CaseNoKey', substring(CaseNo, 1, 10))[1])][Court='CR3']">
  		<xsl:sort select="Time,Defendant" order="ascending"/>

Open in new window

Gertone (Geert Bormans)Information Architect
Top Expert 2006

Commented:
@sweetfa2,

I appreciate that you have a different style and share your personal preference,
but your alternative is fundamentally wrong.

First let me say that in my point of view you have two types of discriminators:
- the technical ones (from muenchian) are only there to make sure you select unique nodes
- the business ones (value of Court)
Seperating both technical discriminators from business discriminators in my opninion gives better maintenance
But I accept this is a matter of taste and you posed it as a personal reference, so no problem with that

Now, you are selecting the first unique node matching the 'case' key and after that you add the court restriction.
That works in this example. But you seem to pose it as a general approach.
What would happen if a case changes court after the first hearing
I have altered the XML for this thought experiment

    <Case>
        <Time>09:00 AM</Time>
        <Judge>EAM</Judge>
        <Court>CR4</Court>
        <CaseNo>CR04003107-01</CaseNo>
        <Defendant>BLOUGH, JOE</Defendant>
    </Case>
    <Case>
        <Time>09:00 AM</Time>
        <Judge>EAM</Judge>
        <Court>CR3</Court>
        <CaseNo>CR04003107-02</CaseNo>
        <Defendant>BLOUGH, JOE</Defendant>
    </Case>

You would simply miss this case given your approach.
So there is a fundamental error in there
If you would alter my code to fit your prefered style,
you should do this
            <xsl:apply-templates select="Case[generate-id() = generate-id(key('case', substring(CaseNo, 1, 10))[Court = 'CR3'][1])]"/>



Gertone (Geert Bormans)Information Architect
Top Expert 2006

Commented:
Then, sweetfa2s suggestion for sort is wrong

<xsl:sort select="Time,Defendant" order="ascending"/>
is an illegal XPath expression

Here is what you should do
               <xsl:sort select ="Time" order="ascending"/>
                <xsl:sort select="Defendant" order="ascending"/>
will first order on Time (ascending, text data type)
and then on defendant (ascending text data type)

If you want to sort on Defendant first, you can simply swap
                 <xsl:sort select="Defendant" order="ascending"/>
              <xsl:sort select ="Time" order="ascending"/>

This sorting can be used with the for-each approach sweetfa uses,
but it can also be used with my apply-templates approach
(for maintenance reasons I do prefer apply-templates over for-each)

            <xsl:apply-templates select="Case[generate-id() = generate-id(key('case', substring(CaseNo, 1, 10))[1])]">
                <xsl:sort select="Time" order="ascending"/>
                <xsl:sort select="Defendant" order="ascending"/>
            </xsl:apply-templates>
Gertone (Geert Bormans)Information Architect
Top Expert 2006

Commented:
As a final comment

In most XSLT1 processors (there is not much internal optimisation going on)
indexing with a predicate in the key has better performance than being selective later
So, on large datasets there could be a performance benefit in my approach

I also see a lot of d-o-e (disable-output-escape) in sweetfa's code.
It serves nothing and it is generally understood that d-o-e should preferably not be used
              <xsl:text disable-output-escaping="yes">    </xsl:text>
in the spaces there is nothing to escape, so why add it there?
d-o-e is not a mandatory aspect of the XSLT1 recommendation, which means that a processor can still be compliant without supporting it.
The Firefox transformer for example does not support it, and still is considered a compliant XSLT processor.
So use it sparsely and only if you control the processor (don't use it in browser XSLT)

I see a lot of XSLT programmers use d-o-e by default on every xsl:text (if they do that they should also use it on every xsl:value-of to be consistent)
The main reason is that some programs override the XSLTs serialisation settings
but this should be changed in teh java/VB/C# code that is calling the XSLT, not by throwing d-o-e allover the stylesheet