Contents
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.
Througout the document we will use the following terms:
http://relaxng.org/ns/structure/1.0
.
http://icebearsoft.euweb.cz/ns/annotation
.
We will use the r:
prefix for the RNG namespace and the a:
prefix for the annotation namespace.
Contents
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.
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.
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 |
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> |
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.
<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.
<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.
<a:elem>
This element makes writing the element names in the annotation part easier because
you need not write the angle brackets using <
and >
. You
can simply write <a:elem>r:element</a:elem>
in order to produce
<r:element>
.
<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:
for
attribute is not given: The value of
/r:grammar/@ns
or /r:grammar/@datatypeLibrary
, respectively, is
displayed.
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.
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. |
|
|
---|---|---|
2. |
|
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> |
Contents
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.
Stylesheet parameters enable changes of the basic properties of the annotated Relax NG Schema.
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> |
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> |
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'/> |
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.
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> |
<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> |
<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> |
<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> |
<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> |
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.
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.
<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> |
<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> |
<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.
<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> |
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.
<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:
$head
:
the <head>
element
$tocname
:
the name of the table of contents taken from the very first <h2>
element
$toc
:
the very first <ul>
element containing the body of the table of
contents
$body-attributes
:
the sequence of all attributes of the <body>
element
<!-- ***** 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.
<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> |
<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> |
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