Search

An XSLT pipeline approach to OpenlyLocal committee analysis

Having fixed a problem in the eXist  handling of XSLT with an upgrade, I re-wrote the XQuery scripts to create the  analysis of a council's committees by party using a small XSLT pipeline.The analysis for Bristol City Council is computed with this pipeline, viewable in a simple pipeline editor. In the editor, you can view the XSLT for each step in the editor, and execute the pipeline step by step. 

There are two XSLT transformations used. The first XSLT script creates an extract from the whole openlylocal site, a view of the total database suitable for the production of the report. It fetches the XML for the given committee, outputs some basic data and the members and their parties, and then for each committee, fetches the committee XML file and extracts the members of that committee.



<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:param name="id"/>
    <xsl:variable name="councilurl" select="concat(&#34;http://openlylocal.com/councils/&#34;,$id,&#34;.xml&#34;)"/>
    <xsl:variable name="council" select="doc($councilurl)/council"/>
    <xsl:template match="/">
        <council>
            <xsl:copy-of select="$council/name"/>
            <xsl:copy-of select="$council/id"/>
            <xsl:apply-templates select="$council/members/member"/>
            <xsl:apply-templates select="$council/committees/committee"/>
        </council>
    </xsl:template>
    <xsl:template match="member">
        <member>
            <name>
                <xsl:value-of select="concat(first-name,' ',last-name)"/>
            </name>
            <xsl:copy-of select="party"/>
        </member>
    </xsl:template>
    <xsl:template match="committee">
        <committee>
            <xsl:copy-of select="title"/>
            <xsl:copy-of select="id"/>
            <xsl:variable name="id" select="id"/>
            <xsl:variable name="url" select="concat('http://openlylocal.com/committees/',$id,'.xml')"/>
            <xsl:variable name="committee" select="doc($url)/committee"/>
            <xsl:for-each select="$committee/members/member">
                <member>
                    <name>
                        <xsl:value-of select="concat(first-name,' ',last-name)"/>
                    </name>
                    <xsl:copy-of select="party"/>
                </member>
            </xsl:for-each>
        </committee>
    </xsl:template>
</xsl:stylesheet>


The second XSLT script transforms the XML view into HTML. It first creates a list of the parties, in order of descending number of members with its assigned colour.  This list is then used to create the barcharts for the committees so that the parties are in a consistant order.



