Ice Bear SoftTool for annotation of Relax NG Schemas

Contents

1. Introduction
2. Terminology
3. Annotating the Relax NG schema
  3.1. Using the stylesheet
  3.1.1. Usage from a command line
  3.1.2. Usage in Ant
  3.2. Elements of the annotation namespace
  3.2.1. Element <a:title>
  3.2.2. Element <a:annotation>
  3.2.3. Element <a:elem>
  3.2.4. Elements <a:namespace> and <a:datatype>
  3.2.5. References to patterns
4. Customization
  4.1. Customization by parameters
  4.1.1. Changing colours
  4.1.2. Extension of imported schemas
  4.1.3. Providing a link to the original schema
  4.1.4. Localization
  4.2. Changing the appearance using a cascading stylesheet
  4.2.1. <xsl:template match='/'>
  4.2.2. <xsl:template match='html'>
  4.2.3. <xsl:template match='pre'>
  4.2.4. <xsl:template match='*'>
  4.2.5. Adding the modification time
  4.3. Display in frames
  4.3.1. <xsl:template match='html'>
  4.3.2. <xsl:template match='*' mode='toc'>
  4.3.3. <xsl:template match='a' mode='toc'>
  4.3.4. <xsl:template match='a'>
  4.4. Documentation in several HTML files
  4.4.1. <xsl:template match='html'>
  4.4.2. <xsl:template match='a[@name]'>
  4.4.3. <xsl:template match='a[@href]'>
5. Download

1. Introduction

The first version of this tool was developed when I was working on cstugbulletin.rng.html. It was created step by step from scratch and was documented because it was a public project and the schema should be readable by anybody. Soon it became so large that it was impossible to see it in the window of my text editor and scrolling was necessary. I realized that some kind of pretty view preferably with hyperlinks is needed. Later I had to work on other Relax NG schemas and I disliked the necessity of editing the stylesheet in order to fit to each particular case. Therefore the tool was generalized and made customizable.

The first version used just XSLT 1.0 features and could be linked by the processing instruction to the Relax NG Schema file so that it can be open directly in Mozilla. It was found later that some browsers do not display correctly national characters when presenting XML files transformed on-the-fly and moreover some browsers do not support transformation at all. The stylesheet is therefore intended to be used with an XSLT 2.0 processors such as Saxon but it still intentionally relies mostly on XSLT 1.0 features. Should you wish to downgrade the stylesheet to XSLT 1.0, you must replace all occurences of element() with node() and rewrite teplates for elements <a:namespace> and <a:datatype> with simpler ones implementing a reasonable subset. Such a stylesheet should work.

 Top of the page 

2. Terminology

Througout the document we will use the following terms:

We will use the r: prefix for the RNG namespace and the a: prefix for the annotation namespace.

 Top of the page 

3. Annotating the Relax NG schema

Contents

3.1. Using the stylesheet
  3.1.1. Usage from a command line
  3.1.2. Usage in Ant
3.2. Elements of the annotation namespace
  3.2.1. Element <a:title>
  3.2.2. Element <a:annotation>
  3.2.3. Element <a:elem>
  3.2.4. Elements <a:namespace> and <a:datatype>
  3.2.5. References to patterns

The Relax NG schemas consist of elements in the RNG namespace. Elements in other namespaces are ignored by the validator. We will use the elements in the annotation namespace to insert documentation of the schema. This tool will convert them to HTML. Typical view of the annotated schema, generated by rng.xsl, is shown below.

3.1. Using the stylesheet

The rng.xsl stylesheet was developed for use with Saxon 8. It will work with other XSLT 2.0 processors except for one function in the <a:namespace> and <a:datatype> elements.

3.1.1. Usage from a command line

Let's suppose that saxon8 is your script which sets the CLASSPATH for running Saxon and invokes Java as defined in the Saxon documentation. It transfers all arguments to Saxon. The calling sequence then is:

saxon8 options -o filename.rng.html filename.rng rng.xsl parameters

You may supply any Saxon options, e.g. -l will switch on line numbering. The parameters will be described later in section Customization. By prefixing the parameter with an exclamation mark you can set output properties, e.g. the output encoding may be changed to ISO 8859-2 by:

!encoding=iso-8859-2

3.1.2. Usage in Ant

