User:Stevage/tagsupport/code

From OpenStreetMap Wiki
Jump to navigation Jump to search
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
This XSLT 2.0 stylesheet produces a table of OSM feature support in (currently) Mapnik and Osmarender, by analysing their XML files
and looking for expressions like "natural in (water, wood)", which it interprets as support for natural=water and natural=wood.

It's not foolproof, and there is probably more information in there that it's missing, but it produces a good enough table to be
interesting and perhaps useful.

Author: Steve Bennett (wiki.openstreetmap.org/wiki/User:Stevage)
Date: December 2009
Licence: WTFPL.

Usage:
Needs to be run from a directory structure with these files available as follows:

/rendering_mapnik/osm.xml/
/osmarender/osm-map-features-z17.xml

These are available from the OpenStreetMaps Subversion repository: http://svn.openstreetmap.org/applications/

How it works:
1) Parse Mapnik:
1a) Add info from filters
1b) Add some hard coded stuff from osm2pgsql
1c) Add some rules that use direct sql queries instead of filters
2) Parse Osmarender: too easy.
3) Combine Mapnik and Osmarender information, aggregating it into a results tree.
4) Turn results tree into a pretty table. Within the limitations of my CSS ability. :)
-->
<xsl:variable name="doublequote" select='""""' /> <!-- Only way I could figure out to solve one escaping problem. I think I wasn't trying hard enough. -->

<!-- Matches Mapnik osm.xml -->
<xsl:template match="Map">
<tags>
        <xsl:for-each select="Style/Rule/Filter/..">
            <!-- convert "[natural] = 'peak'" to "natural=peak" -->
            <xsl:analyze-string select="Filter" regex="\[([-a-zA-Z0-9_]+)\]\s*<>\s*''" >
	      <xsl:matching-substring >
	          <tag><xsl:attribute name="key"><xsl:value-of select="regex-group(1)" /></xsl:attribute>
                      <value><xsl:attribute name="source">mapnik</xsl:attribute>*</value>
                      <xsl:value-of select="'(',regex-group(1),')'" />
	          </tag>
	      </xsl:matching-substring >
            </xsl:analyze-string>

            <xsl:analyze-string select="Filter" regex="\[([-a-zA-Z0-9_]+)\]\s*(<>|=)\s*'([^']*)'" >
	      <xsl:matching-substring >
	        <tag><xsl:attribute name="key"><xsl:value-of select="regex-group(1)" /></xsl:attribute>
                  <value><xsl:attribute name="source">mapnik</xsl:attribute><xsl:value-of select="regex-group(3)" /></value>
                  <xsl:value-of select="'(',regex-group(1),regex-group(3),')'" />
       	        </tag>
	      </xsl:matching-substring >
            </xsl:analyze-string>


            <xsl:analyze-string select="Filter" regex="\[([-a-zA-Z0-9_]+)\]\s*=\s*([0-9+]+)" >
	      <xsl:matching-substring >
	        <tag><xsl:attribute name="key"><xsl:value-of select="regex-group(1)" /></xsl:attribute>
                  <value><xsl:attribute name="source">mapnik</xsl:attribute><xsl:value-of select="regex-group(2)" /></value>
                  <xsl:value-of select="'(',regex-group(1),regex-group(2),')'" />
       	        </tag>
	      </xsl:matching-substring >
            </xsl:analyze-string> >

            <xsl:analyze-string select="Filter" regex="\[([-a-zA-Z0-9_]+)\]\s*<?>?=?<?>?\s*([0-9+]+)" >
	      <xsl:matching-substring >
	        <tag><xsl:attribute name="key"><xsl:value-of select="regex-group(1)" /></xsl:attribute>
                  <value><xsl:attribute name="source">mapnik</xsl:attribute>
                    <xsl:value-of select="regex-group(2)" />
                  </value>
                  <xsl:value-of select="'(',regex-group(1),regex-group(2),')'" />
       	        </tag>
	      </xsl:matching-substring >
            </xsl:analyze-string> >


        </xsl:for-each>

        <!-- Throw another pile of tags on the heap - no values, just keys. -->
        <!-- this dodgy list comes from osm2pgsql's default.style. Should probably parse it "live". Structure is messy, but does have node/area/way info. -->
        <xsl:for-each select="tokenize('source,note,access,addr:flats,addr:housenumber,addr:interpolation,admin_level,aerialway,aeroway,amenity,area,barrier,bicycle,bridge,boundary,building,capital,construction,cutting,disused,ele,embankment,foot,highway,historic,horse,junction,landuse,layer,learning,leisure,lock,man_made,military,motorcar,name,natural,oneway,operator,poi,power,power_source,place,railway,ref,religion,residence,route,service,shop,sport,tourism,tracktype,tunnel,waterway,width,wood,z_order,way_area',',')">
          <tag><xsl:attribute name="key"><xsl:value-of select="." /></xsl:attribute>
            <value><xsl:attribute name="source">mapnik</xsl:attribute>*</value>
          </tag>
        </xsl:for-each>