<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xhtml" media-type="text/html"/>
    <xsl:variable name="council" select="/council"/>
    <xsl:variable name="partyColors">
        <colors>
            <color party="Conservative" color="blue"/>
            <color party="Labour" color="red"/>
            <color party="Liberal Democrat" color="orange"/>
            <color party="Green" color="green"/>
            <color party="Independent" color="purple"/>
        </colors>
    </xsl:variable>
    
    <xsl:variable name="randomColors" select="(&#34;#ABC&#34;,&#34;#C96&#34;,&#34;#E85&#34;,&#34;#67A&#34;, &#34;#56A&#34;, &#34;#75c&#34;, &#34;#462&#34;,&#34;#63b&#34;)"/>
    
    <xsl:variable name="parties">
        <parties>
            <xsl:for-each select="distinct-values($council/member/party)">             
                <xsl:sort select="count($council/member[party=current()])" order="descending"></xsl:sort>
                <xsl:variable name="party" select="."/>
                <xsl:variable name="i" select="position()"/>
                <xsl:variable name="count" select="count($council/member[party=$party])"/>
                <xsl:variable name="color" 
                     select="($partyColors/colors/color[@party=$party]/@color,
                              $randomColors[$i]
                              )[1]"
                />
                <party>
                    <xsl:attribute name="count" select="$count"/>
                    <xsl:attribute name="name" select="$party"/>
                    <xsl:attribute name="color" select="$color"/>
                </party>
            </xsl:for-each>
        </parties>
    </xsl:variable>
    
    <xsl:variable name="scale" select="round(800 div $parties/parties/party[1]/@count)"/>
    
    <xsl:template match="/council">
        <html>
            <head>
                <title>
                    <xsl:value-of select="name"/>: Committee Structure</title>
            </head>
            <body>
                <h1>
                    
                         
                    
                    : Committee Structure</h1>
                <table border="1" class="sortable">
                    <tr>
                        <th>Committee</th>
                        <th>Total</th>
                        <th>Party Breakdown</th>
                    </tr>
                    <xsl:apply-templates select="committee">
                        <xsl:sort select="count(member)" order="descending"/>
                    </xsl:apply-templates>
                </table>
            </body>
        </html>
    </xsl:template>
    
    <xsl:template match="committee[count(member) &gt; 0 ]">
        <xsl:variable name="committee" select="."/>
        <xsl:variable name="total" select="count(member)"/>
        <tr>
            <th align="right" width="20%">
                
                    
                
            </th>
            <td align="right">
                <xsl:value-of select="$total"/>
            </td>
            <td>
                <table>
                    <xsl:for-each select="$parties/parties/party">
                        <xsl:variable name="party" select="."/>
                        <xsl:variable name="count" 
                            select="count($committee/member[party = $party/@name])"/>
                        <xsl:variable name="width" select="$count * $scale"/>
                        <xsl:if test="$count &gt; 0">
                            <tr>
                                <td align="right">
                                    <xsl:value-of select="$count"/>
                                </td>
                                <td>
                                    <div>
                                        <xsl:attribute name="title" select="$party/@name"/>
                                        <xsl:attribute name="style"
                                            select="concat('background-color:',$party/@color,'; width:', $width,'px')"/>
                                          &#160;
                                       </div>
                                </td>
                            </tr>
                        </xsl:if>
                    </xsl:for-each>
                </table>
            </td>
        </tr>
    </xsl:template>
    <xsl:template match="*"/>
</xsl:stylesheet>


To create a simple index of the councils,  I wrote  another pipeline with just one XSTL script to transform the full council XML to a table.  The council id is embedded in the pipe URL to create the link to the first pipeline.



<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:template match="/councils">
        <html>
            <head>
                <title>Councils from OpenLocally</title>
                <script src="../jscripts/sorttable.js"></script>
            </head>
            <body>
            <h1>Councils from OpenLocally</h1>
            <table class="sortable">  
                <tr><th>Council</th><th>Country</th><th>Population</th><th>Links</th></tr>
                <xsl:apply-templates select="council"/>
            </table>
           </body>
        </html>
    </xsl:template>
    <xsl:template match="council">
        <tr>
            <td></td>
            <td><xsl:value-of select="country"/></td>
            <td><xsl:value-of select="population"/></td>
            <td>Committee Party Breakdown</td> 
        </tr>
    </xsl:template>
</xsl:stylesheet>


This all looks a bit like Meccano, with all the holes, nuts and bolts showing.  This is not surpising really, I'm a child of  Meccano. I guess for the same reason I'm also a fan of the school of rough animators like Tim Hinkin and the Cabaret Mechanical Theatre. Oh and Heath Robinson! 

Thinking more about a pipeline construction kit, I come back to the idea of working with a conceptual model of a site.  For OpenlyLocal with its (I now understand) Rails generated XML a  conceptual model of the relationships between councils, committees, members etc could be defined as an XML description. With this, the construction tool could translate virtual XPath expressions like committee/member/party into the cross-document accesses logically needed to compute this sequence.  Views of the site could then be expressed more conceptually, whilst the mechanics of translating that view into logical XPath expressions on the underlying XML documents can be handled by the platform, which could also look after document caching. The underlying documents do not need to be XML, they could be HTML with a suitable mapping of concepts into paths in the HTML code, or even RDF.  I started work on this idea last year, with a small demo in the Wikibook inspired by Daniel Bennett's ideas

If only I wasn't supposed to be writing an exam...