First you have to put saxon8.jar to CLASSPATH and inform Ant to use Saxon 8. You will achieve it by defining the following system property in ANT_OPTS (do not put it to ANT_ARGS, it will not work):

javax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl

A single document can be transformed by:

<xslt style="rng.xsl" in="filename.rng" out="filename.rng.html">
  <!-- nested elements -->
</xslt>

If you transform several documents, you have to use a nested mapper:

<xslt basedir='.'
      destdir='.'
      style='rng.xsl'
      includes='**/*.rng'>
  <mapper type='glob' from='*' to='*.html'/>
  <!-- other nested elements -->
</xslt>

The optional parameters are defined in nested <param> elements. The output properties are specified in <outputproperty> elements. For instance, you can change the output encoding to ISO 8859-2 by:

<outputproperty name='encoding' value='iso-8859-2'/>

Saxon options are defined in the <factory> element. For instance, you can switch line numbering on by:

<factory>
  <attribute name='http://saxon.sf.net/feature/linenumbering' value='true'/>
</factory>
 Top of the page 

3.2. Elements of the annotation namespace

Contents

3.2.1. Element <a:title>
3.2.2. Element <a:annotation>
3.2.3. Element <a:elem>
3.2.4. Elements <a:namespace> and <a:datatype>
3.2.5. References to patterns

The annotation namespace contains a few special elements that will be described in the following sections. All other elements in the annotation namespace not mentioned here will be converted to HTML elements of the same name and copies of all attributes. It is thus easy to enter any HTML markup.

All elements of the annotation namespace must be the children of the topmost <r:grammar> element. Relax NG allows to put annotation anywhere but this tool will be confused.

3.2.1. Element <a:title>

This must be the very first child of the topmost <r:grammar> element. It prints the title of the page and generates the table of contents.

3.2.2. Element <a:annotation>

This is the main element for annotations. It may contain any text with arbitrary HTML markup. If it does not contain any <a:p> element, the whole contents will be placed inside a newly created <p> element and all attributes of the <a:annotation> element will be copied to it. It may be useful for the class attribute.

There is an important reason for using the <a:annotation> element with optional <a:p> inside. The <a:annotation> element builds a labelled header if the following sibling is <r:define> or <r:start>. If you have several consecutive <a:annotation> elements, only the last one will appear below the header. You can put several paragraphs of text below the header only by placing <a:annotation> around <a:p> elements. If you do not use <a:annotation> elements, the labelled headers will not be created and the table of contents will not work.

3.2.3. Element <a:elem>

This element makes writing the element names in the annotation part easier because you need not write the angle brackets using &lt; and &gt;. You can simply write <a:elem>r:element</a:elem> in order to produce <r:element>.

3.2.4. Elements <a:namespace> and <a:datatype>

These elements output the namespace (value of the ns attribute) and data type library for the selected context. They offer three possibilities:

  1. The value of the for attribute is not given: The value of /r:grammar/@ns or /r:grammar/@datatypeLibrary, respectively, is displayed.
  2. The value of the for attribute is a single dot: The template finds the first following element in the RNG namespace and displays the value of the ns or datatypeLibrary attribute of the element or the value inherited from the nearest ancestor. The namespace for the <r:attribute> element cannot be inherited.
  3. The for attribute has other value: If the function saxon:evaluate is available, the value is treated as an XPath expression which must be a single element. The namespace or data type library is obtained exactly as above. Be sure that the expression retuns a single node. For instance, //r:element[1] will return a sequence of nodes (unless you have a very special schema). You have to use (//r:element)[1]. If the saxon:evaluate is unavailable, the template outputs Not implemented!

You can test the elements on the test.rng file supplied with the package. The annotation contains <a:namespace/>. Replace it with the two following codes:

1.
<a:namespace for='.'/>
2.
<a:namespace for='(//r:element[@name="include"])[1]'/>

3.2.5. References to patterns

The stylesheet creates labelled headers for all patterns. The name is equal to the value of the name attribute of the <r:define> element or start for the <r:start> element. If you wish refer to the pattern defined by <define name='def.article.contents'>, you may achieve it by the following code:

<a:a title="article contents" href="#def.article.contents">link to article contents</a:a>
 Top of the page 

4. Customization

Contents

