<?xml-stylesheet href="http://www.w3.org/StyleSheets/base.css" type="text/css"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns='http://www.w3.org/1999/xhtml'
  xmlns:html='http://www.w3.org/1999/xhtml'
  xmlns:filter='http://www.w3.org/2005/07/filter-utils'
  exclude-result-prefixes="filter html"
  version="1.0">

<!-- Mini constraint language parser. The constraint language:

       constraints  ::= constraint [<sp> constraint <sp> ...]
       constraint   ::= prefix <colon> option [<bar> option <bar> ...]
       option       ::= string1 [<hypen> string2 <hyphen> ... stringN]

                        where string1 is a subclass of string2 and
                        and so on to some root class stringN

     Not implemented: "not"
     Ian Jacobs <ij@w3.org>

--> 

<!--
     candidate is of type "constraints" where each constraint has:
           - a different prefix
           - one option per prefix
           - N types per option

     reference is of type "constraints" in the general form.

     Returns true if all constraints in reference are consistent
     with the constraints in candidate, where:

             - only constraints with same prefix are compared.

               Example of how different constraints would be compared:

         candidate          reference      will be compared
         ==================================================
        (pre1:a pre2:z)   pre1:a|b|c  pre1 strings
        (pre1:a pre2:z)   pre2:a|b|c  pre2 strings
        (pre1:a pre2:z)  (pre1:z pre2:a|b|c)  pre1, pre2 strings
        (pre1:a pre2:z)   pre3:a|b|c  none


             - given a list of options, at least one matches.

             - an option in candidate matches an option in
               reference if either the candidate string matches
               the entire reference or any superclass like these
               examples:

                   candidate          reference        match
                   =========================================
                   a-b-c              a-b-c             y
                   a-b-c                b-c             y
                   a-b-c                  c             y
                   a-b-c                                y
                   a-b-c              z-b-c             n
                   z-b-c              a-b-c             n
                   b-c                a-b-c             n
                          
-->     

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <link rel="stylesheet" href="http://www.w3.org/StyleSheets/base"/>
    <title>General Purpose Markup Filter</title>
    <style type="text/css">
      template, variable, param, tests { display: none }
    </style>
  </head>
  <body>
    <div class='head'><a href="/"><img src="/Icons/w3c_home" alt="W3C"/></a> </div>
    <h1>General Purpose Markup Filter</h1>

    <p>Given marked up xml and a filter, this style sheet will produce
       result xml that matches the filter. The filter is called the
       "candidate" and the marked up xml the "reference" below.</p>
  
    <p>The filter language lets you express "and" (among classes)
       "or" (among options within a given class), and includes
       a rudimentary subclassing mechanism (available for a given
       option). The filters are strings with reserved character such
       as space, "|" and "-".</p>


    <p>A filter applies <em>unless</em> it contains the string "false".</p>
       
    <p>It comes with a Unit test, to allow easier debugging; the test is obtained by running the style sheet against any XML, and the output is given through <code>xsl:message</code>.</p>

    <p class="copyright">Copyright &#169; 2005 <a href="http://www.w3.org/">World Wide Web Consortium</a>, (<a
href="http://www.csail.mit.edu/"><acronym title="Massachusetts Institute of
Technology">M.I.T.</acronym></a>, <a
href="http://www.ercim.org/"><acronym
title="European Research Consortium for Informatics and Mathematics">ERCIM</acronym></a>, <a
href="http://www.keio.ac.jp/">Keio University</a>). All Rights
    Reserved. http://www.w3.org/Consortium/Legal/. W3C <a href="http://www.w3.org/Consortium/Legal/copyright-software">software licensing</a> rules apply.</p>
    <address><a href="http://www.w3.org/People/Jacobs/">Ian Jacobs</a> - $Id: 13-filter-utils.xsl,v 1.41 2007/11/12 17:22:10 ijacobs Exp $</address>
    </body>
