<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:test="http://www.jenitennison.com/xslt/unit-test"
                exclude-result-prefixes="xs test"
                xmlns:ev="http://www.lmnl.org/event">
  
<xsl:key name="namespaces" match="ns" use="@prefix" />
<xsl:key name="entities" match="entity" use="@name" />
  
<xsl:template match="/">
  <xsl:apply-templates select="." mode="ev:parse-lmnl" />
</xsl:template>
  
<xsl:template match="node() | /" mode="ev:parse-lmnl">
  <xsl:call-template name="ev:parse-lmnl">
    <xsl:with-param name="lmnl" select="string(.)" />
  </xsl:call-template>
</xsl:template>  
  
<test:tests>
  <test:title>Parsing LMNL</test:title>
  <!--
  <test:test>
    <test:title>Text</test:title>
    <test:param name="lmnl" select="string(/text())">test</test:param>
    <test:expect>
      <ev:events>
        <ev:text chars="test" ws="false" hash="text('false')" line="1" col="0" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Atom</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      {{test}}
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-atom name="test" ns="" hash="start-atom('','test')" line="1" col="0" />
        <ev:end-atom name="test" ns="" hash="end-atom('','test')" line="1" col="6" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Text and atom</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      ...{{test}}...
    </test:param>
    <test:expect>
      <ev:events>
        <ev:text chars="..." ws="false" hash="text('false')" line="1" col="0" />
        <ev:start-atom name="test" ns="" hash="start-atom('','test')" line="1" col="3" />
        <ev:end-atom name="test" ns="" hash="end-atom('','test')" line="1" col="9" />
        <ev:text chars="..." ws="false" hash="text('false')" line="1" col="11" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Empty range</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [test]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="test" ns="" id="" line="1" col="0" />
        <ev:end-start-tag name="test" ns="" id="" line="1" col="5" />
        <ev:end-tag name="test" ns="" id="" line="1" col="5" />
        <ev:end-end-tag name="test" ns="" id="" line="1" col="5" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Simple range</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [test}...{test]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="test" ns="" id="" line="1" col="0" />
        <ev:end-start-tag name="test" ns="" id="" line="1" col="5" />
        <ev:text chars="..." ws="false" line="1" col="6" />
        <ev:end-tag name="test" ns="" id="" line="1" col="9" />
        <ev:end-end-tag name="test" ns="" id="" line="1" col="14" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Simple range in text</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      ...[test}...{test]...
    </test:param>
    <test:expect>
      <ev:events>
        <ev:text chars="..." ws="false" line="1" col="0" />
        <ev:start-tag name="test" ns="" id="" line="1" col="3" />
        <ev:end-start-tag name="test" ns="" id="" line="1" col="8" />
        <ev:text chars="..." ws="false" line="1" col="9" />
        <ev:end-tag name="test" ns="" id="" line="1" col="12" />
        <ev:end-end-tag name="test" ns="" id="" line="1" col="17" />
        <ev:text chars="..." ws="false" line="1" col="18" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Non-overlapping ranges</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo}...{foo]...[bar}...{bar]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:end-start-tag name="foo" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:end-tag name="foo" ns="" id="" />
        <ev:end-end-tag name="foo" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:start-tag name="bar" ns="" id="" />
        <ev:end-start-tag name="bar" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:end-tag name="bar" ns="" id="" />
        <ev:end-end-tag name="bar" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Overlapping ranges</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo}...[bar}...{foo]...{bar]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:end-start-tag name="foo" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:start-tag name="bar" ns="" id="" />
        <ev:end-start-tag name="bar" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:end-tag name="foo" ns="" id="" />
        <ev:end-end-tag name="foo" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:end-tag name="bar" ns="" id="" />
        <ev:end-end-tag name="bar" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Identical ranges</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo}[bar}...{bar]{foo]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:end-start-tag name="foo" ns="" id="" />
        <ev:start-tag name="bar" ns="" id="" />
        <ev:end-start-tag name="bar" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:end-tag name="bar" ns="" id="" />
        <ev:end-end-tag name="bar" ns="" id="" />
        <ev:end-tag name="foo" ns="" id="" />
        <ev:end-end-tag name="foo" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Simple annotation</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo [bar]]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:start-annotation name="bar" ns="" />
        <ev:end-start-annotation name="bar" ns="" />
        <ev:end-annotation name="bar" ns="" />
        <ev:end-end-annotation name="bar" ns="" />
        <ev:end-start-tag name="foo" ns="" id="" />
        <ev:end-tag name="foo" ns="" id="" />
        <ev:end-end-tag name="foo" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Text annotation</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo [bar}...{bar]]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:start-annotation name="bar" ns="" />
        <ev:end-start-annotation name="bar" ns="" />
        <ev:text chars="..." ws="false" />
        <ev:end-annotation name="bar" ns="" />
        <ev:end-end-annotation name="bar" ns="" />
        <ev:end-start-tag name="foo" ns="" id="" />
        <ev:end-tag name="foo" ns="" id="" />
        <ev:end-end-tag name="foo" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Annotation with internal range</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo [bar}...[baz]...{bar]]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:start-annotation name="bar" ns="" />
        <ev:end-start-annotation name="bar" ns="" />
        <ev:text chars="..." ws="false" />
        <ev:start-tag name="baz" ns="" id="" />
        <ev:end-start-tag name="baz" ns="" id="" />
        <ev:end-tag name="baz" ns="" id="" />
        <ev:end-end-tag name="baz" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:end-annotation name="bar" ns="" />
        <ev:end-end-annotation name="bar" ns="" />
        <ev:end-start-tag name="foo" ns="" id="" />
        <ev:end-tag name="foo" ns="" id="" />
        <ev:end-end-tag name="foo" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Two annotations</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo [bar}...{bar] [baz}...{baz]]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:start-annotation name="bar" ns="" />
        <ev:end-start-annotation name="bar" ns="" />
        <ev:text chars="..." ws="false" />
        <ev:end-annotation name="bar" ns="" />
        <ev:end-end-annotation name="bar" ns="" />
        <ev:start-annotation name="baz" ns="" />
        <ev:end-start-annotation name="baz" ns="" />
        <ev:text chars="..." ws="false" />
        <ev:end-annotation name="baz" ns="" />
        <ev:end-end-annotation name="baz" ns="" />
        <ev:end-start-tag name="foo" ns="" id="" />
        <ev:end-tag name="foo" ns="" id="" />
        <ev:end-end-tag name="foo" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Overlapping annotations (error)</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo [bar}...[baz}...{bar]...{baz]]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:start-annotation name="bar" ns="" />
        <ev:end-start-annotation name="bar" ns="" />
        <ev:text chars="..." ws="false" />
        <ev:start-tag name="baz" ns="" id="" />
        <ev:end-start-tag name="baz" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:error parsing="end tag"
                  found="{bar]...{baz]]"
                  message="end tag doesn't match any open start tag"/>
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>Nested annotations</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo [bar [baz}...{baz]}...{bar]]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" />
        <ev:start-annotation name="bar" ns="" />
        <ev:start-annotation name="baz" ns="" />
        <ev:end-start-annotation name="baz" ns="" />
        <ev:text chars="..." ws="false" />
        <ev:end-annotation name="baz" ns="" />
        <ev:end-end-annotation name="baz" ns="" />
        <ev:end-start-annotation name="bar" ns="" />
        <ev:text chars="..." ws="false" />
        <ev:end-annotation name="bar" ns="" />
        <ev:end-end-annotation name="bar" ns="" />
        <ev:end-start-tag name="foo" ns="" id="" />
        <ev:end-tag name="foo" ns="" id="" />
        <ev:end-end-tag name="foo" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  <test:test>
    <test:title>l range</test:title>
    <test:param name="lmnl" select="normalize-space(.)">
      [l}...{l]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="l" ns="" id="" />
        <ev:end-start-tag name="l" ns="" id="" />
        <ev:text chars="..." ws="false" />
        <ev:end-tag name="l" ns="" id="" />
        <ev:end-end-tag name="l" ns="" id="" />
      </ev:events>
    </test:expect>
  </test:test>
  -->
  <test:test>
    <test:title>Simple annotation with shortened end-tag</test:title>
    <test:param name="lmnl" select="normalize-space(/text())">
      [foo [bar}...{]]
    </test:param>
    <test:expect>
      <ev:events>
        <ev:start-tag name="foo" ns="" id="" hash="start-tag('','foo','')" line="1" col="0" />
        <ev:start-annotation name="bar" ns="" hash="start-annotation('','bar')" line="1" col="5" />
        <ev:end-start-annotation name="bar" ns="" hash="end-start-annotation('','bar')" line="1" col="9" />
        <ev:text chars="..." ws="false" hash="text('false')" line="1" col="10" />
        <ev:end-annotation name="bar" ns="" hash="end-annotation('','bar')" line="1" col="13" />
        <ev:end-end-annotation name="bar" ns="" hash="end-end-annotation('','bar')" line="1" col="13" />
        <ev:end-start-tag name="foo" ns="" id="" hash="end-start-tag('','foo','')" line="1" col="15" />
        <ev:end-tag name="foo" ns="" id="" hash="end-tag('','foo','')" line="1" col="15" />
        <ev:end-end-tag name="foo" ns="" id="" hash="end-end-tag('','foo','')" line="1" col="15" />
      </ev:events>
    </test:expect>
  </test:test>
