Search code examples
xsltxslt-1.0xslt-grouping

Groups two xml files like a sql group-by [2]


This is an evolution of my ask here : Groups two xml files like a sql group-by The Example given and Dimitre Solution was counting distinct isbn value. Now modify library xml to have mylibrary.xml :

<library>  
   <book id="1" isbn="1"/>
   <book id="2" isbn="1"/>
   <book id="3" isbn="2"/>
   <book id="4" isbn="4"/>
   <book id="5" isbn="5"/>
   <book id="6" isbn="4"/>
   <book id="7" isbn="4"/>   
</library>  

and this one that can be used : bookreference.xml :

<reference>  
    <book isbn="1">  
        <category>SF</category>  
    </book>  
    <book isbn="2">  
        <category>SF</category>  
    </book>  
    <book isbn="3">  
        <category>SF</category>  
    </book>  
    <book isbn="4">  
        <category>Comedy</category>  
    </book>  
    <book isbn="5">  
        <category>Comedy</category>  
    </book>
</reference>  

i want to get the numbers of book i got in mylibrary 'even if some have same isbn', groupby category, using xslt 1-0.

output wanted:

SF : 3 book(s) 
Comedy : 4 book(s) 

my xslt propose here : Groups two xml files like a sql group-by works fine but of course use 'for-each' loop and extension functions. Surely there is a better solution.


Solution

  • Again a very good question! (+1)

    This transformation, using two keys for achieving full efficiency:

    <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:key name="kBookByCat" match="book"
              use="category"/>
    
         <xsl:key name="kBookByIsbn" match="book"
              use="@isbn"/>
    
         <xsl:variable name="vDoc" select="/"/>
    
         <xsl:variable name="vRef" select=
         "document('file:///c:/temp/delete/reference.xml')"/>
    
         <xsl:variable name="vMyIsbns" select="/*/*/@isbn"/>
    
         <xsl:variable name="vResult">
          <xsl:apply-templates select="$vRef/*"/>
         </xsl:variable>
    
         <xsl:template match="/">
          <xsl:copy-of select="$vResult"/>
         </xsl:template>
    
         <xsl:template match=
          "book[generate-id()
               =
                generate-id(key('kBookByCat', category)[1])
                ]
          ">
             <xsl:variable name="vBooksinCat" select=
                  "key('kBookByCat', category)"/>
    
             <xsl:value-of select="category"/> : <xsl:text/>
             <xsl:for-each select="$vDoc">
               <xsl:value-of select="count(key('kBookByIsbn',$vBooksinCat/@isbn))"/>
             </xsl:for-each>
             <xsl:text> book(s)&#xA;</xsl:text>
         </xsl:template>
         <xsl:template match="text()"/>
    </xsl:stylesheet>
    

    when applied on the provided XML document contained in the file mylibrary.xml:

    <library>
       <book id="1" isbn="1"/>
       <book id="2" isbn="1"/>
       <book id="3" isbn="2"/>
       <book id="4" isbn="4"/>
       <book id="5" isbn="5"/>
       <book id="6" isbn="4"/>
       <book id="7" isbn="4"/>
    </library>
    

    and having this provided XML document in C:\temp\delete\reference.xml:

    <reference>
        <book isbn="1">
            <category>SF</category>
        </book>
        <book isbn="2">
            <category>SF</category>
        </book>
        <book isbn="3">
            <category>SF</category>
        </book>
        <book isbn="4">
            <category>Comedy</category>
        </book>
        <book isbn="5">
            <category>Comedy</category>
        </book>
    </reference>
    

    produces the wanted, correct output:

    SF : 3 book(s)
    Comedy : 4 book(s)