Link to home
Start Free TrialLog in
Avatar of GJDuquette
GJDuquette

asked on

XSL to Select Unique and Sort

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

Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

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

<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

ASKER CERTIFIED SOLUTION
Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of GJDuquette
GJDuquette

ASKER

Very nice! How can we add sorting by time and defendant?
  	<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

@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])]"/>



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>
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