4.1. Customization by parameters
  4.1.1. Changing colours
  4.1.2. Extension of imported schemas
  4.1.3. Providing a link to the original schema
  4.1.4. Localization
4.2. Changing the appearance using a cascading stylesheet
  4.2.1. <xsl:template match='/'>
  4.2.2. <xsl:template match='html'>
  4.2.3. <xsl:template match='pre'>
  4.2.4. <xsl:template match='*'>
  4.2.5. Adding the modification time
4.3. Display in frames
  4.3.1. <xsl:template match='html'>
  4.3.2. <xsl:template match='*' mode='toc'>
  4.3.3. <xsl:template match='a' mode='toc'>
  4.3.4. <xsl:template match='a'>
4.4. Documentation in several HTML files
  4.4.1. <xsl:template match='html'>
  4.4.2. <xsl:template match='a[@name]'>
  4.4.3. <xsl:template match='a[@href]'>

Simple customization may be achieved by parameters given during transformation. Complex changes will require writing a custom stylesheet which will import rng.xsl and either override the templates or get the nodeset resulting from the first transformation and process it again through other templates. Both methods will be described in the following sections.

The custom stylesheets make extensive use of XSLT 2.0 features which enable to use the result of a template as a node set. I suggest not to rewrite the stylesheets for XSLT 1.0 processors although it is possible. You would have to use the node-set function from EXSLT. The implementation of this function differs in different processor. You can compare both approaches in the stylesheets that do the same: bibliography2.xsl that uses XSLT 2.0 features, and bibliography.xsl doing the same with XSLT 1.0 features only. Notice the search for the particular implementation of the node-set function.

4.1. Customization by parameters

Stylesheet parameters enable changes of the basic properties of the annotated Relax NG Schema.

4.1.1. Changing colours

Colours can be changed by parameters the names of which are the sam as the corresponding attributes of the <body> element. The default values are:

<xsl:param name='link'>#900000</xsl:param>
<xsl:param name='vlink'>#d30000</xsl:param>
<xsl:param name='alink'>#c14242</xsl:param>
<xsl:param name='bgcolor'>#ffffff</xsl:param>
<xsl:param name='textcolor'>#000000</xsl:param>

4.1.2. Extension of imported schemas

It is assumed that all files are transformed with the same stylesheet to HTML. If the pattern refers to an included schema, the stylesheet has to append an extension of the corresponding HTML file. The extension is specified in the ext parameter. The default is obvious:

<xsl:param name='ext'>.html</xsl:param>

4.1.3. Providing a link to the original schema

We may wish to publish both the HTML view and the original Relax NG schema on the web. If the parameter rnghref is nonempty and nonzero, the link will be created. It is assumed that both files are in the same directory. If the original file is elsewhere, you must specify its prefix in the hrefprefix parameter. The default values are empty:

<xsl:param name='rnghref'/>
<xsl:param name='hrefprefix'/>

4.1.4. Localization

The stylesheet inserts English texts "Table of contents" and "The pattern is used in:". You can supply an XML file with language variants using parameter rngtexts. If the file contains variants for several languages, you should select the language using parameter rnglang. The document element of the file with the language variants must be <rngtexts> and it must contain elements <contents-name> and <used-in>. Both these element may contain the lang attribute. If the rnglang parameter is given, the stylesheet will select the elements having the lang attribute with the same value and the elements without the lang attribute. If the rnglang parameter is omitted, the stylesheet selects the elements without the lang attribute. The default values of both parameters are empty.

<xsl:param name='rngtexts'/>
<xsl:param name='rnglang'/>

The distribution package contains the file rng-cs.xml with the texts in the Czech language:

<rngtexts>
  <!-- Table of contents -->
  <contents-name>Obsah</contents-name>
  <!-- The pattern is used in: -->
  <used-in>Vzor je použit v:</used-in>
</rngtexts>

I will be glad if you send me translations to other languages and allow me to distribute them as a part of this package.

 Top of the page 

4.2. Changing the appearance using a cascading stylesheet

Contents

4.2.1. <xsl:template match='/'>
4.2.2. <xsl:template match='html'>
4.2.3. <xsl:template match='pre'>
4.2.4. <xsl:template match='*'>
4.2.5. Adding the modification time