<!-- now get the rest of osm.xml that doesn't use filters... -->
  <xsl:for-each select="//Layer/Datasource/Parameter[matches(.,'select')]" >
    <!-- Find "where" clause -->
    <xsl:analyze-string select="normalize-space(replace(.,$doublequote,''''))" regex="^.* from ([^ ]+).* where(.*) as." flags="s">
      <xsl:matching-substring>
        <!-- analyze "where" clause, look for "in"s: bridge is not in (yes,no). -->
        <xsl:analyze-string select="regex-group(2)" regex="'?([a-z0-9_-]+)'?\s(not\s)?in\s\(([^\)]+)\)" flags="s">
          <xsl:matching-substring>
            <!-- split up "in" expression into pieces -->
            <xsl:for-each select="tokenize(regex-group(3), ',')">
              <tag> <xsl:attribute name="key"><xsl:value-of select="regex-group(1)"/></xsl:attribute>
                <value>
                  <xsl:attribute name="source">mapnik</xsl:attribute>
                  <xsl:value-of select="replace(current(),'''','')" />
                </value>
              </tag>
            </xsl:for-each>
          </xsl:matching-substring>
          <xsl:non-matching-substring />
        </xsl:analyze-string>

        <!-- analyze "where" clause again, look for "is (not) null" -->
        <xsl:analyze-string select="regex-group(2)" regex="'?([a-z0-9_-]+)'?\sis\s(not\s)?null" flags="s">
        <xsl:matching-substring>
          <tag>
          <xsl:attribute name="key"><xsl:value-of select="regex-group(1)"/></xsl:attribute>
          <value><xsl:attribute name="source">mapnik</xsl:attribute><xsl:value-of select="'*'" /></value>
          </tag>

        </xsl:matching-substring>
        <xsl:non-matching-substring />
        </xsl:analyze-string>

        <!-- analyze "where" clause again, look for "<> or =" -->
        <!-- waterway <> 'riverbank') -->
        <xsl:analyze-string select="regex-group(2)" regex="'?([a-z0-9_]+)'?\s?(=|<>)\s?'([^']*)'" flags="s">
        <xsl:matching-substring>
          <tag>
          <xsl:attribute name="key"><xsl:value-of select="regex-group(1)"/></xsl:attribute>
          <value><xsl:attribute name="source">mapnik</xsl:attribute><xsl:value-of select="regex-group(3)" /></value>
          </tag>

        </xsl:matching-substring>
        <xsl:non-matching-substring>
        </xsl:non-matching-substring>
        </xsl:analyze-string>
      </xsl:matching-substring>
      <xsl:non-matching-substring>
      </xsl:non-matching-substring>
    </xsl:analyze-string>
  </xsl:for-each>
</tags>
</xsl:template>

