This is an attempt for literate programming of a transformation stylesheet. What does literate programming mean? This is an approach when one source file contains both the code and its verbose documentation. Some tool then extracts the code so that it can be executed and another tools generates pretty-printed documentation. There are WEB systems (do not confuse it with World Wide Web) for most programming languages and ltxdoc and companion docstrip packages for LaTeX. All these tools can be found on CTAN. Please do not say that other kind of programming is illiterate. Traditional style programmers do not like it.
XSLT as such is a good candidate for literate programming
tools. Several such systems can be found on the web. However, I am not
aware of any tool for literate programming of XSLT stylesheets. Although
noweb or possibly other configurable tools could
be used for literate programming of a stylesheet, I decided to stay with
pure XML solution. I tried to make the source file and stylesheets as
simple as possible. You will thus see that the test
attribute
of the <xsl:if>
and <xsl:when>
display
"<
" and ">
" while they actually
contain <
and >
. The test
also includes quotes inside quotes which is not allowed. The real code
mixes quotes and apostrophes.
Contents
The pages have both English and Czech version. The contents should be the same. However, it is no easy to maintain consistently two sets of pages. It is convenient if both language version remain in the same file. Originally the pages contained two columns with the English text in the left one and the Czech text in the right one. The common parts were typed in a single column which broke the two-column layout.
Two-column layout has its disadvantages. A reader is disturbed by presence of the second language. In addition some parts of the text make sense in one language version only. Thus the columns are not well balanced. It was therefore decided to make separate Czech and English pages which will be generated from the common source written in XML.
We should first choose the input format. It is possible to use standard DocBook. However, this format is quite involved. It offers numerous possibilities which will not be utilized here since the text should serve solely for web pages and other processing is not assumed. What is important is the visual appearance of the pages. We could download tools for transforming DocBook into HTML but refining these tools so that they satisfy our requirements would be painstaking. This format was therefore refused.
The first version of these pages was written directly in HTML. It seemed to be natural to enrich HTML with a few elements. This gave birth to the second version which was developed at the time when I knew just a little XML and XSLT and I was learning them on this project. Now I realized that a great many things were done in a complicated way without any good conception. I thus made an ambitious plan and it turned out that I knew less than I thought. When writing this document I learned again numerous new things. The idea of enriching HTML with a few elements, however, continues to live.
In the very first version generated from XML I wanted to
have the extension elements in my own namespace. However, I did not like
that the root <html>
element contained the namespace
declaration although none of these elements got into the page. At that
time I did not know that it is sufficient to put the prefix of my
namespace to the exclude-result-prefixes
attribute of the
<xsl:stylesheet>
element. I considered again the use of
a namespace. I came to the conclusion that the input format is defined
by my own tags which intentionally agree with HTML elements in a great
many cases. That way I rationalized my solution.
Page formatting can be achieved by two extreme methods:
The former method leads to an involved code. It need not matter because the pages are generated from XML but the formatting markup would mess up the transformation stylesheet. The latter method brings the following disadvantages:
Generally we must refuse formatting by CSS. On the other hand, cascading stylesheets offer useful possibilities some of which are not available in HTML. We can thus arrive at a compromise solution: the basic formatting that must be visible in all browsers must be accomplished by means of HTML markup which is further embellished by CSS. Pages built in this way can even be viewed by Lynx.
In the future the cascading stylesheet may also be literate programmed. For now it is written directly. You can look at its text version.
These pages are created using the Saxon XSLT processor because it is one of the fastest and is written in Java which makes it work in all operating systems. The stylesheet was originally written for version 6.5.2 but recently has been modified to work with version 8 which supports XSLT 2.0. Several places in this document describe how it was done with XSLT 1.0.
The goal of literate programming is to keep the code and its
documentation in sync. That is why everything is in one file. If we want
to apply this method to programming the stylesheet we should create it
in such a way that if run on itself the stylesheet generates its own
documentation. In principle it could be done but the source file would
be complicated and it would be easy to make errors in it. We therefore
prepare a small stylesheet which takes webmake.xml
as its
source and generates a stylesheet for transformation into HTML from it.
This transformation stylesheet will then be used not only for generation
of the web pages but also for creation of the documentation of the
stylesheet.
The stylesheet is nothing but an XML file, therefore it can be generated by transformation from another XML file. In order to do it we must use a kind of a trick. It is not sufficient to declare the XSLT namespace, we must declare an additional namespace which will be used as an alias later on.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslt="namespace-alias" version="2.0" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"> |
The xsl:
prefix belongs to the XSLT namespace,
the xslt:
prefix is assigned to an arbitrary namespace. The
<xsl:namespace-alias>
element declares that the
elements with the xslt:
prefix will have the
xsl:
prefix in the result file. Thus the templates can
contain transformation instructions that will not be executed but
written to the output file. The code will be explained later.
<xsl:namespace-alias stylesheet-prefix="xslt" result-prefix="xsl"/> <xsl:output method="xml" indent="yes" encoding="utf-8" saxon:character-representation="native"/> <xsl:strip-space elements="*"/> <xsl:preserve-space elements="xslt:text xsl:text"/> <xsl:param name="mode"/> <xsl:template match="/"> <xslt:stylesheet version="2.0" saxon:trace="no" exclude-result-prefixes="zw xs" extension-element-prefixes="saxon"> <xsl:apply-templates select="//zw-pre"/> </xslt:stylesheet> </xsl:template> <xsl:template match="zw-pre"> <xsl:if test="@mode=$mode or (not($mode) and not(@mode))"> <xsl:apply-templates/> </xsl:if> </xsl:template> <xsl:template match="zw-hide"> <xsl:apply-templates/> </xsl:template> <xsl:template match="*|@*"> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:apply-templates/> </xsl:copy> </xsl:template> <xsl:template match="text()"> <xsl:value-of select="." disable-output-escaping="yes"/> </xsl:template> <xsl:template match="processing-instruction()"> <xsl:processing-instruction name="{name()}"><xsl:value-of select="."/></xsl:processing-instruction> </xsl:template> <xsl:template match="zw-text"> <xslt:text> <xsl:value-of select="."/> </xslt:text> </xsl:template> |
The output format is always XML in the UTF-8 encoding. We
ignore spaces in all elements except for <xsl:text>
and
<xslt:text>
. The template selects only contents of
the <zw-pre>
elements no matter how deeply they are
nested. Several stylesheets will be generated from a single source file.
We will distinguish them by the global mode
parameter and
an attribute of the same name used in the <zw-pre>
element. In the webmake.xml
file, which is the source of
the stylesheet, these elements must belong to the XSLT namespace,
otherwise we would not get a working stylesheet. If we made elements
dynamically by <xsl:element>
, we would have to use the
xsl:
prefix in the templates, not the xslt:
alias.
The <zw-hide>
element is used for hiding
the contents which we do not wish to display for security reason. Here
we must copy its contents but the listing will be replaced with an
alternative text as will be shown later.
When we transform the <text>
element and
processing instructions, we wish to preserve real characters and do not
replace them with entities. We will therefore use the
disable-output-escaping='yes'
attribute.
The <zw-text>
must be transformed into
<xsl:text>
and characters entities must be used inside
it.
Notice that it is not necessary to declare the XSLT
namespace in the <xslt:stylesheet>
element because the
XSLT processor will insert it automatically. More precisely, two
identical namespaces for both prefixes xslt:
and
xsl:
will be declared. We need not declare the Saxon
namespace either since the XSLT processor will add it where it is
required. And this is the very stumbling block. This namespace is
required in the <xsl:output>
element because it contains
the saxon:character-representation
attribute. It is not
formally necessary in the <xslt:stylesheet>
element.
However, the extension-element-prefixes
attribute requires
that all prefixes are assigned to valid namespaces. We therefore add the
saxon:trace='no'
attribute although it is the default. It
is nothing but a coercive device which forces the XSLT processor to
insert the namespace declaration.
The following templates require extensions of the Saxon processor. The templates will not work with other XSLT processors.
The end of the file needs a closing tag.
</xsl:stylesheet> |
In the previous part we generated the transformation stylesheet from the document which you are just reading. The stylesheet is quite long. Its description will therefore be subdivided into sections.
When transforming the document it will be nacessary to
process some elements repeatedly in a different way. We will make use
of the mode
attribute. XSLT 1.0 allows to
process the template in one mode only. If we need to perform the same
action in several modes, we have to create a named template for one of
the modes:
<xsl:template match="something" mode="foo" name="something-foo"> <xsl:text>Do something here</xsl:text> </xsl:template> |
The templates for other modes will call the previous template by name:
<xsl:template match="something" mode="bar"> <xsl:call-template name="something-foo"/> </xsl:template> |
XSLT 2.0 allows to use the template in several modes. The example can thus be rewritten as:
<xsl:template match="something" mode="foo bar"> <xsl:text>Do something here</xsl:text> </xsl:template> |
Exact syntax can be found in the specification at http://www.w3.org/TR/xslt20/#modes.
One of the main tasks is to divide the contents according
to language. For this purpose the standard xml:lang
attribute and lang()
function can be used. This approach
is advantageous if we have large parts of the document in the same
language. However, we wish to interleave small pieces of text in
various languages and moreover we want to have common parts which
belong to all language versions. Making use of the standard would thus
lead to difficulties.
Element <zw>
was therefore invented for
language selection. It was intended for bilingual pages only. It
contained a simple condition and could not be nested.
Obsolete code |
---|
<xsl:template match="zw" name="zw"> <xsl:if test="@lang=$DocumentLanguage"> <xsl:apply-templates/> </xsl:if> </xsl:template> |
The new template for the <zw>
element is
multilingual and enables nesting. The element has two attributes:
lang
and notlang
. Both of them may contain
a list of language codes separated by spaces or commas. The
notlang
attribute specifies the list of languages in
which the contents must not appear, the lang
attribute
selects languages in which the contents should appear. it makes little
sense to use both attributes together but if it happens the
notlang
attribute has higher priority. If both
attributes are missing, the contents of the <zw>
element is treated as a comment which may contain any well formed
markup including comments. The template must work identically in all
modes.
<xsl:template match="zw" mode="#all"> <xsl:choose> <xsl:when test="not(@lang) and not(@notlang)"/> <xsl:when test="contains(@notlang, $DocumentLanguage)"> <xsl:apply-templates select="zw" mode="#current"/> </xsl:when> <xsl:when test="contains(@lang, $DocumentLanguage) or not(@lang)"> <xsl:apply-templates mode="#current"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="zw" mode="#current"/> </xsl:otherwise> </xsl:choose> </xsl:template> |
The WWW browser will select the language version by means
of content negotiation implemented in a PHP script. Thus we must
generate an index script for each document. We will do it with a
simple stylesheet which processes the root node only. Notice that the
PHP code is generated as text. The comment defines the place where the
ISP puts an advertisement banner. It appears afer the program code so
that the banner does not appear twice. As you will see later, the
advertisement banner will be added as a part of processing the
<document>
element.
mode = index |
---|
<xsl:output method="text" indent="yes" encoding="utf-8"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <zw-text><?php require 'engine.php'; if ($inc) { include $fn; } else virtual($fn); exit; ?> <!--WZ-REKLAMA-1.0--> </zw-text> </xsl:template> |
The output method is html
and we request
indentation. Language selection requires knowledge of the code that will be specified on
the command line.
<xsl:output method="html" indent="yes" encoding="utf-8"/> <xsl:param name="DocumentLanguage"/> |
The previous version made use of two tiny stylesheets
which only set parameters of the respective language and included the
main file by element
<xsl:import href="xml2html.xsl"/>
.
The XML document contains a lot of spaces which would
spoil formatting of the resulting document, mainly the code listing of
the stylesheet. Therefore we will ignore all spaces. We must, however,
preserve spaces in the <xsl:text>
element as well as
some other inline elements.
<xsl:strip-space elements="*"/> <xsl:preserve-space elements="xsl:text p i b li dt dd zw pre"/> |
During further processing we will need the document name
without directories and an extension. We know that the source document
has always the .xml
extension, therefore we store
everything between the last slash and this extension into the global
variable basename
.
<xsl:variable name="basename"> <xsl:analyze-string select="saxon:systemId()" regex="[^/]+\.xml$"> <xsl:matching-substring> <xsl:value-of select="."/> </xsl:matching-substring> </xsl:analyze-string> </xsl:variable> |
Version for XSLT 1.0 contained:
Obsolete code |
---|
<xsl:variable name="basename" select="substring-before(saxon:tokenize(saxon:systemId(),'/')[last()],'.xml')"/> |
All pages will refer to the main page and the cascading
stylesheet. The pages should be independet of their location so that
it is possible to test them locally. The URL must therefore be
relative. It it thus necessary to determine the number of back steps.
The base directory with the XML documents is .../xml/
. We
will therefore obtain the system identifier of the currently processed
document and take everything after this string. We split it into parts
by slashes. The directories will then be replaced with two dots and
the result will be stored in global variable
backdir
.
<xsl:variable name="backdir"> <xsl:analyze-string select="substring-after(saxon:systemId(), "/xml/")" regex="[^/]+/"> <xsl:matching-substring> <xsl:text>../</xsl:text> </xsl:matching-substring> </xsl:analyze-string> </xsl:variable> |
Here is the code used in the old version:
Obsolete code |
---|
<xsl:variable name="backdir"> <xsl:variable name="ntoks" select="count(saxon:tokenize(substring-after(saxon:systemId(), '/xml/'), '/'))"/> <xsl:if test="$ntoks > 1"> <xsl:for-each select="saxon:range(2, $ntoks)"> <xsl:text>../</xsl:text> </xsl:for-each> </xsl:if> </xsl:variable> |
The link to the home page is either the contents of the
$backdir
variable or ./
.
<xsl:variable name="home"> <xsl:choose> <xsl:when test="$backdir = ''"> <xsl:text>./</xsl:text> </xsl:when> <xsl:otherwise> <xsl:value-of select="$backdir"/> </xsl:otherwise> </xsl:choose> </xsl:variable> |
Finally we store the text "Main page" in the respective
language. The file extension will always be .php
.
<xsl:variable name="mainpage"> <xsl:choose> <xsl:when test="$DocumentLanguage = 'cs'"> <xsl:text>Hlavní stránka</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage = 'hi'"> <xsl:text>मुख्य पृष्ठ</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Main page</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="htmlext"> <xsl:text>.php</xsl:text> </xsl:variable> |
Now the document can be processed. The template will make
use of a simple trick. The root element in our format must be
<document>
. Verification that the document
satisfies this condition is easy. The template will specify that other
templates should be applied to the <document>
element. If the root element is different, the resulting page will
contain only empty <html>
element which will be
easily noticed. The processing instruction which is being generated at
the beginning of the file is used for inputting the code for language selection by the
method described in the preceding section.
<xsl:template match="/"> <xsl:processing-instruction name="php"> if (!function_exists('FindLanguage')) { require 'functions.php'; EmitHeader(basename($_SERVER['SCRIPT_NAME'])); } Expires(); ?</xsl:processing-instruction> <html> <xsl:apply-templates select="document"/> </html> </xsl:template> |
Offline manual is generated from some pages. Part of the contents makes sense on the web pages only, other part is useful for the manual only. We therefore build two templates which make possible to enter alternatives. it is assumed that they will be redefined in the stylesheet for generating the manual.
<xsl:template match="zw-online"> <xsl:apply-templates/> </xsl:template> <xsl:template match="zw-offline"/> |
These elements must not contain elements
<section>
and <title>
, neither
directly nor indirectly. In case of element <title>
the solution is easy, switches <zw-online>
and
<zw-offline>
can appear inside. For use with the
<section>
element the role
attribute
is prepared. This will be explained in sections "Sectioning"
a "Templates for table of contents". Here we provide a variable containing the name
of the role to be ignored.
<xsl:variable name="ignore-role"> offline</xsl:variable> |
XPath contains a device for determining current date and time but cannot find the date and time of file modification. In the old version of the stylesheet we used an external program which scanned the tree of the source documents and created an auxilliary file with required information. The name of the external file was entered as a parameter and its document node was stored in a variable.
Obsolete code |
---|
<xsl:param name="FileInfo" required="yes"/> <xsl:variable name="filenode" select="document($FileInfo)/zw-fileinfo"/> |
The above mentioned file contained only empty elements
<zw-file>
that had two attributes. The
name
attribute contained the file name, the
lastmod
attribute was the date and time of the last
modification. The date and time were separated by a nonbreakable
space. We defined a key for more efficient use.
Obsolete code |
---|
<xsl:key name="flastmod" match="zw-file" use="@name"/> |
This template used to return modification date and time of a file given as a parameter. As default it returned modification date and time of the file containing the context node.
Obsolete code |
---|
<xsl:template name="flastmod"> <xsl:param name="file"> <xsl:value-of select="saxon:system-id()"/> </xsl:param> <xsl:for-each select="$filenode"> <xsl:value-of select="key("flastmod", $file)/@lastmod"/> </xsl:for-each> </xsl:template> |
The <zw-datetime>
element will write the
modification date and time of a file making use of the package with the Saxon extension
function. The file name is taken from the file
attribute, or, in case of XML files, from the xmlfile
attribute. If none of these attributes is given, the template will
create the modification date and time of the file containing the
context node. The template will be named so that it can be used
later.
<xsl:template match="zw-datetime" name="zw-datetime"> <xsl:choose use-when="function-available("zw:last-modified")"> <xsl:when test="@file"> <xsl:value-of select="zw:file-timestamp(@file)"/> </xsl:when> <xsl:when test="@xmlfile"> <xsl:value-of select="zw:last-modified(document(@xmlfile))"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="zw:last-modified()"/> </xsl:otherwise> </xsl:choose> </xsl:template> |
Modificaton date will be obtained by stripping time from the value determined by means of the preceding template.
<xsl:template match="zw-date"> <xsl:variable name="datetime"> <xsl:call-template name="zw-datetime"/> </xsl:variable> <xsl:value-of select="substring-before($datetime, "T")"/> </xsl:template> |
We perform three actions during processing the document.
first, inside the header, we generate the page title which will be
dispayed by the browser. In the second step we create the link to the
cascading stylesheet. Finally we process the body of the document.
Each of these steps will be executed by a separate template which will
be invoked by <xsl:call-template>
. We do it purely for
the reason of clarity because each of these templates is used in one
place only.
<xsl:template match="document"> <head> <meta name="google-site-verification" content="pQX0Li9frYMCGMBkoPTHoG3lgeK_Gx9i-BO8-jcjn18"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="http-equiv" content="{concat('Content-Language: ', $DocumentLanguage)}"/> <xsl:call-template name="doc-title"/> <xsl:if test="@css"> <xsl:call-template name="doc-css"/> </xsl:if> <xsl:if test="@redirect"> <meta http-equiv="refresh" content="{concat('1; ', @redirect)}"/> </xsl:if> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-98691706-1', 'auto'); ga('send', 'pageview'); </script> </head> <xsl:call-template name="doc-body"/> </xsl:template> |
The title which should be displayed on the titlebar will
be taken from the <title>
element. This element is,
however, primarily used as a page title which may not be suitable for
the titlebar. We would therefore prefer the
<titlebar>
element if it exists. Notice the
mode='titlebar'
attribute in the
<xsl:apply-templates>
element. Its meaning will be
explained later.
This is also a good place for insertion of othe contents
into the <head>
element. We will insert the contents
of all elements of that name.
<xsl:template name="doc-title"> <title> <xsl:choose> <xsl:when test="count(titlebar)>0"> <xsl:apply-templates select="titlebar" mode="titlebar"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="title" mode="titlebar"/> </xsl:otherwise> </xsl:choose> </title> <xsl:apply-templates select="head" mode="title"/> </xsl:template> |
Processing of the document body is easy. First we copy all
attributes of the <document>
element except of
top
and css
. Then we display the title. We
attach label top
to it so that we can later generate the
links to the top of the page. The template for processing the
<title>
element is called with the
mode='title'
attribute. Afterwards templates are
applied to all elements. We must, however, send them the
hdr
parameter which will be used in the template for the
<section>
element. If the value of the
top
attribute is positive, we also call the template for generating the link to the top of
the page which will be defined later. The bottom of the page
will hold a standard footer.
<xsl:template name="doc-body"> <body> <xsl:copy-of select="@* except (@top|@css)"/> <h1 id="top"> <xsl:apply-templates select="title" mode="title"/> </h1> <xsl:apply-templates> <xsl:with-param name="hdr" select="2"/> </xsl:apply-templates> <xsl:if test="@top > 0"> <xsl:call-template name="top-link"/> </xsl:if> <xsl:call-template name="footer"/> </body> </xsl:template> |
First we leave a little empty space, otherwise the
optional link to the top of the page could collide with the footer.
Afterwards we make sure whether we are not on the main page. In such a
case we would display just a horizontal line. If we are not in the
main directory, we call a template for adding links defined in the
<zw-a>
elements. Afterwards we put the link to the
main document of the directory. Its name will be retrieved from the
<title>
element processed in the
titlebar
mode. Further we put the copyright and the date
of the last modification making use of already defined template. At
the very end of the page we insert the links to the other language
versions in order not to confuse indexing robots. The cascading
stylesheet will move them to the top of the page. The comment
<!--WZ-REKLAMA-1.0-->
specifies a location where the server automatically
inserts an advertisement banner. It can be followed by another
advertisement which is read from a document the URL of which is
defined in the reklama
attribute.
<xsl:template name="footer"> <br/> <br/> <br/> <xsl:variable name="urlbase" select="concat($backdir, $basename)"/> <xsl:choose> <xsl:when test="$urlbase = 'index.xml'"> <hr/> </xsl:when> <xsl:otherwise> <div class="links"> <xsl:call-template name="other-links"/> <xsl:if test="starts-with($urlbase, '../') and $basename != 'index.xml'"> <a href="./"> <xsl:attribute name="title"> <xsl:choose> <xsl:when test="count(document('index.xml', /)/document/titlebar) > 0"> <xsl:apply-templates select="document("index.xml", /)/document/titlebar" mode="titlebar"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="document("index.xml", /)/document/title" mode="titlebar"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:apply-templates select="document("index.xml", /)/document/title" mode="titlebar"/> </a> <br/> </xsl:if> <a title="{$mainpage}" href="{$home}"> <xsl:value-of select="$mainpage"/> </a> </div> </xsl:otherwise> </xsl:choose> <div id="copyright"> <xsl:text>© Z. Wagner - Ice Bear Soft, </xsl:text> <xsl:value-of select="zw:last-modified()" use-when="function-available("zw:last-modified")"/> </div> <xsl:text> </xsl:text> <xsl:processing-instruction name="php"> LangLinks(); Counter(); ?</xsl:processing-instruction> <xsl:comment>WZ-REKLAMA-1.0</xsl:comment> <xsl:if test="@reklama"> <hr/> <xsl:apply-templates select="document(@reklama)" mode="include"/> </xsl:if> </xsl:template> |
This template inserts the contents of all
<zw-a>
elements processed in the footer
mode.
<xsl:template name="other-links"> <xsl:apply-templates select="//zw-a" mode="footer"/> </xsl:template> |
The <zw-a>
element has almost the same
syntax as the <a>
element. The only difference is
that the title
attribute of the <a>
element can be given in the <title>
element so that
we can enter several language versions. In the footer
mode the <zw-a>
element will therefore be replaced
with the <a>
element followed by the line break, in
the default mode it will be ignored. However, if the contents of the
<zw-a>
element is empty, we fill it in together
with the title
attribute from the title of the
referenced document. When inserting the link we take into account the language version
according to the attributes lang
and notlang
.
<xsl:template match="zw-a" mode="footer"> <xsl:choose> <xsl:when test="@notlang eq $DocumentLanguage"/> <xsl:when test="@lang ne '' and @lang ne $DocumentLanguage"/> <xsl:otherwise> <xsl:call-template name="zw-a"/> <br/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="zw-a"> <a> <xsl:copy-of select="@*"/> <xsl:call-template name="zw-a-content"/> </a> </xsl:template> <xsl:template match="zw-a"/> <xsl:template name="zw-a-content"> <xsl:choose> <xsl:when test="count(*) > 0 or string-length(.) > 0"> <xsl:if test="title"> <xsl:attribute name="title"> <xsl:apply-templates select="title" mode="titlebar"/> </xsl:attribute> </xsl:if> <xsl:apply-templates select="*|text() except title"/> </xsl:when> <xsl:otherwise> <xsl:choose> <xsl:when test="contains(@href, "#")"> <xsl:variable name="xml" as="xs:string" select="if (starts-with(@href, "#")) then @href else replace(replace(@href, "/#", "/index.php#"), "\.php#", ".xml#")"/> <xsl:variable name="root" select="if (starts-with(@href, "#")) then (/) else document(substring-before($xml, "#"), /)"/> <xsl:variable name="title" as="element()" select="$root//section[@label=substring-after($xml,"#")]/title"/> <xsl:attribute name="title"> <xsl:apply-templates select="$title" mode="titlebar"/> </xsl:attribute> <xsl:apply-templates select="$title" mode="title"/> </xsl:when> <xsl:otherwise> <xsl:variable name="xml" as="xs:string" select="replace(replace(@href, "/$", "/index.php"), "\.php$", ".xml")"/> <xsl:attribute name="title"> <xsl:choose> <xsl:when test="count(document($xml, /)/document/titlebar) > 0"> <xsl:apply-templates select="document($xml, /)/document/titlebar" mode="titlebar"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="document($xml, /)/document/title" mode="titlebar"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:apply-templates select="document($xml, /)/document/title" mode="nologo"/> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> |
In the preceding templates we have explicitly called
templates for the <title>
,
<titlebar>
and <head>
elements.
These elements must be ignored when procesing the document body. We
therefore create two templates. The first of them will contain the
mode='title titlebar'
attribute. The template will
process nested elements in the same mode because the templates for
nested elements may also differ. The second template which will be
invoked in the standard mode will be empty so that the elements will
be ignored.
<xsl:template match="title|titlebar|head" mode="title titlebar nologo"> <xsl:apply-templates mode="#current"/> </xsl:template> <xsl:template match="title|titlebar|head"/> |
Creation of the link to the cascading stylesheet is more
involved. If the css
attribute of the
<document>
element contains a URL, it is simple. The
problem arises if it containt a period instead of the URL. We then
must evaluate the name and location of the standard stylesheet. The
URL must be relative so that the pages do not depend upon particular
location and can be tested locally. We must therefore insert correct
number of back steps from the global backdir
variable and
add the stylesheet name.
<xsl:template name="doc-css"> <xsl:for-each select="tokenize(@css, '\s+')"> <xsl:variable name="css"> <xsl:choose> <xsl:when test=".='.'"> <xsl:value-of select="$backdir"/> <xsl:text>css/style.css</xsl:text> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> </xsl:otherwise> </xsl:choose> </xsl:variable> <link rel="stylesheet" href="{$css}" type="text/css"/> </xsl:for-each> <xsl:variable name="favicon" select="concat($backdir, 'images/favicon.ico')"/> <link rel="shortcut icon" type="image/x-icon" href="{$favicon}"/> </xsl:template> |
We will need the number of a section in several templates.
We herefore create a template which will be reused. The
<xsl:number>
element will number the sections
hierarchically if the level='multiple'
attribute is
used.
<xsl:template name="secnum"> <xsl:number level="multiple" format="1." count="section[not(@role) or @role != $ignore-role]"/> </xsl:template> |
Similarly we create a reusable template for generating a
label. If we wish to refer to a section from another place, we must
spedify the label in the label
attribute of the
<section>
element. If we require the label in the
automatically generated table of contents, we let the template create
a unique identifier.
<xsl:template name="seclabel"> <xsl:choose> <xsl:when test="@label"> <xsl:value-of select="@label"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="generate-id()"/> </xsl:otherwise> </xsl:choose> </xsl:template> |
The contents of the element will be processed only if it
is not prohibitted by the role
attribute (see Templates for a manual). First we store the label value to the
seclabel
variable so that we could work with it more
comfortably. Afterwards we have to create the element contaning the
title. The title level can be obtained from the hdr
parameter. Remember that the template for the
<document>
element set the value of this parameter
to 2 when calling other templates. The
<section>
element of the highest level will thus have
its title in the <h2>
element. The title will have a
unique label and will be automatically numbered. Notice that the
template for the <title>
element is again called in
the title
mode. When calling the templates for other
elements the value of the hdr
parameter must be
incremented so that nested sections use correct elements for their
titles. The root element <document>
may contain the
top
attribute specifying the deepest level of the title
of the <section>
element where the link to the
beginning of the page should be generated. The title level is stored
in the value of the hdr
parameter. Several nested
sections may end at the same place. In such a case we would get
several consecutive links to the top of the page. We therefore apply
the count(following-sibling::node()) > 0
to ensure
that we have some nodes after the end of section. White spaces are
ignored because we have specfied <xsl:strip-space>
everywhere but in the <xsl:text>
element.
<xsl:template match="section"> <xsl:param name="hdr"/> <xsl:if test="not(@role) or @role != $ignore-role"> <xsl:variable name="label"> <xsl:call-template name="seclabel"/> </xsl:variable> <xsl:element name="{concat('h', string($hdr))}"> <xsl:attribute name="id" select="$label"/> <xsl:call-template name="secnum"/> <xsl:text> </xsl:text> <xsl:apply-templates select="title" mode="title"/> </xsl:element> <xsl:apply-templates> <xsl:with-param name="hdr" select="$hdr + 1"/> </xsl:apply-templates> <xsl:if test="/document/@top>=$hdr and count(following-sibling::element()[name()!='zw-a'] except following-sibling::section[@role=$ignore-role]/descendant-or-self::element())>0"> <xsl:call-template name="top-link"/> </xsl:if> </xsl:if> </xsl:template> |
The title may contain a hyperlink which should be preserved. We will
thus make use of a template for <zw-a>
which works in the required
mode.
<xsl:template match="a" mode="title"> <xsl:call-template name="zw-a"/> </xsl:template> |
The link to the top of the page will be placed ino a
right-justified one-cell framed table the background of which will
be coloured by the cascading stylesheet. We cannot colour directly
the background of the <div>
element because it
would lead to a colour stripe across the whole wodth of the browser
window.
<xsl:template name="top-link"> <xsl:variable name="top"> <xsl:text> </xsl:text> <xsl:call-template name="top"/> <xsl:text> </xsl:text> </xsl:variable> <div> <table class="top" align="right" border="1" frame="box" rules="none"> <tr> <td> <a href="#top" title="{$top}"> <xsl:value-of select="$top"/> </a> </td> </tr> </table> </div> </xsl:template> |
The text which will be displayed in the link to the top of the page will be created for clarity in a separate template.
<xsl:template name="top"> <xsl:choose> <xsl:when test="$DocumentLanguage='cs'"> <xsl:text>Začátek stránky</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage='en'"> <xsl:text>Top of the page</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Top of the page</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:template> |
First we will describe an auxiliary template. Notice that
the template has the match
atribute, so that it could be
used for processing the <toc-heading>
element, as
well as the name
attribute, so that it could be called by
name from another template.
<xsl:template name="toc-heading" match="toc-heading"> <p> <b> <xsl:choose> <xsl:when test="$DocumentLanguage='cs'"> <xsl:text>Obsah</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage='en'"> <xsl:text>Contents</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage='hi'"> <xsl:text>विषय-सूची</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Contents</xsl:text> </xsl:otherwise> </xsl:choose> </b> </p> </xsl:template> |
The <toc>
element serves for displaying
the table of contents both at the beginning of the page and inside a
particular section. However, if the section does not contain any
nested subsections, the element should be ignored. We must therefore
first find whether the element, in which <toc>
appears, contains any sections. It could be easily verified by the
count(../section) > 0
condition. We will,
however, need also the maximum nesting level. We determine it by means
of an XPath 2.0 expression. The for
loop will
create a sequence of all <section>
elements which do
not contain any other nested section and count the numbers of their
ancestors. We also added the root node so that the sequence was not
empty if the current section did not contain any nested section. On
the other hand we removed sections which have to be ignored (see Templates for a manual). We find the maximum number of ancestors and
subtract the number of ancestors of the current section.If the nesting
level is positive, the table of contents will be displayed. First we
process the contents of the <toc>
element. If the
contents of the element is empty and the value of the
heading
attribute is yes
, we call the
toc-heading
template. Finally we apply templates to the
paren element <section>
. Notice that the templates
will be applied in the toc
mode and the value of the
depth
variable is supplied in the span
parameter.
<xsl:template match="toc"> <xsl:variable name="depth"> <xsl:value-of select="max(for $s in ((..//section[not(section)]|/) except (//element()[role=$ignore-role]/descendant-or-self::element())) return count($s/ancestor::element())) - count(../ancestor::element())"/> </xsl:variable> <xsl:if test="$depth > 0"> <xsl:choose> <xsl:when test="count(*) > 0"> <xsl:apply-templates/> </xsl:when> <xsl:when test="@heading='yes'"> <xsl:call-template name="toc-heading"/> </xsl:when> </xsl:choose> <table border="0" frame="none" rules="none" class="toc"> <xsl:apply-templates select="../section" mode="toc"> <xsl:with-param name="span" select="$depth"/> </xsl:apply-templates> </table> </xsl:if> </xsl:template> |
Old method made use of XPath 1.0 and Saxon
extension. We began with the ../section
expression and
step by step appended /section
within a
<saxon:while>
loop while incrementing the
depth
variable. In these days offline manuals were not
automatically generated, therefore the <section>
element did not have the role
attribute.
Obsolete code |
---|
<xsl:variable name="depth" select="0" saxon:assignable="yes"/> <xsl:variable name="path" saxon:assignable="yes"> <xsl:text>../section</xsl:text> </xsl:variable> <saxon:while test="count(saxon:evaluate($path)) > 0"> <saxon:assign name="depth" select="$depth + 1"/> <saxon:assign name="path"> <xsl:value-of select="concat($path, '/section')"/> </saxon:assign> </saxon:while> |
Now we are going to explain the code which inserts
information to the table of contents. If there are nested sections, we
would like to have them properly indented. It could be reached by a
listing environment in HTML. Unfortunaly, <ol>
will
number the first level, mark the second level with letters and will
lose hierarchical numbering. It could be solved by a cascading
stylesheet, bat as was already mentioned, cascading stylesheets are
nor correctly implemented in all browsers and are even not implemented
at all in some of them. We must therefore generate the table of
contents in such a way that it is viewable even in a browser which
does not support cascading stylesheets. The preceding template thus
started a table for the contents. In order to indent the titles of the
nested sections we must insert an empty cell spanning the correct
number of column at the beginning of the row. The next column contains
the right justified number and finally we put the section title
spanning over the remaining columns. The title is retrieved fron the
<title>
element but its template must be invoked in
the toc
mode. The number as well as the label will be
obtained by templates which have already been used and explained.
At the first call the span
parameter got the
value of the maximum nesting, the indent
parameter had
the default zero value. After the row is written, we process a nested
<section>
element whence indent
is
increased and span
is decreased. Recursion will continue
to the point when we will process a nested section with a zero value
of the span
parameter. The table will then go haywire.
Fortunately this cannot happen. We have calculated the maximum nesting
level in advance and thus we know that such a section does not exist
in the document. It is not necessary to use a condition inside the
template because <xsl:apply-templates>
finds no element
for the zero value of the span
parameter.
Similarly as in section "Sectioning" we process
only elements not prohibited by the role
attribute.
<xsl:template match="section" mode="toc"> <xsl:param name="indent" select="0"/> <xsl:param name="span" select="-1"/> <xsl:if test="not(@role) or @role != $ignore-role"> <xsl:variable name="label"> <xsl:call-template name="seclabel"/> </xsl:variable> <tr> <xsl:if test="$indent > 0"> <td> <xsl:if test="$indent > 1"> <xsl:attribute name="colspan"> <xsl:value-of select="$indent"/> </xsl:attribute> </xsl:if> <xsl:text> </xsl:text> </td> </xsl:if> <td align="right"> <a href="{concat('#', $label)}"> <xsl:call-template name="secnum"/> </a> </td> <td> <xsl:if test="$span > 1"> <xsl:attribute name="colspan"> <xsl:value-of select="$span"/> </xsl:attribute> </xsl:if> <a href="{concat('#', $label)}"> <xsl:apply-templates select="title" mode="toc"/> </a> </td> </tr> <xsl:apply-templates select="section" mode="toc"> <xsl:with-param name="indent" select="$indent + 1"/> <xsl:with-param name="span" select="$span - 1"/> </xsl:apply-templates> </xsl:if> </xsl:template> |
Hyperlinks will be ignored in the toc
mode,
only their contents will be preserved.
<xsl:template match="a" mode="toc"> <xsl:call-template name="zw-a-content"/> </xsl:template> |
All pages should clearly identify to whom they belong. We will therefore wish to have a logo with a hyperlink to the main page in the upper right corner. We will create a useful template.
<xsl:template match="zw-logo" mode="#default title"> <a title="{$mainpage}" href="{$home}"> <img src="{concat($backdir, 'images/ibslogo1.gif')}" width="175" height="45" alt="Ice Bear Soft" align="right" border="0"/> </a> </xsl:template> |
The <zw-logo>
element will be used at the
beginning of the <title>
element. We will therefore
ignore it in the titlebar
mode.
<xsl:template match="zw-logo" mode="titlebar nologo"/> |
We often refer to our own documents and we would like to
let the title
attribute as well as the text fill in from
the title of the corresponding document. The <a>
element will therefore be processed by the template used previously
for the <zw-a>
element in the footer
mode. Auxiliary element
<zw-label>
will be handled the same way.
<xsl:template match="a|zw-label"> <xsl:call-template name="zw-a"/> </xsl:template> |
Some images will be displayed in a separate window using an external JavaScript function. Code for its invocation will be generated in PHP. The template will therefore output a processing instruction.
<xsl:template match="zw-jsimg"> <xsl:processing-instruction name="php"><xsl:text>jsimg('</xsl:text> <xsl:value-of select="@src"/><xsl:text>', '</xsl:text> <xsl:value-of select="@align"/><xsl:text>');</xsl:text> ?</xsl:processing-instruction> </xsl:template> |
The jsimg
function relies on images.js and contains:
# JavaScript images function jsimg($src, $align) { if ($align) $align = "align=\"$align\""; $sz = GetImageSize($src); $thsrc = ''; if (preg_match('/^(.+)(\.[^.]+)$/', $src, $parts)) { $thsrc = $parts[1] . '_small' . $parts[2]; $thsz = GetImageSize($thsrc); ?> <script language="JavaScript"> <!-- <?php printf("img('%s', %d, %d, '%s', %d, %d, '%s')", $thsrc, $thsz[0], $thsz[1], $src, $sz[0], $sz[1], $align); ?> // --> </script> <noscript> <a href="<?php echo $src; ?>" target="image"> <img src="<?php echo $thsrc; ?>" hspace=5 vspace=1 width=<?php echo $thsz[0]; ?> height=<?php printf('%d %s', $thsz[1], $align); ?>> </a></noscript> <?php }} |
We sometimes need to write a source text which should be
output as a comment. We thes create a <zw-comment>
element. It can be useful for instance for generation of the server
side includes directives.
<xsl:template match="zw-comment"> <xsl:comment><xsl:apply-templates/></xsl:comment> </xsl:template> |
We do not wish to display some elements for security
reasons. We will therefore enclose them into the
<zw-hide>
element and supply an alternative text in
the alt
attribute.
<xsl:template match="zw-hide"> <span class="hide"> <xsl:value-of select="@alt"/> </span> </xsl:template> |
We sometimes wish to attach some special information to
the part of a document but do not like to make use of a comment. It
would be useful to have the possibility of marking a part of a
document in a simple way so that we could decide whether the contents
should appear in the HTML page. We will therefore define the
<zw-info>
element which can have arbitrary
attributes with arbitrary contents. If the include
attribute has the yes
value, the contents of the element
will be processed. If the value is different or the attribute is
missing, the contents will be ignored.
<xsl:template match="zw-info"> <xsl:if test="@include = "yes""> <xsl:apply-templates/> </xsl:if> </xsl:template> |
PHP Processing instructions will be just copied but with appended question mark at the end.
<xsl:template match="processing-instruction("php")"> <xsl:processing-instruction name="php"><xsl:value-of select="."/><xsl:text>?</xsl:text> </xsl:processing-instruction> </xsl:template> |
Other processing instructions will be ignored.
<xsl:template match="processing-instruction()" mode="#all"/> |
All other element without explicit templates will be just
copied with their attributes. We do not wish to use
<xsl:copy>
because the element would contain the
declarations of all namespaces. The same action will be performed both
in the standard and title
modes.
<xsl:template match="*" mode="#default title nologo"> <xsl:element name="{name()}"> <xsl:apply-templates select="@*"/> <xsl:apply-templates mode="#current"/> </xsl:element> </xsl:template> |
The attributes can be easily copied by means of
<xsl:copy-of>
.
<xsl:template match="@*"> <xsl:copy-of select="."/> </xsl:template> |
Some templates will include whole documents but the
headers must not be generated. We will therefore make use of the
include
mode.
<xsl:template match="/" mode="include"> <xsl:apply-templates/> </xsl:template> |
Finally we show the link to the pages with the protest against SW patents.
<xsl:template match="zw-nopatents" mode="#default title"> <xsl:variable name="title"> <xsl:choose> <xsl:when test="$DocumentLanguage = "cs""> <xsl:text>Žádné softwarové patenty!</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>No software patents!</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:variable> <a href="http://noepatents.eu.org" title="{$title}"> <img width="480" height="60" src="{concat($backdir, 'images/swpatbanner.en.png')}"> <xsl:copy-of select="@*"/> </img> </a> </xsl:template> |
In this document we often mention some elements. It would
be uncomfortable to write the <code>
elements with
the angle brackets encoded as entities <
and
>
and close it with the </code>
tag. We therefore create an auxiliary template
<zw-elem>
for any element. The element can
optionally have a q
attribute the only meaningful value
of which is a slash. Letter q
was chosen because it is at
the edge of the keyboard.
<xsl:template match="zw-elem" mode="#default title toc nologo"> <xsl:call-template name="zw-elem"> <xsl:with-param name="prefix"> <xsl:value-of select="@q"/> </xsl:with-param> </xsl:call-template> </xsl:template> |
We also often mention elements with the xsl:
prefix. It would be unconfortable to write it explicitly, we therefore
create an <zw-xelem>
element which will do the job
for us.
<xsl:template match="zw-xelem" mode="#default title toc nologo"> <xsl:call-template name="zw-elem"> <xsl:with-param name="prefix"> <xsl:value-of select="@q"/> <xsl:text>xsl:</xsl:text> </xsl:with-param> </xsl:call-template> </xsl:template> |
The whole job is done by the following template. The
preceding templates send the prefix
parameter which can
contain a slash and/or the xsl;
prefix.
<xsl:template name="zw-elem"> <xsl:param name="prefix"/> <code> <xsl:text><</xsl:text> <xsl:value-of select="$prefix"/> <xsl:apply-templates/> <xsl:text>></xsl:text> </code> </xsl:template> |
The stylesheet for bootstraping generated a stylesheet for transformation into HTML from the code
of the <zw-pre>
elements. We now display the contents
of these elements as the code samples. The cascading stylesheet will
define their yellow background. Under some circumstances the lines may
exceed the width of the browser window. The
background-color
property then does not work correctly.
It seems that the background of a table is colored always correctly.
The contents of the <zw-pre>
element is thus
displayed in a one-row one-column table. The browser which does not
support cascading stylesheets will at least frame it. Only in case
that the <zw-pre>
element has the lang
attribute a title is added to the table.
Now we cannot use the <pre>
element in
the document because formatting of this example would turn into
garbage. Instead of it we introduce a <verbatim>
synonym. This new element differs from <zw-pre>
mainly because its contents will not appear in the generated
stylesheet. The line edn inserted at several places is just for
aesthetics. The only important thing is that nested elements are
processed in the verbatim
mode.
<xsl:template match="zw-pre|verbatim" name="verbatim"> <xsl:text> </xsl:text> <table border="2" frame="box" rules="rows" cellpadding="5" class="pre"> <xsl:if test="@mode"> <tr> <th> <xsl:choose> <xsl:when test="@mode="old""> <xsl:choose> <xsl:when test="$DocumentLanguage='cs'"> <xsl:text>Zastaralý kód</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage='en'"> <xsl:text>Obsolete code</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Obsolete code</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:text>mode = </xsl:text> <xsl:value-of select="@mode"/> </xsl:otherwise> </xsl:choose> </th> </tr> </xsl:if> <tr> <td> <pre> <xsl:if test="name() != 'verbatim'"> <xsl:text> </xsl:text> </xsl:if> <xsl:apply-templates mode="verbatim"/> </pre> </td> </tr> </table> <xsl:text> </xsl:text> </xsl:template> |
Now we process all elements. Since HTML requires all angle
brackets be converted to character entities, the XSLT processor will
not consider then as elements and indentation must be therefore
inserted by us. The value of indentation is stored in the
indent
parameter. Its value is initially empty. The
element name is written after the entity representing the opening
bracket and afterwards we call a template for attributes, of course in
the verbatim
mode. If the element is empty, we close i by
a slash and a closing bracket, otherwise we call a template for nested
elements with increased indentation. Finally we output a closing
tag.
<xsl:template match="*" mode="verbatim"> <xsl:param name="indent"/> <xsl:param name="amount"> <xsl:text> </xsl:text> </xsl:param> <xsl:param name="endline"> <xsl:text> </xsl:text> </xsl:param> <xsl:value-of select="$indent"/> <xsl:text><</xsl:text> <xsl:value-of select="name()"/> <xsl:apply-templates select="@*" mode="verbatim"/> <xsl:choose> <xsl:when test="count(*)=0 and string-length(.)=0"> <xsl:text>/></xsl:text> <xsl:value-of select="$endline"/> </xsl:when> <xsl:otherwise> <xsl:text>> </xsl:text> <xsl:apply-templates mode="verbatim"> <xsl:with-param name="indent"> <xsl:value-of select="concat($indent, $amount)"/> </xsl:with-param> </xsl:apply-templates> <xsl:value-of select="$indent"/> <xsl:text></</xsl:text> <xsl:value-of select="name()"/> <xsl:text>></xsl:text> <xsl:value-of select="$endline"/> </xsl:otherwise> </xsl:choose> </xsl:template> |
the contents of the <pre>
element will be
displayed analogically but the original formatting will be preserved.
The element is listed in
<xsl:preserve-spaces>
.
<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> |
The templates for <xsl:text>
and
<zw-text>
elements, for
<xsl:processing-instruction>
and
<xsl:comment>
are slightly different. We want to keep
the element with its contents on a single line.
<xsl:template match="xsl:text|zw-text|xsl:comment|xsl:processing-instruction" mode="verbatim"> <xsl:param name="indent"/> <xsl:value-of select="$indent"/> <xsl:text><</xsl:text> <xsl:value-of select="name()"/> <xsl:apply-templates select="@*" mode="verbatim"/> <xsl:text>></xsl:text> <xsl:choose> <xsl:when test="count(*)=0"> <xsl:value-of select="."/> </xsl:when> <xsl:otherwise> <xsl:apply-templates mode="verbatim"> <xsl:with-param name="endline"/> </xsl:apply-templates> </xsl:otherwise> </xsl:choose> <xsl:text></</xsl:text> <xsl:value-of select="name()"/> <xsl:text>> </xsl:text> </xsl:template> |
Processing of the attributes is easy. We write the attribute name, equal sign and the value in quotes.
<xsl:template match="@*" mode="verbatim"> <xsl:text> </xsl:text> <xsl:value-of select="name()"/> <xsl:text>="</xsl:text> <xsl:value-of select="normalize-space(.)"/> <xsl:text>"</xsl:text> </xsl:template> |
We request normalization of spaces in text nodes.
<xsl:template match="text()" mode="verbatim"> <xsl:value-of select="normalize-space(.)"/> </xsl:template> |
The relative path to the bootstraping stylesheet is
defined in the document as an extern entity named
bootstrap
. The stylesheet is read in the Bootstrapping section by
using &bootstrap;
. Its root element is
<xsl:stylesheet>
. The following template will process
the file contents by means of the same template as was used for
<zw-pre>
.
Notice that the root element will not appear in the
listing. It is the only element which was written in the manual by
hand.
<xsl:template match="xsl:stylesheet"> <xsl:call-template name="verbatim"/> </xsl:template> |
We want to insert nonbreakable spaces after one-letter prepositions in the
texts in the Czech language. Unfortunatelly you cannot see the nonbreakable space after
$1
and it is not apparent that the last square bracket in the second
parameter of the replace
function contains
[ 
]
.
<xsl:template match="text()"> <xsl:variable name="lang" as="node()*" select="(ancestor-or-self::*/@xml:lang | ancestor-or-self::*/@lang)[last()]"/> <xsl:choose> <xsl:when test="$lang = 'cs'"> <xsl:value-of select="replace(., '([ (,][AaIiKkOoSsUuVvZz])[ ]+', '$1 ')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> </xsl:otherwise> </xsl:choose> </xsl:template> |
The nonbreakable spaces must not be inserted in verbatim-like elements.
<xsl:template match="zw-pre/text()|pre/text()|code/text()|verbatim/text()"> <xsl:value-of select="."/> </xsl:template> |
7.1. | Modified templates for manuals |
7.2. | Logo with a hyperlink for manuals |
7.3. | Processing the root element |
7.4. | Postprocessing |
7.5. | Reference to the cascading stylesheet |
7.6. | Page footer |
The stylesheet for manuals imports the stylesheet for transformation into HTML and redefines a few templates and variables.
mode = offline |
---|
<xsl:import href="xml2html.xsl"/> <xsl:variable name="ignore-role"> online</xsl:variable> |
Explanation of these templates has already appeared in section "Templates for a manual". The modified versions with exchanged roles are defined here.
mode = offline |
---|
<xsl:template match="zw-offline"> <xsl:apply-templates/> </xsl:template> <xsl:template match="zw-online"/> |
Hypertext link connected with the logo must point to the page on internet. Logo itself must be distributed with the manual so that it can be viewed offline. Its name will thus be given in a parameter.
mode = offline |
---|
<xsl:variable name="ibslogo"> ibslogo1.gif</xsl:variable> <xsl:variable name="home"> http://icebearsoft.euweb.cz/</xsl:variable> <xsl:template match="zw-logo" mode="#default title"> <a title="{$mainpage}" href="{$home}"> <img src="{$ibslogo}" width="175" height="45" alt="Ice Bear Soft" align="right" border="0"/> </a> </xsl:template> |
The manual is always pure HTML without any PHP code. We
will therefore create the <html>
element and process
the <document>
element inseide it. Hyperlinks may be
created by various mechanisms. In order to convert relative
hyperlinks, refering to other pages, into the absolute ones, we store
the result of transformation into a variable and transform it again.
We assume that images and cascading stylesheets are included so that
their references need not be modified.
mode = offline |
---|
<xsl:template match="/"> <xsl:variable name="offline-document" as="element()"> <html> <xsl:apply-templates select="document"/> </html> </xsl:variable> <xsl:apply-templates select="$offline-document" mode="offline-postprocessing"/> </xsl:template> |
The only thing to be processed is the href
attribute of the <a>
element. We will change neither
links refering to the anchors inside the same page nor absolute
links.
mode = offline |
---|
<xsl:template match="a" mode="offline-postprocessing"> <a> <xsl:copy-of select="@*"/> <xsl:if test="name(..) != 'span' and name(..) != 'div' and (not(../@id) or ../@id != 'langlinks') and not(contains(@href, '://')) and not(starts-with(@href, '#'))"> <xsl:attribute name="href"> <xsl:call-template name="postprocess-href"> <xsl:with-param name="href"> <xsl:value-of select="replace(concat($referer, @href), '/\./', '/')"/> </xsl:with-param> </xsl:call-template> </xsl:attribute> </xsl:if> <xsl:apply-templates mode="#current"/> </a> </xsl:template> |
Variable referer
contains base URL, i.e.
http://icebearsoft.euweb.cz/
followed by the part of
the file name after /xml/
up to the last slash.
mode = offline |
---|
<xsl:variable name="referer"> <xsl:text>http://icebearsoft.euweb.cz/</xsl:text> <xsl:analyze-string select="substring-after(saxon:systemId(), '/xml/')" regex="^(.*?)[^/]+$"> <xsl:matching-substring> <xsl:value-of select="regex-group(1)"/> </xsl:matching-substring> </xsl:analyze-string> </xsl:variable> |
Now we remove references to a parend directory using a recursive template.
mode = offline |
---|
<xsl:template name="postprocess-href"> <xsl:param name="href" as="xs:string" required="yes"/> <xsl:analyze-string select="$href" regex="$(.*?)/[^/]+/\.\.(/.*)$"> <xsl:matching-substring> <xsl:call-template name="postprocess-href"> <xsl:with-param name="href"> <xsl:value-of select="concat(regex-group(1), regex-group(2))"/> </xsl:with-param> </xsl:call-template> </xsl:matching-substring> <xsl:non-matching-substring> <xsl:value-of select="."/> </xsl:non-matching-substring> </xsl:analyze-string> </xsl:template> |
Other elements will be copied including their attributes.
mode = offline |
---|
<xsl:template match="*" mode="offline-postprocessing"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates mode="#current"/> </xsl:copy> </xsl:template> |
Similarly as the logo the cascading stylesheet must also be included. Its name and location will be defined in the stylesheet parameter.
mode = offline |
---|
<xsl:variable name="css"> style.css</xsl:variable> <xsl:template name="doc-css"> <link rel="stylesheet" href="{$css}" type="text/css"/> </xsl:template> |
The footer will contain just the copyright and links to language variants, nothing else will be present. Files with other language variants will be specified via a parameter where the main file name (without a hyphen and extension) will be followed by a colon and comma separated list of language codes.
mode = offline |
---|
<xsl:param name="langlinks" as="xs:string" required="yes"/> <xsl:template name="footer"> <br/> <br/> <br/> <hr/> <div id="copyright"> <xsl:text>© Z. Wagner - Ice Bear Soft, </xsl:text> <xsl:value-of select="zw:last-modified()" use-when="function-available("zw:last-modified")"/> </div> <xsl:text> </xsl:text> <xsl:if test="contains($langlinks, ':')"> <div id="langlinks"> <xsl:text> </xsl:text> <xsl:for-each select="tokenize(substring-after($langlinks, ':'), ',')"> <a href="{concat(substring-before($langlinks, ':'), '-', ., '.html')}"> <xsl:value-of select="upper-case(.)"/> </a> <xsl:text> </xsl:text> </xsl:for-each> </div> </xsl:if> </xsl:template> |
You can download generated stylesheets. You will also need a module with the Saxon extension function.
Generation of the WWW pages requires several actions which must be carried out in a correct order. Some actions need not be carried out if the source files did not change. Automation of the whole process is achieved by using Ant. There is partially bad news for OS/2 users. The starting scripts were prepared for version 1.6alpha. Several things were changed in version 1.6.1 and the scripts no longer work. You can find working scripts in version 1.6.2. You can read information about the changes in Bugzilla in bug report number 28226. Versions 1.5.x have OS/2 specific bugs, therefore you should rather not use them.
The antfile used in this project will (perhaps) be documented later. We will now show just the basic rules:
bootstrap.xsl
or webmake.xml
are modified, all xml2html*.xsl
stylesheets must be
generated.
xml2html*.xsl
stylesheets is
modified, all HTML (PHP) files must be generated.
I create the WWW pages at home on my computer with OS/2. After testing, the new and modified pages must be sent to the server via FTP. This action is automated by a PHP script. PHP must, however, run as a separate executable. The script expects four parameters:
PHP 4.0.6 for OS/2 had a bug in function
ftp_put
. It was fixed in version 4.1.0.
Here you can find:
The PHP script uses functions ftp_size
and
ftp_mdtm
which may not work on some FTP servers.