Suppose that we wish to define appearance using a cascading stylesheet similar to that used for display of this page. Moreover we wish to display the date and time of the last modificatin of the original file. The page should look as shown in the picture below.

We will solve it by developing stylesheet rng-css.xsl which gets the NodeSet from the imported stylesheet rng.xsl and processes it again:

<xsl:import href='rng.xsl'/>

The stylesheet has an additional parameter for specifying the URL of the cascading stylesheet to be used.

<xsl:param name='css'>rng.css</xsl:param>

4.2.1. <xsl:template match='/'>

This template just stores the result of transformation by the imported stylesheet and then applies other templates to it.

<xsl:template match='/'>
  <xsl:variable as='item()*' name='node'>
    <xsl:next-match/>
  </xsl:variable>
  <xsl:apply-templates select='$node'/>
</xsl:template>

4.2.2. <xsl:template match='html'>

The document element from the imported stylesheet is <html>. We reprocess it by a template which first copies the contents of its <head> child and adds the reference to the cascading stylesheet. Then we strip the attributes from the <body> element and process its body.

<xsl:template match='html'>
  <html>
    <head>
      <xsl:apply-templates select='head/*'/>
      <link rel="STYLESHEET" href="{$css}" type="text/css"/>
    </head>
    <body>
      <xsl:apply-templates select='body/*'/>
    </body>
  </html>
</xsl:template>

4.2.3. <xsl:template match='pre'>

The <pre> element will be put into a one-cell table.

<xsl:template match='pre'>
  <table border="2" frame="box" rules="rows" cellpadding="5" class="pre">
    <tr><td><pre>
      <xsl:apply-templates/>
    </pre></td></tr>
  </table>
</xsl:template>

4.2.4. <xsl:template match='*'>

The remaining elements are just copied with their attributes. We cannot simply override the template for all elements because the imported stylesheet would use it. We know that the HTML elements have empty namespace URI. We therefore test it and if the namespace URI is nonempty, we use a matching template from the imported stylesheet.

<xsl:template match='*'>
  <xsl:choose>
    <xsl:when test='not(namespace-uri(.))'>
      <xsl:copy>
        <xsl:copy-of select='@*'/>
        <xsl:apply-templates/>
      </xsl:copy>
    </xsl:when>
    <xsl:otherwise>
      <xsl:next-match/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

4.2.5. Adding the modification time

The link to the original file is displayed in the <p> element which immediately follows the <h1> element with the main title and precedes the <h2> elements with the table of contents. We will write a template which matches exactly this element. In order to add the modification time we need an extension function. The use-when attrbute tests availability of the zw:last-modified function. If the function is not available, the template will not be compiled and the element will not be modified.

<xsl:template match='p[name(preceding-sibling::element()[1])="h1" and
                       name(following-sibling::element()[1])="h2"]'
     xmlns:zw='java:cz.euweb.icebearsoft.saxon.ExtUtils'
     use-when='function-available("zw:last-modified")'>
  <p>Original file:
    <xsl:apply-templates/>, last modified:
    <xsl:value-of select='zw:last-modified($root)'/>
  </p>
</xsl:template>

The zw:last-modified function could be invoked without an argument in order to report the modification time of the document containing the context node. However, the context node no longer belongs to the original document. It comes from the NodeSet generated by the imported stylesheet. If you use just <xsl:value-of select='zw:last-modified()'/>, you will get the modification time of the imported stylesheet rng.xsl (try it). We must therefore use the root node of the original document stored in the global stylesheet variable:

<xsl:variable name='root' select='/' />

Do not forget to specify the rnghref=1 parameter when using the stylesheet, otherwise the reference to the original file will not be created.

 Top of the page 

4.3. Display in frames

Contents

4.3.1. <xsl:template match='html'>
4.3.2. <xsl:template match='*' mode='toc'>
4.3.3. <xsl:template match='a' mode='toc'>
4.3.4. <xsl:template match='a'>

In this section we will show how the document can be transformed to the framed view where the left frame contains the table of contents and the right frame contatins the whole document with the table of contents stripped off.

We will use the same approach as in section Changing the appearance using a cascading stylesheet. The following text will therefore describe only the new templates.

The stylesheet has additional parameters defining the names of the frame files and the width of the left frame.