</test:tests>  
<xsl:template name="ev:parse-lmnl">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <ev:events>
    <xsl:call-template name="ev:parse-document">
      <xsl:with-param name="lmnl" select="$lmnl" />
      <xsl:with-param name="namespaces" tunnel="yes" as="document-node()">
        <xsl:document>
          <ev:namespaces />
        </xsl:document>
      </xsl:with-param>
      <xsl:with-param name="entities" tunnel="yes" as="document-node()">
        <xsl:document>
          <ev:entities />
        </xsl:document>
      </xsl:with-param>
      <xsl:with-param name="open-ranges" tunnel="yes" select="()" />
      <xsl:with-param name="open-documents" tunnel="yes" select="()" />
      <xsl:with-param name="line" tunnel="yes" select="1" />
      <xsl:with-param name="col" tunnel="yes" select="0" />
    </xsl:call-template>
  </ev:events>
</xsl:template>
  
<xsl:template name="ev:parse-document">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="open-ranges" as="element()*" tunnel="yes" required="yes" />
  <xsl:param name="open-documents" as="element(ev:document)*" tunnel="yes" required="yes" />
  <xsl:choose>
    <xsl:when test="$lmnl = ''">
      <xsl:choose>
        <xsl:when test="exists($open-ranges)">
          <xsl:call-template name="ev:error">
            <xsl:with-param name="message" 
              select="concat('end of document, but not all ranges are closed: ',
                             string-join($open-ranges/concat(@name, '=', @id), ', '))" />
          </xsl:call-template>
        </xsl:when>
        <xsl:when test="exists($open-documents)">
          <xsl:call-template name="ev:error">
            <xsl:with-param name="message" 
              select="concat('end of document, but not all documents are closed: ',
                             string-join($open-documents/@in, ', '))" />
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:message>Parse completed successfully</xsl:message>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:when test="starts-with($lmnl, '[!--')">
      <xsl:call-template name="ev:parse-comment">
        <xsl:with-param name="lmnl" select="$lmnl" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="starts-with($lmnl, '[')">
      <xsl:call-template name="ev:parse-start-tag">
        <xsl:with-param name="lmnl" select="$lmnl" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="starts-with($lmnl, '{{')">
      <xsl:call-template name="ev:parse-atom">
        <xsl:with-param name="lmnl" select="$lmnl" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="starts-with($lmnl, '{')">
      <xsl:call-template name="ev:parse-end-tag">
        <xsl:with-param name="lmnl" select="$lmnl" />
      </xsl:call-template>      
    </xsl:when>
    <xsl:when test="starts-with($lmnl, '&amp;')">
      <xsl:call-template name="ev:parse-entity">
        <xsl:with-param name="lmnl" select="$lmnl" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ev:parse-text">
        <xsl:with-param name="lmnl" select="$lmnl" />
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>  
  