</html>

 <tests xmlns="http://www.w3.org/2005/07/filter-utils">
  <test>
    <candidate>p:a-b-c</candidate>
    <reference>p:a-b-c</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>p:a-b-c</candidate>
    <reference>p:b-c</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>p:a-b-c</candidate>
    <reference>p:c</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>p:a-b-c</candidate>
    <reference></reference>
    <result>true</result>
  </test>
  <test>
    <candidate>p:a-b-c</candidate>
    <reference>p:z-b-c</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>p:a-b-c</candidate>
    <reference>p:a-z-c</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>p:a-b-c</candidate>
    <reference>p:a-b-z</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>p:z-b-c</candidate>
    <reference>p:a-b-c</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>p:a</candidate>
    <reference>p:a-b-c</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>p:b-c</candidate>
    <reference>p:a-b-c</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>pre1:a pre2:b</candidate>
    <reference>pre1:a pre2:b</reference>
    <result>truetrue</result>
  </test>
  <test>
    <candidate>pre1:a pre2:b</candidate>
    <reference>pre1:a</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>pre1:a pre2:b</candidate>
    <reference>pre1:b</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>pre1:a pre2:b</candidate>
    <reference>pre2:b</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>pre1:a pre2:b</candidate>
    <reference>pre2:c</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>pre1:a pre2:b</candidate>
    <reference></reference>
    <result>true</result>
  </test>
  <test>
    <candidate>pre1:a pre2:b</candidate>
    <reference>pre3:c</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>pre1:a pre2:b</candidate>
    <reference>pre3:c pre4:d</reference>
    <result>falsefalse</result>
  </test>
  <test>
    <candidate>pre1:a</candidate>
    <reference>pre1:a|b</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>pre1:a</candidate>
    <reference>pre1:a|b pre2:z</reference>
    <result>truefalse</result>
  </test>
  <test>
    <candidate>pre1:b</candidate>
    <reference>pre1:a|b</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>pre1:c</candidate>
    <reference>pre1:a|b</reference>
    <result>false</result>
  </test>
  <test>
    <candidate>pre1:c</candidate>
    <reference>pre1:a|b|c</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>pre1:a-b</candidate>
    <reference>pre1:a-b|c</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>pre1:a</candidate>
    <reference></reference>
    <result>true</result>
  </test>
  <test>
    <candidate>doc:pr-tr</candidate>
    <reference>doc:tr|per-tr</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>doc:per-tr</candidate>
    <reference>doc:tr|per-tr</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>doc:per-tr</candidate>
    <reference>doc:tr|per-tr|test1|test2</reference>
    <result>true</result>
  </test>
  <test>
    <candidate>doc:a-b-c</candidate>
    <reference>doc:tr|per-tr|test1|test2</reference>
    <result>false</result>
  </test>
 </tests>

<!-- 
     Unit testing based on DHM's example; see
     http://www.w3.org/2004/12/makeRelativeUri.xsl
-->

<!-- For the Unit Test -->
<xsl:template match="/" mode="unittest">
  <xsl:message>Starting</xsl:message>
  <xsl:for-each select="document('')/xsl:stylesheet/filter:tests/filter:test">
      <xsl:message>Running test # <xsl:value-of select="position()"/> with candidate=<xsl:value-of select="filter:candidate"/> and reference=<xsl:value-of select="filter:reference"/>.</xsl:message>

    <xsl:variable name="output">
      <xsl:call-template name="filter:eval-references">
        <xsl:with-param name="candidate" select="filter:candidate"/>
        <xsl:with-param name="reference" select="filter:reference"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="not($output=filter:result)">

        <xsl:message>-&gt; failed: got <xsl:value-of select="$output"/>, expected <xsl:value-of select="filter:result"/></xsl:message>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message>success!</xsl:message>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each>
</xsl:template>