<xsl:param name='left' select='concat(document-uri(/), "-", "left.html")'/>
<xsl:param name='right' select='concat(document-uri(/), "-", "right.html")'/>
<xsl:param name='width'>30%</xsl:param>

Remember that the stylesheet will fail if <a:title> is not used. The test of validity is not difficult, do it yourself as a homework.

4.3.1. <xsl:template match='html'>

Reprocessing the <html> element must now generate three files. The first one contains the copy of the <head> element and defines the frame set. The second file will contain the table of contents. The first <ul> element will be processed in the toc mode. The last file will contain the rest except the elements that went to the table of contents. Remember that the imported stylesheet contains its own templates for processing the attributes. For the sake of safety we insert the attributes by <xsl:copy-of select='body/@*'/>

<xsl:template match='html'>
  <html>
    <xsl:copy-of select='head'/>
    <frameset cols='{concat($width, ",*" )}'>
      <frame name='left' src='{$left}'/>
      <frame name='right' src='{$right}'/>
    </frameset>
  </html>
  <xsl:result-document href='{$left}'>
    <html>
      <head>
        <title><xsl:value-of select='body/h2[1]'/></title>
      </head>
      <body>
        <xsl:copy-of select='body/@*'/>
        <p><b><xsl:copy-of select='body/h2[1]'/></b></p>
        <xsl:apply-templates select='body/ul[1]' mode='toc'/>
      </body>
    </html>
  </xsl:result-document>
  <xsl:result-document href='{$right}'>
    <html>
      <xsl:copy-of select='head'/>
      <body>
        <xsl:copy-of select='body/@*'/>
        <xsl:apply-templates select='body/* except (body/h2[1], body/ul[1])'/>
      </body>
    </html>
  </xsl:result-document>
</xsl:template>

4.3.2. <xsl:template match='*' mode='toc'>

The elements processed in the toc mode will just be copied with its attributes. We will only need a special template for the <a> element which will be shown in the next section.

<xsl:template match='*' mode='toc'>
  <xsl:copy>
    <xsl:copy-of select='@*'/>
    <xsl:apply-templates mode='toc'/>
  </xsl:copy>
</xsl:template>

4.3.3. <xsl:template match='a' mode='toc'>

The toc mode is used when processing the table of contents in the left frame. The links must therefore be modified so that they contain the correct target and the file name for the right frame is prepended.

<xsl:template match='a' mode='toc'>
  <a>
    <xsl:copy-of select='@*'/>
    <xsl:attribute name='target'>right</xsl:attribute>
    <xsl:attribute name='href'>
      <xsl:value-of select='concat($right, @href)'/>
    </xsl:attribute>
    <xsl:apply-templates mode='toc'/>
  </a>
</xsl:template>

It may now seem that the <a> element contains two href attributes which is not allowed in XML. However, according to XSLT specification, if you create several attribute nodes with the same name, only the last one is used. We can thus overwrite the original href attribute with the new contents.

4.3.4. <xsl:template match='a'>

The <a> elements in the right frame need special treatment too. Only the local links should be displayed in the same window. If the link refers to another file, it should be displayed in the top frame which will replace the frame set.

<xsl:template match='a'>
  <a>
    <xsl:copy-of select='@*'/>
    <xsl:attribute name='target'>
      <xsl:value-of
           select='if (starts-with(@href, "#")) then "_self" else "_top"'/>
    </xsl:attribute>
    <xsl:apply-templates/>
  </a>
</xsl:template>
 Top of the page 

4.4. Documentation in several HTML files

Contents

4.4.1. <xsl:template match='html'>
4.4.2. <xsl:template match='a[@name]'>
4.4.3. <xsl:template match='a[@href]'>

This stylesheet will convert the document to a series of files each of which will contain the table of contents in the left column and the documentation of a single pattern in the right column. The contents before the first <h2> element will be placed to the introduction page. The typical view is shown below.

We will again reprocess the result of the imported stylesheet as described in section Changing the appearance using a cascading stylesheet. Similarly as in section Display in frames the stylesheet will fail if the <a:title> element is not used.

The stylesheet requires two additional parameters specifying the base name of the introduction file and the extension:

<xsl:param name='main' as='xs:string' select='document-uri(/)'/>
<xsl:param name='ext'>.html</xsl:param>