<!-- This trivial bit is all that's needed to pull out all the value/key pairs from osm-map-features-z17.xml for osmarender. -->
<xsl:template match="/rules">
  <tags>
  <xsl:for-each select="//rule[@k and @v]">
    <xsl:variable name="v" select="@v" />
    <xsl:for-each select="tokenize(@k, '\|')">
      <xsl:variable name="k" select="." />
      <xsl:for-each select="tokenize($v, '\|')">
        <tag><xsl:attribute name="key"><xsl:value-of select='$k' /></xsl:attribute>
          <value source="osmarender"><xsl:value-of select="." /></value>
        </tag>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:for-each>
  </tags>
</xsl:template>

<!-- It seems like Saxon (and maybe XSLT in general) always needs to be called on a source xml file,
even though all the work is being done on external files. So I use a pointless file with a pointless node in it. -->
<xsl:template match="pointless">

<!-- Harvest mapnik tags -->
<xsl:variable name="mapnik_tags" as="element(*)">
  <xsl:apply-templates select="document('rendering_mapnik/osm.xml')" />
</xsl:variable>

<!-- Harvest osmarender tags -->
<xsl:variable name="osma_tags" as="element(*)+">
  <xsl:apply-templates select="document('osmarender/osm-map-features-z17.xml')" />
</xsl:variable>

<!-- build combined tree -->
<xsl:variable name="combined_tags" as="element(*)+">
<tags combined="true">
<xsl:for-each-group select="$mapnik_tags/*|$osma_tags/*" group-by="@key">
  <xsl:sort select="@key"/>
  <tag><xsl:attribute name="key"><xsl:value-of select="current-grouping-key()" /></xsl:attribute>
    <xsl:for-each-group select="current-group()" group-by="value">
      <value>
      <!-- aggregate and attach source information -->
      <xsl:attribute name="source">
        <xsl:value-of select="distinct-values(current-group()/value/@source)" separator=", " />
      </xsl:attribute>
      <xsl:value-of select="current-grouping-key()" />
      </value>
    </xsl:for-each-group>
  </tag>
</xsl:for-each-group>
</tags>
</xsl:variable>

  <html>
  <body>



  <p>Total recognised keys: <xsl:value-of select="count($combined_tags/*)" /></p>
  <p>Total recognised key/value pairs: <xsl:value-of select="count($combined_tags//value)" /></p>
  <!-- Draw table -->
  <xsl:apply-templates select="$combined_tags" />

  </body>
  </html>
</xsl:template>

<xsl:template match="tags[@combined='true']">
<!-- Draws table of combined tag pool: values on left, tags on right. -->
  <table style="border-collapse:collapse;
		background:#EFF4FB;
		border-style:1px solid black;
		font:75% 'Trebuchet MS',helvetica,arial,verdana;
		color: #333;">
  <th style="padding:1px; border-bottom:1px dotted #9999ff;">
  <tr/>
  <td>Key</td>
  <td>Value</td>
  <td>Mapnik</td>
  <td>Osmarender</td>
  </th>
  <xsl:for-each select="tag">
    <tr style="border-bottom: 5px solid black;" />
      <td style="font-size:large; color: #4488ff; text-color: red; text-align:right;">
      <xsl:attribute name="rowspan"><xsl:value-of select="count(value)+1"/></xsl:attribute>
      <xsl:value-of select="@key" /></td>
      <xsl:for-each select="value">
      <xsl:sort select="."/>
              <tr><xsl:attribute name="style"><xsl:value-of select="if(position()=last()) then 'border-bottom: 5px solid black;' else ''"/></xsl:attribute>
              <td style="padding:1px; border-bottom:1px dotted #9999ff; border-right:1px solid #6699ff;"><xsl:value-of select="." /></td>
              <td style="padding:1px; border-bottom:1px dotted #9999ff;"><xsl:value-of select="if (matches(@source,'mapnik')) then 'yes' else ''"/></td>
              <td style="padding:1px; border-bottom:1px dotted #9999ff;"><xsl:value-of select="if (matches(@source,'osmarender')) then 'yes' else ''"/></td>
              </tr>
      </xsl:for-each>

  </xsl:for-each>
  </table>
</xsl:template>

</xsl:stylesheet>