<xsl:template name="filter:eval-references">
  <xsl:param name="candidate"/>
  <xsl:param name="reference"/>
  <xsl:choose>
    <xsl:when test="$reference=''">true</xsl:when>
    <xsl:otherwise>
      <xsl:variable name="head" select="substring-before($reference,' ')"/>
      <!-- See if any pattern in the candidate has the same prefix as
           the prefix of each piece of reference; if so, evaluate the
           two together -->
      <xsl:choose>
	<xsl:when test="$head=''">
	  <xsl:call-template name="filter:eval-reference">
	    <xsl:with-param name="candidate" select="$candidate"/>
	    <xsl:with-param name="reference" select="$reference"/>
	  </xsl:call-template>
	</xsl:when>
	<xsl:otherwise>
	  <xsl:call-template name="filter:eval-reference">
	    <xsl:with-param name="candidate" select="$candidate"/>
	    <xsl:with-param name="reference" select="$head"/>
	  </xsl:call-template>
	  <xsl:call-template name="filter:eval-references">
	    <xsl:with-param name="candidate" select="$candidate"/>
	    <xsl:with-param name="reference" select="substring-after($reference,' ')"/>
	  </xsl:call-template>
	</xsl:otherwise>
      </xsl:choose>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="filter:eval-reference">
  <xsl:param name="candidate"/>
  <xsl:param name="reference"/>
  <xsl:variable name="prefix" select="concat(substring-before($reference,':'),':')"/>
  <xsl:variable name="res">
    <xsl:call-template name="filter:eval-classes">
      <xsl:with-param name="candidate" select="$candidate"/>
      <xsl:with-param name="reference" select="$reference"/>
      <xsl:with-param name="prefix" select="$prefix"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:choose>
    <!-- if $res='' that means that a prefix that appears
         in the ref does not appear in the candidate -->
    <xsl:when test="$res=''">false</xsl:when>
    <xsl:otherwise><xsl:value-of select="$res"/></xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="filter:eval-classes">
  <xsl:param name="candidate"/>
  <xsl:param name="reference"/>
  <xsl:param name="prefix"/>
  <xsl:if test="$candidate">
    <xsl:variable name="head" select="substring-before($candidate,' ')"/>
    <xsl:choose>
      <xsl:when test="$head=''">
	<xsl:call-template name="filter:eval-class">
	  <xsl:with-param name="candidate" select="$candidate"/>
	  <xsl:with-param name="reference" select="$reference"/>
	  <xsl:with-param name="prefix" select="$prefix"/>
	</xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
	<xsl:call-template name="filter:eval-class">
	  <xsl:with-param name="candidate" select="$head"/>
	  <xsl:with-param name="reference" select="$reference"/>
	  <xsl:with-param name="prefix" select="$prefix"/>
	</xsl:call-template>
	<xsl:call-template name="filter:eval-classes">
	  <xsl:with-param name="candidate" select="substring-after($candidate,' ')"/>
	  <xsl:with-param name="reference" select="$reference"/>
	  <xsl:with-param name="prefix" select="$prefix"/>
	</xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
</xsl:template>

<xsl:template name="filter:eval-class">
  <xsl:param name="candidate"/>
  <xsl:param name="reference"/>
  <xsl:param name="prefix"/>
  <xsl:if test="contains($candidate,$prefix)">
    <xsl:call-template name="filter:eval-options">
      <xsl:with-param name="candidate" select="substring-after($candidate,':')"/>
      <xsl:with-param name="reference" select="substring-after($reference,':')"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>

<xsl:template name="filter:eval-options">
  <xsl:param name="candidate"/>
  <xsl:param name="reference"/>
  <xsl:choose>
    <xsl:when test="$reference=''">false</xsl:when>
    <xsl:otherwise>
      <xsl:variable name="head" select="substring-before($reference,'|')"/>
      <xsl:choose>
	<xsl:when test="$head=''">
	  <xsl:call-template name="filter:eval-types">
	    <xsl:with-param name="candidate" select="$candidate"/>
	    <xsl:with-param name="reference" select="$reference"/>
	  </xsl:call-template>
	</xsl:when>
	<xsl:otherwise>
	  <xsl:variable name="headresult">
	    <xsl:call-template name="filter:eval-types">
	      <xsl:with-param name="candidate" select="$candidate"/>
	      <xsl:with-param name="reference" select="$head"/>
	    </xsl:call-template>
	  </xsl:variable>
	  <xsl:choose>
	    <xsl:when test="not(contains($headresult,'false'))">true</xsl:when>
	    <xsl:otherwise>
	      <xsl:call-template name="filter:eval-options">
		<xsl:with-param name="candidate" select="$candidate"/>
		<xsl:with-param name="reference" select="substring-after($reference,'|')"/>
	      </xsl:call-template>
	    </xsl:otherwise>
	  </xsl:choose>
	</xsl:otherwise>
      </xsl:choose>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="filter:eval-types">
  <xsl:param name="candidate"/>
  <xsl:param name="reference"/>
  <xsl:choose>
    <xsl:when test="$candidate=''">false</xsl:when>
    <xsl:otherwise>
      <xsl:choose>
	<xsl:when test="$reference=$candidate">true</xsl:when>
	<xsl:otherwise>
	  <xsl:variable name="head" select="substring-after($candidate,'-')"/>
	  <xsl:choose>
	    <xsl:when test="$head=''">false</xsl:when>
	    <xsl:otherwise>
	      <xsl:call-template name="filter:eval-types">
		<xsl:with-param name="candidate" select="$head"/>
		<xsl:with-param name="reference" select="$reference"/>
	      </xsl:call-template>
	    </xsl:otherwise>
	  </xsl:choose>
	</xsl:otherwise>
      </xsl:choose>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>