This presents another potential drawback. The main document must have the same name as the schema file with the extenstion appended, otherwise the links to included schemas will not work. The default values conform to this requirement.

4.4.1. <xsl:template match='html'>

Almost all actions are done in the template for the <html> element. The template is therefore quite long. We will split it to smaller (not well-formed) pieces and explain them later.

<xsl:template match='html'>
  <!-- ***** Storing information ***** -->
  <xsl:variable name='head' select='head'/>
  <xsl:variable name='tocname' select='body/h2[1]'/>
  <xsl:variable name='toc' select='body/ul[1]/*'/>
  <xsl:variable name='body-attributes' select='body/@*'/>

At the beginning of the template we have to store the following information:

  <!-- ***** Grouping the contents except table of contents by <h2> ***** -->
  <xsl:for-each-group select='body/* except (body/h2[1], body/ul[1])' group-starting-with='h2'>

The second piece started a loop over all child elements of <body> except those containing the table of contents. Each pattern description starts with the <h2> header, so we use it as a start element of the group.

    <!-- ***** Preparing the file name ***** -->
    <xsl:variable name='fn' as='xs:string'
         select='if (name() = "h1") then $main else concat($main, "-", a/@name)'/>

Now we have to prepare the file name. The context item is the first element of the group. Remember that the document starts with the main title. If the name of the context element is h1, we are generating the main file the name of which must be taken from the $main parameter. The names of other files will be based upon the name attribute of the <a> element which is always a child of the <h2> element. Remember that the schema file may include other schemas. If the file names were base solely upon the pattern names, they might not be unique. We will therefore prepend the main name and a hyphen.

    <!-- ***** Opening the result document ***** -->
    <xsl:result-document href='{concat($fn, $ext)}'>
      <html>
        <xsl:copy-of select='$head'/>
        <body>
          <xsl:copy-of select='$body-attributes'/>
          <table cellspacing='25' width='100%'>
            <tr valign='top'>

When opening the file we append the extension. We copy the $head variable and insert the attributes to the <body> element.

              <!-- ***** Left column with table of contents ***** -->
              <td>
                <p><b><font size='+2'>
                  <xsl:copy-of select='$tocname/*|$tocname/text()'/>
                </font></b></p>
                <ul>
                  <xsl:choose>
                    <xsl:when test='$fn = $main'>
                      <xsl:apply-templates select='$toc'/>
                    </xsl:when>
                    <xsl:otherwise>
                      <li>
                        <a href='{concat($main, $ext)}' title='Introduction'>Introduction</a>
                      </li>
                      <xsl:apply-templates select='$toc except $toc/li[a/@href = $fn]'/>
                    </xsl:otherwise>
                  </xsl:choose>
                </ul>
              </td>

We do not wish to use the <h2> header with the table of contents, we just display it in boldface usind somewhat larger font. The main page should contain the whole table of contents, the other pages must contain the link to the introductory page but the link to itself should be omitted.

              <!-- ***** Right column with the contents ***** -->
              <td width='100%'>
                <xsl:apply-templates select='current-group()'/>
              </td>
            </tr>
          </table>
        </body>
      </html>
    </xsl:result-document>
  </xsl:for-each-group>
</xsl:template>

In the right column we process the whole contents of the current group.

4.4.2. <xsl:template match='a[@name]'>

The labels are no longer needed. We omit them and imsert just their contents.

<xsl:template match='a[@name]'>
  <xsl:apply-templates/>
</xsl:template>

4.4.3. <xsl:template match='a[@href]'>

The references to the patterns must be turned into references to the corresponding files. If the href attribute starts with #, we must prepend the main file name and remove the hash character.

<xsl:template match='a[@href]'>
  <a>
    <xsl:copy-of select='@*'/>
    <xsl:attribute name='href'>
      <xsl:value-of
           select='if (starts-with(@href, "#"))
           then concat($main, "-", substring-after(@href, "#"), $ext) else @href'/>
    </xsl:attribute>
    <xsl:apply-templates/>
  </a>
</xsl:template>
 Top of the page 

5. Download

The distribution package contains all above mentioned files together with testing file test.rng. The test file is based upon cstugbulletin.rng but you should not use it for validation of the CSTUG bulletins. This is meant only for testing.

Download rngdoc.zip,.197  KB

 Top of the page