<xsl:template name="ev:parse-entity">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="entities" as="document-node()" tunnel="yes" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:variable name="text-regex">^(&amp;(\i\c*);)(.+)$</xsl:variable>
  <xsl:analyze-string select="$lmnl" flags="s" regex="{$text-regex}">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string+" select="tokenize($parsed, '\n')" />
      <xsl:variable name="entity" as="xs:string" select="regex-group(2)" />
      <xsl:variable name="rest" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="text" as="xs:string" select="key('entities', $entity, $entities)" />
      <xsl:variable name="ws" as="xs:boolean" select="normalize-space($text) = ''" />
      <ev:text chars="{$text}" ws="{$ws}" hash="text('{$ws}')" line="{$line}" col="{$col}" />
      <xsl:call-template name="ev:parse-document">
        <xsl:with-param name="lmnl" select="$rest" />
        <xsl:with-param name="line" select="$line + count($lines) - 1" tunnel="yes" />
        <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
      </xsl:call-template>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="parsing" select="'entity'" />
        <xsl:with-param name="found" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>  
  
<xsl:template name="ev:parse-text">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:variable name="text-regex">^([^\[\{&amp;]+)(.*)$</xsl:variable>
  <xsl:analyze-string select="$lmnl" flags="s" regex="{$text-regex}">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string+" select="tokenize($parsed, '\n')" />
      <xsl:variable name="text" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="rest" as="xs:string" select="regex-group(2)" />
      <xsl:variable name="ws" as="xs:boolean" select="normalize-space($text) = ''" />
      <ev:text chars="{$text}" ws="{$ws}" hash="text('{$ws}')" line="{$line}" col="{$col}" />
      <xsl:call-template name="ev:parse-document">
        <xsl:with-param name="lmnl" select="$rest" />
        <xsl:with-param name="line" select="$line + count($lines) - 1" tunnel="yes" />
        <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
      </xsl:call-template>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="parsing" select="'text'" />
        <xsl:with-param name="found" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>
  
<xsl:template name="ev:parse-atom">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="open-documents" as="element(ev:document)*" tunnel="yes" required="yes" />
  <xsl:param name="open-ranges" as="element(ev:start-tag)*" tunnel="yes" required="yes" />
  <xsl:param name="namespaces" as="document-node()" tunnel="yes" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:variable name="start-tag-regex">^(\{\{(((\i\c*):)?(\i\c*)))(.+)$</xsl:variable>
  <xsl:analyze-string select="$lmnl" flags="s" regex="{$start-tag-regex}">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string+" select="tokenize($parsed, '\n')" />
      <xsl:variable name="prefix" as="xs:string" select="regex-group(4)" />
      <xsl:variable name="ns" as="xs:string" select="ev:ns($prefix, $namespaces)" />
      <xsl:variable name="name" as="xs:string" select="regex-group(5)" />
      <xsl:variable name="rest" as="xs:string" select="regex-group(6)" />
      <xsl:variable name="start-atom" as="element()">
        <ev:start-atom name="{$name}" ns="{$ns}" hash="start-atom('{$ns}','{$name}')" line="{$line}" col="{$col}" />
      </xsl:variable>
      <xsl:sequence select="$start-atom" />
      <xsl:call-template name="ev:parse-initial-metadata">
        <xsl:with-param name="lmnl" select="$rest" />
        <xsl:with-param name="open-ranges" select="()" tunnel="yes" />
        <xsl:with-param name="open-documents" as="element(ev:document)+" tunnel="yes">
          <ev:document in="atom">
            <xsl:sequence select="$start-atom, $open-ranges" />
          </ev:document>
          <xsl:sequence select="$open-documents" />
        </xsl:with-param>
        <xsl:with-param name="line" select="$line + count($lines) - 1" tunnel="yes" />
        <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
      </xsl:call-template>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="parsing" select="'atom'" />
        <xsl:with-param name="found" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>  
  
<xsl:template name="ev:parse-start-tag">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="namespaces" as="document-node()" tunnel="yes" required="yes" />
  <xsl:param name="open-ranges" as="element()*" tunnel="yes" required="yes" />
  <xsl:param name="open-documents" as="element(ev:document)*" tunnel="yes" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:variable name="start-tag-regex">^(\[(((\i\c*):)?(\i\c*))?(\s?=(\i\c*))?)(.+)$</xsl:variable>
  <xsl:analyze-string select="$lmnl" flags="s" regex="{$start-tag-regex}">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string+" select="tokenize($parsed, '\n')" />
      <xsl:variable name="prefix" as="xs:string" select="regex-group(4)" />
      <xsl:variable name="ns" as="xs:string" select="ev:ns($prefix, $namespaces)" />
      <xsl:variable name="name" as="xs:string" select="regex-group(5)" />
      <xsl:variable name="id" as="xs:string" select="regex-group(7)" />
      <xsl:variable name="rest" as="xs:string" select="regex-group(8)" />
      <xsl:variable name="tag" as="element(ev:start-tag)">
        <ev:start-tag name="{$name}" ns="{$ns}" id="{$id}" 
          hash="start-tag('{$ns}','{$name}','{$id}')"
          line="{$line}" col="{$col}"/>
      </xsl:variable>
      <xsl:sequence select="$tag" />
      <xsl:call-template name="ev:parse-initial-metadata">
        <xsl:with-param name="lmnl" select="$rest" />
        <xsl:with-param name="open-ranges" select="()" tunnel="yes" />
        <xsl:with-param name="open-documents" as="element(ev:document)+" tunnel="yes">
          <ev:document in="start-tag">
            <xsl:sequence select="($tag, $open-ranges)" />
          </ev:document>
          <xsl:sequence select="$open-documents" />
        </xsl:with-param>
        <xsl:with-param name="line" select="$line + count($lines) - 1" tunnel="yes" />
        <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
      </xsl:call-template>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="parsing" select="'start tag'" />
        <xsl:with-param name="found" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>
  
<xsl:template name="ev:parse-end-tag">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="namespaces" as="document-node()" tunnel="yes" required="yes" />
  <xsl:param name="open-ranges" as="element()*" tunnel="yes" required="yes" />
  <xsl:param name="open-documents" as="element(ev:document)*" tunnel="yes" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:variable name="end-tag-regex">^(\{(((\i\c*):)?(\i\c*))?(\s?=(\i\c*)\s?)?)(.+)$</xsl:variable>
  <xsl:analyze-string select="$lmnl" flags="s" regex="{$end-tag-regex}">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string+" select="tokenize($parsed, '\n')" />
      <xsl:variable name="prefix" as="xs:string" select="regex-group(4)" />
      <xsl:variable name="ns" as="xs:string" select="ev:ns($prefix, $namespaces)" />
      <xsl:variable name="name" as="xs:string" select="regex-group(5)" />
      <xsl:variable name="id" as="xs:string" select="regex-group(7)" />
      <xsl:variable name="rest" as="xs:string" select="regex-group(8)" />
      <xsl:choose>
        <xsl:when test="$open-ranges instance of element(ev:start-annotation)">
          <xsl:variable name="open-annotation" as="element(ev:start-annotation)" 
            select="$open-ranges" />
          <xsl:choose>
            <xsl:when test="starts-with($lmnl, '{]')">
              <ev:end-annotation name="{$open-annotation/@name}"
                                 ns="{$open-annotation/@ns}"
                                 hash="end-annotation('{$open-annotation/@ns}','{$open-annotation/@name}')"
                                 line="{$line}" col="{$col}"/>
              <ev:end-end-annotation name="{$open-annotation/@name}"
                                     ns="{$open-annotation/@ns}"
                                     hash="end-end-annotation('{$open-annotation/@ns}','{$open-annotation/@name}')"
                                     line="{$line}" col="{$col}"/>
              <xsl:call-template name="ev:parse-metadata">
                <xsl:with-param name="lmnl" select="substring($lmnl, 3)" />
                <xsl:with-param name="open-ranges" select="()" tunnel="yes" />
                <xsl:with-param name="col" select="$col + 2" tunnel="yes" />
              </xsl:call-template>
            </xsl:when>
            <xsl:when test="$open-annotation/@name eq $name and
                            $open-annotation/@ns eq $ns">
              <xsl:variable name="end-annotation" as="element()">
                <ev:end-annotation name="{$name}"
                                   ns="{$ns}"
                                   hash="end-annotation('{$ns}','{$name}')"
                                   line="{$line}" col="{$col}"/>
              </xsl:variable>
              <xsl:sequence select="$end-annotation" />
              <xsl:call-template name="ev:parse-initial-metadata">
                <xsl:with-param name="lmnl" select="$rest" />
                <xsl:with-param name="open-ranges" select="()" tunnel="yes" />
                <xsl:with-param name="open-documents" as="element(ev:document)+" tunnel="yes">
                  <ev:document in="end-annotation">
                    <xsl:sequence select="$end-annotation" />
                  </ev:document>
                  <xsl:sequence select="$open-documents" />
                </xsl:with-param>
                <xsl:with-param name="line" select="$line + count($lines) - 1" tunnel="yes" />
                <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
              </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
              <xsl:call-template name="ev:error">
                <xsl:with-param name="parsing" select="'end tag'" />
                <xsl:with-param name="found" select="$lmnl" />
                <xsl:with-param name="message" select="'end annotation doesn''t match open annotation'" />
              </xsl:call-template>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:when>
        <xsl:when test="exists($open-ranges)">
          <xsl:variable name="matching-start-tag" as="element(ev:start-tag)*" 
            select="if ($id ne '')
                    then $open-ranges[@id eq $id]
                    else $open-ranges[self::ev:start-tag and
                                      @name eq $name and @ns eq $ns and @id eq ''][1]" />
          <xsl:choose>
            <xsl:when test="count($matching-start-tag) > 1">
              <xsl:call-template name="ev:error">
                <xsl:with-param name="parsing" select="'end tag'" />
                <xsl:with-param name="found" select="$lmnl" />
                <xsl:with-param name="message" select="'duplicate start tag id'" />
              </xsl:call-template>
            </xsl:when>
            <xsl:when test="exists($matching-start-tag)">
              <xsl:variable name="end-tag" as="element()">
                <ev:end-tag name="{$name}" ns="{$ns}" id="{$id}" hash="end-tag('{$ns}','{$name}','{$id}')" line="{$line}" col="{$col}" />
              </xsl:variable>
              <xsl:sequence select="$end-tag" />
              <xsl:call-template name="ev:parse-initial-metadata">
                <xsl:with-param name="lmnl" select="$rest" />
                <xsl:with-param name="open-ranges" select="()" tunnel="yes" />
                <xsl:with-param name="open-documents" as="element(ev:document)+" tunnel="yes">
                  <ev:document in="end-tag">
                    <xsl:sequence select="$end-tag, ($open-ranges except $matching-start-tag)" />
                  </ev:document>
                  <xsl:sequence select="$open-documents" />
                </xsl:with-param>
                <xsl:with-param name="line" select="$line + count($lines) - 1" tunnel="yes" />
                <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
              </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
              <xsl:call-template name="ev:error">
                <xsl:with-param name="parsing" select="'end tag'" />
                <xsl:with-param name="found" select="$lmnl" />
                <xsl:with-param name="message" select="'end tag doesn''t match any open start tag'" />
              </xsl:call-template>
            </xsl:otherwise>
          </xsl:choose>      
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="ev:error">
            <xsl:with-param name="parsing" select="'end tag'" />
            <xsl:with-param name="found" select="$lmnl" />
            <xsl:with-param name="message" select="'no open range or annotation to match this end tag'" />
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="parsing" select="'end tag'" />
        <xsl:with-param name="found" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>

<xsl:template name="ev:parse-initial-metadata">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:analyze-string select="$lmnl" flags="s" regex="^(\s+)(.+)$">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string+" select="tokenize($parsed, '\n')" />
      <xsl:variable name="rest" as="xs:string" select="regex-group(2)" />
      <xsl:call-template name="ev:parse-metadata">
        <xsl:with-param name="lmnl" select="$rest" />
        <xsl:with-param name="line" select="$line + count($lines) - 1" tunnel="yes" />
        <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
      </xsl:call-template>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:parse-metadata-end">
        <xsl:with-param name="lmnl" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>

<xsl:template name="ev:parse-metadata">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:analyze-string select="$lmnl" flags="s" regex="^(\s*)(\[.+)$">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string*" select="tokenize($parsed, '\n')" />
      <xsl:call-template name="ev:parse-annotation">
        <xsl:with-param name="lmnl" select="regex-group(2)" />
        <xsl:with-param name="line" select="$line + (if (empty($lines)) then 0 else (count($lines) - 1))" tunnel="yes" />
        <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
      </xsl:call-template>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:parse-metadata-end">
        <xsl:with-param name="lmnl" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>  
  
<xsl:template name="ev:parse-metadata-end">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="open-documents" as="element(ev:document)*" tunnel="yes" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:variable name="context" as="xs:string" select="$open-documents[1]/@in" />
  <xsl:variable name="ranges" as="element()*" select="$open-documents[1]/ev:*" />
  <xsl:variable name="name" as="xs:string" select="$ranges[1]/@name" />
  <xsl:variable name="ns" as="xs:string" select="$ranges[1]/@ns" />
  <xsl:variable name="id" as="xs:string" select="string($ranges[1]/@id)" />
  <xsl:choose>
    <xsl:when test="$context = 'atom' and 
                    starts-with($lmnl, '}}')">
      <ev:end-atom name="{$name}" ns="{$ns}" hash="end-atom('{$ns}','{$name}')" line="{$line}" col="{$col}" />
      <xsl:call-template name="ev:parse-document">
        <xsl:with-param name="lmnl" select="substring($lmnl, 3)" />
        <xsl:with-param name="open-ranges" select="$ranges[position() > 1]" tunnel="yes" />
        <xsl:with-param name="open-documents" select="$open-documents[position() > 1]" tunnel="yes" />
        <xsl:with-param name="col" select="$col + 2" tunnel="yes" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$context = 'start-tag' and
                    starts-with($lmnl, '}')">
      <ev:end-start-tag name="{$name}" ns="{$ns}" id="{$id}" hash="end-start-tag('{$ns}','{$name}','{$id}')" line="{$line}" col="{$col}"/>
      <xsl:call-template name="ev:parse-document">
        <xsl:with-param name="lmnl" select="substring($lmnl, 2)" />
        <xsl:with-param name="open-ranges" select="$ranges" tunnel="yes" />
        <xsl:with-param name="open-documents" select="$open-documents[position() > 1]" tunnel="yes" />
        <xsl:with-param name="col" select="$col + 1" tunnel="yes" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$context = 'start-tag' and
                    starts-with($lmnl, ']')">
      <ev:end-start-tag name="{$name}" ns="{$ns}" id="{$id}" hash="end-start-tag('{$ns}','{$name}','{$id}')" line="{$line}" col="{$col}" />
      <ev:end-tag name="{$name}" ns="{$ns}" id="{$id}" hash="end-tag('{$ns}','{$name}','{$id}')" line="{$line}" col="{$col}" />
      <ev:end-end-tag name="{$name}" ns="{$ns}" id="{$id}" hash="end-end-tag('{$ns}','{$name}','{$id}')" line="{$line}" col="{$col}" />
      <xsl:call-template name="ev:parse-document">
        <xsl:with-param name="lmnl" select="substring($lmnl, 2)" />
        <xsl:with-param name="open-ranges" select="$ranges[position() > 1]" tunnel="yes" />
        <xsl:with-param name="open-documents" select="$open-documents[position() > 1]" tunnel="yes" />
        <xsl:with-param name="col" select="$col + 1" tunnel="yes" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$context = 'end-tag' and
                    starts-with($lmnl, ']')">
      <ev:end-end-tag name="{$name}" ns="{$ns}" id="{$id}" hash="end-end-tag('{$ns}','{$name}','{$id}')" line="{$line}" col="{$col}" />
      <xsl:call-template name="ev:parse-document">
        <xsl:with-param name="lmnl" select="substring($lmnl, 2)" />
        <xsl:with-param name="open-ranges" select="$ranges[position() > 1]" tunnel="yes" />
        <xsl:with-param name="open-documents" select="$open-documents[position() > 1]" tunnel="yes" />
        <xsl:with-param name="col" select="$col + 1" tunnel="yes" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$context = 'start-annotation' and
                    starts-with($lmnl, '}')">
      <ev:end-start-annotation name="{$name}" ns="{$ns}" hash="end-start-annotation('{$ns}','{$name}')" line="{$line}" col="{$col}" />
      <xsl:call-template name="ev:parse-document">
        <xsl:with-param name="lmnl" select="substring($lmnl, 2)" />
        <xsl:with-param name="open-ranges" select="$ranges" tunnel="yes" />
        <xsl:with-param name="open-documents" select="$open-documents[position() > 1]" tunnel="yes" />
        <xsl:with-param name="col" select="$col + 1" tunnel="yes" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$context = 'start-annotation' and
                    starts-with($lmnl, ']')">
      <ev:end-start-annotation name="{$name}" ns="{$ns}" hash="end-start-annotation('{$ns}','{$name}')" line="{$line}" col="{$col}" />
      <ev:end-annotation name="{$name}" ns="{$ns}" hash="end-annotation('{$ns}','{$name}')" line="{$line}" col="{$col}" />
      <ev:end-end-annotation name="{$name}" ns="{$ns}" hash="end-end-annotation('{$ns}','{$name}')" line="{$line}" col="{$col}" />
      <xsl:call-template name="ev:parse-metadata">
        <xsl:with-param name="lmnl" select="substring($lmnl, 2)" />
        <xsl:with-param name="open-ranges" select="()" tunnel="yes" />
        <xsl:with-param name="open-documents" select="$open-documents[position() > 1]" tunnel="yes" />
        <xsl:with-param name="col" select="$col + 1" tunnel="yes" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$context = 'end-annotation' and
                    starts-with($lmnl, ']')">
      <ev:end-end-annotation name="{$name}" ns="{$ns}" hash="end-end-annotation('{$ns}','{$name}')" line="{$line}" col="{$col}" />
      <xsl:call-template name="ev:parse-metadata">
        <xsl:with-param name="lmnl" select="substring($lmnl, 2)" />
        <xsl:with-param name="open-ranges" select="()" tunnel="yes" />
        <xsl:with-param name="open-documents" select="$open-documents[position() > 1]" tunnel="yes" />
        <xsl:with-param name="col" select="$col + 1" tunnel="yes" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="parsing" select="'metadata'" />
        <xsl:with-param name="found" select="$lmnl" />
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>  
</xsl:template>  

<xsl:template name="ev:parse-annotation">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="namespaces" as="document-node()" tunnel="yes" select="/" />
  <xsl:param name="open-ranges" as="element()*" tunnel="yes" select="()" />
  <xsl:param name="open-documents" as="element(ev:document)*" tunnel="yes" select="()" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:variable name="start-annotation-regex">^(\[(((\i\c*):)?(\i\c*)))(.+)$</xsl:variable>
  <xsl:analyze-string select="$lmnl" flags="s" regex="{$start-annotation-regex}">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string+" select="tokenize($parsed, '\n')" />
      <xsl:variable name="prefix" as="xs:string" select="regex-group(4)" />
      <xsl:variable name="ns" as="xs:string" select="ev:ns($prefix, $namespaces)" />
      <xsl:variable name="name" as="xs:string" select="regex-group(5)" />
      <xsl:variable name="rest" as="xs:string" select="regex-group(6)" />
      <xsl:variable name="annotation" as="element(ev:start-annotation)">
        <ev:start-annotation name="{$name}" ns="{$ns}" hash="start-annotation('{$ns}','{$name}')" line="{$line}" col="{$col}" />
      </xsl:variable>
      <xsl:sequence select="$annotation" />
      <xsl:call-template name="ev:parse-initial-metadata">
        <xsl:with-param name="lmnl" select="$rest" />
        <xsl:with-param name="open-ranges" select="$annotation" tunnel="yes" />
        <xsl:with-param name="open-documents" as="element(ev:document)+" tunnel="yes">
          <ev:document in="start-annotation">
            <xsl:sequence select="$annotation" />
          </ev:document>
          <xsl:sequence select="$open-documents" />
        </xsl:with-param>
        <xsl:with-param name="line" select="$line + count($lines) - 1" tunnel="yes" />
        <xsl:with-param name="col" select="string-length($lines[last()]) + (if (count($lines) > 1) then 0 else $col)" tunnel="yes" />
      </xsl:call-template>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="parsing" select="'annotation'" />
        <xsl:with-param name="found" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>  

<xsl:template name="ev:parse-comment">
  <xsl:param name="lmnl" as="xs:string" required="yes" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:variable name="comment-regex">^(\[!--(([^-]|(-[^-])|(--[^\]]))*)--\])(.*)$</xsl:variable>
  <xsl:analyze-string select="$lmnl" flags="s" regex="{$comment-regex}">
    <xsl:matching-substring>
      <xsl:variable name="parsed" as="xs:string" select="regex-group(1)" />
      <xsl:variable name="lines" as="xs:string+" select="tokenize($parsed, '\n')" />
      <xsl:variable name="content" as="xs:string" select="regex-group(2)" />
      <xsl:variable name="rest" as="xs:string" select="regex-group(6)" />
      <ev:comment content="{$content}" hash="comment()" line="{$line}" col="{$col}" />
      <!-- *** TODO: parsing comments in tags *** -->
      <xsl:call-template name="ev:parse-document">
        <xsl:with-param name="lmnl" select="$rest" />
      </xsl:call-template>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="parsing" select="'comment'" />
        <xsl:with-param name="found" select="$lmnl" />
      </xsl:call-template>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>  
  
<xsl:function name="ev:ns" as="xs:string">
  <xsl:param name="prefix" as="xs:string" />
  <xsl:param name="namespaces" as="document-node()" />
  <xsl:variable name="ns" as="xs:string?" select="key('namespaces', $prefix, $namespaces)" />
  <xsl:choose>
    <xsl:when test="exists($ns)">
      <xsl:sequence select="$ns" />
    </xsl:when>
    <xsl:when test="$prefix = ''">
      <xsl:sequence select="''" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ev:error">
        <xsl:with-param name="message" select="concat('no namespace with prefix ', $prefix)" />
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>  
  
<xsl:template name="ev:error">
  <xsl:param name="parsing" as="xs:string" select="''" />
  <xsl:param name="found" as="xs:string" select="''" />
  <xsl:param name="message" as="xs:string" select="''" />
  <xsl:param name="line" as="xs:integer" tunnel="yes" required="yes" />
  <xsl:param name="col" as="xs:integer" tunnel="yes" required="yes" />
  <ev:error parsing="{$parsing}" 
            found="{$found}"
            message="{$message}"
            line="{$line}"
            col="{$col}" />
</xsl:template>  
  
<!-- *** ev:hash() *** -->
  
<xsl:function name="ev:hash" as="xs:string">
  <xsl:param name="kind" as="xs:string" />
  <xsl:param name="arg" as="xs:string" />
  <xsl:sequence select="concat($kind, '(''', $arg, ''')')" />
</xsl:function>  
  
<xsl:function name="ev:hash" as="xs:string">
  <xsl:param name="kind" as="xs:string" />
  <xsl:param name="ns" as="xs:string" />
  <xsl:param name="name" as="xs:string" />
  <xsl:sequence select="concat($kind, '(''', $ns, ''',''', $name, ''')')" />
</xsl:function>  
  
<xsl:function name="ev:hash" as="xs:string">
  <xsl:param name="kind" as="xs:string" />
  <xsl:param name="ns" as="xs:string" />
  <xsl:param name="name" as="xs:string" />
  <xsl:param name="id" as="xs:string" />
  <xsl:sequence select="concat($kind, '(''', $ns, ''',''', $name, ''',''', $id, ''')')" />
</xsl:function>  
  
<xsl:function name="ev:hash" as="xs:string">
  <xsl:param name="event" as="element()" />
  <xsl:apply-templates select="$event" mode="ev:hash" />
</xsl:function>
  
<xsl:template match="ev:start-tag | ev:end-start-tag | 
                     ev:end-tag | ev:end-end-tag |
                     ev:start-element | ev:end-start-element |
                     ev:end-element | ev:end-end-element |
                     ev:start-range | ev:end-start-range |
                     ev:end-range | ev:end-end-range"
              mode="ev:hash" as="xs:string">
  <xsl:sequence select="concat(local-name(), '(''', @ns, ''',''', @name,
    ''',''', @id, ''')')" />
</xsl:template>
  
<xsl:template match="ev:start-annotation | ev:end-start-annotation | 
                     ev:end-annotation | ev:end-end-annotation |
                     ev:start-atom | ev:end-atom"
              mode="ev:hash" as="xs:string">
  <xsl:sequence select="concat(local-name(), '(''', @ns, ''',''', @name, ''')')" />
</xsl:template>
  
<xsl:template match="ev:text"
              mode="ev:hash" as="xs:string">
  <xsl:sequence select="concat(local-name(), '(''', @ws, ''')')" />
</xsl:template>
  
</xsl:stylesheet>
