[css2xslfo] 01/13: Imported Upstream version 1.6.2

Tristan Seligmann mithrandi at moszumanska.debian.org
Sun Jan 19 14:33:04 UTC 2014


This is an automated email from the git hooks/post-receive script.

mithrandi pushed a commit to branch master
in repository css2xslfo.

commit b2d86986be12d04e248c0d0b690b88162cc7b778
Author: Tristan Seligmann <mithrandi at mithrandi.net>
Date:   Sun Jan 19 14:18:14 2014 +0200

    Imported Upstream version 1.6.2
---
 build.xml                                          |  247 +
 doc/manual.xhtml                                   | 5880 ++++++++++++++++++++
 doc/preprocess_xhtml.xsl                           |  126 +
 doc/xhtml_report.css                               |  253 +
 dtd/catalog                                        |   90 +
 dtd/xhtml1/xhtml-lat1.ent                          |  196 +
 dtd/xhtml1/xhtml-special.ent                       |   80 +
 dtd/xhtml1/xhtml-symbol.ent                        |  237 +
 dtd/xhtml1/xhtml1-frameset.dtd                     | 1235 ++++
 dtd/xhtml1/xhtml1-strict.dtd                       |  978 ++++
 dtd/xhtml1/xhtml1-transitional.dtd                 | 1201 ++++
 lib_src/flute_src.zip                              |  Bin 0 -> 202375 bytes
 lib_src/sac_src.zip                                |  Bin 0 -> 146108 bytes
 res/LICENCE                                        |    5 +
 res/MANIFEST_css2fop.MF                            |    6 +
 res/MANIFEST_css2fopnew.MF                         |    6 +
 res/MANIFEST_css2xep.MF                            |    7 +
 res/MANIFEST_css2xinc.MF                           |    6 +
 res/MANIFEST_css2xsl.MF                            |    6 +
 res/MANIFEST_css2xslfo.MF                          |    5 +
 res/W3C_COPYRIGHT.html                             |   74 +
 res/release_notes.txt                              |  351 ++
 src/META-INF/services/org.w3c.css.sac.parser       |    1 +
 src/be/re/css/BlockContainerFilter.java            |  200 +
 src/be/re/css/CSSToFOP.java                        |  396 ++
 src/be/re/css/CSSToFOPNew.java                     |  403 ++
 src/be/re/css/CSSToXEP.java                        |  415 ++
 src/be/re/css/CSSToXSLFO.java                      |  307 +
 src/be/re/css/CSSToXSLFOException.java             |   82 +
 src/be/re/css/CSSToXSLFOFilter.java                |  405 ++
 src/be/re/css/CSSToXSLFormatter.java               |  216 +
 src/be/re/css/CSSToXinc.java                       |  380 ++
 src/be/re/css/CenterFilter.java                    |  214 +
 src/be/re/css/Compiled.java                        |  621 +++
 src/be/re/css/Constants.java                       |   15 +
 src/be/re/css/Context.java                         |   17 +
 src/be/re/css/DisplayNonePropagator.java           |  106 +
 src/be/re/css/Element.java                         |   79 +
 src/be/re/css/FOMarkerFilter.java                  |  164 +
 src/be/re/css/FirstLetterFilter.java               |  376 ++
 src/be/re/css/FootnoteFilter.java                  |  223 +
 src/be/re/css/ForeignFilter.java                   |  129 +
 src/be/re/css/InternedElementSelector.java         |   52 +
 src/be/re/css/InvalidPropertyFilter.java           |  374 ++
 src/be/re/css/LengthAdjustFilter.java              |  163 +
 src/be/re/css/LinkFilter.java                      |  218 +
 src/be/re/css/ListImageLabelFilter.java            |  115 +
 src/be/re/css/MarkerFilter.java                    |  389 ++
 src/be/re/css/Matcher.java                         |  652 +++
 src/be/re/css/NormalizeTableFilter.java            |  704 +++
 src/be/re/css/PageRule.java                        |   99 +
 src/be/re/css/PageSetupFilter.java                 | 1276 +++++
 src/be/re/css/ProjectorFilter.java                 | 2264 ++++++++
 src/be/re/css/Property.java                        |  708 +++
 src/be/re/css/Rule.java                            |  413 ++
 src/be/re/css/RuleCollector.java                   |  349 ++
 src/be/re/css/RuleComparator.java                  |   51 +
 src/be/re/css/SpaceCorrectionFilter.java           |  118 +
 src/be/re/css/TestSAC.java                         |  416 ++
 src/be/re/css/Util.java                            | 1187 ++++
 src/be/re/css/WidthAndMarginsFilter.java           |  328 ++
 src/be/re/css/WrapperFilter.java                   |  136 +
 src/be/re/css/XHTMLAttributeTranslationFilter.java |  837 +++
 src/be/re/css/ant/CSSToFOP.java                    |   81 +
 src/be/re/css/ant/CSSToFOPNew.java                 |   65 +
 src/be/re/css/ant/CSSToXEP.java                    |   71 +
 src/be/re/css/ant/CSSToXSLFO.java                  |   42 +
 src/be/re/css/ant/CSSToXSLFormatter.java           |   61 +
 src/be/re/css/ant/CSSToXinc.java                   |   42 +
 src/be/re/css/ant/TaskBase.java                    |  164 +
 src/be/re/css/res/space_correction.prop            |   12 +
 src/be/re/css/style/css.xsl                        | 1043 ++++
 src/be/re/css/style/deltaxml.css                   |   40 +
 src/be/re/css/style/fo_setup.xsl                   |  319 ++
 src/be/re/css/style/fop_filter.xsl                 |  177 +
 src/be/re/css/style/ua.css                         |    3 +
 src/be/re/css/style/util.xsl                       |  106 +
 src/be/re/css/style/xhtml.css                      |  160 +
 src/be/re/css/style/xhtml_print.css                |  211 +
 src/be/re/css/style/xlink.css                      |    9 +
 src/be/re/io/FlushOutputStream.java                |   28 +
 src/be/re/io/IOException.java                      |   43 +
 src/be/re/io/ReadLineInputStream.java              |   96 +
 src/be/re/io/StreamConnector.java                  |  245 +
 src/be/re/io/Util.java                             |  190 +
 src/be/re/net/BasicUser.java                       |  142 +
 src/be/re/net/Headers.java                         |  282 +
 src/be/re/net/User.java                            |    9 +
 src/be/re/net/Util.java                            | 1265 +++++
 src/be/re/util/Array.java                          |  941 ++++
 src/be/re/util/DigitalTree.java                    |  207 +
 src/be/re/util/Equal.java                          |   24 +
 src/be/re/util/UUID.java                           |  194 +
 src/be/re/util/Util.java                           | 1088 ++++
 src/be/re/xml/Accumulator.java                     |  404 ++
 src/be/re/xml/CatalogResolver.java                 |  388 ++
 src/be/re/xml/DOMToContentHandler.java             |  284 +
 src/be/re/xml/ExpandedName.java                    |   85 +
 src/be/re/xml/Util.java                            | 1276 +++++
 src/be/re/xml/sax/BalanceChecker.java              |  157 +
 src/be/re/xml/sax/ErrorHandler.java                |   68 +
 src/be/re/xml/sax/FilterOfFilters.java             |  343 ++
 src/be/re/xml/sax/GobbleDocumentEvents.java        |   46 +
 src/be/re/xml/sax/ProtectEventHandlerFilter.java   |   91 +
 src/be/re/xml/sax/Tee.java                         |  190 +
 src/be/re/xml/sax/TransformerHandlerFilter.java    |   49 +
 src/be/re/xml/sax/Util.java                        |  267 +
 src/catalog                                        |    9 +
 src/xhtml1/xhtml-lat1.ent                          |  196 +
 src/xhtml1/xhtml-special.ent                       |   80 +
 src/xhtml1/xhtml-symbol.ent                        |  237 +
 src/xhtml1/xhtml1-frameset.dtd                     | 1235 ++++
 src/xhtml1/xhtml1-strict.dtd                       |  978 ++++
 src/xhtml1/xhtml1-transitional.dtd                 | 1201 ++++
 114 files changed, 41531 insertions(+)

diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..c485b7c
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,247 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<project name="CSSToXSLFO" basedir="." default="all">
+
+  <property name="version" value="1_6_2" />
+  <property name="view" value="csstoxslfo_release" />
+
+  <taskdef name="webdav-sync" classname="be.re.webdav.cmd.SyncAntTask" classpath="${user.home}/bin/webdav_sync.jar" />
+
+  <fileset id="compile-classpath" dir="lib">
+    <include name="flute.jar" />
+    <include name="sac.jar" />
+    <include name="stax-api-1.0.1.jar" />
+  </fileset>
+
+  <patternset id="lib-include">
+    <include name="lib/flute.jar" />
+    <include name="lib/sac.jar" />
+    <include name="lib/stax-api-1.0.1.jar" />
+  </patternset>
+
+  <!-- Main targets -->
+  <target name="all" depends="xslfo" />
+
+  <target name="release">
+    <antcall target="clean" />
+    <antcall target="xslfo" />
+    <antcall target="clean" />
+    <antcall target="xep" />
+    <antcall target="clean" />
+    <antcall target="xsl" />
+    <antcall target="clean" />
+    <antcall target="fop" />
+    <antcall target="clean" />
+    <antcall target="fopnew" />
+    <antcall target="clean" />
+    <antcall target="xinc" />
+    <antcall target="pack-source" />
+    <antcall target="clean" />
+  </target>
+
+  <target name="build" depends="compile-css2xslfo,copy-resources" />
+  <target name="build-xep" depends="compile-css2xep,copy-resources" />
+  <target name="build-xsl" depends="compile-css2xsl,copy-resources" />
+  <target name="build-fop" depends="compile-css2fop,copy-resources" />
+  <target name="build-fopnew" depends="compile-css2fopnew,copy-resources" />
+  <target name="build-xinc" depends="compile-css2xinc,copy-resources" />
+
+  <target name="clean">
+    <delete dir="classes" />
+  </target>
+
+  <target name="compile-css2xslfo" depends="init">
+    <javac debug="on" debuglevel="lines,vars,source" srcdir="src" destdir="classes">
+      <classpath>
+        <fileset refid="compile-classpath" />
+      </classpath>
+      <exclude name="be/re/css/CSSToXEP.java" />
+      <exclude name="be/re/css/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/CSSToFOP.java" />
+      <exclude name="be/re/css/CSSToFOPNew.java" />
+      <exclude name="be/re/css/CSSToXinc.java" />
+      <exclude name="be/re/css/ant/CSSToXEP.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/ant/CSSToFOP.java" />
+      <exclude name="be/re/css/ant/CSSToFOPNew.java" />
+      <exclude name="be/re/css/ant/CSSToXinc.java" />
+    </javac>
+  </target>
+
+  <target name="compile-css2xep" depends="init">
+    <javac debug="on" debuglevel="lines,vars,source" srcdir="src" destdir="classes">
+      <classpath>
+        <fileset refid="compile-classpath" />
+        <fileset file="lib/xep.jar" />
+      </classpath>
+      <exclude name="be/re/css/CSSToXSLFO.java" />
+      <exclude name="be/re/css/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/CSSToFOP.java" />
+      <exclude name="be/re/css/CSSToFOPNew.java" />
+      <exclude name="be/re/css/CSSToXinc.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFO.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/ant/CSSToFOP.java" />
+      <exclude name="be/re/css/ant/CSSToFOPNew.java" />
+      <exclude name="be/re/css/ant/CSSToXinc.java" />
+    </javac>
+  </target>
+
+  <target name="compile-css2xsl" depends="init">
+    <javac debug="on" debuglevel="lines,vars,source" srcdir="src" destdir="classes">
+      <classpath>
+        <fileset refid="compile-classpath" />
+        <fileset file="lib/XfoJavaCtl.jar" />
+      </classpath>
+      <exclude name="be/re/css/CSSToXEP.java" />
+      <exclude name="be/re/css/CSSToXSLFO.java" />
+      <exclude name="be/re/css/CSSToFOP.java" />
+      <exclude name="be/re/css/CSSToFOPNew.java" />
+      <exclude name="be/re/css/CSSToXinc.java" />
+      <exclude name="be/re/css/ant/CSSToXEP.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFO.java" />
+      <exclude name="be/re/css/ant/CSSToFOP.java" />
+      <exclude name="be/re/css/ant/CSSToFOPNew.java" />
+      <exclude name="be/re/css/ant/CSSToXinc.java" />
+    </javac>
+  </target>
+
+  <target name="compile-css2fop" depends="init">
+    <javac debug="on" debuglevel="lines,vars,source" srcdir="src" destdir="classes">
+      <classpath>
+        <fileset refid="compile-classpath" />
+        <fileset file="lib/fop-must-come-after-new-0.20.5.jar" />
+        <fileset file="lib/avalon-framework-4.2.0.jar" />
+      </classpath>
+      <exclude name="be/re/css/CSSToXEP.java" />
+      <exclude name="be/re/css/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/CSSToXSLFO.java" />
+      <exclude name="be/re/css/CSSToFOPNew.java" />
+      <exclude name="be/re/css/CSSToXinc.java" />
+      <exclude name="be/re/css/ant/CSSToXEP.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFO.java" />
+      <exclude name="be/re/css/ant/CSSToFOPNew.java" />
+      <exclude name="be/re/css/ant/CSSToXinc.java" />
+    </javac>
+  </target>
+
+  <target name="compile-css2fopnew" depends="init">
+    <javac debug="on" debuglevel="lines,vars,source" srcdir="src" destdir="classes">
+      <classpath>
+        <fileset refid="compile-classpath" />
+        <fileset file="lib/fop-1.0.jar" />
+        <fileset file="lib/avalon-framework-4.2.0.jar" />
+      </classpath>
+      <exclude name="be/re/css/CSSToXEP.java" />
+      <exclude name="be/re/css/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/CSSToFOP.java" />
+      <exclude name="be/re/css/CSSToXSLFO.java" />
+      <exclude name="be/re/css/CSSToXinc.java" />
+      <exclude name="be/re/css/ant/CSSToXEP.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/ant/CSSToFOP.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFO.java" />
+      <exclude name="be/re/css/ant/CSSToXinc.java" />
+    </javac>
+  </target>
+
+  <target name="compile-css2xinc" depends="init">
+    <javac debug="on" debuglevel="lines,vars,source" srcdir="src" destdir="classes">
+      <classpath>
+        <fileset refid="compile-classpath" />
+        <fileset file="lib/xinc.jar" />
+      </classpath>
+      <exclude name="be/re/css/CSSToXEP.java" />
+      <exclude name="be/re/css/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/CSSToFOP.java" />
+      <exclude name="be/re/css/CSSToFOPNew.java" />
+      <exclude name="be/re/css/CSSToXSLFO.java" />
+      <exclude name="be/re/css/ant/CSSToXEP.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFormatter.java" />
+      <exclude name="be/re/css/ant/CSSToFOP.java" />
+      <exclude name="be/re/css/ant/CSSToFOPNew.java" />
+      <exclude name="be/re/css/ant/CSSToXSLFO.java" />
+    </javac>
+  </target>
+
+  <target name="copy-resources" depends="init">
+    <copy todir="classes">
+      <fileset dir="src" includes="be/**,catalog,xhtml1/**" excludes="**/*.java" />
+    </copy>
+  </target>
+
+  <target name="init">
+    <tstamp />
+    <mkdir dir="classes" />
+    <mkdir dir="bin" />
+    <webdav-sync url="https://werner@re.pincette.net/view/${view}/java/src/" directory="src" direction="down" />
+    <webdav-sync url="https://werner@re.pincette.net/view/${view}/java/lib/" directory="lib" direction="down" />
+    <webdav-sync url="https://werner@re.pincette.net/view/${view}/java/applications/css2xslfo/doc/" directory="doc" direction="down" />
+    <webdav-sync url="https://werner@re.pincette.net/view/${view}/java/applications/css2xslfo/res/" directory="res" direction="down" />
+    <webdav-sync url="https://werner@re.pincette.net/view/${view}/java/applications/css2xslfo/lib_src/" directory="lib_src" direction="down" />
+    <webdav-sync url="https://werner@re.pincette.net/view/${view}/dtd/" directory="dtd" direction="down" />
+  </target>
+
+  <target name="jar">
+    <jar destfile="bin/${variant}${version}.jar" manifest="res/MANIFEST_${variant}.MF">
+      <fileset dir="classes" />
+      <zipgroupfileset dir=".">
+        <patternset refid="lib-include" />
+      </zipgroupfileset>
+      <fileset dir="src">
+        <include name="META-INF/**" />
+      </fileset>
+      <fileset dir="res">
+        <include name="release_notes.txt" />
+        <include name="LICENCE" />
+        <include name="W3C_COPYRIGHT.html" />
+      </fileset>
+    </jar>
+  </target>
+
+  <target name="xslfo" depends="clean,build">
+    <antcall target="jar">
+      <param name="variant" value="css2xslfo" />
+    </antcall>
+  </target>
+
+  <target name="xep" depends="clean,build-xep">
+    <antcall target="jar">
+      <param name="variant" value="css2xep" />
+    </antcall>
+  </target>
+
+  <target name="xsl" depends="clean,build-xsl">
+    <antcall target="jar">
+      <param name="variant" value="css2xsl" />
+    </antcall>
+  </target>
+
+  <target name="fop" depends="clean,build-fop">
+    <antcall target="jar">
+      <param name="variant" value="css2fop" />
+    </antcall>
+  </target>
+
+  <target name="fopnew" depends="clean,build-fopnew">
+    <antcall target="jar">
+      <param name="variant" value="css2fopnew" />
+    </antcall>
+  </target>
+
+  <target name="xinc" depends="clean,build-xinc">
+    <antcall target="jar">
+      <param name="variant" value="css2xinc" />
+    </antcall>
+  </target>
+
+  <target name="pack-source" depends="clean">
+    <zip destfile="css2xslfo${version}_src.zip">
+      <zipfileset dir="." includes="doc/**,dtd/**,lib_src/**,res/**,src/**,build.xml,build.properties" prefix="css2xslfo" />
+      <zipfileset dir="." prefix="css2xslfo">
+        <patternset refid="lib-include" />
+      </zipfileset>
+    </zip>
+  </target>
+
+</project>
diff --git a/doc/manual.xhtml b/doc/manual.xhtml
new file mode 100644
index 0000000..b0f66c9
--- /dev/null
+++ b/doc/manual.xhtml
@@ -0,0 +1,5880 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" [
+  <!ENTITY css "<acronym>CSS</acronym>">
+  <!ENTITY css2 "<acronym>CSS2</acronym>">
+  <!ENTITY css3 "<acronym>CSS3</acronym>">
+  <!ENTITY csstoxslfo "<acronym>CSSToXSLFO</acronym>">
+  <!ENTITY fop "<acronym>FOP</acronym>">
+  <!ENTITY html "<acronym>HTML</acronym>">
+  <!ENTITY id "<acronym>ID</acronym>">
+  <!ENTITY jar "<acronym>JAR</acronym>">
+  <!ENTITY names "<acronym>NAMES</acronym>">
+  <!ENTITY pdf "<acronym>PDF</acronym>">
+  <!ENTITY sax "<acronym>SAX</acronym>">
+  <!ENTITY svg "<acronym>SVG</acronym>">
+  <!ENTITY xep "<acronym>XEP</acronym>">
+  <!ENTITY xep4 "<acronym>XEP4</acronym>">
+  <!ENTITY xhtml "<acronym>XHTML</acronym>">
+  <!ENTITY xinc "<acronym>XINC</acronym>">
+  <!ENTITY xml "<acronym>XML</acronym>">
+  <!ENTITY xslfo "<acronym>XSL-FO</acronym>">
+  <!ENTITY xslt "<acronym>XSLT</acronym>">
+  <!ENTITY uri "<acronym>URI</acronym>">
+  <!ENTITY url "<acronym>URL</acronym>">
+]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>The CSSToXSLFO User Guide</title>
+    <link type="text/css" media="print" rel="stylesheet" href="xhtml_report.css" />
+    <style type="text/css" xml:space="preserve">
+table
+{
+  border-collapse: collapse;
+  table-layout: fixed;
+  width: 100%;
+}
+
+table table
+{
+  margin-bottom: 0;
+  margin-top: 0;
+}
+
+table.property tr > td:first-child
+{
+  font-style: italic;
+}
+
+table.property tr > td
+{
+  padding: 0pt;
+}
+
+td
+{
+  padding: 5pt;
+  vertical-align: top;
+}
+
+th
+{
+  font-style: italic;
+  font-weight: normal;
+  padding: 5pt;
+  text-align: left;
+}
+
+table.specifications td:first-child, table.specifications th:first-child,
+table.ant-tasks td:first-child, table.ant-tasks th:first-child,
+table.task-attributes td:first-child, table.task-attributes th:first-child
+{
+  padding-left: 0pt;
+}
+
+table.specifications td + td + td, table.specifications th + th + th,
+table.ant-tasks td + td, table.ant-tasks th + th,
+table.task-attributes td + td + td, table.task-attributes th + th + th
+{
+  padding-right: 0pt;
+}
+
+div.method
+{
+  font-style: italic;
+  margin-bottom: 1.2em;
+  margin-top: 1.2em;
+  page-break-inside: avoid;
+}
+
+div.method > div.p
+{
+  padding-left: 1em;
+}
+
+ at media screen
+{
+  *:link
+  {
+    color: #982936;
+  }
+
+  *:visited
+  {
+    color: purple;
+  }
+
+  body
+  {
+    background: white;
+    font-family: sans-serif;
+  }
+
+  div.bottom, div.top, div.doc-info, div.title-page, div.copy-right
+  {
+    display: none;
+  }
+
+  div.h1
+  {
+    margin: 0.67em 0;
+    text-align: center;
+  }
+
+  div.h2
+  {
+    margin-bottom: 0.83em;
+    margin-left: 0;
+    margin-right: 0;
+    margin-top: 2em;
+  }
+
+  div.h3
+  {
+    margin-bottom: 1em;
+    margin-left: 0;
+    margin-right: 0;
+    margin-top: 1.8em;
+  }
+
+  div.h1, div.h2, div.h3
+  {
+    border-bottom: solid 0.4pt;
+    color: #982936;
+    width: 100%;
+  }
+
+  div.h2 h2
+  {
+    font-style: italic;
+    font-size: 1.3em;
+  }
+
+  div.h3 h3
+  {
+    font-style: italic;
+    font-size: 1.1em;
+  }
+
+  div.h1 h1, div.h2 h2, div.h3 h3
+  {
+    display: inline;
+    font-weight: normal;
+    color: white;
+    background-color: #982936;
+    margin: 0;
+    padding-left: 5pt;
+    padding-right: 5pt;
+    padding-top: 2pt;
+  }
+
+  div.toc-h1
+  {
+    margin-left: 1em;
+  }
+
+  div.toc-h2
+  {
+    margin-left: 2em;
+  }
+
+  div.toc-h3
+  {
+    margin-left: 3em;
+  }
+
+  div.text
+  {
+    margin-left: 5em;
+  }
+
+  div.title-screen
+  {
+    font-size: 2em;
+    font-weight: bold;
+  }
+
+  hr
+  {
+    border-bottom: none;
+    border-left: none;
+    border-right: none;
+    border-top: 0.2pt solid;
+  }
+
+  table
+  {
+    margin-bottom: 0.83em;
+    margin-top: 0.83em;
+  }
+}
+
+ at media print
+{
+  div.blank
+  {
+    page: blank;
+  }
+
+  div.bottom
+  {
+    height: 3em;
+    padding-top: 2em;
+    region: bottom;
+  }
+
+  div.bottom > span:before
+  {
+    content: counter(page);
+  }
+
+  div.copy-right
+  {
+    page: copy-right;
+  }
+
+  div.first
+  {
+    page: first;
+  }
+
+  div.front
+  {
+    page: front;
+  }
+
+  div.front.left
+  {
+    page: left-front;
+  }
+
+  div.front.right
+  {
+    page: right-front;
+  }
+
+  div.front.bottom > span:before
+  {
+    content: counter(page, lower-roman);
+  }
+
+  div.left
+  {
+    page: left;
+    text-align: left;
+  }
+
+  div.right
+  {
+    page: right;
+    text-align: right;
+  }
+
+  div.title.first
+  {
+    page: first-title;
+  }
+
+  div.copy-right.first
+  {
+    page: first-copy-right;
+  }
+
+  div.top
+  {
+    font-family: serif-title;
+    font-style: italic;
+    height: 4em;
+    region: top;
+  }
+
+  div.top > span:before
+  {
+    content: string(chapter);
+  }
+
+  div.copy-right > p
+  {
+    text-indent: 0pt;
+  }
+
+  div.back, div.main, div.toc-back, div.toc-main
+  {
+    counter-reset: h1;
+  }
+
+  div.title-screen
+  {
+    display: none;
+  }
+
+  div.title-page
+  {
+    page: title;
+  }
+
+  div.title-page > div.doc-info
+  {
+    border-right: solid 0.2pt;
+    display: block;
+    font-size: 12pt;
+    padding-right: 5mm;
+    text-align: right;
+  }
+
+  div.title-page > div.title-area
+  {
+    border-left: solid 0.2pt;
+    padding-left: 5mm;
+  }
+
+  div.title-page > div.title-area > div
+  {
+    font-size: 21pt;
+  }
+
+  div.title-page > div.title-area > div.title-main
+  {
+    font-size: 36pt;
+    font-style: italic;
+    font-weight: normal;
+    margin-bottom: 1em;
+    margin-top: 1em;
+  }
+
+  h1, div.toc-h1
+  {
+    counter-increment: h1;
+    counter-reset: footnote h2;
+  }
+
+  div.back > h1
+  {
+    page: back;
+  }
+
+  div.main > h1
+  {
+    page: main;
+  }
+
+  div.main > h1:first-child
+  {
+    page: main-first;
+  }
+
+  div.main h2:before, div.main h3:before,
+  div.back h2:before, div.back h3:before
+  {
+    padding-right: 1em;
+  }
+
+  div.toc-h1:before, div.toc-h2:before, div.toc-h3:before
+  {
+    display: marker;
+    marker-offset: 12pt;
+    text-align: right;
+    width: 4em;
+  }
+
+  h1
+  {
+    margin-bottom: 2.4em;
+    text-align-last: justify;
+  }
+
+  h1 > span:before
+  {
+    display: leader;
+  }
+
+  h1 > span:after
+  {
+    font-family: serif-title;
+    font-size: 3em; /* Results in 4.8 */
+    font-style: italic;
+    text-transform: uppercase;
+  }
+
+  div.main h1 > span:after, div.toc-main > div.toc-h1:before
+  {
+    content: counter(h1);
+  }
+
+  div.back h1 > span:after, div.toc-back > div.toc-h1:before
+  {
+    content: counter(h1, upper-alpha);
+  }
+
+  h2, div.toc-h2
+  {
+    counter-increment: h2;
+    counter-reset: h3;
+  }
+
+  div.main h2:before, div.toc-main > div.toc-h2:before
+  {
+    content: counter(h1) "." counter(h2);
+  }
+
+  div.main h2.example:before
+  {
+    display: marker;
+    marker-offset: 0.5em;
+    padding-right: 0pt;
+    text-align: right;
+    width: 3em;
+  }
+
+  div.back h2:before, div.toc-back > div.toc-h2:before
+  {
+    content: counter(h1, upper-alpha) "." counter(h2);
+  }
+
+  h3, div.toc-h3
+  {
+    counter-increment: h3;
+  }
+
+  div.main h3:before, div.toc-main > div.toc-h3:before
+  {
+    content: counter(h1) "." counter(h2) "." counter(h3);
+  }
+
+  div.back h3:before, div.toc-back > div.toc-h3:before
+  {
+    content: counter(h1, upper-alpha) "." counter(h2) "." counter(h3);
+  }
+
+  div.toc span.leader
+  {
+    display: none;
+  }
+
+  div.toc span.page-ref
+  {
+    padding-left: 2em;
+  }
+
+  div.toc-h1 > a, div.toc-h2 > a, div.toc-h3 > a
+  {
+    font-family: serif-title;
+  }
+
+  ol.example
+  {
+    counter-reset: list-counter;
+  }
+
+  ol.example li
+  {
+    margin-left: 2em;
+  }
+
+  ol.example li:before
+  {
+    content: counter(list-counter, lower-roman) ".";
+    counter-increment: list-counter;
+    display: marker;
+    marker-offset: 0.5em;
+    text-align: right;
+    width: 2em;
+  }
+
+  p.first-letter-example:first-letter
+  {
+    font-family: serif-swash;
+    font-size: 46pt;
+    font-style: italic;
+    float: left;
+    line-height: 46pt;
+    padding-right: 6pt;
+    margin-bottom: -12pt;
+    vertical-align: 9pt;
+  }
+}
+    </style>
+  </head>
+  <body>
+    <div class="first top" />
+    <div class="title first bottom" />
+    <div class="title first top" />
+    <div class="copy-right first bottom" />
+    <div class="copy-right first top" />
+    <div class="left bottom"><span /></div>
+    <div class="right bottom"><span /></div>
+    <div class="left top"><span /></div>
+    <div class="right top"><span /></div>
+    <div class="front left bottom"><span /></div>
+    <div class="front right bottom"><span /></div>
+    <div class="blank bottom" />
+    <div class="blank top" />
+
+    <div class="title-page">
+      <div class="title-area">
+        <div>The</div>
+        <div class="title-main">CSSToXSLFO</div>
+        <div>User Guide</div>
+      </div>
+
+      <div style="height: 10cm" />
+
+      <div class="doc-info">
+        <div>Version 1.6.2</div>
+        <div>Werner Donné</div>
+        <div>Pincette bvba</div>
+        <div>23 August 2010</div>
+      </div>
+    </div>
+
+    <div class="title-screen">The CSSToXSLFO User Guide</div>
+
+    <div class="copy-right">
+      <p>© 2004-2010 Pincette bvba. All rights granted.</p>
+
+      <p>This software is free and will remain free.</p>
+
+      <p>To use at your own responsibility.</p>
+    </div>
+
+    <div class="front">
+      <div class="toc" />
+    </div>
+
+    <div class="main">
+
+    <h1>Introduction<span /></h1>
+
+    <p>&csstoxslfo; is a tool which converts an &xml; document, combined with a
+&css2; style sheet, into an &xslfo; file. It has some special provisions for
+&xhtml;, which is also an &xml; vocabulary.
+The tool implements a reasonable subset of &css2;. It also
+adds a few extensions for handling page-related issues properly. Note that the
+tool is not a general-purpose printing tool for any kind of &html; pages you
+can find on the Internet.</p>
+
+    <p>The goal of &csstoxslfo; is to provide a rather easy interface to fine
+printing environments that use &xslfo; as their input. It is
+a compromise between the simplicity of style sheet expression and the quality
+of the result. &xslfo; is quite difficult. Writing style
+sheets that produce it are mostly written in &xslt;, which is
+not straightforward to everyone either. &css; on the other hand
+is rather simple and yet it is powerful. In fact it combines element selection
+and formatting specification in one easy-to-learn syntax. The cost is that a
+lot of interesting &xslfo; features are not available.</p>
+
+    <p>An area where the tool can be a plus is the programmatic generation of
+reports within applications. The variety in style for reports is not that
+great. The offered feature set of &csstoxslfo; can be sufficient. Having report
+programmers learn &xslfo; and &xslt; is not always an option, while many know
+&css; and &xhtml; well enough to be productive with it.</p>
+
+    <p>Another use-case for &csstoxslfo; is writing documents in &xml;. One can
+put work in a style sheet once and reuse that through the mark-up paradigm,
+in which content and formatting are separated.
+The formatting features should be sufficient to produce day-to-day documents in
+a typical business environment. Such documents don't tend to be very
+complicated, with respect to layout that is.</p>
+
+    <div class="separator" />
+
+    <h1>In Practice<span /></h1>
+
+    <h2>Specifying Style Sheets</h2>
+
+    <p>The most general way of specifying a style sheet for a document with
+&csstoxslfo; is the proposal in section 2.2 of [<a href="#CSS2" shape="rect">&css2;</a>]. It
+consists of a processing instruction, which precedes the document, looking like
+this:</p>
+
+    <pre xml:space="preserve">  <?xml-stylesheet type="text/css" href="style.css"?></pre>
+
+    <p>For &xhtml; there are a few additional options. You can
+use the <code>link</code> element to link a style sheet (only persistent style
+sheets) to the document or you can embed it with the <code>style</code> element.
+The <code>style</code> attribute is also available as specified in
+[<a href="#XHTML" shape="rect">&xhtml;</a>].</p>
+
+    <h2>Running It</h2>
+
+    <p>There are six packages you can run from the command-line: one that
+produces plain &xslfo;, one that returns the output of &xep;,
+a product from RenderX (<a href="http://www.renderx.com" shape="rect">http://​www.​renderx.​com</a>),
+another that returns the output of XSLFormatter, a product from Antenna
+House
+(<a href="http://www.antennahouse.com" shape="rect">http://​www.antennahouse.com</a>),
+yet another that returns the output of Xinc, a product from Lunasil
+<acronym>LTD</acronym> (<a href="http://www.lunasil.com" shape="rect">http://​www.​lunasil.​com</a>)
+and finally, two that run &fop;
+(<a href="http://xml.apache.org/fop/" shape="rect">http://​xml.​apache.​org/​fop/</a>).
+One is for version 0.20.5 and the other for version 1.0.
+The 0.20.5 one comes with a filter that removes a few properties, which are not
+supported by &fop;. This makes &fop; complain less.</p>
+
+<p>You need <acronym>JDK1.3</acronym> or higher to run the packages. For 1.3
+you should create a classpath with a namespace-aware &xml; parser and an &xslt;
+processor. The command-lines look as follows for plain &csstoxslfo;:</p>
+
+    <pre xml:space="preserve">  > java -jar css2xslfo.jar url_or_filename <options></pre>
+
+    <p>For <acronym>XEP3</acronym>:</p>
+
+    <pre xml:space="preserve">  > java -Dcom.renderx.xep.ROOT=<XEP location> -jar css2xep.jar
+      url_or_filename <options></pre>
+
+    <p>For &xep4;:</p>
+
+    <pre xml:space="preserve">  > java -Dcom.renderx.xep.CONFIG=<XEP location>/xep.xml
+      -jar <XEP location>/lib/css2xep.jar url_or_filename
+      <options></pre>
+
+    <p>For XSLFormatter:</p>
+
+    <pre xml:space="preserve">  > set dynamic library path to <XSLFormatter location>/lib
+  > set environment variable AH_FONT_CONFIGFILE to
+      <XSLFormatter location>/etc/font-config.xml
+  > java -jar <XSLFormatter location>/lib/css2xsl.jar
+      url_or_filename <options></pre>
+
+    <p>For Xinc:</p>
+
+      <pre xml:space="preserve">  > java -jar css2xinc.jar url_or_filename <options></pre>
+
+    <p>For &fop; 0.20.5:</p>
+
+    <pre xml:space="preserve">  > java -jar css2fop.jar url_or_filename <options></pre>
+
+    <p>For &fop; 0.95:</p>
+
+    <pre xml:space="preserve">  > java -jar css2fopnew.jar url_or_filename <options></pre>
+
+    <p>Additional system properties and/or enviroment variables can be set.
+Please consult the product-specific documentation for this.</p>
+
+    <p>In order for css2xep.jar to work, you should place it in the <XEP
+location>/lib directory and create a link to or a copy of your &xep; &jar; file
+with the name <q>xep.jar</q>. Since &xep4; the link or copy are no longer
+needed, because the &xep; &jar; file has the expected name.
+For css2xsl.jar to work, you should place it in <XSLFormatter location>/lib.
+The css2fop.jar file needs to be next to fop.jar, which should be next
+to the packages is uses. Therefore you should copy fop.jar from the &fop; build
+directory to its lib directory. The same procedure is required for
+css2fopnew.jar. The css2xinc.jar should be in the &xinc; lib directory.</p>
+
+    <p>&csstoxslfo; uses the &xslt;-processor that comes with
+the <acronym>JDK1.4</acronym>, which is
+Xalan from <a href="http://www.apache.org" shape="rect">Apache</a>. For better performance
+you can prepend <a href="http://xml.apache.org/xalan-j/" shape="rect">Xalan 2.6.0+</a> or <a href="http://saxon.sourceforge.net/" shape="rect">Saxon 8.3+</a> to
+your boot classpath as follows (assuming /usr/local as the installation
+directory of Xalan):</p>
+
+    <pre xml:space="preserve">  > java -Xbootclasspath/p:/usr/local/xalan-j_2_6_0/bin/xalan.jar
+      -jar css2xslfo.jar url_or_filename <options></pre>
+
+    <p>You can also use <acronym>JDK1.5</acronym>, which comes with a faster
+&xslt; processor.</p>
+
+    <p>For &xep; there is a special note. You have to specify another &xslt;
+processor, because &xep; uses Saxon 6.5.x, with which it doesn't work. You can
+either prepend another &xslt; processor to the boot classpath or you can simply
+copy saxon8.jar in the &xep; lib directory.</p>
+
+    <h3>Common Options</h3>
+
+    <p>The following options are common to all six variants. The document
+to be processed can be specified with a &url; or filename. If it is omitted,
+stdin will be read.</p>
+
+    <dl>
+      <dt>-baseurl <URL></dt>
+      <dd>Change the base &url; of the input document. By default it is the
+&url; of the document itself.</dd>
+
+      <dt>-c <URL or filename></dt>
+      <dd>Specify a catalog in the format defined by SGML Open Technical
+Resolution <acronym>TR9401:1997</acronym>. Only the <q>PUBLIC</q> and
+<q>SYSTEM</q> keywords are supported.</dd>
+
+      <dt>-h</dt><dd>Display the command-line syntax.</dd>
+
+      <dt>-p <comma-separated list of &url;s or filenames></dt>
+      <dd>A list of pre-processing &xslt; style sheets that are executed on the
+input document, in the specified order, before anything else.</dd>
+
+      <dt>-uacss <URL or filename></dt>
+      <dd>Use another User Agent style sheet than the one built-in.</dd>
+
+      <dt>-v</dt><dd>Turn on &xml; validation of the input document.</dd>
+
+      <dt>parameter=value</dt>
+      <dd>Specify User Agent parameters. Equivalent &css; constructs precede
+these.</dd>
+    </dl>
+
+    <h3>Options Specific To css2xslfo.jar</h3>
+
+    <dl>
+      <dt>-debug</dt>
+      <dd>Produces a number of intermediary files representing the different
+processing steps.</dd>
+
+      <dt>-fo <filename></dt>
+      <dd>The &xslfo; output file. If it is omitted stdout will be written
+instead.</dd>
+    </dl>
+
+    <h3>Options Specific To css2xep.jar</h3>
+
+    <p>One the following options should be specified.</p>
+
+    <dl>
+      <dt>-pdf <filename></dt>
+      <dd>The &pdf; output file. Either this option or the <q>-ps</q> options
+should be present.</dd>
+
+      <dt>-ps <filename></dt>
+      <dd>The PostScript output file. Either this option or the <q>-pdf</q>
+options should be present.</dd>
+
+      <dt>-config <URL or filename></dt>
+      <dd>The &xep; configuration file.</dd>
+
+      <dt>-q</dt>
+      <dd>Makes &xep; silent.</dd>
+    </dl>
+
+    <h3>Options Specific To css2xsl.jar</h3>
+
+    <dl>
+      <dt>-pdf <filename></dt>
+      <dd>The &pdf; output file. This option is mandatory.</dd>
+
+      <dt>-config <URL or filename></dt>
+      <dd>The XSLFormatter configuration file.</dd>
+    </dl>
+
+    <h3>Options Specific To css2fop.jar</h3>
+
+    <dl>
+      <dt>-fc <filename></dt>
+      <dd>A user configuration file.</dd>
+
+      <dt>-pdf <filename></dt>
+      <dd>The &pdf; output file. Either this option or one of the <q>-ps</q>
+and <q>-svg</q> options should be present.</dd>
+
+      <dt>-ps <filename></dt>
+      <dd>The PostScript output file. Either this option or one of the
+<q>-pdf</q> and <q>-svg</q> options should be present.</dd>
+
+      <dt>-q</dt>
+      <dd>Makes &fop; silent.</dd>
+
+      <dt>-svg <filename></dt>
+      <dd>The <acronym>SVG</acronym> output file. Either this option or one of
+the <q>-pdf</q> and <q>-ps</q> options should be present.</dd>
+    </dl>
+
+    <h3>Options Specific To css2fopnew.jar</h3>
+
+    <dl>
+      <dt>-fop <options></dt>
+      <dd>The rest of the command-line will be parsed by FOP. Specifying an
+input file here doesn't work. The <q>-q</q> option will only work if you
+configure the Apache logger environment. The &fop; command-line logger can be
+set by assigning the value
+<code>org.​apache.​fop.​util.​CommandLineLogger</code>
+to the system property
+<code>org.​apache.​commons.​logging.​Log</code>. This
+option is mandatory.</dd>
+    </dl>
+
+    <h3>Options Specific To css2xinc.jar</h3>
+
+    <p>One the following options should be specified.</p>
+
+    <dl>
+      <dt>-pdf <filename></dt>
+      <dd>The &pdf; output file. This option is mandatory.</dd>
+
+      <dt>-config <URL or filename></dt>
+      <dd>The &xinc; configuration file.</dd>
+    </dl>
+
+    <h3 id="uapar">User Agent Parameters</h3>
+
+    <p>The User Agent parameters are common to all three packages. They have
+no effect if there are @page rules in the style sheet, except for the
+<q>rule-thickness</q> parameter. Furthermore, equivalent &css; constructs, when
+present in the style sheet, always precede.</p>
+
+    <dl>
+      <dt>column-count</dt>
+      <dd>The number of columns on a page. The default is <q>1</q>.</dd>
+
+      <dt>country</dt>
+      <dd>The country code. The default is <q>GB</q>.</dd>
+
+      <dt>font-size</dt>
+      <dd>The point size of the font. The default for paper sizes <q>a5</q>
+and <q>b5</q> is <q>10pt</q>. For all other paper sizes the default is
+<q>11pt</q>. See also the <q>paper-size</q> parameter.</dd>
+
+      <dt>html-header-mark</dt>
+      <dd>An &html; element can be passed here. Its contents will be used as the
+running header. By default there is no mark.</dd>
+
+      <dt>language</dt>
+      <dd>The language code. The default is <q>en</q>.</dd>
+
+      <dt>odd-even-hift</dt>
+      <dd>The amount by which the page contents is shifted in the inline
+progression direction when the paper mode is <q>twosided</q>. The default is
+<q>10mm</q>. See also the <q>paper-mode</q> parameter.</dd>
+
+      <dt>orientation</dt>
+      <dd>The allowed values are <q>portrait</q>, which is the default, and
+<q>landscape</q>.</dd>
+
+      <dt>paper-margin-bottom</dt>
+      <dd>The bottom margin of a page. The default is <q>0mm</q>.</dd>
+
+      <dt>paper-margin-left</dt>
+      <dd>The left margin of a page. The default is <q>25mm</q>.</dd>
+
+      <dt>paper-margin-right</dt>
+      <dd>The right margin of a page. The default is <q>25mm</q>.</dd>
+
+      <dt>paper-margin-top</dt>
+      <dd>The top margin of a page. The default is <q>10mm</q>.</dd>
+
+      <dt>paper-mode</dt>
+      <dd>The allowed values are <q>onesided</q>, which is the default, and
+<q>twosided</q>.</dd>
+
+      <dt>paper-size</dt>
+      <dd>The allowed values are <q>a4</q>, which is the default, <q>a0</q>,
+<q>a1</q>, <q>a2</q>, <q>a3</q>, <q>a5</q>, <q>b5</q>, <q>executive</q>,
+<q>letter</q> and <q>legal</q>.</dd>
+
+      <dt>rule-thickness</dt>
+      <dd>The default thickness for rules when there was no &css; specification
+for it. The default is <q>0.2pt</q>.</dd>
+
+      <dt>writing-mode</dt>
+      <dd>The &xslfo; writing mode. The default is <q>lr-tb</q>. Other possible
+values are <q>rl-tb</q>, <q>tb-rl</q>, <q>lr</q>, <q>rl</q> and <q>tb</q>. See
+also [<a href="#XSLFO" shape="rect">&xslfo;</a>].</dd>
+    </dl>
+
+    <h2>Building CSSToXSLFO</h2>
+
+    <p>The tool comes with an <a href="http://ant.apache.org/" shape="rect"><acronym>ANT</acronym></a> file.
+The default target only builds the css2xslfo.jar file. Then there are also the
+<code>xep</code>, <code>xsl</code>, <code>xinc</code>, <code>fop</code> and
+<code>fopnew</code> targets, which produce css2xep.jar, css2xsl.jar,
+css2xinc.jar, css2fop.jar and css2fopnew.jar respectively.</p>
+
+    <h2>Ant Tasks</h2>
+
+    <p>In each package there is a corresponding Ant task. The following table
+gives the respective class names.</p>
+
+    <table class="ant-tasks" rules="all">
+      <col width="1*" span="1" />
+      <col width="2*" span="1" />
+      <thead>
+        <tr>
+          <th rowspan="1" colspan="1">Package</th>
+          <th rowspan="1" colspan="1">Ant task class name</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">css2xslfo.jar</td>
+          <td rowspan="1" colspan="1">be.re.css.ant.CSSToXSLFO</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">css2xep.jar</td>
+          <td rowspan="1" colspan="1">be.re.css.ant.CSSToXEP</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">css2xsl.jar</td>
+          <td rowspan="1" colspan="1">be.re.css.ant.CSSToXSLFormatter</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">css2fop.jar</td>
+          <td rowspan="1" colspan="1">be.re.css.ant.CSSToFOP</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">css2fopnew.jar</td>
+          <td rowspan="1" colspan="1">be.re.css.ant.CSSToFOPNew</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">css2xinc.jar</td>
+          <td rowspan="1" colspan="1">be.re.css.ant.CSSToXinc</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <h3>Common Options</h3>
+
+    <p>All tasks support the nested elements <code>parameter</code> and
+<code>preprocessor</code>. With the former the parameters described in
+<q><a href="#uapar" shape="rect">User Agent Parameter</a></q> can be specified through its
+<code>name</code> and <code>value</code> attributes. With the latter a sequence
+of pre-processing &xslt; style sheets can be provided. Its
+<code>stylesheet</code> attribute should be set to a filename or &url;.
+The following attributes are common to all tasks.</p>
+
+    <table class="task-attributes" rules="all">
+      <col width="2*" span="1" />
+      <col width="4*" span="1" />
+      <col width="1*" span="1" />
+      <thead>
+        <tr>
+          <th rowspan="1" colspan="1">Attribute</th>
+          <th rowspan="1" colspan="1">Description</th>
+          <th rowspan="1" colspan="1">Required</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">baseurl</td>
+          <td rowspan="1" colspan="1">Change the base &url; of the input document. By default it is the
+&url; of the document itself.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">catalog</td>
+          <td rowspan="1" colspan="1">Specify a catalog in the format defined by SGML Open Technical
+Resolution <acronym>TR9401:1997</acronym>. Only the <q>PUBLIC</q> and
+<q>SYSTEM</q> keywords are supported.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">input</td>
+          <td rowspan="1" colspan="1">The input document as a &url; or filename.</td>
+          <td rowspan="1" colspan="1">Yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">output</td>
+          <td rowspan="1" colspan="1">The output document as a filename. The format is derived from its
+extension.</td>
+          <td rowspan="1" colspan="1">Yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">useragentstylesheet</td>
+          <td rowspan="1" colspan="1">Use another User Agent style sheet than the one built-in.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">validate</td>
+          <td rowspan="1" colspan="1">Turn on &xml; validation of the input document. Defaults to
+<code>false</code>.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <h3>Options Specific To css2xep.jar</h3>
+
+    <table class="task-attributes" rules="all">
+      <col width="1*" span="1" />
+      <col width="3*" span="1" />
+      <col width="1*" span="1" />
+      <thead>
+        <tr>
+          <th rowspan="1" colspan="1">Attribute</th>
+          <th rowspan="1" colspan="1">Description</th>
+          <th rowspan="1" colspan="1">Required</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">config</td>
+          <td rowspan="1" colspan="1">The &xep; configuration file. It may be a filename or a
+&url;.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">quiet</td>
+          <td rowspan="1" colspan="1">Makes &xep; silent.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <h3>Options Specific To css2fop.jar</h3>
+
+    <table class="task-attributes" rules="all">
+      <col width="1*" span="1" />
+      <col width="3*" span="1" />
+      <col width="1*" span="1" />
+      <thead>
+        <tr>
+          <th rowspan="1" colspan="1">Attribute</th>
+          <th rowspan="1" colspan="1">Description</th>
+          <th rowspan="1" colspan="1">Required</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">config</td>
+          <td rowspan="1" colspan="1">A user configuration file.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">quiet</td>
+          <td rowspan="1" colspan="1">Makes &fop; silent.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <h3>Options Specific To css2fopnew.jar</h3>
+
+    <table class="task-attributes" rules="all">
+      <col width="1*" span="1" />
+      <col width="3*" span="1" />
+      <col width="1*" span="1" />
+      <thead>
+        <tr>
+          <th rowspan="1" colspan="1">Attribute</th>
+          <th rowspan="1" colspan="1">Description</th>
+          <th rowspan="1" colspan="1">Required</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">config</td>
+          <td rowspan="1" colspan="1">A user configuration file.</td>
+          <td rowspan="1" colspan="1">No</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <div class="separator" />
+
+    <h1>Compliance With CSS2<span /></h1>
+
+    <h2>Specifications</h2>
+
+    <table class="specifications" rules="all">
+      <col width="2*" span="1" />
+      <col width="1*" span="1" />
+      <col width="2*" align="justify" span="1" />
+      <thead>
+        <tr>
+          <th rowspan="1" colspan="1">Section</th>
+          <th rowspan="1" colspan="1">Implemented</th>
+          <th rowspan="1" colspan="1">Remarks and restrictions</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">4.1 Syntax</td> 
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1">Thanks to Flute.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">4.2 Rules for handling parsing errors</td> 
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">Unknown properties will end up in the &xslfo; file and cause
+errors in a &xslfo; processor.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">4.3 Values</td> 
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1">Thanks to Flute.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">4.4 &css; document representation</td> 
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1">Thanks to Flute.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">5 Selectors</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">All sections but 5.11.2 and 5.11.3. The
+<code>:first-letter</code> pseudo element is implemented with the restriction
+that letter combinations, which are considered as one letter, are not examined.
+As a workaround you can use the ligature Unicode characters instead. The
+<code>vertical-align</code> is also valid when <code>float</code> is
+<code>none</code>. In that case it applies to the inline material which is
+affected by the pseudo element.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">6 Assigning property values, Cascading and Inheritance</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">7 Media types</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1">By design, only types <code>all</code> and <code>print</code>
+are supported.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">8 Box model</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.1.1 The viewport</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.1.2 Containing blocks</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.2.1 Block-level elements and block boxes</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">Compact and run-in boxes are not supported.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.2.2 Inline-level elements and inline boxes</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">Compact and run-in boxes and inline tables are not
+supported.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.2.3 Compact boxes</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.2.4 Run-in boxes</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.2.5 The 'display' property</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">See property table.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.3 Positioning schemes</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.4 Normal flow</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.5 Floats</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.6 Absolute positioning</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.7 Relationships between 'display', 'position', and 'float'</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.9 Layered presentation</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">9.10 Text direction: the 'direction' and 'unicode-bibi'
+properties</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">10 Visual formatting model details</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">See the property table for the height property.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">11 Visual effects</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">12.1 The :before and :after pseudo-elements</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">12.2 The 'content' property</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">12.3 Interaction of :before and :after with 'compact' and
+'run-in' elements</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">12.4 Quotation marks</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">12.5 Automatic counters and numbering</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">12.6.1 Markers: the 'marker-offset' property</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">12.6.2 Lists: the 'list-style-type', 'list-style-image',
+'list-style-position', and 'list-style' properties</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">13.2.1 Page margins</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">13.2.2 Page size: the 'size' property</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">13.2.3 Crop marks: the 'marks' property</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">13.2.4 Left, right, and first pages</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">13.2.5 Content outside the page box</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">13.3 Page breaks</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">Named pages are only supported for block-level and table
+elements, which are not inside of a table and have an ancestor with the
+<code>region</code> property set to <code>body</code>.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">13.4 Cascading in the page context</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">14 Colors and Backgrounds</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">15 Fonts</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">@font-face and descriptors are not supported.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">16 Text</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">17 Tables</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">Inline tables are not supported. Anonymous table objects are
+only supported for missing table groups and missing table cells in a row, on
+the condition that there are table column elements. Audio rendering is not
+supported.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">18 User interface</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">19 Aural style sheets</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+      </tbody>
+    </table>
+
+    <h2>Properties</h2>
+
+    <table class="specifications" rules="all">
+      <col width="2*" span="1" />
+      <col width="1*" span="1" />
+      <col width="2*" align="justify" span="1" />
+      <thead>
+        <tr>
+          <th rowspan="1" colspan="1">Property</th>
+          <th rowspan="1" colspan="1">Implemented</th>
+          <th rowspan="1" colspan="1">Remarks and restrictions</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">azimuth</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">background</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">background-attachment</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">background-color</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">background-image</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">background-position</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">background-repeat</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-bottom</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-bottom-color</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-bottom-style</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-bottom-width</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-collapse</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">Not for <code>inline-table</code>.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-color</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-left</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-left-color</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-left-style</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-left-width</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-right</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-right-color</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-right-style</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-right-width</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-spacing</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">Not for <code>inline-table</code>.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-style</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-top</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-top-color</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-top-style</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">border-top-width</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">borded-width</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">bottom</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">caption-side</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">clear</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">clip</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">color</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">content</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">counter-increment</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">counter-reset</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">cue</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">cue-after</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">cue-before</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">cursor</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">direction</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">display</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">The values <code>run-in</code>, <code>compact</code>
+and <code>inline-table</code> are not supported. The <code>marker</code> value
+is supported with the limiation that the value <code>auto</code> for the
+<code>width</code> property is not. Markers also don't work with floats.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">elevation</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">empty-cells</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">float</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">fonts</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">font-family</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">font-size</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">font-size-adjust</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">font-stretch</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">font-style</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">font-variant</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">font-weight</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">height</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">A percentage value for the height of a block, which is in another
+block with an explicit height, will be treated as <code>auto</code>. This stems
+from the fact that in this case a block has to be split in a fo:block-container
+and a nested fo:block, because there are properties that don't apply to both of
+them. The inner original block will therefore have a parent without an
+explicit height specification. The latter has moved to the surrounding
+fo:block-container.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">left</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">letter-spacing</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">line-height</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">list-style</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">See individual properties.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">list-style-image</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">list-style-position</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">A list should be uniform. Specifying different values for
+different list items will produce undesired results.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">list-style-type</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">
+            <p>Only the values <code>disc</code>, <code>circle</code>,
+<code>square</code>, <code>decimal</code>, <code>lower-roman</code>,
+<code>upper-roman</code>, <code>lower-alpha</code>, <code>lower-latin</code>,
+<code>upper-alpha</code>, <code>upper-latin</code> and <code>none</code> are
+supported. The additional glyphs defined in [<a href="#CSS3L" shape="rect"><acronym>CSS3L</acronym></a>] are also supported. Those are
+<code>box</code>, <code>check</code>, <code>diamond</code> and
+<code>hyphen</code>.</p>
+            <p>A list should be uniform. Specifying different values for
+different list items will produce undesired results.</p>
+          </td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">margin</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">margin-bottom</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">margin-left</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">margin-right</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">margin-top</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">marker-offset</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">marks</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">max-height</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">max-width</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">min-height</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">min-width</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">orphans</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">outline</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">outline-color</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">outline-style</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">outline-width</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">overflow</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">padding</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">padding-bottom</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">padding-left</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">padding-right</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">padding-top</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">page</td>
+          <td rowspan="1" colspan="1">partial</td>
+          <td rowspan="1" colspan="1">Only for block-level and table elements, which are not inside
+of a table and have an ancestor with the <code>region</code> property set to
+<code>body</code>.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">page-break-after</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">page-break-before</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">page-break-inside</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">pause</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">pause-after</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">pause-before</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">pitch</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">play-during</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">play-range</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">position</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">quotes</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">richness</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">right</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">size</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">speak</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">speak-header</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">speak-numeral</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">speak-punctuation</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">speech-rate</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">stress</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">table-layout</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">text-align</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">text-decoration</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">text-indent</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">text-transform</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">top</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">unicode-bibi</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">vertical-align</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">visibility</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">voice-family</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">volume</td>
+          <td rowspan="1" colspan="1">no</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">white-space</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">widows</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">width</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">word-spacing</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">z-index</td>
+          <td rowspan="1" colspan="1">yes</td>
+          <td rowspan="1" colspan="1" />
+        </tr>
+      </tbody>
+    </table>
+
+    <div class="separator" />
+
+    <h1>Extensions<span /></h1>
+
+    <p>The extension features of the tool mostly pertain to page-oriented
+aspects. Care has been taken to not introduce new syntax. There are, however, a
+number of new properties. Those are normally safely ignored by browsers. In the
+case where there would be an impact on the layout produced by browsers, the
+properties can be confined to the <q>print</q> medium through @media rules.</p>
+
+    <h2>Page Regions</h2>
+
+    <p>This extension introduces &xslfo;-compatible page regions. Regions can
+be defined by placing a <code>region</code> property on an element. The allowed
+values are <code>bottom</code>, <code>left</code>, <code>right</code>,
+<code>top</code> and <code>body</code>. At least one element with the
+<code>region</code> property set to <code>body</code> should be present in the
+document.<span class="footnote-reference" /><span class="footnote-body">The
+&xhtml; User Agent style sheet sets this property to the <code>body</code>
+element.</span> Page sequences are only generated for the content of such an
+element. The regions other than the body region must be the first direct
+children of the body region. Otherwise they are ignored. In the case of
+&xhtml;, for example, this means that they should come at the
+beginning of the <code>body</code> element.</p>
+
+    <p>On top of that, either the <code>width</code> property, for left and
+right regions, or the <code>height</code> property, for top and bottom regions,
+should be defined. They will determine the dimensions of the page
+regions. The default value for <code>width</code> is <q>20mm</q>. For
+<code>height</code> it is <q>10mm</q>.</p>
+
+    <p>The extension property <code>precedence</code> is also available for
+the top and bottom regions. Its value can be <code>true</code> or
+<code>false</code>, the latter being the initial value. The property says
+whether the width of the top or bottom region is equal to that of the page
+reference area or if they give way to the left and right regions.</p>
+
+    <p>The regions work together with the @page rules, of which there should be
+at least one. It is possible to specify different regions, which correspond to
+the different page types in the style sheet. This can be achieved by also
+specifying the <code>page</code> property, which is a standard &css2; property.
+Consider the following example:</p>
+
+    <pre xml:space="preserve">  div.bottom-left, div.bottom-right { display: none; }
+
+  @media print
+  {
+    div.bottom-left
+    {
+      height: 15mm;
+      page: left;
+      region: bottom;
+      text-align: left;
+    }
+
+    div.bottom-right
+    {
+      height: 15mm;
+      page: right;
+      region: bottom;
+      text-align: right;
+    }
+
+    span.page:before { content: counter(page); }
+  }</pre>
+
+    <p>This says that on left pages the bottom region is left-aligned, while
+on right pages it is right-aligned. The <code>span</code> element is used in
+the following region definitions:</p>
+
+    <pre xml:space="preserve">  <div class="bottom-left">
+    <p>&nbsp;</p>
+    <div><span class="page"/></div>
+  </div>
+
+  <div class="bottom-right">
+    <p>&nbsp;</p>
+    <div><span class="page"/></div>
+  </div></pre>
+
+    <p>The <code>page</code> property bears a kind of inheritance mechanism.
+For any page the regions with the most specific <code>page</code> property will
+be selected. A region without a <code>page</code> property is the least
+specific. A named page is more specific and the values <code>left</code> and
+<code>right</code> are yet more specific. After this comes the new pseudo page
+<code>blank</code>, which is for blank pages that are generated because of page
+positioning constraints such as left and right. The first page of a chapter,
+for example, is sometimes forced to be a right page. This can produce an extra
+blank page for the previous chapter. In fact, this maps directly to the &xslfo;
+blank pages. There are special values, which are even more specific, such as
+<code>first-right</code>, <code>blank-left</code>,
+<code>left-<page-name></code>, etc. If, for example, there is no bottom
+region for <code>first-right</code>, but there is one for <code>first</code>,
+the latter will be selected if the first page happens to on the right. See
+section <a href="#page-prop" shape="rect"><q><i>page</i></q></a> for the
+precise precedence rules.</p>
+
+    <p>In order for the top, bottom, left and right region elements not to
+interfere with the normal flow it is best to set their display type to
+<code>none</code>.</p>
+
+    <h2>Page Numbering</h2>
+
+    <p>The two special counters <code>page</code> and <code>pages</code> in
+this tool are taken over from the &css3; Paged Media Module (see also [<a href="#CSS3P" shape="rect"><acronym>CSS3P</acronym></a>]). The <code>page</code> can be
+used just like any other counter, except that it is confined to the regions.
+The following example shows a document with a preface and a body. Each reset
+the page count. The preface has a lower Roman numbering style, while the body
+uses the decimal style. If the body page didn't reset the counter,
+numbering would continue from the preface, but with a change of style.</p>
+
+    <pre xml:space="preserve">  <?xml version="1.0" encoding="UTF-8"?>
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+      <title></title>
+      <style type="text/css">
+  @page preface
+  {
+    counter-reset: page;
+    margin: 10%;
+  }
+
+  @page body
+  {
+    counter-reset: page;
+    margin: 10%;
+  }
+  
+  div.bottom-preface
+  {
+    page: preface;
+    region: bottom;
+  }
+  
+  div.bottom-body
+  {
+    page: body;
+    region: bottom;
+  }
+  
+  div.bottom-preface > span.page:before
+  {
+    content: counter(page, lower-roman);
+  }
+  
+  div.bottom-body > span.page:before
+  {
+    content: counter(page, decimal);
+  }
+  
+  div.preface { page: preface; }
+  div.body { page: body; }
+      </style>
+    </head>
+    <body>
+      <div class="bottom-preface"><span class="page"/></div>
+      <div class="bottom-body"><span class="page"/></div>
+      <div class="preface">
+        <p>Text.</p>
+      </div>
+      <div class="body">
+        <p>Text.</p>
+      </div>
+    </body>
+  </html></pre>
+
+    <p>When switching between named pages you can control how the ending named
+page sequence should be terminated with the extension property
+<code>force-page-count</code>. For example, it some page sequence produces five
+pages, you can force the sequence to produce six pages by setting the property
+to <code>even</code>. An extra blank page will then be generated before starting
+the new page sequence. If you don't want such behaviour, you should set the
+property to <code>no-force</code>, since the initial value is
+<code>auto</code>.</p>
+
+    <h2>Page References</h2>
+
+    <p>You sometimes want to write phrases like <q>The diagram on page 19
+...</q>. The &csstoxslfo; tool provides this functionality through the
+<code>page-ref</code> function, which can be used in the <code>content</code>
+property. Its only parameter is the name of an attribute that contains the &id;
+of another element. The function call will be replaced with the number of the
+page that element is on.</p>
+
+    <p>In &xhtml; it is a bit more complicated to achieve the desired result,
+because there aren't many extension attributes available for it. The
+following fragment shows how it can be done:</p>
+
+    <pre xml:space="preserve">  <img id="img1" src="file:///t.png"/>
+  ...
+  <span class="page-ref"><span class="img1"/></span></pre>
+
+    <p>The accompanying style sheet rule would then be:</p>
+
+    <pre xml:space="preserve">  span.page-ref > span:before { content: page-ref(class); }</pre>
+
+    <h2>Leaders</h2>
+
+    <p>It is possible to use &xslfo; leaders through the display type
+<code>leader</code>. The properties defined in section 7.21 of [<a href="#XSLFO" shape="rect">&xslfo;</a>] (<q>Leader and Rule Properties</q>) can be used in
+a &css; style sheet, with the exception that the <code>leader-length</code>
+property cannot have a length range as a value. If you want to create table of
+contents lines or something similar, you also need the &xslfo; property
+<code>text-align-last</code>, described in section 7.15.10 of
+[<a href="#XSLFO" shape="rect">&xslfo;</a>]. The following example shows how a table of
+contents line could be made in &xhtml;.</p>
+
+    <pre xml:space="preserve">  <div class="toc">
+    <a href="#chapter1">Title of Chapter 1</a>
+    <span class="leader"/>
+    <span class="page-ref"><span class="chapter1"/></span>
+  </div></pre>
+
+    <p>The piece of style sheet that goes with it is:</p>
+
+    <pre xml:space="preserve">  div.toc
+  {
+    text-align-last: justify;
+  }
+
+  span.leader
+  {
+    display: leader;
+    leader-pattern: dots;
+    leader-pattern-width: 5pt;
+  }
+
+  span.page-ref > span:before
+  {
+    content: page-ref(class);
+  }</pre>
+
+    <h2>Named Strings</h2>
+
+    <p>Named strings, as described in [<a href="#CSS3G" shape="rect"><acronym>CSS3G</acronym></a>], are supported in &csstoxslfo;.
+This consists of the <code>string-set</code> property,
+with which contents can be captured, and the <code>string()</code> function.
+The latter can occur in the value of the <code>content</code> property. The
+<code>string-set</code> property accepts values which are similar to those of
+the <code>content</code> property. There is an additional keyword
+<code>contents</code>, which is replaced with the string value of the element
+carrying the <code>string-set</code> property.</p>
+
+    <p>The following is a simple &xhtml; example of how you can create a running
+header that refers to the current chapter.</p>
+
+    <pre xml:space="preserve">  <body>
+    <div class="top">
+      <span class="mark"/>
+    </div>
+    ...
+    <h1>Chapter Title</h1>
+    ...
+  </body></pre>
+
+    <p>Here is the bit of style sheet that does it:</p>
+
+    <pre xml:space="preserve">  div.top
+  {
+    region: top;
+    display: none;
+  }
+
+  div.top > span.mark:before { content: string(mark); }
+
+  h1 { string-set: mark contents; }</pre>
+
+    <h2>Hyphenation</h2>
+
+    <p>Text can be hyphenated through the <code>hyphenate</code> property,
+which is inherited. The possible values are <code>true</code> and
+<code>false</code>. Hyphenation is turned off by default.</p>
+
+    <h2>Footnotes</h2>
+
+    <p>It is possible to produce footnotes using the
+<code>footnote-reference</code> and <code>footnote-body</code> display types.
+The former is displayed in the flow, while
+the latter goes to the footnote area at the bottom of the page. When a footnote
+body occurs it must be either immediately preceded by a footnote reference or
+have a <code>:before</code> pseudo element with the display type
+<code>footnote-reference</code>. Otherwise
+it is treated as if the display were <code>none</code>. Whitespace between a
+footnote reference and body is gobbled. A footnote reference can also occur on
+its own.</p>
+
+    <p>The contents of both the footnote reference and body is free. Both
+display types exist to give you complete control over the contents and style.
+Usually some footnote counter is used, as shown in the example below. There is
+an extra counter style <code>footnote</code>, which produces symbols, such as
+an asterix, dagger, etc.</p>
+
+    <pre xml:space="preserve">  h1 { counter-reset: footnote; }
+
+  span.footnote-body
+  {
+    display: footnote-body;
+    font-size: 0.83em;
+  }
+
+  span.footnote-body:before
+  {
+    content: counter(footnote);
+    padding-right: 1em;
+  }
+
+  span.footnote-reference
+  {
+    display: footnote-reference;
+  }
+
+  span.footnote-reference:before
+  {
+    counter-increment: footnote;
+    content: counter(footnote);
+    font-size: 0.83em;
+    vertical-align: super;
+  }</pre>
+
+    <p>In the document a footnote would then look like this:</p>
+
+    <pre xml:space="preserve">  <p>Paragraph text.<span class="footnote-reference"/><span
+  class="footnote-body">Footnote text.</span></p></pre>
+
+    <p>You might find it cumbersome to have to place a footnote reference in
+front of every footnote body. It can be avoided, at the expense of formatting
+control however. You can define a <code>:before</code> pseudo element for the
+footnote body and give it the display type <code>footnote-reference</code>.
+Whatever contents it generates will then be used for the reference in the flow,
+as well as in the footnote body at the bottom of the page. As a consequence,
+the style is constrained by the fact that it must be decent for both contexts.
+The style sheet becomes a bit simpler:</p>
+
+    <pre xml:space="preserve">  h1 { counter-reset: footnote; }
+
+  span.footnote-body
+  {
+    display: footnote-body;
+    font-size: 0.83em;
+  }
+
+  span.footnote-body:before
+  {
+    counter-increment: footnote;
+    content: counter(footnote);
+    display: footnote-reference;
+    font-size: 0.83em;
+    vertical-align: super;
+  }</pre>
+
+    <p>If you want full control over the formatting in both contexts and at the
+same time want to omit the footnote reference elements in the document, the
+solution is to pre-process the document. The transformation is rather
+trivial.</p>
+
+    <h2>Orientation</h2>
+
+    <p>You can rotate text with the <code>orientation</code> property. This
+works only for block, table and table cell elements. The possible values are 0,
+90, 180, 270, -90, -180, -270. They represent the degrees in the
+counter-clockwise direction. The initial value is 0.</p>
+
+    <h2>List Style Types</h2>
+
+    <p>The glyphs for the <code>list-style-type</code> property, as defined in
+[<a href="#CSS3L" shape="rect"><acronym>CSS3L</acronym></a>], are implemented.</p>
+
+    <h2>Multicolumn</h2>
+
+    <p>With the properties <code>column-count</code>, which must be strictly
+positive, and <code>column-gap</code>, which is a length, a multi-column layout
+can be specified for a page. Both properties are allowed in an @page rule. As a
+consequence, if you want to switch between column modes, you have to switch
+pages as well.</p>
+
+    <p>With the <code>column-span</code> property a blocks and tables, that
+are not themselves inside of another table, can be made to span all the columns
+of a multi-column page. The allowed values for the property are <code>all</code>
+and <code>none</code>.</p>
+
+    <h2>Change Bars</h2>
+
+    <p>The change bar properties introduced in
+[<a href="#XSLFO11" shape="rect"><acronym>XSL-FO11</acronym></a>] are available for
+<code>:before</code> and <code>:after</code> pseudo elements. For the latter,
+only the <code>change-bar-class</code> property is relevant.
+The following is a simple example:</p>
+
+    <pre xml:space="preserve">  p.changed:before
+  {
+    change-bar-class: changed;
+    change-bar-style: solid; /* initial value is none */
+    change-bar-width: 0.2pt;
+  }
+
+  p.changed:after { change-bar-class: changed; }</pre>
+
+    <p>Note that this feature only works for &xep4; at the moment.</p>
+
+    <h2>Links</h2>
+
+    <p>The <code>link</code> property can have the name of an attribute as
+its value. The value of that attribute will be used for the generated link, as
+the target &url; or the internal target &id;, if it is an
+<acronym>IDREF</acronym> attribute, which distinguishes it from a relative
+&url;. Likewise, the value of the <code>anchor</code> property can be the name
+of an attribute, the value of which will become the target &id;. This way an
+internal link destination can be created. For example:</p>
+
+    <pre xml:space="preserve">  a[href] { link: href; }
+  a[name] { anchor: name; }</pre>
+
+    <h2>Graphics</h2>
+
+    <p>An external graphic can be included in a document through the display
+type <code>graphic</code>, which is an inline level display type. The elements
+marked with it are <q>replaced elements</q>. As a consequence, the properties
+<code>height</code> and <code>width</code> apply. The &xslfo;
+properties <code>content-height</code>, <code>content-width</code>,
+<code>content-type</code>, <code>scaling</code> and <code>scaling-method</code>
+are also supported. Their definition is in [<a href="#XSLFO" shape="rect">&xslfo;</a>]. The
+property <code>src</code> is interpreted differently. Its value should be the
+name of an attribute that has a &uri; for a value. For the &xhtml; element
+<code>img</code>, for example, the User Agent style sheet contains the
+following:</p>
+
+    <pre xml:space="preserve">  img
+  {
+    content-height: scale-to-fit;
+    content-width: scale-to-fit;
+    display: graphic;
+    scaling: uniform;
+    src: src;
+  }</pre>
+
+    <h2>Column And Row Spanning</h2>
+
+    <p>In &xhtml; one can specify column and row spanning with the
+<code>colspan</code> and <code>rowspan</code> attributes on the <code>td</code>
+and <code>th</code> elements. It is, however, also possible to apply &css; to
+other &xml; vocabularies. Hence, there should be an equivalent feature in &css;
+to express this. The extension properties <code>colspan</code> and
+<code>rowspan</code> serve that purpose. They can be used for elements with the
+display type <code>table-cell</code>.</p>
+
+    <h2>Proportional Column Widths</h2>
+
+    <p>Again in &xhtml; it is possible to say that a column should occupy a
+relative portion of the total table width. It is done by setting the
+<code>width</code> attribute to a number, followed by an asterix. If we have,
+for example, three columns with the widths <q>1*</q>, <q>2*</q> and <q>3*</q>,
+they occupy 1, 2 and 3 sixth of the table width respectively. This is not part
+of the &html; specification, but it is a widely supported feature.</p>
+
+    <p>In order to provide it for other &xml; vocabularies then &xhtml;, the
+unit <code>pcw</code>, which stands for <q>proportional column width</q>, is
+available for the <code>width</code> property of an element with the display
+type <code>table-column</code>.</p>
+
+    <h2>Repeating Table Headers And Footers</h2>
+
+    <p>By default table headers and footers are repeated when a table spans
+several pages. You can suppress this by setting the
+<code>table-omit-header-at-break</code> and
+<code>table-omit-footer-at-break</code> properties to <code>true</code>
+respectively.</p>
+
+    <h2>CSS3 Namespaces</h2>
+
+    <p>Namespaces for selectors, as defined in [<a href="#CSS3S" shape="rect"><acronym>CSS3S</acronym></a>], are implemented. This means you
+can use namespace prefixes in element selectors and attribute conditions. The
+prefixes are separated from the local name with a pipe sign (<q>|</q>).</p>
+
+    <p>The namespaces are declared with the <code>@namespace</code> rule, which
+should always come right after the <code>@import</code> rules if there are any.
+In the following example the &xhtml; namespace has been declared as the default
+namespace. Next to that, the DeltaXML namespace is declared with the prefix
+<q>dx</q>. You also see the use of the <q>attr</q> function with an attribute
+that has a prefix.</p>
+
+    <pre xml:space="preserve">  @namespace url(http://www.w3.org/1999/xhtml);
+  @namespace dx
+    url(http://www.deltaxml.com/ns/well-formed-delta-v1);
+
+  *[dx|delta=add], dx|new
+  {
+    text-decoration: underline;
+  }
+
+  *[dx|delta=delete], dx|old
+  {
+    text-decoration: line-through;
+  }
+
+  p[dx|delta]:before
+  {
+    content: attr(dx|delta);
+    display: marker;
+    marker-offset: 0.5em;
+    text-align: right;
+  }</pre>
+
+    <h2>Wrappers</h2>
+
+    <p>When processing &xml; in general you might encounter elements which
+represent pure structure, i.e. they are not directly related to layout. For such
+elements there shouldn't be any formatting objects in the output. Normally you
+would have to pre-process the document in order to get rid of them in the proper
+way.</p>
+
+    <p>The display type <code>wrapper</code> is introduced to cope with common
+cases. When an element has this display type, it will not contribute any
+formatting objects. However, its inherited properties will be passed on to its
+child elements, according to the property inheritance rules.</p>
+
+    <p>With respect to &xml; processing, a wrapper seems to be
+<q>transparant</q>. Note however that, while a wrapper can occur anywhere, it
+influences &css; selector matching. For instance, it will interfere with
+<q>direct sibling</q> and <q>direct child</q> selectors.</p>
+
+    <h2>Foreign Elements</h2>
+
+    <p>With the display type <code>foreign</code> it is possible to transfer
+part of a document unmodified to an <code>fo:instream-foreign-object</code>
+element. This may be useful for elements that are in another namespace than
+that of the document itself and which are supported by the &xslfo; processor.
+Typical examples are &svg; and MathML.</p>
+
+    <h2 id="property-specs">Property Specifications</h2>
+
+    <h3 id="anchor-prop">anchor</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><identifier> | attr(X)</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">block-level and inline-level elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><identifier></dt>
+      <dd>The qualified name of an attribute, the value of which is the target
+&id;. This type of value is <i>deprecated</i>, because it doesn't support
+namespace prefixes.</dd>
+
+      <dt>attr(X)</dt>
+      <dd>This returns the value of the attribute of the subject with the
+qualified name X. The &css3; namespace prefixes are supported. The value is the
+target &id;.</dd>
+    </dl>
+
+    <h3 id="change-bar-class-prop">change-bar-class</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><name></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none, value required</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">before and after pseudo elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><name></dt>
+      <dd>An NCName, as defined in [<a href="#NAMES" shape="rect">&names;</a>], to allow
+pairing of before and after elements, which don't have to belong to the same
+element. This way a change bar context is created.</dd>
+    </dl>
+
+    <h3 id="change-bar-color-prop">change-bar-color</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><color></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">the value of the <code>color</code> property</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">before pseudo elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><color></dt>
+      <dd>Specifies the color of the change bar.</dd>
+    </dl>
+
+    <h3 id="change-bar-offset-prop">change-bar-offset</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><length></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">6pt</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">before pseudo elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><length></dt>
+      <dd>Gives the distance from the edge of the column area containing the
+text that is marked as changed to the center of the generated change bar. A
+positive distance is directed away from the column region and into the margin
+regardless of the <code>change-bar-placement</code> property. Relative lengths
+(i.e., percentage values and lengths with units of <q>em</q>) are not permitted
+for the value of this property.</dd>
+    </dl>
+
+    <h3 id="change-bar-placement-prop">change-bar-placement</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">left | right | inside | outside | alternate</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">start</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">before pseudo elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>alternate</dt>
+      <dd>When there are exactly two columns, the change bar will be offset
+from the left edge of all column one areas and the right edge of all column two
+areas; when there are any other number of columns, this value is equivalent to
+<q>outside</q>.</dd>
+
+      <dt>inside</dt>
+      <dd>If the page binding edge is on the left-edge, the change bar
+will be offset from the left edge of all column areas. If the binding is the
+right-edge, the change bar will be offset from the right edge of all column
+areas. If the page binding edge is on neither the left-edge nor right-edge, the
+change bar will be offset from the left edge of all column areas.</dd>
+
+      <dt>left</dt>
+      <dd>The change bar will be offset from the left edge of all column
+areas.</dd>
+
+      <dt>outside</dt>
+      <dd>If the page binding edge is on the left-edge, the change bar
+will be offset from the right edge of all column areas. If the binding is the
+right-edge, the change bar will be offset from the left edge of all column
+areas. If the page binding edge is on neither the left-edge nor right-edge, the
+change bar will be offset from the right edge of all column areas.</dd>
+
+      <dt>right</dt>
+      <dd>The change bar will be offset from the right edge of all column
+areas.</dd>
+    </dl>
+
+    <h3 id="change-bar-style-prop">change-bar-style</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><border-style></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">before pseudo elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <h3 id="change-bar-width-prop">change-bar-width</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><border-width></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">medium</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">before pseudo elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><border-width></dt>
+      <dd>Relative lengths (i.e., percentage values and lengths with units of
+<q>em</q>) are not permitted for the value of this property.</dd>
+    </dl>
+
+    <h3 id="colspan-prop">colspan</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><integer></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">1</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">table cells</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><integer></dt>
+      <dd>Expresses the number of columns the table cell will span. The value
+must be larger than or equal to 1.</dd>
+    </dl>
+
+    <h3 id="column-count-prop">column-count</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><integer> | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">1</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">the page context</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><integer></dt>
+      <dd>The value must be larger than or equal to 1.</dd>
+    </dl>
+
+    <h3 id="column-gap-prop">column-gap</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><length> | <percentage> | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">12.0pt</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">the page context</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">refer to the width of the body region</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><length></dt>
+      <dd>This is an unsigned length, If a negative value has been specified a
+value of 0pt will be used.</dd>
+
+      <dt><percentage></dt>
+      <dd>The value is a percentage of the width of the body region.</dd>
+    </dl>
+
+    <h3 id="column-span-prop">column-span</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">none | all | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">block elements which are not in table elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>all</dt>
+      <dd>This element spans all columns of a multi-column region.</dd>
+
+      <dt>none</dt>
+      <dd>This element does not span multiple columns of a multi-column
+region.</dd>
+    </dl>
+
+    <h3 id="content-height-prop">content-height</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">auto | scale-to-fit | <length> |
+<percentage> | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">auto</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">graphic elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">intrinsic height</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>auto</dt>
+      <dd>The content-height should be the intrinsic content-height.</dd>
+
+      <dt>scale-to-fit</dt>
+      <dd>A size of the content-height equal to the height of the viewport.
+This implies a certain scaling factor to be applied onto the content.</dd>
+
+      <dt><length></dt>
+      <dd>An absolute size for the content-height. This implies a certain
+scaling factor to be applied onto the content.</dd>
+
+      <dt><percentage></dt>
+      <dd>A percentage representing a scaling factor applied to the intrinsic
+height.</dd>
+    </dl>
+
+    <h3 id="content-type-prop">content-type</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">auto | <string></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">auto</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">graphic elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">intrinsic height</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>auto</dt>
+      <dd>No identification of the content-type. The User Agent may determine
+it by <q>sniffing</q> or by other means.</dd>
+
+      <dt><string></dt>
+      <dd>A specification of the content-type in terms of a mime-type, which
+has the form <q>content-type:</q> followed by a mime content-type, e.g.,
+content-type="content-type:image/svg+xml".</dd>
+    </dl>
+
+    <h3 id="content-width-prop">content-width</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">auto | scale-to-fit | <length> |
+<percentage> | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">auto</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">graphic elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">intrinsic width</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>auto</dt>
+      <dd>The content-width should be the intrinsic content-width.</dd>
+
+      <dt>scale-to-fit</dt>
+      <dd>A size of the content-width equal to the width of the viewport.
+This implies a certain scaling factor to be applied onto the content.</dd>
+
+      <dt><length></dt>
+      <dd>An absolute size for the content-width. This implies a certain
+scaling factor to be applied onto the content.</dd>
+
+      <dt><percentage></dt>
+      <dd>A percentage representing a scaling factor applied to the intrinsic
+width.</dd>
+    </dl>
+
+    <h3 id="display-prop">display</h3>
+
+    <p>This section specifies additional values for the property.</p>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">footnote-body | footnote-reference | foreign | graphic | leader | wrapper</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">inline</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">all elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>footnote-body</dt>
+      <dd>The contents of the element goes to the footnote area. The element
+must be either immediately preceded by an element of type
+<code>footnote-reference</code> or have a <code>:before</code> pseudo element
+of that type. Otherwise it is treated as if its display type
+were <code>none</code>. Whitespace between a footnote reference and body is
+removed. In case a pseudo element is used, the contents it generates is
+displayed in the flow, as well as in the footnote body.</dd>
+
+      <dt>footnote-reference</dt>
+      <dd>This is an inline variant. Its contents is displayed in the flow. It
+can occur without a following <code>footnote-body</code> element.</dd>
+
+      <dt>foreign</dt>
+      <dd>If an element has this display type, it is placed unmodified in an
+<code>fo:instream-foreign-object</code> element.</dd>
+
+      <dt>graphic</dt>
+      <dd>This display type is used to include external graphics. It is an
+inline level display type. Elements marked with it are replaced elements.</dd>
+
+      <dt>leader</dt>
+      <dd>This display type is used to produce &xslfo; leaders. It is an inline
+level display type.</dd>
+
+      <dt>wrapper</dt>
+      <dd>An element with this display type doesn't contribute any formatting
+objects. Its inherited properties are nevertheless inherited by its
+subtree.</dd>
+    </dl>
+
+    <h3 id="force-page-count-prop">force-page-count</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">auto | even | odd | end-on-even |
+end-on-odd | no-force | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">auto</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">the page context</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <p>The property is used to impose a constraint on the number of pages
+in a page sequence. In the event that this constraint is not satisfied, an
+additional page will be added to the end of the sequence. This page becomes the
+<q>last</q> page of that sequence.</p>
+
+    <dl>
+      <dt>auto</dt>
+      <dd>Force the last page in this page sequence to be an odd page if the
+initial page number of the next page sequence is even. Force it to be an even
+page if the initial page number of the next page sequence is odd. If there is
+no next page sequence or if the value of its initial page number is <q>auto</q>
+do not force any page.</dd>
+
+      <dt>even</dt>
+      <dd>Force an even number of pages in this page sequence.</dd>
+
+      <dt>odd</dt>
+      <dd>Force an odd number of pages in this page sequence.</dd>
+
+      <dt>end-on-even</dt>
+      <dd>Force the last page in this page sequence to be an even page.</dd>
+
+      <dt>end-on-odd</dt>
+      <dd>Force the last page in this page sequence to be an odd page.</dd>
+
+      <dt>no-force</dt>
+      <dd>Do not force either an even or an odd number of pages in this
+page sequence.</dd>
+    </dl>
+
+    <h3 id="hyphenate-prop">hyphenate</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">false | true | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">false</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">block-level and inline-level elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>false</dt>
+      <dd>Hyphenation is not active for the text in this element.</dd>
+
+      <dt>true</dt>
+      <dd>Hyphenation is active for the text in this element.</dd>
+    </dl>
+
+    <h3 id="initial-page-number-prop">initial-page-number</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">auto | auto-odd | auto-even | <integer>
+| inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">auto</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">the page context</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>auto</dt>
+      <dd>The initial number shall be set to 1 if no previous page-sequence
+exists in the document. If a preceding page-sequence exists, the initial number
+will be one greater than the last number for that sequence.</dd>
+
+      <dt>auto-odd</dt>
+      <dd>A value is determined in the same manner as for <q>auto</q>. If that
+value is an even number 1 is added.</dd>
+
+      <dt>auto-even</dt>
+      <dd>A value is determined in the same manner as for <q>auto</q>. If that
+value is an odd number 1 is added.</dd>
+
+      <dt><integer></dt>
+      <dd>A positive integer. If a negative or non-integer value is provided,
+the value will be rounded to the nearest integer value greater than or equal to
+1.</dd>
+    </dl>
+
+    <h3 id="leader-alignment-prop">leader-alignment</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">none | reference-area | page | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">leader elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <p>Specifies whether leader elements having identical content and property
+values shall have their patterns aligned with each other, with respect to their
+common reference-area or page. For leader elements where the
+<code>leader-pattern</code> property is specified as <code>dots</code> or as
+<code>use-content</code>, this property will be honored. If the leader elements
+is aligned, the left-edge of each cycle of the repeated pattern will be placed
+on the left-edge of the next cycle in the appropriate pattern-alignment
+grid.</p>
+
+    <dl>
+      <dt>none</dt>
+      <dd>Leader-pattern has no special alignment.</dd>
+
+      <dt>page</dt>
+      <dd>Leader-pattern is aligned as if it began on the current page's
+left-edge.</dd>
+
+      <dt>reference-area</dt>
+      <dd>Leader-pattern is aligned as if it began on the current
+reference-area's content-rectangle left-edge.</dd>
+    </dl>
+
+    <h3 id="leader-length-prop">leader-length</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><length> | <percentage> | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">12.0pt</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">leader elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">refer to the width of the content-rectangle of the parent
+area.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><length></dt>
+      <dd>Sets the length of a leader element.</dd>
+
+      <dt><percentage></dt>
+      <dd>Sets the length of a leader element to a percentage of the
+width of the content-rectangle of the parent area.</dd>
+    </dl>
+
+    <h3 id="leader-pattern-prop">leader-pattern</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">space | rule | dots | use-content |
+inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">space</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">leader elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>dots</dt>
+      <dd>Leader is to be filled with a repeating sequence of dots. The choice
+of dot character is dependent on the user agent.</dd>
+
+      <dt>rule</dt>
+      <dd>Leader is to be filled with a rule. If this choice is selected, the
+<code>rule-thickness</code> and <code>rule-style</code> properties are used to
+set the leader's style.</dd>
+
+      <dt>space</dt>
+      <dd>Leader is to be filled with blank space.</dd>
+
+      <dt>use-content</dt>
+      <dd>Leader is to be filled with a repeating pattern as specified by the
+children of the leader element.</dd>
+    </dl>
+
+    <h3 id="leader-pattern-width-prop">leader-pattern-width</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">use-font-metrics | <length> |
+<percentage> | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">use-font-metrics</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">leader elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">refer to the width of the content-rectangle of the parent
+area.</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>use-font-metrics</dt>
+      <dd>Use the width of the leader-pattern as determined from its font
+metrics.</dd>
+
+      <dt><length></dt>
+      <dd>Sets the length for leader-pattern-repeating. The leader will have an
+inline-space inserted after each pattern cycle to account for any difference
+between the width of the pattern as determined by the font metrics and the
+width specified in this property. If the length specified is less than the
+value that would be determined via the <code>use-font-metrics</code> choice,
+the value of this property is computed as if <code>use-font-metrics</code>
+choice had been specified.</dd>
+
+      <dt><percentage></dt>
+      <dd>Sets the length for leader-pattern-repeating to a percentage of the
+width of the content-rectangle of the parent area.</dd>
+    </dl>
+
+    <p>For leader elements where the
+<code>leader-pattern</code> property is specified as <code>dots</code> or as
+<code>use-content</code>, this property will be honored.</p>
+
+    <h3 id="link-prop">link</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><identifier> | attr(X)</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">block-level and inline-level elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><identifier></dt>
+      <dd>The qualified name of an attribute, the value of which is either a
+target &id; or a &uri;. It is considered as an &id; if the attribute is of type
+<acronym>IDREF</acronym>. This way a distinction is made with a relative &url;.
+Note that the attribute type information should be available. This requires a
+document type definition. This type of value is <i>deprecated</i>, because it
+doesn't support namespace prefixes.</dd>
+
+      <dt>attr(X)</dt>
+      <dd>This returns the value of the attribute of the subject with the
+qualified name X. The &css3; namespace prefixes are supported. The value is
+considered as an &id; if the attribute is of type <acronym>IDREF</acronym>.
+This way a distinction is made with a relative &url;. Note that the attribute
+type information should be available. This requires a document type
+definition.</dd>
+    </dl>
+
+    <h3 id="list-style-type-prop">list-style-type</h3>
+
+    <p>This section specifies additional glyph values for the property.</p>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">box | check | diamond | hyphen</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">disc</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">elements with <q>display: list-item</q></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>box</dt>
+      <dd>A hollow square.</dd>
+
+      <dt>check</dt>
+      <dd>A check mark.</dd>
+
+      <dt>diamond</dt>
+      <dd>A filled diamond.</dd>
+
+      <dt>hyphen</dt>
+      <dd>A hyphen bullet.</dd>
+    </dl>
+
+    <h3 id="orientation-prop">orientation</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">0 | 90 | 180 | 270 | -90 | -180 | -270 |
+inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">0</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">block, table and table cell elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>0</dt>
+      <dd>The material in this element is not rotated.</dd>
+
+      <dt>90</dt>
+      <dd>The material in this element is rotated 90 degrees counter-clockwise
+with respect to the containing block element.</dd>
+
+      <dt>180</dt>
+      <dd>The material in this element is rotated 180 degrees counter-clockwise
+with respect to the containing block element.</dd>
+
+      <dt>270</dt>
+      <dd>The material in this element is rotated 270 degrees counter-clockwise
+with respect to the containing block element.</dd>
+
+      <dt>-90</dt>
+      <dd>The material in this element is rotated 90 degrees clockwise
+with respect to the containing block element.</dd>
+
+      <dt>-180</dt>
+      <dd>The material in this element is rotated 180 degrees clockwise
+with respect to the containing block element.</dd>
+
+      <dt>-270</dt>
+      <dd>The material in this element is rotated 270 degrees clockwise
+with respect to the containing block element.</dd>
+    </dl>
+
+    <h3 id="page-prop">page</h3>
+
+    <p>This section specifies the property in the context of static regions. It
+defines the pages to which the static region applies. If more than one static
+region of the same kind (left, right, top or bottom) applies to a page, the
+most specific is selected, i.e. the one for which the most conditions are
+fulfilled. Each property value expresses a number of conditions.</p>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">auto | first | last | left | right |
+blank | first-left | first-right | last-left | last-right |
+blank-left | blank-right | first-<identifier> | last-<identifier> |
+left-<identifier> | right-<identifier> | blank-<identifier> |
+first-left-<identifier> | first-right-<identifier> |
+last-left-<identifier> | last-right-<identifier> |
+blank-left-<identifier> | blank-right-<identifier> |
+<identifier></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">auto</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">static regions</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>auto</dt>
+      <dd>Applies to any page.</dd>
+
+      <dt>blank</dt>
+      <dd>Applies if the page is a blank page. Blank pages can be generated,
+for example, when page breaks are forced to left or right pages.</dd>
+
+      <dt>blank-left</dt>
+      <dd>Applies if the page is a blank and a left page.</dd>
+
+      <dt>blank-right</dt>
+      <dd>Applies if the page is a blank and a right page.</dd>
+
+      <dt>blank-<identifier></dt>
+      <dd>Applies if the page is a blank and a named page, with the name
+set to the specified identifier.</dd>
+
+      <dt>blank-left-<identifier></dt>
+      <dd>Applies if the page is a blank, left and named page, with the name
+set to the specified identifier.</dd>
+
+      <dt>blank-right-<identifier></dt>
+      <dd>Applies if the page is a blank, right and named page, with the name
+set to the specified identifier.</dd>
+
+      <dt>first</dt>
+      <dd>Applies if the page is a first page.</dd>
+
+      <dt>first-left</dt>
+      <dd>Applies if the page is a first and a left page.</dd>
+
+      <dt>first-right</dt>
+      <dd>Applies if the page is a first and a right page.</dd>
+
+      <dt>first-<identifier></dt>
+      <dd>Applies if the page is a first and a named page, with the name
+set to the specified identifier. When the document switches to a named page
+sequence, using the <code>page</code> property in the regular way, the first
+page of that sequence is a first page.</dd>
+
+      <dt>first-left-<identifier></dt>
+      <dd>Applies if the page is a first, left and named page, with the name
+set to the specified identifier.</dd>
+
+      <dt>first-right-<identifier></dt>
+      <dd>Applies if the page is a first, right and named page, with the name
+set to the specified identifier.</dd>
+
+      <dt>last</dt>
+      <dd>Applies if the page is a last page.</dd>
+
+      <dt>last-left</dt>
+      <dd>Applies if the page is a last and a left page.</dd>
+
+      <dt>last-right</dt>
+      <dd>Applies if the page is a last and a right page.</dd>
+
+      <dt>last-<identifier></dt>
+      <dd>Applies if the page is a last and a named page, with the name
+set to the specified identifier. When the document switches to a named page
+sequence, using the <code>page</code> property in the regular way, the last
+page of that sequence is a last page.</dd>
+
+      <dt>last-left-<identifier></dt>
+      <dd>Applies if the page is a last, left and named page, with the name
+set to the specified identifier.</dd>
+
+      <dt>last-right-<identifier></dt>
+      <dd>Applies if the page is a last, right and named page, with the name
+set to the specified identifier.</dd>
+
+      <dt>left</dt>
+      <dd>Applies if the page is a left page.</dd>
+
+      <dt>left-<identifier></dt>
+      <dd>Applies if the page is a left and a named page, with the name
+set to the specified identifier.</dd>
+
+      <dt>right</dt>
+      <dd>Applies if the page is a right page.</dd>
+
+      <dt>right-<identifier></dt>
+      <dd>Applies if the page is a right and a named page, with the name
+set to the specified identifier.</dd>
+
+      <dt><identifier></dt>
+      <dd>Applies if the page is a named page, with the name
+set to the specified identifier.</dd>
+    </dl>
+
+    <h3 id="precedence-prop">precedence</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">false | true | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">false</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">static top and bottom regions</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>false</dt>
+      <dd>The width of the region is reduced by the incursions of the left and
+right regions.</dd>
+
+      <dt>true</dt>
+      <dd>The height of the left and right regions is reduced by the incursions
+of this region.</dd>
+    </dl>
+
+    <h3 id="region-prop">region</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">body | left | right | top | bottom | none</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">all elements, but see prose</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>body</dt>
+      <dd>There should be one element with this value for the property. For the
+contents of this element the page sequences will be generated.</dd>
+
+      <dt>bottom</dt>
+      <dd>This element becomes the bottom static region. The pages for which
+this is the case can be limited through the
+<a href="#page-prop" shape="rect"><code>page</code></a> property.</dd>
+
+      <dt>left</dt>
+      <dd>This element becomes the left static region.</dd>
+
+      <dt>none</dt>
+      <dd>The element is not a region.</dd>
+
+      <dt>right</dt>
+      <dd>This element becomes the right static region.</dd>
+
+      <dt>top</dt>
+      <dd>This element becomes the top static region.</dd>
+    </dl>
+
+    <p>The static region elements should be the first child elements of the
+body region. In other words, they should precede all elements which are not
+static regions, otherwise their <code>region</code> property is ignored. The
+property is also ignored if there are no @page rules. In that case the default
+page set-up is generated.</p>
+
+    <h3 id="rowspan-prop">rowspan</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><integer></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">1</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">table cells</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><integer></dt>
+      <dd>Expresses the number of rows the table cell will span. The value
+must be larger than or equal to 1.</dd>
+    </dl>
+
+    <h3 id="rule-style-prop">rule-style</h3>
+
+    <p>This property applies only if the <code>leader-pattern</code> property is
+specified as <code>rule</code>.</p>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">none | dotted | dashed | solid | double |
+groove | ridge | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">solid</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">leader elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>dashed</dt>
+      <dd>The rule is a series of short line segments.</dd>
+
+      <dt>dotted</dt>
+      <dd>The rule is a series of dots.</dd>
+
+      <dt>double</dt>
+      <dd>The rule is two solid lines. The sum of the two lines and the space
+between them equals the value of the <code>rule-thickness</code> property.</dd>
+
+      <dt>groove</dt>
+      <dd>The rule looks as though it were carved into the canvas. (Top/left
+half of the rule's thickness is the color specified; the other half is
+white.)</dd>
+
+      <dt>none</dt>
+      <dd>No rule, forces <code>rule-thickness</code> to 0.</dd>
+
+      <dt>ridge</dt>
+      <dd>The opposite of <q>groove</q>, the rule looks as though it were
+coming out of the canvas. (Bottom/right half of the rule's thickness is the
+color specified; the other half is white.)</dd>
+
+      <dt>solid</dt>
+      <dd>The rule is a single line segment.</dd>
+    </dl>
+
+    <h3 id="rule-thickness-prop">rule-thickness</h3>
+
+    <p>This property applies only if the <code>leader-pattern</code> property is
+specified as <code>rule</code>.</p>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><length></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">1.0pt</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">leader elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">yes</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><length></dt>
+      <dd>The rule-thickness is always perpendicular to its length-axis. The
+rule is thickened equally above and below the line's alignment position.</dd>
+    </dl>
+
+    <h3 id="scaling-prop">scaling</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">uniform | non-uniform | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">uniform</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">graphic elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">intrinsic width</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>non-uniform</dt>
+      <dd>Scaling need not preserve the intrinsic aspect ratio.</dd>
+
+      <dt>uniform</dt>
+      <dd>Scaling should preserve the intrinsic aspect ratio.</dd>
+    </dl>
+
+    <h3 id="scaling-method-prop">scaling-method</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">auto | integer-pixels |
+resample-any-method | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">auto</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">graphic elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">intrinsic width</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>auto</dt>
+      <dd>The User Agent is free to choose either resampling, integer scaling,
+or any other scaling method.</dd>
+
+      <dt>integer-pixels</dt>
+      <dd>The User Agent should scale the image such that each pixel in the
+original image is scaled to the nearest integer number of device-pixels that
+yields an image less-then-or-equal-to the image size derived from the
+content-height, content-width, and scaling properties.</dd>
+
+      <dt>resample-any-method</dt>
+      <dd>The User Agent should resample the supplied image to provide an image
+that fills the size derived from the <code>content-height</code>,
+<code>content-width</code>, and <code>scaling</code> properties. The user agent
+may use any sampling method.</dd>
+    </dl>
+
+    <h3 id="src-prop">src</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1"><identifier> | attr(X)</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none, value required</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">graphic elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt><identifier></dt>
+      <dd>The qualified name of an attribute, the value of which is a
+&uri;. This type of value is <i>deprecated</i>, because it doesn't support
+namespace prefixes.</dd>
+
+      <dt>attr(X)</dt>
+      <dd>This returns the value of the attribute of the subject with the
+qualified name X. The &css3; namespace prefixes are supported. The value is a
+&uri;.</dd>
+    </dl>
+
+    <h3 id="string-set-prop">string-set</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">none | <identifier> contents |
+<identifier> <content></td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">none</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">all elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>none</dt>
+      <dd>No named string is set.</dd>
+
+      <dt><identifier> contents</dt>
+      <dd>The string named by the identifier is set to the textual contents of
+the element.</dd>
+
+      <dt><identifier> <content></dt>
+      <dd>The string named by the identifier is set to the result of the
+evaluation of the expression in <content>. The syntax for the expression is
+the same as that for the <code>content</code> property.</dd>
+    </dl>
+
+    <h3 id="text-align-last-prop">text-align-last</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">center | inside | justify | left |
+outside | relative | right | inherit</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">relative</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">block elements</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>center</dt>
+      <dd>Specifies that the contents is to be centered horizontally.</dd>
+
+      <dt>inside</dt>
+      <dd>If the page binding edge is on the left-edge, the alignment will be
+left. If the binding is on the right-edge, the alignment will be right. If
+neither, use left alignment.</dd>
+
+      <dt>justify</dt>
+      <dd>Specifies that the contents is to be expanded to fill the available
+width.</dd>
+
+      <dt>left</dt>
+      <dd>Specifies that the contents is to be aligned on the left-edge.</dd>
+
+      <dt>outside</dt>
+      <dd>If the page binding edge is on the left-edge, the alignment will be
+right. If the binding is on the right-edge, the alignment will be left. If
+neither, use right alignment.</dd>
+
+      <dt>relative</dt>
+      <dd>If <code>text-align</code> is <code>justify</code>, then the
+alignment of the last line, and of any line ending in <span style="font-family: serif-title">U+000A</span>, will be left. If <code>text-align</code> is not
+<code>justify</code>, <code>text-align-last</code> will use the value of
+<code>text-align</code>.</dd>
+
+      <dt>right</dt>
+      <dd>Specifies that the contents is to be aligned on the right-edge.</dd>
+    </dl>
+
+    <h3 id="table-omit-footer-at-break-prop">table-omit-footer-at-break</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">false | true</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">false</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">tables</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>false</dt>
+      <dd>This property specifies that the footer should not be ommitted.</dd>
+
+      <dt>true</dt>
+      <dd>This property specifies that the footer should be ommitted.</dd>
+    </dl>
+
+    <h3 id="table-omit-header-at-break-prop">table-omit-header-at-break</h3>
+
+    <table class="property">
+      <col width="1*" span="1" />
+      <col width="5*" span="1" />
+      <tbody>
+        <tr>
+          <td rowspan="1" colspan="1">Value:</td>
+          <td rowspan="1" colspan="1">false | true</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Initial:</td>
+          <td rowspan="1" colspan="1">false</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Applies to:</td>
+          <td rowspan="1" colspan="1">tables</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Inherited:</td>
+          <td rowspan="1" colspan="1">no</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Percentages:</td>
+          <td rowspan="1" colspan="1">N/A</td>
+        </tr>
+        <tr>
+          <td rowspan="1" colspan="1">Media:</td>
+          <td rowspan="1" colspan="1">print</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <dl>
+      <dt>false</dt>
+      <dd>This property specifies that the header should not be ommitted.</dd>
+
+      <dt>true</dt>
+      <dd>This property specifies that the header should be ommitted.</dd>
+    </dl>
+
+    <h2 id="misc-specs">Miscellaneous Specifications</h2>
+
+    <h3 id="blank-pseudo-class">The :blank Pseudo-class</h3>
+
+    <p>The :blank pseudo-class is available to specify properties in the page
+context for blank pages. Those can be generated, for example, when pages are
+forced to start at the left or right.</p>
+
+    <h3 id="last-pseudo-class">The :last Pseudo-class</h3>
+
+    <p>The :last pseudo-class is available to specify properties in the page
+context for last pages. This is analogous to the :first pseudo-class.</p>
+
+    <h3 id="page-properties">The background, border and padding page properties</h3>
+
+    <p>The background, border and padding properties, as defined in [<a href="#CSS3P" shape="rect"><acronym>CSS3P</acronym></a>], are implemented. They
+are, however, not entirely compatible with that specification. The
+implementation applies the properties to the <code>region-body</code>, because
+in &xslfo; they are not defined at the page master level.</p>
+
+    <h3 id="page-counters">The page And pages Counters</h3>
+
+    <p>The page counter represents the current page number, while the pages
+counter represents the total number of pages in the document. Both can be used
+in static regions only. The page counter may be reset in the page context.</p>
+
+    <h3 id="page-ref-function">The page-ref Function</h3>
+
+    <p>The page-ref function can be used in the <code>content</code>
+property. Its only parameter is either the qualified name of an attribute that
+contains the &id; of another element, or the attr(X) function, where X is the
+qualified name of such an attribute. The former is deprecated, because it
+doesn't support &css3; namespace prefixes, while the latter does. The function
+call will be replaced with the number of the page the target element is on.</p>
+
+    <h3 id="string-function">The string Function</h3>
+
+    <p>The string function produces the string that was saved with a
+<code>string-set</code> property. Its argument is the name used in a
+<code>string-set</code> property. If a named string is set more than once on a
+page, the first occurrence will be returned by the string function.</p>
+
+    <h3 id="footnote-counter">The footnote Counter Style</h3>
+
+    <p>This counter style produces symbols in the following order: *, †,
+‡, §, ||, ¶, #, **, ††,
+‡‡, §§. If the counter value if larger than the
+number of symbols in the preceding list, the * symbol is generated.</p>
+
+    <h3 id="pcw-unit">The pcw Unit</h3>
+
+    <p>This unit is available for the <code>width</code> property of an element
+with display type <code>table-column</code>. It expresses the proportional width
+for a table column. The value should be divided by the sum of all the present
+proportional widths, which itself is equal to the width of the table minus all
+fixed column widths.</p>
+
+    <h3 id="namespaces">The @namespace Rule</h3>
+
+    <p>With the @namespace rule a namespace can be declared, with or without a
+prefix. In the latter case it is the default namespace. The scope of a declared
+namespace is limited to the style sheet entity in which it is declared. The
+ at namespace rule should come right after the @import rules if there are any and
+before all other rules. An @namespace rule has an optional prefix argument,
+which is an identifier, followed by a mandatory &uri; specification. Consult
+[<a href="#CSS3S" shape="rect"><acronym>CSS3S</acronym></a>] to learn how namespaces work
+with selectors.</p>
+
+    <div class="separator" />
+
+    <h1>Embedding In An Application<span /></h1>
+
+    <h2>API Specification</h2>
+
+    <h3 id="CSSToXSLFOFilter">be.re.css.CSSToXSLFOFilter</h3>
+
+    <p>This class extends 
+<a href="http://www.saxproject.org/apidoc/org/xml/sax/helpers/XMLFilterImpl.html" shape="rect">org.​xml.​sax.​helpers.​XMLFilterImpl</a>.
+The source of the &sax; events that are sent through the filter must be
+namespace aware. If it is an &xml; parser this option must be turned on.</p>
+
+    <h4>Constructors</h4>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" shape="rect">java.util.Map</a>
+userAgentParameters,</div>
+      <div class="p"><a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLReader.html" shape="rect">XMLReader
+parent,</a></div>
+      <div class="p">boolean debug</div>
+      <div>) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <dl>
+      <dt id="baseUrl">baseUrl</dt>
+      <dd>
+        <p>The <code>baseUrl</code> is used to resolve relative &url;s. This
+includes references to style sheets as well as other resources. Because of the
+latter the resulting &xslfo; document is not relocatable. The relative &url;s
+in the input document are transformed into absolute ones using the
+<code>baseUrl</code>. This copes with the case where the input document is an
+anonymous stream that refers to style sheets in a relative way. The set of
+style sheets is then relocatable through the <code>baseUrl</code>.</p>
+
+        <p>If <code>baseUrl</code> is <code>null</code> no resolution of
+relative &url;s will be done.</p>
+    </dd>
+
+      <dt id="userAgentStyleSheet">userAgentStyleSheet</dt>
+      <dd>The default style sheet for the filter. It is used in the way
+described in section 6.4 of <a href="#CSS2" shape="rect">[&css2;]</a>. If the parameter is
+<code>null</code>, a default style sheet for &xhtml; is used.</dd>
+
+      <dt id="userAgentParameters">userAgentParameters</dt>
+      <dd>These are defined in section <a href="#uapar" shape="rect"><q><i>User Agent
+Parameters</i></q></a>.</dd>
+
+      <dt>parent</dt>
+      <dd>The parent filter. It must not be <code>null</code>.</dd>
+
+      <dt id="debug">debug</dt>
+      <dd>If <code>debug</code> is <code>true</code> a number of debug
+files are dumped. They show the results of the internal processing steps.</dd>
+    </dl>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" shape="rect">java.util.Map</a>
+userAgentParameters,</div>
+      <div class="p"><a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLReader.html" shape="rect">XMLReader
+parent</a></div>
+      <div>) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter with <code>debug</code> set to
+<code>false</code>.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet,</div>
+      <div class="p"><a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLReader.html" shape="rect">XMLReader
+parent</a></div>
+      <div>) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter with an empty <code>userAgentParameters</code> map
+and <code>debug</code> set to <code>false</code>.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl,</div>
+      <div class="p"><a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLReader.html" shape="rect">XMLReader
+parent</a></div>
+      <div>) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter with <code>userAgentStyleSheet</code> set to
+<code>null</code>, an empty <code>userAgentParameters</code> map
+and <code>debug</code> set to <code>false</code>.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter</div>
+      <div>(</div>
+      <div class="p"><a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLReader.html" shape="rect">XMLReader
+parent</a></div>
+      <div>) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter with <code>baseUrl</code> and
+<code>userAgentStyleSheet</code> set to
+<code>null</code>, an empty <code>userAgentParameters</code> map
+and <code>debug</code> set to <code>false</code>.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" shape="rect">java.util.Map</a>
+userAgentParameters,</div>
+      <div class="p">boolean debug</div>
+      <div>) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter without a parent.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" shape="rect">java.util.Map</a>
+userAgentParameters</div>
+      <div>) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter without a parent and <code>debug</code> set to
+<code>false</code>.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet</div>
+      <div>) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter without a parent, an empty
+<code>userAgentParameters</code> map and <code>debug</code> set to
+<code>false</code>.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter(<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl) throws <a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter without a parent, <code>userAgentStyleSheet</code>
+set to <code>null</code>, an empty <code>userAgentParameters</code> map and
+<code>debug</code> set to <code>false</code>.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOFilter() throws
+<a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Constructs the filter without a parent, <code>baseUrl</code> and
+<code>userAgentStyleSheet</code> set to <code>null</code>, an empty
+<code>userAgentParameters</code> map and <code>debug</code> set to
+<code>false</code>.</p>
+
+    <h4>Methods</h4>
+
+    <div class="method">
+      <div id="getBaseUrl">public <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+getBaseUrl()</div>
+    </div>
+
+    <p>Returns the base &url; of the filter. It is either set in a constructor
+or with the <a href="#setBaseUrl" shape="rect"><code>setBaseUrl</code></a> method.</p>
+
+    <div class="method">
+      <div id="getParameters">public <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" shape="rect">java.util.Map</a>
+getParameters()</div>
+    </div>
+
+    <p>Returns the User Agent parameters of the filter. They are either set in
+a constructor or with the <a href="#setUserAgentStyleSheet" shape="rect"><code>setUserAgentStyleSheet</code></a>
+method.</p>
+
+    <div class="method">
+      <div id="getUserAgentStyleSheet">public <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+getUserAgentStyleSheet()</div>
+    </div>
+
+    <p>Returns the User Agent style sheet of the filter. It is either set in a
+constructor or with the <a href="#setUserAgentStyleSheet" shape="rect"><code>setUserAgentStyleSheet</code></a>
+method.</p>
+
+    <div class="method">
+      <div id="setBaseUrl">public void setBaseUrl(<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl)</div>
+    </div>
+
+    <p>Sets the base &url; of the filter. The base &url; is used to resolve
+relative &url;s in the input document. See also <a href="#baseUrl" shape="rect"><code>baseUrl</code></a>.</p>
+
+    <div class="method">
+      <div id="setParameters">public void setParameters(<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" shape="rect">java.util.Map</a>
+parameters)</div>
+    </div>
+
+    <p>Sets the User Agent parameters of the filter. See also <a href="#userAgentParameters" shape="rect"><code>userAgentParameters</code></a>.</p>
+
+    <div class="method">
+      <div id="setUserAgentStyleSheet">public void setUserAgentStyleSheet(<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet)</div>
+    </div>
+
+    <p>Sets the User Agent style sheet of the filter. See also <a href="#userAgentStyleSheet" shape="rect"><code>userAgentStyleSheet</code></a>.</p>
+
+    <h3 id="CSSToXSLFOException">be.re.css.CSSToXSLFOException</h3>
+
+    <p>This class extends <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Exception.html" shape="rect">java.lang.Exception</a>.</p>
+
+    <h4>Constructors</h4>
+
+    <div class="method">
+      <div>public CSSToXSLFOException(<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Exception.html" shape="rect">java.lang.Exception
+e</a>)</div>
+    </div>
+
+    <p>This is just a wrapper around <code>e</code>.</p>
+
+    <div class="method">
+      <div>public CSSToXSLFOException(<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html" shape="rect">java.lang.String
+message</a>)</div>
+    </div>
+
+    <p>The parameter will be returned by the method <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Throwable.html#getMessage()" shape="rect"><code>getMessage</code></a>.</p>
+
+    <h3 id="CSSToXSLFO">be.re.css.CSSToXSLFO</h3>
+
+    <p>This class contains a few convenience methods with which an &xml; filter
+set-up can be avoided, because they do it for you.</p>
+
+    <h4>Methods</h4>
+
+    <div class="method">
+      <div>public static void convert</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.InputStream.html" shape="rect">java.io.InputStream</a>
+in,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.OutputStream.html" shape="rect">java.io.OutputStream</a>
+out,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+baseUrl,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+catalog,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" shape="rect">java.util.Map</a>
+userAgentParameters,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL[]</a>
+preprocessors,</div>
+      <div class="p">boolean validate,</div>
+      <div class="p">boolean debug</div>
+      <div>) throws <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.IOException.html" shape="rect">java.io.IOException</a>,
+<a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <dl>
+      <dt>in</dt>
+      <dd>The input document. It must not be <code>null</code>.</dd>
+
+      <dt>out</dt>
+      <dd>The output document. It must not be <code>null</code>.</dd>
+
+      <dt>baseUrl</dt>
+      <dd>See <a href="#baseUrl" shape="rect"><code>baseUrl</code></a>.</dd>
+
+      <dt>userAgentStyleSheet</dt>
+      <dd>See <a href="#userAgentStyleSheet" shape="rect"><code>userAgentStyleSheet</code></a>.</dd>
+
+      <dt>catalog</dt>
+      <dd>The catalog used to resolve entities during &xml; parsing. It must
+have the format defined by SGML Open Technical
+Resolution <acronym>TR9401:1997</acronym>. Only the <q>PUBLIC</q> and
+<q>SYSTEM</q> keywords are supported. It may be <code>null</code>.</dd>
+
+      <dt>userAgentParameters</dt>
+      <dd>See <a href="#userAgentParameters" shape="rect"><code>userAgentParameters</code></a>.</dd>
+
+      <dt>preprocessors</dt>
+      <dd>An array of &xslt; style sheets. The input goes through them in the
+specified order and before the
+<a href="#CSSToXSLFOFilter" shape="rect"><code>CSSToXSLFOFilter</code></a>.</dd>
+
+      <dt>validate</dt>
+      <dd>Turns on validation during &xml; parsing of the input document.</dd>
+
+      <dt>debug</dt>
+      <dd>See <a href="#debug" shape="rect"><code>debug</code></a>.</dd>
+    </dl>
+
+    <div class="method">
+      <div>public static void convert</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.InputStream.html" shape="rect">java.io.InputStream</a>
+in,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.OutputStream.html" shape="rect">java.io.OutputStream</a>
+out,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/URL.html" shape="rect">java.net.URL</a>
+userAgentStyleSheet</div>
+      <div>) throws <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.IOException.html" shape="rect">java.io.IOException</a>,
+<a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Calls the first variant of <code>convert</code> with
+<code>baseUrl</code>, <code>catalog</code> and <code>preprocessors</code> set
+to <code>null</code>, <code>validate</code> and <code>debug</code> set to false
+and an empty <code>userAgentParameters</code> map.</p>
+
+    <div class="method">
+      <div>public static void convert</div>
+      <div>(</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.InputStream.html" shape="rect">java.io.InputStream</a>
+in,</div>
+      <div class="p"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.OutputStream.html" shape="rect">java.io.OutputStream</a>
+out</div>
+      <div>) throws <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io.IOException.html" shape="rect">java.io.IOException</a>,
+<a href="#CSSToXSLFOException" shape="rect">CSSToXSLFOException</a></div>
+    </div>
+
+    <p>Calls the first variant of <code>convert</code> with
+<code>baseUrl</code>, <code>userAgentStyleSheet</code>, <code>catalog</code> and
+<code>preprocessors</code> set to <code>null</code>, <code>validate</code> and
+<code>debug</code> set to false and an empty <code>userAgentParameters</code>
+map.</p>
+
+    <h2>Examples</h2>
+
+    <p>Since <a href="#CSSToXSLFOFilter" shape="rect">CSSToXSLFOFilter</a> is derived from 
+<a href="http://www.saxproject.org/apidoc/org/xml/sax/helpers/XMLFilterImpl.html" shape="rect">org.​xml.​sax.​helpers.​XMLFilterImpl.</a>,
+it implements all &sax; event interfaces, as well as <a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLFilter.html" shape="rect">org.​xml.​sax.​XMLFilter</a>.
+As a consequence, the filter can occur in input and output filter chains.</p>
+
+    <h3>Example 1</h3>
+
+    <p>The most straight-forward scenario is an application that reads the
+input document from a file and that writes an &xslfo; document into another
+file. For this we need an &xml; parser that can produce &sax; events. The
+parser implements the <a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLReader.html" shape="rect">org.​xml.​sax.​XMLReader</a>
+interface, so we can make it the parent of <a href="#CSSToXSLFOFilter" shape="rect">CSSToXSLFOFilter</a>.</p>
+
+    <p>In order to create a parser, we first have to set up the parser factory
+and make it namespace-aware. This happens at the lines 6 through 8. The filter
+can now be created with the input document as the base &url; (in case any
+relative &url;s need to be resolved) and an &xml; parser as its parent. This is
+done at the lines 9 through 14.</p>
+
+    <p>We now have to prepare the output part. We use an &xslt; transformer
+without a style sheet to copy the &sax; events to the output. The transformer
+must be in a form that accepts &sax; events. This is why a <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/xml/transform/sax/TransformerHandler.html" shape="rect">javax.​xml.​transform.​sax.​TransformerHandler</a>
+is created at lines 15 through 19. It implements the <a href="http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html" shape="rect">org.​xml.​sax.​ContentHandler</a>
+interface. By giving it the output file as a result (lines 20 through 26), the
+&sax; events are transformed in the &xml; syntax.</p>
+
+    <p>The input and output parts can now be connected by setting the content
+handler of the filter to the transformer handler (line 27). The whole chain is
+then activated by calling the <code>parse</code> method, passing it the input
+document in the form of a file. The filter will pass this call onto the parser,
+which is its parent. The parser starts producing &sax; events that go through
+the filter and into the transformer handler.</p>
+
+    <pre xml:space="preserve"> 1 public class Example1
+ 2 {
+ 3   public static void
+ 4   main(String[] args) throws Exception
+ 5   {
+ 6     javax.xml.parsers.SAXParserFactory	factory =
+ 7       javax.xml.parsers.SAXParserFactory.newInstance();
+
+ 8     factory.setNamespaceAware(true);
+
+ 9     be.re.css.CSSToXSLFOFilter	filter =
+10       new be.re.css.CSSToXSLFOFilter
+11       (
+12         new java.io.File(args[0]).toURL(), // base URL.
+13         factory.newSAXParser().getXMLReader()
+14       );
+
+15     javax.xml.transform.sax.TransformerHandler	handler =
+16       (
+17         (javax.xml.transform.sax.SAXTransformerFactory)
+18           javax.xml.transform.TransformerFactory.newInstance()
+19       ).newTransformerHandler();
+
+20     handler.setResult
+21     (
+22       new javax.xml.transform.stream.StreamResult
+23       (
+24         new java.io.File(args[1])
+25       )
+26     );
+
+27     filter.setContentHandler(handler);
+
+28     filter.parse
+29     (
+30       new org.xml.sax.InputSource
+31       (
+32         new java.io.FileInputStream(args[0])
+33       )
+34     );
+35   }
+36 }</pre>
+
+    <h3>Example 2</h3>
+
+    <p>A variation of the previous example is to perform the transformation of
+the &sax; events coming out of the filter to &xml; syntax in another way. In
+the previous example the parser had the control flow and the transformer acted
+as a handler of &sax; events. We can also give the control flow to a
+transformer that reads the input and copies it to the output, because we don't
+give it any style sheet. We need to create a <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/xml/transform/Transformer.html" shape="rect">javax.​xml.​transform.​Transformer</a>.
+It is done at lines 15 through 17. The actual transformation is launched at
+lines 18 through 32. For this to work, we have to wrap our filter in a <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/xml/transform/sax/SAXSource.html" shape="rect">javax.​xml.​transform.​sax.​SAXSource</a>.
+For the transformer it is as if it is going to call an &xml; parser.</p>
+
+    <pre xml:space="preserve"> 1 public class Example2
+ 2 {
+ 3   public static void
+ 4   main(String[] args) throws Exception
+ 5   {
+ 6     javax.xml.parsers.SAXParserFactory	factory =
+ 7       javax.xml.parsers.SAXParserFactory.newInstance();
+
+ 8     factory.setNamespaceAware(true);
+
+ 9     be.re.css.CSSToXSLFOFilter	filter =
+10       new be.re.css.CSSToXSLFOFilter
+11       (
+12         new java.io.File(args[0]).toURL(), // base URL.
+13         factory.newSAXParser().getXMLReader()
+14       );
+
+15     javax.xml.transform.Transformer	transformer =
+16       javax.xml.transform.TransformerFactory.newInstance().
+17         newTransformer();
+
+18     transformer.transform
+19     (
+20       new javax.xml.transform.sax.SAXSource
+21       (
+22         filter, // Acts as the XMLReader.
+23         new org.xml.sax.InputSource
+24         (
+25           new java.io.FileInputStream(args[0])
+26         )
+27       ),
+28       new javax.xml.transform.stream.StreamResult
+29       (
+30         new java.io.File(args[1])
+31       )
+32     );
+33   }
+34 }</pre>
+
+    <h3>Example 3</h3>
+
+    <p>This example shows how a pre-processing step can be added to the filter
+chain. The input document is transformed by the pre-processor and the resulting
+&sax; events go through the conversion filter. The pre-processor is created at
+lines 9 through 13. This one does nothing, i.e. it lets the events go through
+unmodified. In reality you would replace it with a class of your own.</p>
+
+    <p>The pre-processor instead of the filter is now initialised with the
+&xml; parser as its parent. The pre-processor will become the parent of the
+filter, as shown at line 18. When the <code>parse</code> method is called, the
+filter passes the call onto the pre-processor, which in turn passes it onto the
+parser. The &sax; events produced by the parser will then flow through the
+pre-processor, which in turn forwards them, possibly modified, to the
+filter.</p>
+
+    <pre xml:space="preserve"> 1 public class Example3
+ 2 {
+ 3   public static void
+ 4   main(String[] args) throws Exception
+ 5   {
+ 6     javax.xml.parsers.SAXParserFactory	factory =
+ 7       javax.xml.parsers.SAXParserFactory.newInstance();
+
+ 8     factory.setNamespaceAware(true);
+
+ 9     org.xml.sax.helpers.XMLFilterImpl	myPreprocessor =
+10       new org.xml.sax.helpers.XMLFilterImpl
+11       (
+12         factory.newSAXParser().getXMLReader()
+13       );
+
+14     be.re.css.CSSToXSLFOFilter	filter =
+15       new be.re.css.CSSToXSLFOFilter
+16       (
+17         new java.io.File(args[0]).toURL(), // base URL.
+18         myPreprocessor
+19       );
+
+20     javax.xml.transform.sax.TransformerHandler	handler =
+21       (
+22         (javax.xml.transform.sax.SAXTransformerFactory)
+23           javax.xml.transform.TransformerFactory.newInstance()
+24       ).newTransformerHandler();
+
+25     handler.setResult
+26     (
+27       new javax.xml.transform.stream.StreamResult
+28       (
+29         new java.io.File(args[1])
+30       )
+31     );
+
+32     filter.setContentHandler(handler);
+
+33     filter.parse
+34     (
+35       new org.xml.sax.InputSource
+36       (
+37         new java.io.FileInputStream(args[0])
+38       )
+39     );
+40   }
+41 }</pre>
+
+    <h3>Example 4</h3>
+
+    <p>The previous example can be modified in such a way that the
+pre-processor is an &xslt; style sheet. From this style sheet a <a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLFilter.html" shape="rect">org.​xml.​sax.​XMLFilter</a>
+must be made, because it will sit between the &xml; parser and the filter. This
+is shown at lines 9 through 19. The transformer factory is re-used afterwards
+to create also the output handler.</p>
+
+    <pre xml:space="preserve"> 1 public class Example4
+ 2 {
+ 3   public static void
+ 4   main(String[] args) throws Exception
+ 5   {
+ 6     javax.xml.parsers.SAXParserFactory	factory =
+ 7       javax.xml.parsers.SAXParserFactory.newInstance();
+
+ 8     factory.setNamespaceAware(true);
+
+ 9     javax.xml.transform.sax.SAXTransformerFactory	trFactory =
+10       (javax.xml.transform.sax.SAXTransformerFactory)
+11         javax.xml.transform.TransformerFactory.newInstance();
+
+12     org.xml.sax.XMLFilter	myPreprocessor =
+13       trFactory.newXMLFilter
+14       (
+15         new javax.xml.transform.stream.StreamSource
+16         (
+17           new java.io.File(args[2])
+18         )
+19       );
+
+20     myPreprocessor.setParent
+21     (
+22       factory.newSAXParser().getXMLReader()
+23     );
+
+24     be.re.css.CSSToXSLFOFilter	filter =
+25       new be.re.css.CSSToXSLFOFilter
+26       (
+27         new java.io.File(args[0]).toURL(), // base URL.
+28         myPreprocessor
+29       );
+
+30     javax.xml.transform.sax.TransformerHandler	handler =
+31       trFactory.newTransformerHandler();
+
+32     handler.setResult
+33     (
+34       new javax.xml.transform.stream.StreamResult
+35       (
+36         new java.io.File(args[1])
+37       )
+38     );
+
+39     filter.setContentHandler(handler);
+
+40     filter.parse
+41     (
+42       new org.xml.sax.InputSource
+43       (
+44         new java.io.FileInputStream(args[0])
+45       )
+46     );
+47   }
+48 }</pre>
+
+    <h3>Example 5</h3>
+
+    <p>In all previous examples we have been parsing an input document. In some
+applications, however, the data might come from somewhere else. It is possible,
+for example, to synthesize the &xml; from data that resides in the database. In
+such a scenario our filter no longer has a parent but becomes the &sax; event
+handler of some system method, <code>generateReport</code> in this example.<span class="footnote-reference" /><span class="footnote-body">Note
+that a real system method would probably need more than just the filter to do
+its work. It would therefore have more parameters.</span>
+This system method has the control flow. It fetches the data and generates the
+&sax; events. In the case the generated &xml; stream is not suitable for &css;
+conversion, a pre-processor may be specified as the parent of the filter.</p>
+
+    <pre xml:space="preserve"> 1 public class Example5
+ 2 {
+ 3   public static void
+ 4   main(String[] args) throws Exception
+ 5   {
+ 6     be.re.css.CSSToXSLFOFilter	filter =
+ 7       new be.re.css.CSSToXSLFOFilter();
+
+ 8     javax.xml.transform.sax.TransformerHandler	handler =
+ 9       (
+10         (javax.xml.transform.sax.SAXTransformerFactory)
+11           javax.xml.transform.TransformerFactory.newInstance()
+12       ).newTransformerHandler();
+
+13     handler.setResult
+14     (
+15       new javax.xml.transform.stream.StreamResult
+16       (
+17         new java.io.File(args[0])
+18       )
+19     );
+
+20     filter.setContentHandler(handler);
+21     generateReport(filter);
+22   }
+
+23   private static void
+24   generateReport(org.xml.sax.ContentHandler handler)
+25   {
+26   }
+27 }</pre>
+
+    <h3>Example 6</h3>
+
+    <p>It may be the case that you want to synthesize the &xml; stream in a
+system method, which needs the control flow, but that the interface of your
+&xslfo; formatter is such that it also needs the control flow. In other words,
+the formatter is not available in the form of a &sax; event handler, but has
+some method that must be called to perform the actual formatting. At lines 21
+through 31 there a hypothetical example of such a formatter.</p>
+
+    <p>To solve this control flow conflict you can create an adapter that
+implements the <a href="http://www.saxproject.org/apidoc/org/xml/sax/XMLReader.html" shape="rect">org.​xml.​sax.​XMLReader</a>
+interface. Instead of actually parsing some &xml; you let both
+<code>parse</code> methods call your system method. The parameters the latter
+needs are passed through the constructor of the adapter. When the formatter now
+calls the <code>parse</code> method it really ends up calling the system
+method, which synthesizes the &sax; events.</p>
+
+    <pre xml:space="preserve"> 1 public class Example6
+ 2 {
+ 3   public static void
+ 4   main(String[] args) throws Exception
+ 5   {
+ 6     be.re.css.CSSToXSLFOFilter	filter =
+ 7       new be.re.css.CSSToXSLFOFilter
+ 8       (
+ 9         new MyReportGenerator(new Object())
+10       );
+11     MyXSLFOFormatter		myFormatter = new MyXSLFOFormatter();
+
+12     myFormatter.format
+13     (
+14       new javax.xml.transform.sax.SAXSource(filter, null),
+15       new java.io.FileOutputStream(args[0]));
+16   }
+
+17   private static void
+18   generateReport(Object context)
+19   {
+20   }
+
+21   public static class MyXSLFOFormatter
+22   {
+23     public void
+24     format
+25     (
+26       javax.xml.transform.Source source,
+27       java.io.OutputStream out
+28     )
+29     {
+30     }
+31   }
+
+32   public static class MyReportGenerator
+33     extends org.xml.sax.helpers.XMLFilterImpl
+34   {
+35     private Object	context;
+
+36     public
+37     MyReportGenerator(Object context)
+38     {
+39       this.context = context;
+40     }
+
+41     public void
+42     parse(org.xml.sax.InputSource input)
+43       throws org.xml.sax.SAXException, java.io.IOException
+44     {
+45       generateReport(context);
+46     }
+
+47     public void
+48     parse(String systemId)
+49       throws org.xml.sax.SAXException, java.io.IOException
+50     {
+51       generateReport(context);
+52     }
+53   }
+54 }</pre>
+
+    <div class="separator" />
+
+    <h1>Some Techniques<span /></h1>
+
+    <p>A few practical cases of formatting contructs, which are either more
+advanced or not yet very common, are described in this chapter. Gradually, new
+cases will be added. The chapter is some sort of <q>how to</q> section in the
+user guide. The examples use &xhtml; as the input document language.</p>
+
+    <h2>Customising List Labels With Markers</h2>
+
+    <p>The genaration of the labels of an itemised list is somewhat fixed. It
+depends on the value of the <code>list-style-type</code> property.<span class="footnote-reference" /><span class="footnote-body">The
+<code>list-style-image</code> and <code>list-style-position</code> properties
+are not supported by this tool.</span> Sometimes, however, more control is
+required over how the labels look like. This can be achieved through
+markers.</p>
+
+    <p>Basically, you have to specify a <code>:before</code> pseudo element
+with the display type <code>marker</code> in your style sheet for those
+elements you have given the display type <code>list-item</code>. Strictly
+speaking that display type is not needed, but if you are about to convert your
+existing lists, those elements would have that display type.</p>
+
+    <p>In the pseudo element you have control over the formatting of the label.
+The only exception is that the <code>width</code> property must be fixed. The
+tool doesn't support the automatic calculation of the required width. If your
+style sheet doesn't specify a width, a default value will be used. In order to
+not depend on this value, it is best to specify one.</p>
+
+    <p>The following example is an ordered list with a nested ordered list in
+the second item. We are going to change the numbering as well as the alignment
+of the labels.</p>
+
+    <pre xml:space="preserve">  <ol>
+    <li>Item 1</li>
+    <li>Item 2
+      <ol>
+        <li>Subitem 1</li>
+        <li>Subitem 2</li>
+        <li>Subitem 3</li>
+      </ol>
+    </li>
+    <li>Item 3</li>
+  </ol></pre>
+
+    <p>In the style sheet we say that the <code>:before</code> pseudo element
+of any <code>li</code> under a <code>ol</code>, no matter the level, is a
+marker. In there, we increment the counter that is reset for each level of
+<code>ol</code>. We also display it with the <code>lower-roman</code> counter
+style instead of the default style (<code>decimal</code>). This style will show
+the effect of the right alignment of the text inside the label. The
+<code>marker-offset</code> property provides for a bit of space between the
+label and the list item body.</p>
+
+    <p>The <code>width</code> property deserves special attention. First of all
+it defines the width of the labels. Since markers shouldn't influence the
+positioning of the element they are attached to, the labels would stick out to
+the left by the amount of the value of the <code>width</code> property. In
+order to compensate this, we have to add a <code>margin-left</code> with the
+same value to the list item itself.</p>
+
+    <pre xml:space="preserve">  ol { counter-reset: list-counter; }
+  
+  ol li { margin-left: 2em; }
+  
+  ol li:before
+  {
+    content: counter(list-counter, lower-roman) ".";
+    counter-increment: list-counter;
+    display: marker;
+    marker-offset: 0.5em;
+    text-align: right;
+    width: 2em;
+  }</pre>
+
+  <p>The rendered result would like this:</p>
+
+  <ol class="example">
+    <li>Item 1</li>
+    <li>Item 2
+      <ol class="example">
+        <li>Subitem 1</li>
+        <li>Subitem 2</li>
+        <li>Subitem 3</li>
+      </ol>
+    </li>
+    <li>Item 3</li>
+  </ol>
+
+  <h2 class="example">Making Section Numbers <q>Stick Out</q></h2>
+
+  <p>Sometimes the text of the section titles must be aligned with the rest of
+the material, at the left side for example. As consequence, if the titles also
+have section numbers, those will stick out at the left side of the title, into
+the margin, just like the title of the current section. This can be obtained by
+specifying a <code>:before</code> pseudo element for the section titles with
+the display type <code>marker</code>. Because markers shouldn't influence the
+positioning of their associated element, the marker content is prepended. This
+is the piece of style sheet you would need:</p>
+
+  <pre xml:space="preserve">  h2:before
+  {
+    display: marker;
+    marker-offset: 0.5em;
+    padding-right: 0pt;
+    text-align: right;
+    width: 3em;
+  }</pre>
+
+  <h2>This Guide's Page Set-up</h2>
+
+  <p>The page set-up of this guide is rather advanced and is therefore an
+interesting practical case. The difficulty lies in specifying the static
+regions if there are many kinds of pages and if for each of those the static
+regions are different. i.e. very specific.</p>
+
+  <p>In order to avoid an explosion of &css; property specifications for all
+those regions, we can work in a sort of multidimensional way. This is possible
+through the combination of two things. First, we have the &css; cascading
+mechanism, which allows us to centralise common property specifications.
+Second, the can make use of the fact that the <code>class</code> attribute is a
+space-separated list of names. Because of this, an element can belong to
+several classes. These are the static regions of this guide:</p>
+
+  <pre xml:space="preserve">  <div class="first top"/>
+  <div class="title first bottom"/>
+  <div class="title first top"/>
+  <div class="copy-right first bottom"/>
+  <div class="copy-right first top"/>
+  <div class="left bottom"><span/></div>
+  <div class="right bottom"><span/></div>
+  <div class="left top"><span/></div>
+  <div class="right top"><span/></div>
+  <div class="front left bottom"><span/></div>
+  <div class="front right bottom"><span/></div>
+  <div class="blank bottom"/>
+  <div class="blank top"/></pre>
+
+  <p>Neither of them has a lot of content. It is all generated using the
+<code>span</code> element as a hook. For the empty <code>div</code> elements no
+content will be generated. You can see that all regions belong to more than one
+class. The more classes they belong to, the more specific is the set of pages
+they apply to. This is one dimension. The other is whether the static region is
+a top or bottom region.</p>
+
+  <p>The first region, for example, is a bottom region that applies only to the
+first page of the named page sequence called <q>title</q>. This is expressed in
+the style sheet as follows:</p>
+
+  <pre xml:space="preserve">  div.title.first
+  {
+    page: first-title;
+  }</pre>
+
+  <p>The rule applies if <code>class</code> is <q>title</q> <i>and</i>
+<q>first</q>. This is more specific then the following page assignment which
+also occurs in the style sheet:</p>
+
+  <pre xml:space="preserve">  div.first
+  {
+    page: first;
+  }</pre>
+
+  <p>Another example is the pair of bottom regions for left and right pages.
+They apply for left and right pages, no matter the page sequence. For the
+<q>front</q> page sequence, however, there is a more specific version. Making
+it more specific is done by the two page assignments in the following style
+sheet part. The style of the bottom region of the front part is a bit
+different. It display the page numbers in lower Roman. The complete style is
+obtained by cascading all the rules for the class <q>bottom</q>. The first two
+rules define the style for all bottom regions and the last one overrides the
+counter style.</p>
+
+  <pre xml:space="preserve">  div.bottom
+  {
+    height: 3em;
+    padding-top: 2em;
+    region: bottom;
+  }
+
+  div.bottom > span:before
+  {
+    content: counter(page);
+  }
+
+  div.front.left
+  {
+    page: left-front;
+  }
+
+  div.front.right
+  {
+    page: right-front;
+  }
+
+  div.front.bottom > span:before
+  {
+    content: counter(page, lower-roman);
+  }</pre>
+
+  <p>For blank pages we want no static regions at all, at least not visually.
+The presence of the top and bottom regions is defined generally through the
+<code>div.top</code> and <code>div.bottom</code> rules. As a consequence, no
+matter the page sequence, those static regions are always generated. All we
+have to do now is making sure they don't contain anything. This is done by the
+two empty regions with the <code>class</code> attribute set to <q>blank top</q>
+and <q>blank bottom</q> respectively. Those regions are assigned to blank pages
+with the following style sheet part:</p>
+
+  <pre xml:space="preserve">  div.blank
+  {
+    page: blank;
+  }</pre>
+
+  <p>There is one more special construct left to discuss: the absence of static
+top regions on the first page of a chapter. As with blank pages they are not
+really absent. They are merely made empty. It is in fact the first region with
+the classes <q>first</q> and <q>top</q>. This region is assigned to the
+<code>first</code> pseudo page. All chapters are however in the named page
+sequence <q>main</q>. If we do nothing only the first page of the first chapter
+will have an empty top region. We therefore should toggle the <code>page</code>
+property without adding extra pages. This can be achieved by inserting an empty
+<code>div</code> element between the chapters with the class <q>separator</q>.
+The page assignment for that class is <q>separator</q>. This named page is not
+used for anything else. Since the element is empty no page sequence is
+generated. The next <q>main</q> element, however, will start a new page
+sequence.</p>
+
+  <p>After the front matter not only the page number style changes to decimal,
+but the page numbering is also reset. We can't just reset the <code>page</code>
+counter in the <q>main</q> page context, because then the numbering would be
+reset for each chapter. Instead, the style sheet defines a
+<code>main-first</code> @page rule, which contains the same definitions as
+<code>main</code> and a <code>page</code> counter reset on top of it. This page
+is assigned to the first chapter only, using the <code>first-child</code>
+pseudo-class on the <code>h1</code> element.</p>
+
+  <h2>A Two-column Article</h2>
+
+  <p>Many articles and papers are formatted in two column mode. The title,
+abstract, authors, etc. are usually displayed across the two columns. With two
+extension properties it is possible to do this. The <code>column-count</code>
+should be set to <q>2</q> in the page context. The title material can be
+wrapped in a block element for which the <code>column-span</code> property is
+set to <q>all</q>.</p>
+
+  <h2>Initial Capitals</h2>
+
+  <p class="first-letter-example">A typographical effect that is often used are
+initial capitals. It consists of making the first letter of an article or
+chapter stand out by rendering it bigger and perhaps in another font and/or
+colour. In &css; this is supported through the <code>:first-letter</code>
+pseudo element, which is described in section 5.12.2 of
+[<a href="#CSS2" shape="rect">&css2;</a>]. In &csstoxslfo; it is implemented with the
+restriction that letter combinations, which are considered as one letter, are
+not examined. In case you need that, you can always use the Unicode ligature
+characters instead.</p>
+
+  <p>The technique was applied to the previous paragraph using the piece of
+style sheet below. Note the second deviation from the specification being the
+usage of the property <code>vertical-align</code> while the <code>float</code>
+property has the value <code>none</code>. It is allowed in &csstoxslfo; because
+otherwise we have no control over the alignment of the first letter with the
+lines next to it. This depends on the font and will always require some trial
+and error in order to get it right. The values for the other properties are
+obtained in the same way. In fact, for this special case, we work around the
+normal way a glyph is layed out in a line.</p>
+
+  <pre xml:space="preserve">  p:first-letter
+  {
+    font-family: serif-swash;
+    font-size: 46pt;
+    font-style: italic;
+    float: left;
+    line-height: 46pt;
+    padding-right: 6pt;
+    margin-bottom: -12pt;
+    vertical-align: 9pt;
+  }</pre>
+
+  </div>
+
+
+  <div class="back">
+
+    <h1>Special Provisions for XHTML<span /></h1>
+
+    <p>While the tool works for any &xml; vocabulary it does a number of
+things for &xhtml; specifically. Other vocabularies may be supported in the same
+way at some later stage. The items are the following:</p>
+
+    <ul>
+      <li>Non-&css; presentational hints are translated to the corresponding
+&css; rules, as prescribed in section 6.4.4 of
+[<a href="#CSS2" shape="rect">&css2;</a>];</li>
+
+      <li>The <code>lang</code> attribute is honored;</li>
+
+      <li>Hyperlinks are recognized and translated in &xslfo; links;</li>
+
+      <li>The <code>link</code> element can be used to specify external style
+sheets;</li>
+
+      <li>Style sheets can be embedded with the <code>style</code>
+element;</li>
+
+      <li>The <code>style</code> attribute is honored;</li>
+
+      <li>The <code>img</code> element is interpreted and processed;</li>
+
+      <li>The <code>html-header-mark</code> user agent parameter is
+available;</li>
+
+      <li>There is a user agent style sheet for &xhtml; that cascades against
+the one in appendix A of [<a href="#CSS2" shape="rect">&css2;</a>].</li>
+    </ul>
+
+    <div class="separator" />
+
+    <h1>The User Agent Style Sheet<span /></h1>
+
+    <h2>XHTML</h2>
+
+    <pre style="page-break-inside: auto" xml:space="preserve">@import "xhtml.css";
+ at namespace url(http://www.w3.org/1999/xhtml);
+
+ at media print
+{
+  a[href]
+  {
+    color: blue;
+    link: attr(href);
+    text-decoration: none;
+  }
+
+  a[name]
+  {
+    anchor: name;
+  }
+
+  blockquote, dl, ol, p, ul
+  {
+    margin: 0.83em 0pt;
+  }
+
+  blockquote
+  {
+    margin-left: 3em;
+    margin-right: 3em;
+  }
+
+  body
+  {
+    font-family: serif;
+    padding: 0pt;
+    region: body;
+  }
+
+  body:lang(da)
+  {
+    quotes: "\00BB" "\00AB";
+  }
+
+  body:lang(de-DE), body:lang(de-AT)
+  {
+    quotes: "\201E" "\201C" "\201A" "\2018"
+  }
+
+  body, body:lang(en), body:lang(es)
+  {
+    quotes: "\201C" "\201D" "\2018" "\2019";
+  }
+
+  body:lang(fr)
+  {
+    quotes: "\00AB " " \00BB" "\2039 " " \203A";
+  }
+
+  body:lang(it)
+  {
+    quotes: "\00AB " " \00BB";
+  }
+
+  body:lang(nl)
+  {
+    quotes: "\201D" "\201D" "\2019" "\2019";
+  }
+
+  body:lang(no), bodylang:(pt), body:lang(de-CH)
+  {
+    quotes: "\00AB" "\00BB" "\2039" "\203A"
+  }
+
+  body:lang(sv)
+  {
+    quotes: "\00BB" "\00BB";
+  }
+
+  caption
+  {
+    margin: 0.5em 0pt;
+  }
+
+  dt
+  {
+    page-break-after: avoid;
+  }
+
+  h1
+  {
+    font-size: 1.6em;
+    margin-bottom: 0.7em;
+    margin-top: 1.4em;
+  }
+
+  h2
+  {
+    font-size: 1.3em;
+    margin-bottom: 0.6em;
+    margin-top: 1.2em;
+  }
+
+  h3
+  {
+    font-size: 1.1em;
+  }
+
+  h3, h4
+  {
+    margin-bottom: 0.5em;
+    margin-top: 1em;
+  }
+
+  h1, h2, h3, h4, h5, h6
+  {
+    hyphenate: false;
+  }
+
+  hr
+  {
+    border: 0.1pt solid;
+  }
+
+  img
+  {
+    content-height: scale-to-fit;
+    content-width: scale-to-fit;
+    display: graphic;
+    scaling: uniform;
+    src: attr(src);
+  }
+
+  li
+  {
+    margin-bottom: 0.8em;
+    margin-top: 0.8em;
+  }
+
+  li p, li blockquote, li dl, li ol, li ul
+  {
+    margin-bottom: 0.5em;
+    margin-top: 0.5em;
+  }
+
+  li li
+  {
+    margin-bottom: 0.5em;
+    margin-top: 0.5em;
+  }
+
+  li li p, li li blockquote, li li dl, li li ol, li li ul
+  {
+    margin-bottom: 0.3em;
+    margin-top: 0.3em;
+  }
+
+  li li li
+  {
+    margin-bottom: 0.4em;
+    margin-top: 0.4em;
+  }
+
+  li li li p, li li li blockquote, li li li dl, li li li ol,
+    li li li ul
+  {
+    margin-bottom: 0.3em;
+    margin-top: 0.3em;
+  }
+
+  li, p
+  {
+    text-align: justify;
+  }
+
+  pre
+  {
+    font-size: 0.85em;
+  }
+
+  ul
+  {
+    list-style-type: disc;
+  }
+
+  ol li ul, ul li ul
+  {
+    list-style-type: circle;
+  }
+
+  ol li ol li ul, ol li ul li ul, ul li ol li ul, ul li ul li ul
+  {
+    list-style-type: square;
+  }
+
+  q:after
+  {
+    content: close-quote;
+  }
+
+  q:before
+  {
+    content: open-quote;
+  }
+
+  script
+  {
+    display: none;
+  }
+
+  span.section-number
+  {
+    padding-right: 1em;
+  }
+}</pre>
+
+    <h2 style="page-break-before: always">DeltaXML</h2>
+
+    <pre style="page-break-inside: auto" xml:space="preserve">@namespace deltaxml
+  url(http://www.deltaxml.com/ns/well-formed-delta-v1);
+
+ at media print
+{
+  deltaxml|PCDATAnew, deltaxml|PCDATAold
+  {
+    display: inline;
+  }
+
+  deltaxml|exchange, deltaxml|new, deltaxml|old
+  {
+    display: wrapper;
+  }
+
+  *[deltaxml|delta="add"], deltaxml|PCDATAnew, deltaxml|new
+  {
+    text-decoration: underline;
+  }
+
+  *[deltaxml|delta="delete"], deltaxml|PCDATAold, deltaxml|old
+  {
+    text-decoration: line-through;
+  }
+
+  *[deltaxml|delta="add"]:before, deltaxml|PCDATAnew:before,
+    deltaxml|new, *[deltaxml|delta="delete"]:before,
+    deltaxml|PCDATAold:before, deltaxml|old
+  {
+    change-bar-class: changed;
+    change-bar-placement: alternate;
+    change-bar-style: solid;
+    change-bar-width: 0.2pt;
+  }
+
+  *[deltaxml|delta="add"]:after, deltaxml|PCDATAnew:after,
+    *[deltaxml|delta="delete"]:after, deltaxml|PCDATAold:after
+  {
+    change-bar-class: changed;
+  }
+}</pre>
+
+    <h2 style="page-break-before: always">XLink</h2>
+
+    <pre style="page-break-inside: auto" xml:space="preserve">@namespace xlink url(http://www.w3.org/1999/xlink);
+
+ at media print
+{
+  *[xlink|href]
+  {
+    link: attr(xlink|href);
+  }
+}</pre>
+
+    <div class="separator" />
+
+    <h1>References<span /></h1>
+
+    <dl>
+      <dt id="CSS2">[&css2;]</dt>
+      <dd><q><span class="title">Cascading Style Sheets, level 2, CSS2
+Specification</span></q>, W3C
+Recommendation 12 May 1998, Bert Bos, Håkon Wium Lie, Chris Lilley, Ian
+Jacobs,
+<a href="http://www.w3.org/TR/1998/REC-CSS2-19980512" shape="rect">http://www.w3.org/​TR/​1998/​REC-CSS2-19980512</a>.</dd>
+
+      <dt id="CSS3G">[<acronym>CSS3G</acronym>]</dt>
+      <dd><q><span class="title">CSS3 Generated and Replaced Content
+Module</span></q>, W3C Working Draft 14 May 2003, Ian Hickson,
+<a href="http://www.w3.org/TR/2003/WD-css3-content-20030514" shape="rect">http://www.w3.org/​TR/​2003/​WD-css3-content-20030514</a>.</dd>
+
+      <dt id="CSS3L">[<acronym>CSS3L</acronym>]</dt>
+      <dd><q><span class="title">CSS3 module: Lists</span></q>, W3C Working
+Draft 7 November 2002, Ian Hickson, Tantek Çelik,
+<a href="http://www.w3.org/TR/2002/WD-css3-lists-20021107" shape="rect">http://www.w3.org/​TR/​2004/​WD-css3-lists-20021107</a>.</dd>
+
+      <dt id="CSS3P">[<acronym>CSS3P</acronym>]</dt>
+      <dd><q><span class="title">CSS3 Module: Paged Media</span></q>, W3C
+Working Draft 10 October 2006, Håkon Wium Lie, Melinda Grant,
+<a href="http://www.w3.org/TR/2006/WD-css3-page-20061010" shape="rect">http://www.w3.org/​TR/​2006/​WD-css3-page-20061010</a>.</dd>
+
+      <dt id="CSS3S">[<acronym>CSS3S</acronym>]</dt>
+      <dd><q><span class="title">CSS3 Selectors</span></q>, W3C Working Draft 15
+December 2005, Daniel Glazman, Tantek
+Çelik, Ian Hickson, Peter Linss, John Williams, <a href="http://www.w3.org/TR/2005/WD-css3-selectors-20051215" shape="rect">http://www.w3.org/​TR/​2005/​WD-css3-selectors-20051215</a>.</dd>
+
+      <dt id="DELTA">[<acronym>DELTA</acronym>]</dt>
+      <dd><q><span class="title">How DeltaXML Represents Changes to XML
+Files</span></q>, DeltaXML Ltd., <a href="http://www.deltaxml.com/library/how-deltaxml-represents-changes.html" shape="rect">http://www.deltaxml.com/​library/​how-deltaxml-represents-changes.html</a>.</dd>
+
+      <dt id="NAMES">[<acronym>NAMES</acronym>]</dt>
+      <dd><q><span class="title">Namespaces in XML</span></q>, W3C
+Recommendation 14 January 1999, Tim Bray, Dave Hollander, Andrew Layman, <a href="http://www.w3.org/TR/REC-xml-names/" shape="rect">http://www.w3.org/​TR/​REC-xml-names/</a>.</dd>
+
+      <dt id="XHTML">[&xhtml;]</dt>
+      <dd><q><span class="title">XHTML™ 1.0 The Extensible HyperText
+Markup Language (Second Edition)</span></q>, W3C Recommendation 26 January 2000,
+revised 1 August 2002,
+<a href="http://www.w3.org/TR/2002/REC-xhtml1-20020801" shape="rect">http://www.w3.org/​TR/​2002/​REC-xhtml1-20020801</a>.</dd>
+
+      <dt id="XLINK">[<acronym>XLINK</acronym>]</dt>
+      <dd><q><span class="title">XML Linking Language (XLink) Version
+1.0</span></q>, W3C Recommendation 27 June 2001, Steve DeRose, Eve Maler, David
+Orchard, <a href="http://www.w3.org/TR/2001/REC-xlink-20010627" shape="rect">http://www.w3.org/​TR/​2001/​REC-xlink-20010627</a>.</dd>
+
+      <dt id="XSLFO">[&xslfo;]</dt>
+      <dd><q><span class="title">Extensible Stylesheet Language (XSL), Version
+1.0</span></q>, W3C
+Recommendation 15 October 2001, Sharon Adler, Anders Berglund, Jeff Caruso,
+Stephen Deach, Tony Graham, Paul Grosso, Eduardo Gutentag, Alex Milowski, Scott
+Parnell, Jeremy Richman, Steve Zilles,
+<a href="http://www.w3.org/TR/2001/REC-xsl-20011015/" shape="rect">http://www.w3.org/​TR/​2001/​REC-xsl-20011015/</a>.</dd>
+
+      <dt id="XSLFO11">[<acronym>XSL-FO11</acronym>]</dt>
+      <dd><q><span class="title">Extensible Stylesheet Language (XSL), Version
+1.1</span></q>, W3C Recommendation 05 December 2006, Anders Berglund,
+<a href="http://www.w3.org/TR/2006/REC-xsl11-20061205/" shape="rect">http://www.w3.org/​TR/​2006/​REC-xsl11-20061205/</a>.</dd>
+    </dl>
+
+    </div>
+  </body>
+</html>
diff --git a/doc/preprocess_xhtml.xsl b/doc/preprocess_xhtml.xsl
new file mode 100644
index 0000000..3d93c66
--- /dev/null
+++ b/doc/preprocess_xhtml.xsl
@@ -0,0 +1,126 @@
+<?xml version='1.0'?>
+<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml" xmlns:xh="http://www.w3.org/1999/xhtml" version="1.0" exclude-result-prefixes="xh">
+
+  <xsl:output method="xml" indent="no" version="1.0" encoding="UTF-8" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" omit-xml-declaration="no" />
+
+  <xsl:template match="@* | node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@* | node()" />
+    </xsl:copy>
+  </xsl:template>
+
+
+
+  <xsl:template match="xh:caption" mode="tot">
+    <div class="tot-line">
+      <xsl:call-template name="toc-link" />
+    </div>
+  </xsl:template>
+
+
+
+  <xsl:template match="xh:div[@class = 'toc' and not(node())]">
+    <xsl:copy>
+      <xsl:apply-templates select="@*" />
+      <h1 />
+      <div class="toc-main">
+        <xsl:apply-templates select="//xh:div[@class = 'main']//xh:h1 |             //xh:div[@class = 'main']//xh:h2 |             //xh:div[@class = 'main']//xh:h3" mode="toc" />
+      </div>
+      <div class="toc-back">
+        <xsl:apply-templates select="//xh:div[@class = 'back']//xh:h1 |             //xh:div[@class = 'back']//xh:h2 |             //xh:div[@class = 'back']//xh:h3" mode="toc" />
+      </div>
+    </xsl:copy>
+  </xsl:template>
+
+
+
+  <xsl:template match="xh:div[@class = 'tof' and not(node())]">
+    <xsl:copy>
+      <xsl:apply-templates select="@*" />
+      <h1 />
+      <div class="tof-main">
+        <xsl:apply-templates select="//xh:div[@class = 'main']//xh:div[@class = 'img-caption']" mode="tof" />
+      </div>
+      <div class="tof-back">
+        <xsl:apply-templates select="//xh:div[@class = 'back']//xh:div[@class = 'img-caption']" mode="tof" />
+      </div>
+    </xsl:copy>
+  </xsl:template>
+
+
+
+  <xsl:template match="xh:div[@class = 'tot' and not(node())]">
+    <xsl:copy>
+      <xsl:apply-templates select="@*" />
+      <h1 />
+      <div class="tot-main">
+        <xsl:apply-templates select="//xh:div[@class = 'main']//xh:caption" mode="tot" />
+      </div>
+      <div class="tot-back">
+        <xsl:apply-templates select="//xh:div[@class = 'back']//xh:caption" mode="tot" />
+      </div>
+    </xsl:copy>
+  </xsl:template>
+
+
+
+  <xsl:template match="xh:caption | xh:h1 | xh:h2 | xh:h3 | xh:div[@class = 'img-caption']">
+    <xsl:copy>
+      <xsl:attribute name="id">
+        <xsl:value-of select="generate-id()" />
+      </xsl:attribute>
+      <xsl:apply-templates select="@* | node()" />
+    </xsl:copy>
+  </xsl:template>
+
+
+
+  <xsl:template match="xh:h1 | xh:h2 | xh:h3" mode="toc">
+    <div class="{concat('toc-', name())}">
+      <xsl:call-template name="toc-link" />
+    </div>
+  </xsl:template>
+
+
+
+  <xsl:template match="xh:div[@class = 'img-caption']" mode="tof">
+    <div class="tof-line">
+      <xsl:call-template name="toc-link" />
+    </div>
+  </xsl:template>
+
+
+
+  <xsl:template match="@id | @xml:id" mode="href">
+    <xsl:attribute name="href">
+      <xsl:text>#</xsl:text>
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@id | @xml:id" mode="class">
+    <xsl:attribute name="class">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <!-- Named templates. -->
+
+  <xsl:template name="toc-link">
+    <a href="{concat('#', generate-id())}">
+      <xsl:apply-templates select="@id | @xml:id" mode="href" />
+      <xsl:apply-templates select="node()" />
+    </a>
+    <span class="leader" />
+    <span class="page-ref">
+      <span class="{generate-id()}">
+        <xsl:apply-templates select="@id | @xml:id" mode="class" />
+      </span>
+    </span>
+  </xsl:template>
+
+</xsl:transform>
diff --git a/doc/xhtml_report.css b/doc/xhtml_report.css
new file mode 100644
index 0000000..f305aa5
--- /dev/null
+++ b/doc/xhtml_report.css
@@ -0,0 +1,253 @@
+ at page
+{
+  margin-bottom: 25mm;
+  margin-top: 25mm;
+}
+
+/* A page for the back matter. */
+ at page back
+{
+  force-page-count: even;
+}
+
+/* A page for the back of the title page with copy right notice. */
+ at page copy-right
+{
+  margin-top: 35mm;
+}
+
+/* A page for the front matter. */
+ at page front
+{
+  force-page-count: even;
+  counter-reset: page 1;
+}
+
+/* A page for the main matter. */
+ at page main
+{
+  force-page-count: even;
+}
+
+/* A main page to reset the page number. */
+ at page main-first
+{
+  counter-reset: page 1;
+  force-page-count: even;
+}
+
+/* A page to force page switches without generating content. */
+ at page separator
+{
+  margin-top: 35mm;
+}
+
+/* The title page. */
+ at page title
+{
+  margin-left: 25mm;
+  margin-right: 25mm;
+  margin-top: 50mm;
+}
+
+ at page :left
+{
+  margin-left: 40mm;
+  margin-right: 40mm;
+}
+
+ at page :right
+{
+  margin-left: 40mm;
+  margin-right: 40mm;
+}
+
+ at media print
+{
+  a
+  {
+    color: black;
+  }
+
+  acronym
+  {
+    font-family: serif-small-caps;
+    text-transform: lowercase;
+  }   
+
+  body
+  { 
+    font-family: serif;
+    font-size: 11pt;
+    hyphenate: true;
+    line-height: 1.2;
+  } 
+
+  dd
+  {
+    text-align: justify;
+  } 
+
+  div.page-reset
+  {
+    page: page-reset;
+  }
+
+  div.separator
+  {
+    page: separator;
+  }
+
+  dl, pre, table
+  {
+    margin-bottom: 1.2em;
+    margin-top: 1.2em;
+  }
+
+  table table
+  {
+    margin-bottom: 0pt;
+    margin-top: 0pt;
+  }
+
+  h1, h2, h3, h4, h5, h6
+  {
+    font-weight: normal;
+  }
+
+  h1
+  {
+    font-size: 1.6em;
+    margin-bottom: 2.88em;
+    margin-top: 0pt;
+    page-break-before: right;
+    string-set: chapter contents;
+  }
+
+  h1, h2
+  {
+    font-family: serif-small-caps;
+    letter-spacing: 0.1em;
+    text-transform: lowercase;
+  }
+
+  h2
+  {
+    font-size: 1.2em;
+    margin-bottom: 1.08em;
+    margin-top: 1.08em;
+  }
+
+  h3, h4, h5, h6
+  {
+    font-family: serif-title;
+  }
+
+  h3
+  {
+    font-style: italic;
+  }
+
+  h3, h4, h5, h6
+  {
+    font-size: 1em;
+    margin-bottom: 1.2em;
+    margin-top: 1.2em;
+  }
+
+  p
+  {
+    margin-bottom: 0pt;
+    margin-top: 0pt;
+  }
+
+  p + p
+  {
+    text-indent: 1.5em;
+  }
+
+  pre
+  {
+    page-break-inside: auto;
+  }
+
+  span.footnote-body
+  { 
+    display: footnote-body;
+    font-size: 0.83em;
+  }
+  
+  span.footnote-body:before
+  { 
+    content: counter(footnote);
+    padding-right: 1em;
+  }
+
+  span.footnote-reference
+  {
+    display: footnote-reference;
+  }
+
+  span.footnote-reference:before
+  {
+    counter-increment: footnote;
+    content: counter(footnote);
+    font-size: 0.83em;
+    vertical-align: super;
+  } 
+
+  span.leader
+  {
+    display: leader;
+  }
+
+  span.page-ref > span:before
+  {
+    content: page-ref(attr(class));
+  }
+
+  span.title
+  {
+    font-family: serif-title;
+  }
+
+  sub
+  {
+    font-size: 0.72em;
+    vertical-align: -25%;
+  }
+
+  sup
+  {
+    font-size: 0.72em;
+    vertical-align: 25%;
+  }
+
+  div.toc > h1
+  {
+    string-set: chapter "Table Of Contents";
+    text-align-last: left;
+  }
+
+  div.toc > h1:before
+  {
+    content: "Table Of Contents";
+  }
+
+  div.toc-h1, div.toc-h2, div.toc-h3
+  {
+    text-align: left;
+  }
+
+  div.toc-h1
+  {
+    margin-bottom: 2em;
+    margin-top: 2em;
+  }
+
+  div.toc-h2
+  {
+    margin-bottom: 1em;
+    margin-top: 1em;
+  }
+}
diff --git a/dtd/catalog b/dtd/catalog
new file mode 100644
index 0000000..3db67bb
--- /dev/null
+++ b/dtd/catalog
@@ -0,0 +1,90 @@
+PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+       "xhtml1/xhtml1-strict.dtd"
+SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+       "xhtml1/xhtml1-strict.dtd"
+
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+       "xhtml1/xhtml1-transitional.dtd"
+SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
+       "xhtml1/xhtml1-transitional.dtd"
+
+PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+       "xhtml1/xhtml1-frameset.dtd"
+SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"
+       "xhtml1/xhtml1-frameset.dtd"
+
+PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+       "xhtml11/xhtml11.dtd"
+
+SYSTEM "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
+       "xhtml11/xhtml11.dtd"
+
+PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+       "html4/loose.dtd"
+SYSTEM "http://www.w3.org/TR/html4/loose.dtd"
+       "html4/loose.dtd"
+
+PUBLIC "-//W3C//DTD HTML 4.01//EN"
+       "html4/strict.dtd"
+SYSTEM "http://www.w3.org/TR/html4/strict.dtd"
+       "html4/strict.dtd"
+
+PUBLIC "-//BEA Systems, Inc.//DTD WebLogic 8.1.0 EJB//EN"
+       "j2ee/weblogic810-ejb.jar.dtd"
+SYSTEM "http://www.bea.com/servers/wls810/dtd/weblogic-ejb-jar.dtd"
+       "j2ee/weblogic810-ejb.jar.dtd"
+
+PUBLIC "-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN"
+       "j2ee/weblogic-ejb-jar.dtd"
+SYSTEM "http://www.bea.com/servers/wls600/dtd/weblogic-ejb-jar.dtd"
+       "j2ee/weblogic-ejb-jar.dtd"
+
+PUBLIC "-//BEA Systems, Inc.//DTD Web Application 6.1//EN"
+       "j2ee/weblogic-ejb-jar.dtd"
+SYSTEM "http://www.bea.com/servers/wls600/dtd/weblogic-ejb-jar.dtd"
+       "j2ee/weblogic-ejb-jar.dtd"
+
+PUBLIC "-//BEA Systems, Inc.//DTD WebLogic 6.0.0 Connector//EN"
+       "j2ee/weblogic600-ra.dtd"
+SYSTEM "http://www.bea.com/servers/wls600/dtd/weblogic600-ra.dtd"
+       "j2ee/weblogic600-ra.dtd"
+
+PUBLIC "-//BEA Systems, Inc.//DTD WebLogic Application 8.1.0//EN"
+       "j2ee/weblogic-application_2_0.dtd"
+SYSTEM "http://www.bea.com/servers/wls810/dtd/weblogic-application_2_0.dtd"
+       "j2ee/weblogic-application_2_0.dtd"
+
+PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
+       "j2ee/web-app_2_2.dtd"
+SYSTEM "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"
+       "j2ee/web-app_2_2.dtd"
+
+PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+       "j2ee/web-app_2_3.dtd"
+SYSTEM "http://java.sun.com/dtd/web-app_2_3.dtd"
+       "j2ee/web-app_2_3.dtd"
+
+PUBLIC "-//Sun Microsystems, Inc.//DTD Connector 1.0//EN"
+       "j2ee/connector_1_0.dtd"
+SYSTEM "http://java.sun.com/dtd/connector_1_0.dtd"
+       "j2ee/connector_1_0.dtd"
+
+PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
+       "j2ee/web-jsptaglibrary_1_2.dtd"
+SYSTEM "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"
+       "j2ee/web-jsptaglibrary_1_2.dtd"
+
+PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application Client 1.3//EN"
+       "j2ee/application-client_1_3.dtd"
+SYSTEM "http://java.sun.com/dtd/application-client_1_3.dtd"
+       "j2ee/application-client_1_3.dtd"
+
+PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"
+       "j2ee/application_1_3.dtd"
+SYSTEM "http://java.sun.com/dtd/application_1_3.dtd"
+       "j2ee/application_1_3.dtd"
+
+PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
+       "j2ee/ejb-jar_2_0.dtd"
+SYSTEM "http://java.sun.com/dtd/ejb-jar_2_0.dtd"
+       "j2ee/ejb-jar_2_0.dtd"
diff --git a/dtd/xhtml1/xhtml-lat1.ent b/dtd/xhtml1/xhtml-lat1.ent
new file mode 100644
index 0000000..ffee223
--- /dev/null
+++ b/dtd/xhtml1/xhtml-lat1.ent
@@ -0,0 +1,196 @@
+<!-- Portions (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+<!-- Character entity set. Typical invocation:
+    <!ENTITY % HTMLlat1 PUBLIC
+       "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+       "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent">
+    %HTMLlat1;
+-->
+
+<!ENTITY nbsp   " "> <!-- no-break space = non-breaking space,
+                                  U+00A0 ISOnum -->
+<!ENTITY iexcl  "¡"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+<!ENTITY cent   "¢"> <!-- cent sign, U+00A2 ISOnum -->
+<!ENTITY pound  "£"> <!-- pound sign, U+00A3 ISOnum -->
+<!ENTITY curren "¤"> <!-- currency sign, U+00A4 ISOnum -->
+<!ENTITY yen    "¥"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+<!ENTITY brvbar "¦"> <!-- broken bar = broken vertical bar,
+                                  U+00A6 ISOnum -->
+<!ENTITY sect   "§"> <!-- section sign, U+00A7 ISOnum -->
+<!ENTITY uml    "¨"> <!-- diaeresis = spacing diaeresis,
+                                  U+00A8 ISOdia -->
+<!ENTITY copy   "©"> <!-- copyright sign, U+00A9 ISOnum -->
+<!ENTITY ordf   "ª"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+<!ENTITY laquo  "«"> <!-- left-pointing double angle quotation mark
+                                  = left pointing guillemet, U+00AB ISOnum -->
+<!ENTITY not    "¬"> <!-- not sign = angled dash,
+                                  U+00AC ISOnum -->
+<!ENTITY shy    "­"> <!-- soft hyphen = discretionary hyphen,
+                                  U+00AD ISOnum -->
+<!ENTITY reg    "®"> <!-- registered sign = registered trade mark sign,
+                                  U+00AE ISOnum -->
+<!ENTITY macr   "¯"> <!-- macron = spacing macron = overline
+                                  = APL overbar, U+00AF ISOdia -->
+<!ENTITY deg    "°"> <!-- degree sign, U+00B0 ISOnum -->
+<!ENTITY plusmn "±"> <!-- plus-minus sign = plus-or-minus sign,
+                                  U+00B1 ISOnum -->
+<!ENTITY sup2   "²"> <!-- superscript two = superscript digit two
+                                  = squared, U+00B2 ISOnum -->
+<!ENTITY sup3   "³"> <!-- superscript three = superscript digit three
+                                  = cubed, U+00B3 ISOnum -->
+<!ENTITY acute  "´"> <!-- acute accent = spacing acute,
+                                  U+00B4 ISOdia -->
+<!ENTITY micro  "µ"> <!-- micro sign, U+00B5 ISOnum -->
+<!ENTITY para   "¶"> <!-- pilcrow sign = paragraph sign,
+                                  U+00B6 ISOnum -->
+<!ENTITY middot "·"> <!-- middle dot = Georgian comma
+                                  = Greek middle dot, U+00B7 ISOnum -->
+<!ENTITY cedil  "¸"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+<!ENTITY sup1   "¹"> <!-- superscript one = superscript digit one,
+                                  U+00B9 ISOnum -->
+<!ENTITY ordm   "º"> <!-- masculine ordinal indicator,
+                                  U+00BA ISOnum -->
+<!ENTITY raquo  "»"> <!-- right-pointing double angle quotation mark
+                                  = right pointing guillemet, U+00BB ISOnum -->
+<!ENTITY frac14 "¼"> <!-- vulgar fraction one quarter
+                                  = fraction one quarter, U+00BC ISOnum -->
+<!ENTITY frac12 "½"> <!-- vulgar fraction one half
+                                  = fraction one half, U+00BD ISOnum -->
+<!ENTITY frac34 "¾"> <!-- vulgar fraction three quarters
+                                  = fraction three quarters, U+00BE ISOnum -->
+<!ENTITY iquest "¿"> <!-- inverted question mark
+                                  = turned question mark, U+00BF ISOnum -->
+<!ENTITY Agrave "À"> <!-- latin capital letter A with grave
+                                  = latin capital letter A grave,
+                                  U+00C0 ISOlat1 -->
+<!ENTITY Aacute "Á"> <!-- latin capital letter A with acute,
+                                  U+00C1 ISOlat1 -->
+<!ENTITY Acirc  "Â"> <!-- latin capital letter A with circumflex,
+                                  U+00C2 ISOlat1 -->
+<!ENTITY Atilde "Ã"> <!-- latin capital letter A with tilde,
+                                  U+00C3 ISOlat1 -->
+<!ENTITY Auml   "Ä"> <!-- latin capital letter A with diaeresis,
+                                  U+00C4 ISOlat1 -->
+<!ENTITY Aring  "Å"> <!-- latin capital letter A with ring above
+                                  = latin capital letter A ring,
+                                  U+00C5 ISOlat1 -->
+<!ENTITY AElig  "Æ"> <!-- latin capital letter AE
+                                  = latin capital ligature AE,
+                                  U+00C6 ISOlat1 -->
+<!ENTITY Ccedil "Ç"> <!-- latin capital letter C with cedilla,
+                                  U+00C7 ISOlat1 -->
+<!ENTITY Egrave "È"> <!-- latin capital letter E with grave,
+                                  U+00C8 ISOlat1 -->
+<!ENTITY Eacute "É"> <!-- latin capital letter E with acute,
+                                  U+00C9 ISOlat1 -->
+<!ENTITY Ecirc  "Ê"> <!-- latin capital letter E with circumflex,
+                                  U+00CA ISOlat1 -->
+<!ENTITY Euml   "Ë"> <!-- latin capital letter E with diaeresis,
+                                  U+00CB ISOlat1 -->
+<!ENTITY Igrave "Ì"> <!-- latin capital letter I with grave,
+                                  U+00CC ISOlat1 -->
+<!ENTITY Iacute "Í"> <!-- latin capital letter I with acute,
+                                  U+00CD ISOlat1 -->
+<!ENTITY Icirc  "Î"> <!-- latin capital letter I with circumflex,
+                                  U+00CE ISOlat1 -->
+<!ENTITY Iuml   "Ï"> <!-- latin capital letter I with diaeresis,
+                                  U+00CF ISOlat1 -->
+<!ENTITY ETH    "Ð"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+<!ENTITY Ntilde "Ñ"> <!-- latin capital letter N with tilde,
+                                  U+00D1 ISOlat1 -->
+<!ENTITY Ograve "Ò"> <!-- latin capital letter O with grave,
+                                  U+00D2 ISOlat1 -->
+<!ENTITY Oacute "Ó"> <!-- latin capital letter O with acute,
+                                  U+00D3 ISOlat1 -->
+<!ENTITY Ocirc  "Ô"> <!-- latin capital letter O with circumflex,
+                                  U+00D4 ISOlat1 -->
+<!ENTITY Otilde "Õ"> <!-- latin capital letter O with tilde,
+                                  U+00D5 ISOlat1 -->
+<!ENTITY Ouml   "Ö"> <!-- latin capital letter O with diaeresis,
+                                  U+00D6 ISOlat1 -->
+<!ENTITY times  "×"> <!-- multiplication sign, U+00D7 ISOnum -->
+<!ENTITY Oslash "Ø"> <!-- latin capital letter O with stroke
+                                  = latin capital letter O slash,
+                                  U+00D8 ISOlat1 -->
+<!ENTITY Ugrave "Ù"> <!-- latin capital letter U with grave,
+                                  U+00D9 ISOlat1 -->
+<!ENTITY Uacute "Ú"> <!-- latin capital letter U with acute,
+                                  U+00DA ISOlat1 -->
+<!ENTITY Ucirc  "Û"> <!-- latin capital letter U with circumflex,
+                                  U+00DB ISOlat1 -->
+<!ENTITY Uuml   "Ü"> <!-- latin capital letter U with diaeresis,
+                                  U+00DC ISOlat1 -->
+<!ENTITY Yacute "Ý"> <!-- latin capital letter Y with acute,
+                                  U+00DD ISOlat1 -->
+<!ENTITY THORN  "Þ"> <!-- latin capital letter THORN,
+                                  U+00DE ISOlat1 -->
+<!ENTITY szlig  "ß"> <!-- latin small letter sharp s = ess-zed,
+                                  U+00DF ISOlat1 -->
+<!ENTITY agrave "à"> <!-- latin small letter a with grave
+                                  = latin small letter a grave,
+                                  U+00E0 ISOlat1 -->
+<!ENTITY aacute "á"> <!-- latin small letter a with acute,
+                                  U+00E1 ISOlat1 -->
+<!ENTITY acirc  "â"> <!-- latin small letter a with circumflex,
+                                  U+00E2 ISOlat1 -->
+<!ENTITY atilde "ã"> <!-- latin small letter a with tilde,
+                                  U+00E3 ISOlat1 -->
+<!ENTITY auml   "ä"> <!-- latin small letter a with diaeresis,
+                                  U+00E4 ISOlat1 -->
+<!ENTITY aring  "å"> <!-- latin small letter a with ring above
+                                  = latin small letter a ring,
+                                  U+00E5 ISOlat1 -->
+<!ENTITY aelig  "æ"> <!-- latin small letter ae
+                                  = latin small ligature ae, U+00E6 ISOlat1 -->
+<!ENTITY ccedil "ç"> <!-- latin small letter c with cedilla,
+                                  U+00E7 ISOlat1 -->
+<!ENTITY egrave "è"> <!-- latin small letter e with grave,
+                                  U+00E8 ISOlat1 -->
+<!ENTITY eacute "é"> <!-- latin small letter e with acute,
+                                  U+00E9 ISOlat1 -->
+<!ENTITY ecirc  "ê"> <!-- latin small letter e with circumflex,
+                                  U+00EA ISOlat1 -->
+<!ENTITY euml   "ë"> <!-- latin small letter e with diaeresis,
+                                  U+00EB ISOlat1 -->
+<!ENTITY igrave "ì"> <!-- latin small letter i with grave,
+                                  U+00EC ISOlat1 -->
+<!ENTITY iacute "í"> <!-- latin small letter i with acute,
+                                  U+00ED ISOlat1 -->
+<!ENTITY icirc  "î"> <!-- latin small letter i with circumflex,
+                                  U+00EE ISOlat1 -->
+<!ENTITY iuml   "ï"> <!-- latin small letter i with diaeresis,
+                                  U+00EF ISOlat1 -->
+<!ENTITY eth    "ð"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+<!ENTITY ntilde "ñ"> <!-- latin small letter n with tilde,
+                                  U+00F1 ISOlat1 -->
+<!ENTITY ograve "ò"> <!-- latin small letter o with grave,
+                                  U+00F2 ISOlat1 -->
+<!ENTITY oacute "ó"> <!-- latin small letter o with acute,
+                                  U+00F3 ISOlat1 -->
+<!ENTITY ocirc  "ô"> <!-- latin small letter o with circumflex,
+                                  U+00F4 ISOlat1 -->
+<!ENTITY otilde "õ"> <!-- latin small letter o with tilde,
+                                  U+00F5 ISOlat1 -->
+<!ENTITY ouml   "ö"> <!-- latin small letter o with diaeresis,
+                                  U+00F6 ISOlat1 -->
+<!ENTITY divide "÷"> <!-- division sign, U+00F7 ISOnum -->
+<!ENTITY oslash "ø"> <!-- latin small letter o with stroke,
+                                  = latin small letter o slash,
+                                  U+00F8 ISOlat1 -->
+<!ENTITY ugrave "ù"> <!-- latin small letter u with grave,
+                                  U+00F9 ISOlat1 -->
+<!ENTITY uacute "ú"> <!-- latin small letter u with acute,
+                                  U+00FA ISOlat1 -->
+<!ENTITY ucirc  "û"> <!-- latin small letter u with circumflex,
+                                  U+00FB ISOlat1 -->
+<!ENTITY uuml   "ü"> <!-- latin small letter u with diaeresis,
+                                  U+00FC ISOlat1 -->
+<!ENTITY yacute "ý"> <!-- latin small letter y with acute,
+                                  U+00FD ISOlat1 -->
+<!ENTITY thorn  "þ"> <!-- latin small letter thorn,
+                                  U+00FE ISOlat1 -->
+<!ENTITY yuml   "ÿ"> <!-- latin small letter y with diaeresis,
+                                  U+00FF ISOlat1 -->
diff --git a/dtd/xhtml1/xhtml-special.ent b/dtd/xhtml1/xhtml-special.ent
new file mode 100644
index 0000000..ca358b2
--- /dev/null
+++ b/dtd/xhtml1/xhtml-special.ent
@@ -0,0 +1,80 @@
+<!-- Special characters for XHTML -->
+
+<!-- Character entity set. Typical invocation:
+     <!ENTITY % HTMLspecial PUBLIC
+        "-//W3C//ENTITIES Special for XHTML//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent">
+     %HTMLspecial;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+     New names (i.e., not in ISO 8879 list) do not clash with any
+     existing ISO 8879 entity names. ISO 10646 character numbers
+     are given for each character, in hex. values are decimal
+     conversions of the ISO 10646 values and refer to the document
+     character set. Names are Unicode names. 
+-->
+
+<!-- C0 Controls and Basic Latin -->
+<!ENTITY quot    """> <!--  quotation mark, U+0022 ISOnum -->
+<!ENTITY amp     "&#38;"> <!--  ampersand, U+0026 ISOnum -->
+<!ENTITY lt      "&#60;"> <!--  less-than sign, U+003C ISOnum -->
+<!ENTITY gt      ">"> <!--  greater-than sign, U+003E ISOnum -->
+<!ENTITY apos	 "'"> <!--  apostrophe = APL quote, U+0027 ISOnum -->
+
+<!-- Latin Extended-A -->
+<!ENTITY OElig   "Œ"> <!--  latin capital ligature OE,
+                                    U+0152 ISOlat2 -->
+<!ENTITY oelig   "œ"> <!--  latin small ligature oe, U+0153 ISOlat2 -->
+<!-- ligature is a misnomer, this is a separate character in some languages -->
+<!ENTITY Scaron  "Š"> <!--  latin capital letter S with caron,
+                                    U+0160 ISOlat2 -->
+<!ENTITY scaron  "š"> <!--  latin small letter s with caron,
+                                    U+0161 ISOlat2 -->
+<!ENTITY Yuml    "Ÿ"> <!--  latin capital letter Y with diaeresis,
+                                    U+0178 ISOlat2 -->
+
+<!-- Spacing Modifier Letters -->
+<!ENTITY circ    "ˆ"> <!--  modifier letter circumflex accent,
+                                    U+02C6 ISOpub -->
+<!ENTITY tilde   "˜"> <!--  small tilde, U+02DC ISOdia -->
+
+<!-- General Punctuation -->
+<!ENTITY ensp    " "> <!-- en space, U+2002 ISOpub -->
+<!ENTITY emsp    " "> <!-- em space, U+2003 ISOpub -->
+<!ENTITY thinsp  " "> <!-- thin space, U+2009 ISOpub -->
+<!ENTITY zwnj    "‌"> <!-- zero width non-joiner,
+                                    U+200C NEW RFC 2070 -->
+<!ENTITY zwj     "‍"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+<!ENTITY lrm     "‎"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+<!ENTITY rlm     "‏"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+<!ENTITY ndash   "–"> <!-- en dash, U+2013 ISOpub -->
+<!ENTITY mdash   "—"> <!-- em dash, U+2014 ISOpub -->
+<!ENTITY lsquo   "‘"> <!-- left single quotation mark,
+                                    U+2018 ISOnum -->
+<!ENTITY rsquo   "’"> <!-- right single quotation mark,
+                                    U+2019 ISOnum -->
+<!ENTITY sbquo   "‚"> <!-- single low-9 quotation mark, U+201A NEW -->
+<!ENTITY ldquo   "“"> <!-- left double quotation mark,
+                                    U+201C ISOnum -->
+<!ENTITY rdquo   "”"> <!-- right double quotation mark,
+                                    U+201D ISOnum -->
+<!ENTITY bdquo   "„"> <!-- double low-9 quotation mark, U+201E NEW -->
+<!ENTITY dagger  "†"> <!-- dagger, U+2020 ISOpub -->
+<!ENTITY Dagger  "‡"> <!-- double dagger, U+2021 ISOpub -->
+<!ENTITY permil  "‰"> <!-- per mille sign, U+2030 ISOtech -->
+<!ENTITY lsaquo  "‹"> <!-- single left-pointing angle quotation mark,
+                                    U+2039 ISO proposed -->
+<!-- lsaquo is proposed but not yet ISO standardized -->
+<!ENTITY rsaquo  "›"> <!-- single right-pointing angle quotation mark,
+                                    U+203A ISO proposed -->
+<!-- rsaquo is proposed but not yet ISO standardized -->
+
+<!-- Currency Symbols -->
+<!ENTITY euro   "€"> <!--  euro sign, U+20AC NEW -->
diff --git a/dtd/xhtml1/xhtml-symbol.ent b/dtd/xhtml1/xhtml-symbol.ent
new file mode 100644
index 0000000..63c2abf
--- /dev/null
+++ b/dtd/xhtml1/xhtml-symbol.ent
@@ -0,0 +1,237 @@
+<!-- Mathematical, Greek and Symbolic characters for XHTML -->
+
+<!-- Character entity set. Typical invocation:
+     <!ENTITY % HTMLsymbol PUBLIC
+        "-//W3C//ENTITIES Symbols for XHTML//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent">
+     %HTMLsymbol;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+     New names (i.e., not in ISO 8879 list) do not clash with any
+     existing ISO 8879 entity names. ISO 10646 character numbers
+     are given for each character, in hex. values are decimal
+     conversions of the ISO 10646 values and refer to the document
+     character set. Names are Unicode names. 
+-->
+
+<!-- Latin Extended-B -->
+<!ENTITY fnof     "ƒ"> <!-- latin small letter f with hook = function
+                                    = florin, U+0192 ISOtech -->
+
+<!-- Greek -->
+<!ENTITY Alpha    "Α"> <!-- greek capital letter alpha, U+0391 -->
+<!ENTITY Beta     "Β"> <!-- greek capital letter beta, U+0392 -->
+<!ENTITY Gamma    "Γ"> <!-- greek capital letter gamma,
+                                    U+0393 ISOgrk3 -->
+<!ENTITY Delta    "Δ"> <!-- greek capital letter delta,
+                                    U+0394 ISOgrk3 -->
+<!ENTITY Epsilon  "Ε"> <!-- greek capital letter epsilon, U+0395 -->
+<!ENTITY Zeta     "Ζ"> <!-- greek capital letter zeta, U+0396 -->
+<!ENTITY Eta      "Η"> <!-- greek capital letter eta, U+0397 -->
+<!ENTITY Theta    "Θ"> <!-- greek capital letter theta,
+                                    U+0398 ISOgrk3 -->
+<!ENTITY Iota     "Ι"> <!-- greek capital letter iota, U+0399 -->
+<!ENTITY Kappa    "Κ"> <!-- greek capital letter kappa, U+039A -->
+<!ENTITY Lambda   "Λ"> <!-- greek capital letter lamda,
+                                    U+039B ISOgrk3 -->
+<!ENTITY Mu       "Μ"> <!-- greek capital letter mu, U+039C -->
+<!ENTITY Nu       "Ν"> <!-- greek capital letter nu, U+039D -->
+<!ENTITY Xi       "Ξ"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+<!ENTITY Omicron  "Ο"> <!-- greek capital letter omicron, U+039F -->
+<!ENTITY Pi       "Π"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+<!ENTITY Rho      "Ρ"> <!-- greek capital letter rho, U+03A1 -->
+<!-- there is no Sigmaf, and no U+03A2 character either -->
+<!ENTITY Sigma    "Σ"> <!-- greek capital letter sigma,
+                                    U+03A3 ISOgrk3 -->
+<!ENTITY Tau      "Τ"> <!-- greek capital letter tau, U+03A4 -->
+<!ENTITY Upsilon  "Υ"> <!-- greek capital letter upsilon,
+                                    U+03A5 ISOgrk3 -->
+<!ENTITY Phi      "Φ"> <!-- greek capital letter phi,
+                                    U+03A6 ISOgrk3 -->
+<!ENTITY Chi      "Χ"> <!-- greek capital letter chi, U+03A7 -->
+<!ENTITY Psi      "Ψ"> <!-- greek capital letter psi,
+                                    U+03A8 ISOgrk3 -->
+<!ENTITY Omega    "Ω"> <!-- greek capital letter omega,
+                                    U+03A9 ISOgrk3 -->
+
+<!ENTITY alpha    "α"> <!-- greek small letter alpha,
+                                    U+03B1 ISOgrk3 -->
+<!ENTITY beta     "β"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+<!ENTITY gamma    "γ"> <!-- greek small letter gamma,
+                                    U+03B3 ISOgrk3 -->
+<!ENTITY delta    "δ"> <!-- greek small letter delta,
+                                    U+03B4 ISOgrk3 -->
+<!ENTITY epsilon  "ε"> <!-- greek small letter epsilon,
+                                    U+03B5 ISOgrk3 -->
+<!ENTITY zeta     "ζ"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+<!ENTITY eta      "η"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+<!ENTITY theta    "θ"> <!-- greek small letter theta,
+                                    U+03B8 ISOgrk3 -->
+<!ENTITY iota     "ι"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+<!ENTITY kappa    "κ"> <!-- greek small letter kappa,
+                                    U+03BA ISOgrk3 -->
+<!ENTITY lambda   "λ"> <!-- greek small letter lamda,
+                                    U+03BB ISOgrk3 -->
+<!ENTITY mu       "μ"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+<!ENTITY nu       "ν"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+<!ENTITY xi       "ξ"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+<!ENTITY omicron  "ο"> <!-- greek small letter omicron, U+03BF NEW -->
+<!ENTITY pi       "π"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+<!ENTITY rho      "ρ"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+<!ENTITY sigmaf   "ς"> <!-- greek small letter final sigma,
+                                    U+03C2 ISOgrk3 -->
+<!ENTITY sigma    "σ"> <!-- greek small letter sigma,
+                                    U+03C3 ISOgrk3 -->
+<!ENTITY tau      "τ"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+<!ENTITY upsilon  "υ"> <!-- greek small letter upsilon,
+                                    U+03C5 ISOgrk3 -->
+<!ENTITY phi      "φ"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+<!ENTITY chi      "χ"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+<!ENTITY psi      "ψ"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+<!ENTITY omega    "ω"> <!-- greek small letter omega,
+                                    U+03C9 ISOgrk3 -->
+<!ENTITY thetasym "ϑ"> <!-- greek theta symbol,
+                                    U+03D1 NEW -->
+<!ENTITY upsih    "ϒ"> <!-- greek upsilon with hook symbol,
+                                    U+03D2 NEW -->
+<!ENTITY piv      "ϖ"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+<!-- General Punctuation -->
+<!ENTITY bull     "•"> <!-- bullet = black small circle,
+                                     U+2022 ISOpub  -->
+<!-- bullet is NOT the same as bullet operator, U+2219 -->
+<!ENTITY hellip   "…"> <!-- horizontal ellipsis = three dot leader,
+                                     U+2026 ISOpub  -->
+<!ENTITY prime    "′"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+<!ENTITY Prime    "″"> <!-- double prime = seconds = inches,
+                                     U+2033 ISOtech -->
+<!ENTITY oline    "‾"> <!-- overline = spacing overscore,
+                                     U+203E NEW -->
+<!ENTITY frasl    "⁄"> <!-- fraction slash, U+2044 NEW -->
+
+<!-- Letterlike Symbols -->
+<!ENTITY weierp   "℘"> <!-- script capital P = power set
+                                     = Weierstrass p, U+2118 ISOamso -->
+<!ENTITY image    "ℑ"> <!-- black-letter capital I = imaginary part,
+                                     U+2111 ISOamso -->
+<!ENTITY real     "ℜ"> <!-- black-letter capital R = real part symbol,
+                                     U+211C ISOamso -->
+<!ENTITY trade    "™"> <!-- trade mark sign, U+2122 ISOnum -->
+<!ENTITY alefsym  "ℵ"> <!-- alef symbol = first transfinite cardinal,
+                                     U+2135 NEW -->
+<!-- alef symbol is NOT the same as hebrew letter alef,
+     U+05D0 although the same glyph could be used to depict both characters -->
+
+<!-- Arrows -->
+<!ENTITY larr     "←"> <!-- leftwards arrow, U+2190 ISOnum -->
+<!ENTITY uarr     "↑"> <!-- upwards arrow, U+2191 ISOnum-->
+<!ENTITY rarr     "→"> <!-- rightwards arrow, U+2192 ISOnum -->
+<!ENTITY darr     "↓"> <!-- downwards arrow, U+2193 ISOnum -->
+<!ENTITY harr     "↔"> <!-- left right arrow, U+2194 ISOamsa -->
+<!ENTITY crarr    "↵"> <!-- downwards arrow with corner leftwards
+                                     = carriage return, U+21B5 NEW -->
+<!ENTITY lArr     "⇐"> <!-- leftwards double arrow, U+21D0 ISOtech -->
+<!-- Unicode does not say that lArr is the same as the 'is implied by' arrow
+    but also does not have any other character for that function. So lArr can
+    be used for 'is implied by' as ISOtech suggests -->
+<!ENTITY uArr     "⇑"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+<!ENTITY rArr     "⇒"> <!-- rightwards double arrow,
+                                     U+21D2 ISOtech -->
+<!-- Unicode does not say this is the 'implies' character but does not have 
+     another character with this function so rArr can be used for 'implies'
+     as ISOtech suggests -->
+<!ENTITY dArr     "⇓"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+<!ENTITY hArr     "⇔"> <!-- left right double arrow,
+                                     U+21D4 ISOamsa -->
+
+<!-- Mathematical Operators -->
+<!ENTITY forall   "∀"> <!-- for all, U+2200 ISOtech -->
+<!ENTITY part     "∂"> <!-- partial differential, U+2202 ISOtech  -->
+<!ENTITY exist    "∃"> <!-- there exists, U+2203 ISOtech -->
+<!ENTITY empty    "∅"> <!-- empty set = null set, U+2205 ISOamso -->
+<!ENTITY nabla    "∇"> <!-- nabla = backward difference,
+                                     U+2207 ISOtech -->
+<!ENTITY isin     "∈"> <!-- element of, U+2208 ISOtech -->
+<!ENTITY notin    "∉"> <!-- not an element of, U+2209 ISOtech -->
+<!ENTITY ni       "∋"> <!-- contains as member, U+220B ISOtech -->
+<!ENTITY prod     "∏"> <!-- n-ary product = product sign,
+                                     U+220F ISOamsb -->
+<!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though
+     the same glyph might be used for both -->
+<!ENTITY sum      "∑"> <!-- n-ary summation, U+2211 ISOamsb -->
+<!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+     though the same glyph might be used for both -->
+<!ENTITY minus    "−"> <!-- minus sign, U+2212 ISOtech -->
+<!ENTITY lowast   "∗"> <!-- asterisk operator, U+2217 ISOtech -->
+<!ENTITY radic    "√"> <!-- square root = radical sign,
+                                     U+221A ISOtech -->
+<!ENTITY prop     "∝"> <!-- proportional to, U+221D ISOtech -->
+<!ENTITY infin    "∞"> <!-- infinity, U+221E ISOtech -->
+<!ENTITY ang      "∠"> <!-- angle, U+2220 ISOamso -->
+<!ENTITY and      "∧"> <!-- logical and = wedge, U+2227 ISOtech -->
+<!ENTITY or       "∨"> <!-- logical or = vee, U+2228 ISOtech -->
+<!ENTITY cap      "∩"> <!-- intersection = cap, U+2229 ISOtech -->
+<!ENTITY cup      "∪"> <!-- union = cup, U+222A ISOtech -->
+<!ENTITY int      "∫"> <!-- integral, U+222B ISOtech -->
+<!ENTITY there4   "∴"> <!-- therefore, U+2234 ISOtech -->
+<!ENTITY sim      "∼"> <!-- tilde operator = varies with = similar to,
+                                     U+223C ISOtech -->
+<!-- tilde operator is NOT the same character as the tilde, U+007E,
+     although the same glyph might be used to represent both  -->
+<!ENTITY cong     "≅"> <!-- approximately equal to, U+2245 ISOtech -->
+<!ENTITY asymp    "≈"> <!-- almost equal to = asymptotic to,
+                                     U+2248 ISOamsr -->
+<!ENTITY ne       "≠"> <!-- not equal to, U+2260 ISOtech -->
+<!ENTITY equiv    "≡"> <!-- identical to, U+2261 ISOtech -->
+<!ENTITY le       "≤"> <!-- less-than or equal to, U+2264 ISOtech -->
+<!ENTITY ge       "≥"> <!-- greater-than or equal to,
+                                     U+2265 ISOtech -->
+<!ENTITY sub      "⊂"> <!-- subset of, U+2282 ISOtech -->
+<!ENTITY sup      "⊃"> <!-- superset of, U+2283 ISOtech -->
+<!ENTITY nsub     "⊄"> <!-- not a subset of, U+2284 ISOamsn -->
+<!ENTITY sube     "⊆"> <!-- subset of or equal to, U+2286 ISOtech -->
+<!ENTITY supe     "⊇"> <!-- superset of or equal to,
+                                     U+2287 ISOtech -->
+<!ENTITY oplus    "⊕"> <!-- circled plus = direct sum,
+                                     U+2295 ISOamsb -->
+<!ENTITY otimes   "⊗"> <!-- circled times = vector product,
+                                     U+2297 ISOamsb -->
+<!ENTITY perp     "⊥"> <!-- up tack = orthogonal to = perpendicular,
+                                     U+22A5 ISOtech -->
+<!ENTITY sdot     "⋅"> <!-- dot operator, U+22C5 ISOamsb -->
+<!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+<!-- Miscellaneous Technical -->
+<!ENTITY lceil    "⌈"> <!-- left ceiling = APL upstile,
+                                     U+2308 ISOamsc  -->
+<!ENTITY rceil    "⌉"> <!-- right ceiling, U+2309 ISOamsc  -->
+<!ENTITY lfloor   "⌊"> <!-- left floor = APL downstile,
+                                     U+230A ISOamsc  -->
+<!ENTITY rfloor   "⌋"> <!-- right floor, U+230B ISOamsc  -->
+<!ENTITY lang     "〈"> <!-- left-pointing angle bracket = bra,
+                                     U+2329 ISOtech -->
+<!-- lang is NOT the same character as U+003C 'less than sign' 
+     or U+2039 'single left-pointing angle quotation mark' -->
+<!ENTITY rang     "〉"> <!-- right-pointing angle bracket = ket,
+                                     U+232A ISOtech -->
+<!-- rang is NOT the same character as U+003E 'greater than sign' 
+     or U+203A 'single right-pointing angle quotation mark' -->
+
+<!-- Geometric Shapes -->
+<!ENTITY loz      "◊"> <!-- lozenge, U+25CA ISOpub -->
+
+<!-- Miscellaneous Symbols -->
+<!ENTITY spades   "♠"> <!-- black spade suit, U+2660 ISOpub -->
+<!-- black here seems to mean filled as opposed to hollow -->
+<!ENTITY clubs    "♣"> <!-- black club suit = shamrock,
+                                     U+2663 ISOpub -->
+<!ENTITY hearts   "♥"> <!-- black heart suit = valentine,
+                                     U+2665 ISOpub -->
+<!ENTITY diams    "♦"> <!-- black diamond suit, U+2666 ISOpub -->
diff --git a/dtd/xhtml1/xhtml1-frameset.dtd b/dtd/xhtml1/xhtml1-frameset.dtd
new file mode 100644
index 0000000..d128f2e
--- /dev/null
+++ b/dtd/xhtml1/xhtml1-frameset.dtd
@@ -0,0 +1,1235 @@
+<!--
+   Extensible HTML version 1.0 Frameset DTD
+
+   This is the same as HTML 4 Frameset except for
+   changes due to the differences between XML and SGML.
+
+   Namespace = http://www.w3.org/1999/xhtml
+
+   For further information, see: http://www.w3.org/TR/xhtml1
+
+   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+   All Rights Reserved. 
+
+   This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+   PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+   SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"
+
+   $Revision: 1.2 $
+   $Date: 2002/08/01 18:37:55 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+   "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+   "xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+   "-//W3C//ENTITIES Symbols for XHTML//EN"
+   "xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+   "-//W3C//ENTITIES Special for XHTML//EN"
+   "xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+    <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+    <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+    <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+    <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+    <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+    <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+    <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+    <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+    <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+    <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+    <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+    <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+    <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+    <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+    <!-- used for titles etc. -->
+
+<!ENTITY % FrameTarget "NMTOKEN">
+    <!-- render in this frame -->
+
+<!ENTITY % Length "CDATA">
+    <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+    <!-- pixel, percentage, or relative -->
+
+<!ENTITY % MultiLengths "CDATA">
+    <!-- comma-separated list of MultiLength -->
+
+<!ENTITY % Pixels "CDATA">
+    <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+    <!-- comma separated list of lengths -->
+
+<!-- used for object, applet, img, input and iframe -->
+<!ENTITY % ImgAlign "(top|middle|bottom|left|right)">
+
+<!-- a color using sRGB: #RRGGBB as Hex values -->
+<!ENTITY % Color "CDATA">
+
+<!-- There are also 16 widely known color names with their sRGB values:
+
+    Black  = #000000    Green  = #008000
+    Silver = #C0C0C0    Lime   = #00FF00
+    Gray   = #808080    Olive  = #808000
+    White  = #FFFFFF    Yellow = #FFFF00
+    Maroon = #800000    Navy   = #000080
+    Red    = #FF0000    Blue   = #0000FF
+    Purple = #800080    Teal   = #008080
+    Fuchsia= #FF00FF    Aqua   = #00FFFF
+-->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+  id       document-wide unique id
+  class    space separated list of classes
+  style    associated style info
+  title    advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id          ID             #IMPLIED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED"
+  >
+
+<!-- internationalization attributes
+  lang        language code (backwards compatible)
+  xml:lang    language code (as per XML 1.0 spec)
+  dir         direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #IMPLIED"
+  >
+
+<!-- attributes for common UI events
+  onclick     a pointer button was clicked
+  ondblclick  a pointer button was double clicked
+  onmousedown a pointer button was pressed down
+  onmouseup   a pointer button was released
+  onmousemove a pointer was moved onto the element
+  onmouseout  a pointer was moved away from the element
+  onkeypress  a key was pressed and released
+  onkeydown   a key was pressed down
+  onkeyup     a key was released
+-->
+<!ENTITY % events
+ "onclick     %Script;       #IMPLIED
+  ondblclick  %Script;       #IMPLIED
+  onmousedown %Script;       #IMPLIED
+  onmouseup   %Script;       #IMPLIED
+  onmouseover %Script;       #IMPLIED
+  onmousemove %Script;       #IMPLIED
+  onmouseout  %Script;       #IMPLIED
+  onkeypress  %Script;       #IMPLIED
+  onkeydown   %Script;       #IMPLIED
+  onkeyup     %Script;       #IMPLIED"
+  >
+
+<!-- attributes for elements that can get the focus
+  accesskey   accessibility key character
+  tabindex    position in tabbing order
+  onfocus     the element got the focus
+  onblur      the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey   %Character;    #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED"
+  >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!-- text alignment for p, div, h1-h6. The default is
+     align="left" for ltr headings, "right" for rtl -->
+
+<!ENTITY % TextAlign "align (left|center|right|justify) #IMPLIED">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.extra
+   "object | applet | img | map | iframe">
+	
+<!ENTITY % special.basic
+	"br | span | bdo">
+
+<!ENTITY % special
+   "%special.basic; | %special.extra;">
+
+<!ENTITY % fontstyle.extra "big | small | font | basefont">
+
+<!ENTITY % fontstyle.basic "tt | i | b | u
+                      | s | strike ">
+
+<!ENTITY % fontstyle "%fontstyle.basic; | %fontstyle.extra;">
+
+<!ENTITY % phrase.extra "sub | sup">
+<!ENTITY % phrase.basic "em | strong | dfn | code | q |
+                   samp | kbd | var | cite | abbr | acronym">
+
+<!ENTITY % phrase "%phrase.basic; | %phrase.extra;">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl | menu | dir">
+<!ENTITY % blocktext "pre | hr | blockquote | address | center">
+
+<!ENTITY % block
+    "p | %heading; | div | %lists; | %blocktext; | isindex | fieldset | table">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+   "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding img, object, applet, big, small,
+     sub, sup, font, or basefont -->
+
+<!ENTITY % pre.content
+   "(#PCDATA | a | %special.basic; | %fontstyle.basic; | %phrase.basic; |
+	   %inline.forms; | %misc.inline;)*">
+
+
+<!-- form uses %Flow; excluding form -->
+
+<!ENTITY % form.content "(#PCDATA | %block; | %inline; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form, form controls, iframe -->
+
+<!ENTITY % button.content
+   "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+      table | br | span | bdo | object | applet | img | map |
+      %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, frameset)>
+<!ATTLIST html
+  %i18n;
+  id          ID             #IMPLIED
+  xmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'
+  >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object|isindex)*">
+
+<!-- content model is %head.misc; combined with a single
+     title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+     ((title, %head.misc;, (base, %head.misc;)?) |
+      (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+  %i18n;
+  id          ID             #IMPLIED
+  profile     %URI;          #IMPLIED
+  >
+
+<!-- The title element is not considered part of the flow of text.
+       It should be displayed, for example as the page header or
+       window title. Exactly one title is required per document.
+    -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title 
+  %i18n;
+  id          ID             #IMPLIED
+  >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+  id          ID             #IMPLIED
+  href        %URI;          #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+  %i18n;
+  id          ID             #IMPLIED
+  http-equiv  CDATA          #IMPLIED
+  name        CDATA          #IMPLIED
+  content     CDATA          #REQUIRED
+  scheme      CDATA          #IMPLIED
+  >
+
+<!--
+  Relationship values can be used in principle:
+
+   a) for document specific toolbars/menus when used
+      with the link element in document head e.g.
+        start, contents, previous, next, index, end, help
+   b) to link to a separate style sheet (rel="stylesheet")
+   c) to make a link to a script (rel="script")
+   d) by stylesheets to control how collections of
+      html nodes are rendered into printed documents
+   e) to make a link to a printable version of this document
+      e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+  %attrs;
+  charset     %Charset;      #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  type        %ContentType;  #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  media       %MediaDesc;    #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+  %i18n;
+  id          ID             #IMPLIED
+  type        %ContentType;  #REQUIRED
+  media       %MediaDesc;    #IMPLIED
+  title       %Text;         #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+  id          ID             #IMPLIED
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #REQUIRED
+  language    CDATA          #IMPLIED
+  src         %URI;          #IMPLIED
+  defer       (defer)        #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Flow;>
+<!ATTLIST noscript
+  %attrs;
+  >
+
+<!--======================= Frames =======================================-->
+
+<!-- only one noframes element permitted per document -->
+
+<!ELEMENT frameset (frameset|frame|noframes)*>
+<!ATTLIST frameset
+  %coreattrs;
+  rows        %MultiLengths; #IMPLIED
+  cols        %MultiLengths; #IMPLIED
+  onload      %Script;       #IMPLIED
+  onunload    %Script;       #IMPLIED
+  >
+
+<!-- reserved frame names start with "_" otherwise starts with letter -->
+
+<!-- tiled window within frameset -->
+
+<!ELEMENT frame EMPTY>
+<!ATTLIST frame
+  %coreattrs;
+  longdesc    %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  src         %URI;          #IMPLIED
+  frameborder (1|0)          "1"
+  marginwidth %Pixels;       #IMPLIED
+  marginheight %Pixels;      #IMPLIED
+  noresize    (noresize)     #IMPLIED
+  scrolling   (yes|no|auto)  "auto"
+  >
+
+<!-- inline subwindow -->
+
+<!ELEMENT iframe %Flow;>
+<!ATTLIST iframe
+  %coreattrs;
+  longdesc    %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  src         %URI;          #IMPLIED
+  frameborder (1|0)          "1"
+  marginwidth %Pixels;       #IMPLIED
+  marginheight %Pixels;      #IMPLIED
+  scrolling   (yes|no|auto)  "auto"
+  align       %ImgAlign;     #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  >
+
+<!-- alternate content container for non frame-based rendering -->
+
+<!ELEMENT noframes (body)>
+<!ATTLIST noframes
+  %attrs;
+  >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Flow;>
+<!ATTLIST body
+  %attrs;
+  onload      %Script;       #IMPLIED
+  onunload    %Script;       #IMPLIED
+  background  %URI;          #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  text        %Color;        #IMPLIED
+  link        %Color;        #IMPLIED
+  vlink       %Color;        #IMPLIED
+  alink       %Color;        #IMPLIED
+  >
+
+<!ELEMENT div %Flow;>  <!-- generic language/style container -->
+<!ATTLIST div
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Headings =========================================-->
+
+<!--
+  There are six levels of headings from h1 (the most important)
+  to h6 (the least important).
+-->
+
+<!ELEMENT h1  %Inline;>
+<!ATTLIST h1
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list bullet styles -->
+
+<!ENTITY % ULStyle "(disc|square|circle)">
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+  %attrs;
+  type        %ULStyle;     #IMPLIED
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- Ordered list numbering style
+
+    1   arabic numbers      1, 2, 3, ...
+    a   lower alpha         a, b, c, ...
+    A   upper alpha         A, B, C, ...
+    i   lower roman         i, ii, iii, ...
+    I   upper roman         I, II, III, ...
+
+    The style is applied to the sequence number which by default
+    is reset to 1 for the first list item in an ordered list.
+-->
+<!ENTITY % OLStyle "CDATA">
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+  %attrs;
+  type        %OLStyle;      #IMPLIED
+  compact     (compact)      #IMPLIED
+  start       %Number;       #IMPLIED
+  >
+
+<!-- single column list (DEPRECATED) --> 
+<!ELEMENT menu (li)+>
+<!ATTLIST menu
+  %attrs;
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- multiple column list (DEPRECATED) --> 
+<!ELEMENT dir (li)+>
+<!ATTLIST dir
+  %attrs;
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- LIStyle is constrained to: "(%ULStyle;|%OLStyle;)" -->
+<!ENTITY % LIStyle "CDATA">
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+  %attrs;
+  type        %LIStyle;      #IMPLIED
+  value       %Number;       #IMPLIED
+  >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+  %attrs;
+  compact     (compact)      #IMPLIED
+  >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+  %attrs;
+  >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+  %attrs;
+  >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address (#PCDATA | %inline; | %misc.inline; | p)*>
+<!ATTLIST address
+  %attrs;
+  >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+  %attrs;
+  align       (left|center|right) #IMPLIED
+  noshade     (noshade)      #IMPLIED
+  size        %Pixels;       #IMPLIED
+  width       %Length;       #IMPLIED
+  >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding 
+        "img|object|applet|big|small|sub|sup|font|basefont" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+  %attrs;
+  width       %Number;      #IMPLIED
+  xml:space   (preserve)    #FIXED 'preserve'
+  >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Flow;>
+<!ATTLIST blockquote
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!--=================== Text alignment ===================================-->
+
+<!-- center content -->
+<!ELEMENT center %Flow;>
+<!ATTLIST center
+  %attrs;
+  >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+
+<!--
+  ins/del are allowed in block and inline content, but its
+  inappropriate to include block content within an ins element
+  occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+  %attrs;
+  %focus;
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+  %attrs;
+  >
+
+<!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+  %coreattrs;
+  %events;
+  lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #REQUIRED
+  >
+
+<!ELEMENT br EMPTY>   <!-- forced line break -->
+<!ATTLIST br
+  %coreattrs;
+  clear       (left|all|right|none) "none"
+  >
+
+<!ELEMENT em %Inline;>   <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;>   <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;>   <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;>   <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;>   <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;>  <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;>   <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;>   <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;>   <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;>   <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;>   <!-- inlined quote -->
+<!ATTLIST q
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;>   <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;>   <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;>   <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;>   <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;>   <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!ELEMENT u %Inline;>   <!-- underline -->
+<!ATTLIST u %attrs;>
+
+<!ELEMENT s %Inline;>   <!-- strike-through -->
+<!ATTLIST s %attrs;>
+
+<!ELEMENT strike %Inline;>   <!-- strike-through -->
+<!ATTLIST strike %attrs;>
+
+<!ELEMENT basefont EMPTY>  <!-- base font size -->
+<!ATTLIST basefont
+  id          ID             #IMPLIED
+  size        CDATA          #REQUIRED
+  color       %Color;        #IMPLIED
+  face        CDATA          #IMPLIED
+  >
+
+<!ELEMENT font %Inline;> <!-- local change to font -->
+<!ATTLIST font
+  %coreattrs;
+  %i18n;
+  size        CDATA          #IMPLIED
+  color       %Color;        #IMPLIED
+  face        CDATA          #IMPLIED
+  >
+
+<!--==================== Object ======================================-->
+<!--
+  object is used to embed objects as part of HTML pages.
+  param elements should precede other content. Parameters
+  can also be expressed as attribute/value pairs on the
+  object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form |%inline; | %misc;)*>
+<!ATTLIST object
+  %attrs;
+  declare     (declare)      #IMPLIED
+  classid     %URI;          #IMPLIED
+  codebase    %URI;          #IMPLIED
+  data        %URI;          #IMPLIED
+  type        %ContentType;  #IMPLIED
+  codetype    %ContentType;  #IMPLIED
+  archive     %UriList;      #IMPLIED
+  standby     %Text;         #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  border      %Pixels;       #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!--
+  param is used to supply a named property value.
+  In XML it would seem natural to follow RDF and support an
+  abbreviated syntax where the param elements are replaced
+  by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+  id          ID             #IMPLIED
+  name        CDATA          #REQUIRED
+  value       CDATA          #IMPLIED
+  valuetype   (data|ref|object) "data"
+  type        %ContentType;  #IMPLIED
+  >
+
+<!--=================== Java applet ==================================-->
+<!--
+  One of code or object attributes must be present.
+  Place param elements before other content.
+-->
+<!ELEMENT applet (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST applet
+  %coreattrs;
+  codebase    %URI;          #IMPLIED
+  archive     CDATA          #IMPLIED
+  code        CDATA          #IMPLIED
+  object      CDATA          #IMPLIED
+  alt         %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  width       %Length;       #REQUIRED
+  height      %Length;       #REQUIRED
+  align       %ImgAlign;     #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!--=================== Images ===========================================-->
+
+<!--
+   To avoid accessibility problems for people who aren't
+   able to see the image, you should provide a text
+   description using the alt and longdesc attributes.
+   In addition, avoid the use of server-side image maps.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+  %attrs;
+  src         %URI;          #REQUIRED
+  alt         %Text;         #REQUIRED
+  name        NMTOKEN        #IMPLIED
+  longdesc    %URI;          #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  ismap       (ismap)        #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  border      %Pixels;       #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!-- usemap points to a map element which may be in this document
+  or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+     separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+  %i18n;
+  %events;
+  id          ID             #REQUIRED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+  %attrs;
+  %focus;
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  href        %URI;          #IMPLIED
+  nohref      (nohref)       #IMPLIED
+  alt         %Text;         #REQUIRED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--================ Forms ===============================================-->
+
+<!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+  %attrs;
+  action      %URI;          #REQUIRED
+  method      (get|post)     "get"
+  name        NMTOKEN        #IMPLIED
+  enctype     %ContentType;  "application/x-www-form-urlencoded"
+  onsubmit    %Script;       #IMPLIED
+  onreset     %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  accept-charset %Charsets;  #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--
+  Each label must not contain more than ONE field
+  Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+  %attrs;
+  for         IDREF          #IMPLIED
+  accesskey   %Character;    #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  >
+
+<!ENTITY % InputType
+  "(text | password | checkbox |
+    radio | submit | reset |
+    file | hidden | image | button)"
+   >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY>     <!-- form control -->
+<!ATTLIST input
+  %attrs;
+  %focus;
+  type        %InputType;    "text"
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  checked     (checked)      #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  size        CDATA          #IMPLIED
+  maxlength   %Number;       #IMPLIED
+  src         %URI;          #IMPLIED
+  alt         CDATA          #IMPLIED
+  usemap      %URI;          #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  >
+
+<!ELEMENT select (optgroup|option)+>  <!-- option selector -->
+<!ATTLIST select
+  %attrs;
+  name        CDATA          #IMPLIED
+  size        %Number;       #IMPLIED
+  multiple    (multiple)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!ELEMENT optgroup (option)+>   <!-- option group -->
+<!ATTLIST optgroup
+  %attrs;
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #REQUIRED
+  >
+
+<!ELEMENT option (#PCDATA)>     <!-- selectable choice -->
+<!ATTLIST option
+  %attrs;
+  selected    (selected)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #IMPLIED
+  value       CDATA          #IMPLIED
+  >
+
+<!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->
+<!ATTLIST textarea
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  rows        %Number;       #REQUIRED
+  cols        %Number;       #REQUIRED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!--
+  The fieldset element is used to group form fields.
+  Only one legend element should occur in the content
+  and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+  %attrs;
+  >
+
+<!ENTITY % LAlign "(top|bottom|left|right)">
+
+<!ELEMENT legend %Inline;>     <!-- fieldset label -->
+<!ATTLIST legend
+  %attrs;
+  accesskey   %Character;    #IMPLIED
+  align       %LAlign;       #IMPLIED
+  >
+
+<!--
+ Content is %Flow; excluding a, form, form controls, iframe
+--> 
+<!ELEMENT button %button.content;>  <!-- push button -->
+<!ATTLIST button
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  type        (button|submit|reset) "submit"
+  disabled    (disabled)     #IMPLIED
+  >
+
+<!-- single-line text input control (DEPRECATED) -->
+<!ELEMENT isindex EMPTY>
+<!ATTLIST isindex
+  %coreattrs;
+  %i18n;
+  prompt      %Text;         #IMPLIED
+  >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+     "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+  
+<!-- horizontal placement of table relative to document -->
+<!ENTITY % TAlign "(left|center|right)">
+
+<!-- horizontal alignment attributes for cell contents
+
+  char        alignment char, e.g. char=":"
+  charoff     offset for alignment char
+-->
+<!ENTITY % cellhalign
+  "align      (left|center|right|justify|char) #IMPLIED
+   char       %Character;    #IMPLIED
+   charoff    %Length;       #IMPLIED"
+  >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+  "valign     (top|middle|bottom|baseline) #IMPLIED"
+  >
+
+<!ELEMENT table
+     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption  %Inline;>
+<!ELEMENT thead    (tr)+>
+<!ELEMENT tfoot    (tr)+>
+<!ELEMENT tbody    (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col      EMPTY>
+<!ELEMENT tr       (th|td)+>
+<!ELEMENT th       %Flow;>
+<!ELEMENT td       %Flow;>
+
+<!ATTLIST table
+  %attrs;
+  summary     %Text;         #IMPLIED
+  width       %Length;       #IMPLIED
+  border      %Pixels;       #IMPLIED
+  frame       %TFrame;       #IMPLIED
+  rules       %TRules;       #IMPLIED
+  cellspacing %Length;       #IMPLIED
+  cellpadding %Length;       #IMPLIED
+  align       %TAlign;       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  >
+
+<!ENTITY % CAlign "(top|bottom|left|right)">
+
+<!ATTLIST caption
+  %attrs;
+  align       %CAlign;       #IMPLIED
+  >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+     width=64        width in screen pixels
+     width=0.5*      relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+    Use thead to duplicate headers when breaking table
+    across page boundaries, or for static headers when
+    tbody sections are rendered in scrolling panel.
+
+    Use tfoot to duplicate footers when breaking table
+    across page boundaries, or for static footers when
+    tbody sections are rendered in scrolling panel.
+
+    Use multiple tbody sections when rules are needed
+    between groups of table rows.
+-->
+<!ATTLIST thead
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tfoot
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tbody
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tr
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  bgcolor     %Color;        #IMPLIED
+  >
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  nowrap      (nowrap)       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  width       %Pixels;       #IMPLIED
+  height      %Pixels;       #IMPLIED
+  >
+
+<!ATTLIST td
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  nowrap      (nowrap)       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  width       %Pixels;       #IMPLIED
+  height      %Pixels;       #IMPLIED
+  >
+
diff --git a/dtd/xhtml1/xhtml1-strict.dtd b/dtd/xhtml1/xhtml1-strict.dtd
new file mode 100644
index 0000000..2927b9e
--- /dev/null
+++ b/dtd/xhtml1/xhtml1-strict.dtd
@@ -0,0 +1,978 @@
+<!--
+   Extensible HTML version 1.0 Strict DTD
+
+   This is the same as HTML 4 Strict except for
+   changes due to the differences between XML and SGML.
+
+   Namespace = http://www.w3.org/1999/xhtml
+
+   For further information, see: http://www.w3.org/TR/xhtml1
+
+   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+   All Rights Reserved. 
+
+   This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+   PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+
+   $Revision: 1.1 $
+   $Date: 2002/08/01 13:56:03 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+   "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+   "xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+   "-//W3C//ENTITIES Symbols for XHTML//EN"
+   "xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+   "-//W3C//ENTITIES Special for XHTML//EN"
+   "xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+    <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+    <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+    <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+    <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+    <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+    <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+    <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+    <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+    <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+    <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+    <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+    <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+    <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+    <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+    <!-- used for titles etc. -->
+
+<!ENTITY % Length "CDATA">
+    <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+    <!-- pixel, percentage, or relative -->
+
+<!ENTITY % Pixels "CDATA">
+    <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+    <!-- comma separated list of lengths -->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+  id       document-wide unique id
+  class    space separated list of classes
+  style    associated style info
+  title    advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id          ID             #IMPLIED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED"
+  >
+
+<!-- internationalization attributes
+  lang        language code (backwards compatible)
+  xml:lang    language code (as per XML 1.0 spec)
+  dir         direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #IMPLIED"
+  >
+
+<!-- attributes for common UI events
+  onclick     a pointer button was clicked
+  ondblclick  a pointer button was double clicked
+  onmousedown a pointer button was pressed down
+  onmouseup   a pointer button was released
+  onmousemove a pointer was moved onto the element
+  onmouseout  a pointer was moved away from the element
+  onkeypress  a key was pressed and released
+  onkeydown   a key was pressed down
+  onkeyup     a key was released
+-->
+<!ENTITY % events
+ "onclick     %Script;       #IMPLIED
+  ondblclick  %Script;       #IMPLIED
+  onmousedown %Script;       #IMPLIED
+  onmouseup   %Script;       #IMPLIED
+  onmouseover %Script;       #IMPLIED
+  onmousemove %Script;       #IMPLIED
+  onmouseout  %Script;       #IMPLIED
+  onkeypress  %Script;       #IMPLIED
+  onkeydown   %Script;       #IMPLIED
+  onkeyup     %Script;       #IMPLIED"
+  >
+
+<!-- attributes for elements that can get the focus
+  accesskey   accessibility key character
+  tabindex    position in tabbing order
+  onfocus     the element got the focus
+  onblur      the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey   %Character;    #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED"
+  >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.pre
+   "br | span | bdo | map">
+
+
+<!ENTITY % special
+   "%special.pre; | object | img ">
+
+<!ENTITY % fontstyle "tt | i | b | big | small ">
+
+<!ENTITY % phrase "em | strong | dfn | code | q |
+                   samp | kbd | var | cite | abbr | acronym | sub | sup ">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl">
+<!ENTITY % blocktext "pre | hr | blockquote | address">
+
+<!ENTITY % block
+     "p | %heading; | div | %lists; | %blocktext; | fieldset | table">
+
+<!ENTITY % Block "(%block; | form | %misc;)*">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+   "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding big, small, sup or sup -->
+
+<!ENTITY % pre.content
+   "(#PCDATA | a | %fontstyle; | %phrase; | %special.pre; | %misc.inline;
+      | %inline.forms;)*">
+
+<!-- form uses %Block; excluding form -->
+
+<!ENTITY % form.content "(%block; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form and form controls -->
+
+<!ENTITY % button.content
+   "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+    table | %special; | %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, body)>
+<!ATTLIST html
+  %i18n;
+  id          ID             #IMPLIED
+  xmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'
+  >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object)*">
+
+<!-- content model is %head.misc; combined with a single
+     title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+     ((title, %head.misc;, (base, %head.misc;)?) |
+      (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+  %i18n;
+  id          ID             #IMPLIED
+  profile     %URI;          #IMPLIED
+  >
+
+<!-- The title element is not considered part of the flow of text.
+       It should be displayed, for example as the page header or
+       window title. Exactly one title is required per document.
+    -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title 
+  %i18n;
+  id          ID             #IMPLIED
+  >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+  href        %URI;          #REQUIRED
+  id          ID             #IMPLIED
+  >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+  %i18n;
+  id          ID             #IMPLIED
+  http-equiv  CDATA          #IMPLIED
+  name        CDATA          #IMPLIED
+  content     CDATA          #REQUIRED
+  scheme      CDATA          #IMPLIED
+  >
+
+<!--
+  Relationship values can be used in principle:
+
+   a) for document specific toolbars/menus when used
+      with the link element in document head e.g.
+        start, contents, previous, next, index, end, help
+   b) to link to a separate style sheet (rel="stylesheet")
+   c) to make a link to a script (rel="script")
+   d) by stylesheets to control how collections of
+      html nodes are rendered into printed documents
+   e) to make a link to a printable version of this document
+      e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+  %attrs;
+  charset     %Charset;      #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  type        %ContentType;  #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  media       %MediaDesc;    #IMPLIED
+  >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+  %i18n;
+  id          ID             #IMPLIED
+  type        %ContentType;  #REQUIRED
+  media       %MediaDesc;    #IMPLIED
+  title       %Text;         #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+  id          ID             #IMPLIED
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #REQUIRED
+  src         %URI;          #IMPLIED
+  defer       (defer)        #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Block;>
+<!ATTLIST noscript
+  %attrs;
+  >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Block;>
+<!ATTLIST body
+  %attrs;
+  onload          %Script;   #IMPLIED
+  onunload        %Script;   #IMPLIED
+  >
+
+<!ELEMENT div %Flow;>  <!-- generic language/style container -->
+<!ATTLIST div
+  %attrs;
+  >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+  %attrs;
+  >
+
+<!--=================== Headings =========================================-->
+
+<!--
+  There are six levels of headings from h1 (the most important)
+  to h6 (the least important).
+-->
+
+<!ELEMENT h1  %Inline;>
+<!ATTLIST h1
+   %attrs;
+   >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+   %attrs;
+   >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+   %attrs;
+   >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+   %attrs;
+   >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+   %attrs;
+   >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+   %attrs;
+   >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+  %attrs;
+  >
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+  %attrs;
+  >
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+  %attrs;
+  >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+  %attrs;
+  >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+  %attrs;
+  >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+  %attrs;
+  >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address %Inline;>
+<!ATTLIST address
+  %attrs;
+  >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+  %attrs;
+  >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding "img|object|big|small|sub|sup" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+  %attrs;
+  xml:space (preserve) #FIXED 'preserve'
+  >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Block;>
+<!ATTLIST blockquote
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+<!--
+  ins/del are allowed in block and inline content, but its
+  inappropriate to include block content within an ins element
+  occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+  %attrs;
+  %focus;
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+  %attrs;
+  >
+
+<!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+  %coreattrs;
+  %events;
+  lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #REQUIRED
+  >
+
+<!ELEMENT br EMPTY>   <!-- forced line break -->
+<!ATTLIST br
+  %coreattrs;
+  >
+
+<!ELEMENT em %Inline;>   <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;>   <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;>   <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;>   <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;>   <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;>  <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;>   <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;>   <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;>   <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;>   <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;>   <!-- inlined quote -->
+<!ATTLIST q
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;>   <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;>   <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;>   <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;>   <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;>   <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!--==================== Object ======================================-->
+<!--
+  object is used to embed objects as part of HTML pages.
+  param elements should precede other content. Parameters
+  can also be expressed as attribute/value pairs on the
+  object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST object
+  %attrs;
+  declare     (declare)      #IMPLIED
+  classid     %URI;          #IMPLIED
+  codebase    %URI;          #IMPLIED
+  data        %URI;          #IMPLIED
+  type        %ContentType;  #IMPLIED
+  codetype    %ContentType;  #IMPLIED
+  archive     %UriList;      #IMPLIED
+  standby     %Text;         #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  >
+
+<!--
+  param is used to supply a named property value.
+  In XML it would seem natural to follow RDF and support an
+  abbreviated syntax where the param elements are replaced
+  by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+  id          ID             #IMPLIED
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  valuetype   (data|ref|object) "data"
+  type        %ContentType;  #IMPLIED
+  >
+
+<!--=================== Images ===========================================-->
+
+<!--
+   To avoid accessibility problems for people who aren't
+   able to see the image, you should provide a text
+   description using the alt and longdesc attributes.
+   In addition, avoid the use of server-side image maps.
+   Note that in this DTD there is no name attribute. That
+   is only available in the transitional and frameset DTD.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+  %attrs;
+  src         %URI;          #REQUIRED
+  alt         %Text;         #REQUIRED
+  longdesc    %URI;          #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  ismap       (ismap)        #IMPLIED
+  >
+
+<!-- usemap points to a map element which may be in this document
+  or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+     separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+  %i18n;
+  %events;
+  id          ID             #REQUIRED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+  %attrs;
+  %focus;
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  href        %URI;          #IMPLIED
+  nohref      (nohref)       #IMPLIED
+  alt         %Text;         #REQUIRED
+  >
+
+<!--================ Forms ===============================================-->
+<!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+  %attrs;
+  action      %URI;          #REQUIRED
+  method      (get|post)     "get"
+  enctype     %ContentType;  "application/x-www-form-urlencoded"
+  onsubmit    %Script;       #IMPLIED
+  onreset     %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  accept-charset %Charsets;  #IMPLIED
+  >
+
+<!--
+  Each label must not contain more than ONE field
+  Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+  %attrs;
+  for         IDREF          #IMPLIED
+  accesskey   %Character;    #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  >
+
+<!ENTITY % InputType
+  "(text | password | checkbox |
+    radio | submit | reset |
+    file | hidden | image | button)"
+   >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY>     <!-- form control -->
+<!ATTLIST input
+  %attrs;
+  %focus;
+  type        %InputType;    "text"
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  checked     (checked)      #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  size        CDATA          #IMPLIED
+  maxlength   %Number;       #IMPLIED
+  src         %URI;          #IMPLIED
+  alt         CDATA          #IMPLIED
+  usemap      %URI;          #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  >
+
+<!ELEMENT select (optgroup|option)+>  <!-- option selector -->
+<!ATTLIST select
+  %attrs;
+  name        CDATA          #IMPLIED
+  size        %Number;       #IMPLIED
+  multiple    (multiple)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!ELEMENT optgroup (option)+>   <!-- option group -->
+<!ATTLIST optgroup
+  %attrs;
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #REQUIRED
+  >
+
+<!ELEMENT option (#PCDATA)>     <!-- selectable choice -->
+<!ATTLIST option
+  %attrs;
+  selected    (selected)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #IMPLIED
+  value       CDATA          #IMPLIED
+  >
+
+<!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->
+<!ATTLIST textarea
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  rows        %Number;       #REQUIRED
+  cols        %Number;       #REQUIRED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!--
+  The fieldset element is used to group form fields.
+  Only one legend element should occur in the content
+  and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+  %attrs;
+  >
+
+<!ELEMENT legend %Inline;>     <!-- fieldset label -->
+<!ATTLIST legend
+  %attrs;
+  accesskey   %Character;    #IMPLIED
+  >
+
+<!--
+ Content is %Flow; excluding a, form and form controls
+--> 
+<!ELEMENT button %button.content;>  <!-- push button -->
+<!ATTLIST button
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  type        (button|submit|reset) "submit"
+  disabled    (disabled)     #IMPLIED
+  >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+     "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+  
+<!-- horizontal alignment attributes for cell contents
+
+  char        alignment char, e.g. char=':'
+  charoff     offset for alignment char
+-->
+<!ENTITY % cellhalign
+  "align      (left|center|right|justify|char) #IMPLIED
+   char       %Character;    #IMPLIED
+   charoff    %Length;       #IMPLIED"
+  >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+  "valign     (top|middle|bottom|baseline) #IMPLIED"
+  >
+
+<!ELEMENT table
+     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption  %Inline;>
+<!ELEMENT thead    (tr)+>
+<!ELEMENT tfoot    (tr)+>
+<!ELEMENT tbody    (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col      EMPTY>
+<!ELEMENT tr       (th|td)+>
+<!ELEMENT th       %Flow;>
+<!ELEMENT td       %Flow;>
+
+<!ATTLIST table
+  %attrs;
+  summary     %Text;         #IMPLIED
+  width       %Length;       #IMPLIED
+  border      %Pixels;       #IMPLIED
+  frame       %TFrame;       #IMPLIED
+  rules       %TRules;       #IMPLIED
+  cellspacing %Length;       #IMPLIED
+  cellpadding %Length;       #IMPLIED
+  >
+
+<!ATTLIST caption
+  %attrs;
+  >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+     width=64        width in screen pixels
+     width=0.5*      relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+    Use thead to duplicate headers when breaking table
+    across page boundaries, or for static headers when
+    tbody sections are rendered in scrolling panel.
+
+    Use tfoot to duplicate footers when breaking table
+    across page boundaries, or for static footers when
+    tbody sections are rendered in scrolling panel.
+
+    Use multiple tbody sections when rules are needed
+    between groups of table rows.
+-->
+<!ATTLIST thead
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tfoot
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tbody
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tr
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST td
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  >
+
diff --git a/dtd/xhtml1/xhtml1-transitional.dtd b/dtd/xhtml1/xhtml1-transitional.dtd
new file mode 100644
index 0000000..628f27a
--- /dev/null
+++ b/dtd/xhtml1/xhtml1-transitional.dtd
@@ -0,0 +1,1201 @@
+<!--
+   Extensible HTML version 1.0 Transitional DTD
+
+   This is the same as HTML 4 Transitional except for
+   changes due to the differences between XML and SGML.
+
+   Namespace = http://www.w3.org/1999/xhtml
+
+   For further information, see: http://www.w3.org/TR/xhtml1
+
+   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+   All Rights Reserved. 
+
+   This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+   PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+   SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
+
+   $Revision: 1.2 $
+   $Date: 2002/08/01 18:37:55 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+   "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+   "xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+   "-//W3C//ENTITIES Symbols for XHTML//EN"
+   "xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+   "-//W3C//ENTITIES Special for XHTML//EN"
+   "xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+    <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+    <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+    <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+    <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+    <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+    <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+    <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+    <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+    <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+    <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+    <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+    <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+    <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+    <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+    <!-- used for titles etc. -->
+
+<!ENTITY % FrameTarget "NMTOKEN">
+    <!-- render in this frame -->
+
+<!ENTITY % Length "CDATA">
+    <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+    <!-- pixel, percentage, or relative -->
+
+<!ENTITY % Pixels "CDATA">
+    <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+    <!-- comma separated list of lengths -->
+
+<!-- used for object, applet, img, input and iframe -->
+<!ENTITY % ImgAlign "(top|middle|bottom|left|right)">
+
+<!-- a color using sRGB: #RRGGBB as Hex values -->
+<!ENTITY % Color "CDATA">
+
+<!-- There are also 16 widely known color names with their sRGB values:
+
+    Black  = #000000    Green  = #008000
+    Silver = #C0C0C0    Lime   = #00FF00
+    Gray   = #808080    Olive  = #808000
+    White  = #FFFFFF    Yellow = #FFFF00
+    Maroon = #800000    Navy   = #000080
+    Red    = #FF0000    Blue   = #0000FF
+    Purple = #800080    Teal   = #008080
+    Fuchsia= #FF00FF    Aqua   = #00FFFF
+-->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+  id       document-wide unique id
+  class    space separated list of classes
+  style    associated style info
+  title    advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id          ID             #IMPLIED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED"
+  >
+
+<!-- internationalization attributes
+  lang        language code (backwards compatible)
+  xml:lang    language code (as per XML 1.0 spec)
+  dir         direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #IMPLIED"
+  >
+
+<!-- attributes for common UI events
+  onclick     a pointer button was clicked
+  ondblclick  a pointer button was double clicked
+  onmousedown a pointer button was pressed down
+  onmouseup   a pointer button was released
+  onmousemove a pointer was moved onto the element
+  onmouseout  a pointer was moved away from the element
+  onkeypress  a key was pressed and released
+  onkeydown   a key was pressed down
+  onkeyup     a key was released
+-->
+<!ENTITY % events
+ "onclick     %Script;       #IMPLIED
+  ondblclick  %Script;       #IMPLIED
+  onmousedown %Script;       #IMPLIED
+  onmouseup   %Script;       #IMPLIED
+  onmouseover %Script;       #IMPLIED
+  onmousemove %Script;       #IMPLIED
+  onmouseout  %Script;       #IMPLIED
+  onkeypress  %Script;       #IMPLIED
+  onkeydown   %Script;       #IMPLIED
+  onkeyup     %Script;       #IMPLIED"
+  >
+
+<!-- attributes for elements that can get the focus
+  accesskey   accessibility key character
+  tabindex    position in tabbing order
+  onfocus     the element got the focus
+  onblur      the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey   %Character;    #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED"
+  >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!-- text alignment for p, div, h1-h6. The default is
+     align="left" for ltr headings, "right" for rtl -->
+
+<!ENTITY % TextAlign "align (left|center|right|justify) #IMPLIED">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.extra
+   "object | applet | img | map | iframe">
+	
+<!ENTITY % special.basic
+	"br | span | bdo">
+
+<!ENTITY % special
+   "%special.basic; | %special.extra;">
+
+<!ENTITY % fontstyle.extra "big | small | font | basefont">
+
+<!ENTITY % fontstyle.basic "tt | i | b | u
+                      | s | strike ">
+
+<!ENTITY % fontstyle "%fontstyle.basic; | %fontstyle.extra;">
+
+<!ENTITY % phrase.extra "sub | sup">
+<!ENTITY % phrase.basic "em | strong | dfn | code | q |
+                   samp | kbd | var | cite | abbr | acronym">
+
+<!ENTITY % phrase "%phrase.basic; | %phrase.extra;">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl | menu | dir">
+<!ENTITY % blocktext "pre | hr | blockquote | address | center | noframes">
+
+<!ENTITY % block
+    "p | %heading; | div | %lists; | %blocktext; | isindex |fieldset | table">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+   "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding img, object, applet, big, small,
+     font, or basefont -->
+
+<!ENTITY % pre.content
+   "(#PCDATA | a | %special.basic; | %fontstyle.basic; | %phrase.basic; |
+	   %inline.forms; | %misc.inline;)*">
+
+<!-- form uses %Flow; excluding form -->
+
+<!ENTITY % form.content "(#PCDATA | %block; | %inline; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form, form controls, iframe -->
+
+<!ENTITY % button.content
+   "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+      table | br | span | bdo | object | applet | img | map |
+      %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, body)>
+<!ATTLIST html
+  %i18n;
+  id          ID             #IMPLIED
+  xmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'
+  >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object|isindex)*">
+
+<!-- content model is %head.misc; combined with a single
+     title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+     ((title, %head.misc;, (base, %head.misc;)?) |
+      (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+  %i18n;
+  id          ID             #IMPLIED
+  profile     %URI;          #IMPLIED
+  >
+
+<!-- The title element is not considered part of the flow of text.
+       It should be displayed, for example as the page header or
+       window title. Exactly one title is required per document.
+    -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title 
+  %i18n;
+  id          ID             #IMPLIED
+  >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+  id          ID             #IMPLIED
+  href        %URI;          #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+  %i18n;
+  id          ID             #IMPLIED
+  http-equiv  CDATA          #IMPLIED
+  name        CDATA          #IMPLIED
+  content     CDATA          #REQUIRED
+  scheme      CDATA          #IMPLIED
+  >
+
+<!--
+  Relationship values can be used in principle:
+
+   a) for document specific toolbars/menus when used
+      with the link element in document head e.g.
+        start, contents, previous, next, index, end, help
+   b) to link to a separate style sheet (rel="stylesheet")
+   c) to make a link to a script (rel="script")
+   d) by stylesheets to control how collections of
+      html nodes are rendered into printed documents
+   e) to make a link to a printable version of this document
+      e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+  %attrs;
+  charset     %Charset;      #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  type        %ContentType;  #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  media       %MediaDesc;    #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+  %i18n;
+  id          ID             #IMPLIED
+  type        %ContentType;  #REQUIRED
+  media       %MediaDesc;    #IMPLIED
+  title       %Text;         #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+  id          ID             #IMPLIED
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #REQUIRED
+  language    CDATA          #IMPLIED
+  src         %URI;          #IMPLIED
+  defer       (defer)        #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Flow;>
+<!ATTLIST noscript
+  %attrs;
+  >
+
+<!--======================= Frames =======================================-->
+
+<!-- inline subwindow -->
+
+<!ELEMENT iframe %Flow;>
+<!ATTLIST iframe
+  %coreattrs;
+  longdesc    %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  src         %URI;          #IMPLIED
+  frameborder (1|0)          "1"
+  marginwidth %Pixels;       #IMPLIED
+  marginheight %Pixels;      #IMPLIED
+  scrolling   (yes|no|auto)  "auto"
+  align       %ImgAlign;     #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  >
+
+<!-- alternate content container for non frame-based rendering -->
+
+<!ELEMENT noframes %Flow;>
+<!ATTLIST noframes
+  %attrs;
+  >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Flow;>
+<!ATTLIST body
+  %attrs;
+  onload      %Script;       #IMPLIED
+  onunload    %Script;       #IMPLIED
+  background  %URI;          #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  text        %Color;        #IMPLIED
+  link        %Color;        #IMPLIED
+  vlink       %Color;        #IMPLIED
+  alink       %Color;        #IMPLIED
+  >
+
+<!ELEMENT div %Flow;>  <!-- generic language/style container -->
+<!ATTLIST div
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Headings =========================================-->
+
+<!--
+  There are six levels of headings from h1 (the most important)
+  to h6 (the least important).
+-->
+
+<!ELEMENT h1  %Inline;>
+<!ATTLIST h1
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list bullet styles -->
+
+<!ENTITY % ULStyle "(disc|square|circle)">
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+  %attrs;
+  type        %ULStyle;     #IMPLIED
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- Ordered list numbering style
+
+    1   arabic numbers      1, 2, 3, ...
+    a   lower alpha         a, b, c, ...
+    A   upper alpha         A, B, C, ...
+    i   lower roman         i, ii, iii, ...
+    I   upper roman         I, II, III, ...
+
+    The style is applied to the sequence number which by default
+    is reset to 1 for the first list item in an ordered list.
+-->
+<!ENTITY % OLStyle "CDATA">
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+  %attrs;
+  type        %OLStyle;      #IMPLIED
+  compact     (compact)      #IMPLIED
+  start       %Number;       #IMPLIED
+  >
+
+<!-- single column list (DEPRECATED) --> 
+<!ELEMENT menu (li)+>
+<!ATTLIST menu
+  %attrs;
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- multiple column list (DEPRECATED) --> 
+<!ELEMENT dir (li)+>
+<!ATTLIST dir
+  %attrs;
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- LIStyle is constrained to: "(%ULStyle;|%OLStyle;)" -->
+<!ENTITY % LIStyle "CDATA">
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+  %attrs;
+  type        %LIStyle;      #IMPLIED
+  value       %Number;       #IMPLIED
+  >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+  %attrs;
+  compact     (compact)      #IMPLIED
+  >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+  %attrs;
+  >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+  %attrs;
+  >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address (#PCDATA | %inline; | %misc.inline; | p)*>
+<!ATTLIST address
+  %attrs;
+  >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+  %attrs;
+  align       (left|center|right) #IMPLIED
+  noshade     (noshade)      #IMPLIED
+  size        %Pixels;       #IMPLIED
+  width       %Length;       #IMPLIED
+  >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding 
+        "img|object|applet|big|small|sub|sup|font|basefont" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+  %attrs;
+  width       %Number;      #IMPLIED
+  xml:space   (preserve)    #FIXED 'preserve'
+  >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Flow;>
+<!ATTLIST blockquote
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!--=================== Text alignment ===================================-->
+
+<!-- center content -->
+<!ELEMENT center %Flow;>
+<!ATTLIST center
+  %attrs;
+  >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+<!--
+  ins/del are allowed in block and inline content, but its
+  inappropriate to include block content within an ins element
+  occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+  %attrs;
+  %focus;
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+  %attrs;
+  >
+
+<!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+  %coreattrs;
+  %events;
+  lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #REQUIRED
+  >
+
+<!ELEMENT br EMPTY>   <!-- forced line break -->
+<!ATTLIST br
+  %coreattrs;
+  clear       (left|all|right|none) "none"
+  >
+
+<!ELEMENT em %Inline;>   <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;>   <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;>   <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;>   <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;>   <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;>  <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;>   <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;>   <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;>   <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;>   <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;>   <!-- inlined quote -->
+<!ATTLIST q
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;>   <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;>   <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;>   <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;>   <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;>   <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!ELEMENT u %Inline;>   <!-- underline -->
+<!ATTLIST u %attrs;>
+
+<!ELEMENT s %Inline;>   <!-- strike-through -->
+<!ATTLIST s %attrs;>
+
+<!ELEMENT strike %Inline;>   <!-- strike-through -->
+<!ATTLIST strike %attrs;>
+
+<!ELEMENT basefont EMPTY>  <!-- base font size -->
+<!ATTLIST basefont
+  id          ID             #IMPLIED
+  size        CDATA          #REQUIRED
+  color       %Color;        #IMPLIED
+  face        CDATA          #IMPLIED
+  >
+
+<!ELEMENT font %Inline;> <!-- local change to font -->
+<!ATTLIST font
+  %coreattrs;
+  %i18n;
+  size        CDATA          #IMPLIED
+  color       %Color;        #IMPLIED
+  face        CDATA          #IMPLIED
+  >
+
+<!--==================== Object ======================================-->
+<!--
+  object is used to embed objects as part of HTML pages.
+  param elements should precede other content. Parameters
+  can also be expressed as attribute/value pairs on the
+  object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST object
+  %attrs;
+  declare     (declare)      #IMPLIED
+  classid     %URI;          #IMPLIED
+  codebase    %URI;          #IMPLIED
+  data        %URI;          #IMPLIED
+  type        %ContentType;  #IMPLIED
+  codetype    %ContentType;  #IMPLIED
+  archive     %UriList;      #IMPLIED
+  standby     %Text;         #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  border      %Pixels;       #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!--
+  param is used to supply a named property value.
+  In XML it would seem natural to follow RDF and support an
+  abbreviated syntax where the param elements are replaced
+  by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+  id          ID             #IMPLIED
+  name        CDATA          #REQUIRED
+  value       CDATA          #IMPLIED
+  valuetype   (data|ref|object) "data"
+  type        %ContentType;  #IMPLIED
+  >
+
+<!--=================== Java applet ==================================-->
+<!--
+  One of code or object attributes must be present.
+  Place param elements before other content.
+-->
+<!ELEMENT applet (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST applet
+  %coreattrs;
+  codebase    %URI;          #IMPLIED
+  archive     CDATA          #IMPLIED
+  code        CDATA          #IMPLIED
+  object      CDATA          #IMPLIED
+  alt         %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  width       %Length;       #REQUIRED
+  height      %Length;       #REQUIRED
+  align       %ImgAlign;     #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!--=================== Images ===========================================-->
+
+<!--
+   To avoid accessibility problems for people who aren't
+   able to see the image, you should provide a text
+   description using the alt and longdesc attributes.
+   In addition, avoid the use of server-side image maps.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+  %attrs;
+  src         %URI;          #REQUIRED
+  alt         %Text;         #REQUIRED
+  name        NMTOKEN        #IMPLIED
+  longdesc    %URI;          #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  ismap       (ismap)        #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  border      %Length;       #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!-- usemap points to a map element which may be in this document
+  or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+     separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+  %i18n;
+  %events;
+  id          ID             #REQUIRED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED
+  name        CDATA          #IMPLIED
+  >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+  %attrs;
+  %focus;
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  href        %URI;          #IMPLIED
+  nohref      (nohref)       #IMPLIED
+  alt         %Text;         #REQUIRED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--================ Forms ===============================================-->
+
+<!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+  %attrs;
+  action      %URI;          #REQUIRED
+  method      (get|post)     "get"
+  name        NMTOKEN        #IMPLIED
+  enctype     %ContentType;  "application/x-www-form-urlencoded"
+  onsubmit    %Script;       #IMPLIED
+  onreset     %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  accept-charset %Charsets;  #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--
+  Each label must not contain more than ONE field
+  Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+  %attrs;
+  for         IDREF          #IMPLIED
+  accesskey   %Character;    #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  >
+
+<!ENTITY % InputType
+  "(text | password | checkbox |
+    radio | submit | reset |
+    file | hidden | image | button)"
+   >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY>     <!-- form control -->
+<!ATTLIST input
+  %attrs;
+  %focus;
+  type        %InputType;    "text"
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  checked     (checked)      #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  size        CDATA          #IMPLIED
+  maxlength   %Number;       #IMPLIED
+  src         %URI;          #IMPLIED
+  alt         CDATA          #IMPLIED
+  usemap      %URI;          #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  >
+
+<!ELEMENT select (optgroup|option)+>  <!-- option selector -->
+<!ATTLIST select
+  %attrs;
+  name        CDATA          #IMPLIED
+  size        %Number;       #IMPLIED
+  multiple    (multiple)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!ELEMENT optgroup (option)+>   <!-- option group -->
+<!ATTLIST optgroup
+  %attrs;
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #REQUIRED
+  >
+
+<!ELEMENT option (#PCDATA)>     <!-- selectable choice -->
+<!ATTLIST option
+  %attrs;
+  selected    (selected)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #IMPLIED
+  value       CDATA          #IMPLIED
+  >
+
+<!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->
+<!ATTLIST textarea
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  rows        %Number;       #REQUIRED
+  cols        %Number;       #REQUIRED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!--
+  The fieldset element is used to group form fields.
+  Only one legend element should occur in the content
+  and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+  %attrs;
+  >
+
+<!ENTITY % LAlign "(top|bottom|left|right)">
+
+<!ELEMENT legend %Inline;>     <!-- fieldset label -->
+<!ATTLIST legend
+  %attrs;
+  accesskey   %Character;    #IMPLIED
+  align       %LAlign;       #IMPLIED
+  >
+
+<!--
+ Content is %Flow; excluding a, form, form controls, iframe
+--> 
+<!ELEMENT button %button.content;>  <!-- push button -->
+<!ATTLIST button
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  type        (button|submit|reset) "submit"
+  disabled    (disabled)     #IMPLIED
+  >
+
+<!-- single-line text input control (DEPRECATED) -->
+<!ELEMENT isindex EMPTY>
+<!ATTLIST isindex
+  %coreattrs;
+  %i18n;
+  prompt      %Text;         #IMPLIED
+  >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+     "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+  
+<!-- horizontal placement of table relative to document -->
+<!ENTITY % TAlign "(left|center|right)">
+
+<!-- horizontal alignment attributes for cell contents
+
+  char        alignment char, e.g. char=':'
+  charoff     offset for alignment char
+-->
+<!ENTITY % cellhalign
+  "align      (left|center|right|justify|char) #IMPLIED
+   char       %Character;    #IMPLIED
+   charoff    %Length;       #IMPLIED"
+  >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+  "valign     (top|middle|bottom|baseline) #IMPLIED"
+  >
+
+<!ELEMENT table
+     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption  %Inline;>
+<!ELEMENT thead    (tr)+>
+<!ELEMENT tfoot    (tr)+>
+<!ELEMENT tbody    (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col      EMPTY>
+<!ELEMENT tr       (th|td)+>
+<!ELEMENT th       %Flow;>
+<!ELEMENT td       %Flow;>
+
+<!ATTLIST table
+  %attrs;
+  summary     %Text;         #IMPLIED
+  width       %Length;       #IMPLIED
+  border      %Pixels;       #IMPLIED
+  frame       %TFrame;       #IMPLIED
+  rules       %TRules;       #IMPLIED
+  cellspacing %Length;       #IMPLIED
+  cellpadding %Length;       #IMPLIED
+  align       %TAlign;       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  >
+
+<!ENTITY % CAlign "(top|bottom|left|right)">
+
+<!ATTLIST caption
+  %attrs;
+  align       %CAlign;       #IMPLIED
+  >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+     width=64        width in screen pixels
+     width=0.5*      relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+    Use thead to duplicate headers when breaking table
+    across page boundaries, or for static headers when
+    tbody sections are rendered in scrolling panel.
+
+    Use tfoot to duplicate footers when breaking table
+    across page boundaries, or for static footers when
+    tbody sections are rendered in scrolling panel.
+
+    Use multiple tbody sections when rules are needed
+    between groups of table rows.
+-->
+<!ATTLIST thead
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tfoot
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tbody
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tr
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  bgcolor     %Color;        #IMPLIED
+  >
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  nowrap      (nowrap)       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  width       %Length;       #IMPLIED
+  height      %Length;       #IMPLIED
+  >
+
+<!ATTLIST td
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  nowrap      (nowrap)       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  width       %Length;       #IMPLIED
+  height      %Length;       #IMPLIED
+  >
+
diff --git a/lib_src/flute_src.zip b/lib_src/flute_src.zip
new file mode 100644
index 0000000..11df86d
Binary files /dev/null and b/lib_src/flute_src.zip differ
diff --git a/lib_src/sac_src.zip b/lib_src/sac_src.zip
new file mode 100644
index 0000000..f19d2a3
Binary files /dev/null and b/lib_src/sac_src.zip differ
diff --git a/res/LICENCE b/res/LICENCE
new file mode 100644
index 0000000..b4a2ac3
--- /dev/null
+++ b/res/LICENCE
@@ -0,0 +1,5 @@
+All rights granted.
+
+This software is free and will remain free.
+
+To use at your own responsibility.
diff --git a/res/MANIFEST_css2fop.MF b/res/MANIFEST_css2fop.MF
new file mode 100644
index 0000000..744d2cd
--- /dev/null
+++ b/res/MANIFEST_css2fop.MF
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+Main-Class: be.re.css.CSSToFOP
+Implementation-Title: CSSToFOP
+Implementation-Version: 1.6.2
+Implementation-Vendor: Pincette bvba
+Class-Path: fop.jar
diff --git a/res/MANIFEST_css2fopnew.MF b/res/MANIFEST_css2fopnew.MF
new file mode 100644
index 0000000..41dd1d5
--- /dev/null
+++ b/res/MANIFEST_css2fopnew.MF
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+Main-Class: be.re.css.CSSToFOPNew
+Implementation-Title: CSSToFOPNew
+Implementation-Version: 1.6.2
+Implementation-Vendor: Pincette bvba
+Class-Path: fop.jar
diff --git a/res/MANIFEST_css2xep.MF b/res/MANIFEST_css2xep.MF
new file mode 100644
index 0000000..b713c45
--- /dev/null
+++ b/res/MANIFEST_css2xep.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Main-Class: be.re.css.CSSToXEP
+Implementation-Title: CSSToXEP
+Implementation-Version: 1.6.2
+Implementation-Vendor: Pincette bvba
+Class-Path: saxon8.jar xep.jar
+
diff --git a/res/MANIFEST_css2xinc.MF b/res/MANIFEST_css2xinc.MF
new file mode 100644
index 0000000..c31bed6
--- /dev/null
+++ b/res/MANIFEST_css2xinc.MF
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+Main-Class: be.re.css.CSSToXinc
+Implementation-Title: CSSToXinc
+Implementation-Version: 1.6.2
+Implementation-Vendor: Pincette bvba
+Class-Path: xinc.jar commons-logging.jar
diff --git a/res/MANIFEST_css2xsl.MF b/res/MANIFEST_css2xsl.MF
new file mode 100644
index 0000000..cddba9d
--- /dev/null
+++ b/res/MANIFEST_css2xsl.MF
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+Main-Class: be.re.css.CSSToXSLFormatter
+Implementation-Title: CSSToXSLFormatter
+Implementation-Version: 1.6.2
+Implementation-Vendor: Pincette bvba
+Class-Path: XfoJavaCtl.jar
diff --git a/res/MANIFEST_css2xslfo.MF b/res/MANIFEST_css2xslfo.MF
new file mode 100644
index 0000000..bd09ed6
--- /dev/null
+++ b/res/MANIFEST_css2xslfo.MF
@@ -0,0 +1,5 @@
+Manifest-Version: 1.0
+Main-Class: be.re.css.CSSToXSLFO
+Implementation-Title: CSSToXSLFO
+Implementation-Version: 1.6.2
+Implementation-Vendor: Pincette bvba
diff --git a/res/W3C_COPYRIGHT.html b/res/W3C_COPYRIGHT.html
new file mode 100644
index 0000000..2424d22
--- /dev/null
+++ b/res/W3C_COPYRIGHT.html
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>W3C IPR SOFTWARE NOTICE</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+  <style type='text/css'>
+     body { color: black; background: white; }
+  </style>
+</head>
+
+<body>
+<h1>W3C IPR SOFTWARE NOTICE</h1>
+
+<h3>Copyright � 2002 World Wide Web Consortium, (Massachusetts Institute of
+Technology, Institut National de Recherche en Informatique et en Automatique,
+Keio University). All Rights Reserved.</h3>
+
+<p><b>Note:</b> The original version of the W3C Software Copyright Notice and
+License could be found at <a
+href="http://www.w3.org/Consortium/Legal/copyright-software-19980720">http://www.w3.org/Consortium/Legal/copyright-software-19980720</a></p>
+
+<h3>Copyright � 1994-2000 <a href="http://www.w3.org/">World Wide Web
+Consortium</a>, (<a href="http://www.lcs.mit.edu/">Massachusetts Institute of
+Technology</a>, <a href="http://www.inria.fr/">Institut National de Recherche
+en Informatique et en Automatique</a>, <a href="http://www.keio.ac.jp/">Keio
+University</a>). All Rights Reserved. http://www.w3.org/Consortium/Legal/</h3>
+
+<p>This W3C work (including software, documents, or other related items) is
+being provided by the copyright holders under the following license. By
+obtaining, using and/or copying this work, you (the licensee) agree that you
+have read, understood, and will comply with the following terms and
+conditions:</p>
+
+<p>Permission to use, copy, and modify this software and its documentation,
+with or without modification,� for any purpose and without fee or royalty is
+hereby granted, provided that you include the following on ALL copies of the
+software and documentation or portions thereof, including modifications, that
+you make:</p>
+<ol>
+  <li>The full text of this NOTICE in a location viewable to users of the
+    redistributed or derivative work.</li>
+  <li>Any pre-existing intellectual property disclaimers, notices, or terms
+    and conditions. If none exist, a short notice of the following form
+    (hypertext is preferred, text is permitted) should be used within the body
+    of any redistributed or derivative code: "Copyright � 2002
+    <a href="http://www.w3.org/">World Wide Web Consortium</a>, (<a
+    href="http://www.lcs.mit.edu/">Massachusetts Institute of Technology</a>,
+    <a href="http://www.inria.fr/">Institut National de Recherche en
+    Informatique et en Automatique</a>, <a href="http://www.keio.ac.jp/">Keio
+    University</a>).  All Rights Reserved.
+    http://www.w3.org/Consortium/Legal/"</li>
+  <li>Notice of any changes or modifications to the W3C files, including the
+    date changes were made. (We recommend you provide URIs to the location
+    from which the code is derived.)</li>
+</ol>
+
+<p>THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS
+MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
+PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY
+THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.</p>
+
+<p>COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR
+DOCUMENTATION.</p>
+
+<p>The name and trademarks of copyright holders may NOT be used in advertising
+or publicity pertaining to the software without specific, written prior
+permission. Title to copyright in this software and any associated
+documentation will at all times remain with copyright holders.</p>
+</body>
+</html>
diff --git a/res/release_notes.txt b/res/release_notes.txt
new file mode 100644
index 0000000..195c801
--- /dev/null
+++ b/res/release_notes.txt
@@ -0,0 +1,351 @@
+Release Notes CSSToXSLFO 1.6.2
+23 August 2010
+
+1. Requirements
+
+You need JRE 1.5 or a higher version to run the application.
+
+If you run css2xep.jar you also have to specify another XSLT processor, because
+XEP uses Saxon 6.5.x, with which it doesn't work. You can either prepend
+another XSLT processor to the boot classpath or you can simply copy saxon8.jar
+in the XEP lib directory.
+
+2. Installation
+
+You can place the jar file where you want and launch the application as
+follows:
+
+> java -jar css2xslfo.jar options
+
+Consult the manual for details about running the application.
+
+3. Changes
+
+3.1 Changes Since Version 1.6.1
+
+3051512: Recompile for FOP 1.0
+
+Bug fixes
+
+3.2 Changes Since Version 1.6
+
+Bug fixes
+
+2571787: Bug in ProjectorFilter
+2588555: The value "transparent" is not recognized as a color
+2758546: Relative URLs are always resolved to absolute URLs
+
+3.3 Changes Since Version 1.5.2
+
+Bug fixes
+
+1722531: hard coded urls in css file are all set to lowercase
+1911693: xml:lang or lang on document element is ignored
+1984171: The XHTML catalog is no longer included
+1997883: table element with a region property causes program to die
+
+New features
+
+- Add FOP command-line parsing for fopnew (feature request 1989920). With the
+  new "-fop" option any FOP command-line option can be used. Note that the
+  complete command-line syntax is not compatible with the previous version. The
+  "-fc" has been removed, because there now is the FOP equivalent.
+- Add the "-config" command-line option to the Xinc variant.
+- Add the "-config" command-line option to the XSLFormatter variant.
+
+3.4 Changes Since Version 1.5.1
+
+Bug fixes
+
+1807366: Floating point values are locale dependent
+1811419: The text-align-last property is not treated as inherited
+1824076: Splitting shorthand properties four ways uses a wrong order
+1843554: Setting float to "none" still generates an fo:float
+
+3.5 Changes Since Version 1.5
+
+Bug fixes
+
+1802080: A named page sequence with only tables is considered empty
+
+3.6 Changes Since Version 1.4.2
+
+Bug fixes
+
+1716939: Regression because of invalid property bug (1691044)
+1722531: hard coded urls in css file are all set to lowercase
+1722541: underscores in class name
+1723310: Relative URLs in a style sheet are not resolved
+1731447: Inline properties are not applied to "graphic" and "leader"
+1733555: The width and height properties are not applied to "graphic"
+1737664: Floating point values shouldn't use exponential notation
+1767912: Using as filter without calling parse or setParent fails
+1768297: The text-align property is no longer applied to table cells
+1770690: The page-break properties are no longer applied to table-row
+1778499: The fopnew option -fc is broken
+
+New features
+
+- Support last pages, analogous to first pages (feature request 1720073).
+- Implement the "list-style-image" property (feature request 1721782).
+- Implement the background, border and padding page properties that are defined
+  in CSS3 (feature request 1724392). This deviates from that specification in
+  that the properties are applied to the region-body, because in XSL-FO they are
+  not defined at the page master level.
+
+3.7 Changes Since Version 1.4.1
+
+Bug fixes
+
+1701428: The border-width shorthand property is not split
+1715203: Regression because of font-family bug (1679537)
+
+3.8 Changes Since Version 1.4
+
+Bug fixes
+
+1679537: Font family names not quoted when they contain spaces
+1681734: Only block-container should have absolute positioning
+1684177: ID attribute on body is duplicated
+1691044: Unexpected CSS attributes come out as bogus FO attributes
+1691392: Named strings fail when contents is fragmented
+
+3.9 Changes Since Version 1.3.3
+
+Bug fixes
+
+1594025: Css2FopNew user configuration file
+1626094: Testing a style sheet URL is too slow
+
+New features
+
+- New Ant tasks (feature request 1554682).
+- New options "-config" and "-q" for css2xep.jar.
+
+3.10 Changes Since Version 1.3.2
+
+Bug fixes
+
+1496388: Rules with document element don't work when in open document
+1496389: xml:base treatment is not scoped
+1553292: BASE element and xml:base not taken into account for links
+
+New features
+
+Use version 0.92beta instead of 0.91beta for the package for the new FOP branch.
+
+3.11 Changes Since Version 1.3.1
+
+Bug fixes
+
+1359835: When display is "none" on regions, they are no longer shown
+1462265: Regions which are not specific are not inherited
+1482296: Overriding anonymous page context properties doesn't work
+1482644: Drop width conversion for % values when parent has no width
+
+3.12 Changes Since Version 1.3
+
+Bug fixes
+
+1455266: Regression with XSLTC because of prefix mapping events
+
+3.13 Changes Since Version 1.2
+
+Bug fixes
+
+1356430: list-style and its singles are not treated correctly
+1359835: When display is "none" on regions, they are no longer shown
+1361765: No default for page counter reset
+1425336: Invalid flow-name references
+
+New features
+
+- CSS3 namespaces.
+- Implement the "list-style-position" property.
+- Implement the ":first-letter" pseudo element.
+- Add the "wrapper" display type, which prevents an element from contributing
+  formatting objects, but for which property inheritance works.
+- Add DeltaXML (http://www.deltaxml.com) to the User Agent style sheet. It
+  takes care of the difference mark-up in the DeltaXML namespace. Note that the
+  style sheet uses change bars, which work only for XEP4
+  (http://www.renderx.com) at the moment.
+- Add XLink to the User Agent style sheet. This merely consists of supporting
+  the global XLink "href" attribute.
+- Relax the constraint that there should be no whitespace between a footnote
+  reference and a footnote body. Whitespace between them is now gobbled.
+- Add the possibility for a footnote body to have a ":before" pseudo element
+  with the display type "footnote-reference" instead of an explicit footnote
+  reference element. The generated contents will go in the flow as well as in
+  the footnote body.
+- Add support for xml:id.
+- Add the "foreign" display type. Elements having it end up in an
+  fo:instream-foreign-object element.
+
+3.14 Changes Since Version 1.1
+
+Bug fixes
+
+1348367: Fix table normalisation when display type is "none".
+1348374: Fix link processing.
+1348383: A CSS style sheet is not treated as case-insensitive.
+1349014: Correct manual section about running with FOP.
+
+New features
+
+- The special Unicode spaces U+2000 through U+200A are post-processed using
+  fo:leader elements (reminiscent of Jirka Kosek's DocBook style sheet).
+
+3.15 Changes Since Version 1.0
+
+Bug fixes
+
+1281230: The presence of the anonymous @page rule is assumed.
+1285991: Move fo:marker to the next allowed place in the XSL-FO tree.
+1286078: Turn off hyphenation by default for User Agent page set-up.
+1291094: A marker pseudo element shouldn't imply the other is one too.
+1315758: Don't generate empty page sequences.
+
+New features
+
+- Add the "colspan" and "rowspan" properties for table cells.
+- Add the unit "pcw" (proportional column width) for table column widths.
+- Add the "table-omit-footer-at-break" and "table-omit-header-at-break"
+  properties for tables.
+
+3.16 Changes Since Version 1.0rc2
+
+Incompatible changes
+
+- The "footnote" property no longer exists. It is replaced by the new display
+  types "footnote-reference" and "footnote-body". This provides control over
+  contents and style of both the footnote reference and body. The list style
+  type "footnote" has also been added. It generates symbols such as the
+  asterix, dagger, etc.
+- Regions have an incompatible restriction. They must now be the first children
+  of the body region element. For XHTML the latter is the body element.
+- Move "counter-reset" for the "page" counter from the static regions to the
+  @page rule.
+- Hyphenation is turned off by default instead of on. This puts it in line
+  with XSL-FO.
+
+Enhancements
+
+- Add the "precedence" property for the top and bottom regions.
+- Implement the change bar extension. This works only with XEP at the moment.
+- Add the link extension, which covers internal and external links.
+- Make it work with Saxon 8.3 and later.
+- Make it work with JDK 1.5 without having to prepend an XSLT processor in the
+  boot class path.
+- Remove the CSS rules for the XHTML elements "sup" and "sub" from the User
+  Agent style sheet. Set the "line-height-shift-adjustment" property to
+  "disregard-shifts" to compensate this.
+- Show a stack trace only in the main functions when -Dbe.re.stack is on.
+- Generalize the named pages feature. It is no longer bound to a sibling list
+  below the XHTML "body" element or the document element for another XML
+  vocabulary.
+- Enhance performance and reduce memory usage.
+- Add a Xinc package.
+- Add the XSL-FO "force-page-count" property.
+- Implement markers with the limitation that the value "auto" is not supported
+  for the the "width" property. Check out section 12.6 of the CSS2
+  specification to learn how you can have much more control over lists through
+  markers.
+- Implement the "column-span" property for multicolumn pages.
+- Introduce the display type "graphic".
+- Add the pseudo page "blank".
+
+Bug fixes
+
+- Fix the "string-set" property bug. It didn't cascade properly and it didn't
+  work when placed in the XHTML "style" attribute.
+- Fix the CSSToXSLFOFilter constructor. It didn't check the absence of the
+  user agent parameters.
+- Fix the absence of the "any" simple-page-master when there were @page rules
+  without an anonymous @page rule.
+- Fix a page rule cascade bug. The page rules weren't split before sorting them.
+- Do the inheritance of text-align and vertical-align in tables only for XHTML
+  tables and take into account the specificity of UA rules and universal
+  selectors.
+- Fix the bug where properties on pseudo elements were also applied to the
+  elements themselves.
+- Change the processing instruction xml:stylesheet into xml-stylesheet. The
+  XML namespaces specification doesn't allow colons in PI targets. The new
+  target follows the CSS2.1 convention.
+- Fix the combination of pseudo classes and pseudo elements. The specification
+  ":first-child:before" didn't work.
+- Fix the application of the "content" property. It didn't cascade, but
+  combined all matched property values.
+- Fix the specificity calculation in the case where several conditions are
+  combined.
+- Set the "font-selection-strategy" property to "character-by-character", which
+  corresponds to CSS.
+- Implement centering of blocks and tables when "margin-left" and
+  "margin-right" are set to "auto".
+- Report missing command-line options in css2fop.jar.
+- Produce a descent error message when an URL is invalid when trying to parse
+  a CSS style sheet.
+- The value "contents" for the "string-set" property also included the
+  generated content for the element it was set on.
+- Fix the bug where the "userAgentParameters" argument to the constructor can't
+  be "null".
+- Fix the treatment of the "clear" property.
+
+3.17 Changes Since Version 1.0rc1
+
+- Install an error handler on the filter chain for the validating mode,
+  otherwise there are no error messages when Xalan 2.6.0 or higher are used.
+  This is because Xalan's TraXFilter now correctly propagates error handler
+  events instead of throwing an exception.
+- Also add the default unit "px" to the "width" and "height" of the XHTML "img"
+  element if there is no specified unit.
+- Honor the "media" attribute of the XHTML "style" and "link" elements.
+- Fix a selector bug in the case unnamed element selectors are used elsewhere
+  than at the final position.
+- Fix a named pages regression when using a more recent Xalan version.
+- Implement the multicolumn extension.
+
+3.18 Changes Since Version 1.0b3
+
+- Implement positioned elements (position, top, bottom, left and right
+  properties).
+- Implement layered presentation (z-index property).
+- Use a block-container instead of a plain block when one of the following
+  properties is specified on a block-level element: clip, height, min-height,
+  min-width, overflow, width. This bug disabled those properties.
+- Add a FOP package variant with a FOP filter that removes some of the
+  non-supported XSL-FO properties.
+- Implement the additional CSS3 List Module glyphs for the list-style-type
+  property.
+
+3.19 Changes Since Version 1.0b2
+
+- Change the common -p option to accept a comma-separated list of URLs or
+  filenames instead of just one.
+- The anonymous page block elements following a named page block element are
+  now part of the same name page type of the latter.
+- Give lower precedence to the UA style sheet.
+- Fix a bug in the translation of XHTML attributes into CSS properties.
+- Fix a bug in the static region mapping onto pages.
+- Fix a bug in the load of the embedded XHTML catalog.
+- Document the footnote extension.
+- Document the orientation extension.
+- Complete the "Compliance With CSS2" section of the manual.
+
+3.20 Changes Since Version 1.0b1
+
+- Drop XSLTC because it produces wrong results.
+- Add the option "-v" to turn on XML validation explicitly. By default no
+  validation is done.
+- Replace the deprecated XEP interface with the new interface.
+
+4. Known Problems
+
+- The built-in XSLT-processors in JDK1.5.0 and JDK1.6.0 don't treat the
+  "ancestor-or-self" axis as a reverse axis. This causes the "list-style-type"
+  detection to fail for nested lists with different style types. As a
+  workaround you should prepend Saxon or Xalan 2.7.0 to the boot classpath.
+
+5. Contact Information
+
+Comments and bug reports can be sent to support at pincette.biz. Information about
+the application is available at http://www.re.be/css2xslfo/.
diff --git a/src/META-INF/services/org.w3c.css.sac.parser b/src/META-INF/services/org.w3c.css.sac.parser
new file mode 100644
index 0000000..a60a1e9
--- /dev/null
+++ b/src/META-INF/services/org.w3c.css.sac.parser
@@ -0,0 +1 @@
+org.w3c.flute.parser.Parser
diff --git a/src/be/re/css/BlockContainerFilter.java b/src/be/re/css/BlockContainerFilter.java
new file mode 100644
index 0000000..dafaa76
--- /dev/null
+++ b/src/be/re/css/BlockContainerFilter.java
@@ -0,0 +1,200 @@
+package be.re.css;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Checks properties to see if an element has to be wrapped in a
+ * fo:block-container.
+ * @author Werner Donn\u00e9
+ */
+
+class BlockContainerFilter extends XMLFilterImpl
+
+{
+
+  private final static Set	containerProperties =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "bottom", "clip", "height", "left", "max-height", "max-width",
+            "min-height", "min-width", "orientation", "overflow", "position",
+            "right", "top", "width", "z-index"
+        }
+      )
+    );
+  private final static Set	triggeringBlockProperties =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "clip", "height", "max-height", "max-width", "min-height",
+            "min-width", "orientation", "overflow", "width", "z-index"
+        }
+      )
+    );
+  private Stack		stack = new Stack();
+
+
+
+  BlockContainerFilter()
+  {
+  }
+
+
+
+  BlockContainerFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    super.endElement(namespaceURI, localName, qName);
+
+    if (stack.pop().equals(new Boolean(true)))
+    {
+      super.endElement(Constants.XSLFO, "block", "fo:block");
+      super.
+        endElement(Constants.XSLFO, "block-container", "fo:block-container");
+    }
+  }
+
+
+
+  private static boolean
+  isContainerAttribute(Attributes atts, int index)
+  {
+    String	name = atts.getLocalName(index);
+
+    return
+      Constants.CSS.equals(atts.getURI(index)) &&
+        (
+          containerProperties.contains(name) || name.startsWith("background-")
+            || name.startsWith("border-") || name.startsWith("margin-") ||
+            name.startsWith("padding-") || name.startsWith("page-break-")
+        );
+  }
+
+
+
+  private static AttributesImpl
+  selectAttributes(Attributes atts, boolean container)
+  {
+    AttributesImpl	result = new AttributesImpl();
+
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if (container == isContainerAttribute(atts, i))
+      {
+        result.addAttribute
+        (
+          atts.getURI(i),
+          atts.getLocalName(i),
+          atts.getQName(i),
+          atts.getType(i),
+          atts.getValue(i)
+        );
+      }
+    }
+
+    return result;
+  }
+
+
+
+  private static boolean
+  shouldWrap(Attributes atts)
+  {
+    boolean	block =
+      "block".equals(atts.getValue(Constants.CSS, "display"));
+
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if
+      (
+        Constants.CSS.equals(atts.getURI(i))				&&
+        (
+          (
+            block							&&
+            triggeringBlockProperties.contains(atts.getLocalName(i))
+          )								||
+          (
+            "position".equals(atts.getLocalName(i))			&&
+            (
+              "absolute".equals(atts.getValue(i))			||
+              "fixed".equals(atts.getValue(i))
+            )
+          )
+        )
+      )
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    boolean	wrap = shouldWrap(atts);
+
+    if (wrap)
+    {
+      super.startElement
+      (
+        Constants.XSLFO,
+        "block-container",
+        "fo:block-container",
+        selectAttributes(atts, true)
+      );
+
+      super.startElement
+      (
+        Constants.XSLFO,
+        "block",
+        "fo:block",
+        new AttributesImpl()
+      );
+    }
+
+    super.startElement
+    (
+      namespaceURI,
+      localName,
+      qName,
+      wrap ? selectAttributes(atts, false) : atts
+    );
+
+    stack.push(new Boolean(wrap));
+  }
+
+} // BlockContainerFilter
diff --git a/src/be/re/css/CSSToFOP.java b/src/be/re/css/CSSToFOP.java
new file mode 100644
index 0000000..e31be0d
--- /dev/null
+++ b/src/be/re/css/CSSToFOP.java
@@ -0,0 +1,396 @@
+package be.re.css;
+
+import be.re.xml.sax.ProtectEventHandlerFilter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamSource;
+import org.apache.avalon.framework.logger.ConsoleLogger;
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.avalon.framework.logger.NullLogger;
+import org.apache.fop.apps.Driver;
+import org.apache.fop.apps.Options;
+import org.apache.fop.messaging.MessageHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+
+
+/**
+ * Convenience class for the conversion from CSS to the FOP XSL-FO formatter.
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToFOP
+
+{
+
+  public static void
+  convert(URL in, OutputStream out, URL userAgentStyleSheet, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in.openStream(),
+      out,
+      in,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      format,
+      false,
+      false
+    );
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert(in, out, null, format);
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out, URL userAgentStyleSheet, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in,
+      out,
+      null,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      format,
+      false,
+      false
+    );
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			baseUrl,
+    URL			userAgentStyleSheet,
+    URL			catalog,
+    Map			parameters,
+    URL[]		preprocessors,
+    int			format,
+    boolean		quiet,
+    boolean		validate
+  ) throws IOException, CSSToXSLFOException
+  {
+    try
+    {
+      XMLReader	parser = be.re.xml.sax.Util.getParser(catalog, validate);
+      XMLFilter	parent = new ProtectEventHandlerFilter(true, true, parser);
+
+      if (preprocessors != null)
+      {
+        parent = Util.createPreprocessorFilter(preprocessors, parent);
+      }
+
+      XMLFilter	filter =
+        new CSSToXSLFOFilter
+        (
+          baseUrl,
+          userAgentStyleSheet,
+          parameters,
+          parent,
+          System.getProperty("be.re.css.debug") != null
+        );
+
+      InputSource	source = new InputSource(in);
+
+      if (baseUrl != null)
+      {
+        source.setSystemId(baseUrl.toString());
+      }
+
+      Driver	driver = new Driver();
+
+      MessageHandler.setScreenLogger
+      (
+        quiet ?
+          (Logger) new NullLogger() :
+          (Logger) new ConsoleLogger(ConsoleLogger.LEVEL_INFO)
+      );
+
+      driver.setLogger
+      (
+        quiet ?
+          (Logger) new NullLogger() :
+          (Logger) new ConsoleLogger(ConsoleLogger.LEVEL_INFO)
+      );
+
+      driver.setRenderer(format);
+
+      if (out != null)
+      {
+        driver.setOutputStream(out);
+      }
+
+      TransformerHandler        handler =
+        be.re.xml.sax.Util.newSAXTransformerFactory().newTransformerHandler
+        (
+          new StreamSource
+          (
+            CSSToFOP.class.getResourceAsStream("style/fop_filter.xsl")
+          )
+        );
+
+      handler.setResult(new SAXResult(driver.getContentHandler()));
+      filter.setContentHandler(handler);
+      filter.parse(source);
+    }
+
+    catch (Exception e)
+    {
+      throw new CSSToXSLFOException(e);
+    }
+  }
+
+
+
+  public static void
+  main(String[] args) throws Exception
+  {
+    URL		baseUrl = null;
+    URL		catalog = null;
+    File	configFile = null;
+    int		format = -1;
+    String	output = null;
+    Map		parameters = new HashMap();
+    URL[]	preprocessors = null;
+    boolean	quiet = false;
+    URL		url = null;
+    URL		userAgentStyleSheet = null;
+    boolean	validate = false;
+
+    for (int i = 0; i < args.length; ++i)
+    {
+      if (args[i].equals("-h"))
+      {
+        usage(0);
+      }
+
+      if (args[i].equals("-baseurl"))
+      {
+        if (i == args.length - 1)
+        {
+          usage(1);
+        }
+
+        baseUrl = Util.createUrl(args[++i]);
+      }
+      else
+      {
+        if (args[i].equals("-uacss"))
+        {
+          if (i == args.length - 1)
+          {
+            usage(1);
+          }
+
+          userAgentStyleSheet = Util.createUrl(args[++i]);
+        }
+        else
+        {
+          if (args[i].equals("-q"))
+          {
+            quiet = true;
+          }
+          else
+          {
+            if (args[i].equals("-pdf"))
+            {
+              if (i == args.length - 1 || format != -1)
+              {
+                usage(1);
+              }
+  
+              format = Driver.RENDER_PDF;
+              output = args[++i];
+            }
+            else
+            {
+              if (args[i].equals("-ps"))
+              {
+                if (i == args.length - 1 || format != -1)
+                {
+                  usage(1);
+                }
+  
+                format = Driver.RENDER_PS;
+                output = args[++i];
+              }
+              else
+              {
+                if (args[i].equals("-svg"))
+                {
+                  if (i == args.length - 1 || format != -1)
+                  {
+                    usage(1);
+                  }
+  
+                  format = Driver.RENDER_SVG;
+                  output = args[++i];
+                }
+                else
+                {
+                  if (args[i].equals("-c"))
+                  {
+                    if (i == args.length - 1)
+                    {
+                      usage(1);
+                    }
+    
+                    catalog = Util.createUrl(args[++i]);
+                  }
+                  else
+                  {
+                    if (args[i].equals("-p"))
+                    {
+                      if (i == args.length - 1)
+                      {
+                        usage(1);
+                      }
+    
+                      preprocessors = Util.createUrls(args[++i]);
+                    }
+                    else
+                    {
+                      if (args[i].equals("-v"))
+                      {
+                        validate = true;
+                      }
+                      else
+                      {
+                        if (args[i].equals("-fc"))
+                        {
+                          if (i == args.length - 1)
+                          {
+                            usage(1);
+                          }
+          
+                          configFile = new File(args[++i]);
+                        }
+                        else
+                        {
+                          if (args[i].indexOf('=') != -1)
+                          {
+                            parameters.put
+                            (
+                              args[i].substring(0, args[i].indexOf('=')),
+                              args[i].indexOf('=') == args[i].length() - 1 ?
+                                "" : args[i].substring(args[i].indexOf('=') + 1)
+                            );
+                          }
+                          else
+                          {
+                            if (url != null)
+                            {
+                              usage(1);
+                            }
+    
+                            url = Util.createUrl(args[i]);
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (format == -1)
+    {
+      usage(1);
+    }
+
+    if (configFile != null)
+    {
+      new Options(configFile);
+    }
+
+    parameters.put
+    (
+      "base-url",
+      baseUrl != null ?
+        baseUrl.toString() : (url != null ? url.toString() : "")
+    );
+
+    try
+    {
+      convert
+      (
+        url != null ? url.openStream() : System.in,
+        output != null ? new FileOutputStream(output) : null,
+        baseUrl != null ? baseUrl : url,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToFOP.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        format,
+        quiet,
+        validate
+      );
+    }
+
+    catch (Throwable e)
+    {
+      System.err.println(e.getMessage());
+      be.re.util.Util.printStackTrace(e);
+    }
+  }
+
+
+
+  private static void
+  usage(int code)
+  {
+    System.err.println("Usage: be.re.css.CSSToFOP");
+    System.err.println("  [-h]: show this help");
+    System.err.println("  [-baseurl url]: base URL ");
+    System.err.println("  [-c url_or_filename]: catalog for entity resolution");
+    System.err.println("  [-config url_or_filename]: extra configuration");
+    System.err.println("  [-p url_or_filename_comma_list]: preprocessors");
+    System.err.println("  [-q]: quiet mode");
+    System.err.println("  [-uacss url_or_filename]: User Agent style sheet");
+    System.err.println("  [-v]: turn on validation");
+    System.err.
+      println("  [url_or_filename]: the input document, uses stdin by default");
+    System.err.println("  [parameter=value ...] ");
+    System.err.
+      println("  (-pdf filename | -ps filename | -svg filename): output file");
+    System.err.println();
+    Util.printUserAgentParameters(System.err);
+    System.exit(code);
+  }
+
+} // CSSToFOP
diff --git a/src/be/re/css/CSSToFOPNew.java b/src/be/re/css/CSSToFOPNew.java
new file mode 100644
index 0000000..4f35ae4
--- /dev/null
+++ b/src/be/re/css/CSSToFOPNew.java
@@ -0,0 +1,403 @@
+package be.re.css;
+
+import be.re.xml.sax.ProtectEventHandlerFilter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.cli.CommandLineOptions;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+
+
+/**
+ * Convenience class for the conversion from CSS to the new FOP XSL-FO
+ * formatter.
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToFOPNew
+
+{
+
+  public static void
+  convert(URL in, OutputStream out, URL userAgentStyleSheet, String format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in.openStream(),
+      out,
+      in,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      format,
+      null,
+      false,
+      null
+    );
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out, String format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert(in, out, null, format);
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			userAgentStyleSheet,
+    String		format
+  ) throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in,
+      out,
+      null,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      format,
+      null,
+      false,
+      null
+    );
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			baseUrl,
+    URL			userAgentStyleSheet,
+    URL			catalog,
+    Map			parameters,
+    URL[]		preprocessors,
+    String		format,
+    File		configFile,
+    boolean		validate
+  ) throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in,
+      out,
+      baseUrl,
+      userAgentStyleSheet,
+      catalog,
+      parameters,
+      preprocessors,
+      format,
+      configFile,
+      validate,
+      null
+    );
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			baseUrl,
+    URL			userAgentStyleSheet,
+    URL			catalog,
+    Map			parameters,
+    URL[]		preprocessors,
+    String		format,
+    File		configFile,
+    boolean		validate,
+    FOUserAgent		agent
+  ) throws IOException, CSSToXSLFOException
+  {
+    try
+    {
+      XMLReader	parser = be.re.xml.sax.Util.getParser(catalog, validate);
+      XMLFilter	parent = new ProtectEventHandlerFilter(true, true, parser);
+
+      if (preprocessors != null)
+      {
+        parent = Util.createPreprocessorFilter(preprocessors, parent);
+      }
+
+      XMLFilter	filter =
+        new CSSToXSLFOFilter
+        (
+          baseUrl,
+          userAgentStyleSheet,
+          parameters,
+          parent,
+          System.getProperty("be.re.css.debug") != null
+        );
+
+      InputSource	source = new InputSource(in);
+
+      if (baseUrl != null)
+      {
+        source.setSystemId(baseUrl.toString());
+      }
+
+      FopFactory	factory = FopFactory.newInstance();
+
+      if (configFile != null)
+      {
+        factory.setUserConfig
+        (
+          new DefaultConfigurationBuilder().buildFromFile(configFile)
+        );
+      }
+
+      if (agent == null)
+      {
+        agent = factory.newFOUserAgent();
+      }
+
+      filter.setContentHandler
+      (
+        factory.newFop(format, agent, out).getDefaultHandler()
+      );
+
+      filter.parse(source);
+    }
+
+    catch (Exception e)
+    {
+      throw new CSSToXSLFOException(e);
+    }
+  }
+
+
+
+  public static void
+  main(String[] args) throws Exception
+  {
+    URL		baseUrl = null;
+    URL		catalog = null;
+    String[]	fopOptions = null;
+    Map		parameters = new HashMap();
+    URL[]	preprocessors = null;
+    URL		url = null;
+    URL		userAgentStyleSheet = null;
+    boolean	validate = false;
+
+    for (int i = 0; i < args.length; ++i)
+    {
+      if (args[i].equals("-h"))
+      {
+        usage(0);
+      }
+
+      if (args[i].equals("-fop"))
+      {
+        fopOptions = new String[args.length - ++i];
+
+        for (int j = 0; i < args.length; ++i, ++j)
+        {
+          fopOptions[j] = args[i];
+        }
+      }
+      else
+      {
+        if (args[i].equals("-baseurl"))
+        {
+          if (i == args.length - 1)
+          {
+            usage(1);
+          }
+
+          baseUrl = Util.createUrl(args[++i]);
+        }
+        else
+        {
+          if (args[i].equals("-uacss"))
+          {
+            if (i == args.length - 1)
+            {
+              usage(1);
+            }
+
+            userAgentStyleSheet = Util.createUrl(args[++i]);
+          }
+          else
+          {
+            if (args[i].equals("-c"))
+            {
+              if (i == args.length - 1)
+              {
+                usage(1);
+              }
+  
+              catalog = Util.createUrl(args[++i]);
+            }
+            else
+            {
+              if (args[i].equals("-p"))
+              {
+                if (i == args.length - 1)
+                {
+                  usage(1);
+                }
+  
+                preprocessors = Util.createUrls(args[++i]);
+              }
+              else
+              {
+                if (args[i].equals("-v"))
+                {
+                  validate = true;
+                }
+                else
+                {
+                  if (args[i].indexOf('=') != -1)
+                  {
+                    parameters.put
+                    (
+                      args[i].substring(0, args[i].indexOf('=')),
+                      args[i].indexOf('=') == args[i].length() - 1 ?
+                        "" : args[i].substring(args[i].indexOf('=') + 1)
+                    );
+                  }
+                  else
+                  {
+                    if (url != null)
+                    {
+                      usage(1);
+                    }
+
+                    url = Util.createUrl(args[i]);
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    parameters.put
+    (
+      "base-url",
+      baseUrl != null ?
+        baseUrl.toString() : (url != null ? url.toString() : "")
+    );
+
+    FOUserAgent	agent = null;
+
+    FopCommandLine	options = new FopCommandLine();
+
+    options.
+      parse(setDummyInputFile(fopOptions != null ? fopOptions : new String[0]));
+    agent = options.getFOUserAgent();
+
+    try
+    {
+      convert
+      (
+        url != null ? url.openStream() : System.in,
+        new FileOutputStream(options.getOutputFile()),
+        baseUrl != null ? baseUrl : url,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToFOPNew.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        options.getOutputFormat(),
+        null,
+        validate,
+        agent
+      );
+    }
+
+    catch (Throwable e)
+    {
+      System.err.println(e.getMessage());
+      be.re.util.Util.printStackTrace(e);
+    }
+  }
+
+
+
+  private static String[]
+  setDummyInputFile(String[] args) throws Exception
+  {
+    String[]	result = new String[args.length + 2];
+
+    System.arraycopy(args, 0, result, 0, args.length);
+    result[result.length - 2] = "-fo";
+    result[result.length - 1] =
+      be.re.io.Util.createTempFile("css2fopnew.", null).getAbsolutePath();
+
+    return result;
+  }
+
+
+
+  private static void
+  usage(int code)
+  {
+    System.err.println("Usage: be.re.css.CSSToFOPNew");
+    System.err.println("  [-h]: show this help");
+    System.err.println("  [-baseurl url]: base URL");
+    System.err.println("  [-c url_or_filename]: catalog for entity resolution");
+    System.err.println("  [-p url_or_filename_comma_list]: preprocessors");
+    System.err.println("  [-uacss url_or_filename]: User Agent style sheet");
+    System.err.println("  [-v]: turn on validation");
+    System.err.
+      println("  [url_or_filename]: the input document, uses stdin by default");
+    System.err.println("  [parameter=value ...] ");
+    System.err.
+      println("  -fop options: the rest of the command-line is for FOP");
+    System.err.println();
+    Util.printUserAgentParameters(System.err);
+    System.exit(code);
+  }
+
+
+
+  private static class FopCommandLine extends CommandLineOptions
+
+  {
+
+    public FOUserAgent
+    getFOUserAgent()
+    {
+      return super.getFOUserAgent();
+    }
+
+
+
+    public String
+    getOutputFormat() throws FOPException
+    {
+      return super.getOutputFormat();
+    }
+
+  } // FopCommandLine
+
+} // CSSToFOPNew
diff --git a/src/be/re/css/CSSToXEP.java b/src/be/re/css/CSSToXEP.java
new file mode 100644
index 0000000..2b11433
--- /dev/null
+++ b/src/be/re/css/CSSToXEP.java
@@ -0,0 +1,415 @@
+package be.re.css;
+
+import be.re.xml.sax.ProtectEventHandlerFilter;
+import com.renderx.xep.FOTarget;
+import com.renderx.xep.FormatterImpl;
+import com.renderx.xep.lib.DefaultLogger;
+import com.renderx.xep.lib.Logger;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stream.StreamSource;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+
+
+/**
+ * Convenience class for the conversion from CSS to the XEP XSL-FO formatter.
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToXEP
+
+{
+
+  public static final int	PDF = 0;
+  public static final int	POSTSCRIPT = 1;
+
+
+
+  public static void
+  convert(URL in, OutputStream out, URL userAgentStyleSheet, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in.openStream(),
+      out,
+      in,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      format,
+      false
+    );
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert(in, out, null, format);
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out, URL userAgentStyleSheet, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in,
+      out,
+      null,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      format,
+      false
+    );
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			baseUrl,
+    URL			userAgentStyleSheet,
+    URL			catalog,
+    Map			parameters,
+    URL[]		preprocessors,
+    int			format,
+    boolean		validate
+  ) throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in,
+      out,
+      baseUrl,
+      userAgentStyleSheet,
+      catalog,
+      parameters,
+      preprocessors,
+      format,
+      validate,
+      false,
+      null
+    );
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			baseUrl,
+    URL			userAgentStyleSheet,
+    URL			catalog,
+    Map			parameters,
+    URL[]		preprocessors,
+    int			format,
+    boolean		validate,
+    boolean		quiet,
+    URL			configuration
+  ) throws IOException, CSSToXSLFOException
+  {
+    if (format != PDF && format != POSTSCRIPT)
+    {
+      throw
+        new IllegalArgumentException
+        (
+          "be.re.css.CSSToXEP.convert takes only be.re.css.CSSToXEP.PDF " +
+            "or be.re.css.CSSToXEP.POSTSCRIPT as format values."
+        );
+    }
+
+    try
+    {
+      XMLReader	parser = be.re.xml.sax.Util.getParser(catalog, validate);
+      XMLFilter	parent = new ProtectEventHandlerFilter(true, true, parser);
+
+      if (preprocessors != null)
+      {
+        parent = Util.createPreprocessorFilter(preprocessors, parent);
+      }
+
+      XMLFilter	filter =
+        new CSSToXSLFOFilter
+        (
+          baseUrl,
+          userAgentStyleSheet,
+          parameters,
+          parent,
+          System.getProperty("be.re.css.debug") != null
+        );
+
+      InputSource	source = new InputSource(in);
+
+      if (baseUrl != null)
+      {
+        source.setSystemId(baseUrl.toString());
+      }
+
+      if (configuration != null)
+      {
+        new FormatterImpl
+        (
+          new StreamSource(configuration.toString())
+        ).render
+        (
+          new SAXSource(filter, source),
+          new FOTarget(out, format == PDF ? "PDF" : "PostScript"),
+          quiet ? Logger.NULL_LOGGER : new DefaultLogger()
+        );
+      }
+      else
+      {
+        new FormatterImpl().render
+        (
+          new SAXSource(filter, source),
+          new FOTarget(out, format == PDF ? "PDF" : "PostScript"),
+          quiet ? Logger.NULL_LOGGER : new DefaultLogger()
+        );
+      }
+    }
+
+    catch (Exception e)
+    {
+      throw new CSSToXSLFOException(e);
+    }
+  }
+
+
+
+  public static void
+  main(String[] args) throws Exception
+  {
+    URL		baseUrl = null;
+    URL		catalog = null;
+    URL		configuration = null;
+    String	pdf = null;
+    Map		parameters = new HashMap();
+    String	postScript = null;
+    URL[]	preprocessors = null;
+    boolean	quiet = false;
+    URL		url = null;
+    URL		userAgentStyleSheet = null;
+    boolean	validate = false;
+
+    for (int i = 0; i < args.length; ++i)
+    {
+      if (args[i].equals("-h"))
+      {
+        usage(0);
+      }
+
+      if (args[i].equals("-baseurl"))
+      {
+        if (i == args.length - 1)
+        {
+          usage(1);
+        }
+
+        baseUrl = Util.createUrl(args[++i]);
+      }
+      else
+      {
+        if (args[i].equals("-uacss"))
+        {
+          if (i == args.length - 1)
+          {
+            usage(1);
+          }
+
+          userAgentStyleSheet = Util.createUrl(args[++i]);
+        }
+        else
+        {
+          if (args[i].equals("-pdf"))
+          {
+            if (i == args.length - 1)
+            {
+              usage(1);
+            }
+
+            pdf = args[++i];
+          }
+          else
+          {
+            if (args[i].equals("-ps"))
+            {
+              if (i == args.length - 1)
+              {
+                usage(1);
+              }
+
+              postScript = args[++i];
+            }
+            else
+            {
+              if (args[i].equals("-c"))
+              {
+                if (i == args.length - 1)
+                {
+                  usage(1);
+                }
+
+                catalog = Util.createUrl(args[++i]);
+              }
+              else
+              {
+                if (args[i].equals("-p"))
+                {
+                  if (i == args.length - 1)
+                  {
+                    usage(1);
+                  }
+
+                  preprocessors = Util.createUrls(args[++i]);
+                }
+                else
+                {
+                  if (args[i].equals("-v"))
+                  {
+                    validate = true;
+                  }
+                  else
+                  {
+                    if (args[i].equals("-config"))
+                    {
+                      if (i == args.length - 1)
+                      {
+                        usage(1);
+                      }
+
+                      configuration = Util.createUrl(args[++i]);
+                    }
+                    else
+                    {
+                      if (args[i].equals("-q"))
+                      {
+                        quiet = true;
+                      }
+                      else
+                      {
+                        if (args[i].indexOf('=') != -1)
+                        {
+                          parameters.put
+                          (
+                            args[i].substring(0, args[i].indexOf('=')),
+                            args[i].indexOf('=') == args[i].length() - 1 ?
+                              "" : args[i].substring(args[i].indexOf('=') + 1)
+                          );
+                        }
+                        else
+                        {
+                          if (url != null)
+                          {
+                            usage(1);
+                          }
+
+                          url = Util.createUrl(args[i]);
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if
+    (
+      (
+        pdf == null		&&
+        postScript == null
+      )				||
+      (
+        pdf != null		&&
+        postScript != null
+      )
+    )
+    {
+      usage(1);
+    }
+
+    parameters.put
+    (
+      "base-url",
+      baseUrl != null ?
+        baseUrl.toString() : (url != null ? url.toString() : "")
+    );
+
+    try
+    {
+      convert
+      (
+        url != null ? url.openStream() : System.in,
+        new FileOutputStream(pdf != null ? pdf : postScript),
+        baseUrl != null ? baseUrl : url,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToXEP.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        pdf != null ? PDF : POSTSCRIPT,
+        validate,
+        quiet,
+        configuration
+      );
+    }
+
+    catch (Throwable e)
+    {
+      System.err.println(e.getMessage());
+      be.re.util.Util.printStackTrace(e);
+    }
+  }
+
+
+
+  private static void
+  usage(int code)
+  {
+    System.err.println("Usage: be.re.css.CSSToXEP");
+    System.err.println("  [-h]: show this help");
+    System.err.println("  [-baseurl url]: base URL ");
+    System.err.println("  [-c url_or_filename]: catalog for entity resolution");
+    System.err.println("  [-config url_or_filename]: extra configuration");
+    System.err.println("  [-p url_or_filename_comma_list]: preprocessors");
+    System.err.println("  [-q]: quiet mode");
+    System.err.println("  [-uacss url_or_filename]: User Agent style sheet");
+    System.err.println("  [-v]: turn on validation");
+    System.err.
+      println("  [url_or_filename]: the input document, uses stdin by default");
+    System.err.println("  [parameter=value ...] ");
+    System.err.println("  (-pdf filename | -ps filename): output file");
+    System.err.println();
+    Util.printUserAgentParameters(System.err);
+    System.exit(code);
+  }
+
+} // CSSToXEP
diff --git a/src/be/re/css/CSSToXSLFO.java b/src/be/re/css/CSSToXSLFO.java
new file mode 100644
index 0000000..6a14d1a
--- /dev/null
+++ b/src/be/re/css/CSSToXSLFO.java
@@ -0,0 +1,307 @@
+package be.re.css;
+
+import be.re.xml.sax.ProtectEventHandlerFilter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+
+
+/**
+ * Convenience class for the conversion from CSS to XSL-FO.
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToXSLFO
+
+{
+
+  /**
+   * Takes in an XML document and produces an XSL-FO document.
+   */
+
+  public static void
+  convert(URL in, OutputStream out, URL userAgentStyleSheet)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in.openStream(),
+      out,
+      in,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      false,
+      false
+    );
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out)
+    throws IOException, CSSToXSLFOException
+  {
+    convert(in, out, null);
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out, URL userAgentStyleSheet)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in,
+      out,
+      null,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      false,
+      false
+    );
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			baseUrl,
+    URL			userAgentStyleSheet,
+    URL			catalog,
+    Map			userAgentParameters,
+    URL[]		preprocessors,
+    boolean		validate,
+    boolean		debug
+  ) throws IOException, CSSToXSLFOException
+  {
+    try
+    {
+      XMLReader	parser = be.re.xml.sax.Util.getParser(catalog, validate);
+      XMLFilter	parent = new ProtectEventHandlerFilter(true, true, parser);
+
+      if (preprocessors != null)
+      {
+        parent = Util.createPreprocessorFilter(preprocessors, parent);
+      }
+
+      XMLFilter	filter =
+        new CSSToXSLFOFilter
+        (
+          baseUrl,
+          userAgentStyleSheet,
+          userAgentParameters,
+          parent,
+          debug
+        );
+
+      InputSource	source = new InputSource(in);
+
+      if (baseUrl != null)
+      {
+        source.setSystemId(baseUrl.toString());
+      }
+
+      TransformerHandler	handler =
+        be.re.xml.sax.Util.newSAXTransformerFactory().newTransformerHandler();
+
+      handler.setResult(new StreamResult(out));
+      filter.setContentHandler(handler);
+      filter.parse(source);
+    }
+
+    catch (Exception e)
+    {
+      throw new CSSToXSLFOException(e);
+    }
+  }
+
+
+
+  public static void
+  main(String[] args) throws Exception
+  {
+    URL		baseUrl = null;
+    URL		catalog = null;
+    boolean	debug = false;
+    String	filename = null;
+    Map		parameters = new HashMap();
+    URL[]	preprocessors = null;
+    URL		url = null;
+    URL		userAgentStyleSheet = null;
+    boolean	validate = false;
+
+    for (int i = 0; i < args.length; ++i)
+    {
+      if (args[i].equals("-h"))
+      {
+        usage(0);
+      }
+
+      if (args[i].equals("-baseurl"))
+      {
+        if (i == args.length - 1)
+        {
+          usage(1);
+        }
+
+        baseUrl = Util.createUrl(args[++i]);
+      }
+      else
+      {
+        if (args[i].equals("-fo"))
+        {
+          if (i == args.length - 1)
+          {
+            usage(1);
+          }
+
+          filename = args[++i];
+        }
+        else
+        {
+          if (args[i].equals("-uacss"))
+          {
+            if (i == args.length - 1)
+            {
+              usage(1);
+            }
+
+            userAgentStyleSheet = Util.createUrl(args[++i]);
+          }
+          else
+          {
+            if (args[i].equals("-debug"))
+            {
+              debug = true;
+            }
+            else
+            {
+              if (args[i].equals("-c"))
+              {
+                if (i == args.length - 1)
+                {
+                  usage(1);
+                }
+
+                catalog = Util.createUrl(args[++i]);
+              }
+              else
+              {
+                if (args[i].equals("-p"))
+                {
+                  if (i == args.length - 1)
+                  {
+                    usage(1);
+                  }
+
+                  preprocessors = Util.createUrls(args[++i]);
+                }
+                else
+                {
+                  if (args[i].equals("-v"))
+                  {
+                    validate = true;
+                  }
+                  else
+                  {
+                    if (args[i].indexOf('=') != -1)
+                    {
+                      parameters.put
+                      (
+                        args[i].substring(0, args[i].indexOf('=')),
+                        args[i].indexOf('=') == args[i].length() - 1 ?
+                          "" : args[i].substring(args[i].indexOf('=') + 1)
+                      );
+                    }
+                    else
+                    {
+                      if (url != null)
+                      {
+                        usage(1);
+                      }
+
+                      url = Util.createUrl(args[i]);
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    parameters.put
+    (
+      "base-url", 
+      baseUrl != null ? 
+        baseUrl.toString() : (url != null ? url.toString() : "")
+    );
+
+    try
+    {
+      convert
+      (
+        url != null ? url.openStream() : System.in,
+        filename != null ?
+          (OutputStream) new FileOutputStream(filename) :
+          (OutputStream) System.out,
+        baseUrl != null ? baseUrl : url,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToXSLFO.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        validate,
+        debug
+      );
+    }
+
+    catch (Throwable e)
+    {
+      System.err.println(e.getMessage());
+      be.re.util.Util.printStackTrace(e);
+    }
+  }
+
+
+
+  private static void
+  usage(int code)
+  {
+    System.err.println("Usage: be.re.css.CSSToXSLFO");
+    System.err.println("  [-h]: show this help");
+    System.err.println("  [-baseurl url]: base URL ");
+    System.err.println("  [-c url_or_filename]: catalog for entity resolution");
+    System.err.println("  [-config url_or_filename]: extra configuration");
+    System.err.println("  [-debug]: debug mode");
+    System.err.println("  [-fo filename]: output file, uses stdout by default");
+    System.err.println("  [-p url_or_filename_comma_list]: preprocessors");
+    System.err.println("  [-uacss url_or_filename]: User Agent style sheet");
+    System.err.println("  [-v]: turn on validation");
+    System.err.
+      println("  [url_or_filename]: the input document, use stdin by default");
+    System.err.println("  [parameter=value ...] ");
+    System.err.println();
+    Util.printUserAgentParameters(System.err);
+    System.exit(code);
+  }
+
+} // CSSToXSLFO
diff --git a/src/be/re/css/CSSToXSLFOException.java b/src/be/re/css/CSSToXSLFOException.java
new file mode 100644
index 0000000..5e713cb
--- /dev/null
+++ b/src/be/re/css/CSSToXSLFOException.java
@@ -0,0 +1,82 @@
+package be.re.css;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+
+
+/**
+ * General exception for the conversion from CSS to XSL-FO.
+ * @author Werner Donne\u00e9
+ */
+
+public class CSSToXSLFOException extends Exception
+
+{
+
+  private Exception	exception;
+  private String	message;
+
+
+
+  public
+  CSSToXSLFOException(Exception e)
+  {
+    this.exception = e;
+  }
+
+
+
+  public
+  CSSToXSLFOException(String message)
+  {
+    this.message = message;
+  }
+
+
+
+  public Exception
+  getException()
+  {
+    return exception;
+  }
+
+
+
+  public String
+  getMessage()
+  {
+    return
+      message != null ?
+        message : (exception != null ? exception.getMessage() : null);
+  }
+
+
+
+  public void
+  printStackTrace(PrintStream s)
+  {
+    super.printStackTrace(s);
+
+    if (exception != null)
+    {
+      s.println("Caused by:");
+      exception.printStackTrace(s);
+    }
+  }
+
+
+
+  public void
+  printStackTrace(PrintWriter s)
+  {
+    super.printStackTrace(s);
+
+    if (exception != null)
+    {
+      s.println("Caused by:");
+      exception.printStackTrace(s);
+    }
+  }
+
+} // CSSToXSLFOException
diff --git a/src/be/re/css/CSSToXSLFOFilter.java b/src/be/re/css/CSSToXSLFOFilter.java
new file mode 100644
index 0000000..d46cdfa
--- /dev/null
+++ b/src/be/re/css/CSSToXSLFOFilter.java
@@ -0,0 +1,405 @@
+package be.re.css;
+
+import be.re.xml.sax.FilterOfFilters;
+import be.re.xml.sax.TransformerHandlerFilter;
+import java.io.IOException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.stream.StreamSource;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * A filter that accepts an XML document and produces an XSL-FO document.
+ * @author Werner Donne\u00e9
+ */
+
+public class CSSToXSLFOFilter extends XMLFilterImpl
+
+{
+
+  private static SAXTransformerFactory	factory;
+  private XMLFilterImpl			filter;
+  private PageSetupFilter		pageSetupFilter;
+  private Util.PostProjectionFilter	postProjectionFilter;
+  private ProjectorFilter		projectorFilter;
+  private static Templates		templates = loadStyleSheet();
+  private Map				userAgentParameters;
+
+
+
+  public
+  CSSToXSLFOFilter() throws CSSToXSLFOException
+  {
+    this(null, null, new HashMap(), false);
+  }
+
+
+
+  public
+  CSSToXSLFOFilter(URL baseUrl) throws CSSToXSLFOException
+  {
+    this(baseUrl, null, new HashMap(), false);
+  }
+
+
+
+  public
+  CSSToXSLFOFilter(URL baseUrl, URL userAgentStyleSheet)
+    throws CSSToXSLFOException
+  {
+    this(baseUrl, userAgentStyleSheet, new HashMap(), false);
+  }
+
+
+
+  public
+  CSSToXSLFOFilter
+  (
+    URL	baseUrl,
+    URL	userAgentStyleSheet,
+    Map	userAgentParameters
+  ) throws CSSToXSLFOException
+  {
+    this(baseUrl, userAgentStyleSheet, userAgentParameters, false);
+  }
+
+
+
+  public
+  CSSToXSLFOFilter
+  (
+    URL		baseUrl,
+    URL		userAgentStyleSheet,
+    Map		userAgentParameters,
+    boolean	debug
+  ) throws CSSToXSLFOException
+  {
+    this.userAgentParameters =
+      userAgentParameters != null ? userAgentParameters : new HashMap();
+
+    try
+    {
+      Context	context = new Context();
+
+      projectorFilter =
+        new ProjectorFilter
+        (
+          baseUrl,
+          userAgentStyleSheet,
+          userAgentParameters,
+          context
+        );
+
+      postProjectionFilter =
+        Util.createPostProjectionFilter(baseUrl, userAgentParameters, debug);
+      pageSetupFilter =
+        new PageSetupFilter(context, baseUrl, userAgentParameters, debug);
+
+      filter =
+        new FilterOfFilters
+        (
+          new XMLFilter[]
+          {
+            projectorFilter,
+            new FOMarkerFilter(),
+            postProjectionFilter.getFilter(),
+            pageSetupFilter,
+            new TransformerHandlerFilter
+            (
+              be.re.xml.sax.Util.
+                newTemplatesHandler(templates, userAgentParameters, factory)
+            ),
+            new SpaceCorrectionFilter()
+          },
+          debug
+        );
+
+      super.setContentHandler(filter);
+      super.setDTDHandler(filter);
+      super.setEntityResolver(filter);
+      super.setErrorHandler(filter);
+    }
+
+    catch (Exception e)
+    {
+      throw new CSSToXSLFOException(e);
+    }
+  }
+
+
+
+  public
+  CSSToXSLFOFilter(XMLReader parent) throws CSSToXSLFOException
+  {
+    this(null, null, new HashMap(), parent, false);
+  }
+
+
+
+  public
+  CSSToXSLFOFilter(URL baseUrl, XMLReader parent) throws CSSToXSLFOException
+  {
+    this(baseUrl, null, new HashMap(), parent, false);
+  }
+
+
+
+  public
+  CSSToXSLFOFilter(URL baseUrl, URL userAgentStyleSheet, XMLReader parent)
+    throws CSSToXSLFOException
+  {
+    this(baseUrl, userAgentStyleSheet, new HashMap(), parent, false);
+  }
+
+
+
+  public
+  CSSToXSLFOFilter
+  (
+    URL		baseUrl,
+    URL		userAgentStyleSheet,
+    Map		userAgentParameters,
+    XMLReader	parent
+  ) throws CSSToXSLFOException
+  {
+    this(baseUrl, userAgentStyleSheet, userAgentParameters, parent, false);
+  }
+
+
+
+  public
+  CSSToXSLFOFilter
+  (
+    URL		baseUrl,
+    URL		userAgentStyleSheet,
+    Map		userAgentParameters,
+    XMLReader	parent,
+    boolean	debug
+  ) throws CSSToXSLFOException
+  {
+    this(baseUrl, userAgentStyleSheet, userAgentParameters, debug);
+    setParent(parent);
+  }
+
+
+
+  public URL
+  getBaseUrl()
+  {
+    return projectorFilter.getBaseUrl();
+  }
+
+
+
+  public ContentHandler
+  getContentHandler()
+  {
+    return filter.getContentHandler();
+  }
+
+
+
+  public DTDHandler
+  getDTDHandler()
+  {
+    return filter.getDTDHandler();
+  }
+
+
+
+  public EntityResolver
+  getEntityResolver()
+  {
+    return filter.getEntityResolver();
+  }
+
+
+
+  public ErrorHandler
+  getErrorHandler()
+  {
+    return filter.getErrorHandler();
+  }
+
+
+
+  public Map
+  getParameters()
+  {
+    return userAgentParameters;
+  }
+
+
+
+  public URL
+  getUserAgentStyleSheet()
+  {
+    return projectorFilter.getUserAgentStyleSheet();
+  }
+
+
+
+  private static Templates
+  loadStyleSheet()
+  {
+    try
+    {
+      factory = be.re.xml.sax.Util.newSAXTransformerFactory();
+
+      factory.setURIResolver
+      (
+        new URIResolver()
+        {
+          public Source
+          resolve(String href, String base)
+          {
+            try
+            {
+              return
+                new StreamSource
+                (
+                  base != null && be.re.net.Util.isUrl(base) ?
+                    new URL(new URL(base), href).toString() :
+                    new URL
+                    (
+                      CSSToXSLFOFilter.class.getResource("style/css.xsl"), href
+                    ).toString()
+                );
+            }
+
+            catch (Exception e)
+            {
+              return null;
+            }
+          }
+        }
+      );
+
+      return
+        factory.newTemplates
+        (
+          new StreamSource
+          (
+            CSSToXSLFOFilter.class.getResource("style/css.xsl").toString()
+          )
+        );
+    }
+
+    catch (Exception e)
+    {
+      throw new RuntimeException(e);
+    }
+  }
+
+
+
+  public void
+  parse(InputSource input) throws IOException, SAXException
+  {
+    if (getBaseUrl() == null && input.getSystemId() != null)
+    {
+      setBaseUrl(new URL(input.getSystemId()));
+    }
+
+    filter.parse(input);
+  }
+
+
+
+  public void
+  parse(String systemId) throws IOException, SAXException
+  {
+    if (getBaseUrl() == null && systemId != null)
+    {
+      setBaseUrl(new URL(systemId));
+    }
+
+    filter.parse(systemId);
+  }
+
+
+
+  public void
+  setBaseUrl(URL baseUrl)
+  {
+    projectorFilter.setBaseUrl(baseUrl);
+    pageSetupFilter.setBaseUrl(baseUrl);
+    postProjectionFilter.setBaseUrl(baseUrl);
+  }
+
+
+
+  public void
+  setContentHandler(ContentHandler handler)
+  {
+    filter.setContentHandler(handler);
+  }
+
+
+
+  public void
+  setDTDHandler(DTDHandler handler)
+  {
+    filter.setDTDHandler(handler);
+  }
+
+
+
+  public void
+  setEntityResolver(EntityResolver resolver)
+  {
+    filter.setEntityResolver(resolver);
+  }
+
+
+
+  public void
+  setErrorHandler(ErrorHandler handler)
+  {
+    filter.setErrorHandler(handler);
+  }
+
+
+
+  public void
+  setParameters(Map userAgentParameters)
+  {
+    this.userAgentParameters = userAgentParameters;
+  }
+
+
+
+  public void
+  setParent(XMLReader parent)
+  {
+    super.setParent(parent);
+      // Some XMLFilterImpl functions seem to use parent directly instead of
+      // getParent.
+    filter.setParent(parent);
+    parent.setContentHandler(filter);
+  }
+
+
+
+  public void
+  setUserAgentStyleSheet(URL userAgentStyleSheet)
+  {
+    projectorFilter.setUserAgentStyleSheet(userAgentStyleSheet);
+  }
+
+} // CSSToXSLFOFilter
diff --git a/src/be/re/css/CSSToXSLFormatter.java b/src/be/re/css/CSSToXSLFormatter.java
new file mode 100644
index 0000000..0fd022e
--- /dev/null
+++ b/src/be/re/css/CSSToXSLFormatter.java
@@ -0,0 +1,216 @@
+package be.re.css;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import jp.co.antenna.XfoJavaCtl.XfoObj;
+
+
+
+/**
+ * Convenience class for the conversion from CSS to the XSLFormatter XSL-FO
+ * formatter.
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToXSLFormatter
+
+{
+
+  public static void
+  main(String[] args) throws Exception
+  {
+    URL		baseUrl = null;
+    URL		catalog = null;
+    URL		configuration = null;
+    String	pdf = null;
+    Map		parameters = new HashMap();
+    URL[]	preprocessors = null;
+    URL		url = null;
+    URL		userAgentStyleSheet = null;
+    boolean	validate = false;
+
+    for (int i = 0; i < args.length; ++i)
+    {
+      if (args[i].equals("-h"))
+      {
+        usage(0);
+      }
+
+      if (args[i].equals("-baseurl"))
+      {
+        if (i == args.length - 1)
+        {
+          usage(1);
+        }
+
+        baseUrl = Util.createUrl(args[++i]);
+      }
+      else
+      {
+        if (args[i].equals("-uacss"))
+        {
+          if (i == args.length - 1)
+          {
+            usage(1);
+          }
+
+          userAgentStyleSheet = Util.createUrl(args[++i]);
+        }
+        else
+        {
+          if (args[i].equals("-pdf"))
+          {
+            if (i == args.length - 1)
+            {
+              usage(1);
+            }
+
+            pdf = args[++i];
+          }
+          else
+          {
+            if (args[i].equals("-c"))
+            {
+              if (i == args.length - 1)
+              {
+                usage(1);
+              }
+
+              catalog = Util.createUrl(args[++i]);
+            }
+            else
+            {
+              if (args[i].equals("-p"))
+              {
+                if (i == args.length - 1)
+                {
+                  usage(1);
+                }
+
+                preprocessors = Util.createUrls(args[++i]);
+              }
+              else
+              {
+                if (args[i].equals("-v"))
+                {
+                  validate = true;
+                }
+                else
+                {
+                  if (args[i].equals("-config"))
+                  {
+                    if (i == args.length - 1)
+                    {
+                      usage(1);
+                    }
+
+                    configuration = Util.createUrl(args[++i]);
+                  }
+                  else
+                  {
+                    if (args[i].indexOf('=') != -1)
+                    {
+                      parameters.put
+                      (
+                        args[i].substring(0, args[i].indexOf('=')),
+                        args[i].indexOf('=') == args[i].length() - 1 ?
+                          "" : args[i].substring(args[i].indexOf('=') + 1)
+                      );
+                    }
+                    else
+                    {
+                      if (url != null)
+                      {
+                        usage(1);
+                      }
+
+                      url = Util.createUrl(args[i]);
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (pdf == null)
+    {
+      usage(1);
+    }
+
+    parameters.put
+    (
+      "base-url",
+      baseUrl != null ?
+        baseUrl.toString() : (url != null ? url.toString() : "")
+    );
+
+    File	f = File.createTempFile("be.re.css.", "css2xslformatter");
+
+    f.deleteOnExit();
+
+    try
+    {
+      CSSToXSLFO.convert
+      (
+        url != null ? url.openStream() : System.in,
+        new FileOutputStream(f),
+        baseUrl != null ? baseUrl : url,
+        userAgentStyleSheet,
+        catalog != null ?
+          catalog : CSSToXSLFormatter.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        validate,
+        System.getProperty("be.re.css.debug") != null
+      );
+    }
+
+    catch (Throwable e)
+    {
+      System.err.println(e.getMessage());
+      be.re.util.Util.printStackTrace(e);
+    }
+
+    XfoObj	o = new XfoObj();
+
+    if (configuration != null)
+    {
+      o.addOptionFileURI(configuration.toString());
+    }
+
+    o.render(new FileInputStream(f), new FileOutputStream(pdf));
+  }
+
+
+
+  private static void
+  usage(int code)
+  {
+    System.err.println("Usage: be.re.css.CSSToXSLFormatter");
+    System.err.println("  [-h]: show this help");
+    System.err.println("  [-baseurl url]: base URL ");
+    System.err.println("  [-c url_or_filename]: catalog for entity resolution");
+    System.err.println("  [-config url_or_filename]: extra configuration");
+    System.err.println("  [-p url_or_filename_comma_list]: preprocessors");
+    System.err.println("  [-uacss url_or_filename]: User Agent style sheet");
+    System.err.println("  [-v]: turn on validation");
+    System.err.
+      println("  [url_or_filename]: the input document, uses stdin by default");
+    System.err.println("  [parameter=value ...] ");
+    System.err.println("  -pdf filename: output file");
+    System.err.println();
+    Util.printUserAgentParameters(System.err);
+    System.exit(code);
+  }
+
+} // CSSToXSLFormatter
diff --git a/src/be/re/css/CSSToXinc.java b/src/be/re/css/CSSToXinc.java
new file mode 100644
index 0000000..f108aba
--- /dev/null
+++ b/src/be/re/css/CSSToXinc.java
@@ -0,0 +1,380 @@
+package be.re.css;
+
+import be.re.xml.sax.ProtectEventHandlerFilter;
+import com.lunasil.xf.XincEngine;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+
+
+/**
+ * Convenience class for the conversion from CSS to the Xinc XSL-FO formatter.
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToXinc
+
+{
+
+  public static final int	PDF = 0;
+
+
+
+  public static void
+  convert(URL in, OutputStream out, URL userAgentStyleSheet, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in.openStream(),
+      out,
+      in,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      format,
+      false
+    );
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert(in, out, null, format);
+  }
+
+
+
+  public static void
+  convert(InputStream in, OutputStream out, URL userAgentStyleSheet, int format)
+    throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in,
+      out,
+      null,
+      userAgentStyleSheet,
+      null,
+      new HashMap(),
+      null,
+      format,
+      false
+    );
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			baseUrl,
+    URL			userAgentStyleSheet,
+    URL			catalog,
+    Map			parameters,
+    URL[]		preprocessors,
+    int			format,
+    boolean		validate
+  ) throws IOException, CSSToXSLFOException
+  {
+    convert
+    (
+      in,
+      out,
+      baseUrl,
+      userAgentStyleSheet,
+      catalog,
+      parameters,
+      preprocessors,
+      format,
+      validate,
+      null
+    );
+  }
+
+
+
+  public static void
+  convert
+  (
+    InputStream		in,
+    OutputStream	out,
+    URL			baseUrl,
+    URL			userAgentStyleSheet,
+    URL			catalog,
+    Map			parameters,
+    URL[]		preprocessors,
+    int			format,
+    boolean		validate,
+    URL			configuration
+  ) throws IOException, CSSToXSLFOException
+  {
+    if (format != PDF)
+    {
+      throw
+        new IllegalArgumentException
+        (
+          "be.re.css.CSSToXinc.convert takes only be.re.css.CSSToXinc.PDF " +
+            "as a format value."
+        );
+    }
+
+    try
+    {
+      XMLReader	parser = be.re.xml.sax.Util.getParser(catalog, validate);
+      XMLFilter	parent = new ProtectEventHandlerFilter(true, true, parser);
+
+      if (preprocessors != null)
+      {
+        parent = Util.createPreprocessorFilter(preprocessors, parent);
+      }
+
+      XMLFilter	filter =
+        new CSSToXSLFOFilter
+        (
+          baseUrl,
+          userAgentStyleSheet,
+          parameters,
+          parent,
+          System.getProperty("be.re.css.debug") != null
+        );
+
+      InputSource	source = new InputSource(in);
+
+      if (baseUrl != null)
+      {
+        source.setSystemId(baseUrl.toString());
+      }
+
+      XincEngine	engine = new XincEngine();
+
+      if (configuration != null)
+      {
+        try
+        {
+          engine.setConfiguration
+          (
+            be.re.xml.Util.newDocumentBuilderFactory(false).
+              newDocumentBuilder().parse(configuration.toString())
+          );
+        }
+
+        catch (IOException e)
+        {
+          throw e;
+        }
+
+        catch (Exception e)
+        {
+          throw new be.re.io.IOException(e);
+        }
+      }
+
+      engine.setOutputStream(out);
+      filter.setContentHandler(engine.createContentHandler());
+      filter.parse(source);
+    }
+
+    catch (Exception e)
+    {
+      throw new CSSToXSLFOException(e);
+    }
+  }
+
+
+
+  public static void
+  main(String[] args) throws Exception
+  {
+    URL		baseUrl = null;
+    URL		catalog = null;
+    URL		configuration = null;
+    String	pdf = null;
+    Map		parameters = new HashMap();
+    URL[]	preprocessors = null;
+    URL		url = null;
+    URL		userAgentStyleSheet = null;
+    boolean	validate = false;
+
+    for (int i = 0; i < args.length; ++i)
+    {
+      if (args[i].equals("-h"))
+      {
+        usage(0);
+      }
+
+      if (args[i].equals("-baseurl"))
+      {
+        if (i == args.length - 1)
+        {
+          usage(1);
+        }
+
+        baseUrl = Util.createUrl(args[++i]);
+      }
+      else
+      {
+        if (args[i].equals("-uacss"))
+        {
+          if (i == args.length - 1)
+          {
+            usage(1);
+          }
+
+          userAgentStyleSheet = Util.createUrl(args[++i]);
+        }
+        else
+        {
+          if (args[i].equals("-pdf"))
+          {
+            if (i == args.length - 1)
+            {
+              usage(1);
+            }
+
+            pdf = args[++i];
+          }
+          else
+          {
+            if (args[i].equals("-c"))
+            {
+              if (i == args.length - 1)
+              {
+                usage(1);
+              }
+
+              catalog = Util.createUrl(args[++i]);
+            }
+            else
+            {
+              if (args[i].equals("-p"))
+              {
+                if (i == args.length - 1)
+                {
+                  usage(1);
+                }
+
+                preprocessors = Util.createUrls(args[++i]);
+              }
+              else
+              {
+                if (args[i].equals("-v"))
+                {
+                  validate = true;
+                }
+                else
+                {
+                  if (args[i].equals("-config"))
+                  {
+                    if (i == args.length - 1)
+                    {
+                      usage(1);
+                    }
+
+                    configuration = Util.createUrl(args[++i]);
+                  }
+                  else
+                  {
+                    if (args[i].indexOf('=') != -1)
+                    {
+                      parameters.put
+                      (
+                        args[i].substring(0, args[i].indexOf('=')),
+                        args[i].indexOf('=') == args[i].length() - 1 ?
+                          "" : args[i].substring(args[i].indexOf('=') + 1)
+                      );
+                    }
+                    else
+                    {
+                      if (url != null)
+                      {
+                        usage(1);
+                      }
+
+                      url = Util.createUrl(args[i]);
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (pdf == null)
+    {
+      usage(1);
+    }
+
+    parameters.put
+    (
+      "base-url",
+      baseUrl != null ?
+        baseUrl.toString() : (url != null ? url.toString() : "")
+    );
+
+    try
+    {
+      convert
+      (
+        url != null ? url.openStream() : System.in,
+        new FileOutputStream(pdf),
+        baseUrl != null ? baseUrl : url,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToXinc.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        PDF,
+        validate,
+        configuration
+      );
+    }
+
+    catch (Throwable e)
+    {
+      System.err.println(e.getMessage());
+      be.re.util.Util.printStackTrace(e);
+    }
+  }
+
+
+
+  private static void
+  usage(int code)
+  {
+    System.err.println("Usage: be.re.css.CSSToXinc");
+    System.err.println("  [-h]: show this help");
+    System.err.println("  [-baseurl url]: base URL ");
+    System.err.println("  [-c url_or_filename]: catalog for entity resolution");
+    System.err.println("  [-config url_or_filename]: extra configuration");
+    System.err.println("  [-p url_or_filename_comma_list]: preprocessors");
+    System.err.println("  [-uacss url_or_filename]: User Agent style sheet");
+    System.err.println("  [-v]: turn on validation");
+    System.err.
+      println("  [url_or_filename]: the input document, use stdin by default");
+    System.err.println("  [parameter=value ...] ");
+    System.err.println("  -pdf filename: output file");
+    System.err.println();
+    Util.printUserAgentParameters(System.err);
+    System.exit(code);
+  }
+
+} // CSSToXinc
diff --git a/src/be/re/css/CenterFilter.java b/src/be/re/css/CenterFilter.java
new file mode 100644
index 0000000..d053978
--- /dev/null
+++ b/src/be/re/css/CenterFilter.java
@@ -0,0 +1,214 @@
+package be.re.css;
+
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Detects centering (right and left margins set to "auto") and wraps tables
+ * and blocks in a three column table.
+ * @author Werner Donn\u00e9
+ */
+
+class CenterFilter extends XMLFilterImpl
+
+{
+
+  private Stack	stack = new Stack();
+
+
+
+  CenterFilter()
+  {
+  }
+
+
+
+  CenterFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private void
+  column(String width) throws SAXException
+  {
+    AttributesImpl	atts = displayType("table-column");
+
+    if (width != null)
+    {
+      atts.addAttribute(Constants.CSS, "width", "css:width", "CDATA", width);
+    }
+
+    super.startElement(Constants.CSS, "table-column", "css:table-column", atts);
+    super.endElement(Constants.CSS, "table-column", "css:table-column");
+  }
+
+
+
+  private static AttributesImpl
+  displayType(String type)
+  {
+    AttributesImpl	atts = new AttributesImpl();
+
+    atts.addAttribute
+    (
+      Constants.CSS,
+      "display",
+      "css:display",
+      "CDATA",
+      type
+    );
+
+    return atts;
+  }
+
+
+
+  private void
+  emptyCell() throws SAXException
+  {
+    super.startElement
+    (
+      Constants.CSS,
+      "table-cell",
+      "css:table-cell",
+      displayType("table-cell")
+    );
+
+    super.endElement(Constants.CSS, "table-cell", "css:table-cell");
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    super.endElement(namespaceURI, localName, qName);
+
+    if (((Boolean) stack.pop()).booleanValue())
+    {
+      super.endElement(Constants.CSS, "table-cell", "css:table-cell");
+      emptyCell();
+      super.endElement(Constants.CSS, "table-row", "css:table-row");
+      super.endElement(Constants.CSS, "table-row-group", "css:table-row-group");
+      super.endElement(Constants.CSS, "table", "css:table");
+    }
+  }
+
+
+
+  private void
+  generateTable(Attributes atts) throws SAXException
+  {
+    AttributesImpl	tableAtts = displayType("table");
+
+    Util.copyAttribute(atts, tableAtts, Constants.CSS, "margin-bottom");
+    Util.copyAttribute(atts, tableAtts, Constants.CSS, "margin-top");
+
+    tableAtts.addAttribute
+    (
+      Constants.CSS,
+      "table-layout",
+      "css:table-layout",
+      "CDATA",
+      "fixed"
+    );
+
+    super.startElement(Constants.CSS, "table", "css:table", tableAtts);
+    column("1*");
+    column(atts.getValue(Constants.CSS, "width"));
+    column("1*");
+
+    super.startElement
+    (
+      Constants.CSS,
+      "table-row-group",
+      "css:table-row-group",
+      displayType("table-row-group")
+    );
+
+    super.startElement
+    (
+      Constants.CSS,
+      "table-row",
+      "css:table-row",
+      displayType("table-row")
+    );
+
+    emptyCell();
+
+    super.startElement
+    (
+      Constants.CSS,
+      "table-cell",
+      "css:table-cell",
+      displayType("table-cell")
+    );
+  }
+
+
+
+  private static boolean
+  shouldCenter(Attributes atts)
+  {
+    return
+      "auto".equals(atts.getValue(Constants.CSS, "margin-left")) &&
+        "auto".equals(atts.getValue(Constants.CSS, "margin-right"));
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    String	display = atts.getValue(Constants.CSS, "display");
+    boolean	extra = false;
+
+    if
+    (
+      (
+        "block".equals(display)	||
+        "table".equals(display)
+      )				&&
+      shouldCenter(atts)
+    )
+    {
+      extra = true;
+      generateTable(atts);
+      atts = new AttributesImpl(atts);
+      Util.
+        removeAttribute((AttributesImpl) atts, Constants.CSS, "margin-bottom");
+      Util.removeAttribute((AttributesImpl) atts, Constants.CSS, "margin-top");
+      Util.removeAttribute((AttributesImpl) atts, Constants.CSS, "margin-left");
+      Util.
+        removeAttribute((AttributesImpl) atts, Constants.CSS, "margin-right");
+
+      Util.setAttribute
+      (
+        (AttributesImpl) atts,
+        Constants.CSS,
+        "width",
+        "css:width",
+        "100%"
+      );
+    }
+
+    stack.push(new Boolean(extra));
+    super.startElement(namespaceURI, localName, qName, atts);
+  }
+
+} // CenterFilter
diff --git a/src/be/re/css/Compiled.java b/src/be/re/css/Compiled.java
new file mode 100644
index 0000000..cf1cc80
--- /dev/null
+++ b/src/be/re/css/Compiled.java
@@ -0,0 +1,621 @@
+package be.re.css;
+
+import be.re.util.DigitalTree;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.ElementSelector;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SiblingSelector;
+
+
+
+/**
+ * Represents a CSS style sheet in compiled form.
+ * @author Werner Donn\u00e9
+ */
+
+public class Compiled
+
+{
+
+  static final String		ANY_ELEMENT = "*|*".intern();
+  private static final int	END_STATE = 1;
+  private static final String	EPSILON = "EPSILON".intern();
+  private static final int	ERROR_STATE = -1;
+  static final String		SIBLING = "SIBLING".intern();
+  private static final int	START_STATE = 0;
+
+  private int			dfaStateCounter = 0;
+  private int			nfaStateCounter = 0;
+  private NFAState[]		nfa =
+    new NFAState[]{new NFAState(), new NFAState()};
+  DFAState			startState = null;
+  private static final boolean	trace =
+    System.getProperty("be.re.css.trace") != null;
+
+
+
+  /**
+   * Adds the rule to the NFA being built using the Thompson construction.
+   * The rule should be split, i.e. it should have exactly one property.
+   */
+
+  void
+  addRule(Rule rule)
+  {
+    NFAState[]	states = constructNFA(rule.getSelector());
+
+    if (rule.getPseudoElementName() == null)
+    {
+      states[END_STATE].rules.add(rule);
+    }
+    else
+    {
+      states[END_STATE].pseudoRules.add(rule);
+    }
+
+    nfa[START_STATE].next.add(new Next(EPSILON, states[START_STATE]));
+    states[END_STATE].next.add(new Next(EPSILON, nfa[END_STATE]));
+  }
+
+
+
+  private static Map
+  collectNextSets(SortedSet set)
+  {
+    Map	result = new HashMap();
+
+    for (Iterator i = set.iterator(); i.hasNext();)
+    {
+      for (Iterator j = ((NFAState) i.next()).next.iterator(); j.hasNext();)
+      {
+        Next	next = (Next) j.next();
+
+        if (next.event != EPSILON)
+        {
+          SortedSet	nextSet = (SortedSet) result.get(next.event);
+
+          if (nextSet == null)
+          {
+            nextSet = new TreeSet(set.comparator());
+            result.put(next.event, nextSet);
+          }
+
+          nextSet.add(next.state);
+        }
+      }
+    }
+
+    for (Iterator i = result.values().iterator(); i.hasNext();)
+    {
+      TreeSet	nextSet = (TreeSet) i.next();
+      SortedSet	copy = (SortedSet) nextSet.clone();
+
+      for (Iterator j = copy.iterator(); j.hasNext();)
+      {
+        epsilonMove(nextSet, (NFAState) j.next());
+      }
+    }
+
+    return result;
+  }
+
+
+
+  private static NFAState[]
+  constructAnd(NFAState[] first, NFAState[] second)
+  {
+    first[END_STATE].next.add(new Next(EPSILON, second[START_STATE]));
+
+    return new NFAState[]{first[START_STATE], second[END_STATE]};
+  }
+
+
+
+  private NFAState[]
+  constructChild(DescendantSelector selector)
+  {
+    return
+      selector.getSimpleSelector().getSelectorType() ==
+        Selector.SAC_PSEUDO_ELEMENT_SELECTOR ?
+        constructNFA(selector.getAncestorSelector()) :
+        constructAnd
+        (
+          constructNFA(selector.getAncestorSelector()),
+          constructNFA(selector.getSimpleSelector())
+        );
+  }
+
+
+
+  private NFAState[]
+  constructConditional(ConditionalSelector selector)
+  {
+    NFAState[]	first = constructNFA(selector.getSimpleSelector());
+    NFAState	end = new NFAState();
+
+    first[END_STATE].condition = selector.getCondition();
+    first[END_STATE].next.add(new Next(selector.getCondition(), end));
+
+    return new NFAState[]{first[START_STATE], end};
+  }
+
+
+
+  private NFAState[]
+  constructDescendant(DescendantSelector selector)
+  {
+    return
+      constructAnd
+      (
+        constructAnd
+        (
+          constructNFA(selector.getAncestorSelector()),
+          constructKleeneClosure(ANY_ELEMENT)
+        ),
+        constructNFA(selector.getSimpleSelector())
+      );
+  }
+
+
+
+  private NFAState[]
+  constructElement(ElementSelector selector)
+  {
+    NFAState	start = new NFAState();
+    NFAState	end = new NFAState();
+
+    start.next.add
+    (
+      new Next
+      (
+        (
+          (
+            selector.getNamespaceURI() != null ?
+              selector.getNamespaceURI() : "*"
+          ) + "|" +
+            (
+              selector.getLocalName() != null ?
+                selector.getLocalName().intern() : "*"
+            )
+        ).intern(),
+        end
+      )
+    );
+
+    return new NFAState[]{start, end};
+  }
+
+
+
+  private NFAState[]
+  constructKleeneClosure(Object event)
+  {
+    NFAState	start = new NFAState();
+    NFAState	end = new NFAState();
+    NFAState	from = new NFAState();
+    NFAState	to = new NFAState();
+
+    from.next.add(new Next(event, to));
+    start.next.add(new Next(EPSILON, from));
+    start.next.add(new Next(EPSILON, end));
+    to.next.add(new Next(EPSILON, end));
+    end.next.add(new Next(EPSILON, from));
+
+    return new NFAState[]{start, end};
+  }
+
+
+
+  /**
+   * Applies the Thompson construction.
+   */
+
+  private NFAState[]
+  constructNFA(Selector selector)
+  {
+    switch (selector.getSelectorType())
+    {
+      case Selector.SAC_CONDITIONAL_SELECTOR:
+        return constructConditional((ConditionalSelector) selector);
+
+      case Selector.SAC_CHILD_SELECTOR:
+        return constructChild((DescendantSelector) selector);
+
+      case Selector.SAC_DESCENDANT_SELECTOR:
+        return constructDescendant((DescendantSelector) selector);
+
+      case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
+        return constructSibling((SiblingSelector) selector);
+
+      case Selector.SAC_ELEMENT_NODE_SELECTOR:
+        return constructElement((ElementSelector) selector);
+
+      default:
+        return null; // Ignore non-CSS2 selector types.
+    }
+  }
+
+
+
+  private NFAState[]
+  constructSibling(SiblingSelector selector)
+  {
+    return
+      constructAnd
+      (
+        constructAnd
+        (
+          constructNFA(selector.getSelector()),
+          constructSiblingTransition()
+        ),
+        constructNFA(selector.getSiblingSelector())
+      );
+  }
+
+
+
+  private NFAState[]
+  constructSiblingTransition()
+  {
+    NFAState	start = new NFAState();
+    NFAState	end = new NFAState();
+
+    start.next.add(new Next(SIBLING, end));
+
+    return new NFAState[]{start, end};
+  }
+
+
+
+  void
+  dumpDFA(PrintWriter out)
+  {
+    if (trace)
+    {
+      out.println();
+      out.println("DFA START");
+      out.println();
+      dumpDFA(startState, new HashSet(), out);
+      out.println("DFA END");
+      out.println();
+      out.flush();
+    }
+  }
+
+
+
+  private static void
+  dumpDFA(DFAState state, Set seen, PrintWriter out)
+  {
+    if (seen.contains(new Integer(state.state)))
+    {
+      return;
+    }
+
+    out.println(String.valueOf(state.state) + ":");
+
+    List	values = new ArrayList();
+
+    for (Iterator i = state.events.keySet().iterator(); i.hasNext();)
+    {
+      String	event = (String) i.next();
+      DFAState	nextState = (DFAState) state.events.get(event);
+
+      out.println("  " + event + " -> " + String.valueOf(nextState.state));
+      values.add(nextState);
+    }
+
+    for
+    (
+      Iterator i = state.candidateConditions.keySet().iterator();
+      i.hasNext();
+    )
+    {
+      Condition	event = (Condition) i.next();
+      DFAState	nextState = (DFAState) state.candidateConditions.get(event);
+
+      out.println
+      (
+        "  " + Util.conditionText(event) + " -> " +
+          String.valueOf(nextState.state)
+      );
+
+      values.add(nextState);
+    }
+
+    dumpRules(state.rules, out);
+    dumpRules(state.pseudoRules, out);
+    out.println();
+    seen.add(new Integer(state.state));
+
+    for (Iterator i = values.iterator(); i.hasNext();)
+    {
+      dumpDFA((DFAState) i.next(), seen, out);
+    }
+  }
+
+
+
+  void
+  dumpNFA(PrintWriter out)
+  {
+    if (trace)
+    {
+      out.println();
+      out.println("NFA START");
+      out.println();
+      dumpNFA(nfa[START_STATE], new HashSet(), out);
+      out.println("NFA END");
+      out.println();
+      out.flush();
+    }
+  }
+
+
+
+  private static void
+  dumpNFA(NFAState state, Set seen, PrintWriter out)
+  {
+    if (seen.contains(new Integer(state.state)))
+    {
+      return;
+    }
+
+    out.println(String.valueOf(state.state) + ":");
+
+    for (Iterator i = state.next.iterator(); i.hasNext();)
+    {
+      Next	next = (Next) i.next();
+
+      out.println
+      (
+        "  " +
+          (
+            next.event instanceof Condition ?
+              Util.conditionText((Condition) next.event) : next.event.toString()
+          ) + " -> " + String.valueOf(next.state.state)
+      );
+    }
+
+    dumpRules(state.rules, out);
+    dumpRules(state.pseudoRules, out);
+    out.println();
+    seen.add(new Integer(state.state));
+
+    for (Iterator i = state.next.iterator(); i.hasNext();)
+    {
+      dumpNFA(((Next) i.next()).state, seen, out);
+    }
+  }
+
+
+
+  private static void
+  dumpRules(List rules, PrintWriter out)
+  {
+    for (Iterator i = rules.iterator(); i.hasNext();)
+    {
+      Rule	rule = (Rule) i.next();
+
+      out.println
+      (
+        "  " + (rule.getElementName() != null ? rule.getElementName() : "") +
+          (
+            rule.getPseudoElementName() != null ?
+              rule.getPseudoElementName() : ""
+          ) + ": " + rule.getProperty().getName() + ": " +
+          rule.getProperty().getValue()
+      );
+    }
+  }
+
+
+
+  private static void
+  epsilonMove(Set set, NFAState state)
+  {
+    for (Iterator i = state.next.iterator(); i.hasNext();)
+    {
+      Next	next = (Next) i.next();
+
+      if (next.event == EPSILON && set.add(next.state))
+      {
+        epsilonMove(set, next.state);
+      }
+    }
+  }
+
+
+
+  void
+  generateDFA()
+  {
+    dumpNFA(new PrintWriter(System.out));
+    startState = generateDFA(nfa);
+    dumpDFA(new PrintWriter(System.out));
+  }
+
+
+
+  /**
+   * Applies the subset construction. Returns the start state.
+   */
+
+  private DFAState
+  generateDFA(NFAState[] nfa)
+  {
+    SortedSet	set =
+      new TreeSet // Sorting of the NFA states makes the labels unique.
+      (
+        new Comparator()
+        {
+          public int
+          compare(Object o1, Object o2)
+          {
+            return ((NFAState) o1).state - ((NFAState) o2).state;
+          }
+        }
+      );
+    Map		states = new HashMap();
+
+    set.add(nfa[START_STATE]);
+    epsilonMove(set, nfa[START_STATE]);
+
+    DFAState	result = new DFAState();
+
+    states.put(label(set), result);
+    generateTransitions(result, set, states);
+
+    return result;
+  }
+
+
+
+  private void
+  generateTransitions(DFAState from, SortedSet set, Map states)
+  {
+    Map		nextSets = collectNextSets(set);
+
+    for (Iterator i = nextSets.keySet().iterator(); i.hasNext();)
+    {
+      Object	event = i.next();
+      SortedSet	nextSet = (SortedSet) nextSets.get(event);
+
+      if (nextSet.size() > 0)
+      {
+        DFAState	nextState;
+        String		s = label(nextSet);
+        DFAState	state = (DFAState) states.get(s);
+
+        if (state == null)
+        {
+          nextState = new DFAState();
+          states.put(s, nextState);
+        }
+        else
+        {
+          nextState = state;
+        }
+
+        if (event instanceof Condition)
+        {
+          from.candidateConditions.put(event, nextState);
+        }
+        else
+        {
+          from.events.put((String) event, nextState);
+        }
+
+        for (Iterator j = nextSet.iterator(); j.hasNext();)
+        {
+          NFAState	next = (NFAState) j.next();
+
+          nextState.rules.addAll(next.rules);
+          nextState.pseudoRules.addAll(next.pseudoRules);
+        }
+
+        if (state == null)
+        {
+          generateTransitions(nextState, nextSet, states);
+        }
+      }
+    }
+  }
+
+
+
+  private static String
+  label(SortedSet set)
+  {
+    String	result = "";
+
+    for (Iterator i = set.iterator(); i.hasNext();)
+    {
+      result += "#" + String.valueOf(((NFAState) i.next()).state);
+    }
+
+    return result;
+  }
+
+
+
+  /**
+   * Contains all matching rules sorted from least to most specific.
+   */
+
+  class DFAState
+
+  {
+
+    Map		candidateConditions = new HashMap();
+    DigitalTree	events = new DigitalTree(trace);
+    List	pseudoRules = new ArrayList();
+    List	rules = new ArrayList();
+    int		state;
+
+
+
+    private
+    DFAState()
+    {
+      state = dfaStateCounter++;
+    }
+
+  } // DFAState
+
+
+
+  private static class Next
+
+  {
+
+    private
+    Next(Object event, NFAState state)
+    {
+      this.event = event;
+      this.state = state;
+    }
+
+
+
+    private Object	event;
+    private NFAState	state;
+
+  } // Next
+
+
+
+  private class NFAState
+
+  {
+
+    private Condition	condition;
+    private List	next = new ArrayList();
+    private List	pseudoRules = new ArrayList();
+    private List	rules = new ArrayList();
+    private int		state;
+
+
+
+    private
+    NFAState()
+    {
+      state = nfaStateCounter++;
+    }
+
+  } // NFAState
+
+} // Compiled
diff --git a/src/be/re/css/Constants.java b/src/be/re/css/Constants.java
new file mode 100644
index 0000000..002723e
--- /dev/null
+++ b/src/be/re/css/Constants.java
@@ -0,0 +1,15 @@
+package be.re.css;
+
+public interface Constants
+
+{
+
+  public static final String	CSS = "http://www.w3.org/1998/CSS".intern();
+  public static final String	SPECIF = "urn:be-re-css:specificity".intern();
+  public static final String	XHTML = "http://www.w3.org/1999/xhtml".intern();
+  public static final String	XML =
+    "http://www.w3.org/XML/1998/namespace".intern();
+  public static final String	XSLFO =
+    "http://www.w3.org/1999/XSL/Format".intern();
+
+} // Constants
diff --git a/src/be/re/css/Context.java b/src/be/re/css/Context.java
new file mode 100644
index 0000000..5699a28
--- /dev/null
+++ b/src/be/re/css/Context.java
@@ -0,0 +1,17 @@
+package be.re.css;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+
+class Context
+
+{
+
+  List	pageRules = new ArrayList();
+  Map	regions = new HashMap();
+
+} // Context
diff --git a/src/be/re/css/DisplayNonePropagator.java b/src/be/re/css/DisplayNonePropagator.java
new file mode 100644
index 0000000..a4ea1eb
--- /dev/null
+++ b/src/be/re/css/DisplayNonePropagator.java
@@ -0,0 +1,106 @@
+package be.re.css;
+
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Propagates a "none" display type down the tree, which makes it unnecessary
+ * for subsequent filters to analyse the ancestor chain. Regions can't have
+ * their display set to "none", so in that case the display property it set to
+ * "block".
+ * @author Werner Donn\u00e9
+ */
+
+class DisplayNonePropagator extends XMLFilterImpl
+
+{
+
+  private Stack	stack = new Stack();
+
+
+
+  DisplayNonePropagator()
+  {
+  }
+
+
+
+  DisplayNonePropagator(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    super.endElement(namespaceURI, localName, qName);
+    stack.pop();
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    boolean	displayNone =
+      (stack.isEmpty() ? false : ((Boolean) stack.peek()).booleanValue()) ||
+        (
+          "none".equals(atts.getValue(Constants.CSS, "display")) &&
+            atts.getValue(Constants.CSS, "region") == null
+        );
+
+    stack.push(new Boolean(displayNone));
+
+    if (displayNone)
+    {
+      atts = new AttributesImpl(atts);
+
+      Util.setAttribute
+      (
+        (AttributesImpl) atts,
+        Constants.CSS,
+        "display",
+        "css:display",
+        "none"
+      );
+    }
+    else
+    {
+      if
+      (
+        atts.getValue(Constants.CSS, "region") != null		&&
+        "none".equals(atts.getValue(Constants.CSS, "display"))
+      )
+      {
+        atts = new AttributesImpl(atts);
+
+        Util.setAttribute
+        (
+          (AttributesImpl) atts,
+          Constants.CSS,
+          "display",
+          "css:display",
+          "block"
+        );
+      }
+    }
+
+    super.startElement(namespaceURI, localName, qName, atts);
+  }
+
+} // DisplayNonePropagator
diff --git a/src/be/re/css/Element.java b/src/be/re/css/Element.java
new file mode 100644
index 0000000..3eb7c6b
--- /dev/null
+++ b/src/be/re/css/Element.java
@@ -0,0 +1,79 @@
+package be.re.css;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+
+
+class Element
+
+{
+
+  static final String	BLOCK = "block".intern();
+  static final String	COMPACT = "compact".intern();
+  static final String	GRAPHIC = "graphic".intern();
+  static final String	INLINE = "inline".intern();
+  static final String	INLINE_TABLE = "inline-table".intern();
+  static final String	LEADER = "leader".intern();
+  static final String	LIST_ITEM = "list-item".intern();
+  static final String	MARKER = "marker".intern();
+  static final String	NONE = "none".intern();
+  static final String	RUN_IN = "run-in".intern();
+  static final String	TABLE = "table".intern();
+  static final String	TABLE_CELL = "table-cell".intern();
+  static final String	TABLE_CAPTION = "table-caption".intern();
+  static final String	TABLE_COLUMN = "table-column".intern();
+  static final String	TABLE_COLUMN_GROUP = "table-column-group".intern();
+  static final String	TABLE_FOOTER_GROUP = "table-footer-group".intern();
+  static final String	TABLE_HEADER_GROUP = "table-header-group".intern();
+  static final String	TABLE_ROW = "table-row".intern();
+  static final String	TABLE_ROW_GROUP = "table-row-group".intern();
+
+  Attributes	atts;
+  List		children;
+  String	display; // Interned.
+  Object	extra;
+  String	localName;
+  String	namespaceURI;
+  String	qName;
+
+
+
+  Element(String namespaceURI, String localName, String qName, Attributes atts)
+  {
+    this.namespaceURI = namespaceURI;
+    this.localName = localName;
+    this.qName = qName;
+    this.atts = new AttributesImpl(atts); // Copy because parser reuses them.
+    this.display = atts.getValue(Constants.CSS, "display");
+
+    if (display != null)
+    {
+      display = display.intern();
+    }
+  }
+
+
+
+  void
+  addChild(Element child)
+  {
+    if (children == null)
+    {
+      children = new ArrayList(10);
+    }
+
+    children.add(child);
+  }
+
+
+
+  boolean
+  isDisplay(String knownDisplay)
+  {
+    return knownDisplay == display;
+  }
+
+} // Element
diff --git a/src/be/re/css/FOMarkerFilter.java b/src/be/re/css/FOMarkerFilter.java
new file mode 100644
index 0000000..65b3c10
--- /dev/null
+++ b/src/be/re/css/FOMarkerFilter.java
@@ -0,0 +1,164 @@
+package be.re.css;
+
+import be.re.xml.Accumulator;
+import be.re.xml.DOMToContentHandler;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import org.w3c.dom.Element;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Moves FO-markers to the next allowed place.
+ * @author Werner Donn\u00e9
+ */
+
+class FOMarkerFilter extends XMLFilterImpl
+
+{
+
+  private static final Set		allowedPlaces =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "block", "inline", "list-item", "table", "table-cell",
+            "table-footer-group", "table-header-group", "table-row-group"
+        }
+      )
+    );
+
+  private List	foMarkers = new ArrayList();
+  private Stack	stack = new Stack();
+
+
+
+  FOMarkerFilter()
+  {
+  }
+
+
+
+  FOMarkerFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private void
+  accumulateFOMarker
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    Accumulator.preAccumulate
+    (
+      namespaceURI,
+      localName,
+      qName,
+      atts,
+      this,
+      new Accumulator.ProcessElement()
+      {
+        public void
+        process(Element element, XMLFilter filter) throws SAXException
+        {
+          foMarkers.add(element);
+        }
+      }
+    );
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    stack.pop();
+    super.endElement(namespaceURI, localName, qName);
+  }
+
+
+
+  private void
+  flushFOMarkers() throws SAXException
+  {
+    if (foMarkers.size() > 0)
+    {
+      for (Iterator i = foMarkers.iterator(); i.hasNext();)
+      {
+        Element	element = (Element) i.next();
+
+        DOMToContentHandler.
+          elementToContentHandler(element, getContentHandler());
+      }
+
+      foMarkers.clear();
+    }
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    String	parentDisplay = stack.isEmpty() ? null : (String) stack.peek();
+
+    if
+    (
+      parentDisplay != null			&&
+      Constants.CSS == namespaceURI		&&
+      "fo-marker".equals(localName)		&&
+      !allowedPlaces.contains(parentDisplay)
+    )
+    {
+      accumulateFOMarker(namespaceURI, localName, qName, atts);
+    }
+    else
+    {
+      super.startElement(namespaceURI, localName, qName, atts);
+
+      String	display =
+        parentDisplay != null && parentDisplay.equals("none") ?
+          "none" : atts.getValue(Constants.CSS, "display");
+
+      if
+      (
+        (
+          Constants.CSS != namespaceURI		||
+          !"fo-marker".equals(localName)
+        )					&&
+        allowedPlaces.contains(display)
+      )
+      {
+        flushFOMarkers();
+      }
+
+      stack.push(display);
+    }
+  }
+
+} // FOMarkerFilter
diff --git a/src/be/re/css/FirstLetterFilter.java b/src/be/re/css/FirstLetterFilter.java
new file mode 100644
index 0000000..9dbd13b
--- /dev/null
+++ b/src/be/re/css/FirstLetterFilter.java
@@ -0,0 +1,376 @@
+package be.re.css;
+
+import be.re.xml.Accumulator;
+import be.re.xml.DOMToContentHandler;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+class FirstLetterFilter extends XMLFilterImpl
+
+{
+
+  FirstLetterFilter()
+  {
+  }
+
+
+
+  FirstLetterFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private static Element
+  getFirstLetter(Node node)
+  {
+    return
+      node == null ?
+        null :
+        (
+          Constants.CSS.equals(node.getNamespaceURI()) &&
+            "first-letter".equals(node.getLocalName()) ?
+            (Element) node :
+            getFirstLetter(node.getNextSibling())
+        );
+  }
+
+
+
+  private static Text
+  getFirstTextNode(Node node)
+  {
+    return
+      node == null ?
+        null :
+        (
+          node instanceof Text && ((Text) node).getLength() > 0 ?
+            (Text) node :
+            (
+              node instanceof Element ?
+                (
+                  "inline".equals
+                  (
+                    ((Element) node).getAttributeNS(Constants.CSS, "display")
+                  ) ? getFirstTextNode(node.getFirstChild()) : null
+                ) :
+                (
+                  node.getNextSibling() != null ?
+                    getFirstTextNode(node.getNextSibling()) :
+                    getFirstTextNode(node.getParentNode().getNextSibling())
+                )
+            )
+        );
+  }
+
+
+
+  private static Map
+  getOriginalProperties(Element firstLetter)
+  {
+    NamedNodeMap	attributes = firstLetter.getAttributes();
+    Map			result = new HashMap();
+
+    for (int i = 0; i < attributes.getLength(); ++i)
+    {
+      if (Constants.CSS.equals(attributes.item(i).getNamespaceURI()))
+      {
+        result.put
+        (
+          attributes.item(i).getLocalName(),
+          attributes.item(i).getNodeValue()
+        );
+      }
+    }
+
+    return result;
+  }
+
+
+
+  private static boolean
+  isPunctuation(char c)
+  {
+    return
+      Character.getType(c) == Character.END_PUNCTUATION ||
+        Character.getType(c) == Character.START_PUNCTUATION ||
+        Character.getType(c) == Character.INITIAL_QUOTE_PUNCTUATION ||
+        Character.getType(c) == Character.FINAL_QUOTE_PUNCTUATION ||
+        Character.getType(c) == Character.OTHER_PUNCTUATION;
+  }
+
+
+
+  private static void
+  mergeProperties(Element firstLetter, Node text)
+  {
+    for
+    (
+      Node n = text.getParentNode();
+      n.getParentNode() != null;
+      n = n.getParentNode()
+    )
+    {
+      NamedNodeMap	attributes = n.getAttributes();
+
+      for (int i = 0; i < attributes.getLength(); ++i)
+      {
+        if
+        (
+          Constants.CSS.equals(attributes.item(i).getNamespaceURI())	&&
+          Util.isInherited(attributes.item(i).getLocalName())		&&
+          firstLetter.getAttributeNS
+          (
+            Constants.CSS,
+            attributes.item(i).getLocalName()
+          ).equals("")
+        )
+        {
+          firstLetter.setAttributeNS
+          (
+            Constants.CSS,
+            "css:" + attributes.item(i).getLocalName(),
+            attributes.item(i).getNodeValue()
+          );
+        }
+      }
+    }
+  }
+
+
+
+  private static void
+  removeOriginalProperties(Element element, Map properties)
+  {
+    for (Iterator i = properties.keySet().iterator(); i.hasNext();)
+    {
+      element.removeAttributeNS(Constants.CSS, (String) i.next());
+    }
+  }
+
+
+
+  private static void
+  setOriginalProperties(Element element, Map properties)
+  {
+    for (Iterator i = properties.keySet().iterator(); i.hasNext();)
+    {
+      String	localName = (String) i.next();
+
+      element.setAttributeNS
+      (
+        Constants.CSS,
+        "css:" + localName,
+        (String) properties.get(localName)
+      );
+    }
+  }
+
+
+
+  private static void
+  splitText(Element firstLetter, Text text, int offset)
+  {
+    firstLetter.appendChild
+    (
+      firstLetter.getOwnerDocument().
+        createTextNode(text.getData().substring(0, offset))
+    );
+
+    mergeProperties(firstLetter, text);
+
+    text.getParentNode().insertBefore
+    (
+      text.getOwnerDocument().createTextNode(text.getData().substring(offset)),
+      text
+    );
+
+    text.getParentNode().removeChild(text);
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    if
+    (
+      "block".equals(atts.getValue(Constants.CSS, "display"))		&&
+      "1".equals(atts.getValue(Constants.CSS, "has-first-letter"))
+    )
+    {
+      Accumulator.preAccumulate
+      (
+        namespaceURI,
+        localName,
+        qName,
+        atts,
+        this,
+        new Accumulator.ProcessElement()
+        {
+          public void
+          process(Element element, XMLFilter filter) throws SAXException
+          {
+            DOMToContentHandler.elementToContentHandler
+            (
+              transform(element),
+              filter.getContentHandler()
+            );
+          }
+        }
+      );
+    }
+    else
+    {
+      super.startElement(namespaceURI, localName, qName, atts);
+    }
+  }
+
+
+
+  private static Element
+  transform(Element element)
+  {
+    element.removeAttributeNS(Constants.CSS, "has-first-letter");
+
+    Element	firstLetter = getFirstLetter(element.getFirstChild());
+
+    if (firstLetter == null)
+    {
+      return element;
+    }
+
+    Map	originalProperties = getOriginalProperties(firstLetter);
+
+    firstLetter.setAttributeNS(Constants.CSS, "css:display", "inline");
+
+    Text	text = getFirstTextNode(firstLetter.getNextSibling());
+
+    if (text == null)
+    {
+      return element;
+    }
+
+    if (isPunctuation(text.getData().charAt(0)))
+    {
+      if (text.getLength() > 1)
+      {
+        splitText(firstLetter, text, 2);
+      }
+      else
+      {
+        Text	nextText =
+          getFirstTextNode
+          (
+            text.getNextSibling() != null ?
+              text.getNextSibling() : text.getParentNode().getNextSibling()
+          );
+
+        if (nextText == null)
+        {
+          splitText(firstLetter, text, 1);
+        }
+        else
+        {
+          Element	second = (Element) firstLetter.cloneNode(true);
+
+          element.insertBefore(second, firstLetter.getNextSibling());
+          splitText(firstLetter, text, 1);
+          splitText(second, nextText, 1);
+        }
+      }
+    }
+    else
+    {
+      splitText(firstLetter, text, 1);
+    }
+
+    String	floatValue = firstLetter.getAttributeNS(Constants.CSS, "float");
+
+    if (!"".equals(floatValue) && !"none".equalsIgnoreCase(floatValue))
+    {
+      wrapInFloat
+      (
+        firstLetter,
+        floatValue,
+        firstLetter.getAttributeNS(Constants.CSS, "clear"),
+        originalProperties
+      );
+    }
+
+    return element;
+  }
+
+
+
+  private static void
+  wrapInFloat
+  (
+    Element	firstLetter,
+    String	floatValue,
+    String	clearValue,
+    Map		originalProperties
+  )
+  {
+    Element	block =
+      firstLetter.getOwnerDocument().
+        createElementNS(Constants.CSS, "css:block");
+    Element	floating =
+      firstLetter.getOwnerDocument().
+        createElementNS(Constants.CSS, "css:float");
+
+    floating.appendChild(block);
+    block.setAttributeNS(Constants.CSS, "css:display", "block");
+    floating.setAttributeNS(Constants.CSS, "css:float", floatValue);
+
+    if (!"".equals(clearValue))
+    {
+      floating.setAttributeNS(Constants.CSS, "css:clear", clearValue);
+    }
+
+    Map	blockProperties = new HashMap(originalProperties);
+    Map	inlineProperties = new HashMap(originalProperties);
+
+    blockProperties.remove("float");
+    blockProperties.remove("clear");
+    blockProperties.remove("vertical-align");
+    inlineProperties.remove("vertical-align");
+
+    setOriginalProperties(block, blockProperties);
+    firstLetter.getParentNode().insertBefore(floating, firstLetter);
+    removeOriginalProperties(firstLetter, inlineProperties);
+
+    Element	second =
+      Constants.CSS.equals(firstLetter.getNextSibling().getNamespaceURI()) &&
+        "first-letter".equals(firstLetter.getNextSibling().getLocalName()) ?
+        (Element) firstLetter.getNextSibling() : null;
+
+    block.appendChild(firstLetter);
+
+    if (second != null)
+    {
+      removeOriginalProperties(second, inlineProperties);
+      block.appendChild(second);
+    }
+  }
+
+} // FirstLetterFilter
diff --git a/src/be/re/css/FootnoteFilter.java b/src/be/re/css/FootnoteFilter.java
new file mode 100644
index 0000000..2798440
--- /dev/null
+++ b/src/be/re/css/FootnoteFilter.java
@@ -0,0 +1,223 @@
+package be.re.css;
+
+import be.re.xml.Accumulator;
+import be.re.xml.DOMToContentHandler;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Processes footnotes.
+ * @author Werner Donn\u00e9
+ */
+
+class FootnoteFilter extends XMLFilterImpl
+
+{
+
+  private Element	footnoteReference = null;
+
+
+
+  FootnoteFilter()
+  {
+  }
+
+
+
+  FootnoteFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  public void
+  characters(char[] ch, int start, int length) throws SAXException
+  {
+    if (footnoteReference != null)
+    {
+      if (!Util.isWhitespace(ch, start, length))
+      {
+        flushFootnoteReference();
+        super.characters(ch, start, length);
+      }
+    }
+    else
+    {
+      super.characters(ch, start, length);
+    }
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    flushFootnoteReference();
+    super.endElement(namespaceURI, localName, qName);
+  }
+
+
+
+  private void
+  flushFootnoteReference() throws SAXException
+  {
+    if (footnoteReference != null && getContentHandler() != null)
+    {
+      footnoteReference.setAttributeNS(Constants.CSS, "css:display", "inline");
+      DOMToContentHandler.
+        elementToContentHandler(footnoteReference, getContentHandler());
+      footnoteReference = null;
+    }
+  }
+
+
+
+  private static Element
+  getBeforePseudoElement(Node node)
+  {
+    return
+      node == null ?
+        null :
+        (
+          node instanceof Element &&
+            Constants.CSS.equals(node.getNamespaceURI()) &&
+            "before".equals(node.getLocalName()) &&
+            "footnote-reference".equals
+            (
+              ((Element) node).getAttributeNS(Constants.CSS, "display")
+            ) ?
+            (Element) node :
+            getBeforePseudoElement(node.getNextSibling())
+        );
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    String	display = atts.getValue(Constants.CSS, "display");
+
+    if ("footnote-reference".equals(display))
+    {
+      flushFootnoteReference();
+
+      Accumulator.preAccumulate
+      (
+        namespaceURI,
+        localName,
+        qName,
+        atts,
+        this,
+        new Accumulator.ProcessElement()
+        {
+          public void
+          process(Element element, XMLFilter filter) throws SAXException
+          {
+            footnoteReference = element;
+          }
+        }
+      );
+    }
+    else
+    {
+      if ("footnote-body".equals(display))
+      {
+        Accumulator.preAccumulate
+        (
+          namespaceURI,
+          localName,
+          qName,
+          atts,
+          this,
+          new Accumulator.ProcessElement()
+          {
+            public void
+            process(Element element, XMLFilter filter) throws SAXException
+            {
+              transform(element);
+            }
+          }
+        );
+      }
+      else
+      {
+        flushFootnoteReference();
+        super.startElement(namespaceURI, localName, qName, atts);
+      }
+    }
+  }
+
+
+
+  private void
+  transform(Element element) throws SAXException
+  {
+    Element	before = getBeforePseudoElement(element.getFirstChild());
+
+    if (footnoteReference == null && before == null)
+    {
+      return;
+    }
+
+    if (footnoteReference == null)
+    {
+      footnoteReference = before;
+    }
+
+    super.startElement
+    (
+      Constants.CSS,
+      "footnote",
+      "css:footnote",
+      new AttributesImpl()
+    );
+
+    super.startElement
+    (
+      Constants.CSS,
+      "footnote-reference",
+      "css:footnote-reference",
+      new AttributesImpl()
+    );
+
+    flushFootnoteReference();
+
+    super.endElement
+    (
+      Constants.CSS,
+      "footnote-reference",
+      "css:footnote-reference"
+    );
+
+    super.startElement
+    (
+      Constants.CSS,
+      "footnote-body",
+      "css:footnote-body",
+      new AttributesImpl()
+    );
+
+    element.setAttributeNS(Constants.CSS, "css:display", "block");
+    DOMToContentHandler.elementToContentHandler(element, getContentHandler());
+    super.endElement(Constants.CSS, "footnote-body", "css:footnote-body");
+    super.endElement(Constants.CSS, "footnote", "css:footnote");
+  }
+
+} // FootnoteFilter
diff --git a/src/be/re/css/ForeignFilter.java b/src/be/re/css/ForeignFilter.java
new file mode 100644
index 0000000..6797b34
--- /dev/null
+++ b/src/be/re/css/ForeignFilter.java
@@ -0,0 +1,129 @@
+package be.re.css;
+
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Places elements with the display type "foreign" in an
+ * fo:instream-foreign-object element and removes attributes in the CSS
+ * namespace below it.
+ * @author Werner Donn\u00e9
+ */
+
+class ForeignFilter extends XMLFilterImpl
+
+{
+
+  private Stack	stack = new Stack();
+
+
+
+  ForeignFilter()
+  {
+  }
+
+
+
+  ForeignFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    boolean	foreign = ((Boolean) stack.pop()).booleanValue();
+    boolean	foreignParent =
+      !stack.isEmpty() && ((Boolean) stack.peek()).booleanValue();
+
+    super.endElement(namespaceURI, localName, qName);
+
+    if (!foreignParent && foreign)
+    {
+      super.endElement
+      (
+        Constants.XSLFO,
+        "instream-foreign-object",
+        "fo:instream-foreign-object"
+      );
+    }
+  }
+
+
+
+  private static Attributes
+  removeCSS(Attributes atts)
+  {
+    AttributesImpl	result = new AttributesImpl();
+
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if
+      (
+        !Constants.CSS.equals(atts.getURI(i))		&&
+        !Constants.SPECIF.equals(atts.getURI(i))
+      )
+      {
+        result.addAttribute
+        (
+          atts.getURI(i),
+          atts.getLocalName(i),
+          atts.getQName(i),
+          atts.getType(i),
+          atts.getValue(i)
+        );
+      }
+    }
+
+    return result;
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    boolean	foreignParent =
+      !stack.isEmpty() && ((Boolean) stack.peek()).booleanValue();
+    boolean	foreign =
+      foreignParent ||
+        "foreign".equals(atts.getValue(Constants.CSS, "display"));
+
+    if (!foreignParent && foreign)
+    {
+      super.startElement
+      (
+        Constants.XSLFO,
+        "instream-foreign-object",
+        "fo:instream-foreign-object",
+        new AttributesImpl()
+      );
+    }
+
+    stack.push(new Boolean(foreign));
+
+    super.startElement
+    (
+      namespaceURI,
+      localName,
+      qName,
+      foreign ? removeCSS(atts) : atts
+    );
+  }
+
+} // ForeignFilter
diff --git a/src/be/re/css/InternedElementSelector.java b/src/be/re/css/InternedElementSelector.java
new file mode 100644
index 0000000..0b2fe70
--- /dev/null
+++ b/src/be/re/css/InternedElementSelector.java
@@ -0,0 +1,52 @@
+package be.re.css;
+
+import org.w3c.css.sac.ElementSelector;
+
+
+
+public class InternedElementSelector implements ElementSelector
+
+{
+
+  private String	localName;
+  private String	namespaceURI;
+  private short		selectorType;
+
+
+
+  public
+  InternedElementSelector(ElementSelector selector)
+  {
+    localName =
+      selector.getLocalName() == null ? null : selector.getLocalName().intern();
+    namespaceURI =
+      selector.getNamespaceURI() == null ?
+        null : selector.getNamespaceURI().intern();
+    selectorType = selector.getSelectorType();
+  }
+
+
+
+  public String
+  getLocalName()
+  {
+    return localName;
+  }
+
+
+
+  public String
+  getNamespaceURI()
+  {
+    return namespaceURI;
+  }
+
+
+
+  public short
+  getSelectorType()
+  {
+    return selectorType;
+  }
+
+} // InternedElementSelector
diff --git a/src/be/re/css/InvalidPropertyFilter.java b/src/be/re/css/InvalidPropertyFilter.java
new file mode 100644
index 0000000..6b7f384
--- /dev/null
+++ b/src/be/re/css/InvalidPropertyFilter.java
@@ -0,0 +1,374 @@
+package be.re.css;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Removes invalid properties after projection, where shorthand properties have
+ * already been split.
+ * @author Werner Donn\u00e9
+ */
+
+class InvalidPropertyFilter extends XMLFilterImpl
+
+{
+
+  private final static Set	after =
+    new HashSet(Arrays.asList(new String[]{"change-bar-class", "content"}));
+  private final static Set	alwaysValid =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "background-attachment",
+          "background-color",
+          "background-image",
+          "background-repeat",
+          "border-top-color",
+          "border-right-color",
+          "border-bottom-color",
+          "border-left-color",
+          "border-top-style",
+          "border-right-style",
+          "border-bottom-style",
+          "border-left-style",
+          "border-top-width",
+          "border-right-width",
+          "border-bottom-width",
+          "border-left-width",
+          "color",
+          "counter-increment",
+          "counter-reset",
+          "direction",
+          "display",
+          "font",
+          "font-family",
+          "font-size",
+          "font-size-adjust",
+          "font-stretch",
+          "font-style",
+          "font-variant",
+          "font-weight",
+          "letter-spacing",
+          "line-height",
+          "margin-top",
+          "margin-right",
+          "margin-bottom",
+          "margin-left",
+          "padding-top",
+          "padding-right",
+          "padding-bottom",
+          "padding-left",
+          "position",
+          "quotes",
+          "region",
+          "string-set",
+          "text-decoration",
+          "text-shadow",
+          "text-transform",
+          "unicode-bidi",
+          "visibility",
+          "word-spacing",
+          // Internal attributes.
+          "has-first-letter",
+          "has-markers",
+          "list-label-width"
+        }
+      )
+    );
+  private final static Set	before =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "change-bar-class", "change-bar-color", "change-bar-offset",
+            "change-bar-placement", "change-bar-style", "change-bar-width",
+            "content"
+        }
+      )
+    );
+  private final static Set	blockLevel =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "anchor", "background-position", "clear", "clip", "hyphenate",
+            "link", "orphans", "overflow", "page", "page-break-after",
+            "page-break-before", "page-break-inside", "text-align",
+            "text-align-last", "text-indent", "white-space", "widows"
+        }
+      )
+    );
+  private final static Set	blockLevelNotTable =
+    new HashSet(Arrays.asList(new String[]{"column-span"}));
+  private final static Set	blockOrTableOrTableCell =
+    new HashSet(Arrays.asList(new String[]{"orientation"}));
+  private final static Set	graphic =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "background-position", "content-height", "content-type",
+            "content-width", "height", "max-height", "max-width", "min-height",
+            "min-width", "overflow", "scaling", "scaling-method", "src", "width"
+        }
+      )
+    );
+  private final static Set	inline =
+    new HashSet
+    (
+      Arrays.
+        asList(new String[]{"anchor", "hyphenate", "link", "vertical-align"})
+    );
+  private final static Set	leader =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "leader-alignment", "leader-length", "leader-pattern",
+            "leader-pattern-width", "rule-thickness"
+        }
+      )
+    );
+  private final static Set	listItem =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "list-style", "list-style-image", "list-style-position",
+            "list-style-type"
+        }
+      )
+    );
+  private final static Set	marker =
+    new HashSet(Arrays.asList(new String[]{"marker-offset"}));
+  private final static Set	notInlineOrTable =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]{"max-height", "max-width", "min-height", "min-width"}
+      )
+    );
+  private final static Set	notInlineOrTableColumnOrColumnGroup =
+    new HashSet(Arrays.asList(new String[]{"height"}));
+  private final static Set	notInlineOrTableRowOrRowGroup =
+    new HashSet(Arrays.asList(new String[]{"width"}));
+  private final static Set	notPositioned =
+    new HashSet(Arrays.asList(new String[]{"float"}));
+  private final static Set	positioned =
+    new HashSet
+    (
+      Arrays.asList(new String[]{"bottom", "left", "right", "top", "z-index"})
+    );
+  private final static Set	table =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "border-collapse", "border-spacing", "caption-side", "empty-cells",
+            "table-layout", "table-omit-footer-at-break",
+            "table-omit-header-at-break"
+        }
+      )
+    );
+  private final static Set	tableCaption =
+    new HashSet(Arrays.asList(new String[]{"caption-side"}));
+  private final static Set	tableCell =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]{"colspan", "empty-cells", "rowspan", "vertical-align"}
+      )
+    );
+
+
+
+  InvalidPropertyFilter()
+  {
+  }
+
+
+
+  InvalidPropertyFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private static boolean
+  isBlockLevel(String display)
+  {
+    return
+      display == Element.BLOCK || display == Element.COMPACT ||
+        display == Element.LIST_ITEM || display == Element.RUN_IN ||
+        display == Element.TABLE || display == Element.TABLE_CELL ||
+        display == Element.TABLE_ROW || display == Element.MARKER;
+  }
+
+
+
+  private static boolean
+  isInline(String display)
+  {
+    return
+      display == Element.INLINE || display == Element.GRAPHIC ||
+        display == Element.LEADER;
+  }
+
+
+
+  private static boolean
+  isPositioned(Attributes atts)
+  {
+    String	value = atts.getValue(Constants.CSS, "position");
+
+    return value != null && !"static".equals(value);
+  }
+
+
+
+  private static boolean
+  isValid
+  (
+    Attributes	atts,
+    int		index,
+    String	display,
+    boolean	isBefore,
+    boolean	isAfter,
+    boolean	isPositioned
+  )
+  {
+    String	localName = atts.getLocalName(index);
+
+    return
+      !Constants.CSS.equals(atts.getURI(index)) ||
+        (
+          alwaysValid.contains(localName) ||
+            Util.isInherited(localName) ||
+            (isBlockLevel(display) && blockLevel.contains(localName)) ||
+            (display == Element.LIST_ITEM && listItem.contains(localName)) ||
+            (
+              (display == Element.TABLE || display == Element.INLINE_TABLE) &&
+                table.contains(localName)
+            ) ||
+            (
+              display == Element.TABLE_CAPTION &&
+                tableCaption.contains(localName)
+            ) ||
+            (display == Element.TABLE_CELL && tableCell.contains(localName)) ||
+            (isInline(display) && inline.contains(localName)) ||
+            (
+              !isInline(display) && display != Element.TABLE &&
+                notInlineOrTable.contains(localName)
+            ) ||
+            (
+              !isInline(display) && display != Element.TABLE_COLUMN &&
+                display != Element.TABLE_COLUMN_GROUP &&
+                notInlineOrTableColumnOrColumnGroup.contains(localName)
+            ) ||
+            (
+              !isInline(display) && display != Element.TABLE_ROW &&
+                display != Element.TABLE_ROW_GROUP &&
+                notInlineOrTableRowOrRowGroup.contains(localName)
+            ) ||
+            (
+              display != Element.TABLE && isBlockLevel(display) &&
+                blockLevelNotTable.contains(localName)
+            ) ||
+            (display == Element.MARKER && marker.contains(localName)) ||
+            (display == Element.GRAPHIC && graphic.contains(localName)) ||
+            (display == Element.LEADER && leader.contains(localName)) ||
+            (
+              (
+                display == Element.BLOCK || display == Element.TABLE ||
+                  display == Element.TABLE_CELL
+              ) && blockOrTableOrTableCell.contains(localName)
+            ) ||
+            (isAfter && after.contains(localName)) ||
+            (isBefore && before.contains(localName)) ||
+            (isPositioned && positioned.contains(localName)) ||
+            (!isPositioned && notPositioned.contains(localName))
+        );
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    String	display = atts.getValue(Constants.CSS, "display");
+
+    if
+    (
+      display == null				||
+      (
+        Constants.CSS.equals(namespaceURI)	&&
+        !"after".equals(localName)		&&
+        !"before".equals(localName)
+      )
+    )
+    {
+      super.startElement(namespaceURI, localName, qName, atts);
+
+      return;
+    }
+
+    display = display.intern();
+
+    boolean		after =
+      Constants.CSS.equals(namespaceURI) && "after".equals(localName);
+    boolean		before =
+      Constants.CSS.equals(namespaceURI) && "before".equals(localName);
+    AttributesImpl	newAtts = new AttributesImpl();
+    boolean		positioned = isPositioned(atts);
+
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if (isValid(atts, i, display, before, after, positioned))
+      {
+        newAtts.addAttribute
+        (
+          atts.getURI(i),
+          atts.getLocalName(i),
+          atts.getQName(i),
+          atts.getType(i),
+          atts.getValue(i)
+        );
+      }
+    }
+
+    super.startElement(namespaceURI, localName, qName, newAtts);
+  }
+
+} // InvalidPropertyFilter
diff --git a/src/be/re/css/LengthAdjustFilter.java b/src/be/re/css/LengthAdjustFilter.java
new file mode 100644
index 0000000..4530a65
--- /dev/null
+++ b/src/be/re/css/LengthAdjustFilter.java
@@ -0,0 +1,163 @@
+package be.re.css;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Length properties without a unit are given the unit "px".
+ * @author Werner Donn\u00e9
+ */
+
+class LengthAdjustFilter extends XMLFilterImpl
+
+{
+
+  private static final Set	ofLengthType =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "background-position",
+          "border-bottom-width",
+          "border-left-width",
+          "border-right-width",
+          "border-spacing",
+          "border-top-width",
+          "bottom",
+          "font-size",
+          "height",
+          "img-height",
+          "img-width",
+          "left",
+          "letter-spacing",
+          "line-height",
+          "margin-bottom",
+          "margin-left",
+          "margin-right",
+          "margin-top",
+          "marker-offset",
+          "max-height",
+          "max-width",
+          "min-height",
+          "min-width",
+          "outline-width",
+          "padding-bottom",
+          "padding-left",
+          "padding-right",
+          "padding-top",
+          "right",
+          "size",
+          "text-indent",
+          "text-shadow",
+          "top",
+          "vertical-align",
+          "width",
+          "word-spacing"
+        }
+      )
+    );
+
+
+
+  LengthAdjustFilter()
+  {
+  }
+
+
+
+  LengthAdjustFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private static Attributes
+  adjustAttributes(Attributes atts)
+  {
+    AttributesImpl	result = null;
+
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if
+      (
+        Constants.CSS.equals(atts.getURI(i))			&&
+        ofLengthType.contains(atts.getLocalName(i))
+      )
+      {
+        boolean		changed = false;
+        String		newValue = "";
+        StringTokenizer	tokenizer = new StringTokenizer(atts.getValue(i), " ");
+
+        while (tokenizer.hasMoreTokens())
+        {
+          String	token = tokenizer.nextToken();
+
+          if (mustReplace(token))
+          {
+            changed = true;
+            newValue += (newValue.equals("") ? "" : " " ) + token + "px";
+          }
+          else
+          {
+            newValue += (newValue.equals("") ? "" : " " ) + token;
+          }
+        }
+
+        if (changed)
+        {
+          if (result == null)
+          {
+            result = new AttributesImpl(atts);
+          }
+
+          result.setValue(i, newValue);
+        }
+      }
+    }
+
+    return result != null ? (Attributes) result : atts;
+  }
+
+
+
+  private static boolean
+  mustReplace(String s)
+  {
+    try
+    {
+      return Integer.parseInt(s) > 0;
+    }
+
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    super.startElement(namespaceURI, localName, qName, adjustAttributes(atts));
+  }
+
+} // LengthAdjustFilter
diff --git a/src/be/re/css/LinkFilter.java b/src/be/re/css/LinkFilter.java
new file mode 100644
index 0000000..52fd98f
--- /dev/null
+++ b/src/be/re/css/LinkFilter.java
@@ -0,0 +1,218 @@
+package be.re.css;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Processes css:link and css:anchor properties.
+ * @author Werner Donn\u00e9
+ */
+
+class LinkFilter extends XMLFilterImpl
+
+{
+
+  private static final int	EXTERNAL_LINK = 0;
+  private static final int	INTERNAL_LINK = 1;
+  private static final int	NO_LINK = 2;
+
+  private URL	baseUrl;
+  private Stack	elements = new Stack();
+
+
+
+  LinkFilter(URL baseUrl)
+  {
+    this.baseUrl = baseUrl;
+  }
+
+
+
+  LinkFilter(URL baseUrl, XMLReader parent)
+  {
+    super(parent);
+    this.baseUrl = baseUrl;
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    super.endElement(namespaceURI, localName, qName);
+
+    int	linkType = ((Element) elements.pop()).linkType;
+
+    if (linkType != NO_LINK)
+    {
+      String	name =
+        (linkType == INTERNAL_LINK ? "internal-link" : "external-link");
+
+      super.endElement(Constants.CSS, name, "css:" + name);
+    }
+  }
+
+
+
+  private void
+  handleBaseUrl(Attributes atts) throws SAXException
+  {
+    String	base = atts.getValue("xml:base");
+
+    try
+    {
+      ((Element) elements.peek()).baseUrl =
+        base != null ?
+          new URL(base) :
+          (
+            elements.size() == 1 ?
+              baseUrl : ((Element) elements.get(elements.size() - 2)).baseUrl
+          );
+    }
+
+    catch (MalformedURLException e)
+    {
+      throw new SAXException(e);
+    }
+  }
+
+
+
+  private static boolean
+  isUrl(URL baseUrl, String target)
+  {
+    try
+    {
+      new URL(baseUrl != null ? baseUrl : new URL("file:///nowhere"), target);
+
+      return true;
+    }
+
+    catch (MalformedURLException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  private static Attributes
+  resolveAnchor(Attributes atts)
+  {
+    if (atts.getIndex(Constants.CSS, "anchor") == -1)
+    {
+      return atts;
+    }
+
+    AttributesImpl	result = new AttributesImpl(atts);
+    int			index = result.getIndex(Constants.CSS, "anchor");
+
+    if (result.getValue(index).equalsIgnoreCase("none"))
+    {
+      result.removeAttribute(index);
+    }
+    else
+    {
+      result.setValue(index, Util.getIndirectValue(result, "anchor"));
+    }
+
+    return result;
+  }
+
+
+
+  void
+  setBaseUrl(URL url)
+  {
+    baseUrl = url;
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    Element	element = new Element();
+
+    elements.push(element);
+    handleBaseUrl(atts);
+
+    String	link = atts.getValue(Constants.CSS, "link");
+
+    if (link != null)
+    {
+      String	target = Util.getIndirectValue(atts, "link");
+      String	type = Util.getIndirectType(atts, "link");
+
+      atts = new AttributesImpl(atts);
+      ((AttributesImpl) atts).
+        removeAttribute(atts.getIndex(Constants.CSS, "link"));
+
+      if (!link.equalsIgnoreCase("none") && target != null)
+      {
+        AttributesImpl	linkAtts = new AttributesImpl();
+
+        element.linkType =
+          target.startsWith("#") || "IDREF".equals(type) ?
+            INTERNAL_LINK :
+            (isUrl(element.baseUrl, target) ? EXTERNAL_LINK : NO_LINK);
+
+        if (element.linkType != NO_LINK)
+        {
+          String	name =
+            element.linkType == INTERNAL_LINK ?
+              "internal-link" : "external-link";
+
+          linkAtts.addAttribute
+          (
+            "",
+            "target",
+            "target",
+            "CDATA",
+            element.linkType == INTERNAL_LINK ?
+              target.substring(target.startsWith("#") ? 1 : 0) : target
+          );
+
+          super.startElement(Constants.CSS, name, "css:" + name, linkAtts);
+        }
+      }
+      else
+      {
+        element.linkType = NO_LINK;
+      }
+    }
+    else
+    {
+      element.linkType = NO_LINK;
+    }
+
+    super.startElement(namespaceURI, localName, qName, resolveAnchor(atts));
+  }
+
+
+
+  private static class Element
+
+  {
+
+    private URL	baseUrl;
+    private int	linkType;
+
+  } // Element
+
+} // LinkFilter
diff --git a/src/be/re/css/ListImageLabelFilter.java b/src/be/re/css/ListImageLabelFilter.java
new file mode 100644
index 0000000..2105d3a
--- /dev/null
+++ b/src/be/re/css/ListImageLabelFilter.java
@@ -0,0 +1,115 @@
+package be.re.css;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.URL;
+import javax.imageio.ImageIO;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Fetches images specified in <code>list-style-image</code> properties and
+ * sets their width as the <code>list-label-width</code> property.
+ * @author Werner Donn\u00e9
+ */
+
+class ListImageLabelFilter extends XMLFilterImpl
+
+{
+
+  ListImageLabelFilter()
+  {
+  }
+
+
+
+  ListImageLabelFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private static String
+  decodeUrl(String url)
+  {
+    return
+      url.startsWith("url(") && url.endsWith(")") ?
+        url.substring(4, url.length() - 1) : url;
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    String	url = atts.getValue(Constants.CSS, "list-style-image");
+
+    if (url != null)
+    {
+      if (url.equals("none"))
+      {
+        atts = new AttributesImpl(atts);
+
+        Util.removeAttribute
+        (
+          (AttributesImpl) atts,
+          Constants.CSS,
+          "list-style-image"
+        );
+
+        if (atts.getValue(Constants.CSS, "list-style-type") == null)
+        {
+          ((AttributesImpl) atts).addAttribute
+          (
+            Constants.CSS,
+            "list-style-type",
+            "css:list-style-type",
+            "CDATA",
+            "none"
+          );
+        }
+      }
+      else
+      {
+        try
+        {
+          BufferedImage	image = ImageIO.read(new URL(decodeUrl(url)));
+
+          if (image != null)
+          {
+            atts = new AttributesImpl(atts);
+
+            ((AttributesImpl) atts).addAttribute
+            (
+              Constants.CSS,
+              "list-label-width",
+              "css:list-label-width",
+              "CDATA",
+              String.valueOf(image.getWidth() + 5) + "px"
+            );
+          }
+        }
+
+        catch (IOException e)
+        {
+          throw new SAXException(e);
+        }
+      }
+    }
+
+    super.startElement(namespaceURI, localName, qName, atts);
+  }
+
+} // ListImageLabelFilter
diff --git a/src/be/re/css/MarkerFilter.java b/src/be/re/css/MarkerFilter.java
new file mode 100644
index 0000000..18efaa3
--- /dev/null
+++ b/src/be/re/css/MarkerFilter.java
@@ -0,0 +1,389 @@
+package be.re.css;
+
+import be.re.xml.Accumulator;
+import be.re.xml.DOMToContentHandler;
+import be.re.xml.sax.FilterOfFilters;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NamedNodeMap;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Creates table structures to implement the marker display type.
+ * @author Werner Donn\u00e9
+ */
+
+class MarkerFilter extends XMLFilterImpl
+
+{
+
+  private static final String	DEFAULT_WIDTH = "2em";
+
+
+
+  MarkerFilter()
+  {
+  }
+
+
+
+  MarkerFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private void
+  accumulate
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    Accumulator.preAccumulate
+    (
+      namespaceURI,
+      localName,
+      qName,
+      atts,
+      this,
+      new Accumulator.ProcessElement()
+      {
+        public void
+        process(Element element, XMLFilter filter) throws SAXException
+        {
+          DOMToContentHandler.elementToContentHandler
+          (
+            transform(element),
+            filter.getContentHandler()
+          );
+        }
+      }
+    );
+  }
+
+
+
+  private static void
+  addBody(Element table, Element element, Element before, Element after)
+  {
+    Element	body =
+      table.getOwnerDocument().
+        createElementNS(Constants.CSS, "table-row-group");
+
+    body.setAttributeNS(Constants.CSS, "css:display", "table-row-group");
+    table.appendChild(body);
+
+    Element	row =
+      table.getOwnerDocument().createElementNS(Constants.CSS, "table-row");
+
+    row.setAttributeNS(Constants.CSS, "css:display", "table-row");
+    body.appendChild(row);
+
+    Element	mainCell =
+      table.getOwnerDocument().createElementNS(Constants.CSS, "table-cell");
+
+    mainCell.setAttributeNS(Constants.CSS, "css:display", "table-cell");
+    row.appendChild(mainCell);
+    mainCell.appendChild(element);
+
+    if (before != null)
+    {
+      Element	cell =
+        table.getOwnerDocument().createElementNS(Constants.CSS, "table-cell");
+
+      cell.setAttributeNS(Constants.CSS, "css:display", "table-cell");
+      cell.setAttributeNS(Constants.CSS, "css:vertical-align", "top");
+      row.insertBefore(cell, mainCell);
+      addMarker(cell, before, "right");
+    }
+
+    if (after != null)
+    {
+      Element	cell =
+        table.getOwnerDocument().createElementNS(Constants.CSS, "table-cell");
+
+      cell.setAttributeNS(Constants.CSS, "css:display", "table-cell");
+      cell.setAttributeNS(Constants.CSS, "css:vertical-align", "bottom");
+      row.appendChild(cell);
+      addMarker(cell, after, "left");
+    }
+  }
+
+
+
+  private static void
+  addColumn(Element table, String width)
+  {
+    Element	column =
+      table.getOwnerDocument().createElementNS(Constants.CSS, "table-column");
+
+    column.setAttributeNS(Constants.CSS, "css:display", "table-column");
+
+    column.setAttributeNS
+    (
+      Constants.CSS,
+      "css:width",
+      width.equals("") ? "1*" : width
+    );
+    table.appendChild(column);
+  }
+
+
+
+  private static void
+  addMarker(Element cell, Element marker, String side)
+  {
+    cell.appendChild(marker);
+    marker.removeAttributeNS(Constants.CSS, "width");
+    marker.setAttributeNS(Constants.CSS, "css:display", "block");
+
+    String	markerOffset =
+      marker.getAttributeNS(Constants.CSS, "marker-offset");
+
+    if (!markerOffset.equals(""))
+    {
+      cell.setAttributeNS(Constants.CSS, "css:padding-" + side, markerOffset);
+      marker.removeAttributeNS(Constants.CSS, "marker-offset");
+    }
+  }
+
+
+
+  private static Element
+  getAfterPseudoElement(Node node)
+  {
+    return
+      node == null ?
+        null :
+        (
+          node instanceof Element &&
+            Constants.CSS.equals(node.getNamespaceURI()) &&
+            "after".equals(node.getLocalName()) &&
+            "marker".equals
+            (
+              ((Element) node).getAttributeNS(Constants.CSS, "display")
+            ) ?
+            (Element) node :
+            getAfterPseudoElement(node.getPreviousSibling())
+        );
+  }
+
+
+
+  private static Element
+  getBeforePseudoElement(Node node)
+  {
+    return
+      node == null ?
+        null :
+        (
+          node instanceof Element &&
+            Constants.CSS.equals(node.getNamespaceURI()) &&
+            "before".equals(node.getLocalName()) &&
+            "marker".equals
+            (
+              ((Element) node).getAttributeNS(Constants.CSS, "display")
+            ) ?
+            (Element) node :
+            getBeforePseudoElement(node.getNextSibling())
+        );
+  }
+
+
+
+  private static Element
+  handleNestedMarkers(Element element) throws SAXException
+  {
+    Accumulator		result = new Accumulator();
+    FilterOfFilters	filter =
+      new FilterOfFilters
+      (
+        // The MarkerFilter needs a parent to insert its own accumulator.
+        new XMLFilter[]{new XMLFilterImpl(), new MarkerFilter(), result}
+      );
+
+    filter.startDocument();
+    DOMToContentHandler.elementToContentHandler(element, filter);
+    filter.endDocument();
+
+    return
+      (Element)
+        element.getOwnerDocument().
+          importNode(result.getDocument().getDocumentElement(), true);
+  }
+
+
+
+  private static void
+  moveInheritedProperties(Element element, Element table)
+  {
+    NamedNodeMap	attributes = element.getAttributes();
+
+    for (int i = 0; i < attributes.getLength(); ++i)
+    {
+      Attr	attribute = (Attr) attributes.item(i);
+
+      if
+      (
+        Constants.CSS.equals(attribute.getNamespaceURI())	&&
+        Util.isInherited(attribute.getLocalName())
+      )
+      {
+        element.removeAttributeNode(attribute);
+        table.setAttributeNodeNS(attribute);
+        --i;
+      }
+    }
+  }
+
+
+
+  private static void
+  moveMargin(Element element, Element table, String side)
+  {
+    Attr	margin =
+      element.getAttributeNodeNS(Constants.CSS, "margin-" + side);
+
+    if (margin != null && !margin.getValue().equals(""))
+    {
+      element.removeAttributeNode(margin);
+      table.setAttributeNodeNS(margin);
+    }
+  }
+
+
+
+  private static void
+  moveMargins(Element element, Element table, String beforeWidth)
+  {
+    String	margin = element.getAttributeNS(Constants.CSS, "margin-left");
+
+    if (Util.isZeroLength(margin))
+    {
+      margin = "";
+    }
+
+    if (beforeWidth != null || !margin.equals(""))
+    {
+      table.setAttributeNS
+      (
+        Constants.CSS,
+        "css:margin-left",
+        (beforeWidth != null ? ("-" + beforeWidth) : "") +
+          (!margin.equals("") ? ("+" + margin) : "")
+      );
+    }
+
+    if (!margin.equals(""))
+    {
+      element.removeAttributeNS(Constants.CSS, "margin-left");
+    }
+
+    moveMargin(element, table, "right");
+    moveMargin(element, table, "top");
+    moveMargin(element, table, "bottom");
+  }
+
+
+
+  private static void
+  removeMargins(Element marker)
+  {
+    marker.removeAttributeNS(Constants.CSS, "margin-left");
+    marker.removeAttributeNS(Constants.CSS, "margin-right");
+    marker.removeAttributeNS(Constants.CSS, "margin-top");
+    marker.removeAttributeNS(Constants.CSS, "margin-bottom");
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    if ("1".equals(atts.getValue(Constants.CSS, "has-markers")))
+    {
+      accumulate(namespaceURI, localName, qName, atts);
+    }
+    else
+    {
+      super.startElement(namespaceURI, localName, qName, atts);
+    }
+  }
+
+
+
+  private static Element
+  transform(Element element) throws SAXException
+  {
+    Element	after = getAfterPseudoElement(element.getLastChild());
+    Element	before = getBeforePseudoElement(element.getFirstChild());
+    String	beforeWidth = null;
+    Element	table =
+      element.getOwnerDocument().createElementNS(Constants.CSS, "table");
+
+    table.setAttributeNS(Constants.CSS, "css:display", "table");
+    table.setAttributeNS(Constants.CSS, "css:table-layout", "fixed");
+
+    if (before != null)
+    {
+      beforeWidth = before.getAttributeNS(Constants.CSS, "width");
+
+      if (beforeWidth.equals("") || beforeWidth.equals("auto"))
+      {
+        beforeWidth = DEFAULT_WIDTH;
+      }
+
+      addColumn(table, beforeWidth);
+
+      if ("list-item".equals(element.getAttributeNS(Constants.CSS, "display")))
+      {
+        element.setAttributeNS(Constants.CSS, "css:display", "block");
+      }
+
+      element.removeChild(before);
+      removeMargins(before);
+    }
+
+    addColumn(table, element.getAttributeNS(Constants.CSS, "width"));
+    element.setAttributeNS(Constants.CSS, "css:width", "100%");
+
+    if (after != null)
+    {
+      String	width = before.getAttributeNS(Constants.CSS, "width");
+
+      addColumn
+      (
+        table,
+        !width.equals("") && !width.equals("auto") ? width : DEFAULT_WIDTH
+      );
+
+      element.removeChild(after);
+      removeMargins(after);
+    }
+
+    moveMargins(element, table, beforeWidth);
+    moveInheritedProperties(element, table);
+    element.removeAttributeNS(Constants.CSS, "has-markers");
+    addBody(table, handleNestedMarkers(element), before, after);
+
+    return table;
+  }
+
+} // MarkerFilter
diff --git a/src/be/re/css/Matcher.java b/src/be/re/css/Matcher.java
new file mode 100644
index 0000000..10b63db
--- /dev/null
+++ b/src/be/re/css/Matcher.java
@@ -0,0 +1,652 @@
+package be.re.css;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TreeSet;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.DocumentHandler;
+import org.w3c.css.sac.LangCondition;
+import org.w3c.css.sac.NegativeCondition;
+import org.w3c.css.sac.PositionalCondition;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+
+
+/**
+ * Finds the matching rules as the document goes through it.
+ * @author Werner Donn\u00e9
+ */
+
+public class Matcher implements ContentHandler
+
+{
+
+  private static final String	DEFAULT_LANGUAGE = "en-GB";
+
+  private Stack			elements = new Stack();
+  private Compiled.DFAState	startState;
+  private static final boolean	trace =
+    System.getProperty("be.re.css.trace") != null;
+
+
+
+  public
+  Matcher(Compiled styleSheet)
+  {
+    startState = styleSheet.startState;
+  }
+
+
+
+  public void
+  characters(char[] ch, int start, int length) throws SAXException
+  {
+  }
+
+
+
+  private static boolean
+  checkAttributeCondition(Element e, AttributeCondition c, TestAttribute test)
+  {
+    if (c.getNamespaceURI() != null)
+    {
+      int	index =
+        DocumentHandler.SAC_NO_URI.equals(c.getNamespaceURI()) ?
+          e.attributes.getIndex(c.getLocalName()) :
+          e.attributes.getIndex(c.getNamespaceURI(), c.getLocalName());
+
+      return index != -1 && test.test(e.attributes, index, c);
+    }
+
+    for (int i = 0; i < e.attributes.getLength(); ++i)
+    {
+      if
+      (
+        e.attributes.getLocalName(i).equals(c.getLocalName())	&&
+        test.test(e.attributes, i, c)
+      )
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+
+  private static boolean
+  checkAttributeCondition(Element e, AttributeCondition c)
+  {
+    return
+      checkAttributeCondition
+      (
+        e,
+        c,
+        new TestAttribute()
+        {
+          public boolean
+          test(Attributes atts, int i, AttributeCondition c)
+          {
+            return
+              c.getValue() == null || c.getValue().equals(atts.getValue(i));
+          }
+        }
+      );
+  }
+
+
+
+  private static boolean
+  checkBeginHyphenAttributeCondition(Element e, AttributeCondition c)
+  {
+    return
+      checkAttributeCondition
+      (
+        e,
+        c,
+        new TestAttribute()
+        {
+          public boolean
+          test(Attributes atts, int i, AttributeCondition c)
+          {
+            return
+              atts.getValue(i).startsWith(c.getValue() + "-") ||
+                atts.getValue(i).equals(c.getValue());
+          }
+        }
+      );
+  }
+
+
+
+  private static boolean
+  checkClassCondition(Element e, AttributeCondition c)
+  {
+    String	value;
+
+    return
+      (value = e.attributes.getValue("class")) != null &&
+        hasToken(value, c.getValue());
+  }
+
+
+
+  private static boolean
+  checkCondition(Element e, Condition c)
+  {
+    switch (c.getConditionType())
+    {
+      case Condition.SAC_AND_CONDITION:
+        return
+          checkCondition(e, ((CombinatorCondition) c).getFirstCondition()) &&
+            checkCondition(e, ((CombinatorCondition) c).getSecondCondition());
+
+      case Condition.SAC_ATTRIBUTE_CONDITION:
+        return checkAttributeCondition(e, (AttributeCondition) c);
+
+      case Condition.SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION:
+        return checkBeginHyphenAttributeCondition(e, (AttributeCondition) c);
+
+      case Condition.SAC_CLASS_CONDITION:
+        return checkClassCondition(e, (AttributeCondition) c);
+
+      case Condition.SAC_ID_CONDITION:
+        return checkIdCondition(e, (AttributeCondition) c);
+
+      case Condition.SAC_LANG_CONDITION:
+        return checkLangCondition(e, (LangCondition) c);
+
+      case Condition.SAC_NEGATIVE_CONDITION:
+        return !checkCondition(e, ((NegativeCondition) c).getCondition());
+
+      case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION:
+        return checkOneOfAttributeCondition(e, (AttributeCondition) c);
+
+      case Condition.SAC_OR_CONDITION:
+        return
+          checkCondition(e, ((CombinatorCondition) c).getFirstCondition()) ||
+            checkCondition(e, ((CombinatorCondition) c).getSecondCondition());
+
+      case Condition.SAC_POSITIONAL_CONDITION:
+        return
+          checkPositionalCondition
+          (
+            e,
+            ((PositionalCondition) c).getPosition()
+          );
+
+      case Condition.SAC_PSEUDO_CLASS_CONDITION:
+        return checkPseudoClassCondition(e, (AttributeCondition) c);
+
+      default:
+        return false; // Ignore non-CSS2 or irrelevant condition types.
+    }
+  }
+
+
+
+  private static boolean
+  checkIdCondition(Element e, AttributeCondition c)
+  {
+    for (int i = 0; i < e.attributes.getLength(); ++i)
+    {
+      if
+      (
+        "ID".equals(e.attributes.getType(i))		&&
+        c.getValue().equals(e.attributes.getValue(i))
+      )
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+
+  private static boolean
+  checkLangCondition(Element e, LangCondition c)
+  {
+    return
+      e.language.startsWith(((LangCondition) c).getLang() + "-") ||
+        e.language.equals(((LangCondition) c).getLang());
+  }
+
+
+
+  private static boolean
+  checkOneOfAttributeCondition(Element e, AttributeCondition c)
+  {
+    return
+      checkAttributeCondition
+      (
+        e,
+        c,
+        new TestAttribute()
+        {
+          public boolean
+          test(Attributes atts, int i, AttributeCondition c)
+          {
+            return hasToken(atts.getValue(i), c.getValue());
+          }
+        }
+      );
+  }
+
+
+
+  private static boolean
+  checkPositionalCondition(Element e, int position)
+  {
+    // The element on the top of the stack is not yet in the child list of its
+    // parent. The preceding sibling is the last element in the parent's child
+    // list.
+
+    return e.parent.children.size() == position;
+  }
+
+
+
+  private static boolean
+  checkPseudoClassCondition(Element e, AttributeCondition c)
+  {
+    return
+      "after".equals(c.getValue()) || "before".equals(c.getValue()) ||
+        ("first-child".equals(c.getValue()) && checkPositionalCondition(e, 0));
+  }
+
+
+
+  public void
+  endDocument() throws SAXException
+  {
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    Element	element = (Element) elements.pop();
+
+    ((Element) elements.peek()).children.add(element);
+    element.children = null;
+  }
+
+
+
+  public void
+  endPrefixMapping(String prefix) throws SAXException
+  {
+  }
+
+
+
+  private String
+  getLanguage(String namespaceURI, Attributes attributes, Element parent)
+  {
+    String	result = null;
+
+    if (Constants.XHTML == namespaceURI)
+    {
+      result = attributes.getValue("lang");
+    }
+
+    if (result == null)
+    {
+      result = attributes.getValue("xml:lang");
+    }
+
+    if (result == null)
+    {
+      result = parent.language;
+    }
+
+    return result;
+  }
+
+
+
+  private static Set
+  getSiblingStates(Collection states)
+  {
+    Set	result = new HashSet();
+
+    for (Iterator i = states.iterator(); i.hasNext();)
+    {
+      Object	nextState =
+        ((Compiled.DFAState) i.next()).events.get(Compiled.SIBLING);
+
+      if (nextState != null)
+      {
+        result.add(nextState);
+      }
+    }
+
+    return result;
+  }
+
+
+
+  public void
+  ignorableWhitespace(char[] ch, int start, int length) throws SAXException
+  {
+  }
+
+
+
+  private static boolean
+  hasToken(String s, String token)
+  {
+    int	i;
+
+    return
+      (i = s.indexOf(token)) != -1 && (i == 0 || s.charAt(i - 1) == ' ') &&
+        (
+          i == s.length() - token.length() ||
+            s.charAt(i + token.length()) == ' '
+        );
+  }
+
+
+
+  /**
+   * Returns the rules that match a pseudo element sorted from least to most
+   * specific.
+   */
+
+  public Rule[]
+  matchingPseudoRules()
+  {
+    SortedSet	result = new TreeSet(new RuleComparator());
+
+    for
+    (
+      Iterator i = ((Element) elements.peek()).states.iterator();
+      i.hasNext();
+    )
+    {
+      result.addAll(((Compiled.DFAState) i.next()).pseudoRules);
+    }
+
+    return (Rule[]) result.toArray(new Rule[0]);
+  }
+
+
+
+  /**
+   * Returns the rules that match a normal element sorted from least to most
+   * specific.
+   */
+
+  public Rule[]
+  matchingRules()
+  {
+    SortedSet	result = new TreeSet(new RuleComparator());
+
+    for
+    (
+      Iterator i = ((Element) elements.peek()).states.iterator();
+      i.hasNext();
+    )
+    {
+      result.addAll(((Compiled.DFAState) i.next()).rules);
+    }
+
+    return (Rule[]) result.toArray(new Rule[0]);
+  }
+
+
+
+  public void
+  processingInstruction(String target, String data) throws SAXException
+  {
+  }
+
+
+
+  public void
+  setDocumentLocator(Locator locator)
+  {
+  }
+
+
+
+  public void
+  skippedEntity(String name) throws SAXException
+  {
+  }
+
+
+
+  public void
+  startDocument() throws SAXException
+  {
+    elements.clear();
+
+    Element	root = new Element("", "/");
+
+    root.language = DEFAULT_LANGUAGE;
+    elements.push(root);
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    Element	parent = (Element) elements.peek();
+    Set		currentStates = parent.states;
+    Element	element = new Element(namespaceURI, localName);
+
+    element.attributes = atts;
+    element.language = getLanguage(namespaceURI, atts, parent);
+    element.parent = parent;
+    elements.push(element);
+
+    traceElement(namespaceURI + "|" + localName, atts);
+    stepStates(parent.states, element);
+
+    if (parent.children.size() > 0)
+    {
+      stepStates
+      (
+        getSiblingStates
+        (
+          ((Element) parent.children.get(parent.children.size() - 1)).states
+        ),
+        element
+      );
+    }
+
+    // At every element new rules can be started, because they are relative.
+
+    step(startState, element);
+  }
+
+
+
+  public void
+  startPrefixMapping(String prefix, String uri) throws SAXException
+  {
+  }
+
+
+
+  /**
+   * More than one state transition can occur because when the candidate
+   * conditions are fullfilled, they constitute an event. The universal
+   * selector transitions are also tried.
+   */
+
+  private static void
+  step(Compiled.DFAState state, Element element)
+  {
+    stepOneEvent
+    (
+      state,
+      element,
+      ("".equals(element.namespaceURI) ? "*" : element.namespaceURI) + "|" +
+        element.localName
+    );
+
+    if (!"".equals(element.namespaceURI))
+    {
+      stepOneEvent(state, element, "*|" + element.localName);
+      stepOneEvent(state, element, element.namespaceURI + "|*");
+    }
+    else
+    {
+      stepOneEvent
+      (
+        state,
+        element,
+        DocumentHandler.SAC_NO_URI + "|" + element.localName
+      );
+    }
+
+    stepOneEvent(state, element, Compiled.ANY_ELEMENT);
+  }
+
+
+
+  private static void
+  stepOneEvent(Compiled.DFAState state, Element element, String name)
+  {
+    Compiled.DFAState	nextState = (Compiled.DFAState) state.events.get(name);
+
+    if (nextState != null)
+    {
+      traceTransition(state, nextState, name);
+      element.states.add(nextState);
+      stepThroughConditions(nextState, element);
+    }
+  }
+
+
+
+  private static void
+  stepStates(Collection states, Element element)
+  {
+    for (Iterator i = states.iterator(); i.hasNext();)
+    {
+      step((Compiled.DFAState) i.next(), element);
+    }
+  }
+
+
+
+  private static void
+  stepThroughConditions(Compiled.DFAState state, Element element)
+  {
+    for
+    (
+      Iterator i = state.candidateConditions.keySet().iterator();
+      i.hasNext();
+    )
+    {
+      Condition		c = (Condition) i.next();
+      Compiled.DFAState	nextState =
+        (Compiled.DFAState) state.candidateConditions.get(c);
+
+      if (nextState != null && checkCondition(element, c))
+      {
+        traceTransition(state, nextState, c);
+        element.states.add(nextState);
+      }
+    }
+  }
+
+
+
+  private static void
+  traceElement(String qName, Attributes atts)
+  {
+    if (trace)
+    {
+      System.out.print(qName + ": ");
+
+      for (int i = 0; i < atts.getLength(); ++i)
+      {
+        System.out.print(atts.getQName(i) + "=" + atts.getValue(i) + " ");
+      }
+
+      System.out.println();
+    }
+  }
+
+
+
+  private static void
+  traceTransition(Compiled.DFAState from, Compiled.DFAState to, Object event)
+  {
+    if (trace)
+    {
+      System.out.println
+      (
+        String.valueOf(from.state) + " -> " + String.valueOf(to.state) +
+          ": " +
+          (
+            event instanceof Condition ?
+              Util.conditionText((Condition) event) : event.toString()
+          )
+      );
+    }
+  }
+
+
+
+  private static class Element
+
+  {
+
+    private Attributes	attributes;
+    private List	children = new ArrayList();
+    private String	language;
+    private String	localName;
+    private String	namespaceURI;
+    private Element	parent;
+    private Set		states = new HashSet();
+
+
+
+    private
+    Element(String namespaceURI, String localName)
+    {
+      this.namespaceURI = namespaceURI != null ? namespaceURI : "";
+      this.localName = localName;
+    }
+
+  } // Element
+
+
+
+  private interface TestAttribute
+
+  {
+
+    public boolean	test	(Attributes atts, int i, AttributeCondition c);
+
+  } // TestAttribute
+
+} // Matcher
diff --git a/src/be/re/css/NormalizeTableFilter.java b/src/be/re/css/NormalizeTableFilter.java
new file mode 100644
index 0000000..afc5d61
--- /dev/null
+++ b/src/be/re/css/NormalizeTableFilter.java
@@ -0,0 +1,704 @@
+package be.re.css;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * This filter propagates table-column-group properties to table-column
+ * elements and table-column properties to table-cell elements. It also fills up
+ * rows which are too short with empty table cells.
+ * @author Werner Donn\u00e9
+ */
+
+class NormalizeTableFilter extends XMLFilterImpl
+
+{
+
+  private Stack	columnStack = new Stack();
+  private Stack	elementStack = new Stack();
+
+
+
+  NormalizeTableFilter()
+  {
+  }
+
+
+
+  NormalizeTableFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private void
+  addColumn(Attributes atts)
+  {
+    List	columns = (List) columnStack.peek();
+    int		span = getSpan(atts, "span");
+
+    for (int i = 0; i < span; ++i)
+    {
+      columns.add(atts);
+    }
+  }
+
+
+
+  private void
+  addTableCellRowContributions(Element element)
+  {
+    int	rowSpan = getSpan(element.atts, "rowspan");
+
+    if (rowSpan > 1)
+    {
+      int	colSpan = getSpan(element.atts, "colspan");
+      Element	group = getGroup();
+
+      for (int i = 1; i < rowSpan; ++i) // For the next rows.
+      {
+        if (i == ((List) group.extra).size())
+        {
+          ((List) group.extra).add(new Integer(colSpan));
+        }
+        else
+        {
+          ((List) group.extra).set
+          (
+            i,
+            new Integer
+            (
+              ((Integer) ((List) group.extra).get(i)).intValue() + colSpan
+            )
+          );
+        }
+      }
+    }
+  }
+
+
+
+  private void
+  bookKeeping(Element element, Element parent)
+  {
+    if (element.isDisplay(Element.TABLE))
+    {
+      columnStack.push(new ArrayList());
+    }
+    else
+    {
+      if (element.isDisplay(Element.TABLE_COLUMN))
+      {
+        addColumn(new AttributesImpl(element.atts));
+      }
+      else
+      {
+        if (element.isDisplay(Element.TABLE_ROW))
+        {
+          if (isGroup(parent) && parent.extra == null)
+          {
+            parent.extra = new ArrayList(); // Row seen.
+            ((List) parent.extra).add(new Integer(0));
+              // No contributions because there is no previous row.
+          }
+        }
+        else
+        {
+          if (element.isDisplay(Element.TABLE_CELL))
+          {
+            int	currentCellCount =
+              parent.extra == null ?
+                0 : ((Integer) parent.extra).intValue();
+
+            parent.extra =
+              new Integer
+              (
+                currentCellCount +
+                getSpan(element.atts, "colspan")
+              );
+
+            addTableCellRowContributions(element);
+          }
+        }
+      }
+    }
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    Element	element = (Element) elementStack.pop();
+
+    if
+    (
+      element.isDisplay(Element.TABLE_ROW_GROUP)	&&
+      Constants.CSS.equals(element.namespaceURI)	&&
+      "synthetic".equals(element.localName)
+    )
+    {
+      // Flush pending synthetic group.
+      super.endElement(Constants.CSS, "synthetic", "css:synthetic");
+      endElement(namespaceURI, localName, qName);
+
+      return;
+    }
+
+    Element	parent =
+      elementStack.empty() ? null : ((Element) elementStack.peek());
+
+    if (element.isDisplay(Element.TABLE_COLUMN_GROUP))
+    {
+      synthesizeColumns(element);
+    }
+    else
+    {
+      if (element.isDisplay(Element.TABLE_ROW))
+      {
+        synthesizeCells(element, parent);
+      }
+      else
+      {
+        if (element.isDisplay(Element.TABLE))
+        {
+          columnStack.pop();
+        }
+      }
+    }
+
+    if
+    (
+      !element.isDisplay(Element.TABLE_COLUMN_GROUP)		&&
+      (
+        !element.isDisplay(Element.TABLE_COLUMN)		||
+        parent == null						||
+        !parent.isDisplay(Element.TABLE_COLUMN_GROUP)
+      )
+    )
+    {
+      super.endElement(namespaceURI, localName, qName);
+    }
+  }
+
+
+
+  /**
+   * Searches down the element stack until an element with a display type in
+   * <code>oneOf</code> is found, but not beyond <code>upTo</code>.
+   */
+
+  private Element
+  getAncestor(String[] oneOf, String[] upTo)
+  {
+    for (int i = elementStack.size() - 1; i >= 0; --i)
+    {
+      Element	element = (Element) elementStack.get(i);
+
+      for (int j = 0; j < oneOf.length; ++j)
+      {
+        if (element.isDisplay(oneOf[j]))
+        {
+          return element;
+        }
+      }
+
+      for (int j = 0; j < upTo.length; ++j)
+      {
+        if (element.isDisplay(upTo[j]))
+        {
+          return null;
+        }
+      }
+    }
+
+    return null;
+  }
+
+
+
+  private Attributes
+  getColumn()
+  {
+    List	columns = (List) columnStack.peek();
+    Integer	position = (Integer) ((Element) elementStack.peek()).extra;
+
+    return
+      position == null && columns.size() > 0 ?
+        (Attributes) columns.get(0) :
+        (
+          columns.size() == 0 || position.intValue() >= columns.size() ?
+            null : (Attributes) columns.get(position.intValue())
+        );
+  }
+
+
+
+  private Element
+  getGroup()
+  {
+    return
+      getAncestor
+      (
+        new String[]
+          {
+            Element.TABLE_ROW_GROUP, Element.TABLE_HEADER_GROUP,
+              Element.TABLE_FOOTER_GROUP
+          },
+        new String[] {Element.TABLE}
+      );
+  }
+
+
+
+  private static int
+  getSpan(Attributes atts, String name)
+  {
+    String	span = atts.getValue(Constants.CSS, name);
+
+    return span == null ? 1 : Integer.parseInt(span);
+  }
+
+
+
+  private Attributes
+  getTableCellAttributes(Element element, Element parent)
+  {
+    Element	table =
+      getAncestor(new String[] {Element.TABLE}, new String[0]);
+
+    if (table == null)
+    {
+      return element.atts;
+    }
+
+    Attributes	column = getColumn();
+
+    // While not being general, the XHTML alignment is done here instead of in
+    // XHTMLAttributeTranslationFilter because we have to know to which column
+    // a cell belongs, in order to possibly inherit from it. This is only
+    // possible when the table is normalized.
+
+    Attributes	atts =
+      Constants.XHTML.equals(parent.namespaceURI) ?
+        inheritXHTMLAlign(element.atts, column, parent) : element.atts;
+    int		index = table.atts.getIndex(Constants.CSS, "border-collapse");
+
+    // The following are redundant and harmless for collapse, but it produces
+    // something useful for XSL-FO processors that treat collapse as separate.
+
+    if (index == -1 || "collapse".equals(table.atts.getValue(index)))
+    {
+      if (parent.isDisplay(Element.TABLE_ROW))
+      {
+        atts =
+          Util.
+            mergeAttributes(parent.atts, atts, new String[] {"border*"}, true);
+      }
+
+      if (column != null)
+      {
+        atts =
+          Util.mergeAttributes(column, atts, new String[] {"border*"}, true);
+      }
+    }
+
+    return atts;
+  }
+
+
+
+  private Attributes
+  getTableRowAttributes(Attributes atts, Attributes parentAtts)
+  {
+    // The following are redundant and harmless for collapse, but it produces
+    // something useful for XSL-FO processors that treat collapse as separate.
+
+    return
+      Util.mergeAttributes
+      (
+        parentAtts,
+        atts,
+        new String[] {"border-before*", "border-top*"},
+        true
+      );
+  }
+
+
+
+  private Attributes
+  inheritXHTMLAlign(Attributes atts, Attributes columnAtts, Element parent)
+  {
+    AttributesImpl	result = new AttributesImpl(atts);
+    String		textAlign =
+      atts.getIndex(Constants.SPECIF, "text-align") != -1 ?
+        // Can be overridden by XHTML.
+        null : atts.getValue(Constants.CSS, "text-align");
+    String		verticalAlign =
+      atts.getIndex(Constants.SPECIF, "vertical-align") != -1 ?
+        // Can be overridden by XHTML.
+        null : atts.getValue(Constants.CSS, "vertical-align");
+
+    if (textAlign == null && columnAtts != null)
+    {
+      textAlign = columnAtts.getValue(Constants.CSS, "text-align");
+    }
+
+    if
+    (
+      (
+        textAlign == null			||
+        verticalAlign == null
+      )						&&
+      parent.isDisplay(Element.TABLE_ROW)
+    )
+    {
+      if (textAlign == null)
+      {
+        textAlign = parent.atts.getValue(Constants.CSS, "text-align");
+      }
+
+      if (verticalAlign == null)
+      {
+        verticalAlign = parent.atts.getValue(Constants.CSS, "vertical-align");
+      }
+
+      if (textAlign == null || verticalAlign == null)
+      {
+        Element	group = getGroup();
+
+        if (group != null)
+        {
+          if (textAlign == null)
+          {
+            textAlign = group.atts.getValue(Constants.CSS, "text-align");
+          }
+
+          if (verticalAlign == null)
+          {
+            verticalAlign =
+              group.atts.getValue(Constants.CSS, "vertical-align");
+
+            if (verticalAlign == null && columnAtts != null)
+            {
+              verticalAlign =
+                columnAtts.getValue(Constants.CSS, "vertical-align");
+            }
+          }
+
+          if (textAlign == null || verticalAlign == null)
+          {
+            Element	table =
+              getAncestor(new String[] {Element.TABLE}, new String[0]);
+
+            if (table != null)
+            {
+              if (textAlign == null)
+              {
+                textAlign = table.atts.getValue(Constants.CSS, "text-align");
+              }
+
+              if (verticalAlign == null)
+              {
+                verticalAlign =
+                  table.atts.getValue(Constants.CSS, "vertical-align");
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (textAlign != null)
+    {
+      Util.setAttribute
+      (
+        result,
+        Constants.CSS,
+        "text-align",
+        "css:text-align",
+        textAlign
+      );
+    }
+
+    if (verticalAlign != null)
+    {
+      Util.setAttribute
+      (
+        result,
+        Constants.CSS,
+        "vertical-align",
+        "css:vertical-align",
+        verticalAlign
+      );
+    }
+
+    return result;
+  }
+
+
+
+  private static boolean
+  isGroup(Element element)
+  {
+    return
+      element.isDisplay(Element.TABLE_HEADER_GROUP) ||
+        element.isDisplay(Element.TABLE_FOOTER_GROUP) ||
+        element.isDisplay(Element.TABLE_ROW_GROUP);
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    Element	element =
+      new Element(namespaceURI, localName, qName, atts);
+
+    if (!element.isDisplay(Element.TABLE_COLUMN_GROUP))
+    {
+      Element	parent =
+        elementStack.empty() ? null : (Element) elementStack.peek();
+
+      if
+      (
+        element.isDisplay(Element.TABLE_COLUMN)		&&
+        parent.isDisplay(Element.TABLE_COLUMN_GROUP)
+      )
+      {
+        parent.addChild(element);
+      }
+      else
+      {
+        if
+        (
+          element.isDisplay(Element.TABLE_ROW)	&&
+          parent.isDisplay(Element.TABLE)
+        )
+        {
+          synthesizeGroup();
+          parent = (Element) elementStack.peek();
+        }
+        else
+        {
+          if (isGroup(element) && parent.isDisplay(Element.TABLE_ROW_GROUP))
+          {
+            // Flush pending synthetic group.
+            elementStack.pop();
+            super.endElement(Constants.CSS, "synthetic", "css:synthetic");
+          }
+        }
+
+        element.atts =
+          element.isDisplay(Element.TABLE_CELL) ?
+            getTableCellAttributes(element, parent) :
+            (
+              element.isDisplay(Element.TABLE_ROW) && isGroup(parent) &&
+                parent.extra == null /* No row seen yet */ ?
+                getTableRowAttributes(atts, parent.atts) : atts
+            );
+
+        super.startElement(namespaceURI, localName, qName, element.atts);
+        bookKeeping(element, parent);
+      }
+    }
+
+    elementStack.push(element);
+  }
+
+
+
+  private void
+  synthesizeCells(Element element, Element parent) throws SAXException
+  {
+    if (element.extra != null)
+    {
+      List	columns = (List) columnStack.peek();
+      List	rowContributions = (List) parent.extra;
+        // From group parent.
+      int	contribution =
+        rowContributions.size() > 0 ?
+        //rowContributions != null && rowContributions.size() > 0 ?
+          ((Integer) rowContributions.get(0)).intValue() : 0;
+
+      if (rowContributions.size() > 0)
+      {
+        rowContributions.remove(0);
+      }
+
+      if (rowContributions.size() == 0) // No contributions left for next row.
+      {
+        rowContributions.add(new Integer(0));
+      }
+
+      if (((Integer) element.extra).intValue() + contribution < columns.size())
+      {
+        for
+        (
+          int i = ((Integer) element.extra).intValue();
+          i < columns.size();
+          ++i
+        )
+        {
+          super.startElement
+          (
+            Constants.CSS,
+            "synthetic",
+            "css:synthetic",
+            synthesizeColumnAttributes
+            (
+              (Attributes) columns.get(i),
+              "table-cell",
+              new String[] {"border*"}
+            )
+          );
+
+          super.endElement(Constants.CSS, "synthetic", "css:synthetic");
+        }
+      }
+    }
+  }
+
+
+
+  private static Attributes
+  synthesizeColumnAttributes
+  (
+    Attributes	source,
+    String	display,
+    String[]	include
+  )
+  {
+    AttributesImpl	atts = new AttributesImpl();
+
+    for (int i = 0; i < source.getLength(); ++i)
+    {
+      if (Util.inArray(include, source.getLocalName(i)))
+      {
+        atts.addAttribute
+        (
+          source.getURI(i),
+          source.getLocalName(i),
+          source.getQName(i),
+          source.getType(i),
+          source.getValue(i)
+        );
+      }
+    }
+
+    atts.
+      addAttribute(Constants.CSS, "display", "css:display", "CDATA", display);
+
+    return atts;
+  }
+
+
+
+  private void
+  synthesizeColumns(Element element) throws SAXException
+  {
+    if (element.children == null)
+    {
+      int	span = getSpan(element.atts, "span");
+
+      for (int i = 0; i < span; ++i)
+      {
+        Attributes	atts =
+          synthesizeColumnAttributes
+          (
+            element.atts,
+            "table-column",
+            i == 0 ?
+              // Take over the group border if needed.
+              new String[] {"border-left*", "text-align", "vertical-align"} :
+              (
+                i == span - 1 ?
+                  new String[]
+                    {"border-right*", "text-align", "vertical-align"} :
+                  new String[] {"text-align", "vertical-align"}
+              )
+          );
+
+        addColumn(atts);
+        super.startElement(Constants.CSS, "synthetic", "css:synthetic", atts);
+        super.endElement(Constants.CSS, "synthetic", "css:synthetic");
+      }
+    }
+    else
+    {
+      for (int i = 0; i < element.children.size(); ++i)
+      {
+        Element		child = (Element) element.children.get(i);
+        Attributes	atts =
+          Util.mergeAttributes
+          (
+            element.atts,
+            child.atts,
+            i == 0 ?
+              // Take over the group border if needed.
+              new String[] {"border-left*", "text-align", "vertical-align"} :
+              (
+                i == element.children.size() - 1 ?
+                  new String[]
+                    {"border-right*", "text-align", "vertical-align"} :
+                  new String[] {"text-align", "vertical-align"}
+              ),
+            true
+          );
+
+        addColumn(atts);
+
+        super.startElement
+        (
+          child.namespaceURI,
+          child.localName,
+          child.qName,
+          atts
+        );
+
+        super.endElement(child.namespaceURI, child.localName, child.qName);
+      }
+    }
+  }
+
+
+
+  private void
+  synthesizeGroup() throws SAXException
+  {
+    AttributesImpl	atts = new AttributesImpl();
+
+    atts.addAttribute
+    (
+      Constants.CSS,
+      "display",
+      "css:display",
+      "CDATA",
+      "table-row-group"
+    );
+
+    super.startElement(Constants.CSS, "synthetic", "css:synthetic", atts);
+
+    elementStack.push
+    (
+      new Element(Constants.CSS, "synthetic", "css:synthetic", atts)
+    );
+  }
+
+} // NormalizeTableFilter
diff --git a/src/be/re/css/PageRule.java b/src/be/re/css/PageRule.java
new file mode 100644
index 0000000..b3f81d2
--- /dev/null
+++ b/src/be/re/css/PageRule.java
@@ -0,0 +1,99 @@
+package be.re.css;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+
+/**
+ * Represents one CSS2 @page rule.
+ * @author Werner Donn\u00e9
+ */
+
+class PageRule
+
+{
+
+  private int		position;
+  private List		properties = new ArrayList();
+  private String	name;
+
+
+
+  PageRule(String name, int position)
+  {
+    this.name = name;
+    this.position = position;
+  }
+
+
+
+  void
+  addProperty(Property property)
+  {
+    properties.add(property);
+  }
+
+
+
+  String
+  getName()
+  {
+    return name;
+  }
+
+
+
+  int
+  getPosition()
+  {
+    return position;
+  }
+
+
+
+  Property[]
+  getProperties()
+  {
+    return (Property[]) properties.toArray(new Property[properties.size()]);
+  }
+
+
+
+  void
+  setProperty(Property property)
+  {
+    for (Iterator i = properties.iterator(); i.hasNext();)
+    {
+      if (((Property) i.next()).getName().equals(property.getName()))
+      {
+        i.remove();
+      }
+    }
+
+    properties.add(property);
+  }
+
+
+
+  /**
+   * Splits this rule into a set of equivalent rules in which there is only one
+   * property. For each property of this rule there will be a new one.
+   */
+
+  PageRule[]
+  split()
+  {
+    PageRule[]      result = new PageRule[getProperties().length];
+
+    for (int i = 0; i < result.length; ++i)
+    {
+      result[i] = new PageRule(getName(), getPosition());
+      result[i].addProperty(getProperties()[i]);
+    }
+
+    return result;
+  }
+
+} // PageRule
diff --git a/src/be/re/css/PageSetupFilter.java b/src/be/re/css/PageSetupFilter.java
new file mode 100644
index 0000000..e49dc2b
--- /dev/null
+++ b/src/be/re/css/PageSetupFilter.java
@@ -0,0 +1,1276 @@
+package be.re.css;
+
+import be.re.xml.DOMToContentHandler;
+import be.re.xml.sax.GobbleDocumentEvents;
+import be.re.xml.sax.FilterOfFilters;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TreeSet;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * This filter produces the page setup, taking into account named pages.
+ * Named page properties will be considered inside a body region element on
+ * blocks and outer tables.
+ * @author Werner Donn\u00e9
+ */
+
+class PageSetupFilter extends XMLFilterImpl
+
+{
+
+  private static final String		DEFAULT_COLUMN_COUNT = "1";
+  private static final String		DEFAULT_REGION_HEIGHT = "10mm";
+  private static final String		DEFAULT_REGION_WIDTH = "20mm";
+
+  private static final String[][]	pageInheritanceTable =
+    {
+      {"first-left-named", "unnamed", "left", "first", "named"},
+      {"first-right-named", "unnamed", "right", "first", "named"},
+      {"last-left-named", "unnamed", "left", "last", "named"},
+      {"last-right-named", "unnamed", "right", "last", "named"},
+      {"left-named", "unnamed", "left", "named"},
+      {"right-named", "unnamed", "right", "named"},
+      {"blank-left-named", "unnamed", "left", "blank", "named"},
+      {"blank-right-named", "unnamed", "right", "blank", "named"}
+    };
+  private static final String[]		prefixes =
+      new String[]
+      {
+        "first-left-", "first-right-", "blank-left-", "blank-right-", "first-",
+          "blank-", "left-", "right-", "last-left-", "last-right-", "last-"
+      };
+  private static final String[][]	regionInheritanceTable =
+    {
+      {
+        "first-left-named", "first-named", "first-left", "first", "left-named",
+          "left", "named", "unnamed"
+      },
+      {
+        "first-right-named", "first-named", "first-right", "first",
+          "right-named", "right", "named", "unnamed"
+      },
+      {
+        "last-left-named", "last-named", "last-left", "last", "left-named",
+          "left", "named", "unnamed"
+      },
+      {
+        "last-right-named", "last-named", "last-right", "last", "right-named",
+          "right", "named", "unnamed"
+      },
+      {"left-named", "left", "named", "unnamed"},
+      {"right-named", "right", "named", "unnamed"},
+      {
+        "blank-left-named", "blank-named", "blank-left", "blank", "left-named",
+          "left", "named", "unnamed"
+      },
+      {
+        "blank-right-named", "blank-named", "blank-right", "blank",
+          "right-named", "right", "named", "unnamed"
+      }
+    };
+
+  private URL		baseUrl;
+  private Context	context;
+  private boolean	debug;
+  private Stack		elements = new Stack();
+  private PageRule[]	resolvedPageRules;
+  private Map		userAgentParameters;
+
+
+
+  PageSetupFilter
+  (
+    Context	context,
+    URL		baseUrl,
+    Map		userAgentParameters,
+    boolean	debug
+  )
+  {
+    this.context = context;
+    this.baseUrl = baseUrl;
+    this.userAgentParameters = userAgentParameters;
+    this.debug = debug;
+  }
+
+
+
+  PageSetupFilter
+  (
+    Context	context,
+    URL		baseUrl,
+    Map		userAgentParameters,
+    XMLReader	parent,
+    boolean	debug
+  )
+  {
+    super(parent);
+    this.context = context;
+    this.baseUrl = baseUrl;
+    this.userAgentParameters = userAgentParameters;
+    this.debug = debug;
+  }
+
+
+
+  private static List
+  addUnnamedPageRule(List pageRules)
+  {
+    boolean	unnamedPresent = false;
+
+    for (Iterator i = pageRules.iterator(); i.hasNext() && !unnamedPresent;)
+    {
+      if (((PageRule) i.next()).getName().equals("unnamed"))
+      {
+        unnamedPresent = true;
+      }
+    }
+
+    if (unnamedPresent)
+    {
+      return pageRules;
+    }
+
+    PageRule	rule = new PageRule("unnamed", 0);
+
+    rule.addProperty(new Property("size", "portrait", false, null));
+    pageRules.add(rule);
+
+    return pageRules;
+  }
+
+
+
+  private PageRule[]
+  applyPageRules() throws SAXException
+  {
+    if (context.pageRules.size() == 0)
+    {
+      return new PageRule[0];
+    }
+
+    PageRule[]	pageRules =
+      recomposePageRules
+      (
+        sortPageRules
+        (
+          (PageRule[])
+            addUnnamedPageRule(context.pageRules).toArray(new PageRule[0])
+        )
+      );
+
+    super.
+      startElement(Constants.CSS, "pages", "css:pages", new AttributesImpl());
+
+    String[]	names = getPageRuleNames(pageRules);
+
+    for (int i = 0; i < names.length; ++i)
+    {
+      generatePage
+      (
+        getPageAttributes
+        (
+          pageRules,
+          names[i],
+          getInheritanceTableEntry(pageInheritanceTable, names[i])
+        )
+      );
+    }
+
+    super.endElement(Constants.CSS, "pages", "css:pages");
+
+    return pageRules;
+  }
+
+
+
+  public void
+  characters(char[] ch, int start, int length) throws SAXException
+  {
+    if (shouldEmitContents())
+    {
+      super.characters(ch, start, length);
+    }
+  }
+
+
+
+  private boolean
+  closeElementsInBodyRegion() throws SAXException
+  {
+    boolean	closed = false;
+
+    for
+    (
+      int i = elements.size() - 1;
+      i >= 0 && ((Element) elements.get(i)).inBodyRegion;
+      --i
+    )
+    {
+      Element	element = (Element) elements.get(i);
+
+      super.endElement(element.namespaceURI, element.localName, element.qName);
+      closed = true;
+    }
+
+    return closed;
+  }
+
+
+
+  private void
+  emitRegion(org.w3c.dom.Element region, String flowName)
+    throws SAXException
+  {
+    AttributesImpl	atts = new AttributesImpl();
+    XMLFilterImpl	filter =
+      new FilterOfFilters
+      (
+        new XMLFilter[]
+        {
+          Util.createPostProjectionFilter
+          (
+            baseUrl,
+            userAgentParameters,
+            debug
+          ).getFilter(),
+          new GobbleDocumentEvents()
+            // Give a chance for initialization, but don't interfere
+            // with the chain.
+        }
+      );
+
+    atts.addAttribute("", "flow-name", "flow-name", "CDATA", flowName);
+    filter.setContentHandler(getContentHandler());
+    filter.startDocument();
+
+    super.startElement
+    (
+      Constants.XSLFO,
+      "static-content",
+      "fo:static-content",
+      atts
+    );
+
+    DOMToContentHandler.
+      elementToContentHandler(removeWidthAndHeight(region), filter);
+    filter.endDocument();
+    super.endElement(Constants.XSLFO, "static-content", "fo:static-content");
+  }
+
+
+
+  public void
+  endDocument() throws SAXException
+  {
+    endPrefixMapping("fo");
+    endPrefixMapping("css");
+    endPrefixMapping("xh");
+    endPrefixMapping("sp");
+    super.endDocument();
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    Element	element = (Element) elements.pop();
+
+    if (element.inBodyRegion)
+    {
+      super.endElement(namespaceURI, localName, qName);
+
+      if (elements.isEmpty() || !((Element) elements.peek()).inBodyRegion)
+      {
+        super.startElement
+        (
+          Constants.CSS,
+          "last-page-mark", 
+          "css:last-page-mark",
+          new AttributesImpl()
+        );
+
+        super.endElement(Constants.CSS, "last-page-mark", "css:last-page-mark");
+        super.endElement(Constants.CSS, "page-sequence", "css:page-sequence");
+      }
+      else
+      {
+        if (element.span && closeElementsInBodyRegion())
+        {
+          reopenElementsInBodyRegion(false);
+        }
+      }
+    }
+
+    if (elements.isEmpty())
+    {
+      super.endElement(Constants.CSS, "root", "css:root");
+    }
+  }
+
+
+
+  private static String
+  extractPseudoPrefix(String pageName)
+  {
+    for (int i = 0; i < prefixes.length; ++i)
+    {
+      if (pageName.startsWith(prefixes[i]))
+      {
+        return prefixes[i];
+      }
+    }
+
+    return "";
+  }
+
+
+
+  private void
+  generateBodyRegionExtent(Attributes pageAtts, AttributesImpl regionAtts)
+    throws SAXException
+  {
+    String	columnCount = pageAtts.getValue(Constants.CSS, "column-count");
+    String	columnGap = pageAtts.getValue(Constants.CSS, "column-gap");
+
+    if (columnCount == null)
+    {
+      columnCount = (String) userAgentParameters.get("column-count");
+    }
+
+    if (columnCount != null)
+    {
+      regionAtts.
+        addAttribute("", "column-count", "columnt-count", "CDATA", columnCount);
+    }
+
+    if (columnGap != null)
+    {
+      regionAtts.
+        addAttribute("", "column-gap", "columnt-gap", "CDATA", columnGap);
+    }
+
+    super.startElement
+    (
+      Constants.XSLFO,
+      "region-body",
+      "fo:region-body",
+      regionAtts
+    );
+
+    super.endElement(Constants.XSLFO, "region-body", "fo:region-body");
+  }
+
+
+
+  private void
+  generatePage(Attributes attributes) throws SAXException
+  {
+    AttributesImpl	atts = new AttributesImpl(attributes);
+    AttributesImpl	bodyRegionAttributes = new AttributesImpl();
+
+    moveBodyProperties(atts, bodyRegionAttributes);
+    super.startElement(Constants.CSS, "page", "css:page", atts);
+
+    List		extents = new ArrayList();
+    Set			generated = new HashSet();
+    String		name = atts.getValue(Constants.CSS, "name");
+    String[]		inheritedPages =
+      getInheritanceTableEntry(regionInheritanceTable, name);
+    String[]		pages = new String[inheritedPages.length + 1];
+    String[]		regionNames =
+      new String[]{"top", "bottom", "left", "right"};
+
+    System.arraycopy(inheritedPages, 0, pages, 1, inheritedPages.length);
+    pages[0] = name;
+
+    for (int i = 0; i < pages.length; ++i)
+    {
+      Map	regions = (Map) context.regions.get(pages[i]);
+
+      if (regions != null)
+      {
+        for (int j = 0; j < regionNames.length; ++j)
+        {
+          if (!generated.contains(regionNames[j]))
+          {
+            org.w3c.dom.Element	region =
+              (org.w3c.dom.Element) regions.get(regionNames[j]);
+
+            if (region != null)
+            {
+              generated.add(regionNames[j]);
+
+              String	extent;
+
+              if
+              (
+                "top".equals(regionNames[j])	||
+                "bottom".equals(regionNames[j])
+              )
+              {
+                extent = region.getAttributeNS(Constants.CSS, "height");
+
+                if (extent.equals(""))
+                {
+                  extent = DEFAULT_REGION_HEIGHT;
+                }
+              }
+              else
+              {
+                extent = region.getAttributeNS(Constants.CSS, "width");
+
+                if (extent.equals(""))
+                {
+                  extent = DEFAULT_REGION_WIDTH;
+                }
+              }
+
+              bodyRegionAttributes.addAttribute
+              (
+                "",
+                "margin-" + regionNames[j],
+                "margin-" + regionNames[j],
+                "CDATA",
+                extent
+              );
+
+              extents.add
+              (
+                generateRegionExtent(region, name, regionNames[j], extent)
+              );
+            }
+          }
+        }
+      }
+    }
+
+    // The region order matters.
+
+    generateBodyRegionExtent(atts, bodyRegionAttributes);
+
+    for (Iterator j = sortExtents(extents).iterator(); j.hasNext();)
+    {
+      DOMToContentHandler.elementToContentHandler
+      (
+        (org.w3c.dom.Element) j.next(),
+        getContentHandler()
+      );
+    }
+
+    super.endElement(Constants.CSS, "page", "css:page");
+  }
+
+
+
+  private org.w3c.dom.Element
+  generateRegionExtent
+  (
+    org.w3c.dom.Element	region,
+    String		page,
+    String		name,
+    String		extent
+  ) throws SAXException
+  {
+    String		element =
+      "top".equals(name) ?
+        "region-before" :
+        (
+          "bottom".equals(name) ?
+            "region-after" :
+            ("left".equals(name) ? "region-start" : "region-end")
+        );
+    String		precedence =
+      region.getAttributeNS(Constants.CSS, "precedence");
+    org.w3c.dom.Element	result =
+      region.getOwnerDocument().
+        createElementNS(Constants.XSLFO, "fo:" + element);
+
+    if (!precedence.equals(""))
+    {
+      result.setAttribute("precedence", precedence);
+    }
+
+    result.setAttribute("region-name", page + "-" + name);
+    result.setAttribute("extent", extent);
+
+    return result;
+  }
+
+
+
+  private void
+  generateRegions(String page) throws SAXException
+  {
+    super.startElement
+    (
+      Constants.CSS,
+      "regions",
+      "css:regions",
+      new AttributesImpl()
+    );
+
+    if (context.regions.size() > 0)
+    {
+      Set	generated = new HashSet();
+      String[]	regionNames = new String[]{"top", "bottom", "left", "right"};
+
+      for (int i = 0; i < regionInheritanceTable.length; ++i)
+      {
+        for (int j = 0; j < regionInheritanceTable[i].length; ++j)
+        {
+          String	specificPage =
+            getSpecificPageName(regionInheritanceTable[i][j], page);
+          Map		regions = (Map) context.regions.get(specificPage);
+
+          if (regions != null)
+          {
+            for (int k = 0; k < regionNames.length; ++k)
+            {
+              String	flowName =
+                getSpecificPageName(regionInheritanceTable[i][0], page) + "-" +
+                  regionNames[k];
+
+              if (!generated.contains(flowName))
+              {
+                org.w3c.dom.Element	region =
+                  (org.w3c.dom.Element) regions.get(regionNames[k]);
+  
+                if (region != null)
+                {
+                  generated.add(flowName);
+                  emitRegion(region, flowName);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    super.endElement(Constants.CSS, "regions", "css:regions");
+  }
+
+
+
+  private static String[]
+  getInheritanceTableEntry(String[][] table, String name)
+  {
+    String	symbolic = extractPseudoPrefix(name) + "named";
+    String	unprefixed = stripPseudoPrefix(name);
+
+    for (int i = 0; i < table.length; ++i)
+    {
+      if (table[i][0].equals(symbolic))
+      {
+        List	result = new ArrayList();
+
+        for (int j = 0; j < table[i].length - 1; ++j)
+        {
+          int	index =
+            table[i][j + 1].equals("unnamed") ?
+              -1 : table[i][j + 1].indexOf("named");
+
+          if (!unprefixed.equals("unnamed") || !table[i][j + 1].equals("named"))
+            // The named page "unnamed" doesn't exist.
+          {
+            result.add
+            (
+              index != -1 ?
+                (table[i][j + 1].substring(0, index) + unprefixed) :
+                table[i][j + 1]
+            );
+          }
+        }
+
+        return (String[]) result.toArray(new String[0]);
+      }
+    }
+
+    return new String[0];
+  }
+
+
+
+  /**
+   * The page properties are determined in the order of <code>names</code>.
+   * A successor overrides the values of its predecessors. This implements the
+   * cascade.
+   */
+
+  private Attributes
+  getPageAttributes(PageRule[] pageRules, String pageName, String[] names)
+  {
+    AttributesImpl	result = new AttributesImpl();
+
+    result.addAttribute(Constants.CSS, "name", "css:name", "CDATA", pageName);
+
+    for (int i = 0; i < names.length; ++i)
+    {
+      for (int j = 0; j < pageRules.length; ++j)
+      {
+        if (names[i].equals(pageRules[j].getName()))
+        {
+          Property[]	properties = pageRules[j].getProperties();
+
+          for (int k = 0; k < properties.length; ++k)
+          {
+            Util.setCSSAttribute(result, properties[k], -1);
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+
+
+  /**
+   * The method expands all the style sheet specified page names into first,
+   * last, left, right, blank and any variants, which go in the repeatable page
+   * masters. Only the most precise page names remain.
+   */
+
+  private static String[]
+  getPageRuleNames(PageRule[] rules)
+  {
+    Set	result = new HashSet();
+
+    for (int i = 0; i < rules.length; ++i)
+    {
+      String	stripped = stripPseudoPrefix(rules[i].getName());
+
+      if (!isPseudoPageName(rules[i].getName()))
+      {
+        result.add("first-left-" + stripped);
+        result.add("first-right-" + stripped);
+        result.add("last-left-" + stripped);
+        result.add("last-right-" + stripped);
+        result.add("blank-left-" + stripped);
+        result.add("blank-right-" + stripped);
+        result.add("left-" + stripped);
+        result.add("right-" + stripped);
+      }
+    }
+
+    result.remove("first");
+    result.remove("last");
+    result.remove("blank");
+    result.remove("left");
+    result.remove("right");
+    result.remove("first-left");
+    result.remove("first-right");
+    result.remove("last-left");
+    result.remove("last-right");
+    result.remove("blank-left");
+    result.remove("blank-right");
+    result.remove("unnamed");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+
+
+  private static String
+  getSpecificPageName(String symbolicName, String page)
+  {
+    return
+      symbolicName.indexOf("-named") != -1 ?
+        symbolicName.substring(0, symbolicName.indexOf("-named")) + "-" + page :
+        ("named".equals(symbolicName) ? page : symbolicName);
+  }
+
+
+
+  private static boolean
+  isPseudoPageName(String name)
+  {
+    return
+      Util.
+        inArray(new String[]{"first", "last", "left", "right", "blank"}, name);
+  }
+
+
+
+  private static void
+  moveBodyProperties(AttributesImpl pageAtts, AttributesImpl regionAtts)
+  {
+    for (int i = 0; i < pageAtts.getLength(); ++i)
+    {
+      if
+      (
+        Constants.CSS.equals(pageAtts.getURI(i))		&&
+        (
+          pageAtts.getLocalName(i).startsWith("background-")	||
+          pageAtts.getLocalName(i).startsWith("border-")	||
+          pageAtts.getLocalName(i).startsWith("padding-")
+        )
+      )
+      {
+        if (!pageAtts.getLocalName(i).endsWith(".conditionality"))
+        {
+          regionAtts.addAttribute
+          (
+            pageAtts.getURI(i),
+            pageAtts.getLocalName(i),
+            pageAtts.getQName(i),
+            pageAtts.getType(i),
+            pageAtts.getValue(i)
+          );
+        }
+
+        pageAtts.removeAttribute(i--);
+      }
+    }
+  }
+
+
+
+  public void
+  processingInstruction(String target, String data) throws SAXException
+  {
+    if (shouldEmitContents())
+    {
+      super.processingInstruction(target, data);
+    }
+  }
+
+
+
+  /**
+   * This performs the cascade.
+   */
+
+  private static PageRule[]
+  recomposePageRules(PageRule[] pageRules)
+  {
+    Map	result = new HashMap();
+
+    for (int i = 0; i < pageRules.length; ++i)
+    {
+      PageRule	pageRule = (PageRule) result.get(pageRules[i].getName());
+
+      if (pageRule == null)
+      {
+        pageRule = new PageRule(pageRules[i].getName(), 0);
+        result.put(pageRule.getName(), pageRule);
+      }
+
+      pageRule.setProperty(pageRules[i].getProperties()[0]);
+    }
+
+    return (PageRule[]) result.values().toArray(new PageRule[0]);
+  }
+
+
+
+  private static AttributesImpl
+  removeId(AttributesImpl atts)
+  {
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if ("ID".equals(atts.getType(i)))
+      {
+        atts.removeAttribute(i);
+
+        return atts;
+      }
+    }
+
+    return atts;
+  }
+
+
+
+  private static org.w3c.dom.Element
+  removeWidthAndHeight(org.w3c.dom.Element region)
+  {
+    org.w3c.dom.Element	result = (org.w3c.dom.Element) region.cloneNode(true);
+
+    result.removeAttributeNS(Constants.CSS, "width");
+    result.removeAttributeNS(Constants.CSS, "height");
+
+    return result;
+  }
+
+
+
+  private void
+  reopenElementsInBodyRegion(boolean span) throws SAXException
+  {
+    int	i;
+
+    for
+    (
+      i = elements.size() - 1;
+      i >= 0 && ((Element) elements.get(i)).inBodyRegion;
+      --i
+    );
+
+    if (((Element) elements.get(i + 1)).inBodyRegion)
+    {
+      for (int j = i + 1; j < elements.size(); ++j)
+      {
+        Element		element = (Element) elements.get(j);
+        AttributesImpl	atts = removeId(element.atts); // Avoid duplicate IDs.
+
+        if (span && j == i + 1)
+        {
+          atts = new AttributesImpl(atts);
+
+          atts.addAttribute
+          (
+            Constants.CSS,
+            "column-span",
+            "css:column-span",
+            "CDATA",
+            "all"
+          );
+        }
+
+        super.startElement
+        (
+          element.namespaceURI,
+          element.localName,
+          element.qName,
+          atts
+        );
+      }
+    }
+  }
+
+
+
+  void
+  setBaseUrl(URL baseUrl)
+  {
+    this.baseUrl = baseUrl;
+  }
+
+
+
+  private boolean
+  shouldEmitContents()
+  {
+    return ((Element) elements.peek()).inBodyRegion;
+  }
+
+
+
+  private static Collection
+  sortExtents(Collection extents)
+  {
+    Collection	result =
+      new TreeSet
+      (
+        new Comparator()
+        {
+          public int
+          compare(Object o1, Object o2)
+          {
+            String[]	ordered =
+              new String[]
+              {"region-before", "region-after", "region-start", "region-end"};
+
+            return
+              Util.indexOf(ordered, ((Node) o1).getLocalName()) -
+                Util.indexOf(ordered, ((Node) o2).getLocalName());
+          }
+        }
+      );
+
+    result.addAll(extents);
+
+    return result;
+  }
+
+
+
+  private static PageRule[]
+  sortPageRules(PageRule[] pageRules)
+  {
+    Arrays.sort
+    (
+      pageRules,
+      new Comparator()
+      {
+        public int
+        compare(Object object1, Object object2)
+        {
+          PageRule	rule1 = (PageRule) object1;
+          PageRule	rule2 = (PageRule) object2;
+          String	name1 = rule1.getName();
+          String	name2 = rule2.getName();
+          int		result =
+            !"unnamed".equals(name1) && "unnamed".equals(name2) ?
+              1 :
+              ("unnamed".equals(name1) && !"unnamed".equals(name2) ? -1 : 0);
+
+          if (result == 0)
+          {
+            result =
+              "first".equals(name1) && !"first".equals(name2) ?
+                1 : (!"first".equals(name1) && "first".equals(name2) ? -1 : 0);
+          }
+
+          if (result == 0)
+          {
+            result =
+              "last".equals(name1) && !"last".equals(name2) ?
+                1 : (!"last".equals(name1) && "last".equals(name2) ? -1 : 0);
+          }
+
+          if (result == 0)
+          {
+            result =
+              "left".equals(name1) && !"left".equals(name2) ?
+                1 : (!"left".equals(name1) && "left".equals(name2) ? -1 : 0);
+          }
+
+          if (result == 0)
+          {
+            result =
+              "right".equals(name1) && !"right".equals(name2) ?
+                1 : (!"right".equals(name1) && "right".equals(name2) ? -1 : 0);
+          }
+
+          if (result == 0)
+          {
+            result = rule1.getPosition() - rule2.getPosition();
+          }
+
+          return result;
+        }
+      }
+    );
+
+    return pageRules;
+  }
+
+
+
+  public void
+  startDocument() throws SAXException
+  {
+    super.startDocument();
+    startPrefixMapping("css", Constants.CSS);
+    startPrefixMapping("xh", Constants.XHTML);
+    startPrefixMapping("sp", Constants.SPECIF);
+    startPrefixMapping("fo", Constants.XSLFO);
+    getParent().setContentHandler(new Recorder());
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    String	display = atts.getValue(Constants.CSS, "display");
+    Element	element = new Element(namespaceURI, localName, qName, atts);
+    Element	parent = elements.isEmpty() ? null : (Element) elements.peek();
+
+    if (parent == null)
+    {
+      super.startElement(Constants.CSS, "root", "css:root", atts);
+    }
+
+    element.inBodyRegion =
+      (parent != null && parent.inBodyRegion) ||
+        "body".equals(atts.getValue(Constants.CSS, "region"));
+    element.inTable =
+      (parent != null && parent.inTable) || "table".equals(display);
+
+    if
+    (
+      element.inBodyRegion		&&
+      (
+        parent == null			||
+        (
+          !parent.inTable		&&
+          (
+            element.inTable		||
+            "block".equals(display)
+          )
+        )
+      )
+    )
+    {
+      int	span = element.atts.getIndex(Constants.CSS, "column-span");
+
+      if (span != -1)
+      {
+        element.span = "all".equals(element.atts.getValue(span));
+        element.atts.removeAttribute(span);
+      }
+
+      element.pageName = atts.getValue(Constants.CSS, "page");
+
+      if ("auto".equals(element.pageName))
+      {
+        element.pageName = null;
+      }
+
+      if (element.pageName == null && parent != null)
+      {
+        element.pageName = parent.pageName;
+      }
+
+      if (element.pageName == null)
+      {
+        element.pageName = "unnamed";
+      }
+
+      boolean	newPage =
+        parent == null || !element.pageName.equals(parent.pageName);
+
+      if (newPage || element.span)
+      {
+        boolean		closed = closeElementsInBodyRegion();
+
+        if (newPage)
+        {
+          if (parent != null && parent.pageName != null)
+            // There is an open page sequence.
+          {
+            super.
+              endElement(Constants.CSS, "page-sequence", "css:page-sequence");
+          }
+          else
+          {
+            // Pages go before the first page sequence.
+            resolvedPageRules = applyPageRules();
+          }
+
+          AttributesImpl	attributes = new AttributesImpl();
+
+          attributes.addAttribute
+          (
+            Constants.CSS,
+            "page",
+            "css:page",
+            "CDATA",
+            element.pageName
+          );
+
+          super.startElement
+          (
+            Constants.CSS,
+            "page-sequence",
+            "css:page-sequence",
+            attributes
+          );
+
+          generateRegions(element.pageName);
+        }
+
+        if (closed)
+        {
+          reopenElementsInBodyRegion(element.span);
+        }
+
+        if (parent != null)
+        {
+          parent.pageName = element.pageName;
+        }
+      }
+    }
+    else
+    {
+      if (parent != null)
+      {
+        element.pageName = parent.pageName;
+      }
+    }
+
+    if (element.inBodyRegion)
+    {
+      super.startElement(namespaceURI, localName, qName, element.atts);
+    }
+
+    elements.push(element);
+  }
+
+
+
+  private static String
+  stripPseudoPrefix(String pageName)
+  {
+    for (int i = 0; i < prefixes.length; ++i)
+    {
+      if (pageName.startsWith(prefixes[i]))
+      {
+        return pageName.substring(prefixes[i].length());
+      }
+    }
+
+    return pageName;
+  }
+
+
+
+  private static class Element
+
+  {
+
+    private AttributesImpl	atts;
+    private boolean		inBodyRegion;
+    private boolean		inTable;
+    private String		localName;
+    private String		namespaceURI;
+    private String		pageName;
+    private String		qName;
+    private boolean		span;
+
+
+    private
+    Element
+    (
+      String		namespaceURI,
+      String		localName,
+      String		qName,
+      Attributes	atts
+    )
+    {
+      this.namespaceURI = namespaceURI;
+      this.localName = localName;
+      this.qName = qName;
+      this.atts = new AttributesImpl(atts);
+    }
+
+  } // Element
+
+
+
+  private class Recorder extends XMLFilterImpl
+
+  {
+
+    private List	events = new ArrayList();
+    private Stack	elements = new Stack();
+
+
+
+    public void
+    endElement(String namespaceURI, String localName, String qName)
+      throws SAXException
+    {
+      elements.pop();
+      events.add(new Event(namespaceURI, localName, qName, null));
+    }
+
+
+
+    private void
+    replayEvents() throws SAXException
+    {
+      for (int i = 0; i < events.size(); ++i)
+      {
+        Event	event = (Event) events.get(i);
+
+        if (event.atts != null)
+        {
+          PageSetupFilter.this.startElement
+          (
+            event.namespaceURI,
+            event.localName,
+            event.qName,
+            event.atts
+          );
+        }
+        else
+        {
+          PageSetupFilter.this.
+            endElement(event.namespaceURI, event.localName, event.qName);
+        }
+      }
+    }
+
+
+
+    public void
+    startElement
+    (
+      String		namespaceURI,
+      String		localName,
+      String		qName,
+      Attributes	atts
+    ) throws SAXException
+    {
+      events.add(new Event(namespaceURI, localName, qName, atts));
+
+      if (!elements.isEmpty() && ((Element) elements.peek()).inBodyRegion)
+      {
+        replayEvents();
+        PageSetupFilter.this.getParent().
+          setContentHandler(PageSetupFilter.this);
+      }
+      else
+      {
+        Element	element = new Element(namespaceURI, localName, qName, atts);
+
+        element.inBodyRegion =
+          "body".equals(atts.getValue(Constants.CSS, "region"));
+        elements.push(element);
+      }
+    }
+
+
+
+    private class Event
+
+    {
+
+      private Attributes	atts;
+      private String		localName;
+      private String		namespaceURI;
+      private String		qName;
+
+
+
+      private
+      Event
+      (
+        String		namespaceURI,
+        String		localName,
+        String		qName,
+        Attributes	atts
+      )
+      {
+        this.namespaceURI = namespaceURI;
+        this.localName = localName;
+        this.qName = qName;
+        this.atts = (atts == null ? null : new AttributesImpl(atts));
+      }
+
+    } // Event
+
+  } // Recorder
+
+} // PageSetupFilter
diff --git a/src/be/re/css/ProjectorFilter.java b/src/be/re/css/ProjectorFilter.java
new file mode 100644
index 0000000..e5968d8
--- /dev/null
+++ b/src/be/re/css/ProjectorFilter.java
@@ -0,0 +1,2264 @@
+package be.re.css;
+
+import be.re.xml.Accumulator;
+import be.re.xml.DOMToContentHandler;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import java.util.StringTokenizer;
+import org.w3c.css.sac.CSSException;
+import org.w3c.css.sac.InputSource;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.Parser;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * This class implements the CSS cascading mechanism. It projects the CSS
+ * properties onto the elements in the source document according to the
+ * CSS stylesheet. They are represented as attributes in a distinct namespace.
+ * An external stylesheet can be specified with the processing instruction
+ * which is proposed in section 2.2 of the CSS2 specification.
+ *
+ * Only properties from the "all" and "print" media are considered, besides the
+ * media-neutral ones.
+ *
+ * The filter works for any XML vocabulary, but special measures have been taken
+ * for the XHTML namespace. The style attribute, for example, is recognised
+ * and applied. The XHTML methods of specifying stylesheets, such as the
+ * link and style elements, are also honored.
+ *
+ * All shorthand properties are split into their constituting parts. This makes
+ * it easier to write an XSLT stylesheet which transforms the result of this
+ * class into XSL-FO.
+ * @author Werner Donn\u00e9
+ */
+
+class ProjectorFilter extends XMLFilterImpl
+
+{
+
+  private static final String	DEFAULT_CLOSE_QUOTE = "\"";
+  private static final String	DEFAULT_OPEN_QUOTE = "\"";
+
+  private static final String	AFTER = "after".intern();
+  private static final String	BASE = "base".intern();
+  private static final String	BEFORE = "before".intern();
+  private static final String	BODY = "body".intern();
+  private static final String	FIRST_LETTER = "first-letter".intern();
+  private static final String	FIRST_LINE = "first-line".intern();
+  private static final String	LINK = "link".intern();
+  private static final String	STYLE = "style".intern();
+
+  private static final String[][]	pageFormatTable =
+    {
+      {"armenian", "&#x0561;"},
+      {"decimal", "1"},
+      {"decimal-leading-zero", "01"},
+      {"georgian", "&#x10D0;"},
+      {"hebrew", "&#x05D0;"},
+      {"hiragana", "&#x3042;"},
+      {"hiragana-iroha", "&#x3044;"},
+      {"katakana", "&#x30A2;"},
+      {"katakana-iroha", "&#x30A4;"},
+      {"lower-alpha", "a"},
+      {"lower-greek", "&#x03B1;"},
+      {"lower-latin", "a"},
+      {"lower-roman", "i"},
+      {"upper-alpha", "A"},
+      {"upper-latin", "A"},
+      {"upper-roman", "I"},
+    };
+
+  private URL		baseUrl = null;
+  private boolean	collectStyleSheet = false;
+  private Compiled	compiled = new Compiled();
+  private Context	context;
+  private Stack		counters = new Stack();
+  private Stack		elements = new Stack();
+  private String	embeddedStyleSheet = "";
+  private int		lastRulePosition = 0;
+  private Matcher	matcher = null;
+  private Stack		namedStrings = new Stack();
+  private int		quoteDepth = 0;
+    // Filter state because quotes can match across the hole document.
+  private Map		userAgentParameters;
+  private URL		userAgentStyleSheet = null;
+
+
+
+  ProjectorFilter(Context context)
+  {
+    this(null, null, new HashMap(), context);
+  }
+
+
+
+  ProjectorFilter
+  (
+    URL		baseUrl,
+    URL		userAgentStyleSheet,
+    Map		userAgentParameters,
+    Context	context
+  )
+  {
+    this.baseUrl = baseUrl;
+    this.userAgentStyleSheet =
+      userAgentStyleSheet != null ?
+        userAgentStyleSheet : getClass().getResource("style/ua.css");
+    this.userAgentParameters = userAgentParameters;
+    this.context = context;
+  }
+
+
+
+  private static void
+  addFOMarker(Node parent, String name, String value)
+  {
+    org.w3c.dom.Element	element =
+      parent.getOwnerDocument().createElementNS(Constants.CSS, "css:fo-marker");
+
+    element.appendChild(parent.getOwnerDocument().createTextNode(value));
+    element.setAttributeNS(Constants.CSS, "css:name", name);
+    parent.insertBefore(element, parent.getFirstChild());
+  }
+
+
+
+  private Rule[]
+  appendStyleAttributeRules
+  (
+    Rule[]	matchingRules,
+    Attributes	atts,
+    String	namespaceURI
+  ) throws SAXException
+  {
+    if (Constants.XHTML != namespaceURI)
+    {
+      return matchingRules;
+    }
+
+    String	style = atts.getValue("style");
+
+    if (style == null || "".equals(style))
+    {
+      return matchingRules;
+    }
+
+    try
+    {
+      Rule[]	rules = getStyleAttributeRules(style);
+      Rule[]	result = new Rule[matchingRules.length + rules.length];
+
+      System.arraycopy(matchingRules, 0, result, 0, matchingRules.length);
+      System.arraycopy(rules, 0, result, matchingRules.length, rules.length);
+
+      return result;
+    }
+
+    catch (IOException e)
+    {
+      throw new SAXException(e);
+    }
+  }
+
+
+
+  private void
+  applyContentProperty(Element element, Rule[] pseudoRules) throws SAXException
+  {
+    boolean	seen = false;
+
+    // From most to least specific.
+
+    for (int i = pseudoRules.length - 1; i >= 0 && !seen; --i)
+    {
+      Property[]	properties = pseudoRules[i].getProperties();
+
+      for (int j = 0; j < properties.length && !seen; ++j)
+      {
+        if
+        (
+          "content".equals(properties[j].getName())			&&
+          properties[j].getLexicalUnit().getLexicalUnitType() !=
+            LexicalUnit.SAC_INHERIT
+        )
+        {
+          seen = true;
+
+          for
+          (
+            LexicalUnit k = properties[j].getLexicalUnit();
+            k != null;
+            k = k.getNextLexicalUnit()
+          )
+          {
+            switch (k.getLexicalUnitType())
+            {
+              case LexicalUnit.SAC_ATTR:
+                serializeAttrFunction(k, element, properties[j].getPrefixMap());
+                break;
+
+              case LexicalUnit.SAC_COUNTER_FUNCTION:
+                serializeCounterFunction(k);
+                break;
+
+              case LexicalUnit.SAC_COUNTERS_FUNCTION:
+                serializeCountersFunction(k);
+                break;
+
+              case LexicalUnit.SAC_FUNCTION:
+                serializeFunction(k, element, properties[j].getPrefixMap());
+                break;
+
+              case LexicalUnit.SAC_IDENT:
+                serializeQuote(k, element);
+                break;
+
+              case LexicalUnit.SAC_STRING_VALUE:
+                serializeString(k.getStringValue());
+                break;
+
+              case LexicalUnit.SAC_URI:
+                serializeUriFunction(k);
+                break;
+
+              default:
+                break;
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+
+  private void
+  addFirstLetterMarker(Element element)
+  {
+    Rule[]	pseudoRules =
+      selectPseudoRules(element.matchingPseudoRules, FIRST_LETTER);
+
+    if (pseudoRules.length > 0)
+    {
+      element.appliedAttributes.addAttribute
+      (
+        Constants.CSS,
+        "has-first-letter",
+        "css:has-first-letter",
+        "CDATA",
+        "1"
+      );
+    }
+  }
+
+
+
+  private void
+  applyPseudoRules(Element element, String name)
+    throws SAXException
+  {
+    Rule[]	pseudoRules =
+      selectPseudoRules(element.matchingPseudoRules, name);
+
+    if (pseudoRules.length > 0)
+    {
+      AttributesImpl	extra = new AttributesImpl();
+
+      if (AFTER == name || BEFORE == name)
+      {
+        extra.addAttribute
+        (
+          Constants.CSS,
+          "display",
+          "css:display",
+          "CDATA",
+          "inline"
+        );
+      }
+
+      AttributesImpl	attributes = setCSSAttributes(pseudoRules, extra);
+      AttributesImpl	changeBarAttributes =
+        splitChangeBarAttributes(attributes);
+
+      if (BEFORE == name)
+      {
+        serializeChangeBarBegin(changeBarAttributes);
+      }
+
+      super.startElement(Constants.CSS, name, "css:" + name, attributes);
+
+      if (AFTER == name || BEFORE == name)
+      {
+        serializeFOMarkers(pseudoRules);
+        applyContentProperty(element, pseudoRules);
+      }
+
+      super.endElement(Constants.CSS, name, "css:" + name);
+
+      if (AFTER == name)
+      {
+        serializeChangeBarEnd(changeBarAttributes);
+      }
+    }
+  }
+
+
+
+  public void
+  characters(char[] ch, int start, int length) throws SAXException
+  {
+    if (collectStyleSheet)
+    {
+      embeddedStyleSheet += new String(ch, start, length);
+    }
+
+    super.characters(ch, start, length);
+  }
+
+
+
+  private static String
+  convertPageFormat(String listStyle)
+  {
+    for (int i = 0; i < pageFormatTable.length; ++i)
+    {
+      if (listStyle.equals(pageFormatTable[i][0]))
+      {
+        return pageFormatTable[i][1];
+      }
+    }
+
+    return "1"; // Decimal is the default.
+  }
+
+
+
+  private static void
+  detectMarkers(Element element)
+  {
+    boolean	hasMarkers = false;
+
+    for (int i = 0; i < element.matchingPseudoRules.length && !hasMarkers; ++i)
+    {
+      hasMarkers =
+        (
+          element.matchingPseudoRules[i].getPseudoElementName() == BEFORE ||
+            element.matchingPseudoRules[i].getPseudoElementName() == AFTER
+        ) &&
+          "display".equals
+          (
+            element.matchingPseudoRules[i].getProperty().getName()
+          ) &&
+          "marker".equals
+          (
+            element.matchingPseudoRules[i].getProperty().getValue()
+          );
+    }
+
+    if (hasMarkers)
+    {
+      element.appliedAttributes.addAttribute
+      (
+        Constants.CSS,
+        "has-markers",
+        "css:has-markers",
+        "CDATA",
+        "1"
+      );
+    }
+  }
+
+
+
+  public void
+  endDocument() throws SAXException
+  {
+    endPrefixMapping("css");
+    endPrefixMapping("sp");
+    super.endDocument();
+    matcher.endDocument();
+    reset();
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    counters.pop();
+    namedStrings.pop();
+    matcher.endElement(namespaceURI, localName, qName);
+
+    if (collectStyleSheet)
+    {
+      collectStyleSheet = false;
+
+      try
+      {
+        parseStyleSheet(new StringReader(embeddedStyleSheet), 0, true);
+      }
+
+      catch (IOException e)
+      {
+        throw new SAXException(e);
+      }
+
+      embeddedStyleSheet = "";
+    }
+
+    Element	element = (Element) elements.pop();
+
+    applyPseudoRules(element, AFTER);
+    super.endElement(element.namespaceURI, element.localName, element.qName);
+    element.matchingElementRules = null;
+    element.matchingPseudoRules = null;
+    element.appliedAttributes = null;
+
+    if (element.floating)
+    {
+      super.endElement(Constants.CSS, "float", "css:float");
+    }
+  }
+
+
+
+  private static String
+  evaluateAttrFunction
+  (
+    LexicalUnit	function,
+    Attributes	attributes,
+    Map		prefixMap
+  )
+  {
+    if (function.getStringValue() == null)
+    {
+      return "";
+    }
+
+    String	attribute = function.getStringValue();
+    int		index = attribute.indexOf('|');
+    String	value =
+      index == -1 ?
+        attributes.getValue(attribute) :
+        attributes.getValue
+        (
+          (String) prefixMap.get(attribute.substring(0, index)),
+          attribute.substring(index + 1)
+        );
+
+    return value != null ? value : "";
+  }
+
+
+
+  private String
+  evaluateCounterFunction(LexicalUnit function)
+  {
+    if (function.getParameters() == null)
+    {
+      return "";
+    }
+
+    String	counter = function.getParameters().getStringValue();
+
+    if (counter == null)
+    {
+      return "";
+    }
+
+    Integer	value =
+      (Integer) findCounterScope(counter).get(counter.toLowerCase());
+    String	listStyle = getCounterListStyle(function);
+
+    return
+      value != null && !"none".equalsIgnoreCase(listStyle) &&
+        !"inherit".equalsIgnoreCase(listStyle) ?
+        getCounterString(value.intValue(), listStyle) : "";
+  }
+
+
+
+  private String
+  evaluateCountersFunction(LexicalUnit function) throws SAXException
+  {
+    if (function.getParameters() == null)
+    {
+      return "";
+    }
+
+    String	counter = function.getParameters().getStringValue();
+
+    if (counter == null)
+    {
+      return "";
+    }
+
+    LexicalUnit	parameter =
+      function.getParameters().getNextLexicalUnit().getNextLexicalUnit();
+
+    if (parameter == null)
+    {
+      return "";
+    }
+
+    String	separator = parameter.getStringValue();
+
+    if (separator == null)
+    {
+      return "";
+    }
+
+    String	listStyle = getCountersListStyle(parameter);
+
+    return
+      !"none".equalsIgnoreCase(listStyle) &&
+        !"inherit".equalsIgnoreCase(listStyle) ?
+        getCountersString(counter.toLowerCase(), separator, listStyle) : "";
+  }
+
+
+
+  private static String
+  evaluateQuote(LexicalUnit quote, Element element, int[] quoteDepth)
+  {
+    if (quote.getStringValue().equals("open-quote"))
+    {
+      String	s = selectOpenQuote(element, quoteDepth[0]);
+
+      ++quoteDepth[0];
+
+      return s;
+    }
+
+    if (quote.getStringValue().equals("close-quote"))
+    {
+      --quoteDepth[0];
+
+      String	s = selectCloseQuote(element, quoteDepth[0]);
+
+      return s;
+    }
+
+    if (quote.getStringValue().equals("no-open-quote"))
+    {
+      ++quoteDepth[0];
+    }
+    else
+    {
+      if (quote.getStringValue().equals("no-close-quote"))
+      {
+        --quoteDepth[0];
+      }
+    }
+
+    return "";
+  }
+
+
+
+  private String
+  evaluateStringFunction(LexicalUnit function) throws SAXException
+  {
+    if (function.getParameters() == null)
+    {
+      return "";
+    }
+
+    String	name = function.getParameters().getStringValue().toLowerCase();
+    String	value = (String) findNamedStringScope(name).get(name);
+
+    return value != null ? value : "";
+  }
+
+
+
+  private Map
+  findCounterScope(String counter)
+  {
+    return findScope(counters, counter, new Integer(0));
+  }
+
+
+
+  private Map
+  findNamedStringScope(String namedString)
+  {
+    return findScope(namedStrings, namedString, "");
+  }
+
+
+
+  private static Map
+  findScope(Stack scopes, String item, Object defaultValue)
+  {
+    for (int i = scopes.size() - 1; i >= 0; --i)
+    {
+      if (((Map) scopes.get(i)).containsKey(item))
+      {
+        return (Map) scopes.get(i);
+      }
+    }
+
+    // The highest scope is the default scope.
+
+    ((Map) scopes.get(0)).put(item, defaultValue);
+
+    return (Map) scopes.get(0);
+  }
+
+
+
+  public URL
+  getBaseUrl()
+  {
+    return baseUrl;
+  }
+
+
+
+  private static String
+  getCounterListStyle(LexicalUnit function)
+  {
+    return
+      function.getParameters().getNextLexicalUnit() != null &&
+        function.getParameters().getNextLexicalUnit().getLexicalUnitType() ==
+          LexicalUnit.SAC_OPERATOR_COMMA ?
+        function.getParameters().getNextLexicalUnit().getNextLexicalUnit().
+          getStringValue() :
+        "decimal";
+  }
+
+
+
+  private static String
+  getCountersListStyle(LexicalUnit function)
+  {
+    return
+      function.getNextLexicalUnit() != null &&
+        function.getNextLexicalUnit().getLexicalUnitType() ==
+          LexicalUnit.SAC_OPERATOR_COMMA ?
+        function.getNextLexicalUnit().getNextLexicalUnit().getStringValue() :
+        "decimal";
+  }
+
+
+
+  private static String
+  getCounterString(int value, String listStyle)
+  {
+    if (listStyle.equals("circle"))
+    {
+      return "\u25cb";
+    }
+
+    if (listStyle.equals("disc"))
+    {
+      return "\u2022";
+    }
+
+    if (listStyle.equals("square"))
+    {
+      return "\u25a0";
+    }
+
+    if (listStyle.equals("decimal-leading-zero"))
+    {
+      return (value < 10 ? "0" : "") + String.valueOf(value);
+    }
+
+    if (listStyle.equals("lower-alpha") || listStyle.equals("lower-latin"))
+    {
+      return String.valueOf((char) (value + 96));
+    }
+
+    if (listStyle.equals("upper-alpha") || listStyle.equals("upper-latin"))
+    {
+      return String.valueOf((char) (value + 64));
+    }
+
+    if (listStyle.equals("lower-greek"))
+    {
+      return String.valueOf((char) (value + 944));
+    }
+
+    if (listStyle.equals("lower-roman"))
+    {
+      return Util.toRoman(value).toLowerCase();
+    }
+
+    if (listStyle.equals("upper-roman"))
+    {
+      return Util.toRoman(value);
+    }
+
+    if (listStyle.equals("footnote"))
+    {
+      return Util.toFootnote(value);
+    }
+
+    return String.valueOf(value); // decimal
+  }
+
+
+
+  private String
+  getCountersString(String counter, String separator, String listStyle)
+  {
+    String	result = "";
+
+    for (Iterator i = counters.iterator(); i.hasNext();)
+    {
+      Map	scope = (Map) i.next();
+      Integer	value = (Integer) scope.get(counter);
+
+      if (value != null)
+      {
+        result +=
+          (result.equals("") ? "" : separator) +
+            getCounterString(value.intValue(), listStyle);
+      }
+    }
+
+    return result;
+  }
+
+
+
+  private static String
+  getElementContents(Node node)
+  {
+    return
+      node == null ?
+        "" :
+        (
+          (
+            node instanceof Text ?
+              ((Text) node).getData() :
+              (
+                node instanceof org.w3c.dom.Element &&
+                (
+                  !Constants.CSS.equals(node.getNamespaceURI()) ||
+                  !(
+                    BEFORE.equals(node.getLocalName()) ||
+                      AFTER.equals(node.getLocalName())
+                  )
+                ) ? getElementContents(node.getFirstChild()) : ""
+              ) 
+          ) + getElementContents(node.getNextSibling())
+        );
+  }
+
+
+
+  private static LexicalUnit
+  getQuotePair(LexicalUnit unit, int quoteDepth)
+  {
+    for
+    (
+      int i = 0;
+      i < quoteDepth && unit.getNextLexicalUnit().getNextLexicalUnit() != null;
+      ++i
+    )
+    {
+      unit = unit.getNextLexicalUnit().getNextLexicalUnit();
+    }
+
+    return unit;
+  }
+
+
+
+  private static String[]
+  getSetNamedStringNames(Rule[] rules)
+  {
+    Set	result = new HashSet();
+
+    for (int i = 0; i < rules.length; ++i)
+    {
+      Property[]	properties = rules[i].getProperties();
+
+      for (int j = 0; j < properties.length; ++j)
+      {
+        if
+        (
+          "string-set".equals(properties[j].getName())			  &&
+          properties[j].getLexicalUnit() != null			  &&
+          properties[j].getLexicalUnit().getLexicalUnitType() ==
+            LexicalUnit.SAC_IDENT					  &&
+          !"none".equalsIgnoreCase
+          (
+            properties[j].getLexicalUnit().getStringValue()
+          )
+        )
+        {
+          if
+          (
+            !hasContentsIdentifier // Those are serialized differently.
+            (
+              properties[j].getLexicalUnit().getNextLexicalUnit()
+            )
+          )
+          {
+            result.add
+            (
+              properties[j].getLexicalUnit().getStringValue().toLowerCase()
+            );
+          }
+          else
+          {
+            // In case there is a rule earlier in the cascade that doesn't
+            // have "contents" as the value for the named string.
+
+            result.remove
+            (
+              properties[j].getLexicalUnit().getStringValue().toLowerCase()
+            );
+          }
+        }
+      }
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+
+
+  private Rule[]
+  getStyleAttributeRules(String style) throws IOException, SAXException
+  {
+    final List	rules = new ArrayList();
+
+    try
+    {
+      parseStyleSheet
+      (
+        null,
+        new StringReader("dummy{" + style + "}"),
+        new RuleCollector.RuleEmitter()
+        {
+          public void
+          addRule(Rule rule)
+          {
+            rules.add(rule);
+          }
+        },
+        new ArrayList(),
+        0,
+        false
+      );
+    }
+
+    catch (MalformedURLException e)
+    {
+      throw new UndeclaredThrowableException(e); // Would be a bug.
+    }
+
+    return (Rule[]) rules.toArray(new Rule[0]);
+  }
+
+
+
+  public URL
+  getUserAgentStyleSheet()
+  {
+    return userAgentStyleSheet;
+  }
+
+
+
+  private void
+  handleControlInformation
+  (
+    String		namespaceURI,
+    String		localName,
+    AttributesImpl	atts
+  ) throws SAXException
+  {
+    try
+    {
+      if (atts.getValue("xml:base") != null)
+      {
+        ((Element) elements.peek()).baseUrl =
+          new URL(atts.getValue("xml:base"));
+      }
+
+      setXMLIDType(atts);
+
+      if (Constants.XHTML == namespaceURI)
+      {
+        if (BASE == localName)
+        {
+          if (atts.getValue("href") != null)
+          {
+            URL	base = new URL(atts.getValue("href"));
+
+            for (int i = 0; i < elements.size(); ++i)
+            {
+              ((Element) elements.get(i)).baseUrl = base;
+            }
+          }
+        }
+        else
+        {
+          if (BODY == localName)
+          {
+            URL	base = ((Element) elements.peek()).baseUrl;
+
+            if (base != null)
+              // Make sure the BASE is translated for the rest of the chain.
+            {
+              Util.
+                setAttribute(atts, "", "xml:base", "xml:base", base.toString());
+            }
+          }
+          else
+          {
+            if (LINK == localName)
+            {
+              if (isMatchingStyleSheet(atts) && atts.getValue("href") != null)
+              {
+                parseStyleSheet(atts.getValue("href"), 0, true);
+              }
+            }
+            else
+            {
+              if (STYLE == localName && isMatchingStyleSheet(atts))
+              {
+                collectStyleSheet = true;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    catch (Exception e)
+    {
+      throw new SAXException(e);
+    }
+  }
+
+
+
+  private void
+  handleFloats(Element element)
+    throws SAXException
+  {
+    int	floating = element.appliedAttributes.getIndex(Constants.CSS, "float");
+
+    if (floating == -1)
+    {
+      return;
+    }
+
+    if (!"none".equals(element.appliedAttributes.getValue(floating)))
+    {
+      AttributesImpl	floatAttributes = new AttributesImpl();
+
+      floatAttributes.addAttribute
+      (
+        Constants.CSS,
+        "float",
+        "css:float",
+        "CDATA",
+        element.appliedAttributes.getValue(floating)
+      );
+
+      super.startElement(Constants.CSS, "float", "css:float", floatAttributes);
+      element.floating = true;
+    }
+
+    element.appliedAttributes.removeAttribute(floating);
+  }
+
+
+
+  private void
+  handleGraphics(Element element) throws SAXException
+  {
+    if
+    (
+      "graphic".
+        equals(element.appliedAttributes.getValue(Constants.CSS, "display"))
+    )
+    {
+      String	url = Util.getIndirectValue(element.appliedAttributes, "src");
+
+      if (url != null)
+      {
+        try
+        {
+          element.appliedAttributes.setValue
+          (
+            element.appliedAttributes.getIndex(Constants.CSS, "src"),
+            element.baseUrl != null ?
+              new URL(element.baseUrl, url).toString() : url
+          );
+        }
+
+        catch (MalformedURLException e)
+        {
+          throw new SAXException(e);
+        }
+      }
+    }
+  }
+
+
+
+  private static boolean
+  hasContentsIdentifier(LexicalUnit unit)
+  {
+    return
+      unit == null ?
+        false :
+        (
+          unit.getLexicalUnitType() == LexicalUnit.SAC_IDENT &&
+            "contents".equalsIgnoreCase(unit.getStringValue()) ? true :
+            hasContentsIdentifier(unit.getNextLexicalUnit())
+        );
+  }
+
+
+
+  private void
+  incrementCounter(Property counterIncrement, boolean display)
+  {
+    for
+    (
+      LexicalUnit i = counterIncrement.getLexicalUnit();
+      i != null;
+      i = i.getNextLexicalUnit()
+    )
+    {
+      if (i.getLexicalUnitType() == LexicalUnit.SAC_IDENT)
+      {
+        String	counter = i.getStringValue().toLowerCase();
+
+        if (display || "page".equals(counter))
+        {
+          Map	scope = findCounterScope(i.getStringValue());
+
+          scope.put
+          (
+            counter,
+            new Integer
+            (
+              ((Integer) scope.get(i.getStringValue())).intValue() +
+                (
+                  i.getNextLexicalUnit() != null &&
+                    i.getNextLexicalUnit().getLexicalUnitType() ==
+                      LexicalUnit.SAC_INTEGER ?
+                    i.getNextLexicalUnit().getIntegerValue() : 1
+                )
+            )
+          );
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * The installed accumulator catches region elements processed by this
+   * filter and saves them.
+   */
+
+  private void
+  installRegionAccumulator() throws SAXException
+  {
+    Accumulator.postAccumulate
+    (
+      this,
+      new Accumulator.ProcessElement()
+      {
+        public void
+        process(org.w3c.dom.Element element, XMLFilter filter)
+          throws SAXException
+        {
+          String	pageName =
+            element.getAttributeNS(Constants.CSS, "page");
+
+          if (pageName.equals("") || pageName.equals("auto"))
+          {
+            pageName = "unnamed";
+          }
+
+          element.setAttributeNS(Constants.CSS, "css:page", pageName);
+
+          Map	regionsForPage = (Map) context.regions.get(pageName);
+
+          if (regionsForPage == null)
+          {
+            regionsForPage = new HashMap();
+            context.regions.put(pageName, regionsForPage);
+          }
+
+          regionsForPage.
+            put(element.getAttributeNS(Constants.CSS, "region"), element);
+        }
+      }
+    );
+  }
+
+
+
+  private void
+  installStringSetAccumulator
+  (
+    final String	name,
+    final String	value,
+    final Map		scope
+  ) throws SAXException
+  {
+    Accumulator.postAccumulate
+    (
+      this,
+      new Accumulator.ProcessElement()
+      {
+        public void
+        process(org.w3c.dom.Element element, XMLFilter filter)
+          throws SAXException
+        {
+          String	result =
+            MessageFormat.format
+            (
+              value,
+              new Object[]{getElementContents(element.getFirstChild())}
+            );
+
+          scope.put(name, result);
+          addFOMarker(element, name, result);
+
+          DOMToContentHandler.elementToContentHandler
+          (
+            element,
+            filter.getContentHandler()
+          );
+        }
+      }
+    );
+  }
+
+
+
+  private static boolean
+  isMatchingStyleSheet(Attributes atts)
+  {
+    if
+    (
+      !"text/css".equals(atts.getValue("type"))				||
+      (
+        atts.getValue("rel") != null					&&
+        !"stylesheet".equalsIgnoreCase(atts.getValue("rel").trim())
+      )
+    )
+    {
+      return false;
+    }
+
+    if (atts.getValue("media") == null)
+    {
+      return true;
+    }
+
+    StringTokenizer	tokenizer =
+      new StringTokenizer(atts.getValue("media"), ",");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String	token = tokenizer.nextToken().trim().toLowerCase();
+
+      if ("all".equals(token) || "print".equals(token))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+
+  private static boolean
+  isStaticRegion(Attributes atts)
+  {
+    String	region = atts.getValue(Constants.CSS, "region");
+
+    return
+      region != null && !"body".equalsIgnoreCase(region) &&
+        !"none".equalsIgnoreCase(region);
+  }
+
+
+
+  private void
+  parseStyleSheet(String uri, int offset, boolean resetMatcher)
+    throws CSSException, MalformedURLException, IOException, SAXException
+  {
+    parseStyleSheet(uri, null, offset, resetMatcher);
+  }
+
+
+
+  private void
+  parseStyleSheet(Reader reader, int offset, boolean resetMatcher)
+    throws CSSException, IOException, SAXException
+  {
+    try
+    {
+      parseStyleSheet(null, reader, offset, resetMatcher);
+    }
+
+    catch (MalformedURLException e)
+    {
+      throw new UndeclaredThrowableException(e); // Would be a bug.
+    }
+  }
+
+
+
+  private void
+  parseStyleSheet(String uri, Reader reader, int offset, boolean resetMatcher)
+    throws CSSException, MalformedURLException, IOException, SAXException
+  {
+    parseStyleSheet
+    (
+      uri,
+      reader,
+      new RuleCollector.RuleEmitter()
+      {
+        public void
+        addRule(Rule rule)
+        {
+          compiled.addRule(rule);
+        }
+      },
+      context.pageRules,
+      offset,
+      resetMatcher
+    );
+  }
+
+
+
+  private void
+  parseStyleSheet
+  (
+    String			uri,
+    Reader			reader,
+    RuleCollector.RuleEmitter	ruleEmitter,
+    List			pageRules,
+    int				offset,
+    boolean			resetMatcher
+  ) throws CSSException, MalformedURLException, IOException, SAXException
+  {
+    URL		base =
+      !elements.isEmpty() && ((Element) elements.peek()).baseUrl != null ?
+        ((Element) elements.peek()).baseUrl : baseUrl;
+    Parser	parser = Util.getSacParser();
+    InputSource	source =
+      reader != null ? new InputSource(reader) : new InputSource();
+
+    source.setURI
+    (
+      base != null && uri != null ?
+        new URL(base, uri).toString() :
+        (base != null ? base.toString() : uri)
+    );
+
+    RuleCollector	collector =
+      new RuleCollector
+      (
+        ruleEmitter,
+        pageRules,
+        source.getURI() != null ? new URL(source.getURI()) : null,
+        lastRulePosition,
+        offset
+      );
+
+    parser.setDocumentHandler(collector);
+    parser.parseStyleSheet(source);
+    lastRulePosition = collector.getCurrentPosition();
+
+    if (resetMatcher)
+    {
+      setMatcher();
+    }
+  }
+
+
+
+  public void
+  processingInstruction(String target, String data) throws SAXException
+  {
+    if ("xml-stylesheet".equalsIgnoreCase(target))
+    {
+      StringTokenizer	tokenizer = new StringTokenizer(data, " ");
+      String		type = null;
+      String		uri = null;
+
+      while (tokenizer.hasMoreTokens())
+      {
+        String	token = tokenizer.nextToken();
+
+        if (token.startsWith("type="))
+        {
+          type = token.substring(token.indexOf('=') + 1);
+        }
+        else
+        {
+          if (token.startsWith("href="))
+          {
+            uri = token.substring(token.indexOf('=') + 1);
+          }
+        }
+      }
+
+      if
+      (
+        uri != null						&&
+        uri.length() > 2					&&
+        type != null						&&
+        type.length() > 2					&&
+        type.substring(1, type.length() - 1).equals("text/css")
+      )
+      {
+        try
+        {
+          parseStyleSheet(uri.substring(1, uri.length() - 1), 0, true);
+        }
+
+        catch (Exception e)
+        {
+          throw new SAXException(e);
+        }
+      }
+    }
+
+    super.processingInstruction(target, data);
+  }
+
+
+
+  private void
+  repositionMatcher() throws SAXException
+  {
+    matcher.startDocument();
+
+    // The "/" element is of no concern.
+
+    for (int i = 1; i < elements.size() - 1; ++i)
+    {
+      Element	element = (Element) elements.get(i);
+
+      matcher.startElement
+      (
+        element.namespaceURI,
+        element.localName,
+        element.qName,
+        element.attributes
+      );
+    }
+  }
+
+
+
+  private void
+  reset()
+  {
+    context.pageRules.clear();
+    compiled = new Compiled();
+    matcher = null;
+    collectStyleSheet = false;
+    embeddedStyleSheet = "";
+    elements.clear();
+    counters.clear();
+    namedStrings.clear();
+    context.regions.clear();
+  }
+
+
+
+  private void
+  resetCounter(Property counterReset, boolean display)
+  {
+    for
+    (
+      LexicalUnit i = counterReset.getLexicalUnit();
+      i != null;
+      i = i.getNextLexicalUnit()
+    )
+    {
+      if (i.getLexicalUnitType() == LexicalUnit.SAC_IDENT)
+      {
+        String	counter = i.getStringValue().toLowerCase();
+
+        if (display || "page".equals(counter))
+        {
+          ((Map) counters.peek()).put
+          (
+            counter,
+            new Integer
+            (
+              i.getNextLexicalUnit() != null &&
+                i.getNextLexicalUnit().getLexicalUnitType() ==
+                  LexicalUnit.SAC_INTEGER ?
+                i.getNextLexicalUnit().getIntegerValue() : 0
+            )
+          );
+        }
+      }
+    }
+  }
+
+
+
+  private static String
+  selectCloseQuote(Element element, int quoteDepth)
+  {
+    if (element.quotes == null)
+    {
+      return DEFAULT_CLOSE_QUOTE;
+    }
+
+    return
+      getQuotePair(element.quotes, quoteDepth).getNextLexicalUnit().
+        getStringValue();
+  }
+
+
+
+  private static String
+  selectOpenQuote(Element element, int quoteDepth)
+  {
+    if (element.quotes == null)
+    {
+      return DEFAULT_OPEN_QUOTE;
+    }
+
+    return getQuotePair(element.quotes, quoteDepth).getStringValue();
+  }
+
+
+
+  /**
+   * Maintains the order.
+   */
+
+  private static Rule[]
+  selectPseudoRules(Rule[] rules, String pseudoElementName)
+  {
+    List	result = new ArrayList();
+
+    for (int i = 0; i < rules.length; ++i)
+    {
+      if (pseudoElementName == rules[i].getPseudoElementName())
+      {
+        result.add(rules[i]);
+      }
+    }
+
+    return (Rule[]) result.toArray(new Rule[0]);
+  }
+
+
+
+  private void
+  serializeAttrFunction(LexicalUnit unit, Element element, Map prefixMap)
+    throws SAXException
+  {
+    String	value =
+      evaluateAttrFunction(unit, element.attributes, prefixMap);
+
+    super.characters(value.toCharArray(), 0, value.length());
+  }
+
+
+
+  private void
+  serializeChangeBarBegin(Attributes attributes) throws SAXException
+  {
+    if (attributes.getIndex("change-bar-class") != -1)
+    {
+      super.startElement
+      (
+        Constants.CSS,
+        "change-bar-begin",
+        "css:change-bar-begin",
+        attributes
+      );
+
+      super.
+        endElement(Constants.CSS, "change-bar-begin", "css:change-bar-begin");
+    }
+  }
+
+
+
+  private void
+  serializeChangeBarEnd(Attributes attributes) throws SAXException
+  {
+    int	index = attributes.getIndex("change-bar-class");
+
+    if (index != -1)
+    {
+      AttributesImpl	atts = new AttributesImpl();
+
+      atts.addAttribute
+      (
+        attributes.getURI(index),
+        attributes.getLocalName(index),
+        attributes.getQName(index),
+        attributes.getType(index),
+        attributes.getValue(index)
+      );
+
+      super.startElement
+      (
+        Constants.CSS,
+        "change-bar-end",
+        "css:change-bar-end",
+        atts
+      );
+
+      super.
+        endElement(Constants.CSS, "change-bar-end", "css:change-bar-end");
+    }
+  }
+
+
+
+  private void
+  serializeCounterFunction(LexicalUnit unit) throws SAXException
+  {
+    if (unit.getParameters() == null)
+    {
+      return;
+    }
+
+    String	counter = unit.getParameters().getStringValue().toLowerCase();
+
+    if ("page".equals(counter)) // Special synthetic counter.
+    {
+      serializePageNumber(getCounterListStyle(unit));
+    }
+    else
+    {
+      if ("pages".equals(counter)) // Special synthetic counter.
+      {
+        serializePagesTotal();
+      }
+      else
+      {
+        String	value = evaluateCounterFunction(unit);
+
+        super.characters(value.toCharArray(), 0, value.length());
+      }
+    }
+  }
+
+
+
+  private void
+  serializeCountersFunction(LexicalUnit unit) throws SAXException
+  {
+    String	value = evaluateCountersFunction(unit);
+
+    super.characters(value.toCharArray(), 0, value.length());
+  }
+
+
+
+  private void
+  serializeFOMarkers(Rule[] rules) throws SAXException
+  {
+    String[]	names = getSetNamedStringNames(rules);
+
+    for (int i = 0; i < names.length; ++i)
+    {
+      String		value =
+        (String) findNamedStringScope(names[i]).get(names[i]);
+
+      if (value != null)
+      {
+        AttributesImpl	atts = new AttributesImpl();
+
+        atts.addAttribute(Constants.CSS, "name", "css:name", "CDATA", names[i]);
+        super.startElement(Constants.CSS, "fo-marker", "css:fo-marker", atts);
+        super.characters(value.toCharArray(), 0, value.length());
+        super.endElement(Constants.CSS, "fo-marker", "css:fo-marker");
+      }
+    }
+  }
+
+
+
+  private void
+  serializeFunction(LexicalUnit unit, Element element, Map prefixMap)
+    throws SAXException
+  {
+    if ("string".equalsIgnoreCase(unit.getFunctionName()))
+    {
+      serializeStringFunction(unit);
+    }
+    else
+    {
+      if ("page-ref".equalsIgnoreCase(unit.getFunctionName()))
+      {
+        serializePageRefFunction(unit, element, prefixMap);
+      }
+    }
+  }
+
+
+
+  private void
+  serializeQuote(LexicalUnit unit, Element element) throws SAXException
+  {
+    int[]	quoteDepthReference = new int[] {quoteDepth};
+    String	value = evaluateQuote(unit, element, quoteDepthReference);
+
+    quoteDepth = quoteDepthReference[0];
+    super.characters(value.toCharArray(), 0, value.length());
+  }
+
+
+
+  private void
+  serializePageNumber(String listStyle) throws SAXException
+  {
+    AttributesImpl	attributes = new AttributesImpl();
+
+    attributes.addAttribute
+    (
+      Constants.CSS,
+      "format",
+      "css:format",
+      "CDATA",
+      convertPageFormat(listStyle)
+    );
+
+    if
+    (
+      listStyle.equals("armenian")	||
+      listStyle.equals("georgian")	||
+      listStyle.equals("hebrew")
+    )
+    {
+      attributes.addAttribute
+      (
+        Constants.CSS,
+        "letter-value",
+        "css:letter-value",
+        "CDATA",
+        "traditional"
+      );
+    }
+
+    super.
+      startElement(Constants.CSS, "page-number", "css:page-number", attributes);
+    super.endElement(Constants.CSS, "page-number", "css:page-number");
+  }
+
+
+
+  private void
+  serializePageRefFunction(LexicalUnit unit, Element element, Map prefixMap)
+    throws SAXException
+  {
+    if (unit.getParameters() == null)
+    {
+      return;
+    }
+
+    String	value =
+      unit.getParameters().getLexicalUnitType() == LexicalUnit.SAC_ATTR ?
+        evaluateAttrFunction
+        (
+          unit.getParameters(),
+          element.attributes,
+          prefixMap
+        ) : element.attributes.getValue(unit.getParameters().getStringValue());
+
+    if (value == null)
+    {
+      return;
+    }
+
+    AttributesImpl	attributes = new AttributesImpl();
+
+    attributes.
+      addAttribute(Constants.CSS, "ref-id", "css:ref-id", "CDATA", value);
+    super.startElement(Constants.CSS, "page-ref", "css:page-ref", attributes);
+    super.endElement(Constants.CSS, "page-ref", "css:page-ref");
+  }
+
+
+
+  private void
+  serializePagesTotal() throws SAXException
+  {
+    super.startElement
+    (
+      Constants.CSS,
+      "pages-total",
+      "css:pages-total",
+      new AttributesImpl()
+    );
+
+    super.endElement(Constants.CSS, "pages-total", "css:pages-total");
+  }
+
+
+
+  private void
+  serializeString(String s) throws SAXException
+  {
+    int		position = 0;
+
+    for (int i = s.indexOf('\n'); i != -1; i = s.indexOf(position, '\n'))
+    {
+      super.characters(s.substring(position, i).toCharArray(), 0, i - position);
+
+      super.startElement
+      (
+        Constants.CSS,
+        "newline",
+        "css:newline",
+        new AttributesImpl()
+      );
+
+      super.endElement(Constants.CSS, "newline", "css:newline");
+      position = i + 1;
+    }
+
+    if (position < s.length())
+    {
+      super.characters
+      (
+        s.substring(position).toCharArray(),
+        0,
+        s.length() - position
+      );
+    }
+  }
+
+
+
+  private void
+  serializeStringFunction(LexicalUnit unit) throws SAXException
+  {
+    if (unit.getParameters() == null)
+    {
+      return;
+    }
+
+    AttributesImpl	atts = new AttributesImpl();
+    String		name = unit.getParameters().getStringValue();
+
+    if (name == null)
+    {
+      return;
+    }
+
+    atts.addAttribute(Constants.CSS, "name", "css:name", "CDATA", name);
+
+    super.startElement
+    (
+      Constants.CSS,
+      "retrieve-fo-marker",
+      "css:retrieve-fo-marker",
+      atts
+    );
+
+    super.
+      endElement(Constants.CSS, "retrieve-fo-marker", "css:retrieve-fo-marker");
+  }
+
+
+
+  private void
+  serializeUriFunction(LexicalUnit unit) throws SAXException
+  {
+    if (unit.getStringValue() == null)
+    {
+      return;
+    }
+
+    AttributesImpl	atts = new AttributesImpl();
+
+    atts.addAttribute
+    (
+      Constants.CSS,
+      "href",
+      "css:href",
+      "CDATA",
+      "url(" + unit.getStringValue() + ")"
+    );
+
+    super.startElement(Constants.CSS, "external", "css:external", atts);
+    super.endElement(Constants.CSS, "external", "css:external");
+  }
+
+
+
+  public void
+  setBaseUrl(URL baseUrl)
+  {
+    this.baseUrl = baseUrl;
+  }
+
+
+
+  /**
+   * This method produces CSS attributes according to the matching rules. It
+   * also has a side effect in that it adjusts the counters and named strings.
+   * This was done in order to scan the matching rules only once.
+   */
+
+  private AttributesImpl
+  setCSSAttributes(Rule[] matchingRules, Attributes attributes)
+    throws SAXException
+  {
+    Property		counterIncrement = null;
+    Property		counterReset = null;
+    boolean		displayNone = false;
+    AttributesImpl	result = new AttributesImpl(attributes);
+    Property		stringSet = null;
+
+    // From least to most specific.
+
+    for (int i = 0; i < matchingRules.length; ++i)
+    {
+      Property	property = matchingRules[i].getProperties()[0];
+      String	propertyName = property.getName();
+
+      if (propertyName.equals("counter-increment"))
+      {
+        counterIncrement = property;
+      }
+      else
+      {
+        if (propertyName.equals("counter-reset"))
+        {
+          counterReset = property;
+        }
+        else
+        {
+          if (propertyName.equals("string-set"))
+          {
+            if (matchingRules[i].getPseudoElementName() == null)
+            {
+              stringSet = property;
+            }
+          }
+          else
+          {
+            Util.setCSSAttribute
+            (
+              result,
+              property,
+              matchingRules[i].getSpecificity()
+            );
+
+            if
+            (
+              propertyName.equals("display")			&&
+              "none".equalsIgnoreCase(property.getValue())
+            )
+            {
+              displayNone = true;
+            }
+          }
+        }
+      }
+    }
+
+    if (counterReset != null)
+    {
+      resetCounter(counterReset, !displayNone);
+    }
+
+    if (counterIncrement != null)
+    {
+      incrementCounter(counterIncrement, !displayNone);
+    }
+
+    if (stringSet != null && !displayNone)
+    {
+      setNamedString(stringSet);
+    }
+
+    return result;
+  }
+
+
+
+  private void
+  setMatcher() throws SAXException
+  {
+    compiled.generateDFA();
+    matcher = new Matcher(compiled);
+    repositionMatcher();
+  }
+
+
+
+  private void
+  setNamedString(Property stringSet) throws SAXException
+  {
+    if
+    (
+      stringSet.getLexicalUnit() == null				||
+      stringSet.getLexicalUnit().getLexicalUnitType() !=
+        LexicalUnit.SAC_IDENT						||
+      "none".
+        equalsIgnoreCase(stringSet.getLexicalUnit().getStringValue())
+    )
+    {
+      return;
+    }
+
+    boolean	needContents = false;
+    String	name =
+      stringSet.getLexicalUnit().getStringValue().toLowerCase();
+    String	result = "";
+    Map		scope = findNamedStringScope(name);
+
+    for
+    (
+      LexicalUnit i = stringSet.getLexicalUnit().getNextLexicalUnit();
+      i != null;
+      i = i.getNextLexicalUnit()
+    )
+    {
+      switch (i.getLexicalUnitType())
+      {
+        case LexicalUnit.SAC_ATTR:
+          result +=
+            evaluateAttrFunction
+            (
+              i,
+              ((Element) elements.peek()).attributes,
+              stringSet.getPrefixMap()
+            );
+          break;
+
+        case LexicalUnit.SAC_COUNTER_FUNCTION:
+          result += evaluateCounterFunction(i);
+          break;
+
+        case LexicalUnit.SAC_COUNTERS_FUNCTION:
+          result += evaluateCountersFunction(i);
+          break;
+
+        case LexicalUnit.SAC_FUNCTION:
+          if ("string".equalsIgnoreCase(i.getFunctionName()))
+          {
+            result += evaluateStringFunction(i);
+          }
+          break;
+
+        case LexicalUnit.SAC_IDENT:
+          if ("contents".equalsIgnoreCase(i.getStringValue()))
+          {
+            result += "{0}";
+            needContents = true;
+          }
+          else
+          {
+            result += evaluateQuote(i, (Element) elements.peek(), new int[1]);
+              // Local evaluation.
+          }
+          break;
+
+        case LexicalUnit.SAC_STRING_VALUE:
+          result += i.getStringValue();
+          break;
+
+        default:
+          break;
+      }
+    }
+
+    if (!needContents)
+    {
+      scope.put(name, result);
+    }
+    else
+    {
+      installStringSetAccumulator(name, result, scope);
+    }
+  }
+
+
+
+  private void
+  setQuotes()
+  {
+    Element	element = (Element) elements.peek();
+
+    for (int i = 0; i < element.matchingElementRules.length; ++i)
+    {
+      Property[]	properties =
+        element.matchingElementRules[i].getProperties();
+
+      if (properties[0].getName().equals("quotes"))
+      {
+        element.quotes = properties[0].getLexicalUnit();
+      }
+    }
+
+    if
+    (
+      element.quotes == null						||
+      element.quotes.getLexicalUnitType() == LexicalUnit.SAC_INHERIT
+    )
+    {
+      element.quotes = ((Element) elements.get(elements.size() - 2)).quotes;
+    }
+  }
+
+
+
+  public void
+  setUserAgentStyleSheet(URL userAgentStyleSheet)
+  {
+    this.userAgentStyleSheet = userAgentStyleSheet;
+  }
+
+
+
+  private static void
+  setXMLIDType(AttributesImpl atts)
+  {
+    int	index = atts.getIndex("xml:id");
+
+    if (index != -1 && !"ID".equals(atts.getType(index)))
+    {
+      atts.setType(index, "ID");
+    }
+  }
+
+
+
+  /**
+   * Removes the change-bar attributes from <code>attributes</code> and returns
+   * those that are in the CSS namespace. They are returned without a
+   * namespace however.
+   */
+
+  private static AttributesImpl
+  splitChangeBarAttributes(AttributesImpl attributes)
+  {
+    AttributesImpl	result = new AttributesImpl();
+
+    for (int i = 0; i < attributes.getLength(); ++i)
+    {
+      if (attributes.getLocalName(i).startsWith("change-bar-"))
+      {
+        if (Constants.CSS.equals(attributes.getURI(i)))
+        {
+          result.addAttribute
+          (
+            "",
+            attributes.getLocalName(i),
+            attributes.getLocalName(i),
+            attributes.getType(i),
+            attributes.getValue(i)
+          );
+        }
+
+        attributes.removeAttribute(i--);
+      }
+    }
+
+    return result;
+  }
+
+
+
+  public void
+  startDocument() throws SAXException
+  {
+    reset();
+
+    try
+    {
+      parseStyleSheet(new StringReader("*{display: inline}"), -2, true);
+
+      String	htmlHeaderMark =
+        (String) userAgentParameters.get("html-header-mark");
+
+      if (htmlHeaderMark != null)
+      {
+        parseStyleSheet
+        (
+          new StringReader(htmlHeaderMark + "{string-set: component contents}"),
+          -2,
+          true
+        );
+      }
+
+      parseStyleSheet(userAgentStyleSheet.toString(), -1, true);
+    }
+
+    catch (Exception e)
+    {
+      throw new SAXException(e);
+    }
+
+    Element	root = new Element("", "/", "/");
+
+    root.baseUrl = baseUrl;
+    elements.push(root);
+    counters.push(new HashMap());
+    namedStrings.push(new HashMap());
+    super.startDocument();
+    startPrefixMapping("css", Constants.CSS);
+    startPrefixMapping("sp", Constants.SPECIF);
+  }
+
+
+
+  /**
+   * The string arguments are interned.
+   */
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    if (namespaceURI != null)
+    {
+      namespaceURI = namespaceURI.intern();
+    }
+
+    if (localName != null)
+    {
+      localName = localName.intern();
+    }
+
+    if (qName != null)
+    {
+      qName = qName.intern();
+    }
+
+    Element	element = new Element(namespaceURI, localName, qName);
+
+    element.baseUrl = ((Element) elements.peek()).baseUrl;
+    elements.push(element);
+    element.attributes = new AttributesImpl(atts);
+      // Must be copied because atts might be recuperated by the parser.
+    handleControlInformation(namespaceURI, localName, element.attributes);
+    matcher.startElement(namespaceURI, localName, qName, element.attributes);
+
+    element.matchingElementRules =
+      appendStyleAttributeRules(matcher.matchingRules(), atts, namespaceURI);
+    element.matchingPseudoRules = matcher.matchingPseudoRules();
+    setQuotes();
+
+    element.appliedAttributes =
+      setCSSAttributes(element.matchingElementRules, element.attributes);
+    handleFloats(element);
+    handleGraphics(element);
+    detectMarkers(element);
+
+    if (isStaticRegion(element.appliedAttributes))
+    {
+      installRegionAccumulator();
+    }
+
+    addFirstLetterMarker(element);
+    translateId(element.appliedAttributes);
+    super.
+      startElement(namespaceURI, localName, qName, element.appliedAttributes);
+    serializeFOMarkers(element.matchingElementRules);
+    applyPseudoRules(element, FIRST_LETTER);
+    applyPseudoRules(element, BEFORE);
+    applyPseudoRules(element, FIRST_LINE);
+    counters.push(new HashMap());
+    namedStrings.push(new HashMap());
+  }
+
+
+
+  private static void
+  translateId(AttributesImpl atts)
+  {
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if ("ID".equals(atts.getType(i)))
+      {
+        atts.setAttribute
+        (
+          i,
+          Constants.XML,
+          "id",
+          "xml:id",
+          "ID",
+          atts.getValue(i)
+        );
+      }
+    }
+  }
+
+
+
+  private static class Element
+
+  {
+
+    private AttributesImpl	appliedAttributes;
+    private AttributesImpl	attributes = new AttributesImpl();
+    private URL			baseUrl;
+    private boolean		floating = false;
+    private String		localName;
+    private Rule[]		matchingElementRules = null;
+    private Rule[]		matchingPseudoRules = null;
+    private String		namespaceURI;
+    private String		qName;
+    private LexicalUnit		quotes = null;
+
+
+
+    private
+    Element(String namespaceURI, String localName, String qName)
+    {
+      this.namespaceURI = namespaceURI;
+      this.localName = localName;
+      this.qName = qName;
+    }
+
+  } // Element
+
+} // ProjectorFilter
diff --git a/src/be/re/css/Property.java b/src/be/re/css/Property.java
new file mode 100644
index 0000000..a5449cd
--- /dev/null
+++ b/src/be/re/css/Property.java
@@ -0,0 +1,708 @@
+package be.re.css;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import org.w3c.css.sac.LexicalUnit;
+
+
+
+/**
+ * Represents a CSS2 property. It also implements splitting of shorthand
+ * properties into basic properties.
+ * @author Werner Donn\u00e9
+ */
+
+public class Property
+
+{
+
+  private static final String[]	BACKGROUND_ATTACHMENT = {"fixed", "scroll"};
+  private static final String[]	BACKGROUND_REPEAT =
+    {"repeat", "repeat-x", "repeat-y", "no-repeat"};
+  private static final String[]	BORDER_STYLE =
+    {
+      "none", "hidden", "dotted", "dashed", "solid", "double", "groove",
+        "ridge", "inset", "outset"
+    };
+  private static final String[]	COLORS =
+    {
+      "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon",
+        "navy", "olive", "purple", "red", "silver", "teal", "white", "yellow",
+        "transparent"
+    };
+  private static final String[]	FONT_SIZE =
+    {
+      "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large",
+        "larger", "smaller"
+    };
+  private static final String[]	FONT_STYLE = {"normal", "italic", "oblique"};
+  private static final String[]	FONT_VARIANT = {"normal", "small-caps"};
+  private static final String[]	FONT_WEIGHT =
+    {"normal", "bold", "bolder", "lighter"};
+  private static final String[]	LIST_STYLE_POSITION = {"inside", "outside"};
+  private static final String[]	LIST_STYLE_TYPE =
+    {
+      "disc", "circle", "square", "decimal", "decimal-leading-zero",
+        "lower-roman", "upper-roman", "lower-greek", "lower-alpha",
+        "lower-latin", "upper-alpha", "upper-latin", "hebrew", "armenian",
+        "georgian", "cjk-ideographic", "hiragana", "katakana", "hiragana-iroha",
+        "katakana-iroha", "none"
+    };
+  private static final String[]	SYSTEM_FONTS =
+    {"caption", "icon", "menu", "message-box", "small-caption", "status-bar"};
+
+  private URL		baseUrl;
+  private boolean	important;
+  private String	name;
+  private Map		prefixMap;
+  private LexicalUnit	value;
+  private String	valueAsString;
+
+
+
+  /**
+   * Is not used for shorthand properties.
+   */
+
+  Property(String name, String value, boolean important, Map prefixMap)
+  {
+    this.name = name;
+    this.valueAsString = value;
+    this.important = important;
+    this.prefixMap = prefixMap;
+  }
+
+
+
+  public
+  Property
+  (
+    String	name,
+    LexicalUnit	value,
+    boolean	important,
+    Map		prefixMap,
+    URL		baseUrl
+  )
+  {
+    this.name = name;
+    this.value = value;
+    this.valueAsString =
+      Util.lexicalUnitToString(value, !"font-family".equals(name), baseUrl);
+    this.important = important;
+    this.prefixMap = prefixMap;
+    this.baseUrl = baseUrl;
+  }
+
+
+
+  private Property
+  copy(String name, String value)
+  {
+    return new Property(name, value, getImportant(), getPrefixMap());
+  }
+
+
+
+  public boolean
+  getImportant()
+  {
+    return important;
+  }
+
+
+
+  public String
+  getName()
+  {
+    return name;
+  }
+
+
+
+  public LexicalUnit
+  getLexicalUnit()
+  {
+    return value;
+  }
+
+
+
+  public Map
+  getPrefixMap()
+  {
+    return prefixMap;
+  }
+
+
+
+  public String
+  getValue()
+  {
+    return valueAsString;
+  }
+
+
+
+  private boolean
+  isFontSize(LexicalUnit unit)
+  {
+    int	type = unit.getLexicalUnitType();
+
+    return
+      type == LexicalUnit.SAC_CENTIMETER ||
+      type == LexicalUnit.SAC_EM ||
+      type == LexicalUnit.SAC_EX ||
+      type == LexicalUnit.SAC_INCH ||
+      type == LexicalUnit.SAC_MILLIMETER ||
+      type == LexicalUnit.SAC_PERCENTAGE ||
+      type == LexicalUnit.SAC_PICA ||
+      type == LexicalUnit.SAC_PIXEL ||
+      type == LexicalUnit.SAC_POINT ||
+      Util.inArray(FONT_SIZE, Util.lexicalUnitAtomLower(unit, baseUrl));
+  }
+
+
+
+  private static boolean
+  isFontWeight(String atom)
+  {
+    try
+    {
+      return
+        Util.inArray(FONT_WEIGHT, atom) ||
+          (Integer.parseInt(atom) >= 100 && Integer.parseInt(atom) <= 900);
+    }
+
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  private Property[]
+  setAtoms(String[] names, String[] atoms)
+  {
+    Property[]	result = new Property[names.length];
+
+    for (int i = 0; i < names.length; ++i)
+    {
+      result[i] = copy(names[i], atoms[i]);
+    }
+
+    return result;
+  }
+
+
+
+  public Property[]
+  split()
+  {
+    StringTokenizer	tokenizer = new StringTokenizer(getName(), "-");
+    String		name = "split";
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String	token = tokenizer.nextToken();
+
+      name += token.substring(0, 1).toUpperCase() + token.substring(1);
+    }
+
+    try
+    {
+      return
+        (Property[])
+          getClass().getDeclaredMethod(name, new Class[0]).
+            invoke(this, new Object[0]);
+    }
+
+    catch (IllegalAccessException e)
+    {
+      throw new UndeclaredThrowableException(e);
+    }
+
+    catch (InvocationTargetException e)
+    {
+      throw new UndeclaredThrowableException(e);
+    }
+
+    catch (NoSuchMethodException e)
+    {
+      return new Property[] {this};
+    }
+  }
+
+
+
+  private Property[]
+  splitBackground()
+  {
+    List	result = new ArrayList();
+    String	remaining = null;
+
+    for (LexicalUnit i = value; i != null; i = i.getNextLexicalUnit())
+    {
+      String	atom = Util.lexicalUnitAtomLower(i, baseUrl);
+
+      if (i.getLexicalUnitType() == LexicalUnit.SAC_URI)
+      {
+        result.add(copy("background-image", atom));
+      }
+      else
+      {
+        if
+        (
+          i.getLexicalUnitType() == LexicalUnit.SAC_RGBCOLOR	||
+          Util.inArray(COLORS, atom)
+        )
+        {
+          result.add(copy("background-color", atom));
+        }
+        else
+        {
+          if (Util.inArray(BACKGROUND_ATTACHMENT, atom))
+          {
+            result.add(copy("background-attachment", atom));
+          }
+          else
+          {
+            if (Util.inArray(BACKGROUND_REPEAT, atom))
+            {
+              result.add(copy("background-repeat", atom));
+            }
+            else
+            {
+              remaining = remaining == null ? atom : (remaining + " " + atom);
+            }
+          }
+        }
+      }
+    }
+
+    if (remaining != null)
+    {
+      result.add(copy("background-position", remaining));
+    }
+
+    return (Property[]) result.toArray(new Property[result.size()]);
+  }
+
+
+
+  private Property[]
+  splitBorder()
+  {
+    List	result = new ArrayList();
+    String	remaining = null;
+
+    for (LexicalUnit i = value; i != null; i = i.getNextLexicalUnit())
+    {
+      String    atom = Util.lexicalUnitAtomLower(i, baseUrl);
+
+      if
+      (
+        i.getLexicalUnitType() == LexicalUnit.SAC_RGBCOLOR	||
+        Util.inArray(COLORS, atom)				||
+        atom.equals("transparant")
+      )
+      {
+        result.add(copy("border-top-color", atom));
+        result.add(copy("border-right-color", atom));
+        result.add(copy("border-bottom-color", atom));
+        result.add(copy("border-left-color", atom));
+      }
+      else
+      {
+        if (Util.inArray(BORDER_STYLE, atom))
+        {
+          result.add(copy("border-top-style", atom));
+          result.add(copy("border-right-style", atom));
+          result.add(copy("border-bottom-style", atom));
+          result.add(copy("border-left-style", atom));
+        }
+        else
+        {
+          remaining = remaining == null ? atom : (remaining + " " + atom);
+        }
+      }
+    }
+
+    if (remaining != null)
+    {
+      result.add(copy("border-top-width", remaining));
+      result.add(copy("border-bottom-width", remaining));
+      result.add(copy("border-left-width", remaining));
+      result.add(copy("border-right-width", remaining));
+      result.add(copy("border-after-width.conditionality", "retain"));
+      result.add(copy("border-before-width.conditionality", "retain"));
+    }
+
+    return (Property[]) result.toArray(new Property[result.size()]);
+  }
+
+
+
+  private Property[]
+  splitBorderBottom()
+  {
+    return splitBorderSide("bottom");
+  }
+
+
+
+  private Property[]
+  splitBorderBottomWidth()
+  {
+    Property[]	result = new Property[2];
+
+    result[0] = copy(getName(), getValue());
+    result[1] = copy("border-after-width.conditionality", "retain");
+
+    return result;
+  }
+
+
+
+  private Property[]
+  splitBorderColor()
+  {
+    return splitFourWays(getName(), getImportant());
+  }
+
+
+
+  private Property[]
+  splitBorderLeft()
+  {
+    return splitBorderSide("left");
+  }
+
+
+
+  private Property[]
+  splitBorderRight()
+  {
+    return splitBorderSide("right");
+  }
+
+
+
+  private Property[]
+  splitBorderStyle()
+  {
+    return splitFourWays(getName(), getImportant());
+  }
+
+
+
+  private Property[]
+  splitBorderTop()
+  {
+    return splitBorderSide("top");
+  }
+
+
+
+  private Property[]
+  splitBorderSide(String side)
+  {
+    List	result = new ArrayList();
+    String	remaining = null;
+
+    for (LexicalUnit i = value; i != null; i = i.getNextLexicalUnit())
+    {
+      String    atom = Util.lexicalUnitAtomLower(i, baseUrl);
+
+      if
+      (
+        i.getLexicalUnitType() == LexicalUnit.SAC_RGBCOLOR	||
+        Util.inArray(COLORS, atom)				||
+        atom.equals("transparant")
+      )
+      {
+        result.add(copy("border-" + side + "-color", atom));
+      }
+      else
+      {
+        if (Util.inArray(BORDER_STYLE, atom))
+        {
+          result.add(copy("border-" + side + "-style", atom));
+        }
+        else
+        {
+          remaining = remaining == null ? atom : (remaining + " " + atom);
+        }
+      }
+    }
+
+    if (remaining != null)
+    {
+      result.add(copy("border-" + side + "-width", remaining));
+
+      if ("top".equals(side))
+      {
+        result.add(copy("border-before-width.conditionality", "retain"));
+      }
+      else
+      {
+        if ("bottom".equals(side))
+        {
+          result.add(copy("border-after-width.conditionality", "retain"));
+        }
+      }
+    }
+
+    return (Property[]) result.toArray(new Property[result.size()]);
+  }
+
+
+
+  private Property[]
+  splitBorderTopWidth()
+  {
+    Property[]	result = new Property[2];
+
+    result[0] = copy(getName(), getValue());
+    result[1] = copy("border-before-width.conditionality", "retain");
+
+    return result;
+  }
+
+
+
+  private Property[]
+  splitBorderWidth()
+  {
+    Property[]	values = splitFourWays(getName(), getImportant());
+    Property[]	result = new Property[values.length + 2];
+
+    System.arraycopy(values, 0, result, 0, values.length);
+    result[values.length] = copy("border-after-width.conditionality", "retain");
+    result[values.length + 1] =
+      copy("border-before-width.conditionality", "retain");
+
+    return result;
+  }
+
+
+
+  private Property[]
+  splitFont()
+  {
+    List	result = new ArrayList();
+    String	remaining = null;
+
+    for (LexicalUnit i = value; i != null; i = i.getNextLexicalUnit())
+    {
+      String	originalAtom = Util.lexicalUnitAtom(i, baseUrl);
+      String	atom = originalAtom.toLowerCase();
+
+      if (Util.inArray(SYSTEM_FONTS, atom))
+      {
+        result.add(copy("font", atom));
+      }
+      else
+      {
+        if (Util.inArray(FONT_STYLE, atom))
+        {
+          result.add(copy("font-style", atom));
+        }
+        else
+        {
+          if (Util.inArray(FONT_VARIANT, atom))
+          {
+            result.add(copy("font-variant", atom));
+          }
+          else
+          {
+            if (isFontWeight(atom))
+            {
+              result.add(copy("font-weight", atom));
+            }
+            else
+            {
+              if
+              (
+                (
+                  i.getPreviousLexicalUnit() == null			||
+                  i.getPreviousLexicalUnit().getLexicalUnitType() !=
+                    LexicalUnit.SAC_OPERATOR_SLASH
+                )							&&
+                isFontSize(i)
+              )
+              {
+                result.add(copy("font-size", atom));
+              }
+              else
+              {
+                if (i.getLexicalUnitType() == LexicalUnit.SAC_OPERATOR_SLASH)
+                {
+                  i = i.getNextLexicalUnit();
+
+                  result.add
+                  (
+                    copy("line-height", Util.lexicalUnitAtomLower(i, baseUrl))
+                  );
+                }
+                else
+                {
+                  remaining =
+                    remaining == null ?
+                      originalAtom : (remaining + " " + originalAtom);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (remaining != null)
+    {
+      result.add(copy("font-family", remaining));
+    }
+
+    return (Property[]) result.toArray(new Property[result.size()]);
+  }
+
+
+
+  private Property[]
+  splitFourWays(String name, boolean important)
+  {
+    String[]		names = splitFourWaysNames(name);
+    LexicalUnit[]	units = Util.lexicalUnitArray(value);
+
+    if (units.length == 1)
+    {
+      String	atom = Util.lexicalUnitAtomLower(units[0], baseUrl);
+
+      return setAtoms(names, new String[] {atom, atom, atom, atom});
+    }
+
+    if (units.length == 2)
+    {
+      String	atom1 = Util.lexicalUnitAtomLower(units[0], baseUrl);
+      String	atom2 = Util.lexicalUnitAtomLower(units[1], baseUrl);
+
+      return setAtoms(names, new String[] {atom1, atom2, atom1, atom2});
+    }
+
+    if (units.length == 3)
+    {
+      String	atom1 = Util.lexicalUnitAtomLower(units[0], baseUrl);
+      String	atom2 = Util.lexicalUnitAtomLower(units[1], baseUrl);
+      String	atom3 = Util.lexicalUnitAtomLower(units[2], baseUrl);
+
+      return setAtoms(names, new String[] {atom1, atom2, atom3, atom2});
+    }
+
+    if (units.length == 4)
+    {
+      String	atom1 = Util.lexicalUnitAtomLower(units[0], baseUrl);
+      String	atom2 = Util.lexicalUnitAtomLower(units[1], baseUrl);
+      String	atom3 = Util.lexicalUnitAtomLower(units[2], baseUrl);
+      String	atom4 = Util.lexicalUnitAtomLower(units[3], baseUrl);
+
+      return setAtoms(names, new String[] {atom1, atom2, atom3, atom4});
+    }
+
+    return new Property[] {this};
+  }
+
+
+
+  private static String[]
+  splitFourWaysNames(String name)
+  {
+    String[]		result = new String[4];
+    StringTokenizer	tokenizer = new StringTokenizer(name, "-");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String	token = tokenizer.nextToken();
+
+      if (result[0] == null)
+      {
+        result[0] = token + "-top";
+        result[1] = token + "-right";
+        result[2] = token + "-bottom";
+        result[3] = token + "-left";
+      }
+      else
+      {
+        for (int i = 0; i < result.length; ++i)
+        {
+          result[i] += "-" + token;
+        }
+      }
+    }
+
+    return result;
+  }
+
+
+
+  private Property[]
+  splitListStyle()
+  {
+    List	result = new ArrayList();
+
+    for (LexicalUnit i = value; i != null; i = i.getNextLexicalUnit())
+    {
+      String    atom = Util.lexicalUnitAtomLower(i, baseUrl);
+
+      if (atom.equals("none"))
+      {
+        result.add(copy("list-style-type", atom));
+        result.add(copy("list-style-image", atom));
+      }
+      else
+      {
+        if (i.getLexicalUnitType() == LexicalUnit.SAC_URI)
+        {
+          result.add(copy("list-style-image", atom));
+        }
+        else
+        {
+          if (Util.inArray(LIST_STYLE_POSITION, atom))
+          {
+            result.add(copy("list-style-position", atom));
+          }
+          else
+          {
+            if (Util.inArray(LIST_STYLE_TYPE, atom))
+            {
+              result.add(copy("list-style-type", atom));
+            }
+          }
+        }
+      }
+    }
+
+    return (Property[]) result.toArray(new Property[result.size()]);
+  }
+
+
+
+  private Property[]
+  splitMargin()
+  {
+    return splitFourWays(getName(), getImportant());
+  }
+
+
+
+  private Property[]
+  splitPadding()
+  {
+    return splitFourWays(getName(), getImportant());
+  }
+
+} // Property
diff --git a/src/be/re/css/Rule.java b/src/be/re/css/Rule.java
new file mode 100644
index 0000000..6a043ee
--- /dev/null
+++ b/src/be/re/css/Rule.java
@@ -0,0 +1,413 @@
+package be.re.css;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.ElementSelector;
+import org.w3c.css.sac.NegativeSelector;
+import org.w3c.css.sac.PositionalCondition;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SiblingSelector;
+
+
+
+/**
+ * Represents one CSS2 rule.
+ * @author Werner Donn\u00e9
+ */
+
+public class Rule
+
+{
+
+  private Property[]	cachedArray = null;
+  private String	elementName;
+  private int		position;
+  private Map		properties = new HashMap();
+  private String	pseudoElementName;
+  private Selector	selector;
+  private Selector[]	selectorChain;
+  private int		specificity;
+
+
+
+  /**
+   * Use values like -1, 0 and +1 for <code>offset</code>. This will shift the
+   * specificity up or down, which is needed to account for the style sheet
+   * source.
+   */
+
+  Rule(Selector selector, int position, int offset)
+  {
+    this.selector =
+      selector instanceof ElementSelector ?
+        new InternedElementSelector((ElementSelector) selector) : selector;
+    this.position = position;
+    selectorChain = Util.getSelectorChain(selector);
+    specificity = specificity() + offset * 10000000;
+    elementName = getElementName(selectorChain, selectorChain.length - 1);
+    pseudoElementName = getPseudoElementName(selectorChain);
+
+    if (elementName != null)
+    {
+      elementName = elementName.intern();
+    }
+
+    if (pseudoElementName != null)
+    {
+      pseudoElementName = pseudoElementName.intern();
+    }
+  }
+
+
+
+  /**
+   * The created rule physically shares the selector and specificity
+   * information. This makes it possible to match a set of rules resulting
+   * after a split by picking only one of them.
+   */
+
+  private
+  Rule(Rule source, int position)
+  {
+    this.selector = source.selector;
+    this.position = position;
+    this.elementName = source.elementName;
+    this.pseudoElementName = source.pseudoElementName;
+    this.selectorChain = source.selectorChain;
+    this.specificity = source.specificity;
+  }
+
+
+
+  void
+  addProperty(Property property)
+  {
+    properties.put(property.getName(), property);
+  }
+
+
+
+  /**
+   * Returns the interned name of the element this rule applies to. If it
+   * doesn't apply to an element <code>null</code> is returned.
+   */
+
+  public String
+  getElementName()
+  {
+    return elementName;
+  }
+
+
+
+  private static String
+  getElementName(Selector[] selectorChain, int position)
+  {
+    switch (selectorChain[position].getSelectorType())
+    {
+      case Selector.SAC_ELEMENT_NODE_SELECTOR:
+        return
+          ((ElementSelector) selectorChain[position]).
+            getLocalName();
+      case Selector.SAC_PSEUDO_ELEMENT_SELECTOR:
+        return getElementName(selectorChain, position - 1);
+
+      default: return null;
+    }
+  }
+
+
+
+  int
+  getPosition()
+  {
+    return position;
+  }
+
+
+
+  Property[]
+  getProperties()
+  {
+    if (cachedArray == null || cachedArray.length != properties.size())
+    {
+      cachedArray =
+        (Property[])
+          properties.values().toArray(new Property[properties.size()]);
+    }
+
+    return cachedArray;
+  }
+
+
+
+  public Property
+  getProperty()
+  {
+    Property[]	result = getProperties();
+
+    if (result.length != 1)
+    {
+      throw new RuntimeException("Unsplit rule");
+    }
+
+    return result[0];
+  }
+
+
+
+  private static void
+  getPseudoClassConditions(Condition c, List result)
+  {
+    if (c.getConditionType() == Condition.SAC_PSEUDO_CLASS_CONDITION)
+    {
+      result.add(((AttributeCondition) c).getValue());
+    }
+    else
+    {
+      if (c.getConditionType() == Condition.SAC_AND_CONDITION)
+      {
+        getPseudoClassConditions
+        (
+          ((CombinatorCondition) c).getFirstCondition(),
+          result
+        );
+
+        getPseudoClassConditions
+        (
+          ((CombinatorCondition) c).getSecondCondition(),
+          result
+        );
+      }
+    }
+  }
+
+
+
+  /**
+   * Returns the interned pseudo element name or <code>null</code> if the rule
+   * doesn't apply to a pseudo element.
+   */
+
+  public String
+  getPseudoElementName()
+  {
+    return pseudoElementName;
+  }
+
+
+
+  private static String
+  getPseudoElementName(Selector[] selectorChain)
+  {
+    if
+    (
+      selectorChain[selectorChain.length - 1].getSelectorType() ==
+        Selector.SAC_PSEUDO_ELEMENT_SELECTOR
+    )
+    {
+      return
+        ((ElementSelector) selectorChain[selectorChain.length - 1]).
+          getLocalName();
+    }
+
+    if
+    (
+      selectorChain.length > 1						&&
+      selectorChain[selectorChain.length - 2].getSelectorType() ==
+        Selector.SAC_CONDITIONAL_SELECTOR
+    )
+    {
+      List	conditions = new ArrayList();
+
+      getPseudoClassConditions
+      (
+        ((ConditionalSelector) selectorChain[selectorChain.length - 2]).
+          getCondition(),
+        conditions
+      );
+
+      return
+        conditions.contains("before") ?
+          "before" :
+         (
+           conditions.contains("after") ?
+             "after" :
+             (conditions.contains("first-line") ?  "first-line" : null)
+         );
+    }
+
+    return null;
+  }
+
+
+
+  /**
+   * Returns the selector that matches the rule.
+   */
+
+  public Selector
+  getSelector()
+  {
+    return selector;
+  }
+
+
+
+  /**
+   * Flattens the selector expression tree in infix order.
+   */
+
+  Selector[]
+  getSelectorChain()
+  {
+    return selectorChain;
+  }
+
+
+
+  int
+  getSpecificity()
+  {
+    return specificity;
+  }
+
+
+
+  private int
+  specificity()
+  {
+    Specificity	s = new Specificity();
+
+    specificity(selector, s);
+
+    return 10000 * s.ids + 100 * s.attributes + s.names;
+  }
+
+
+
+  private static void
+  specificity(Selector selector, Specificity s)
+  {
+    if (selector instanceof ConditionalSelector)
+    {
+      specificity(((ConditionalSelector) selector).getCondition(), s);
+      specificity(((ConditionalSelector) selector).getSimpleSelector(), s);
+    }
+    else
+    {
+      if (selector instanceof DescendantSelector)
+      {
+        specificity(((DescendantSelector) selector).getAncestorSelector(), s);
+        specificity(((DescendantSelector) selector).getSimpleSelector(), s);
+      }
+      else
+      {
+        if (selector instanceof NegativeSelector)
+        {
+          specificity(((NegativeSelector) selector).getSimpleSelector(), s);
+        }
+        else
+        {
+          if (selector instanceof SiblingSelector)
+          {
+            specificity(((SiblingSelector) selector).getSelector(), s);
+            specificity(((SiblingSelector) selector).getSiblingSelector(), s);
+          }
+          else
+          {
+            if
+            (
+              selector.getSelectorType() ==
+                Selector.SAC_ELEMENT_NODE_SELECTOR			&&
+              ((ElementSelector) selector).getLocalName() != null
+                // There is no name for "*".
+            )
+            {
+              ++s.names;
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+
+  private static void
+  specificity(Condition c, Specificity s)
+  {
+    switch (c.getConditionType())
+    {
+      case Condition.SAC_ID_CONDITION:
+        ++s.ids;
+        break;
+
+      case Condition.SAC_ATTRIBUTE_CONDITION:
+      case Condition.SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION:
+      case Condition.SAC_CLASS_CONDITION:
+      case Condition.SAC_LANG_CONDITION:
+      case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION:
+      case Condition.SAC_PSEUDO_CLASS_CONDITION:
+        ++s.attributes;
+        break;
+
+      case Condition.SAC_AND_CONDITION:
+      case Condition.SAC_OR_CONDITION:
+        specificity(((CombinatorCondition) c).getFirstCondition(), s);
+        specificity(((CombinatorCondition) c).getSecondCondition(), s);
+        break;
+    }
+
+    if
+    (
+      c.getConditionType() == Condition.SAC_POSITIONAL_CONDITION	&&
+      ((PositionalCondition) c).getPosition() == 1
+        // first-child pseudo class.
+    )
+    {
+      ++s.attributes;
+    }
+  }
+
+
+
+  /**
+   * Splits this rule into a set of equivalent rules in which there is only one
+   * property. For each property of this rule there will be a new one.
+   */
+
+  Rule[]
+  split()
+  {
+    Rule[]	result = new Rule[getProperties().length];
+
+    for (int i = 0; i < result.length; ++i)
+    {
+      result[i] = new Rule(this, getPosition());
+      result[i].addProperty(getProperties()[i]);
+    }
+
+    return result;
+  }
+
+
+
+  private static class Specificity
+
+  {
+
+    private int	attributes;
+    private int	ids;
+    private int	names;
+
+  } // Specificity
+
+} // Rule
diff --git a/src/be/re/css/RuleCollector.java b/src/be/re/css/RuleCollector.java
new file mode 100644
index 0000000..cbb729a
--- /dev/null
+++ b/src/be/re/css/RuleCollector.java
@@ -0,0 +1,349 @@
+package be.re.css;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.w3c.css.sac.CSSException;
+import org.w3c.css.sac.DocumentHandler;
+import org.w3c.css.sac.InputSource;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.Parser;
+import org.w3c.css.sac.SACMediaList;
+import org.w3c.css.sac.SelectorList;
+
+
+
+/**
+ * This class collects rules and page rules from the "all" and "print" media.
+ * The other media are ignored. Rules without any properties are also ignored.
+ * @author Werner Donn\u00e9
+ */
+
+class RuleCollector implements DocumentHandler
+
+{
+
+  private URL		baseUrl;
+  private RuleEmitter	ruleEmitter;
+  private PageRule	currentPageRule = null;
+  private Rule[]	currentRules = null;
+  private boolean	ignore = false;
+  private int		offset;
+  private List		pageRules;
+  private Map		prefixMap = new HashMap();
+  private int		position;
+
+
+
+  RuleCollector
+  (
+    RuleEmitter	ruleEmitter,
+    List	pageRules,
+    URL		baseUrl,
+    int		startPosition,
+    int		offset
+  )
+  {
+    this.ruleEmitter = ruleEmitter;
+    this.pageRules = pageRules;
+    this.baseUrl = baseUrl;
+    this.position = startPosition;
+    this.offset = offset;
+  }
+
+
+
+  public void
+  comment(String text) throws CSSException
+  {
+  }
+
+
+
+  public void
+  endDocument(InputSource source) throws CSSException
+  {
+  }
+
+
+
+  public void
+  endFontFace() throws CSSException
+  {
+  }
+
+
+
+  public void
+  endMedia(SACMediaList media) throws CSSException
+  {
+    ignore = false;
+  }
+
+
+
+  public void
+  endPage(String name, String pseudoPage) throws CSSException
+  {
+    if (currentPageRule.getProperties().length > 0)
+    {
+      PageRule[]	split = currentPageRule.split();
+
+      for (int i = 0; i < split.length; ++i)
+      {
+        pageRules.add(split[i]);
+      }
+    }
+
+    currentPageRule = null;
+  }
+
+
+
+  public void
+  endSelector(SelectorList selectors) throws CSSException
+  {
+    if (!ignore)
+    {
+      for (int i = 0; i < currentRules.length; ++i)
+      {
+        if (currentRules[i].getProperties().length > 0)
+        {
+          Rule[]	split = currentRules[i].split();
+
+          for (int j = 0; j < split.length; ++j)
+          {
+            ruleEmitter.addRule(split[j]);
+          }
+        }
+      }
+
+      currentRules = null;
+    }
+  }
+
+
+
+  int
+  getCurrentPosition()
+  {
+    return position;
+  }
+
+
+
+  private boolean
+  hasOneOfMedia(SACMediaList media, String[] choices)
+  {
+    if (media == null)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < media.getLength(); ++i)
+    {
+      for (int j = 0; j < choices.length; ++j)
+      {
+        if (media.item(i).equals(choices[j]))
+        {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+
+
+  public void
+  ignorableAtRule(String atRule) throws CSSException
+  {
+  }
+
+
+
+  public void
+  importStyle(String uri, SACMediaList media, String defaultNamespaceURI)
+    throws CSSException
+  {
+    if (!ignore)
+    {
+      if
+      (
+        media == null						||
+        hasOneOfMedia(media, new String[] {"all", "print"})
+      )
+      {
+        try
+        {
+          Parser	parser = Util.getSacParser();
+          URL		url =
+            (baseUrl != null ? new URL(baseUrl, uri) : new URL(uri));
+
+          RuleCollector	importCollector =
+            new RuleCollector(ruleEmitter, pageRules, url, position, offset);
+          parser.setDocumentHandler(importCollector);
+          parser.parseStyleSheet(url.toString());
+          position = importCollector.getCurrentPosition();
+        }
+
+        catch (Exception e)
+        {
+          throw new CSSException(e);
+        }
+      }
+    }
+  }
+
+
+
+  public void
+  namespaceDeclaration(String prefix, String uri) throws CSSException
+  {
+    prefixMap.put(prefix, uri);
+  }
+
+
+
+  public void
+  property(String name, LexicalUnit value, boolean important)
+    throws CSSException
+  {
+    if (!ignore)
+    {
+      Property[]	properties =
+        new Property(name.toLowerCase(), value, important, prefixMap, baseUrl).
+          split();
+
+      if (currentRules != null)
+      {
+        for (int i = 0; i < currentRules.length; ++i)
+        {
+          for (int j = 0; j < properties.length; ++j)
+          {
+            currentRules[i].addProperty(properties[j]);
+          }
+        }
+      }
+      else
+      {
+        if (currentPageRule != null)
+        {
+          for (int i = 0; i < properties.length; ++i)
+          {
+            LexicalUnit	unit = properties[i].getLexicalUnit();
+
+            if
+            (
+              "counter-reset".equals(properties[i].getName())		&&
+              unit.getLexicalUnitType() == LexicalUnit.SAC_IDENT	&&
+              "page".equals(unit.getStringValue())			&&
+              (
+                unit.getNextLexicalUnit() == null			||
+                unit.getNextLexicalUnit().getLexicalUnitType() ==
+                  LexicalUnit.SAC_INTEGER
+              )
+            )
+            {
+              properties[i] =
+                unit.getNextLexicalUnit() == null ?
+                  new Property
+                  (
+                    "initial-page-number",
+                    "1",
+                    properties[i].getImportant(),
+                    prefixMap
+                  ) :
+                  new Property
+                  (
+                    "initial-page-number",
+                    unit.getNextLexicalUnit(),
+                    properties[i].getImportant(),
+                    prefixMap,
+                    baseUrl
+                  );
+            }
+
+            currentPageRule.addProperty(properties[i]);
+          }
+        }
+      }
+    }
+  }
+
+
+
+  public void
+  startDocument(InputSource source) throws CSSException
+  {
+  }
+
+
+
+  public void
+  startFontFace() throws CSSException
+  {
+  }
+
+
+
+  public void
+  startMedia(SACMediaList media) throws CSSException
+  {
+    ignore = !hasOneOfMedia(media, new String[] {"all", "print"});
+  }
+
+
+
+  public void
+  startPage(final String name, final String pseudoPage) throws CSSException
+  {
+    if (!ignore)
+    {
+      currentPageRule =
+        new PageRule
+        (
+          name != null && pseudoPage != null ?
+            (pseudoPage + "-" + name ) :
+            (
+              name != null ?
+                name : (pseudoPage != null ? pseudoPage : "unnamed")
+            ),
+          position++
+        );
+    }
+  }
+
+
+
+  public void
+  startSelector(SelectorList selectors) throws CSSException
+  {
+    if (ignore || selectors.getLength() == 0)
+    {
+      currentRules = null;
+
+      return;
+    }
+
+    currentRules = new Rule[selectors.getLength()];
+
+    for (int i = 0; i < currentRules.length; ++i)
+    {
+      currentRules[i] = new Rule(selectors.item(i), position++, offset);
+    }
+  }
+
+
+
+  interface RuleEmitter
+
+  {
+
+    public void	addRule	(Rule rule);
+
+  } // RuleEmitter
+
+} // RuleCollector
diff --git a/src/be/re/css/RuleComparator.java b/src/be/re/css/RuleComparator.java
new file mode 100644
index 0000000..79cdf83
--- /dev/null
+++ b/src/be/re/css/RuleComparator.java
@@ -0,0 +1,51 @@
+package be.re.css;
+
+import java.util.Comparator;
+
+
+
+/**
+ * A comparator for sorting rules from least to most specific. The rules must
+ * be split, i.e. they should have exactly one property.
+ * @author Werner Donn\u00e9
+ */
+
+class RuleComparator implements Comparator
+
+{
+
+  public int
+  compare(Object object1, Object object2)
+  {
+    Rule	rule1 = (Rule) object1;
+    Rule	rule2 = (Rule) object2;
+    Property	property1 = rule1.getProperty();
+    Property	property2 = rule2.getProperty();
+    int		result = property1.getName().compareTo(property2.getName());
+
+    if (result == 0)
+    {
+      result =
+        (
+          !property1.getImportant() && property2.getImportant() ?
+            -1 :
+            (
+              property1.getImportant() && !property2.getImportant() ? 1 : 0
+            )
+        );
+    }
+
+    if (result == 0)
+    {
+      result = rule1.getSpecificity() - rule2.getSpecificity();
+    }
+
+    if (result == 0)
+    {
+      result = rule1.getPosition() - rule2.getPosition();
+    }
+
+    return result;
+  }
+
+} // RuleComparator
diff --git a/src/be/re/css/SpaceCorrectionFilter.java b/src/be/re/css/SpaceCorrectionFilter.java
new file mode 100644
index 0000000..dd5afb5
--- /dev/null
+++ b/src/be/re/css/SpaceCorrectionFilter.java
@@ -0,0 +1,118 @@
+package be.re.css;
+
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.Properties;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Process space characters as in Jirka's Kosek spaces.xsl DocBook style sheet.
+ * @author Werner Donn\u00e9
+ */
+
+class SpaceCorrectionFilter extends XMLFilterImpl
+
+{
+
+  private static final	String[]	widths = loadWidths();
+
+
+
+  SpaceCorrectionFilter()
+  {
+  }
+
+
+
+  SpaceCorrectionFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  public void
+  characters(char[] ch, int start, int length) throws SAXException
+  {
+    int	position = start;
+
+    for (int i = 0; i < length; ++i)
+    {
+      if
+      (
+        ch[start + i] >= '\u2000'			&&
+        ch[start + i] <= '\u200A'			&&
+        widths[ch[start + i] - 0x2000].length() > 0
+      )
+      {
+        super.characters(ch, position, start + i - position);
+        position = start + i + 1;
+
+        AttributesImpl	atts = new AttributesImpl();
+
+        atts.addAttribute
+        (
+          "",
+          "leader-length",
+          "leader-length",
+          "CDATA",
+          widths[ch[start + i] - 0x2000]
+        );
+
+        super.startElement(Constants.XSLFO, "leader", "fo:leader", atts);
+        super.endElement(Constants.XSLFO, "leader", "fo:leader");
+      }
+    }
+
+    if (position < start + length)
+    {
+      super.characters(ch, position, start + length - position);
+    }
+  }
+
+
+
+  private static String[]
+  loadWidths()
+  {
+    try
+    {
+      Properties	properties = new Properties();
+      String[]		result = new String[11];
+
+      properties.load
+      (
+        SpaceCorrectionFilter.class.
+          getResourceAsStream("res/space_correction.prop")
+      );
+
+      for (int i = 0; i < result.length; ++i)
+      {
+        String	value =
+          properties.
+            getProperty(Integer.toString(0x2000 + i, 16).toLowerCase());
+
+        if (value == null)
+        {
+          value =
+            properties.
+              getProperty(Integer.toString(0x2000 + i, 16).toUpperCase());
+        }
+
+        result[i] = value != null ? value : "";
+      }
+
+      return result;
+    }
+
+    catch (Exception e)
+    {
+      throw new UndeclaredThrowableException(e);
+    }
+  }
+
+} // SpaceCorrectionFilter
diff --git a/src/be/re/css/TestSAC.java b/src/be/re/css/TestSAC.java
new file mode 100644
index 0000000..e29d411
--- /dev/null
+++ b/src/be/re/css/TestSAC.java
@@ -0,0 +1,416 @@
+package be.re.css;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.net.URL;
+import org.w3c.css.sac.CSSException;
+import org.w3c.css.sac.CharacterDataSelector;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.DocumentHandler;
+import org.w3c.css.sac.ElementSelector;
+import org.w3c.css.sac.InputSource;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.NegativeSelector;
+import org.w3c.css.sac.Parser;
+import org.w3c.css.sac.ProcessingInstructionSelector;
+import org.w3c.css.sac.SACMediaList;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SelectorList;
+import org.w3c.css.sac.SiblingSelector;
+import org.w3c.css.sac.helpers.ParserFactory;
+
+
+
+public class TestSAC
+
+{
+
+  private static final Tuple[]	tuples =
+    {
+      new Tuple(LexicalUnit.SAC_OPERATOR_COMMA, "SAC_OPERATOR_COMMA"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_PLUS, "SAC_OPERATOR_PLUS"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_MINUS, "SAC_OPERATOR_MINUS"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_MULTIPLY, "SAC_OPERATOR_MULTIPLY"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_SLASH, "SAC_OPERATOR_SLASH"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_MOD, "SAC_OPERATOR_MOD"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_EXP, "SAC_OPERATOR_EXP"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_LT, "SAC_OPERATOR_LT"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_GT, "SAC_OPERATOR_GT"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_LE, "SAC_OPERATOR_LE"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_GE, "SAC_OPERATOR_GE"),
+      new Tuple(LexicalUnit.SAC_OPERATOR_TILDE, "SAC_OPERATOR_TILDE"),
+      new Tuple(LexicalUnit.SAC_INHERIT, "SAC_INHERIT"),
+      new Tuple(LexicalUnit.SAC_INTEGER, "SAC_INTEGER"),
+      new Tuple(LexicalUnit.SAC_REAL, "SAC_REAL"),
+      new Tuple(LexicalUnit.SAC_EM, "SAC_EM"),
+      new Tuple(LexicalUnit.SAC_EX, "SAC_EX"),
+      new Tuple(LexicalUnit.SAC_PIXEL, "SAC_PIXEL"),
+      new Tuple(LexicalUnit.SAC_INCH, "SAC_INCH"),
+      new Tuple(LexicalUnit.SAC_CENTIMETER, "SAC_CENTIMETER"),
+      new Tuple(LexicalUnit.SAC_MILLIMETER, "SAC_MILLIMETER"),
+      new Tuple(LexicalUnit.SAC_POINT, "SAC_POINT"),
+      new Tuple(LexicalUnit.SAC_PICA, "SAC_PICA"),
+      new Tuple(LexicalUnit.SAC_PERCENTAGE, "SAC_PERCENTAGE"),
+      new Tuple(LexicalUnit.SAC_URI, "SAC_URI"),
+      new Tuple(LexicalUnit.SAC_COUNTER_FUNCTION, "SAC_COUNTER_FUNCTION"),
+      new Tuple(LexicalUnit.SAC_COUNTERS_FUNCTION, "SAC_COUNTERS_FUNCTION"),
+      new Tuple(LexicalUnit.SAC_RGBCOLOR, "SAC_RGBCOLOR"),
+      new Tuple(LexicalUnit.SAC_DEGREE, "SAC_DEGREE"),
+      new Tuple(LexicalUnit.SAC_GRADIAN, "SAC_GRADIAN"),
+      new Tuple(LexicalUnit.SAC_RADIAN, "SAC_RADIAN"),
+      new Tuple(LexicalUnit.SAC_MILLISECOND, "SAC_MILLISECOND"),
+      new Tuple(LexicalUnit.SAC_SECOND, "SAC_SECOND"),
+      new Tuple(LexicalUnit.SAC_HERTZ, "SAC_HERTZ"),
+      new Tuple(LexicalUnit.SAC_KILOHERTZ, "SAC_KILOHERTZ"),
+      new Tuple(LexicalUnit.SAC_IDENT, "SAC_IDENT"),
+      new Tuple(LexicalUnit.SAC_STRING_VALUE, "SAC_STRING_VALUE"),
+      new Tuple(LexicalUnit.SAC_ATTR, "SAC_ATTR"),
+      new Tuple(LexicalUnit.SAC_RECT_FUNCTION, "SAC_RECT_FUNCTION"),
+      new Tuple(LexicalUnit.SAC_UNICODERANGE, "SAC_UNICODERANGE"),
+      new Tuple(LexicalUnit.SAC_SUB_EXPRESSION, "SAC_SUB_EXPRESSION"),
+      new Tuple(LexicalUnit.SAC_FUNCTION, "SAC_FUNCTION"),
+      new Tuple(LexicalUnit.SAC_DIMENSION, "SAC_DIMENSION")
+    };
+
+
+
+  public static void
+  main(String[] args) throws Exception
+  {
+    if (args.length != 1)
+    {
+      System.err.println("Usage: be.re.css.TestSAC url_or_filename");
+      return;
+    }
+
+    Parser	parser = new ParserFactory().makeParser();
+    PrintWriter	out = new PrintWriter(System.out);
+
+    parser.setDocumentHandler(new SACWriter(out, new File(args[0]).toURL()));
+
+    parser.parseStyleSheet
+    (
+      be.re.net.Util.isUrl(args[0]) ?
+        new URL(args[0]).toString() :
+        be.re.net.Util.fileToUrl(new File(args[0])).toString()
+    );
+
+    out.flush();
+  }
+
+
+
+  private static class SACWriter implements DocumentHandler
+
+  {
+
+    private URL		baseUrl;
+    private PrintWriter	out;
+
+
+
+    private
+    SACWriter(PrintWriter out, URL baseUrl)
+    {
+      this.out = out;
+      this.baseUrl = baseUrl;
+    }
+
+
+
+    public void
+    comment(String text) throws CSSException
+    {
+      out.println("comment: " + text);
+    }
+
+
+
+    public void
+    endDocument(InputSource source) throws CSSException
+    {
+      out.println("end document");
+    }
+
+
+
+    public void
+    endFontFace() throws CSSException
+    {
+      out.println("end font face");
+    }
+
+
+
+    public void
+    endMedia(SACMediaList media) throws CSSException
+    {
+      out.println("end media");
+    }
+
+
+
+    public void
+    endPage(String name, String pseudePage) throws CSSException
+    {
+      out.println("end page");
+    }
+
+
+
+    public void
+    endSelector(SelectorList selectors) throws CSSException
+    {
+      out.println("end selector");
+    }
+
+
+
+    private static String
+    enumerateUnits(LexicalUnit value)
+    {
+      return
+        lexicalUnitTypeToString(value) +
+          (
+            value.getNextLexicalUnit() != null ?
+              (", " + enumerateUnits(value.getNextLexicalUnit())) : ""
+          );
+    }
+
+
+
+    public void
+    ignorableAtRule(String atRule) throws CSSException
+    {
+      out.println("ignorable at-rule: " + atRule);
+    }
+
+
+
+    public void
+    importStyle(String uri, SACMediaList media, String defaultNamespaceURI)
+      throws CSSException
+    {
+      out.println("import: " + uri);
+
+      try
+      {
+        Parser	parser = new ParserFactory().makeParser();
+
+        parser.setDocumentHandler(new SACWriter(out, new URL(baseUrl, uri)));
+        parser.parseStyleSheet(new URL(baseUrl, uri).toString());
+      }
+
+      catch (Exception e)
+      {
+        throw new CSSException(e);
+      }
+    }
+
+
+
+    private static String
+    lexicalUnitTypeToString(LexicalUnit value)
+    {
+      for (int i = 0; i < tuples.length; ++i)
+      {
+        if (tuples[i].id == value.getLexicalUnitType())
+        {
+          return tuples[i].name;
+        }
+      }
+
+      return "unknown";
+    }
+
+
+
+    public void
+    namespaceDeclaration(String prefix, String uri) throws CSSException
+    {
+      out.println("namespace declaration: " + prefix + ", " + uri);
+    }
+
+
+
+    public void
+    property(String name, LexicalUnit value, boolean important)
+      throws CSSException
+    {
+      out.println
+      (
+        "property: " + name + ": " +
+          Util.lexicalUnitToString(value, false, null) + "(" +
+          enumerateUnits(value) + "), " + String.valueOf(important)
+      );
+    }
+
+
+
+    private String
+    selectorText(Selector selector)
+    {
+      switch (selector.getSelectorType())
+      {
+        case Selector.SAC_ANY_NODE_SELECTOR: return "(any)";
+
+        case Selector.SAC_CDATA_SECTION_NODE_SELECTOR:
+          return
+            "(cdata: " + ((CharacterDataSelector) selector).getData() + ")";
+
+        case Selector.SAC_CHILD_SELECTOR:
+          return
+            "(child: " +
+              selectorText
+              (
+                ((DescendantSelector) selector).getAncestorSelector()
+              ) + " " +
+              selectorText(((DescendantSelector) selector).getSimpleSelector())
+              + ")";
+
+        case Selector.SAC_COMMENT_NODE_SELECTOR:
+          return
+            "(comment: " + ((CharacterDataSelector) selector).getData() + ")";
+
+        case Selector.SAC_CONDITIONAL_SELECTOR:
+          return
+            "(conditional: " +
+              Util.conditionText(((ConditionalSelector) selector).
+                getCondition()) +
+              " " +
+              selectorText(((ConditionalSelector) selector).getSimpleSelector())
+              + ")";
+
+        case Selector.SAC_DESCENDANT_SELECTOR:
+          return
+            "(descendant: " +
+              selectorText
+              (
+                ((DescendantSelector) selector).getAncestorSelector()
+              ) + " " +
+              selectorText(((DescendantSelector) selector).getSimpleSelector())
+              + ")";
+
+        case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
+          return
+            "(sibling: " +
+              selectorText(((SiblingSelector) selector).getSelector()) + " " +
+              selectorText(((SiblingSelector) selector).getSiblingSelector())
+              + ")";
+
+        case Selector.SAC_ELEMENT_NODE_SELECTOR:
+          return
+            "(element: " +
+              (
+                ((ElementSelector) selector).getNamespaceURI() != null ?
+                  (((ElementSelector) selector).getNamespaceURI() + "#") : ""
+              ) + ((ElementSelector) selector).getLocalName() + ")";
+
+        case Selector.SAC_NEGATIVE_SELECTOR:
+          return
+            "(negative: " +
+              selectorText(((NegativeSelector) selector).getSimpleSelector()) +
+              ")";
+
+        case Selector.SAC_PROCESSING_INSTRUCTION_NODE_SELECTOR:
+          return
+            "(pi: (" + ((ProcessingInstructionSelector) selector).getData() +
+            ") (" + ((ProcessingInstructionSelector) selector).getTarget() +
+            "))";
+
+        case Selector.SAC_PSEUDO_ELEMENT_SELECTOR:
+          return
+            "(pseudo: " +
+              (
+                ((ElementSelector) selector).getNamespaceURI() != null ?
+                  (((ElementSelector) selector).getNamespaceURI() + "#") : ""
+              ) + ((ElementSelector) selector).getLocalName() + ")";
+
+        case Selector.SAC_ROOT_NODE_SELECTOR: return "(root)";
+
+        case Selector.SAC_TEXT_NODE_SELECTOR:
+          return
+            "(text: " + ((CharacterDataSelector) selector).getData() + ")";
+
+        default: return "(unknown)";
+      }
+    }
+
+
+
+    public void
+    startDocument(InputSource source) throws CSSException
+    {
+      out.println("start document");
+    }
+
+
+
+    public void
+    startFontFace() throws CSSException
+    {
+      out.println("start font face");
+    }
+
+
+
+    public void
+    startMedia(SACMediaList media) throws CSSException
+    {
+      out.print("start media:");
+
+      for (int i = 0; i < media.getLength(); ++i)
+      {
+        out.print(" " + media.item(i));
+      }
+
+      out.println();
+    }
+
+
+
+    public void
+    startPage(final String name, final String pseudoPage) throws CSSException
+    {
+      out.println("start page: " + name + ", " + pseudoPage);
+    }
+
+
+
+    public void
+    startSelector(SelectorList selectors) throws CSSException
+    {
+      out.print("start selector:");
+
+      for (int i = 0; i < selectors.getLength(); ++i)
+      {
+        out.print(" " + selectorText(selectors.item(i)));
+      }
+
+      out.println();
+    }
+
+  } // SACWriter
+
+
+
+  private static class Tuple
+
+  {
+
+    private int		id;
+    private String	name;
+
+
+
+    private
+    Tuple(int id, String name)
+    {
+      this.id = id;
+      this.name = name;
+    }
+
+  } // Tuple
+
+} // TestSAC
diff --git a/src/be/re/css/Util.java b/src/be/re/css/Util.java
new file mode 100644
index 0000000..7afee0f
--- /dev/null
+++ b/src/be/re/css/Util.java
@@ -0,0 +1,1187 @@
+package be.re.css;
+
+import be.re.xml.sax.FilterOfFilters;
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.stream.StreamSource;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.CSSException;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.ContentCondition;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.ElementSelector;
+import org.w3c.css.sac.LangCondition;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.NegativeCondition;
+import org.w3c.css.sac.NegativeSelector;
+import org.w3c.css.sac.Parser;
+import org.w3c.css.sac.PositionalCondition;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SiblingSelector;
+import org.xml.sax.Attributes;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * A collection of functions.
+ * @author Werner Donn\u00e9
+ */
+
+class Util
+
+{
+
+  private static final String[]	FOOTNOTE_NUMBERS =
+    new String[]
+    {
+      "*",
+      "\u2020",
+      "\u2021",
+      "\u00a7",
+      "\u007c\u007c",
+      "\u00b6",
+      "#",
+      "**",
+      "\u2020\u2020",
+      "\u2021\u2021",
+      "\u00a7\u00a7"
+    };
+  private static final Set	INHERITED =
+    new HashSet
+    (
+      Arrays.asList
+      (
+        new String[]
+        {
+          "azimuth",
+          "border-collapse",
+          "border-spacing",
+          "caption-size",
+          "color",
+          "cursor",
+          "direction",
+          "elevation",
+          "empty-cells",
+          "font",
+          "font-family",
+          "font-size",
+          "font-stretch",
+          "font-style",
+          "font-variant",
+          "font-weight",
+          "hyphenate",
+          "leader-alignment",
+          "leader-length",
+          "leader-pattern",
+          "leader-pattern-width",
+          "letter-spacing",
+          "line-height",
+          "list-style",
+          "list-style-image",
+          "list-style-position",
+          "list-style-type",
+          "orientation",
+          "orphans",
+          "page",
+          "page-break-inside",
+          "pitch",
+          "pitch-range",
+          "quotes",
+          "richness",
+          "rule-style",
+          "rule-thickness",
+          "speak",
+          "speak-header",
+          "speak-numeral",
+          "speak-punctuation",
+          "speech-rate",
+          "stress",
+          "text-align",
+          "text-align-last",
+          "text-indent",
+          "text-transform",
+          "voice-family",
+          "volume",
+          "white-space",
+          "widows",
+          "word-spacing"
+        }
+      )
+    );
+
+  private static Class		sacParserClass = null;
+
+
+
+  static String
+  conditionText(Condition condition)
+  {
+    switch (condition.getConditionType())
+    {
+      case Condition.SAC_AND_CONDITION:
+        return
+          "(and: " +
+            conditionText
+            (
+              ((CombinatorCondition) condition).getFirstCondition()
+            ) + " " +
+            conditionText
+            (
+              ((CombinatorCondition) condition).getSecondCondition()
+            ) + ")";
+
+      case Condition.SAC_ATTRIBUTE_CONDITION:
+        return
+          "(attribute: (" +
+            ((AttributeCondition) condition).getNamespaceURI() + ") (" +
+            ((AttributeCondition) condition).getLocalName() + ") (" +
+            ((AttributeCondition) condition).getSpecified() + ") (" +
+            ((AttributeCondition) condition).getValue() + "))";
+
+      case Condition.SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION:
+        return
+          "(hyphen: (" +
+            ((AttributeCondition) condition).getNamespaceURI() + ") (" +
+            ((AttributeCondition) condition).getLocalName() + ") (" +
+            ((AttributeCondition) condition).getSpecified() + ") (" +
+            ((AttributeCondition) condition).getValue() + "))";
+
+      case Condition.SAC_CLASS_CONDITION:
+        return
+          "(class: (" +
+            ((AttributeCondition) condition).getNamespaceURI() + ") (" +
+            ((AttributeCondition) condition).getLocalName() + ") (" +
+            ((AttributeCondition) condition).getSpecified() + ") (" +
+            ((AttributeCondition) condition).getValue() + "))";
+
+      case Condition.SAC_CONTENT_CONDITION:
+        return "(content: " + ((ContentCondition) condition).getData() + ")";
+
+      case Condition.SAC_ID_CONDITION:
+        return
+          "(id: (" +
+            ((AttributeCondition) condition).getNamespaceURI() + ") (" +
+            ((AttributeCondition) condition).getLocalName() + ") (" +
+            ((AttributeCondition) condition).getSpecified() + ") (" +
+            ((AttributeCondition) condition).getValue() + "))";
+
+      case Condition.SAC_LANG_CONDITION:
+        return "(lang: " + ((LangCondition) condition).getLang() + ")";
+
+      case Condition.SAC_NEGATIVE_CONDITION:
+        return
+          "(negative: " +
+            conditionText(((NegativeCondition) condition).getCondition()) +
+            ")";
+
+      case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION:
+        return
+          "(one of: (" +
+            ((AttributeCondition) condition).getNamespaceURI() + ") (" +
+            ((AttributeCondition) condition).getLocalName() + ") (" +
+            ((AttributeCondition) condition).getSpecified() + ") (" +
+            ((AttributeCondition) condition).getValue() + "))";
+
+      case Condition.SAC_ONLY_CHILD_CONDITION: return "(only child)";
+
+      case Condition.SAC_ONLY_TYPE_CONDITION: return "(only type)";
+
+      case Condition.SAC_OR_CONDITION:
+        return
+          "(or: " +
+            conditionText
+            (
+              ((CombinatorCondition) condition).getFirstCondition()
+            ) + " " +
+            conditionText
+            (
+              ((CombinatorCondition) condition).getSecondCondition()
+            ) + ")";
+
+      case Condition.SAC_POSITIONAL_CONDITION:
+        return
+          "(positional: (" + ((PositionalCondition) condition).getPosition() +
+            ") (" + ((PositionalCondition) condition).getType() + ") (" +
+            ((PositionalCondition) condition).getTypeNode() + "))";
+
+      case Condition.SAC_PSEUDO_CLASS_CONDITION:
+        return
+          "(pseudo class: (" +
+            ((AttributeCondition) condition).getNamespaceURI() + ") (" +
+            ((AttributeCondition) condition).getLocalName() + ") (" +
+            ((AttributeCondition) condition).getSpecified() + ") (" +
+            ((AttributeCondition) condition).getValue() + "))";
+
+      default: return "(unknown)";
+    }
+  }
+
+
+
+  private static String
+  convertFloat(float value)
+  {
+    return
+      new DecimalFormat
+      (
+        "####0.0####",
+        new DecimalFormatSymbols(Locale.ENGLISH)
+      ).format(value);
+  }
+
+
+
+  static void
+  copyAttribute
+  (
+    Attributes		from,
+    AttributesImpl	to,
+    String		namespaceURI,
+    String		localName
+  )
+  {
+    int	index = from.getIndex(namespaceURI, localName);
+
+    if (index != -1)
+    {
+      to.addAttribute
+      (
+        namespaceURI,
+        localName,
+        from.getQName(index),
+        from.getType(index),
+        from.getValue(index)
+      );
+    }
+  }
+
+
+
+  static PostProjectionFilter
+  createPostProjectionFilter
+  (
+    URL		baseUrl,
+    Map		userAgentParameters,
+    boolean	debug
+  )
+  {
+    final LinkFilter	linkFilter = new LinkFilter(baseUrl);
+    final XMLFilterImpl	filter =
+      new FilterOfFilters
+      (
+        new XMLFilter[]
+        {
+          new InvalidPropertyFilter(),
+          new WrapperFilter(),
+          new DisplayNonePropagator(),
+          new ForeignFilter(),
+          new FirstLetterFilter(),
+          userAgentParameters != null &&
+            userAgentParameters.get("rule-thickness") != null ?
+            new XHTMLAttributeTranslationFilter
+            (
+              (String) userAgentParameters.get("rule-thickness")
+            ) : new XHTMLAttributeTranslationFilter(),
+          new NormalizeTableFilter(),
+          new CenterFilter(),
+          new LengthAdjustFilter(),
+          new WidthAndMarginsFilter(),
+          new MarkerFilter(),
+          linkFilter,
+          new FootnoteFilter(),
+          new BlockContainerFilter(),
+          new ListImageLabelFilter()
+        },
+        debug
+      );
+
+    return
+      new PostProjectionFilter()
+      {
+        public XMLFilterImpl
+        getFilter()
+        {
+          return filter;
+        }
+
+        public void
+        setBaseUrl(URL url)
+        {
+          linkFilter.setBaseUrl(url);
+        }
+      };
+  }
+
+
+
+  static XMLFilter
+  createPreprocessorFilter(URL[] preprocessors, XMLFilter parent)
+    throws TransformerConfigurationException
+  {
+    SAXTransformerFactory	factory =
+      be.re.xml.sax.Util.newSAXTransformerFactory();
+    XMLFilter			result = parent;
+
+    for (int i = 0; i < preprocessors.length; ++i)
+    {
+      XMLFilter	transformer =
+        factory.newXMLFilter(new StreamSource(preprocessors[i].toString()));
+
+      transformer.setParent(result);
+      result = transformer;
+    }
+
+    return result;
+  }
+
+
+
+  static URL
+  createUrl(String s) throws MalformedURLException
+  {
+    return
+      be.re.net.Util.isUrl(s) ?
+        new URL(s) : be.re.net.Util.fileToUrl(new File(s));
+  }
+
+
+
+  static URL[]
+  createUrls(String s) throws MalformedURLException
+  {
+    List		result = new ArrayList();
+    StringTokenizer	tokenizer = new StringTokenizer(s, ",");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      result.add(createUrl(tokenizer.nextToken()));
+    }
+
+    return (URL[]) result.toArray(new URL[0]);
+  }
+
+
+
+  static String
+  getIndirectType(Attributes attributes, String property)
+  {
+    String	value = attributes.getValue(Constants.CSS, property);
+    int		index = value != null ? value.lastIndexOf('|') : -1;
+
+    return
+      value != null ?
+        (
+          index != -1 ?
+            attributes.
+              getType(value.substring(0, index), value.substring(index + 1)) :
+            attributes.getType(value)
+        ) : null;
+  }
+
+
+
+  static String
+  getIndirectValue(Attributes attributes, String property)
+  {
+    String	value = attributes.getValue(Constants.CSS, property);
+    int		index = value != null ? value.lastIndexOf('|') : -1;
+
+    return
+      value != null ?
+        (
+          index != -1 ?
+            attributes.
+              getValue(value.substring(0, index), value.substring(index + 1)) :
+            attributes.getValue(value)
+        ) : null;
+  }
+
+
+
+  static Selector
+  getLastSelector(Selector selector)
+  {
+    Selector[]	chain = Util.getSelectorChain(selector);
+
+    return chain[chain.length - 1];
+  }
+
+
+
+  static Parser
+  getSacParser() throws CSSException
+  {
+    try
+    {
+      return (Parser) getSacParserClass().newInstance();
+    }
+
+    catch (Exception e)
+    {
+      if (e instanceof CSSException)
+      {
+        throw (CSSException) e;
+      }
+
+      throw new CSSException(e);
+    }
+  }
+
+
+
+  private static Class
+  getSacParserClass() throws Exception
+  {
+    if (sacParserClass == null)
+    {
+      String	cls =
+        be.re.util.Util.getSystemProperty("org.w3c.css.sac.parser");
+
+      if (cls == null)
+      {
+        throw new CSSException("No value for org.w3c.css.sac.parser");
+      }
+
+      sacParserClass = Class.forName(cls);
+    }
+
+    return sacParserClass;
+  }
+
+
+
+  /**
+   * Flattens the selector expression tree in infix order.
+   */
+
+  static Selector[]
+  getSelectorChain(Selector selector)
+  {
+    List	result = getSelectorChainList(selector);
+
+    return (Selector[]) result.toArray(new Selector[result.size()]);
+  }
+
+
+
+  private static List
+  getSelectorChainList(Selector selector)
+  {
+    List	result;
+
+    switch (selector.getSelectorType())
+    {
+      case Selector.SAC_CHILD_SELECTOR:
+      case Selector.SAC_DESCENDANT_SELECTOR:
+        result =
+          getSelectorChainList
+          (
+            ((DescendantSelector) selector).getAncestorSelector()
+          );
+
+        result.add(selector);
+
+        result.addAll
+        (
+          getSelectorChainList
+          (
+            ((DescendantSelector) selector).getSimpleSelector()
+          )
+        );
+
+        break;
+
+      case Selector.SAC_CONDITIONAL_SELECTOR:
+        result = new ArrayList();
+        result.add(selector);
+
+        result.addAll
+        (
+          getSelectorChainList
+          (
+            ((ConditionalSelector) selector).getSimpleSelector()
+          )
+        );
+
+        break;
+
+      case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
+        result =
+          getSelectorChainList(((SiblingSelector) selector).getSelector());
+        result.add(selector);
+
+        result.addAll
+        (
+          getSelectorChainList
+          (
+            ((SiblingSelector) selector).getSiblingSelector()
+          )
+        );
+
+        break;
+
+      case Selector.SAC_NEGATIVE_SELECTOR:
+        result = new ArrayList();
+        result.add(selector);
+
+        result.addAll
+        (
+          getSelectorChainList
+          (
+            ((NegativeSelector) selector).getSimpleSelector()
+          )
+        );
+
+        break;
+
+      default:
+        result = new ArrayList();
+        result.add(selector);
+    }
+
+    return result;
+  }
+
+
+
+  static boolean
+  inArray(String[] array, String object)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if
+      (
+        (
+          array[i].charAt(array[i].length() - 1) == '*'			  &&
+          object.startsWith(array[i].substring(0, array[i].length() - 1))
+        )								  ||
+        array[i].equals(object)
+      )
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+
+  static int
+  indexOf(Object[] array, Object object)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if (array[i].equals(object))
+      {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  static boolean
+  isInherited(String property)
+  {
+    return INHERITED.contains(property);
+  }
+
+
+
+  static boolean
+  isWhitespace(char[] ch, int start, int length)
+  {
+    for (int i = start; i < ch.length && i < start + length; ++i)
+    {
+      if (!Character.isWhitespace(ch[i]))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  static boolean
+  isZeroLength(String value)
+  {
+    return
+      Util.inArray
+      (
+        new String[] {"0", "0pt", "0px", "0pc", "0mm", "0cm", "0in", "0em"},
+        value
+      );
+  }
+
+
+
+  static LexicalUnit[]
+  lexicalUnitArray(LexicalUnit unit)
+  {
+    List	result = new ArrayList();
+
+    for (LexicalUnit i = unit; i != null; i = i.getNextLexicalUnit())
+    {
+      result.add(i);
+    }
+
+    return (LexicalUnit[]) result.toArray(new LexicalUnit[result.size()]);
+  }
+
+
+
+  static String
+  lexicalUnitAtom(LexicalUnit unit, URL baseUrl)
+  {
+    return lexicalUnitAtom(unit, false, baseUrl);
+  }
+
+
+
+  private static String
+  lexicalUnitAtom(LexicalUnit unit, boolean identifiersToLower, URL baseUrl)
+  {
+    switch (unit.getLexicalUnitType())
+    {
+      case LexicalUnit.SAC_ATTR:
+        return "attr(" + unit.getStringValue().toLowerCase() + ")";
+
+      case LexicalUnit.SAC_CENTIMETER:
+      case LexicalUnit.SAC_DEGREE:
+      case LexicalUnit.SAC_DIMENSION:
+      case LexicalUnit.SAC_EM:
+      case LexicalUnit.SAC_EX:
+      case LexicalUnit.SAC_GRADIAN:
+      case LexicalUnit.SAC_HERTZ:
+      case LexicalUnit.SAC_INCH:
+      case LexicalUnit.SAC_KILOHERTZ:
+      case LexicalUnit.SAC_MILLIMETER:
+      case LexicalUnit.SAC_MILLISECOND:
+      case LexicalUnit.SAC_PERCENTAGE:
+      case LexicalUnit.SAC_PICA:
+      case LexicalUnit.SAC_PIXEL:
+      case LexicalUnit.SAC_POINT:
+      case LexicalUnit.SAC_RADIAN:
+        return
+          (convertFloat(unit.getFloatValue()) + unit.getDimensionUnitText()).
+            toLowerCase();
+
+      // Flute 1.3 work-around, should be in previous list.
+      case LexicalUnit.SAC_REAL:
+        return convertFloat(unit.getFloatValue());
+
+      case LexicalUnit.SAC_COUNTER_FUNCTION:
+      case LexicalUnit.SAC_COUNTERS_FUNCTION:
+      case LexicalUnit.SAC_FUNCTION:
+      case LexicalUnit.SAC_RECT_FUNCTION:
+        return
+          unit.getFunctionName().toLowerCase() + "(" +
+            (
+              unit.getParameters() != null ?
+                lexicalUnitChain
+                (
+                  unit.getParameters(),
+                  identifiersToLower,
+                  baseUrl
+                ) : ""
+            ) + ")";
+
+      case LexicalUnit.SAC_IDENT:
+        return
+          identifiersToLower ?
+            unit.getStringValue().toLowerCase() : unit.getStringValue();
+
+      case LexicalUnit.SAC_INHERIT: return "inherit";
+
+      case LexicalUnit.SAC_INTEGER:
+        return String.valueOf(unit.getIntegerValue());
+
+      case LexicalUnit.SAC_OPERATOR_COMMA: return ",";
+
+      case LexicalUnit.SAC_OPERATOR_EXP: return "^";
+
+      case LexicalUnit.SAC_OPERATOR_GE: return ">=";
+
+      case LexicalUnit.SAC_OPERATOR_GT: return ">";
+
+      case LexicalUnit.SAC_OPERATOR_LE: return "<=";
+
+      case LexicalUnit.SAC_OPERATOR_LT: return "<";
+
+      case LexicalUnit.SAC_OPERATOR_MINUS: return "-";
+
+      case LexicalUnit.SAC_OPERATOR_MOD: return "%";
+
+      case LexicalUnit.SAC_OPERATOR_MULTIPLY: return "*";
+
+      case LexicalUnit.SAC_OPERATOR_PLUS: return "+";
+
+      case LexicalUnit.SAC_OPERATOR_SLASH: return "/";
+
+      case LexicalUnit.SAC_OPERATOR_TILDE: return "~";
+
+      case LexicalUnit.SAC_RGBCOLOR:
+        return
+          "rgb(" +
+            lexicalUnitChain(unit.getParameters(), identifiersToLower, baseUrl)
+            + ")";
+
+      case LexicalUnit.SAC_STRING_VALUE: return unit.getStringValue();
+
+      case LexicalUnit.SAC_URI:
+        try
+        {
+          return
+            "url(" +
+              (
+                baseUrl != null ?
+                  new URL(baseUrl, unit.getStringValue()).toString() :
+                  unit.getStringValue()
+              ) + ")";
+        }
+
+        catch (MalformedURLException e)
+        {
+          throw new RuntimeException(e);
+        }
+
+      default: return "";
+    }
+  }
+
+
+
+  static String
+  lexicalUnitAtomLower(LexicalUnit unit, URL baseUrl)
+  {
+    return
+      unit.getLexicalUnitType() == LexicalUnit.SAC_URI ?
+        lexicalUnitAtom(unit, baseUrl) :
+        lexicalUnitAtom(unit, baseUrl).toLowerCase();
+  }
+
+
+
+  static String[]
+  lexicalUnitAtoms(LexicalUnit unit, URL baseUrl)
+  {
+    return lexicalUnitAtoms(unit, false, baseUrl);
+  }
+
+
+
+  private static String[]
+  lexicalUnitAtoms(LexicalUnit unit, boolean lower, URL baseUrl)
+  {
+    LexicalUnit[]	values = lexicalUnitArray(unit);
+    String[]		result = new String[values.length];
+
+    for (int i = 0; i < values.length; ++i)
+    {
+      result[i] =
+        lower ?
+          lexicalUnitAtomLower(values[i], baseUrl) :
+          lexicalUnitAtom(values[i], baseUrl);
+    }
+
+    return result;
+  }
+
+
+
+  static String[]
+  lexicalUnitAtomsLower(LexicalUnit unit, URL baseUrl)
+  {
+    return lexicalUnitAtoms(unit, true, baseUrl);
+  }
+
+
+
+  private static String
+  lexicalUnitChain(LexicalUnit unit, boolean identifiersToLower, URL baseUrl)
+  {
+    return
+      lexicalUnitAtom(unit, identifiersToLower, baseUrl) +
+	(
+	  unit.getNextLexicalUnit() != null ?
+	    (
+              " " +
+                lexicalUnitChain
+                (
+                  unit.getNextLexicalUnit(),
+                  identifiersToLower,
+                  baseUrl
+                )
+            ) : ""
+	);
+  }
+
+
+
+  static String
+  lexicalUnitToString(LexicalUnit unit, boolean identifiersToLower, URL baseUrl)
+  {
+    return lexicalUnitChain(unit, identifiersToLower, baseUrl);
+  }
+
+
+
+  /**
+   * Adds <code>from</code> attributes to <code>into</code> giving precedence
+   * to the latter.
+   */
+
+  static Attributes
+  mergeAttributes(Attributes from, Attributes into)
+  {
+    return mergeAttributes(from, into, new String[0], false);
+  }
+
+
+
+  /**
+   * Adds <code>from</code> attributes to <code>into</code> giving precedence
+   * to the latter. If <code>include</code> is <code>true</code>, the attribute
+   * in <code>from</code> must be in <code>subset</code> in order for it to be
+   * included. If <code>include</code> is <code>false</code>, the attribute
+   * in <code>from</code> must not be in <code>subset</code> in order for it to
+   * be included.
+   */
+
+  static Attributes
+  mergeAttributes
+  (
+    Attributes	from,
+    Attributes	into,
+    String[]	subset,
+    boolean	include
+  )
+  {
+    AttributesImpl	result = new AttributesImpl(into);
+
+    for (int i = 0; i < from.getLength(); ++i)
+    {
+      if
+      (
+        into.getIndex(from.getURI(i), from.getLocalName(i)) == -1	&&
+        (
+          (
+            !include							&&
+            !inArray(subset, from.getLocalName(i))
+          )								||
+          (
+            include							&&
+            inArray(subset, from.getLocalName(i))
+          )
+        )
+      )
+      {
+        result.addAttribute
+        (
+          from.getURI(i),
+          from.getLocalName(i),
+          from.getQName(i),
+          from.getType(i),
+          from.getValue(i)
+        );
+      }
+    }
+
+    return result;
+  }
+
+
+
+  static void
+  printUserAgentParameters(PrintStream out)
+  {
+    System.err.println("User Agent parameters:");
+    System.err.println("  column-count (default: 1)");
+    System.err.println("  country (default: GB)");
+    System.err.
+      println("  font-size (default: 10pt for a5 and b5, otherwise 11pt)");
+    System.err.println("  html-header-mark: an HTML element (default: none)");
+    System.err.println("  language (default: en)");
+    System.err.println("  odd-even-shift (default: 10mm)");
+    System.err.
+      println("  orientation (default: portrait; other: landscape)");
+    System.err.println("  paper-margin-bottom (default: 0mm)");
+    System.err.println("  paper-margin-left (default: 25mm)");
+    System.err.println("  paper-margin-right (default: 25mm)");
+    System.err.println("  paper-margin-top (default: 10mm)");
+    System.err.println("  paper-mode (default: onesided; other: twosided)");
+
+    System.err.println
+    (
+      "  paper-size (default: a4; others: a0, a1, a2, a3, a5, b5, " +
+        "executive, letter and legal)"
+    );
+
+    System.err.println("  rule-thickness (default: 0.2pt)");
+    System.err.println("  writing-mode (default: lr-tb)");
+  }
+
+
+
+  private static String
+  processFontFamily(String value)
+  {
+    value = value.trim();
+
+    if
+    (
+      value.indexOf(' ') == -1				||
+      (
+        value.charAt(0) == '\''				&&
+        value.charAt(value.length() - 1) == '\''
+      )							||
+      (
+        value.charAt(0) == '"'				&&
+        value.charAt(value.length() - 1) == '"'
+      )
+    )
+    {
+      return value;
+    }
+
+    String		result = "";
+    StringTokenizer	tokenizer = new StringTokenizer(value, ",");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String	token = tokenizer.nextToken().trim();
+
+      result +=
+        (result.equals("") ? "" : ", ") +
+          (token.indexOf(' ') != -1 ? ("'" + token + "'") : token);
+    }
+
+    return result;
+  }
+
+
+
+  static void
+  removeAttribute(AttributesImpl atts, String namespaceURI, String localName)
+  {
+    int	index = atts.getIndex(namespaceURI, localName);
+
+    if (index != -1)
+    {
+      atts.removeAttribute(index);
+    }
+  }
+
+
+
+  static void
+  setAttribute
+  (
+    AttributesImpl	attributes,
+    String		namespaceURI,
+    String		localName,
+    String		qName,
+    String		value
+  )
+  {
+    int	index = attributes.getIndex(namespaceURI, localName);
+
+    if (index == -1)
+    {
+      attributes.addAttribute(namespaceURI, localName, qName, "CDATA", value);
+    }
+    else
+    {
+      attributes.setAttribute
+      (
+        index,
+        namespaceURI,
+        localName,
+        qName,
+        "CDATA",
+        value
+      );
+    }
+  }
+
+
+
+  /**
+   * If the value of a property is a call to the "attr" function and if the
+   * property is not "content", the call is replaced by the expanded attribute
+   * name in which the URI is separated from the local name by a |.
+   */
+
+  static void
+  setCSSAttribute(AttributesImpl attributes, Property property, int specificity)
+  {
+    String	propertyName = property.getName();
+    String	value;
+
+    if
+    (
+      !"content".equals(propertyName)			&&
+      property.getLexicalUnit() != null			&&
+      property.getLexicalUnit().getLexicalUnitType() ==
+        LexicalUnit.SAC_ATTR
+    )
+    {
+      value = property.getLexicalUnit().getStringValue();
+
+      int	index = value.lastIndexOf('|');
+
+      if (index != -1)
+      {
+        value =
+          (String) property.getPrefixMap().get(value.substring(0, index)) +
+            "|" + value.substring(index + 1);
+      }
+    }
+    else
+    {
+      value = property.getValue();
+    }
+
+    Util.setAttribute
+    (
+      attributes,
+      Constants.CSS,
+      propertyName,
+      "css:" + propertyName,
+      "font-family".equals(propertyName) ? processFontFamily(value) : value
+    );
+
+    // XHTML attributes are translated to CSS properties further down the
+    // filter chain. They get a specificity of 0 and a position before the
+    // other rules in the style sheet. Therefore, they can only overwrite
+    // property values selected by the universal selector or comming from the
+    // UA style sheet.
+
+    if (specificity <= 0) // Universal selector is 0, UA rules are < 0.
+    {
+      // Marked as eligible for replacement.
+
+      Util.setAttribute
+      (
+        attributes,
+        Constants.SPECIF,
+        propertyName,
+        "sp:" + propertyName,
+        "1"
+      );
+    }
+  }
+
+
+
+  static String
+  toFootnote(int v)
+  {
+    return v > FOOTNOTE_NUMBERS.length ? "*" : FOOTNOTE_NUMBERS[v - 1];
+  }
+
+
+
+  static String
+  toRoman(int v)
+  {
+    return
+      v < 1 ?
+        "" :
+        (
+          v < 4 ?
+            ("I" + toRoman(v - 1)) :
+            (
+              v < 5 ?
+                "IV" :
+                 (
+                   v < 9 ?
+                     ("V" + toRoman(v - 5)) :
+                     (
+                       v < 10 ?
+                         "IX" :
+                         (
+                           v < 40 ?
+                             ("X" + toRoman(v - 10)) :
+                             (
+                               v < 50 ?
+                                 ("XL" + toRoman(v - 40)) :
+                                 (
+                                   v < 90 ?
+                                     ("L" + toRoman(v - 50)) :
+                                     (
+                                       v < 100 ?
+                                         ("XC" + toRoman(v - 90)) :
+                                         (
+                                           v < 400 ?
+                                             ("C" + toRoman(v - 100)) :
+                                             (
+                                               v < 500 ?
+                                                 ("CD" + toRoman(v - 400)) :
+                                                 (
+                                                   v < 900 ?
+                                                     (
+                                                       "D" +
+                                                         toRoman(v - 500)
+                                                     ) :
+                                                     (
+                                                       v < 1000 ?
+                                                         (
+                                                           "CM" +
+                                                             toRoman(v - 900)
+                                                         ) :
+                                                         (
+                                                           "M" +
+                                                             toRoman(v - 1000)
+                                                         )
+                                                     )
+                                                 )
+                                             )
+                                         )
+                                     )
+                                 )
+                             )
+                         )
+                     )
+                 )
+            )
+        );
+  }
+
+
+
+  interface PostProjectionFilter
+
+  {
+
+    public XMLFilterImpl	getFilter	();
+    public void			setBaseUrl	(URL baseUrl);
+
+  } // BaseUrl
+
+} // Util
diff --git a/src/be/re/css/WidthAndMarginsFilter.java b/src/be/re/css/WidthAndMarginsFilter.java
new file mode 100644
index 0000000..2595367
--- /dev/null
+++ b/src/be/re/css/WidthAndMarginsFilter.java
@@ -0,0 +1,328 @@
+package be.re.css;
+
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Compensates the differences between CSS and XSL-FO with respect to
+ * horizontal margins and width. There should be no shorthand properties
+ * anymore.
+ * @author Werner Donne\u00e9
+ */
+
+class WidthAndMarginsFilter extends XMLFilterImpl
+
+{
+
+  private Stack	stack = new Stack();
+
+
+
+  WidthAndMarginsFilter()
+  {
+  }
+
+
+
+  WidthAndMarginsFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  private static String
+  adjustMargin(String margin, Attributes atts, String edge)
+  {
+    String	s;
+
+    return
+      "auto".equals(margin) ?
+        margin :
+        (
+          margin +
+            (Util.isZeroLength(s = getBorderWidth(atts, edge)) ? "" : ("+" + s))
+        );
+  }
+
+
+
+  private static String
+  canonicLength(String value)
+  {
+    return Util.isZeroLength(value) ? "0pt" : value;
+  }
+
+
+
+  private static Attributes
+  correctBlock(Attributes atts, Attributes parent)
+  {
+    AttributesImpl	result = new AttributesImpl(atts);
+
+    String	marginLeft = getImplicitZeroProperty(atts, "margin-left");
+    String	marginRight = getImplicitZeroProperty(atts, "margin-right");
+    String	width = atts.getValue(Constants.CSS, "width");
+
+    if ("auto".equals(width))
+    {
+      marginLeft = "0pt";
+      marginRight = "0pt";
+    }
+    else
+    {
+      if
+      (
+        width != null			&&
+        !width.equals("auto")		&&
+        !marginLeft.equals("auto")	&&
+        !marginRight.equals("auto")
+      ) // Over-constraint.
+      {
+        String	direction = atts.getValue(Constants.CSS, "direction");
+
+        if (direction == null || direction.equals("ltr"))
+        {
+          marginRight = "auto";
+        }
+        else
+        {
+          marginLeft = "auto";
+        }
+      }
+    }
+
+    if (marginLeft.equals("auto") && marginRight.equals("auto"))
+    {
+      marginRight = "0pt";
+    }
+
+    setValue(result, "margin-left", adjustMargin(marginLeft, atts, "left"));
+    setValue(result, "margin-right", adjustMargin(marginRight, atts, "right"));
+
+    if (width != null)
+    {
+      setValue(result, "width", width);
+    }
+
+    return result;
+  }
+
+
+
+  private static Attributes
+  correctInline(Attributes atts)
+  {
+    AttributesImpl	result = new AttributesImpl(atts);
+    int			index = result.getIndex(Constants.CSS, "width");
+
+    if (index != -1)
+    {
+      result.removeAttribute(index);
+    }
+
+    makeAutoExplicit(result, "margin-left");
+    makeAutoExplicit(result, "margin-right");
+
+    return result;
+  }
+
+
+
+  private static Attributes
+  correctFloat(Attributes atts)
+  {
+    AttributesImpl	result = new AttributesImpl(atts);
+
+    // The "width" property is not touched here because that could disable
+    // replaced elements (width="auto" should be set to "0"). We can't
+    // distinguish replaced and non-replaced elements here.
+
+    makeAutoExplicit(result, "margin-left");
+    makeAutoExplicit(result, "margin-right");
+
+    return result;
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    super.endElement(namespaceURI, localName, qName);
+    stack.pop();
+  }
+
+
+
+  private static String
+  getBorderWidth(Attributes atts, String edge)
+  {
+    String	value =
+      atts.getValue(Constants.CSS, "border-" + edge + "-width");
+
+    return
+      atts.getValue(Constants.CSS, "border-" + edge + "-style") == null ||
+        "none".equals
+        (
+          atts.getValue(Constants.CSS, "border-" + edge + "-style")
+        ) ?
+        "0pt" :
+        (
+          value == null ?
+            "0.6pt" : // medium
+            (
+              "thin".equals(value) ?
+                "0.2pt" :
+                (
+                  "medium".equals(value) ?
+                    "0.6pt" :
+                    ("thick".equals(value) ? "1pt" : canonicLength(value))
+                )
+            )
+        );
+  }
+
+
+
+  private static String
+  getImplicitZeroProperty(Attributes atts, String property)
+  {
+    String	value = atts.getValue(Constants.CSS, property);
+
+    return value == null ? "0pt" : canonicLength(value);
+  }
+
+
+
+  private static String
+  getPadding(Attributes atts, String edge)
+  {
+    String	value = atts.getValue(Constants.CSS, "padding-" + edge);
+
+    return value == null ? "0pt" : canonicLength(value);
+  }
+
+
+
+  private static void
+  makeAutoExplicit(AttributesImpl atts, String property)
+  {
+    int	index = atts.getIndex(Constants.CSS, property);
+
+    if (index == -1)
+    {
+      atts.addAttribute
+      (
+        Constants.CSS,
+        property,
+        "css:" + property,
+        "CDATA",
+        "0pt"
+      );
+    }
+    else
+    {
+      if ("auto".equals(atts.getValue(index)))
+      {
+        atts.setValue(index, "0pt");
+      }
+    }
+  }
+
+
+
+  private static void
+  setValue(AttributesImpl atts, String name, String value)
+  {
+    int	index = atts.getIndex(Constants.CSS, name);
+
+    if (index != -1)
+    {
+      atts.setValue(index, value);
+    }
+    else
+    {
+      atts.addAttribute(Constants.CSS, name, "css:" + name, "CDATA", value);
+    }
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    String	display = atts.getValue(Constants.CSS, "display");
+
+    if (display != null)
+    {
+      if (display.equals("inline"))
+      {
+        atts = correctInline(atts);
+      }
+      else
+      {
+        if
+        (
+          atts.getValue(Constants.CSS, "float") != null		&&
+          !"none".equals(atts.getValue(Constants.CSS, "float"))
+        )
+        {
+          atts = correctFloat(atts);
+        }
+        else
+        {
+          // Absolute and fixed positioning is left to the following processor
+          // because layout calculation results are needed.
+
+          if
+          (
+            Util.inArray
+            (
+              new String[]
+                {"block", "compact", "list-item", "run-in", "table"},
+              display
+            )								 &&
+            !"absolute".equals(atts.getValue(Constants.CSS, "position")) &&
+            !"fixed".equals(atts.getValue(Constants.CSS, "position"))	 &&
+            (
+              // If the parent is a table-cell we leave it (too complicated).
+              stack.isEmpty()						 ||
+              !"table-cell".equals
+              (
+                ((Attributes) stack.peek()).
+                  getValue(Constants.CSS, "display")
+              )
+            )
+          )
+          {
+            atts =
+              correctBlock
+              (
+                atts,
+                !stack.isEmpty() ?
+                  (Attributes) stack.peek() : new AttributesImpl()
+              );
+          }
+        }
+      }
+    }
+
+    stack.push(new AttributesImpl(atts));
+    super.startElement(namespaceURI, localName, qName, atts);
+  }
+
+} // WidthAndMarginsFilter
diff --git a/src/be/re/css/WrapperFilter.java b/src/be/re/css/WrapperFilter.java
new file mode 100644
index 0000000..0a87230
--- /dev/null
+++ b/src/be/re/css/WrapperFilter.java
@@ -0,0 +1,136 @@
+package be.re.css;
+
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * This filter removes elements with the display type "wrapper". The inherited
+ * properties on the element are propagated to its children.
+ * @author Werner Donn\u00e9
+ */
+
+class WrapperFilter extends XMLFilterImpl
+
+{
+
+  private Stack	elements = new Stack();
+
+
+
+  WrapperFilter()
+  {
+  }
+
+
+
+  WrapperFilter(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    if (!((Element) elements.pop()).remove)
+    {
+      super.endElement(namespaceURI, localName, qName);
+    }
+  }
+
+
+
+  private static Attributes
+  mergeInheritedProperties(Attributes atts, Attributes inheritedProperties)
+  {
+    return
+      inheritedProperties == null ?
+        atts : Util.mergeAttributes(inheritedProperties, atts);
+  }
+
+
+
+  private static Attributes
+  selectInheritedProperties(Attributes atts)
+  {
+    AttributesImpl	result = new AttributesImpl();
+
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if
+      (
+        Constants.CSS.equals(atts.getURI(i))	&&
+        Util.isInherited(atts.getLocalName(i))
+      )
+      {
+        result.addAttribute
+        (
+          atts.getURI(i),
+          atts.getLocalName(i),
+          atts.getQName(i),
+          atts.getType(i),
+          atts.getValue(i)
+        );
+      }
+    }
+
+    return result;
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    Element	element = new Element();
+
+    if ("wrapper".equals(atts.getValue(Constants.CSS, "display")))
+    {
+      element.remove = true;
+      element.inheritedProperties = selectInheritedProperties(atts);
+    }
+    else
+    {
+      super.startElement
+      (
+        namespaceURI,
+        localName,
+        qName,
+        mergeInheritedProperties
+        (
+          atts,
+          elements.isEmpty() ?
+            null : ((Element) elements.peek()).inheritedProperties
+        )
+      );
+    }
+
+    elements.push(element);
+  }
+
+
+
+  private static class Element
+
+  {
+
+    private Attributes	inheritedProperties;
+    private boolean	remove;
+
+  } // Element
+
+} // WrapperFilter
diff --git a/src/be/re/css/XHTMLAttributeTranslationFilter.java b/src/be/re/css/XHTMLAttributeTranslationFilter.java
new file mode 100644
index 0000000..3172b51
--- /dev/null
+++ b/src/be/re/css/XHTMLAttributeTranslationFilter.java
@@ -0,0 +1,837 @@
+package be.re.css;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * This filter translates XHTML attributes to the corresponding CSS properties.
+ * @author Werner Donn\u00e9
+ */
+
+class XHTMLAttributeTranslationFilter extends XMLFilterImpl
+
+{
+
+  private static final String	COL = "col".intern();
+  private static final String	COLGROUP = "colgroup".intern();
+  private static final String	TABLE = "table".intern();
+  private static final String	TBODY = "tbody".intern();
+  private static final String	TFOOT = "tfoot".intern();
+  private static final String	THEAD = "thead".intern();
+  private static final String	TD = "td".intern();
+  private static final String	TH = "th".intern();
+  private static final String	TR = "tr".intern();
+
+  private static final Map	map =
+    loadTable
+    (
+      new String[][]
+      { // element, attribute, attribute value, CSS property, CSS property value
+        {"applet", "align", "bottom", "vertical-align", "bottom"},
+        {"applet", "align", "left", "text-align", "left"},
+        {"applet", "align", "middle", "vertical-align", "middle"},
+        {"applet", "align", "right", "text-align", "right"},
+        {"applet", "align", "top", "vertical-align", "top"},
+        {"applet", "height", null, "height", null},
+        {"applet", "hspace", null, "margin-left", null},
+        {"applet", "hspace", null, "margin-right", null},
+        {"applet", "vspace", null, "margin-bottom", null},
+        {"applet", "vspace", null, "margin-top", null},
+        {"applet", "width", null, "width", null},
+        {"body", "background", null, "background-image", null},
+        {"body", "text", null, "color", null},
+        {"caption", "align", null, "caption-side", null},
+        {"col", "align", "center", "text-align", "center"},
+        {"col", "align", "char", "text-align", "@char;."},
+        {"col", "align", "justify", "text-align", "justify"},
+        {"col", "align", "left", "text-align", "left"},
+        {"col", "align", "right", "text-align", "right"},
+        {"col", "span", null, "span", null},
+        {"col", "valign", null, "vertical-align", null},
+        {"col", "width", null, "width", null},
+        {"colgroup", "align", "center", "text-align", "center"},
+        {"colgroup", "align", "char", "text-align", "@char;."},
+        {"colgroup", "align", "justify", "text-align", "justify"},
+        {"colgroup", "align", "left", "text-align", "left"},
+        {"colgroup", "align", "right", "text-align", "right"},
+        {"colgroup", "span", null, "span", null},
+        {"colgroup", "valign", null, "vertical-align", null},
+        {"colgroup", "width", null, "width", null},
+        {"div", "align", null, "text-align", null},
+        {"font", "color", null, "color", null},
+        {"font", "face", null, "font-family", null},
+        {"font", "size", null, "font-size", "f:fontSize"},
+        {"h1", "align", null, "text-align", null},
+        {"h2", "align", null, "text-align", null},
+        {"h3", "align", null, "text-align", null},
+        {"h4", "align", null, "text-align", null},
+        {"h5", "align", null, "text-align", null},
+        {"h6", "align", null, "text-align", null},
+        {"hr", "align", null, "text-align", null},
+        {"hr", "noshade", null, "border-bottom-style", "solid"},
+        {"hr", "noshade", null, "border-left-style", "solid"},
+        {"hr", "noshade", null, "border-right-style", "solid"},
+        {"hr", "noshade", null, "border-top-style", "solid"},
+        {"hr", "size", null, "height", null},
+        {"hr", "width", null, "width", null},
+        {"img", "border", null, "border-bottom-width", null},
+        {"img", "border", null, "border-left-width", null},
+        {"img", "border", null, "border-right-width", null},
+        {"img", "border", null, "border-top-width", null},
+        {"img", "border", null, "border-bottom-style", "solid"},
+        {"img", "border", null, "border-left-style", "solid"},
+        {"img", "border", null, "border-right-style", "solid"},
+        {"img", "border", null, "border-top-style", "solid"},
+        {"img", "border", null, "border-after-width.conditionality", "retain"},
+        {"img", "border", null, "border-before-width.conditionality", "retain"},
+        {"img", "height", null, "height", null},
+        {"img", "hspace", null, "margin-left", null},
+        {"img", "hspace", null, "margin-right", null},
+        {"img", "vspace", null, "margin-bottom", null},
+        {"img", "vspace", null, "margin-top", null},
+        {"img", "width", null, "width", null},
+        {"input", "align", null, "text-align", null},
+        {"object", "border", null, "border-bottom-width", null},
+        {"object", "border", null, "border-left-width", null},
+        {"object", "border", null, "border-right-width", null},
+        {"object", "border", null, "border-top-width", null},
+        {"object", "border", null, "border-bottom-style", "solid"},
+        {"object", "border", null, "border-left-style", "solid"},
+        {"object", "border", null, "border-right-style", "solid"},
+        {"object", "border", null, "border-top-style", "solid"},
+        {
+          "object", "border", null, "border-after-width.conditionality",
+            "retain"
+        },
+        {
+          "object", "border", null, "border-before-width.conditionality",
+            "retain"
+        },
+        {"li", "compact", null, "list-style-position", "inside"},
+        {"li", "type", null, "list-style-type", null},
+        {"object", "height", null, "height", null},
+        {"object", "hspace", null, "margin-left", null},
+        {"object", "hspace", null, "margin-right", null},
+        {"object", "vspace", null, "margin-bottom", null},
+        {"object", "vspace", null, "margin-top", null},
+        {"object", "width", null, "width", null},
+        {"ol", "compact", null, "list-style-position", "inside"},
+        {"ol", "type", "1", "list-style-type", "decimal"},
+        {"ol", "type", "a", "list-style-type", "lower-alpha"},
+        {"ol", "type", "A", "list-style-type", "upper-alpha"},
+        {"ol", "type", "i", "list-style-type", "lower-roman"},
+        {"ol", "type", "I", "list-style-type", "upper-roman"},
+        {"p", "align", null, "text-align", null},
+        {"span", "align", null, "text-align", null},
+        {"table", "width", null, "width", null},
+        {"tbody", "align", "center", "text-align", "center"},
+        {"tbody", "align", "char", "text-align", "@char;."},
+        {"tbody", "align", "justify", "text-align", "justify"},
+        {"tbody", "align", "left", "text-align", "left"},
+        {"tbody", "align", "right", "text-align", "right"},
+        {"tbody", "valign", null, "vertical-align", null},
+        {"td", "align", "center", "text-align", "center"},
+        {"td", "align", "char", "text-align", "@char;."},
+        {"td", "align", "justify", "text-align", "justify"},
+        {"td", "align", "left", "text-align", "left"},
+        {"td", "align", "right", "text-align", "right"},
+        {"td", "colspan", null, "colspan", null},
+        {"td", "height", null, "height", null},
+        {"td", "nowrap", null, "white-space", "nowrap"},
+        {"td", "rowspan", null, "rowspan", null},
+        {"td", "valign", null, "vertical-align", null},
+        {"td", "width", null, "width", null},
+        {"tfoot", "align", "center", "text-align", "center"},
+        {"tfoot", "align", "char", "text-align", "@char;."},
+        {"tfoot", "align", "justify", "text-align", "justify"},
+        {"tfoot", "align", "left", "text-align", "left"},
+        {"tfoot", "align", "right", "text-align", "right"},
+        {"tfoot", "valign", null, "vertical-align", null},
+        {"th", "align", "center", "text-align", "center"},
+        {"th", "align", "char", "text-align", "@char;."},
+        {"th", "align", "justify", "text-align", "justify"},
+        {"th", "align", "left", "text-align", "left"},
+        {"th", "align", "right", "text-align", "right"},
+        {"th", "colspan", null, "colspan", null},
+        {"th", "height", null, "height", null},
+        {"th", "nowrap", null, "white-space", "nowrap"},
+        {"th", "rowspan", null, "rowspan", null},
+        {"th", "valign", null, "vertical-align", null},
+        {"th", "width", null, "width", null},
+        {"thead", "align", "center", "text-align", "center"},
+        {"thead", "align", "char", "text-align", "@char;."},
+        {"thead", "align", "justify", "text-align", "justify"},
+        {"thead", "align", "left", "text-align", "left"},
+        {"thead", "align", "right", "text-align", "right"},
+        {"thead", "valign", null, "vertical-align", null},
+        {"tr", "align", "center", "text-align", "center"},
+        {"tr", "align", "char", "text-align", "@char;."},
+        {"tr", "align", "justify", "text-align", "justify"},
+        {"tr", "align", "left", "text-align", "left"},
+        {"tr", "align", "right", "text-align", "right"},
+        {"tr", "bgcolor", null, "background-color", null},
+        {"tr", "valign", null, "vertical-align", null},
+        {"ul", "compact", null, "list-style-position", "inside"},
+        {"ul", "type", null, "list-style-type", null}
+      }
+    );
+
+  private String	defaultBorderThickness;
+  private Stack		elementStack = new Stack();
+  private Stack		tableStack = new Stack();
+
+
+
+  XHTMLAttributeTranslationFilter()
+  {
+    this("0.2pt");
+  }
+
+
+
+  XHTMLAttributeTranslationFilter(String defaultBorderThickness)
+  {
+    this.defaultBorderThickness = defaultBorderThickness;
+  }
+
+
+
+  XHTMLAttributeTranslationFilter(XMLReader parent)
+  {
+    this(parent, "0.2pt");
+  }
+
+
+
+  XHTMLAttributeTranslationFilter
+  (
+    XMLReader	parent,
+    String	defaultBorderThickness
+  )
+  {
+    super(parent);
+    this.defaultBorderThickness = defaultBorderThickness;
+  }
+
+
+
+  private static String
+  callPropertyResolver
+  (
+    String	function,
+    String	element,
+    Attributes	atts,
+    String	attribute,
+    String	value
+  )
+  {
+    try
+    {
+      return
+        (String)
+          XHTMLAttributeTranslationFilter.class.getDeclaredMethod
+          (
+            function,
+            new Class[]
+              {String.class, Attributes.class, String.class, String.class}
+          ).invoke(null, new Object[] {element, atts, attribute, value});
+    }
+
+    catch (Exception e)
+    {
+      return value;
+    }
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    if (Constants.XHTML == namespaceURI)
+    {
+      elementStack.pop();
+
+      if (TABLE == localName)
+      {
+        tableStack.pop();
+      }
+    }
+
+    super.endElement(namespaceURI, localName, qName);
+  }
+
+
+
+  private static boolean
+  equivalentSibling(String element1, String element2)
+  {
+    return
+      element1 == element2 ||
+        (
+          (THEAD == element1 || TBODY == element1 || TFOOT == element1) &&
+            (THEAD == element2 || TBODY == element2 || TFOOT == element2)
+        );
+  }
+
+
+
+  private static String
+  fontSize(String element, Attributes atts, String attribute, String value)
+  {
+    try
+    {
+      return
+        value.startsWith("+") ?
+          (
+            String.valueOf
+            (
+              (int)
+                (
+                  100.0 *
+                    (100 + 10 * Integer.parseInt(value.substring(1))) / 100
+                )
+            ) + "%"
+          ) :
+          (
+            value.startsWith("-") ?
+              (
+                String.valueOf
+                (
+                  (int)
+                    (
+                      100.0 *
+                        (100 - 10 * Integer.parseInt(value.substring(1))) / 100
+                    )
+                ) + "%"
+              ) : (String.valueOf((Integer.parseInt(value) + 7)) + "pt")
+          );
+    }
+
+    catch (Exception e)
+    {
+      return "100%";
+    }
+  }
+
+
+
+  private static Map
+  loadTable(String[][] table)
+  {
+    Map	result = new HashMap();
+
+    for (int i = 0; i < table.length; ++i)
+    {
+      String	key = table[i][0] + "#" + table[i][1];
+      List	tuples = (List) result.get(key);
+
+      if (tuples == null)
+      {
+        tuples = new ArrayList();
+        result.put(key, tuples);
+      }
+
+      tuples.add(new Tuple(table[i][2], table[i][3], table[i][4]));
+    }
+
+    return result;
+  }
+
+
+
+  private static Tuple[]
+  lookup(String element, Attributes atts, String attribute, String value)
+  {
+    List	tuples = (List) map.get(element + "#" + attribute);
+
+    if (tuples == null)
+    {
+      return new Tuple[0];
+    }
+
+    List	result = new ArrayList();
+
+    for (int i = 0; i < tuples.size(); ++i)
+    {
+      Tuple	tuple = (Tuple) tuples.get(i);
+
+      if (tuple.inValue == null || tuple.inValue.equals(value))
+      {
+        String	otherAttribute;
+
+        result.add
+        (
+          new Tuple
+          (
+            tuple.inValue,
+            tuple.property,
+            tuple.outValue == null ?
+              value :
+              (
+                tuple.outValue.charAt(0) == '@' ?
+                  (
+                    (
+                      otherAttribute =
+                        atts.getValue
+                        (
+                          tuple.outValue.
+                            substring(1, tuple.outValue.indexOf(';'))
+                        )
+                    ) != null ?
+                      otherAttribute :
+                      tuple.outValue.substring(tuple.outValue.indexOf(';') + 1)
+                  ) :
+                  (
+                    tuple.outValue.startsWith("f:") ?
+                      callPropertyResolver
+                      (
+                        tuple.outValue.substring(2),
+                        element,
+                        atts,
+                        attribute,
+                        value
+                      ) : tuple.outValue
+                  )
+              )
+          )
+        );
+      }
+    }
+
+    return (Tuple[]) result.toArray(new Tuple[result.size()]);
+  }
+
+
+
+  private static void
+  mergeAttribute
+  (
+    AttributesImpl	atts,
+    String		originalName,
+    String		cssName,
+    String		value
+  )
+  {
+    int	index1 = atts.getIndex(Constants.CSS, cssName);
+
+    if (index1 == -1)
+    {
+      atts.
+        addAttribute(Constants.CSS, cssName, "css:" + cssName, "CDATA", value);
+    }
+    else
+    {
+      int	index2 = atts.getIndex(Constants.SPECIF, cssName);
+
+      if (index2 != -1)
+      {
+        atts.setValue(index1, value);
+      }
+    }
+
+    if (originalName != null)
+    {
+      index1 = atts.getIndex(originalName);
+
+      if (index1 != -1)
+      {
+        atts.removeAttribute(index1);
+      }
+    }
+  }
+
+
+
+  private AttributesImpl
+  prepareTableAttributes(String localName, Attributes atts)
+  {
+    return
+      TD == localName || TH == localName ?
+         preprocessTableCell
+         (
+           localName,
+           atts,
+           new String[] {"all", "cols"},
+           "left"
+         ) :
+         (
+           TR == localName ?
+             preprocessRulesBorder
+             (
+               localName,
+               atts,
+               new String[] {"all", "rows"},
+               "top"
+             ) :
+             (
+               COL == localName ?
+                 preprocessRulesBorder
+                 (
+                   localName,
+                   atts,
+                   new String[] {"all", "cols"},
+                   "left"
+                 ) :
+                 (
+                   TABLE == localName ?
+                     preprocessTableBorder(atts) :
+                     (
+                       THEAD == localName || TFOOT == localName ||
+                         TBODY == localName ?
+                         preprocessRulesBorder
+                         (
+                           localName,
+                           atts,
+                           new String[] {"all", "rows", "groups"},
+                           "top"
+                         ) : 
+                         (
+                           COLGROUP == localName ?
+                             preprocessRulesBorder
+                             (
+                               localName,
+                               atts,
+                               new String[] {"groups"},
+                               "left"
+                             ) : new AttributesImpl(atts)
+                         )
+                     )
+                )
+            )
+        );
+  }
+
+
+
+  private AttributesImpl
+  preprocessRulesBorder
+  (
+    String	localName,
+    Attributes	atts,
+    String[]	rulesValues,
+    String	borderSide
+  )
+  {
+    Element		table = (Element) tableStack.peek();
+    String		border = table.atts.getValue("border");
+    String		rules = table.atts.getValue("rules");
+    AttributesImpl	result = new AttributesImpl(atts);
+
+    if ("0".equals(border) || "none".equals(rules))
+    {
+      return result;
+    }
+
+    String	borderWidth =
+      border == null ? defaultBorderThickness : (border + "px");
+    Element	parent = (Element) elementStack.peek();
+
+    if
+    (
+      (
+        (
+          border != null						&&
+          rules == null
+        )								||
+        Util.inArray(rulesValues, rules)
+      )									&&
+      equivalentSibling(localName, ((Preceding) parent.extra).element)	&&
+      ((Preceding) parent.extra).count > 0
+    )
+    {
+      mergeAttribute
+      (
+        result,
+        "border",
+        "border-" + borderSide + "-width",
+        borderWidth
+      );
+
+      mergeAttribute
+      (
+        result,
+        null, // Removed already.
+        "border-" + borderSide + "-style",
+        "solid"
+      );
+
+      if ("bottom".equals(borderSide))
+      {
+        mergeAttribute
+        (
+          result,
+          null, // Removed already.
+          "border-after-width.conditionality",
+          "retain"
+        );
+      }
+
+      if ("top".equals(borderSide))
+      {
+        mergeAttribute
+        (
+          result,
+          null, // Removed already.
+          "border-before-width.conditionality",
+          "retain"
+        );
+      }
+    }
+
+    return result;
+  }
+
+
+
+  private AttributesImpl
+  preprocessTableBorder(Attributes atts)
+  {
+    Element		table = (Element) tableStack.peek();
+    String		border = table.atts.getValue("border");
+    String		frame = table.atts.getValue("frame");
+    AttributesImpl	result = new AttributesImpl(atts);
+
+    if ("0".equals(border) || "void".equals(frame))
+    {
+      return result;
+    }
+
+    String	borderWidth =
+      border == null ? defaultBorderThickness : (border + "px");
+
+    if
+    (
+      (
+        border != null							&&
+        frame == null
+      )									||
+      Util.inArray(new String[] {"above", "hsides", "box", "border"}, frame)
+    )
+    {
+      mergeAttribute(result, "border", "border-top-width", borderWidth);
+      mergeAttribute(result, null, "border-top-style", "solid");
+
+      mergeAttribute
+      (
+        result,
+        null, // Removed already
+        "border-before-width.conditionality",
+        "retain"
+      );
+    }
+
+    if
+    (
+      (
+        border != null							&&
+        frame == null
+      )									||
+      Util.inArray(new String[] {"below", "hsides", "box", "border"}, frame)
+    )
+    {
+      mergeAttribute(result, "border", "border-bottom-width", borderWidth);
+      mergeAttribute(result, null, "border-bottom-style", "solid");
+
+      mergeAttribute
+      (
+        result,
+        null, // Removed already
+        "border-after-width.conditionality",
+        "retain"
+      );
+    }
+
+    if
+    (
+      (
+        border != null							&&
+        frame == null
+      )									||
+      Util.inArray(new String[] {"lhs", "vsides", "box", "border"}, frame)
+    )
+    {
+      mergeAttribute(result, "border", "border-left-width", borderWidth);
+      mergeAttribute(result, null, "border-left-style", "solid");
+    }
+
+    if
+    (
+      (
+        border != null							&&
+        frame == null
+      )									||
+      Util.inArray(new String[] {"rhs", "vsides", "box", "border"}, frame)
+    )
+    {
+      mergeAttribute(result, "border", "border-right-width", borderWidth);
+      mergeAttribute(result, null, "border-right-style", "solid");
+    }
+
+    return result;
+  }
+
+
+
+  private AttributesImpl
+  preprocessTableCell
+  (
+    String	localName,
+    Attributes	atts,
+    String[]	rulesValues,
+    String	borderSide
+  )
+  {
+    // If there are columns the normal column propagation can take place. Else
+    // we should place the borders directly on the cells.
+
+    return
+      preprocessRulesBorder
+      (
+        localName,
+        preprocessTableCellPaddingAndSpacing(atts),
+        rulesValues,
+        borderSide
+      );
+  }
+
+
+
+  private AttributesImpl
+  preprocessTableCellPaddingAndSpacing(Attributes atts)
+  {
+    Element		table = (Element) tableStack.peek();
+    String		padding = table.atts.getValue("cellpadding");
+    AttributesImpl	result = new AttributesImpl(atts);
+    String		spacing = table.atts.getValue("cellspacing");
+
+    if (padding != null)
+    {
+      mergeAttribute(result, null, "padding-top", padding);
+      mergeAttribute(result, null, "padding-bottom", padding);
+      mergeAttribute(result, null, "padding-left", padding);
+      mergeAttribute(result, null, "padding-right", padding);
+    }
+
+    if (spacing != null)
+    {
+      mergeAttribute(result, null, "margin-top", spacing);
+      mergeAttribute(result, null, "margin-bottom", spacing);
+      mergeAttribute(result, null, "margin-left", spacing);
+      mergeAttribute(result, null, "margin-right", spacing);
+    }
+
+    return result;
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    if (Constants.XHTML == namespaceURI)
+    {
+      Element	element = new Element(namespaceURI, localName, qName, atts);
+
+      if (TABLE == localName)
+      {
+        element.extra = new Boolean(false); // No columns seen yet.
+        tableStack.push(element);
+      }
+
+      AttributesImpl	newAtts = prepareTableAttributes(localName, atts);
+
+      for (int i = 0; i < atts.getLength(); ++i)
+      {
+        if (atts.getURI(i).equals(""))
+        {
+          Tuple[]	tuples =
+            lookup(localName, atts, atts.getLocalName(i), atts.getValue(i));
+
+          for (int j = 0; j < tuples.length; ++j)
+          {
+            mergeAttribute
+            (
+              newAtts,
+              atts.getLocalName(i),
+              tuples[j].property,
+              tuples[j].outValue
+            );
+          }
+        }
+      }
+
+      Element	parent =
+        elementStack.empty() ? null : (Element) elementStack.peek();
+
+      if (parent != null)
+      {
+        ((Preceding) parent.extra).count =
+          equivalentSibling(localName, ((Preceding) parent.extra).element) ?
+            (((Preceding) parent.extra).count + 1) :
+            1;
+        ((Preceding) parent.extra).element = localName;
+      }
+
+      element.extra = new Preceding();
+      elementStack.push(element);
+
+      super.startElement(namespaceURI, localName, qName, newAtts);
+    }
+    else
+    {
+      super.startElement(namespaceURI, localName, qName, atts);
+    }
+  }
+
+
+
+  private static class Preceding
+
+  {
+
+    private int		count = 0;
+    private String	element;
+
+  } // Preceding
+
+
+
+  private static class Tuple
+
+  {
+
+    private String	inValue;
+    private String	outValue;
+    private String	property;
+
+
+
+    private
+    Tuple(String inValue, String property, String outValue)
+    {
+      this.inValue = inValue;
+      this.property = property;
+      this.outValue = outValue;
+    }
+
+  } // Tuple
+
+} // XHTMLAttributeTranslationFilter
diff --git a/src/be/re/css/ant/CSSToFOP.java b/src/be/re/css/ant/CSSToFOP.java
new file mode 100644
index 0000000..c1755fa
--- /dev/null
+++ b/src/be/re/css/ant/CSSToFOP.java
@@ -0,0 +1,81 @@
+package be.re.css.ant;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import org.apache.fop.apps.Driver;
+import org.apache.fop.apps.Options;
+import org.apache.tools.ant.BuildException;
+
+
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToFOP extends TaskBase
+
+{
+
+  private File		configuration = null;
+  private boolean	quite = false;
+
+
+
+  public void
+  execute() throws BuildException
+  {
+    try
+    {
+      if (configuration != null)
+      {
+        new Options(configuration);
+      }
+
+      be.re.css.CSSToFOP.convert
+      (
+        input.openStream(),
+        new FileOutputStream(output),
+        baseUrl != null ? baseUrl : input,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToFOP.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        output.toString().toLowerCase().endsWith(".pdf") ?
+          Driver.RENDER_PDF :
+          (
+            output.toString().toLowerCase().endsWith(".ps") ?
+              Driver.RENDER_PS :
+              (
+                output.toString().toLowerCase().endsWith(".svg") ?
+                  Driver.RENDER_SVG : -1
+              )
+          ),
+        true,
+        validate
+      );
+    }
+
+    catch (Exception e)
+    {
+      e.printStackTrace();
+      throw new BuildException(e);
+    }
+  }
+
+
+
+  public void
+  setConfig(File value)
+  {
+    configuration = value;
+  }
+
+
+
+  public void
+  setQuite(boolean value)
+  {
+    quite = value;
+  }
+
+} // CSSToFOP
diff --git a/src/be/re/css/ant/CSSToFOPNew.java b/src/be/re/css/ant/CSSToFOPNew.java
new file mode 100644
index 0000000..bba7bcd
--- /dev/null
+++ b/src/be/re/css/ant/CSSToFOPNew.java
@@ -0,0 +1,65 @@
+package be.re.css.ant;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import org.apache.tools.ant.BuildException;
+
+
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToFOPNew extends TaskBase
+
+{
+
+  private File	configuration = null;
+
+
+
+  public void
+  execute() throws BuildException
+  {
+    try
+    {
+      be.re.css.CSSToFOPNew.convert
+      (
+        input.openStream(),
+        new FileOutputStream(output),
+        baseUrl != null ? baseUrl : input,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToFOPNew.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        output.toString().toLowerCase().endsWith(".pdf") ?
+          "application/pdf" :
+          (
+            output.toString().toLowerCase().endsWith(".ps") ?
+              "application/postscript" :
+              (
+                output.toString().toLowerCase().endsWith(".svg") ?
+                  "image/svg+xml" : null
+              )
+          ),
+        configuration,
+        validate
+      );
+    }
+
+    catch (Exception e)
+    {
+      e.printStackTrace();
+      throw new BuildException(e);
+    }
+  }
+
+
+
+  public void
+  setConfig(File value)
+  {
+    value = configuration;
+  }
+
+} // CSSToFOPNew
diff --git a/src/be/re/css/ant/CSSToXEP.java b/src/be/re/css/ant/CSSToXEP.java
new file mode 100644
index 0000000..5b0f08a
--- /dev/null
+++ b/src/be/re/css/ant/CSSToXEP.java
@@ -0,0 +1,71 @@
+package be.re.css.ant;
+
+import java.io.FileOutputStream;
+import java.net.URL;
+import org.apache.tools.ant.BuildException;
+
+
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToXEP extends TaskBase
+
+{
+
+  private URL		configuration = null;
+  private boolean	quiet = false;
+
+
+
+  public void
+  execute() throws BuildException
+  {
+    try
+    {
+      be.re.css.CSSToXEP.convert
+      (
+        input.openStream(),
+        new FileOutputStream(output),
+        baseUrl != null ? baseUrl : input,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToXEP.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        output.toString().toLowerCase().endsWith(".pdf") ?
+          be.re.css.CSSToXEP.PDF :
+          (
+            output.toString().toLowerCase().endsWith(".ps") ?
+              be.re.css.CSSToXEP.POSTSCRIPT : -1
+          ),
+        validate,
+        quiet,
+        configuration
+      );
+    }
+
+    catch (Exception e)
+    {
+      e.printStackTrace();
+      throw new BuildException(e);
+    }
+  }
+
+
+
+  public void
+  setConfig(String value)
+  {
+    configuration = createUrl(value);
+  }
+
+
+
+  public void
+  setQuiet(boolean value)
+  {
+    quiet = value;
+  }
+
+} // CSSToXEP
diff --git a/src/be/re/css/ant/CSSToXSLFO.java b/src/be/re/css/ant/CSSToXSLFO.java
new file mode 100644
index 0000000..36c5a85
--- /dev/null
+++ b/src/be/re/css/ant/CSSToXSLFO.java
@@ -0,0 +1,42 @@
+package be.re.css.ant;
+
+import java.io.FileOutputStream;
+import org.apache.tools.ant.BuildException;
+
+
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToXSLFO extends TaskBase
+
+{
+
+  public void
+  execute() throws BuildException
+  {
+    try
+    {
+      be.re.css.CSSToXSLFO.convert
+      (
+        input.openStream(),
+        new FileOutputStream(output),
+        baseUrl != null ? baseUrl : input,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToXSLFO.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        validate,
+        false
+      );
+    }
+
+    catch (Exception e)
+    {
+      e.printStackTrace();
+      throw new BuildException(e);
+    }
+  }
+
+} // CSSToXSLFO
diff --git a/src/be/re/css/ant/CSSToXSLFormatter.java b/src/be/re/css/ant/CSSToXSLFormatter.java
new file mode 100644
index 0000000..ba0b6c1
--- /dev/null
+++ b/src/be/re/css/ant/CSSToXSLFormatter.java
@@ -0,0 +1,61 @@
+package be.re.css.ant;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import jp.co.antenna.XfoJavaCtl.XfoObj;
+import org.apache.tools.ant.BuildException;
+
+
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToXSLFormatter extends TaskBase
+
+{
+
+  public void
+  execute() throws BuildException
+  {
+    File	f = null;
+
+    try
+    {
+      f = File.createTempFile("be.re.css.", "css2xslformatter");
+      f.deleteOnExit();
+
+      be.re.css.CSSToXSLFO.convert
+      (
+        input.openStream(),
+        new FileOutputStream(f),
+        baseUrl != null ? baseUrl : input,
+        userAgentStyleSheet,
+        catalog != null ?
+          catalog : CSSToXSLFormatter.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        validate,
+        false
+      );
+
+      new XfoObj().render(new FileInputStream(f), new FileOutputStream(output));
+    }
+
+    catch (Exception e)
+    {
+      e.printStackTrace();
+      throw new BuildException(e);
+    }
+
+    finally
+    {
+      if (f != null)
+      {
+        f.delete();
+      }
+    }
+  }
+
+} // CSSToXSLFormatter
diff --git a/src/be/re/css/ant/CSSToXinc.java b/src/be/re/css/ant/CSSToXinc.java
new file mode 100644
index 0000000..d9b8275
--- /dev/null
+++ b/src/be/re/css/ant/CSSToXinc.java
@@ -0,0 +1,42 @@
+package be.re.css.ant;
+
+import java.io.FileOutputStream;
+import org.apache.tools.ant.BuildException;
+
+
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class CSSToXinc extends TaskBase
+
+{
+
+  public void
+  execute() throws BuildException
+  {
+    try
+    {
+      be.re.css.CSSToXinc.convert
+      (
+        input.openStream(),
+        new FileOutputStream(output),
+        baseUrl != null ? baseUrl : input,
+        userAgentStyleSheet,
+        catalog != null ? catalog : CSSToXinc.class.getResource("/catalog"),
+        parameters,
+        preprocessors,
+        be.re.css.CSSToXinc.PDF,
+        validate
+      );
+    }
+
+    catch (Exception e)
+    {
+      e.printStackTrace();
+      throw new BuildException(e);
+    }
+  }
+
+} // CSSToXinc
diff --git a/src/be/re/css/ant/TaskBase.java b/src/be/re/css/ant/TaskBase.java
new file mode 100644
index 0000000..52eee3a
--- /dev/null
+++ b/src/be/re/css/ant/TaskBase.java
@@ -0,0 +1,164 @@
+package be.re.css.ant;
+
+import be.re.util.Array;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.tools.ant.Task;
+
+
+
+/**
+ * The base class for all CSSToXSLFO tasks.
+ * @author Werner Donn\u00e9
+ */
+
+public abstract class TaskBase extends Task
+
+{
+
+  protected URL		baseUrl;
+  protected URL		catalog;
+  protected URL		input;
+  protected File	output;
+  protected Map		parameters = new HashMap();
+  protected URL[]	preprocessors = new URL[0];
+  protected URL		userAgentStyleSheet = null;
+  protected boolean	validate = false;
+
+
+
+  public Parameter
+  createParameter()
+  {
+    return new Parameter();
+  }
+
+
+
+  public Preprocessor
+  createPreprocessor()
+  {
+    return new Preprocessor();
+  }
+
+
+
+  protected URL
+  createUrl(String s)
+  {
+    try
+    {
+      return
+        be.re.net.Util.isUrl(s) ?
+          new URL(s) : be.re.net.Util.fileToUrl(new File(s));
+    }
+
+    catch (MalformedURLException e)
+    {
+      throw new RuntimeException(e);
+    }
+  }
+
+
+
+  public void
+  setBaseurl(String value)
+  {
+    baseUrl = createUrl(value);
+  }
+
+
+
+  public void
+  setCatalog(String value)
+  {
+    catalog = createUrl(value);
+  }
+
+
+
+  public void
+  setInput(String value)
+  {
+    input = createUrl(value);
+  }
+
+
+
+  public void
+  setOutput(File value)
+  {
+    output = value;
+  }
+
+
+
+  public void
+  setUseragentstylesheet(String value)
+  {
+    userAgentStyleSheet = createUrl(value);
+  }
+
+
+
+  public void
+  setValidate(boolean value)
+  {
+    validate = value;
+  }
+
+
+
+  public class Parameter
+
+  {
+
+    private String	name = null;
+    private String	value = null;
+
+
+
+    public void
+    setName(String value)
+    {
+      name = value;
+
+      if (this.value != null)
+      {
+        parameters.put(name, this.value);
+      }
+    }
+
+
+
+    public void
+    setValue(String value)
+    {
+      this.value = value;
+
+      if (name != null)
+      {
+        parameters.put(name, this.value);
+      }
+    }
+
+  } // Parameter
+
+
+
+  public class Preprocessor
+
+  {
+
+    public void
+    setStylesheet(String value)
+    {
+      preprocessors = (URL[]) Array.append(preprocessors, createUrl(value));
+    }
+
+  } // Preprocessor
+
+} // TaskBase
diff --git a/src/be/re/css/res/space_correction.prop b/src/be/re/css/res/space_correction.prop
new file mode 100644
index 0000000..ca5db67
--- /dev/null
+++ b/src/be/re/css/res/space_correction.prop
@@ -0,0 +1,12 @@
+#Should only have entries for U+2000 through U+200A. The rest is ignored.
+2000=0.5em
+2001=1em
+2002=0.5em
+2003=1em
+2004=0.33em
+2005=0.25em
+2006=0.16em
+2007=
+2008=
+2009=0.2em
+200A=0.1em
diff --git a/src/be/re/css/style/css.xsl b/src/be/re/css/style/css.xsl
new file mode 100644
index 0000000..99b2f90
--- /dev/null
+++ b/src/be/re/css/style/css.xsl
@@ -0,0 +1,1043 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:css="http://www.w3.org/1998/CSS" xmlns:rx="http://www.renderx.com/XSL/Extensions" xmlns:exsl="http://exslt.org/common" version="1.0" exclude-result-prefixes="css exsl">
+
+  <xsl:include href="fo_setup.xsl" />
+  <xsl:include href="util.xsl" />
+
+  <xsl:output method="xml" indent="no" version="1.0" encoding="UTF-8" omit-xml-declaration="no" />
+
+
+
+  <!-- User-defined page set-up. -->
+
+  <xsl:template match="/css:root[css:pages]" mode="setup">
+    <fo:root font-selection-strategy="character-by-character" line-height-shift-adjustment="disregard-shifts" font-family="{$font-family}" font-size="{$actual-font-size}" language="{$language}">
+      <!--
+        The document element's properties are put here for inheritance
+        purposes. The display property has no effect because pages are
+        generated for the top-level elements.
+      -->
+      <xsl:apply-templates select="@*" />
+      <xsl:if test="not(@xml:lang) and not(@lang)">
+        <xsl:attribute name="country">
+          <xsl:value-of select="$country" />
+        </xsl:attribute>
+      </xsl:if>
+      <fo:layout-master-set>
+        <xsl:apply-templates select="css:pages/css:page" />
+        <!--
+          If a named page exists then it is always split in six specific
+          versions.
+        -->
+        <xsl:apply-templates select="css:pages/css:page[starts-with(@css:name, 'first-left-')]" mode="master" />
+      </fo:layout-master-set>
+      <xsl:apply-templates select="css:page-sequence" />
+    </fo:root>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page[starts-with(@css:name, 'first-left-')]" mode="master">
+    <xsl:variable name="name" select="substring-after(@css:name, 'first-left-')" />
+    <fo:page-sequence-master master-name="{$name}">
+      <fo:repeatable-page-master-alternatives>
+        <!--
+          The following references are always generated. If the CSS style
+          sheet hasn't specified them they will be equal to the named
+          page definition.
+        -->
+        <fo:conditional-page-master-reference odd-or-even="odd" blank-or-not-blank="not-blank" page-position="first" master-reference="{concat('first-right-', $name)}" />
+        <fo:conditional-page-master-reference odd-or-even="even" blank-or-not-blank="not-blank" page-position="first" master-reference="{concat('first-left-', $name)}" />
+        <fo:conditional-page-master-reference odd-or-even="odd" blank-or-not-blank="not-blank" page-position="last" master-reference="{concat('last-right-', $name)}" />
+        <fo:conditional-page-master-reference odd-or-even="even" blank-or-not-blank="not-blank" page-position="last" master-reference="{concat('last-left-', $name)}" />
+        <fo:conditional-page-master-reference odd-or-even="odd" blank-or-not-blank="not-blank" page-position="any" master-reference="{concat('right-', $name)}" />
+        <fo:conditional-page-master-reference odd-or-even="even" blank-or-not-blank="not-blank" page-position="any" master-reference="{concat('left-', $name)}" />
+        <fo:conditional-page-master-reference odd-or-even="odd" blank-or-not-blank="blank" page-position="any" master-reference="{concat('blank-right-', $name)}" />
+        <fo:conditional-page-master-reference odd-or-even="even" blank-or-not-blank="blank" page-position="any" master-reference="{concat('blank-left-', $name)}" />
+      </fo:repeatable-page-master-alternatives>
+    </fo:page-sequence-master>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page">
+    <fo:simple-page-master master-name="{@css:name}" writing-mode="{$writing-mode}" page-height="{$paper-height}" page-width="{$paper-width}" margin-top="{$paper-margin-top}" margin-bottom="{$paper-margin-bottom}" margin-left="{$paper-margin-left}" margin-right="{$paper-margin-right}">
+      <xsl:apply-templates select="@*[name() != 'css:column-count' and           name() != 'css:force-page-count' and           name() != 'css:initial-page-number' and name() != 'css:column-gap']" />
+      <xsl:apply-templates select="*" />
+    </fo:simple-page-master>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page/@css:force-page-count" priority="1">
+    <xsl:attribute name="force-page-count">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page/@css:initial-page-number" priority="1">
+    <xsl:attribute name="initial-page-number">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <!-- Don't generate empty page sequences. -->
+
+  <xsl:template match="css:page-sequence[(not(css:regions/following-sibling::*//*[(@css:display = 'block' or @css:display = 'table') and @css:page]/*)       and not(css:regions/following-sibling::*//*[@css:display = 'block' and @css:page]/text()) and not(css:regions/following-sibling::*//*[(@css:display = 'block' or @css:display = 'table') and @css:page]/following-sibling::*)  and @css:page != 'unnamed') or (not(*[@css:display = 'block' or @css:display = 'table']/*) and       not(*[@css: [...]
+
+
+
+  <xsl:template match="css:page-sequence">
+    <xsl:variable name="page-name" select="@css:page" />
+    <fo:page-sequence format="1" master-reference="unnamed">
+      <xsl:apply-templates select="@css:page" />
+      <xsl:apply-templates select="/css:root/css:pages/css:page           [@css:name = concat('first-left-', $page-name)]/           @css:initial-page-number" />
+      <xsl:apply-templates select="/css:root/css:pages/css:page           [@css:name = concat('first-left-', $page-name)]/           @css:force-page-count" />
+      <xsl:apply-templates select="css:regions//css:page-number" mode="page-number-setup" />
+      <xsl:apply-templates select="css:regions/fo:static-content" />
+      <fo:static-content flow-name="xsl-footnote-separator">
+        <xsl:call-template name="footnote-separator" />
+      </fo:static-content>
+      <fo:flow flow-name="xsl-region-body">
+        <xsl:apply-templates select="css:regions/following-sibling::*" />
+      </fo:flow>
+    </fo:page-sequence>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page-sequence/@css:page" priority="1">
+    <xsl:attribute name="master-reference">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <!-- Default orientation, i.e. defined from outside. -->
+  <xsl:template match="css:page/@css:size[. = 'auto' or . = 'portrait']" priority="1" />
+
+
+
+  <xsl:template match="css:page/@css:size[. = 'landscape']" priority="1">
+    <xsl:attribute name="page-height">
+      <xsl:value-of select="$paper-width" />
+    </xsl:attribute>
+    <xsl:attribute name="page-width">
+      <xsl:value-of select="$paper-height" />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page/@css:size       [. != 'auto' and . != 'landscape' and . != 'portrait' and         . != 'inherit']" priority="1">
+    <xsl:variable name="tokens">
+      <xsl:call-template name="count-tokens">
+        <xsl:with-param name="s" select="." />
+      </xsl:call-template>
+    </xsl:variable>
+
+    <xsl:choose>
+      <xsl:when test="$tokens = 2">
+        <xsl:attribute name="page-width">
+          <xsl:call-template name="get-token">
+            <xsl:with-param name="position" select="1" />
+            <xsl:with-param name="s" select="." />
+          </xsl:call-template>
+        </xsl:attribute>
+        <xsl:attribute name="page-height">
+          <xsl:call-template name="get-token">
+            <xsl:with-param name="position" select="2" />
+            <xsl:with-param name="s" select="." />
+          </xsl:call-template>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:attribute name="page-width">
+          <xsl:value-of select="." />
+        </xsl:attribute>
+        <xsl:attribute name="page-height">
+          <xsl:value-of select="." />
+        </xsl:attribute>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <!-- General templates. -->
+
+  <xsl:template match="@*" priority="-1" />
+
+
+
+  <xsl:template match="fo:*">
+    <xsl:copy>
+      <xsl:apply-templates select="@* | node()" />
+    </xsl:copy>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:*/@*[not(starts-with(name(), 'css:'))]">
+    <xsl:copy />
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:instream-foreign-object">
+    <xsl:copy-of select="." />
+  </xsl:template>
+
+
+
+  <xsl:template match="@id | @ID | @xml:id" priority="1">
+    <xsl:attribute name="id">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@xml:lang | @lang">
+    <xsl:choose>
+      <xsl:when test="contains(., '-')">
+        <xsl:attribute name="language">
+          <xsl:value-of select="substring-before(., '-')" />
+        </xsl:attribute>
+        <xsl:attribute name="country">
+          <xsl:value-of select="substring-after(., '-')" />
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:attribute name="language">
+          <xsl:value-of select="." />
+        </xsl:attribute>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:*">
+    <xsl:attribute name="{local-name()}">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:content" priority="0.5" />
+  <xsl:template match="@css:counter-increment" priority="0.5" />
+  <xsl:template match="@css:counter-reset" priority="0.5" />
+  <xsl:template match="@css:cursor" priority="0.5" />
+  <xsl:template match="@css:display" priority="0.5" />
+  <xsl:template match="@css:force-page-count" priority="0.5" />
+  <xsl:template match="@css:format" priority="0.5" />
+  <xsl:template match="@css:initial-page-number" priority="0.5" />
+  <xsl:template match="@css:letter-value" priority="0.5" />
+  <xsl:template match="@css:list-label-width" priority="0.5" />
+  <xsl:template match="@css:list-style" priority="0.5" />
+  <xsl:template match="@css:list-style-image" priority="0.5" />
+  <xsl:template match="@css:list-style-position" priority="0.5" />
+  <xsl:template match="@css:list-style-type" priority="0.5" />
+  <xsl:template match="@css:marker-offset" priority="0.5" />
+  <xsl:template match="@css:marks" priority="0.5" />
+  <xsl:template match="@css:name" priority="0.5" />
+  <xsl:template match="@css:outline" priority="0.5" />
+  <xsl:template match="@css:outline-color" priority="0.5" />
+  <xsl:template match="@css:outline-style" priority="0.5" />
+  <xsl:template match="@css:outline-width" priority="0.5" />
+  <xsl:template match="@css:page" priority="0.5" />
+  <xsl:template match="@css:precedence" priority="0.5" />
+  <xsl:template match="@css:quotes" priority="0.5" />
+  <xsl:template match="@css:region" priority="0.5" />
+  <xsl:template match="@css:span" priority="0.5" />
+
+
+
+  <xsl:template match="*[@css:display = 'block']">
+    <fo:block margin-left="0pt" margin-right="0pt">
+      <xsl:apply-templates select="@* | node()" />
+    </fo:block>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:orientation" priority="1">
+    <xsl:attribute name="reference-orientation">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:position[. = 'relative' or . = 'static']" priority="1">
+    <xsl:attribute name="relative-position">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:position[. = 'absolute' or . = 'fixed']" priority="1">
+    <xsl:attribute name="absolute-position">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'inline']">
+    <fo:inline>
+      <xsl:apply-templates select="@* | node()" />
+    </fo:inline>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'none']" />
+
+
+
+  <xsl:template match="*[@css:display = 'graphic']">
+    <fo:external-graphic>
+      <xsl:apply-templates select="@*[name() != 'css:color' and not(starts-with(name(), 'css:font'))] | node()" />
+    </fo:external-graphic>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:src" priority="1">
+    <xsl:attribute name="src">
+      <xsl:value-of select="concat('url(', ., ')')" />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'list-item']">
+    <xsl:variable name="list-style-type">
+      <xsl:apply-templates select="." mode="list-style-type" />
+    </xsl:variable>
+    <xsl:variable name="list-style-position">
+      <xsl:apply-templates select="." mode="list-style-position" />
+    </xsl:variable>
+    <fo:list-item>
+      <xsl:choose>
+        <xsl:when test="$list-style-type != 'none' and $list-style-position = 'outside'">
+          <fo:list-item-label end-indent="label-end()">
+            <fo:block text-align="end">
+              <xsl:call-template name="list-label">
+                <xsl:with-param name="list-type" select="$list-style-type" />
+              </xsl:call-template>
+            </fo:block>
+          </fo:list-item-label>
+          <fo:list-item-body start-indent="body-start()">
+            <fo:block>
+              <xsl:apply-templates select="@* | node()" />
+            </fo:block>
+          </fo:list-item-body>
+        </xsl:when>
+        <xsl:otherwise>
+          <fo:list-item-label end-indent="0">
+            <fo:block />
+          </fo:list-item-label>
+          <fo:list-item-body start-indent="0">
+            <fo:block>
+              <xsl:apply-templates select="@*" />
+              <xsl:if test="$list-style-type != 'none'">
+                <fo:inline>
+                  <xsl:attribute name="width">
+                    <xsl:call-template name="get-list-label-width">
+                      <xsl:with-param name="list-type" select="$list-style-type" />
+                    </xsl:call-template>
+                  </xsl:attribute>
+                  <xsl:call-template name="list-label">
+                    <xsl:with-param name="list-type" select="$list-style-type" />
+                  </xsl:call-template>
+                </fo:inline>
+              </xsl:if>
+              <xsl:apply-templates select="node()" />
+            </fo:block>
+          </fo:list-item-body>
+        </xsl:otherwise>
+      </xsl:choose>
+    </fo:list-item>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[*[@css:display = 'list-item']]" priority="1">
+    <xsl:variable name="list-style-type">
+      <xsl:apply-templates select="*[@css:display = 'list-item'][1]" mode="list-style-type" />
+    </xsl:variable>
+    <xsl:variable name="list-style-position">
+      <xsl:apply-templates select="*[@css:display = 'list-item'][1]" mode="list-style-position" />
+    </xsl:variable>
+    <fo:list-block>
+      <xsl:choose>
+        <xsl:when test="$list-style-type != 'none' and             $list-style-position = 'outside'">
+          <xsl:attribute name="provisional-label-separation">
+            <xsl:text>6pt</xsl:text>
+          </xsl:attribute>
+          <xsl:attribute name="provisional-distance-between-starts">
+            <xsl:call-template name="get-list-label-width">
+              <xsl:with-param name="list-type" select="$list-style-type" />
+            </xsl:call-template>
+          </xsl:attribute>
+        </xsl:when>
+        <xsl:otherwise>
+          <xsl:attribute name="provisional-label-separation">
+            <xsl:text>0</xsl:text>
+          </xsl:attribute>
+          <xsl:attribute name="provisional-distance-between-starts">
+            <xsl:text>0</xsl:text>
+          </xsl:attribute>
+        </xsl:otherwise>
+      </xsl:choose>
+      <xsl:apply-templates select="@* | node()" />
+    </fo:list-block>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'list-item']" mode="list-style-position">
+    <xsl:variable name="list-style-position" select="ancestor-or-self::*[@css:list-style-position != 'inherit'][1]/@css:list-style-position" />
+    <xsl:choose>
+      <xsl:when test="string-length($list-style-position) = 0">
+        <xsl:text>outside</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:value-of select="$list-style-position" />
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'list-item']" mode="list-style-type">
+    <xsl:variable name="att" select="ancestor-or-self::*[@css:list-style-type != 'inherit' or @css:list-style-image != 'inherit'][1]" />
+    <xsl:choose>
+      <xsl:when test="exsl:node-set($att)/@css:list-style-image">
+        <xsl:text>image</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:variable name="list-style-type" select="exsl:node-set($att)/@css:list-style-type" />
+        <xsl:choose>
+          <xsl:when test="string-length($list-style-type) = 0">
+            <xsl:text>disc</xsl:text>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:value-of select="$list-style-type" />
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table' and *[@css:display = 'table-caption'] and       not(*[@css:display = 'table-row'])]">
+    <fo:table-and-caption>
+      <xsl:apply-templates select="*[@css:display = 'table-caption']/@css:caption-side" />
+      <xsl:apply-templates select="@*[starts-with(name(), 'css:margin-')]" />
+      <xsl:apply-templates select="*[@css:display = 'table-caption']" />
+      <fo:table margin-left="0pt" margin-right="0pt">
+        <xsl:apply-templates select="@*[not(starts-with(name(), 'css:margin-'))]" />
+        <xsl:apply-templates select="*[@css:display = 'table-column']" mode="layout" />
+        <xsl:apply-templates select="*[@css:display = 'table-column']" />
+        <xsl:apply-templates select="*[@css:display = 'table-header-group']" />
+        <xsl:apply-templates select="*[@css:display = 'table-footer-group']" />
+        <xsl:apply-templates select="*[@css:display = 'table-row-group']" />
+      </fo:table>
+    </fo:table-and-caption>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table' and       not(*[@css:display = 'table-caption']) and       not(*[@css:display = 'table-row'])]">
+    <fo:table margin-left="0pt" margin-right="0pt">
+      <xsl:apply-templates select="@*" />
+      <xsl:apply-templates select="*[@css:display = 'table-column']" mode="layout" />
+      <xsl:apply-templates select="*[@css:display = 'table-column']" />
+      <xsl:apply-templates select="*[@css:display = 'table-header-group']" />
+      <xsl:apply-templates select="*[@css:display = 'table-footer-group']" />
+      <xsl:apply-templates select="*[@css:display = 'table-row-group']" />
+    </fo:table>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table' and *[@css:display = 'table-caption'] and       *[@css:display = 'table-row']]">
+    <fo:table-and-caption>
+      <xsl:apply-templates select="*[@css:display = 'table-caption']/@css:caption-side" />
+      <xsl:apply-templates select="@*[starts-with(name(), 'css:margin-')]" />
+      <xsl:apply-templates select="*[@css:display = 'table-caption']" />
+      <fo:table margin-left="0pt" margin-right="0pt">
+        <xsl:apply-templates select="@*[not(starts-with(name(), 'css:margin-'))]" />
+        <xsl:apply-templates select="*[@css:display = 'table-column']" mode="layout" />
+        <xsl:apply-templates select="*[@css:display = 'table-column']" />
+        <fo:table-body>
+          <xsl:apply-templates select="*[@css:display = 'table-row']" />
+        </fo:table-body>
+      </fo:table>
+    </fo:table-and-caption>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table' and       not(*[@css:display = 'table-caption']) and       *[@css:display = 'table-row']]">
+    <fo:table margin-left="0pt" margin-right="0pt">
+      <xsl:apply-templates select="@*" />
+      <xsl:apply-templates select="*[@css:display = 'table-column']" mode="layout" />
+      <xsl:apply-templates select="*[@css:display = 'table-column']" />
+      <fo:table-body>
+        <xsl:apply-templates select="*[@css:display = 'table-row']" />
+      </fo:table-body>
+    </fo:table>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-caption']">
+    <fo:table-caption margin-left="0pt" margin-right="0pt">
+      <xsl:apply-templates select="@*[name() != 'css:caption-side']" />
+      <fo:block>
+        <xsl:apply-templates select="node()" />
+      </fo:block>
+    </fo:table-caption>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-caption']/@css:caption-side[. = 'bottom']" priority="1">
+    <xsl:attribute name="caption-side">after</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-caption']/@css:caption-side[. = 'left']" priority="1">
+    <xsl:attribute name="caption-side">start</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-caption']/@css:caption-side[. = 'right']" priority="1">
+    <xsl:attribute name="caption-side">end</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-caption']/@css:caption-side[. = 'top']" priority="1">
+    <xsl:attribute name="caption-side">before</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-cell']">
+    <fo:table-cell>
+      <xsl:apply-templates select="@*" />
+      <fo:block>
+        <xsl:apply-templates select="node()" />
+      </fo:block>
+    </fo:table-cell>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-cell']/@css:colspan" priority="1">
+    <xsl:attribute name="number-columns-spanned">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-cell']/@css:rowspan" priority="1">
+    <xsl:attribute name="number-rows-spanned">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-cell']/@css:vertical-align[. = 'bottom']" priority="1">
+    <xsl:attribute name="display-align">after</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-cell']/@css:vertical-align[. = 'middle']" priority="1">
+    <xsl:attribute name="display-align">center</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-cell']/@css:vertical-align[. = 'top']" priority="1">
+    <xsl:attribute name="display-align">before</xsl:attribute>
+  </xsl:template>
+
+
+
+  <!-- Unsupported vertical-align values for table-cells. -->
+
+  <xsl:template match="*[@css:display = 'table-cell']/       @css:vertical-align[. != 'top' and . != 'bottom' and . != 'middle']" priority="1" />
+
+
+
+  <xsl:template match="*[@css:display = 'table-column']">
+    <fo:table-column>
+      <xsl:apply-templates select="@*" />
+    </fo:table-column>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-column' and contains(@css:width, '*')]" mode="layout">
+    <xsl:attribute name="table-layout">fixed</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-column']/@css:span" priority="1">
+    <xsl:attribute name="number-columns-repeated">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <!--
+    The inheritance mechanism of the table normalizer might have propagated
+    these.
+  -->
+
+  <xsl:template match="*[@css:display = 'table-column']/@css:text-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-column']/@css:vertical-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-footer-group']/@css:text-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-footer-group']/@css:vertical-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-header-group']/@css:text-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-header-group']/@css:vertical-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-row']/@css:text-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-row']/@css:vertical-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-row-group']/@css:text-align" priority="1" />
+  <xsl:template match="*[@css:display = 'table-row-group']/@css:vertical-align" priority="1" />
+
+
+
+  <xsl:template match="*[@css:display = 'table-column']/@css:width" priority="1">
+    <xsl:attribute name="column-width">
+      <xsl:choose>
+        <xsl:when test="starts-with(., '*')">
+          <xsl:value-of select="concat('proportional-column-width(1)',               substring-after(., '*'))" />
+        </xsl:when>
+        <xsl:when test="contains(., '*')">
+          <xsl:value-of select="concat('proportional-column-width(',               substring-before(., '*'), ')', substring-after(., '*'))" />
+        </xsl:when>
+        <xsl:when test="contains(., 'pcw')">
+          <xsl:value-of select="concat('proportional-column-width(',               substring-before(., 'pcw'), ')', substring-after(., 'pcw'))" />
+        </xsl:when>
+        <xsl:otherwise>
+          <xsl:value-of select="." />
+        </xsl:otherwise>
+      </xsl:choose>
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-footer-group']">
+    <fo:table-footer>
+      <xsl:apply-templates select="@* | node()" />
+    </fo:table-footer>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-header-group']">
+    <fo:table-header>
+      <xsl:apply-templates select="@* | node()" />
+    </fo:table-header>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-row']">
+    <fo:table-row>
+      <xsl:apply-templates select="@* | node()" />
+    </fo:table-row>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'table-row-group']">
+    <fo:table-body>
+      <xsl:apply-templates select="@* | node()" />
+    </fo:table-body>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'block']/@css:margin-bottom[. != 'auto'] |       *[@css:display = 'list-item']/@css:margin-bottom[. != 'auto'] |       *[@css:display = 'table']/@css:margin-bottom[. != 'auto']" priority="1">
+    <xsl:attribute name="space-after">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'block']/@css:margin-top[. != 'auto'] |       *[@css:display = 'list-item']/@css:margin-top[. != 'auto'] |       *[@css:display = 'table']/@css:margin-top[. != 'auto']" priority="1">
+    <xsl:attribute name="space-before">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="*[@css:display = 'leader']">
+    <fo:leader>
+      <xsl:apply-templates select="@* | node()" />
+    </fo:leader>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:external-link">
+    <fo:basic-link external-destination="{concat('url(', @target, ')')}">
+      <xsl:apply-templates />
+    </fo:basic-link>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:footnote">
+    <fo:footnote>
+      <xsl:apply-templates select="css:footnote-reference/*" />
+      <fo:footnote-body>
+        <xsl:apply-templates select="css:footnote-body/*" />
+      </fo:footnote-body>
+    </fo:footnote>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:internal-link">
+    <fo:basic-link internal-destination="{@target}">
+      <xsl:apply-templates />
+    </fo:basic-link>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:anchor" priority="1">
+    <xsl:attribute name="id">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:column-span" priority="1">
+    <xsl:attribute name="span">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@css:vlink" priority="1" />
+
+
+
+  <xsl:template match="@css:white-space[. = 'wrap-nocollapse']" priority="1">
+    <xsl:attribute name="linefeed-treatment">preserve</xsl:attribute>
+    <xsl:attribute name="white-space-collapse">false</xsl:attribute>
+    <xsl:attribute name="white-space-treatment">preserve</xsl:attribute>
+    <xsl:attribute name="wrap-option">wrap</xsl:attribute>
+  </xsl:template>
+
+
+
+  <!--
+    The following two templates are temporarily in the RenderX namespace.
+  -->
+
+  <xsl:template match="css:change-bar-begin">
+    <rx:change-bar-begin>
+      <xsl:copy-of select="@*" />
+    </rx:change-bar-begin>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:change-bar-end">
+    <rx:change-bar-end>
+      <xsl:copy-of select="@*" />
+    </rx:change-bar-end>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:external">
+    <fo:external-graphic src="{@css:href}" />
+  </xsl:template>
+
+
+
+  <xsl:template match="css:first-line">
+    <fo:initial-property-set>
+      <xsl:apply-templates select="@*" />
+    </fo:initial-property-set>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:float">
+    <fo:float>
+      <xsl:apply-templates select="@*" />
+      <fo:block>
+        <xsl:apply-templates select="node()" />
+      </fo:block>
+    </fo:float>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:fo-marker">
+    <fo:marker marker-class-name="{@css:name}">
+      <xsl:apply-templates select="node()" />
+    </fo:marker>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:last-page-mark">
+    <fo:block id="last-page" />
+  </xsl:template>
+
+
+
+  <xsl:template match="css:newline">
+    <fo:block />
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page-number">
+    <fo:page-number />
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page-number" mode="page-number-setup">
+    <xsl:apply-templates select="@css:format" mode="page-number-setup" />
+    <xsl:apply-templates select="@css:letter-value" mode="page-number-setup" />
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page-number/@css:format" mode="page-number-setup">
+    <xsl:attribute name="format">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page-number/@css:letter-value" mode="page-number-setup">
+    <xsl:attribute name="letter-value">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="css:pages-total">
+    <fo:page-number-citation ref-id="last-page" />
+  </xsl:template>
+
+
+
+  <xsl:template match="css:page-ref">
+    <fo:page-number-citation ref-id="{@css:ref-id}" />
+  </xsl:template>
+
+
+
+  <xsl:template match="css:retrieve-fo-marker">
+    <fo:retrieve-marker retrieve-class-name="{@css:name}" />
+  </xsl:template>
+
+
+
+  <!-- Named templates. -->
+
+  <xsl:template name="get-list-label-width">
+    <xsl:param name="list-type" />
+
+    <xsl:choose>
+      <xsl:when test="$list-type = 'image'">
+        <xsl:value-of select="ancestor-or-self::*[@css:list-label-width != 'inherit'][1]/@css:list-label-width" />
+      </xsl:when>
+      <xsl:when test="$list-type = 'disc' or $list-type = 'circle' or $list-type = 'square' or $list-type = 'box' or $list-type = 'check' or $list-type = 'diamond' or $list-type = 'hyphen'">
+        <xsl:text>1em</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:call-template name="get-num-list-label-width">
+          <xsl:with-param name="items" select="count(*[@css:display = 'list-item'])" />
+          <xsl:with-param name="list-type" select="$list-type" />
+        </xsl:call-template>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template name="get-num-format">
+    <xsl:param name="list-type" />
+
+    <xsl:choose>
+      <xsl:when test="$list-type = 'lower-alpha' or $list-type = 'lower-latin'">
+        <xsl:text>a</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'lower-roman'">
+        <xsl:text>i</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'upper-alpha' or $list-type = 'upper-latin'">
+        <xsl:text>A</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'upper-roman'">
+        <xsl:text>I</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:text>1</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template name="get-num-list-label-width">
+    <xsl:param name="items" />
+    <xsl:param name="list-type" />
+
+    <xsl:choose>
+      <xsl:when test="$list-type='lower-alpha' or $list-type = 'lower-latin' or           $list-type = 'lower-greek'">
+        <xsl:choose>
+          <xsl:when test="$items < 27">
+            <xsl:text>1.5em</xsl:text>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:text>2em</xsl:text>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:when>
+      <xsl:when test="$list-type='upper-alpha' or $list-type = 'upper-latin'">
+        <xsl:choose>
+          <xsl:when test="$items<27">
+            <xsl:text>1.5em</xsl:text>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:text>2.5em</xsl:text>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:when>
+      <xsl:when test="$list-type='lower-roman'">
+        <xsl:choose>
+          <xsl:when test="$items<7">
+            <xsl:text>1.5em</xsl:text>
+          </xsl:when>
+          <xsl:when test="$items<17">
+            <xsl:text>2em</xsl:text>
+          </xsl:when>
+          <xsl:when test="$items<27">
+            <xsl:text>2.5em</xsl:text>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:text>3.5em</xsl:text>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:when>
+      <xsl:when test="$list-type='upper-roman'">
+        <xsl:choose>
+          <xsl:when test="$items<7">
+            <xsl:text>2em</xsl:text>
+          </xsl:when>
+          <xsl:when test="$items<17">
+            <xsl:text>2.5em</xsl:text>
+          </xsl:when>
+          <xsl:when test="$items<27">
+            <xsl:text>3em</xsl:text>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:text>4em</xsl:text>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:choose>
+          <xsl:when test="$items<10">
+            <xsl:text>1.5em</xsl:text>
+          </xsl:when>
+          <xsl:when test="$items<100">
+            <xsl:text>2em</xsl:text>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:text>2.5em</xsl:text>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template name="list-label">
+    <xsl:param name="list-type" />
+
+    <xsl:choose>
+      <xsl:when test="$list-type = 'image'">
+        <fo:external-graphic>
+          <xsl:attribute name="src">
+            <xsl:value-of select="ancestor-or-self::*[@css:list-style-image != 'inherit'][1]/@css:list-style-image" />
+          </xsl:attribute>
+        </fo:external-graphic>
+      </xsl:when>
+      <xsl:when test="$list-type = 'box'">
+        <xsl:text>□</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'check'">
+        <xsl:text>✓</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'circle'">
+        <xsl:text>◦</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'diamond'">
+        <xsl:text>♦</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'disc'">
+        <xsl:text>•</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'hyphen'">
+        <xsl:text>–</xsl:text>
+      </xsl:when>
+      <xsl:when test="$list-type = 'square'">
+        <xsl:text>■</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:variable name="format">
+          <xsl:call-template name="get-num-format">
+            <xsl:with-param name="list-type" select="$list-type" />
+          </xsl:call-template>
+        </xsl:variable>
+        <xsl:number format="{$format}" value="count(preceding-sibling::*[@css:display = 'list-item']) + 1" />
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+</xsl:transform>
diff --git a/src/be/re/css/style/deltaxml.css b/src/be/re/css/style/deltaxml.css
new file mode 100644
index 0000000..4139253
--- /dev/null
+++ b/src/be/re/css/style/deltaxml.css
@@ -0,0 +1,40 @@
+ at namespace deltaxml url(http://www.deltaxml.com/ns/well-formed-delta-v1);
+
+ at media print
+{
+  deltaxml|PCDATAnew, deltaxml|PCDATAold
+  {
+    display: inline;
+  }
+
+  deltaxml|exchange, deltaxml|new, deltaxml|old
+  {
+    display: wrapper;
+  }
+
+  *[deltaxml|delta="add"], deltaxml|PCDATAnew, deltaxml|new
+  {
+    text-decoration: underline;
+  }
+
+  *[deltaxml|delta="delete"], deltaxml|PCDATAold, deltaxml|old
+  {
+    text-decoration: line-through;
+  }
+
+  *[deltaxml|delta="add"]:before, deltaxml|PCDATAnew:before,
+    deltaxml|new, *[deltaxml|delta="delete"]:before,
+    deltaxml|PCDATAold:before, deltaxml|old
+  {
+    change-bar-class: changed;
+    change-bar-placement: alternate;
+    change-bar-style: solid;
+    change-bar-width: 0.2pt;
+  }
+
+  *[deltaxml|delta="add"]:after, deltaxml|PCDATAnew:after,
+    *[deltaxml|delta="delete"]:after, deltaxml|PCDATAold:after
+  {
+    change-bar-class: changed;
+  }
+}
diff --git a/src/be/re/css/style/fo_setup.xsl b/src/be/re/css/style/fo_setup.xsl
new file mode 100644
index 0000000..2662e17
--- /dev/null
+++ b/src/be/re/css/style/fo_setup.xsl
@@ -0,0 +1,319 @@
+<?xml version='1.0'?>
+<xsl:transform xmlns:css="http://www.w3.org/1998/CSS" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0">
+
+  <xsl:param name="column-count">1</xsl:param>
+  <xsl:param name="country">GB</xsl:param>
+  <xsl:param name="font-family">serif</xsl:param>
+  <xsl:param name="font-size">none</xsl:param>
+  <xsl:param name="language">en</xsl:param>
+  <xsl:param name="link-color">black</xsl:param>
+  <xsl:param name="odd-even-shift">10mm</xsl:param>
+  <xsl:param name="orientation">portrait</xsl:param>
+  <xsl:param name="paper-margin-bottom">10mm</xsl:param>
+  <xsl:param name="paper-margin-left">25mm</xsl:param>
+  <xsl:param name="paper-margin-right">25mm</xsl:param>
+  <xsl:param name="paper-margin-top">10mm</xsl:param>
+  <xsl:param name="paper-mode">onesided</xsl:param>
+    <!-- Can be "twosided" or "onesided". -->
+  <xsl:param name="paper-size">a4</xsl:param>
+  <xsl:param name="rule-thickness">0.2pt</xsl:param>
+
+
+
+  <xsl:variable name="actual-font-size">
+    <xsl:choose>
+      <xsl:when test="$font-size != 'none'">
+        <xsl:value-of select="$font-size" />
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:choose>
+          <xsl:when test="$paper-size = 'a5' or $paper-size = 'b5'">
+            <xsl:text>10pt</xsl:text>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:text>11pt</xsl:text>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="portrait-paper-height">
+    <xsl:choose>
+      <xsl:when test="$paper-size = 'a0'">
+        <xsl:text>1188mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a1'">
+        <xsl:text>840mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a2'">
+        <xsl:text>594mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a3'">
+        <xsl:text>420mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a4'">
+        <xsl:text>297mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a5'">
+        <xsl:text>210mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'b5'">
+        <xsl:text>250mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'executive'">
+        <xsl:text>11in</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'letter'">
+        <xsl:text>11in</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'legal'">
+        <xsl:text>14in</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:message terminate="no">Unknown paper-size, taking a4</xsl:message>
+        <xsl:text>297mm</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="portrait-paper-width">
+    <xsl:choose>
+      <xsl:when test="$paper-size = 'a0'">
+        <xsl:text>840mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a1'">
+        <xsl:text>594mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a2'">
+        <xsl:text>420mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a3'">
+        <xsl:text>297mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a4'">
+        <xsl:text>210mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'a5'">
+        <xsl:text>148mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'b5'">
+        <xsl:text>176mm</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'executive'">
+        <xsl:text>7.25in</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'letter'">
+        <xsl:text>8.5in</xsl:text>
+      </xsl:when>
+      <xsl:when test="$paper-size = 'legal'">
+        <xsl:text>8.5in</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:text>210mm</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="paper-height">
+    <xsl:choose>
+      <xsl:when test="$orientation = 'portrait'">
+        <xsl:value-of select="$portrait-paper-height" />
+      </xsl:when>
+      <xsl:when test="$orientation = 'landscape'">
+        <xsl:value-of select="$portrait-paper-width" />
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:message terminate="no">Unknown orientation, taking portrait</xsl:message>
+        <xsl:value-of select="$portrait-paper-height" />
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="paper-width">
+    <xsl:choose>
+      <xsl:when test="$orientation = 'portrait'">
+        <xsl:value-of select="$portrait-paper-width" />
+      </xsl:when>
+      <xsl:when test="$orientation = 'landscape'">
+        <xsl:value-of select="$portrait-paper-height" />
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:value-of select="$portrait-paper-width" />
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="writing-mode">lr-tb</xsl:variable>
+
+
+
+  <!-- Page setup. -->
+
+  <xsl:template match="/">
+    <xsl:apply-templates select="*" mode="setup" />
+  </xsl:template>
+
+
+
+  <xsl:template match="/css:root[not(css:pages)]" mode="setup">
+    <fo:root font-selection-strategy="character-by-character" line-height-shift-adjustment="disregard-shifts" country="{$country}" font-family="{$font-family}" font-size="{$actual-font-size}" language="{$language}">
+      <xsl:apply-templates select="@xml:lang" />
+      <fo:layout-master-set>
+        <fo:simple-page-master master-name="blank" writing-mode="{$writing-mode}" page-height="{$paper-height}" page-width="{$paper-width}" margin-top="{$paper-margin-top}" margin-bottom="{$paper-margin-bottom}" margin-left="{$paper-margin-left}" margin-right="{$paper-margin-right}">
+          <fo:region-body margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm" />
+        </fo:simple-page-master>
+
+        <fo:simple-page-master master-name="even" writing-mode="{$writing-mode}" page-height="{$paper-height}" page-width="{$paper-width}" margin-top="{$paper-margin-top}" margin-bottom="{$paper-margin-bottom}" margin-left="{$paper-margin-left} - {$odd-even-shift}" margin-right="{$paper-margin-right} + {$odd-even-shift}">
+          <xsl:call-template name="region-body" />
+          <xsl:call-template name="region-before">
+            <xsl:with-param name="region-name">region-before-even</xsl:with-param>
+          </xsl:call-template>
+          <xsl:call-template name="region-after" />
+        </fo:simple-page-master>
+
+        <fo:simple-page-master master-name="odd" writing-mode="{$writing-mode}" page-height="{$paper-height}" page-width="{$paper-width}" margin-top="{$paper-margin-top}" margin-bottom="{$paper-margin-bottom}" margin-left="{$paper-margin-left} + {$odd-even-shift}" margin-right="{$paper-margin-right} - {$odd-even-shift}">
+          <xsl:call-template name="region-body" />
+          <xsl:call-template name="region-before">
+            <xsl:with-param name="region-name">region-before-odd</xsl:with-param>
+          </xsl:call-template>
+          <xsl:call-template name="region-after" />
+        </fo:simple-page-master>
+
+        <fo:simple-page-master master-name="odd-first" writing-mode="{$writing-mode}" page-height="{$paper-height}" page-width="{$paper-width}" margin-top="{$paper-margin-top}" margin-bottom="{$paper-margin-bottom}" margin-left="{$paper-margin-left} + {$odd-even-shift}" margin-right="{$paper-margin-right} - {$odd-even-shift}">
+          <xsl:call-template name="region-body" />
+          <xsl:call-template name="region-after" />
+        </fo:simple-page-master>
+
+        <fo:simple-page-master master-name="onesided" writing-mode="{$writing-mode}" page-height="{$paper-height}" page-width="{$paper-width}" margin-top="{$paper-margin-top}" margin-bottom="{$paper-margin-bottom}" margin-left="{$paper-margin-left}" margin-right="{$paper-margin-right}">
+          <xsl:call-template name="region-body" />
+          <xsl:call-template name="region-before">
+            <xsl:with-param name="region-name">region-before-even</xsl:with-param>
+          </xsl:call-template>
+          <xsl:call-template name="region-after" />
+        </fo:simple-page-master>
+
+        <fo:simple-page-master master-name="onesided-first" writing-mode="{$writing-mode}" page-height="{$paper-height}" page-width="{$paper-width}" margin-top="{$paper-margin-top}" margin-bottom="{$paper-margin-bottom}" margin-left="{$paper-margin-left}" margin-right="{$paper-margin-right}">
+          <xsl:call-template name="region-body" />
+          <xsl:call-template name="region-after" />
+        </fo:simple-page-master>
+
+        <fo:simple-page-master master-name="title" writing-mode="{$writing-mode}" page-height="{$paper-height}" page-width="{$paper-width}" margin-top="{$paper-margin-top}" margin-bottom="{$paper-margin-bottom}">
+          <xsl:if test="$paper-mode='onesided'">
+            <xsl:attribute name="margin-left">
+              <xsl:value-of select="$paper-margin-left" />
+            </xsl:attribute>
+            <xsl:attribute name="margin-right">
+              <xsl:value-of select="$paper-margin-right" />
+            </xsl:attribute>
+          </xsl:if>
+          <xsl:if test="$paper-mode='twosided'">
+            <xsl:attribute name="margin-left">
+              <xsl:value-of select="concat($paper-margin-left, ' + ', $odd-even-shift)" />
+            </xsl:attribute>
+            <xsl:attribute name="margin-right">
+              <xsl:value-of select="concat($paper-margin-left, ' - ', $odd-even-shift)" />
+            </xsl:attribute>
+          </xsl:if>
+          <fo:region-body margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm" />
+        </fo:simple-page-master>
+
+        <fo:page-sequence-master master-name="document">
+          <fo:repeatable-page-master-alternatives>
+            <xsl:if test="$paper-mode='twosided'">
+              <fo:conditional-page-master-reference odd-or-even="even" master-reference="even" />
+              <fo:conditional-page-master-reference odd-or-even="odd" page-position="first" master-reference="odd-first" />
+              <fo:conditional-page-master-reference odd-or-even="odd" page-position="any" master-reference="odd" />
+            </xsl:if>
+
+            <xsl:if test="$paper-mode='onesided'">
+              <fo:conditional-page-master-reference page-position="first" master-reference="onesided-first" />
+              <fo:conditional-page-master-reference page-position="any" master-reference="onesided" />
+            </xsl:if>
+
+            <fo:conditional-page-master-reference blank-or-not-blank="blank" page-position="any" master-reference="blank" />
+          </fo:repeatable-page-master-alternatives>
+        </fo:page-sequence-master>
+      </fo:layout-master-set>
+
+      <fo:page-sequence format="1" initial-page-number="1" master-reference="document">
+        <xsl:call-template name="page-style" />
+        <fo:flow flow-name="xsl-region-body">
+          <xsl:apply-templates select="css:page-sequence/*" />
+        </fo:flow>
+      </fo:page-sequence>
+    </fo:root>
+  </xsl:template>
+
+
+
+  <!-- Named templates. -->
+
+  <xsl:template name="footnote-separator">
+    <fo:block>
+      <fo:leader leader-length="41%" leader-pattern="rule" rule-style="solid" rule-thickness="{$rule-thickness}" />
+    </fo:block>
+  </xsl:template>
+
+
+
+  <xsl:template name="page-style">
+    <fo:static-content flow-name="region-before-even">
+      <xsl:call-template name="running-head">
+        <xsl:with-param name="align">start</xsl:with-param>
+      </xsl:call-template>
+    </fo:static-content>
+
+    <fo:static-content flow-name="region-before-odd">
+      <xsl:call-template name="running-head">
+        <xsl:with-param name="align">end</xsl:with-param>
+      </xsl:call-template>
+    </fo:static-content>
+
+    <fo:static-content flow-name="xsl-footnote-separator">
+      <xsl:call-template name="footnote-separator" />
+    </fo:static-content>
+
+    <fo:static-content flow-name="xsl-region-after">
+      <xsl:call-template name="running-foot" />
+    </fo:static-content>
+  </xsl:template>
+
+
+
+  <xsl:template name="region-after">
+    <fo:region-after display-align="before" extent="10mm" />
+  </xsl:template>
+
+
+
+  <xsl:template name="region-before">
+    <xsl:param name="region-name" />
+
+    <fo:region-before display-align="after" extent="10mm" region-name="{$region-name}" />
+  </xsl:template>
+
+
+
+  <xsl:template name="region-body">
+    <fo:region-body column-count="{$column-count}" margin-bottom="15mm" margin-left="0mm" margin-right="0mm" margin-top="15mm" />
+  </xsl:template>
+
+
+
+  <xsl:template name="running-head">
+    <xsl:param name="align" />
+    <fo:block font-style="oblique" text-align="{$align}" text-transform="uppercase">
+      <fo:retrieve-marker retrieve-class-name="component" retrieve-position="first-starting-within-page" retrieve-boundary="document" />
+    </fo:block>
+  </xsl:template>
+
+
+
+  <xsl:template name="running-foot">
+    <fo:block text-align="center">
+      <fo:page-number />
+    </fo:block>
+  </xsl:template>
+
+</xsl:transform>
diff --git a/src/be/re/css/style/fop_filter.xsl b/src/be/re/css/style/fop_filter.xsl
new file mode 100644
index 0000000..a784436
--- /dev/null
+++ b/src/be/re/css/style/fop_filter.xsl
@@ -0,0 +1,177 @@
+<?xml version='1.0'?>
+<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0">
+
+  <xsl:output method="xml" indent="no" version="1.0" encoding="UTF-8" omit-xml-declaration="no" />
+
+
+
+  <xsl:template match="@*|node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@*|node()" />
+    </xsl:copy>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:list-item-body/fo:block/@space-before" />
+  <xsl:template match="@page-break-after" />
+  <xsl:template match="@page-break-before" />
+  <xsl:template match="@page-break-inside" />
+  <xsl:template match="@text-transform" />
+
+
+
+  <xsl:template match="@font-weight[. = 'bolder']">
+    <xsl:attribute name="font-weight">bold</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@margin-bottom[. = '0']">
+    <xsl:attribute name="space-after">0pt</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:block/@margin-bottom | fo:block-container/@margin-bottom |       fo:table/@margin-bottom">
+    <xsl:attribute name="space-after">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@margin-left[. = '0']" priority="1">
+    <xsl:attribute name="start-indent">
+      <xsl:text>inherited-property-value(start-indent)</xsl:text>
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:block/@margin-left | fo:block-container/@margin-left |       fo:table/@margin-left">
+    <xsl:attribute name="start-indent">
+      <xsl:text>inherited-property-value(start-indent)</xsl:text>
+      <xsl:if test=". != 'auto'">
+        <xsl:text>+</xsl:text>
+        <xsl:value-of select="." />
+      </xsl:if>
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:table-cell">
+    <fo:table-cell start-indent="0pt" end-indent="0pt">
+      <xsl:apply-templates select="@* | node()" />
+    </fo:table-cell>
+  </xsl:template>
+
+
+
+  <xsl:template match="@margin-right[. = '0']" priority="1">
+    <xsl:attribute name="end-indent">
+      <xsl:text>inherited-property-value(end-indent)</xsl:text>
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:block/@margin-right |       fo:block-container/@margin-right |       fo:table/@margin-right">
+    <xsl:attribute name="end-indent">
+      <xsl:text>inherited-property-value(end-indent)</xsl:text>
+      <xsl:if test=". != 'auto'">
+        <xsl:text>+</xsl:text>
+        <xsl:value-of select="." />
+      </xsl:if>
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@margin-top[. = '0']">
+    <xsl:attribute name="space-before">0pt</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:block/@margin-top | fo:block-container/@margin-top |       fo:table/@margin-top">
+    <xsl:attribute name="space-before">
+      <xsl:value-of select="." />
+    </xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@padding-bottom[. = '0']">
+    <xsl:attribute name="padding-bottom">0pt</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@padding-left[. = '0']">
+    <xsl:attribute name="padding-left">0pt</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@padding-right[. = '0']">
+    <xsl:attribute name="padding-right">0pt</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@padding-top[. = '0']">
+    <xsl:attribute name="padding-top">0pt</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="@provisional-distance-between-starts">
+    <xsl:attribute name="provisional-distance-between-starts">2em</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:table">
+    <fo:table width="100%">
+      <xsl:apply-templates select="@*" /> <!-- Can override width. -->
+      <xsl:attribute name="table-layout">fixed</xsl:attribute>
+      <xsl:apply-templates select="(.//fo:table-row)[1]/fo:table-cell" mode="table-no-columns" />
+      <xsl:apply-templates select="node()" />
+    </fo:table>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:table/@width[. = 'auto']">
+    <xsl:attribute name="width">100%</xsl:attribute>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:table[not(fo:table-column)]//fo:table-cell" mode="table-no-columns">
+    <fo:table-column column-width="proportional-column-width(1)" />
+  </xsl:template>
+
+
+
+  <xsl:template match="text()" mode="table-no-columns" />
+
+
+
+  <xsl:template match="fo:table-column">
+    <fo:table-column column-width="proportional-column-width(1)">
+      <xsl:apply-templates select="@*" />
+    </fo:table-column>
+  </xsl:template>
+
+
+
+  <xsl:template match="fo:table-cell//fo:block-container">
+    <fo:block>
+      <xsl:apply-templates select="@* | node()" />
+    </fo:block>
+  </xsl:template>
+
+</xsl:transform>
diff --git a/src/be/re/css/style/ua.css b/src/be/re/css/style/ua.css
new file mode 100644
index 0000000..b7d8d89
--- /dev/null
+++ b/src/be/re/css/style/ua.css
@@ -0,0 +1,3 @@
+ at import "xhtml_print.css";
+ at import "deltaxml.css";
+ at import "xlink.css";
diff --git a/src/be/re/css/style/util.xsl b/src/be/re/css/style/util.xsl
new file mode 100644
index 0000000..3f174c9
--- /dev/null
+++ b/src/be/re/css/style/util.xsl
@@ -0,0 +1,106 @@
+<?xml version='1.0'?>
+<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+  <xsl:template name="count-tokens">
+    <xsl:param name="s" />
+
+    <xsl:variable name="norm" select="normalize-space($s)" />
+
+    <xsl:choose>
+      <xsl:when test="string-length($norm) = 0">
+        <xsl:value-of select="number(0)" />
+      </xsl:when>
+      <xsl:when test="contains($norm, ' ')">
+        <xsl:variable name="value">
+          <xsl:call-template name="count-tokens">
+            <xsl:with-param name="s" select="substring-after($norm, ' ')" />
+          </xsl:call-template>
+        </xsl:variable>
+        <xsl:value-of select="$value + 1" />
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:value-of select="number(1)" />
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template name="get-token">
+    <xsl:param name="current-position" select="number(1)" />
+    <xsl:param name="position" />
+    <xsl:param name="s" />
+
+    <xsl:if test="$position < 1">
+      <xsl:message terminate="yes">Position for get-token must be greater than 0.</xsl:message>
+    </xsl:if>
+
+    <xsl:variable name="norm" select="normalize-space($s)" />
+
+    <xsl:choose>
+      <xsl:when test="$current-position = $position">
+        <xsl:choose>
+          <xsl:when test="contains($norm, ' ')">
+            <xsl:value-of select="substring-before($norm, ' ')" />
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:value-of select="$norm" />
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:choose>
+          <xsl:when test="contains($norm, ' ')">
+            <xsl:call-template name="get-token">
+              <xsl:with-param name="current-position" select="$current-position + 1" />
+              <xsl:with-param name="position" select="$position" />
+              <xsl:with-param name="s" select="substring-after($norm, ' ')" />
+            </xsl:call-template>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:text />
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template name="replace-string">
+    <xsl:param name="s" />
+    <xsl:param name="replace" />
+    <xsl:param name="by" />
+
+    <xsl:choose>
+      <xsl:when test="contains($s, $replace)">
+        <xsl:call-template name="replace-string">
+          <xsl:with-param name="s" select="concat(substring-before($s, $replace), $by,               substring-after($s, $replace))" />
+          <xsl:with-param name="replace" select="$replace" />
+          <xsl:with-param name="by" select="$by" />
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:value-of select="$s" />
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+  <xsl:template name="uri-last-path-segment">
+    <xsl:param name="uri" />
+
+    <xsl:choose>
+      <xsl:when test="contains($uri, '/')">
+        <xsl:call-template name="uri-last-path-segment">
+          <xsl:with-param name="uri" select="substring-after($uri, '/')" />
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:value-of select="$uri" />
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+</xsl:transform>
diff --git a/src/be/re/css/style/xhtml.css b/src/be/re/css/style/xhtml.css
new file mode 100644
index 0000000..15268f5
--- /dev/null
+++ b/src/be/re/css/style/xhtml.css
@@ -0,0 +1,160 @@
+ at namespace url(http://www.w3.org/1999/xhtml);
+
+address, blockquote, body, dd, div, dl, dt, fieldset, form, frame, frameset,
+  h1, h2, h3, h4, h5, h6, iframe, noframes, object, ol, p, ul, applet, center,
+  dir, hr, menu, pre { display: block }
+
+li { display: list-item }
+
+head { display: none }
+
+table { display: table }
+
+tr { display: table-row }
+
+thead { display: table-header-group }
+
+tbody { display: table-row-group }
+
+tfoot { display: table-footer-group }
+
+col { display: table-column }
+
+colgroup { display: table-column-group }
+
+td, th { display: table-cell }
+
+caption { display: table-caption }
+
+th { font-weight: bolder; text-align: center }
+
+caption { text-align: center }
+
+body { padding: 8px; line-height: 1.33 }
+
+h1 { font-size: 2em; margin: .67em 0 }
+
+h2 { font-size: 1.5em; margin: .83em 0 }
+
+h3 { font-size: 1.17em; margin: 1em 0 }
+
+h4, p, blockquote, ul, fieldset, form, ol, dl, dir, menu { margin: 1.33em 0 }
+
+h5 { font-size: .83em; line-height: 1.17em; margin: 1.67em 0 }
+
+h6 { font-size: .67em; margin: 2.33em 0 }
+
+h1, h2, h3, h4, h5, h6, b, strong { font-weight: bolder }
+
+blockquote { margin-left: 40px; margin-right: 40px }
+
+i, cite, em, var, address { font-style: italic }
+
+pre, tt, code, kbd, samp { font-family: monospace }
+
+pre { white-space: pre }
+
+big { font-size: 1.17em }
+
+small, sub, sup { font-size: .83em }
+
+sub { vertical-align: sub }
+
+sup { vertical-align: super }
+
+s, strike, del { text-decoration: line-through }
+
+hr { border: 1px inset }
+
+ol, ul, dir, menu, dd { margin-left: 40px }
+
+ol { list-style-type: decimal }
+
+ol ul, ul ol, ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
+
+u, ins { text-decoration: underline }
+
+center { text-align: center }
+
+br:before { content: "\a" }
+
+/* an example of style for html 4.0 s abbr/acronym elements */
+
+abbr, acronym { font-variant: small-caps; letter-spacing: 0.1em }
+
+a[href] { text-decoration: underline }
+
+:focus { outline: thin dotted invert }
+
+/* begin bidirectionality settings (do not change) */
+
+bdo[dir="ltr"] { direction: ltr; unicode-bidi: bidi-override }
+
+bdo[dir="rtl"] { direction: rtl; unicode-bidi: bidi-override }
+
+*[dir="ltr"] { direction: ltr; unicode-bidi: embed }
+
+*[dir="rtl"] { direction: rtl; unicode-bidi: embed }
+
+/* elements that are block-level in html4 */
+
+address, blockquote, body, dd, div, dl, dt, fieldset, form, frame, frameset, h1,
+  h2, h3, h4, h5, h6, iframe, noscript, noframes, object, ol, p, ul, applet,
+  center, dir, hr, menu, pre, li, table, tr, thead, tbody, tfoot, col, colgroup,
+  td, th, caption { unicode-bidi: embed }
+
+/* end bidi settings */
+
+/*@page { margin: 10% }*/
+
+ at media print
+{
+  h1, h2, h3, h4, h5, h6 { page-break-after: avoid; page-break-inside: avoid }
+
+  blockquote, pre { page-break-inside: avoid }
+
+  ul, ol, dl { page-break-before: avoid }
+}
+
+ at media speech
+{
+  h1, h2, h3, h4, h5, h6 { voice-family: paul, male; stress: 20; richness: 90 }
+
+  h1 { pitch: x-low; pitch-range: 90 }
+
+  h2 { pitch: x-low; pitch-range: 80 }
+
+  h3 { pitch: low; pitch-range: 70 }
+
+  h4 { pitch: medium; pitch-range: 60 }
+
+  h5 { pitch: medium; pitch-range: 50 }
+
+  h6 { pitch: medium; pitch-range: 40 }
+
+  li, dt, dd { pitch: medium; richness: 60 }
+
+  dt { stress: 80 }
+
+  pre, code, tt { pitch: medium; pitch-range: 0; stress: 0; richness: 80 }
+
+  em { pitch: medium; pitch-range: 60; stress: 60; richness: 50 }
+
+  strong { pitch: medium; pitch-range: 60; stress: 90; richness: 90 }
+
+  dfn { pitch: high; pitch-range: 60; stress: 60 }
+
+  s, strike { richness: 0 }
+
+  i { pitch: medium; pitch-range: 60; stress: 60; richness: 50 }
+
+  b { pitch: medium; pitch-range: 60; stress: 90; richness: 90 }
+
+  u { richness: 0 }
+
+  a:link { voice-family: harry, male }
+
+  a:visited { voice-family: betty, female }
+
+  a:active { voice-family: betty, female; pitch-range: 80; pitch: x-high }
+}
diff --git a/src/be/re/css/style/xhtml_print.css b/src/be/re/css/style/xhtml_print.css
new file mode 100644
index 0000000..d7f1aa9
--- /dev/null
+++ b/src/be/re/css/style/xhtml_print.css
@@ -0,0 +1,211 @@
+ at import "xhtml.css";
+ at namespace url(http://www.w3.org/1999/xhtml);
+
+ at media print
+{
+  a[href]
+  {
+    color: blue;
+    link: attr(href);
+    text-decoration: none;
+  }
+
+  a[name]
+  {
+    anchor: name;
+  }
+
+  blockquote, dl, ol, p, ul
+  {
+    margin: 0.83em 0pt;
+  }
+
+  blockquote
+  {
+    margin-left: 3em;
+    margin-right: 3em;
+  }
+
+  body
+  {
+    font-family: serif;
+    padding: 0pt;
+    region: body;
+  }
+
+  body:lang(da)
+  {
+    quotes: "\00BB" "\00AB";
+  }
+
+  body:lang(de-DE), body:lang(de-AT)
+  {
+    quotes: "\201E" "\201C" "\201A" "\2018"
+  }
+
+  body, body:lang(en), body:lang(es)
+  {
+    quotes: "\201C" "\201D" "\2018" "\2019";
+  }
+
+  body:lang(fr)
+  {
+    quotes: "\00AB " " \00BB" "\2039 " " \203A";
+  }
+
+  body:lang(it)
+  {
+    quotes: "\00AB " " \00BB";
+  }
+
+  body:lang(nl)
+  {
+    quotes: "\201D" "\201D" "\2019" "\2019";
+  }
+
+  body:lang(no), bodylang:(pt), body:lang(de-CH)
+  {
+    quotes: "\00AB" "\00BB" "\2039" "\203A"
+  }
+
+  body:lang(sv)
+  {
+    quotes: "\00BB" "\00BB";
+  }
+
+  caption
+  {
+    margin: 0.5em 0pt;
+  }
+
+  dt
+  {
+    page-break-after: avoid;
+  }
+
+  h1
+  {
+    font-size: 1.6em;
+    margin-bottom: 0.7em;
+    margin-top: 1.4em;
+  }
+
+  h2
+  {
+    font-size: 1.3em;
+    margin-bottom: 0.6em;
+    margin-top: 1.2em;
+  }
+
+  h3
+  {
+    font-size: 1.1em;
+  }
+
+  h3, h4
+  {
+    margin-bottom: 0.5em;
+    margin-top: 1em;
+  }
+
+  h1, h2, h3, h4, h5, h6
+  {
+    hyphenate: false;
+  }
+
+  hr
+  {
+    border: 0.1pt solid;
+  }
+
+  img
+  {
+    content-height: scale-to-fit;
+    content-width: scale-to-fit;
+    display: graphic;
+    scaling: uniform;
+    src: attr(src);
+  }
+
+  li
+  {
+    margin-bottom: 0.8em;
+    margin-top: 0.8em;
+  }
+
+  li p, li blockquote, li dl, li ol, li ul
+  {
+    margin-bottom: 0.5em;
+    margin-top: 0.5em;
+  }
+
+  li li
+  {
+    margin-bottom: 0.5em;
+    margin-top: 0.5em;
+  }
+
+  li li p, li li blockquote, li li dl, li li ol, li li ul
+  {
+    margin-bottom: 0.3em;
+    margin-top: 0.3em;
+  }
+
+  li li li
+  {
+    margin-bottom: 0.4em;
+    margin-top: 0.4em;
+  }
+
+  li li li p, li li li blockquote, li li li dl, li li li ol,
+    li li li ul
+  {
+    margin-bottom: 0.3em;
+    margin-top: 0.3em;
+  }
+
+  li, p
+  {
+    text-align: justify;
+  }
+
+  pre
+  {
+    font-size: 0.85em;
+  }
+
+  ul
+  {
+    list-style-type: disc;
+  }
+
+  ol li ul, ul li ul
+  {
+    list-style-type: circle;
+  }
+
+  ol li ol li ul, ol li ul li ul, ul li ol li ul, ul li ul li ul
+  {
+    list-style-type: square;
+  }
+
+  q:after
+  {
+    content: close-quote;
+  }
+
+  q:before
+  {
+    content: open-quote;
+  }
+
+  script
+  {
+    display: none;
+  }
+
+  span.section-number
+  {
+    padding-right: 1em;
+  }
+}
diff --git a/src/be/re/css/style/xlink.css b/src/be/re/css/style/xlink.css
new file mode 100644
index 0000000..baf38e1
--- /dev/null
+++ b/src/be/re/css/style/xlink.css
@@ -0,0 +1,9 @@
+ at namespace xlink url(http://www.w3.org/1999/xlink);
+
+ at media print
+{
+  *[xlink|href]
+  {
+    link: attr(xlink|href);
+  }
+}
diff --git a/src/be/re/io/FlushOutputStream.java b/src/be/re/io/FlushOutputStream.java
new file mode 100644
index 0000000..fe4e267
--- /dev/null
+++ b/src/be/re/io/FlushOutputStream.java
@@ -0,0 +1,28 @@
+package be.re.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+
+public class FlushOutputStream extends FilterOutputStream
+
+{
+
+  public
+  FlushOutputStream(OutputStream out)
+  {
+    super(out);
+  }
+
+
+
+  public void
+  write(byte b) throws IOException
+  {
+    out.write(b);
+    out.flush();
+  }
+
+} // FlushOutputStream
diff --git a/src/be/re/io/IOException.java b/src/be/re/io/IOException.java
new file mode 100644
index 0000000..48285ee
--- /dev/null
+++ b/src/be/re/io/IOException.java
@@ -0,0 +1,43 @@
+package be.re.io;
+
+/**
+ * Make it possible to wrap a cause.
+ * @author Werner Donn\u00e9
+ */
+
+public class IOException extends java.io.IOException
+
+{
+
+  public
+  IOException()
+  {
+  }
+
+
+
+  public
+  IOException(String s)
+  {
+    super(s);
+  }
+
+
+
+  public
+  IOException(String message, Throwable cause)
+  {
+    super(message);
+    initCause(cause);
+  }
+
+
+
+  public
+  IOException(Throwable cause)
+  {
+    super();
+    initCause(cause);
+  }
+
+} // IOException
diff --git a/src/be/re/io/ReadLineInputStream.java b/src/be/re/io/ReadLineInputStream.java
new file mode 100644
index 0000000..3f41f9f
--- /dev/null
+++ b/src/be/re/io/ReadLineInputStream.java
@@ -0,0 +1,96 @@
+package be.re.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.util.Arrays;
+
+
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class ReadLineInputStream extends FilterInputStream
+
+{
+
+  public
+  ReadLineInputStream(InputStream in)
+  {
+    super(new PushbackInputStream(in));
+  }
+
+
+
+  /**
+   * Reads bytes until '\r', '\n' or "\r\n" is encountered and returns them in
+   * an array. The line termination sequence is discarded and not included in
+   * the array. If the end of the stream has been reached <code>null</code> is
+   * returned.
+   */
+
+  public byte[]
+  readLine() throws IOException
+  {
+    return readLine(new byte[1024], 0);
+  }
+
+
+
+  private byte[]
+  readLine(byte[] buffer, int off) throws IOException
+  {
+    Arrays.fill(buffer, off, buffer.length, (byte) 0);
+
+    int	i;
+
+    for
+    (
+      i = off;
+      i < buffer.length && (buffer[i] = (byte) in.read()) != '\n' &&
+        buffer[i] != '\r' && buffer[i] != -1;
+      ++i
+    );
+
+    if (i < buffer.length)
+    {
+      if (i == 0 && buffer[i] == -1)
+      {
+        return null;
+      }
+
+      byte[]	result = new byte[i];
+
+      System.arraycopy(buffer, off, result, 0, result.length);
+
+      if ((char) buffer[i] == '\r')
+      {
+        byte	b = (byte) in.read();
+
+        if (b != '\n')
+        {
+          ((PushbackInputStream) in).unread(b);
+        }
+      }
+
+      return result;
+    }
+
+    return readLine(realloc(buffer), i);
+  }
+
+
+
+  private static byte[]
+  realloc(byte[] buffer)
+  {
+    byte[]	result = new byte[buffer.length * 2];
+
+    System.arraycopy(buffer, 0, result, 0, buffer.length);
+
+    return result;
+  }
+
+} // ReadLineInputStream
diff --git a/src/be/re/io/StreamConnector.java b/src/be/re/io/StreamConnector.java
new file mode 100644
index 0000000..5157ee4
--- /dev/null
+++ b/src/be/re/io/StreamConnector.java
@@ -0,0 +1,245 @@
+package be.re.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+
+/**
+ * This class reads data from an input stream and writes it to an output stream
+ * in a separate thread. The thread stops when the input stream is closed
+ * or when an exception occurs.
+ * @author Werner Donn\u00e9
+ */
+
+public class StreamConnector
+
+{
+
+  private IOException	exception = null;
+  private Thread	thread;
+
+
+
+  public
+  StreamConnector(InputStream in, OutputStream out)
+  {
+    this(in, out, true, true);
+  }
+
+
+
+  public
+  StreamConnector
+  (
+    InputStream		in,
+    OutputStream	out,
+    boolean		closeInput,
+    boolean		closeOutput
+  )
+  {
+    this(in, out, 0x10000, closeInput, closeOutput);
+  }
+
+
+
+  public
+  StreamConnector
+  (
+    InputStream		in,
+    OutputStream	out,
+    boolean		closeInput,
+    boolean		closeOutput,
+    boolean		flush
+  )
+  {
+    this(in, out, 0x10000, closeInput, closeOutput, flush);
+  }
+
+
+
+  public
+  StreamConnector
+  (
+    InputStream		in,
+    OutputStream	out,
+    int			bufferSize,
+    boolean		closeInput,
+    boolean		closeOutput
+  )
+  {
+    this(in, out, bufferSize, closeInput, closeOutput, true);
+  }
+
+
+
+  public
+  StreamConnector
+  (
+    final InputStream	in,
+    final OutputStream	out,
+    final int		bufferSize,
+    final boolean	closeInput,
+    final boolean	closeOutput,
+    final boolean	flush
+  )
+  {
+    (
+      thread =
+        new Thread
+        (
+          new Runnable()
+          {
+            public void
+            run()
+            {
+              try
+              {
+                copy
+                (
+                  in,
+                  out,
+                  bufferSize,
+                  closeInput,
+                  closeOutput,
+                  flush
+                );
+              }
+
+              catch (IOException e)
+              {
+                exception = e;
+              }
+            }
+          }
+        )
+    ).start();
+  }
+
+
+
+  public static void
+  copy(InputStream in, OutputStream out) throws IOException
+  {
+    copy(in, out, true, true);
+  }
+
+
+
+  public static void
+  copy
+  (
+    InputStream		in,
+    OutputStream	out,
+    boolean		closeInput,
+    boolean		closeOutput
+  ) throws IOException
+  {
+    copy(in, out, 0x10000, closeInput, closeOutput);
+  }
+
+
+
+  public static void
+  copy
+  (
+    InputStream		in,
+    OutputStream	out,
+    boolean		closeInput,
+    boolean		closeOutput,
+    boolean		flush
+  ) throws IOException
+  {
+    copy(in, out, 0x10000, closeInput, closeOutput, flush);
+  }
+
+
+
+  public static void
+  copy
+  (
+    InputStream		in,
+    OutputStream	out,
+    int			bufferSize,
+    boolean		closeInput,
+    boolean		closeOutput
+  ) throws IOException
+  {
+    copy(in, out, bufferSize, closeInput, closeOutput, true);
+  }
+
+
+
+  public static void
+  copy
+  (
+    InputStream		in,
+    OutputStream	out,
+    int			bufferSize,
+    boolean		closeInput,
+    boolean		closeOutput,
+    boolean		flush
+  ) throws IOException
+  {
+    byte[]	buffer = new byte[bufferSize];
+    int		len;
+
+    while ((len = in.read(buffer)) != -1)
+    {
+      out.write(buffer, 0, len);
+
+      if (flush)
+      {
+        out.flush();
+      }
+    }
+
+    if (closeInput)
+    {
+      in.close();
+    }
+
+    if (closeOutput)
+    {
+      out.close();
+    }
+    else
+    {
+      if (flush)
+      {
+        out.flush();
+      }
+    }
+  }
+
+
+
+  /**
+   * Breaks the connection. It doesn't touch the streams.
+   */
+
+  public void
+  disconnect()
+  {
+    thread.interrupt();
+  }
+
+
+
+  /**
+   * Joins with the thread implementing the io processing.
+   */
+
+  public void
+  join() throws IOException, InterruptedException
+  {
+    thread.join();
+
+    if (exception != null)
+    {
+      throw exception;
+    }
+  }
+
+} // StreamConnector
diff --git a/src/be/re/io/Util.java b/src/be/re/io/Util.java
new file mode 100644
index 0000000..d78143d
--- /dev/null
+++ b/src/be/re/io/Util.java
@@ -0,0 +1,190 @@
+package be.re.io;
+
+import be.re.util.UUID;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+
+
+public class Util
+
+{
+
+  private static ResourceBundle	bundle = null;
+  private static boolean	deleteOnExitHookAdded = false;
+  private static final Set	dirsToDelete = new HashSet();
+
+
+
+  /**
+   * Creates a temporary directory. When the resulting <code>java.io.File</code>
+   * object is garbage collected, the directory will not be deleted along. If
+   * the caller hasn't deleted it the file will be deleted on exit of the VM.
+   */
+
+  public static File
+  createTempDir(String prefix, String suffix, File directory)
+    throws IOException
+  {
+    final File	result = createTempFileName(prefix, suffix, directory);
+
+    if (!result.mkdir())
+    {
+      throw
+        new IOException
+        (
+          "Can't create temporary directory \"" + result.getAbsolutePath() +
+            "\"."
+        );
+    }
+
+    deleteDirOnExit(result);
+
+    return
+      new File(result.getAbsolutePath())
+      {
+        public boolean
+        delete()
+        {
+          synchronized (dirsToDelete)
+          {
+            dirsToDelete.remove(result);
+          }
+
+          return result.delete();
+        }
+      };
+  }
+
+
+
+  public static File
+  createTempFile(String prefix, String suffix) throws IOException
+  {
+    return createTempFile(prefix, suffix, null);
+  }
+
+
+
+  /**
+   * Creates a temporary file. When the resulting <code>java.io.File</code>
+   * object is garbage collected, the file will not be deleted along. If the
+   * caller hasn't deleted it the file will be deleted on exit of the VM.
+   */
+
+  public static File
+  createTempFile(String prefix, String suffix, File directory)
+    throws IOException
+  {
+    File	result = createTempFileName(prefix, suffix, directory);
+
+    if (!result.createNewFile())
+    {
+      throw
+        new IOException
+        (
+          "Can't create temporary file \"" + result.getAbsolutePath() + "\"."
+        );
+    }
+
+    result.deleteOnExit();
+
+    return result;
+  }
+
+
+
+  public static File
+  createTempFileName(String prefix, String suffix, File directory)
+  {
+    return
+      new File
+      (
+        directory != null ?
+          directory : new File(System.getProperty("java.io.tmpdir")),
+        (prefix != null ? prefix : "") + UUID.generateFormatted() +
+          (suffix != null ? suffix : ".tmp")
+      );
+  }
+
+
+
+  public static boolean
+  deleteDir(File dir)
+  {
+    if (dir.isDirectory())
+    {
+      File[]	files = dir.listFiles();
+
+      for (int i = 0; i < files.length; ++i)
+      {
+        deleteDir(files[i]);
+      }
+    }
+
+    return dir.delete();
+  }
+
+
+
+  public static synchronized void
+  deleteDirOnExit(File dir)
+  {
+    if (!deleteOnExitHookAdded)
+    {
+      deleteOnExitHookAdded = true;
+
+      Runtime.getRuntime().addShutdownHook
+      (
+        new Thread
+        (
+          new Runnable()
+          {
+            public void
+            run()
+            {
+              synchronized (dirsToDelete)
+              {
+                for (Iterator i = dirsToDelete.iterator(); i.hasNext();)
+                {
+                  deleteDir((File) i.next());
+                }
+              }
+            }
+          }
+        )
+      );
+    }
+
+    synchronized (dirsToDelete)
+    {
+      dirsToDelete.add(dir);
+    }
+  }
+
+
+
+  static String
+  getResource(String key)
+  {
+    if (bundle == null)
+    {
+      bundle = ResourceBundle.getBundle("be.re.io.res.Res");
+    }
+
+    return bundle.getString(key);
+  }
+
+
+
+  public static boolean
+  isSymbolicLink(File file) throws IOException
+  {
+    return !file.getCanonicalPath().equals(file.getAbsolutePath());
+  }
+
+} // Util
diff --git a/src/be/re/net/BasicUser.java b/src/be/re/net/BasicUser.java
new file mode 100644
index 0000000..570a8a0
--- /dev/null
+++ b/src/be/re/net/BasicUser.java
@@ -0,0 +1,142 @@
+package be.re.net;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+
+
+public class BasicUser implements User
+
+{
+
+  private String		password;
+  private PropertyChangeSupport	propertyChange =
+    new PropertyChangeSupport(this);
+  private String		username;
+
+
+
+  public
+  BasicUser()
+  {
+    this(null, null);
+  }
+
+
+
+  public
+  BasicUser(String username, String password)
+  {
+    this.username = username;
+    this.password = password;
+  }
+
+
+
+  public void
+  addPropertyChangeListener(PropertyChangeListener listener)
+  {
+    propertyChange.addPropertyChangeListener(listener);
+  }
+
+
+
+  public void
+  addPropertyChangeListener
+  (
+    String			propertyName,
+    PropertyChangeListener	listener
+  )
+  {
+    propertyChange.addPropertyChangeListener(propertyName, listener);
+  }
+
+
+
+  public String
+  getPassword()
+  {
+    return password;
+  }
+
+
+
+  public String
+  getUsername()
+  {
+    return username;
+  }
+
+
+
+  public void
+  removePropertyChangeListener(PropertyChangeListener listener)
+  {
+    propertyChange.removePropertyChangeListener(listener);
+  }
+
+
+
+  public void
+  removePropertyChangeListener
+  (
+    String			propertyName,
+    PropertyChangeListener	listener
+  )
+  {
+    propertyChange.removePropertyChangeListener(propertyName, listener);
+  }
+
+
+
+  public void
+  setPassword(String value)
+  {
+    if
+    (
+      (
+        value == null		&&
+        password == null
+      )				||
+      (
+        value != null		&&
+        value.equals(password)
+      )
+    )
+    {
+      return;
+    }
+
+    String	old = password;
+
+    password = value;
+    propertyChange.firePropertyChange("password", old, value);
+  }
+
+
+
+  public void
+  setUsername(String value)
+  {
+    if
+    (
+      (
+        value == null		&&
+        username == null
+      )				||
+      (
+        value != null		&&
+        value.equals(username)
+      )
+    )
+    {
+      return;
+    }
+
+    String	old = username;
+
+    username = value;
+    propertyChange.firePropertyChange("username", old, value);
+  }
+
+} // BasicUser
diff --git a/src/be/re/net/Headers.java b/src/be/re/net/Headers.java
new file mode 100644
index 0000000..d16285e
--- /dev/null
+++ b/src/be/re/net/Headers.java
@@ -0,0 +1,282 @@
+package be.re.net;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+
+/**
+ * Several Internet protocols use headers. This class can be used to manage
+ * them. Headers can occur more than once. Even name/value pairs may be
+ * dupplicated.
+ * @author Werner Donn\u00e9
+ */
+
+public class Headers
+
+{
+
+  private List	headers = new ArrayList();
+
+
+
+  /**
+   * Adds a name/value pair.
+   */
+
+  public void
+  add(String name, String value)
+  {
+    headers.add(new Header(name, value));
+  }
+
+
+
+  /**
+   * Adds all the headers in <code>headers</code>.
+   */
+
+  public void
+  add(Headers headers)
+  {
+    Header[]	all = headers.getAll();
+
+    for (int i = 0; i < all.length; ++i)
+    {
+      add(all[i].getName(), all[i].getValue());
+    }
+  }
+
+
+
+  /**
+   * Removes all headers.
+   */
+
+  public void
+  clear()
+  {
+    headers.clear();
+  }
+
+
+
+  /**
+   * Returns all the values for the header <code>name</code> in the order of
+   * appearance.
+   */
+
+  public String[]
+  get(String name)
+  {
+    List	values = new ArrayList();
+
+    for (Iterator i = headers.iterator(); i.hasNext();)
+    {
+      Header	tuple = (Header) i.next();
+
+      if (tuple.name.equalsIgnoreCase(name))
+      {
+        values.add(tuple.value);
+      }
+    }
+
+    return (String[]) values.toArray(new String[values.size()]);
+  }
+
+
+
+  /**
+   * Returns all headers.
+   */
+
+  public Header[]
+  getAll()
+  {
+    return (Header[]) headers.toArray(new Header[headers.size()]);
+  }
+
+
+
+  /**
+   * Returns all the values for the header <code>name</code> in the order of
+   * appearance. If a header value is a comma-separated list, the elements in
+   * the list are added separately in the order of appearance and without
+   * surrounding whitespace.
+   */
+
+  public String[]
+  getValuesFromList(String name)
+  {
+    List	values = new ArrayList();
+
+    for (Iterator i = headers.iterator(); i.hasNext();)
+    {
+      Header	tuple = (Header) i.next();
+
+      if (tuple.name.equalsIgnoreCase(name))
+      {
+        StringTokenizer	tokenizer = new StringTokenizer(tuple.value, ",");
+
+        while (tokenizer.hasMoreTokens())
+        {
+          values.add(tokenizer.nextToken().trim());
+        }
+      }
+    }
+
+    return (String[]) values.toArray(new String[values.size()]);
+  }
+
+
+
+  /**
+   * Removes all headers with <code>name</code> as their name.
+   */
+
+  public void
+  remove(String name)
+  {
+    for (Iterator i = headers.iterator(); i.hasNext();)
+    {
+      Header	tuple = (Header) i.next();
+
+      if (tuple.name.equalsIgnoreCase(name))
+      {
+        i.remove();
+      }
+    }
+  }
+
+
+
+  /**
+   * Removes one name/value pair.
+   */
+
+  public void
+  remove(String name, String value)
+  {
+    for (Iterator i = headers.iterator(); i.hasNext();)
+    {
+      Header	tuple = (Header) i.next();
+
+      if (tuple.name.equalsIgnoreCase(name) && tuple.value.equals(value))
+      {
+        i.remove();
+      }
+    }
+  }
+
+
+
+  /**
+   * Replaces all the headers with <code>name</code> as their name with the
+   * given name/value pair.
+   */
+
+  public void
+  set(String name, String value)
+  {
+    remove(name);
+    add(name, value);
+  }
+
+
+
+  /**
+   * Replaces one name/value pair.
+   */
+
+  public void
+  set(String name, String oldValue, String newValue)
+  {
+    for (Iterator i = headers.iterator(); i.hasNext();)
+    {
+      Header	tuple = (Header) i.next();
+
+      if (tuple.name.equalsIgnoreCase(name) && tuple.value.equals(oldValue))
+      {
+        tuple.value = newValue;
+      }
+    }
+  }
+
+
+
+  /**
+   * Returns the number of headers.
+   */
+
+  public int
+  size()
+  {
+    return headers.size();
+  }
+
+
+
+  /**
+   * A string representation of all headers. The string can be used in Internet
+   * protocols.
+   */
+
+  public String
+  toString()
+  {
+    String	result = "";
+
+    for (Iterator i = headers.iterator(); i.hasNext();)
+    {
+      Header	tuple = (Header) i.next();
+
+      result += tuple.name + ":" + tuple.value + "\r\n";
+    }
+
+    return result;
+  }
+
+
+
+  /**
+   * Represents one header.
+   * @author Werner Donn\u00e9
+   */
+
+  public class Header
+
+  {
+
+    private String	name;
+    private String	value;
+
+
+
+    public
+    Header(String name, String value)
+    {
+      this.name = name;
+      this.value = value;
+    }
+
+
+
+    public String
+    getName()
+    {
+      return name;
+    }
+
+
+
+    public String
+    getValue()
+    {
+      return value;
+    }
+
+  } // Header
+
+} // Headers
diff --git a/src/be/re/net/User.java b/src/be/re/net/User.java
new file mode 100644
index 0000000..b61bdec
--- /dev/null
+++ b/src/be/re/net/User.java
@@ -0,0 +1,9 @@
+package be.re.net;
+
+public interface User
+{
+  public String	getPassword	();
+  public String	getUsername	();
+  public void	setPassword	(String password);
+  public void	setUsername	(String username);
+}
diff --git a/src/be/re/net/Util.java b/src/be/re/net/Util.java
new file mode 100644
index 0000000..12118a5
--- /dev/null
+++ b/src/be/re/net/Util.java
@@ -0,0 +1,1265 @@
+package be.re.net;
+
+import be.re.io.ReadLineInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+
+
+
+public class Util
+
+{
+
+  private final static char[]	MARK =
+    {'-', '_', '.', '!', '~', '*', '\'', '(', ')'};
+  private final static char[]	PCHAR_SPECIALS =
+    {':', '@', '&', '=', '+', '$', ','}; // Less than uric_no_slash.
+  private final static char[]	RESERVED =
+    {';', '/', '?', ':', '@', '&', '=', '+', '$', ','};
+
+  private final static String[]	archiveExtensions =
+    {"ear", "jar", "rar", "war", "zip"};
+  private static ResourceBundle	bundle = null;
+  private static Map		bundles = new HashMap();
+
+
+
+  private static boolean
+  compareBytes(byte[] b1, byte[] b2, int length)
+  {
+    for (int i = 0; i < length; ++i)
+    {
+      if (b1[i] != b2[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  private static String
+  escapeUriPart(String s, TestChar t)
+  {
+    byte[]	bytes = null;
+    String	result = "";
+
+    try
+    {
+      bytes = s.getBytes("UTF-8");
+    }
+
+    catch (UnsupportedEncodingException e)
+    {
+      throw new RuntimeException(e); // Would be a bug.
+    }
+
+    char[]	c = new char[bytes.length];
+
+    for (int i = 0; i < bytes.length; ++i)
+    {
+      c[i] = (char) (0xff & bytes[i]);
+    }
+
+    for (int i = 0; i < c.length; ++i)
+    {
+      result +=
+        !t.test(c, i) ?
+          ("%" + (c[i] < 0x10 ? "0" : "") + Integer.toHexString(c[i])) :
+          new String(c, i, 1);
+    }
+
+    return result;
+  }
+
+
+
+  public static String
+  escapeUriPathSegment(String segment)
+  {
+    return escapeUriPathSegment(segment, false);
+  }
+
+
+
+  public static String
+  escapeUriPathSegment(String segment, final boolean noParameters)
+  {
+    return
+      escapeUriPart
+      (
+        segment,
+        new TestChar()
+        {
+          public boolean
+          test(char[] c, int i)
+          {
+            return
+              isPChar(c[i]) || (!noParameters && c[i] == ';') ||
+                isStartOfEscape(c, i);
+          }
+        }
+      );
+  }
+
+
+
+  public static String
+  escapeUriPathSegments(String path)
+  {
+    return escapeUriPathSegments(path, false);
+  }
+
+
+
+  public static String
+  escapeUriPathSegments(String path, boolean noParameters)
+  {
+    String		result = "";
+    StringTokenizer	tokenizer = new StringTokenizer(path, "/");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      result +=
+        (result.equals("") ? "" : "/") +
+          escapeUriPathSegment(tokenizer.nextToken(), noParameters);
+    }
+
+    return
+      (path.length() > 0 && path.charAt(0) == '/' ? "/" : "") + result +
+        (path.length() > 1 && path.charAt(path.length() - 1) == '/' ? "/" : "");
+  }
+
+
+
+  public static String
+  escapeUriQueryString(String queryString)
+  {
+    return
+      escapeUriPart
+      (
+        queryString,
+        new TestChar()
+        {
+          public boolean
+          test(char[] c, int i)
+          {
+            return (!isReserved(c[i]) || c[i] == '=') && isUriChar(c, i);
+          }
+        }
+      );
+  }
+
+
+
+  public static String
+  escapeUriQueryStrings(String path)
+  {
+    String		result = "";
+    StringTokenizer	tokenizer = new StringTokenizer(path, "&");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      result +=
+        (result.equals("") ? "" : "&") +
+          escapeUriQueryString(tokenizer.nextToken());
+    }
+
+    return result;
+  }
+
+
+
+  public static String
+  escapeUriReference(String reference)
+  {
+    return
+      escapeUriPart
+      (
+        reference,
+        new TestChar()
+        {
+          public boolean
+          test(char[] c, int i)
+          {
+            return !isReserved(c[i]) && isUriChar(c, i);
+          }
+        }
+      );
+  }
+
+
+
+  private static URL
+  escapedComposedUrl(String url) throws MalformedURLException
+  {
+    return
+      new URL
+      (
+        url.substring(0, url.indexOf(':')) + ":" +
+          escapedUrl(extractSubUrl(url).toString()).toString() + "!/" +
+          escapeUriPathSegments(extractComposedUrlEntry(url)).toString()
+      );
+  }
+
+
+
+  private static File
+  escapedFile(File file)
+  {
+    return
+      new File
+      (
+        escapeUriPathSegments
+        (
+          file.getAbsolutePath().replace(File.separatorChar, '/')
+        )
+      );
+  }
+
+
+
+  public static String
+  escapedRelativeUrl(String url, String protocol)
+  {
+    return escapedRelativeUrl(url, protocol, false);
+  }
+
+
+
+  public static String
+  escapedRelativeUrl(String url, String protocol, boolean noParameters)
+  {
+    int	queryStart = -1;
+    int	referenceStart = -1;
+
+    if ("http".equals(protocol) || "https".equals(protocol))
+    {
+      queryStart = url.indexOf('?');
+      referenceStart = url.indexOf('#', queryStart != -1 ? queryStart : 0);
+    }
+
+    return
+      escapeUriPathSegments
+      (
+        url.substring
+        (
+          0,
+          queryStart != -1 ?
+            queryStart : (referenceStart != -1 ?  referenceStart : url.length())
+        ),
+        noParameters
+      ) +
+      (
+        queryStart != -1 ?
+          (
+            "?" +
+              escapeUriQueryStrings
+              (
+                url.substring
+                (
+                  queryStart + 1,
+                  referenceStart != -1 ? referenceStart : url.length()
+                )
+              )
+          ) : ""
+      ) +
+      (
+        referenceStart != -1 ?
+          ("#" + escapeUriReference(url.substring(referenceStart + 1))) : ""
+      );
+  }
+
+
+
+  public static URL
+  escapedUrl(String url) throws MalformedURLException
+  {
+    return escapedUrl(url, false);
+  }
+
+
+
+  public static URL
+  escapedUrl(String url, boolean noParameters) throws MalformedURLException
+  {
+    int	colon = url.indexOf(':');
+
+    if (colon != -1 && isComposedUrl(url))
+    {
+      return escapedComposedUrl(url);
+    }
+
+    int	pathStart =
+      colon != -1 ?
+        url.indexOf
+        (
+          '/',
+          colon +
+            (
+              url.length() > colon + 2 &&
+                url.substring(colon + 1, colon + 3).equals("//") ? 3 : 1
+            )
+        ) : 0;
+
+    return
+      pathStart == -1 ?
+        new URL(url) :
+        new URL
+        (
+          url.substring(0, pathStart) +
+            escapedRelativeUrl
+            (
+              url.substring(pathStart),
+              colon != -1 ? url.substring(0, colon) : null,
+              noParameters
+            )
+        );
+  }
+
+
+
+  public static String
+  extractComposedUrlEntry(URL url)
+  {
+    return extractComposedUrlEntry(url.getFile());
+  }
+
+
+
+  public static String
+  extractComposedUrlEntry(String url)
+  {
+    int	index = url.lastIndexOf("!/");
+
+    return
+      index == -1 || index + 2 == url.length() ? "" : url.substring(index + 2);
+  }
+
+
+
+  public static URL
+  extractSubUrl(URL url) throws MalformedURLException
+  {
+    if (!isComposedUrl(url))
+    {
+      throw new MalformedURLException(url.toString() + ": no sub-URL");
+    }
+
+    return
+      new URL
+      (
+        url.toString().substring
+        (
+          url.getProtocol().length() + 1, url.toString().lastIndexOf("!/")
+        )
+      );
+  }
+
+
+
+  public static URL
+  extractSubUrl(String url) throws MalformedURLException
+  {
+    return extractSubUrl(new URL(url));
+  }
+
+
+
+  public static URL
+  fileToUrl(File file)
+  {
+    try
+    {
+      return
+        escapedFile
+        (
+          new File
+          (
+            file.getAbsolutePath().replace(File.separatorChar, '/')
+          )
+        ).toURL();
+    }
+
+    catch (MalformedURLException e)
+    {
+      throw new RuntimeException(e);
+        // We made sure the path can be parsed as an URL by escaping it.
+    }
+  }
+
+
+
+  private static String
+  getExtension(URL url)
+  {
+    return url.toString().substring(url.toString().lastIndexOf(".") + 1);
+  }
+
+
+
+  public static String
+  getFTPFilename(URL url)
+  {
+    // According to RFC 959 the file system conventions of the involved
+    // server must be followed. The applicable information is, however, not
+    // available anymore in the URL, because there the URI rules are followed.
+    // Therefore, we assure to have forward slashes, which are widely accepted.
+
+    return urlToFile(url).getPath().replace('\\', '/');
+  }
+
+
+
+  public static String
+  getHttpReasonPhrase(int statusCode)
+  {
+    return getHttpReasonPhrase(statusCode, new String[0]);
+  }
+
+
+
+  public static String
+  getHttpReasonPhrase(int statusCode, String languageTag)
+  {
+    return getHttpReasonPhrase(statusCode, new String[]{languageTag});
+  }
+
+
+
+  public static String
+  getHttpReasonPhrase(int statusCode, String[] languageTags)
+  {
+    return getResource("http_" + String.valueOf(statusCode), languageTags);
+  }
+
+
+
+  public static String
+  getJarEntry(URL url)
+  {
+    return unescapeUriSpecials(extractComposedUrlEntry(url));
+  }
+
+
+
+  public static String
+  getLastPathSegment(URL url)
+  {
+    return
+      getLastPathSegment
+      (
+        isComposedUrl(url) ?
+          url.getFile().substring(url.getFile().lastIndexOf("!/") + 1) :
+          url.getFile()
+      );
+  }
+
+
+
+  /**
+   * A trailing slash is ignored to determine the last segment, but it is part
+   * of it if it is there.
+   */
+
+  public static String
+  getLastPathSegment(String path)
+  {
+    // We're not interested in the last character if it is a slash.
+
+    path = path.replace('\\', '/');
+
+    return path.substring(path.lastIndexOf('/', path.length() - 2) + 1);
+  }
+
+
+
+  public static Properties
+  getParameters(String queryString)
+  {
+    if (queryString == null)
+    {
+      return new Properties();
+    }
+
+    Properties		result = new Properties();
+    StringTokenizer	tokenizer = new StringTokenizer(queryString, "&");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String	token = tokenizer.nextToken();
+      int	index = token.indexOf('=');
+
+      if (index != -1)
+      {
+        result.
+          setProperty(token.substring(0, index), token.substring(index + 1));
+      }
+    }
+
+    return result;
+  }
+
+
+
+  public static String[]
+  getPathSegments(String path)
+  {
+    List		result = new ArrayList();
+    StringTokenizer	tokenizer = new StringTokenizer(path, "/");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String	token = tokenizer.nextToken();
+
+      if (!"".equals(token))
+      {
+        result.add(token);
+      }
+    }
+
+    return (String[]) result.toArray(new String[0]);
+  }
+
+
+
+  public static String
+  getResource(String key)
+  {
+    if (bundle == null)
+    {
+      bundle = ResourceBundle.getBundle("be.re.net.res.Res");
+    }
+
+    return bundle.getString(key);
+  }
+
+
+
+  public static String
+  getResource(String key, String languageTag)
+  {
+    return getResource(key, new String[]{languageTag});
+  }
+
+
+
+  /**
+   * Try <code>languageTags</code> until a match is found. Return the default
+   * otherwise.
+   */
+
+  public static String
+  getResource(String key, String[] languageTags)
+  {
+    for (int i = 0; i < languageTags.length; ++i)
+    {
+      ResourceBundle	bundle =
+        (ResourceBundle)
+          bundles.get(be.re.util.Util.getLocale(languageTags[i]));
+
+      if (bundle == null)
+      {
+        bundle = getResourceBundle(languageTags[i]);
+
+        if (bundle != null)
+        {
+          bundles.put(be.re.util.Util.getLocale(languageTags[i]), bundle);
+        }
+      }
+
+      if (bundle != null)
+      {
+        return bundle.getString(key);
+      }
+    }
+
+    return getResource(key);
+  }
+
+
+
+  private static ResourceBundle
+  getResourceBundle(String language)
+  {
+    try
+    {
+      Locale		locale = be.re.util.Util.getLocale(language);
+      ResourceBundle	result =
+        ResourceBundle.getBundle("be.re.net.res.Res", locale);
+
+      return result.getLocale().equals(locale) ? result : null;
+    }
+
+    catch (MissingResourceException e)
+    {
+      return null;
+    }
+  }
+
+
+
+  public static boolean
+  hasBody(Headers headers)
+  {
+    return
+      (
+        headers.get("Content-Length").length > 0 &&
+          Integer.parseInt(headers.get("Content-Length")[0]) > 0
+      ) || headers.get("Transfer-Encoding").length > 0;
+  }
+
+
+
+  public static long
+  httpDate(String s)
+  {
+    String[]	patterns =
+      new String[]
+      {
+        "EEE, dd MMM yyyy HH:mm:ss Z", // RFC1123-date
+        "EEEE, dd-MMM-yy HH:mm:ss Z", // RFC850-date
+        "EEE MMM dd HH:mm:ss yyyy", // ASCTIME-date
+        "EEE MMM  d HH:mm:ss yyyy" // ASCTIME-date
+      };
+
+    for (int i = 0; i < patterns.length; ++i)
+    {
+      try
+      {
+        Date	date =
+          new SimpleDateFormat(patterns[i], new Locale("en", "US")).parse(s);
+
+        if (date != null)
+        {
+          return date.getTime();
+        }
+      }
+
+      catch (ParseException e)
+      {
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static String
+  httpDate(long date)
+  {
+    return
+      new SimpleDateFormat
+      (
+        "EEE, dd MMM yyyy HH:mm:ss Z",
+        new Locale("en", "US")
+      ).format(new Date(date));
+  }
+
+
+
+  /**
+   * Extract the status code from the status line or <code>-1</code> if that is
+   * not possible.
+   */
+
+  public static int
+  httpStatusCode(String statusLine)
+  {
+    StringTokenizer	tokenizer = new StringTokenizer(statusLine);
+
+    if
+    (
+      tokenizer.countTokens() < 2			||
+      !tokenizer.nextToken().startsWith("HTTP/")
+    )
+    {
+      return -1;
+    }
+
+    try
+    {
+      return Integer.parseInt(tokenizer.nextToken());
+    }
+
+    catch (NumberFormatException e)
+    {
+      return -1;
+    }
+  }
+
+
+
+  public static boolean
+  isAncestor(URL ancestor, URL child)
+  {
+    try
+    {
+      if (isArchive(ancestor) && "jar".equals(child.getProtocol()))
+      {
+        return isAncestor(new URL("jar:" + ancestor.toString() + "!/"), child);
+      }
+
+      if
+      (
+        "file".equals(ancestor.getProtocol())	&&
+        "jar".equals(child.getProtocol())
+      )
+      {
+        return isAncestor(ancestor, Util.extractSubUrl(child));
+      }
+    }
+
+    catch (MalformedURLException e)
+    {
+      throw new RuntimeException(e); // Would be a bug.
+    }
+
+    if
+    (
+      ancestor.getProtocol() == null				||
+      child.getProtocol() == null				||
+      !ancestor.getProtocol().equals(child.getProtocol())	||
+      !ancestor.getHost().equals(child.getHost())		||
+      ancestor.getPort() != child.getPort()
+    )
+    {
+      return false;
+    }
+
+    return
+      isAncestor(ancestor.getFile(), child.getFile()) ||
+        (
+          "file".equals(ancestor.getProtocol()) &&
+            "file".equals(child.getProtocol()) &&
+            isAncestor(urlToFile(ancestor), urlToFile(child))
+        );
+  }
+
+
+
+  private static boolean
+  isAncestor(String path1, String path2)
+  {
+    StringTokenizer	t1 = new StringTokenizer(path1, "/");
+    StringTokenizer	t2 = new StringTokenizer(path2, "/");
+
+    while (t1.hasMoreTokens())
+    {
+      if (!t2.hasMoreTokens() || !t1.nextToken().equals(t2.nextToken()))
+      {
+        return false;
+      }
+    }
+
+    return t2.hasMoreTokens();
+  }
+
+
+
+  public static boolean
+  isAncestor(File file1, File file2)
+  {
+    return
+      file1 != null && file2 != null &&
+        (
+          file1.equals(file2.getParentFile()) ||
+            isAncestor(file1, file2.getParentFile())
+        );
+  }
+
+
+
+  public static boolean
+  isArchive(URL url)
+  {
+    return
+      url.getProtocol() != null && url.getProtocol().equals("file") &&
+        isArchiveExtension(getExtension(url));
+  }
+
+
+
+  private static boolean
+  isArchiveExtension(String s)
+  {
+    for (int i = 0; i < archiveExtensions.length; ++i)
+    {
+      if (archiveExtensions[i].equalsIgnoreCase(s))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+
+  public static boolean
+  isComposedUrl(URL url)
+  {
+    return
+      "jar".equals(url.getProtocol()) && url.toString().lastIndexOf("!/") != -1;
+  }
+
+
+
+  public static boolean
+  isComposedUrl(String url) throws MalformedURLException
+  {
+    return isComposedUrl(new URL(url));
+  }
+
+
+
+  private static boolean
+  isPChar(char c)
+  {
+    return isUnreserved(c) || be.re.util.Array.inArray(PCHAR_SPECIALS, c);
+  }
+
+
+
+  private static boolean
+  isReserved(char c)
+  {
+    return be.re.util.Array.inArray(RESERVED, c);
+  }
+
+
+
+  private static boolean
+  isStartOfEscape(char[] c, int i)
+  {
+    return
+      c[i] == '%' && i < c.length - 2 &&
+        be.re.util.Util.isInteger(new String(c, i + 1, 2), 16);
+  }
+
+
+
+  private static boolean
+  isUnreserved(char c)
+  {
+    return
+      (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+        (c >= '0' && c <= '9') || be.re.util.Array.inArray(MARK, c);
+  }
+
+
+
+  private static boolean
+  isUriChar(char[] c, int i)
+  {
+    return isReserved(c[i]) || isUnreserved(c[i]) || isStartOfEscape(c, i);
+  }
+
+
+
+  public static boolean
+  isUrl(String s)
+  {
+    try
+    {
+      return s != null && new URL(s) != null;
+    }
+
+    catch (MalformedURLException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public static Headers
+  readHeaders(ReadLineInputStream in) throws IOException
+  {
+    Headers	headers = new Headers();
+    byte[]	line;
+
+    while ((line = in. readLine()) != null && line.length > 0)
+    {
+      String	s = new String(line);
+
+      if (s.indexOf(':') != -1)
+      {
+        headers.add
+        (
+          s.substring(0, s.indexOf(':')).trim(),
+          s.substring(s.indexOf(':') + 1).trim()
+        );
+      }
+    }
+
+    return headers;
+  }
+
+
+
+  public static String
+  resolveFullPath(String basePath, String relativePath)
+    throws MalformedURLException
+  {
+    return
+      resolveFullPath
+      (
+        relativePath.startsWith("/") ?
+          relativePath :
+          (basePath.substring(0, basePath.lastIndexOf('/') + 1) + relativePath)
+      );
+  }
+
+
+
+  /**
+   * Resolves ".." segements and multiple slashes.
+   */
+
+  public static String
+  resolveFullPath(String path) throws MalformedURLException
+  {
+    List		segments = new ArrayList();
+    StringTokenizer	tokenizer = new StringTokenizer(path, "/");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String	token = tokenizer.nextToken();
+
+      if (!token.equals("."))
+      {
+        if (token.equals(".."))
+        {
+          if (segments.size() == 0)
+          {
+            throw
+              new MalformedURLException
+              (
+                "URL specification goes outside of its root"
+              );
+          }
+
+          segments.remove(segments.size() - 1);
+        }
+        else
+        {
+          segments.add(token);
+        }
+      }
+    }
+
+    String	result = "";
+
+    for
+    (
+      Iterator i = segments.iterator();
+      i.hasNext();
+      result += "/" + (String) i.next()
+    );
+
+    return result.equals("") ? "/" : (result + (path.endsWith("/") ? "/" : ""));
+  }
+
+
+
+  public static URL
+  resolvePath(URL url) throws MalformedURLException
+  {
+    if (isComposedUrl(url))
+    {
+      try
+      {
+        String	path = resolveFullPath(extractComposedUrlEntry(url));
+
+        return
+          new URL
+          (
+            url.getProtocol() + ":" +
+              resolvePath(extractSubUrl(url)).toString() + "!" + path
+          );
+      }
+
+      catch (MalformedURLException e)
+      {
+        // This considers a jar URL to be an ordinary directory.
+
+        return
+          resolvePath
+          (
+            new URL
+            (
+              extractSubUrl(url).toString() + "/" + extractComposedUrlEntry(url)
+            )
+          );
+      }
+    }
+
+    String	path = resolveFullPath(url.getFile());
+
+    return
+      new URL
+      (
+        new URL
+        (
+          url.getProtocol() + ":" +
+            (url.getAuthority() != null ? "//" + url.getAuthority() : "")
+        ),
+        path + (url.getRef() != null ? ("#" + url.getRef()) : "")
+      );
+  }
+
+
+
+  public static URL
+  setScheme(URL url, String scheme) throws MalformedURLException
+  {
+    try
+    {
+      return
+        escapedUrl
+        (
+          new URI
+          (
+            scheme,
+            unescapeUriSpecials(url.getUserInfo()),
+              // Avoid escaping escapes by URI.
+            unescapeUriSpecials(url.getHost()),
+              // Avoid escaping escapes by URI.
+            url.getPort(),
+            unescapeUriSpecials(url.getPath()),
+              // Avoid escaping escapes by URI.
+            unescapeUriSpecials(url.getQuery()),
+              // Avoid escaping escapes by URI.
+            unescapeUriSpecials(url.getRef())
+              // Avoid escaping escapes by URI.
+          ).toString()
+        );
+    }
+
+    catch (URISyntaxException e)
+    {
+      throw new MalformedURLException(e.getMessage());
+    }
+  }
+
+
+
+  public static URL
+  setUserInfo(URL url, String userInfo) throws MalformedURLException
+  {
+    try
+    {
+      return
+        escapedUrl
+        (
+          new URI
+          (
+            url.getProtocol(),
+            unescapeUriSpecials(userInfo), // Avoid escaping escapes by URI.
+            unescapeUriSpecials(url.getHost()),
+              // Avoid escaping escapes by URI.
+            url.getPort(),
+            unescapeUriSpecials(url.getPath()),
+              // Avoid escaping escapes by URI.
+            unescapeUriSpecials(url.getQuery()),
+              // Avoid escaping escapes by URI.
+            unescapeUriSpecials(url.getRef())
+              // Avoid escaping escapes by URI.
+          ).toString()
+        );
+    }
+
+    catch (URISyntaxException e)
+    {
+      throw new MalformedURLException(e.getMessage());
+    }
+  }
+
+
+
+  public static String
+  stripQuery(String url)
+  {
+    int	query = url.lastIndexOf('?');
+    int	reference = -1;
+
+    return
+      query != -1 ?
+        (
+          url.substring(0, query) +
+            (
+              (reference = url.lastIndexOf('#')) != -1 ?
+                url.substring(reference) : ""
+            )
+        ) : url;
+  }
+
+
+
+  public static URL
+  stripQuery(URL url) throws MalformedURLException
+  {
+    return new URL(stripQuery(url.toString()));
+  }
+
+
+
+  public static URL
+  stripUserInfo(URL url)
+  {
+    try
+    {
+      return
+        new URL
+        (
+          url.getProtocol() + ":" +
+            (url.getHost() != null ? "//" + url.getHost() : "") +
+            (url.getPort() != -1 ?  (":" + String.valueOf(url.getPort())) : "")
+            + url.getFile() + (url.getRef() != null ? ("#" + url.getRef()) : "")
+        );
+    }
+
+    catch (MalformedURLException e)
+    {
+      throw new RuntimeException(e); // Would be a bug.
+    }
+  }
+
+
+
+  public static void
+  testUrl(URL url) throws IOException
+  {
+    try
+    {
+      url.openStream().close();
+    }
+
+    catch (Exception e)
+    {
+      throw
+        new be.re.io.IOException
+        (
+          "The URL \"" + url.toString() + "\" can't be opened.",
+          e
+        );
+    }
+  }
+
+
+
+  public static String
+  unescapeUriSpecials(String s)
+  {
+    if (s == null)
+    {
+      return null;
+    }
+
+    int		count = 0;
+    byte[]	result = new byte[s.length()];
+
+    for (int i = 0; i < s.length(); ++i)
+    {
+      if
+      (
+        s.charAt(i) == '%'						&&
+        s.length() - i > 2						&&
+        be.re.util.Util.isInteger(s.substring(i + 1, i + 3), 16)
+      )
+      {
+        result[count++] =
+          (byte) Integer.parseInt(s.substring(i + 1, i + 3), 16);
+        i += 2;
+      }
+      else
+      {
+        result[count++] = (byte) s.charAt(i);
+      }
+    }
+
+    try
+    {
+      return new String(result, 0, count, "UTF-8");
+    }
+
+    catch (UnsupportedEncodingException e)
+    {
+      throw new RuntimeException(e); // Would be a bug.
+    }
+  }
+
+
+
+  public static File
+  urlToFile(URL url)
+  {
+    return new File(unescapeUriSpecials(url.getFile()));
+  }
+
+
+
+  public static User
+  userFromUrl(URL url)
+  {
+    User	user = new BasicUser();
+    String	userInfo = url.getUserInfo();
+
+    if (userInfo == null)
+    {
+      return user;
+    }
+
+    StringTokenizer	tokenizer = new StringTokenizer(userInfo, ":");
+
+    if (tokenizer.hasMoreTokens())
+    {
+      user.setUsername(tokenizer.nextToken());
+    }
+
+    if (tokenizer.hasMoreTokens())
+    {
+      user.setPassword(unescapeUriSpecials(tokenizer.nextToken()));
+    }
+
+    return user;
+  }
+
+
+
+  private interface TestChar
+
+  {
+
+    /**
+     * Return <code>true</code> if the character should not be escaped and
+     * <code>false</code> otherwise.
+     */
+
+    public boolean	test	(char[] c, int i);
+
+  } // TestChar
+
+} // Util
diff --git a/src/be/re/util/Array.java b/src/be/re/util/Array.java
new file mode 100644
index 0000000..3c4cffc
--- /dev/null
+++ b/src/be/re/util/Array.java
@@ -0,0 +1,941 @@
+package be.re.util;
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class Array
+
+{
+
+  public static Object[]
+  append(Object[] array, Object object)
+  {
+    Object[]	newArray =
+      (Object[]) java.lang.reflect.Array.
+        newInstance(array.getClass().getComponentType(), array.length + 1);
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    newArray[newArray.length - 1] = object;
+
+    return newArray;
+  }
+
+
+
+  public static byte[]
+  append(byte[] array, byte b)
+  {
+    byte[]	newArray = new byte[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    newArray[newArray.length - 1] = b;
+
+    return newArray;
+  }
+
+
+
+  public static char[]
+  append(char[] array, char c)
+  {
+    char[]	newArray = new char[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    newArray[newArray.length - 1] = c;
+
+    return newArray;
+  }
+
+
+
+  public static short[]
+  append(short[] array, short s)
+  {
+    short[]	newArray = new short[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    newArray[newArray.length - 1] = s;
+
+    return newArray;
+  }
+
+
+
+  public static int[]
+  append(int[] array, int i)
+  {
+    int[]	newArray = new int[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    newArray[newArray.length - 1] = i;
+
+    return newArray;
+  }
+
+
+
+  public static long[]
+  append(long[] array, long l)
+  {
+    long[]	newArray = new long[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    newArray[newArray.length - 1] = l;
+
+    return newArray;
+  }
+
+
+
+  public static float[]
+  append(float[] array, float f)
+  {
+    float[]	newArray = new float[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    newArray[newArray.length - 1] = f;
+
+    return newArray;
+  }
+
+
+
+  public static double[]
+  append(double[] array, double d)
+  {
+    double[]	newArray = new double[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    newArray[newArray.length - 1] = d;
+
+    return newArray;
+  }
+
+
+
+  public static Object[]
+  append(Object[] array, Object[] objects)
+  {
+    Object[]	newArray =
+      (Object[]) java.lang.reflect.Array.newInstance
+      (
+        array.getClass().getComponentType(),
+        array.length + objects.length
+      );
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    System.arraycopy(objects, 0, newArray, array.length, objects.length);
+
+    return newArray;
+  }
+
+
+
+  public static byte[]
+  append(byte[] array, byte[] bytes)
+  {
+    byte[]	newArray = new byte[array.length + bytes.length];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    System.arraycopy(bytes, 0, newArray, array.length, bytes.length);
+
+    return newArray;
+  }
+
+
+
+  public static char[]
+  append(char[] array, char[] chars)
+  {
+    char[]	newArray = new char[array.length + chars.length];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    System.arraycopy(chars, 0, newArray, array.length, chars.length);
+
+    return newArray;
+  }
+
+
+
+  public static short[]
+  append(short[] array, short[] shorts)
+  {
+    short[]	newArray = new short[array.length + shorts.length];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    System.arraycopy(shorts, 0, newArray, array.length, shorts.length);
+
+    return newArray;
+  }
+
+
+
+  public static int[]
+  append(int[] array, int[] ints)
+  {
+    int[]	newArray = new int[array.length + ints.length];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    System.arraycopy(ints, 0, newArray, array.length, ints.length);
+
+    return newArray;
+  }
+
+
+
+  public static long[]
+  append(long[] array, long[] longs)
+  {
+    long[]	newArray = new long[array.length + longs.length];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    System.arraycopy(longs, 0, newArray, array.length, longs.length);
+
+    return newArray;
+  }
+
+
+
+  public static float[]
+  append(float[] array, float[] floats)
+  {
+    float[]	newArray = new float[array.length + floats.length];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    System.arraycopy(floats, 0, newArray, array.length, floats.length);
+
+    return newArray;
+  }
+
+
+
+  public static double[]
+  append(double[] array, double[] doubles)
+  {
+    double[]	newArray = new double[array.length + doubles.length];
+
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    System.arraycopy(doubles, 0, newArray, array.length, doubles.length);
+
+    return newArray;
+  }
+
+
+
+  public static boolean
+  equal(Object[] array1, Object[] array2)
+  {
+    if (array1.length != array2.length)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < array1.length; ++i)
+    {
+      if
+      (
+        (
+          array1[i] == null		&&
+          array2[i] != null
+        )				||
+        (
+          array1[i] != null		&&
+          array2[i] == null
+        )				||
+        !array1[i].equals(array2[i])
+      )
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  equal(byte[] array1, byte[] array2)
+  {
+    if (array1.length != array2.length)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < array1.length; ++i)
+    {
+      if (array1[i] != array2[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  equal(char[] array1, char[] array2)
+  {
+    if (array1.length != array2.length)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < array1.length; ++i)
+    {
+      if (array1[i] != array2[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  equal(short[] array1, short[] array2)
+  {
+    if (array1.length != array2.length)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < array1.length; ++i)
+    {
+      if (array1[i] != array2[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  equal(int[] array1, int[] array2)
+  {
+    if (array1.length != array2.length)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < array1.length; ++i)
+    {
+      if (array1[i] != array2[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  equal(long[] array1, long[] array2)
+  {
+    if (array1.length != array2.length)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < array1.length; ++i)
+    {
+      if (array1[i] != array2[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  equal(float[] array1, float[] array2)
+  {
+    if (array1.length != array2.length)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < array1.length; ++i)
+    {
+      if (array1[i] != array2[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  equal(double[] array1, double[] array2)
+  {
+    if (array1.length != array2.length)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < array1.length; ++i)
+    {
+      if (array1[i] != array2[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  inArray(Object[] array, Object object)
+  {
+    return indexOf(array, object) != -1;
+  }
+
+
+
+  public static boolean
+  inArray(byte[] array, byte b)
+  {
+    return indexOf(array, b) != -1;
+  }
+
+
+
+  public static boolean
+  inArray(char[] array, char c)
+  {
+    return indexOf(array, c) != -1;
+  }
+
+
+
+  public static boolean
+  inArray(short[] array, short s)
+  {
+    return indexOf(array, s) != -1;
+  }
+
+
+
+  public static boolean
+  inArray(int[] array, int i)
+  {
+    return indexOf(array, i) != -1;
+  }
+
+
+
+  public static boolean
+  inArray(long[] array, long l)
+  {
+    return indexOf(array, l) != -1;
+  }
+
+
+
+  public static boolean
+  inArray(float[] array, float f)
+  {
+    return indexOf(array, f) != -1;
+  }
+
+
+
+  public static boolean
+  inArray(double[] array, double d)
+  {
+    return indexOf(array, d) != -1;
+  }
+
+
+
+  public static int
+  indexOf(Object[] array, Object object)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if (array[i].equals(object))
+      {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static int
+  indexOf(byte[] array, byte b)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if (array[i] == b)
+      {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static int
+  indexOf(char[] array, char c)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if (array[i] == c)
+      {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static int
+  indexOf(short[] array, short s)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if (array[i] == s)
+      {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static int
+  indexOf(int[] array, int i)
+  {
+    for (int j = 0; j < array.length; ++j)
+    {
+      if (array[j] == i)
+      {
+        return j;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static int
+  indexOf(long[] array, long l)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if (array[i] == l)
+      {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static int
+  indexOf(float[] array, float f)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if (array[i] == f)
+      {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static int
+  indexOf(double[] array, double d)
+  {
+    for (int i = 0; i < array.length; ++i)
+    {
+      if (array[i] == d)
+      {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+
+
+  public static Object[]
+  insert(Object[] array, int pos, Object object)
+  {
+    Object[]	newArray =
+      (Object[]) java.lang.reflect.Array.
+        newInstance(array.getClass().getComponentType(), array.length + 1);
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    newArray[pos] = object;
+    System.arraycopy(array, pos, newArray, pos + 1, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static byte[]
+  insert(byte[] array, int pos, byte b)
+  {
+    byte[]	newArray = new byte[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    newArray[pos] = b;
+    System.arraycopy(array, pos, newArray, pos + 1, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static char[]
+  insert(char[] array, int pos, char c)
+  {
+    char[]	newArray = new char[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    newArray[pos] = c;
+    System.arraycopy(array, pos, newArray, pos + 1, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static short[]
+  insert(short[] array, int pos, short s)
+  {
+    short[]	newArray = new short[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    newArray[pos] = s;
+    System.arraycopy(array, pos, newArray, pos + 1, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static int[]
+  insert(int[] array, int pos, int i)
+  {
+    int[]	newArray = new int[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    newArray[pos] = i;
+    System.arraycopy(array, pos, newArray, pos + 1, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static long[]
+  insert(long[] array, int pos, long l)
+  {
+    long[]	newArray = new long[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    newArray[pos] = l;
+    System.arraycopy(array, pos, newArray, pos + 1, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static float[]
+  insert(float[] array, int pos, float f)
+  {
+    float[]	newArray = new float[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    newArray[pos] = f;
+    System.arraycopy(array, pos, newArray, pos + 1, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static double[]
+  insert(double[] array, int pos, double d)
+  {
+    double[]	newArray = new double[array.length + 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    newArray[pos] = d;
+    System.arraycopy(array, pos, newArray, pos + 1, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static Object[]
+  insert(Object[] array, int pos, Object[] objects)
+  {
+    Object[]	newArray =
+      (Object[]) java.lang.reflect.Array.
+        newInstance
+        (
+          array.getClass().getComponentType(),
+          array.length + objects.length
+        );
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(objects, 0, newArray, pos, objects.length);
+    System.
+      arraycopy(array, pos, newArray, pos + objects.length, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static byte[]
+  insert(byte[] array, int pos, byte[] bytes)
+  {
+    byte[]	newArray = new byte[array.length + bytes.length];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(bytes, 0, newArray, pos, bytes.length);
+    System.
+      arraycopy(array, pos, newArray, pos + bytes.length, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static char[]
+  insert(char[] array, int pos, char[] chars)
+  {
+    char[]	newArray = new char[array.length + chars.length];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(chars, 0, newArray, pos, chars.length);
+    System.
+      arraycopy(array, pos, newArray, pos + chars.length, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static short[]
+  insert(short[] array, int pos, short[] shorts)
+  {
+    short[]	newArray = new short[array.length + shorts.length];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(shorts, 0, newArray, pos, shorts.length);
+    System.
+      arraycopy(array, pos, newArray, pos + shorts.length, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static int[]
+  insert(int[] array, int pos, int[] ints)
+  {
+    int[]	newArray = new int[array.length + ints.length];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(ints, 0, newArray, pos, ints.length);
+    System.
+      arraycopy(array, pos, newArray, pos + ints.length, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static long[]
+  insert(long[] array, int pos, long[] longs)
+  {
+    long[]	newArray = new long[array.length + longs.length];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(longs, 0, newArray, pos, longs.length);
+    System.
+      arraycopy(array, pos, newArray, pos + longs.length, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static float[]
+  insert(float[] array, int pos, float[] floats)
+  {
+    float[]	newArray = new float[array.length + floats.length];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(floats, 0, newArray, pos, floats.length);
+    System.
+      arraycopy(array, pos, newArray, pos + floats.length, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static double[]
+  insert(double[] array, int pos, double[] doubles)
+  {
+    double[]	newArray = new double[array.length + doubles.length];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(doubles, 0, newArray, pos, doubles.length);
+    System.
+      arraycopy(array, pos, newArray, pos + doubles.length, array.length - pos);
+
+    return newArray;
+  }
+
+
+
+  public static Object[]
+  remove(Object[] array, int pos)
+  {
+    Object[]	newArray =
+      (Object[]) java.lang.reflect.Array.
+        newInstance(array.getClass().getComponentType(), array.length - 1);
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(array, pos + 1, newArray, pos, array.length - pos - 1);
+
+    return newArray;
+  }
+
+
+
+  public static byte[]
+  remove(byte[] array, int pos)
+  {
+    byte[]	newArray = new byte[array.length - 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(array, pos + 1, newArray, pos, array.length - pos - 1);
+
+    return newArray;
+  }
+
+
+
+  public static char[]
+  remove(char[] array, int pos)
+  {
+    char[]	newArray = new char[array.length - 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(array, pos + 1, newArray, pos, array.length - pos - 1);
+
+    return newArray;
+  }
+
+
+
+  public static short[]
+  remove(short[] array, int pos)
+  {
+    short[]	newArray = new short[array.length - 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(array, pos + 1, newArray, pos, array.length - pos - 1);
+
+    return newArray;
+  }
+
+
+
+  public static int[]
+  remove(int[] array, int pos)
+  {
+    int[]	newArray = new int[array.length - 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(array, pos + 1, newArray, pos, array.length - pos - 1);
+
+    return newArray;
+  }
+
+
+
+  public static long[]
+  remove(long[] array, int pos)
+  {
+    long[]	newArray = new long[array.length - 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(array, pos + 1, newArray, pos, array.length - pos - 1);
+
+    return newArray;
+  }
+
+
+
+  public static float[]
+  remove(float[] array, int pos)
+  {
+    float[]	newArray = new float[array.length - 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(array, pos + 1, newArray, pos, array.length - pos - 1);
+
+    return newArray;
+  }
+
+
+
+  public static double[]
+  remove(double[] array, int pos)
+  {
+    double[]	newArray = new double[array.length - 1];
+
+    System.arraycopy(array, 0, newArray, 0, pos);
+    System.arraycopy(array, pos + 1, newArray, pos, array.length - pos - 1);
+
+    return newArray;
+  }
+
+} // Array
diff --git a/src/be/re/util/DigitalTree.java b/src/be/re/util/DigitalTree.java
new file mode 100644
index 0000000..5fd07b4
--- /dev/null
+++ b/src/be/re/util/DigitalTree.java
@@ -0,0 +1,207 @@
+package be.re.util;
+
+import java.util.HashSet;
+import java.util.Set;
+
+
+
+public class DigitalTree implements Cloneable
+
+{
+
+  private static final int	BITS = 8;
+  private static final int	MASK = (0x1 << BITS) - 1;
+  private static final int	SIZE = (0x1 << BITS);
+
+  private Set		keys;
+  private Node[]	root = new Node[SIZE];
+
+
+
+  public
+  DigitalTree()
+  {
+    this(false);
+  }
+
+
+
+  public
+  DigitalTree(boolean saveKeys)
+  {
+    keys = saveKeys ? new HashSet() : null;
+  }
+
+
+
+  /**
+   * Returns a shallow copy.
+   */
+
+  public Object
+  clone()
+  {
+    DigitalTree	copy = null;
+
+    try
+    {
+      copy = (DigitalTree) super.clone();
+    }
+
+    catch (CloneNotSupportedException e)
+    {
+      return null;
+    }
+
+    copy.root = cloneNodes(root);
+
+    if (keys != null)
+    {
+      copy.keys = (Set) ((HashSet) keys).clone();
+    }
+
+    return copy;
+  }
+
+
+
+  private static Node[]
+  cloneNodes(Node[] nodes)
+  {
+    Node[]	result = new Node[nodes.length];
+
+    for (int i = 0; i < nodes.length; ++i)
+    {
+      if (nodes[i] != null)
+      {
+        result[i] = (Node) nodes[i].clone();
+      }
+    }
+
+    return result;
+  }
+
+
+
+  public Object
+  get(String key)
+  {
+    Node[]	current = root;
+    int		i = 0;
+    int		length = key.length();
+    Node	node = null;
+
+    for (i = 0; i < length && current != null; ++i)
+    {
+      char	c = key.charAt(i);
+
+      for (int j = 0; j < 16 / BITS && current != null; ++j)
+      {
+        node = current[((MASK << (j * BITS)) & c) >>> (j * BITS)];
+        current = node != null ? node.nodes : null;
+      }
+    }
+
+    return i == length && node != null ? node.object : null;
+  }
+
+
+
+  public Set
+  keySet()
+  {
+    return keys;
+  }
+
+
+
+  public void
+  put(String key, Object o)
+  {
+    Node[]	current = root;
+    int		length = key.length();
+    Node	node = null;
+
+    for (int i = 0; i < length; ++i)
+    {
+      char	c = key.charAt(i);
+
+      for (int j = 0; j < 16 / BITS; ++j)
+      {
+        int	value = ((MASK << (j * BITS)) & c) >>> (j * BITS);
+
+        if (current[value] == null)
+        {
+          current[value] = new Node();
+        }
+
+        if
+        (
+          current[value].nodes == null	&&
+          (
+            i < length - 1		||
+            j < (16 / BITS) - 1
+          )
+        )
+        {
+          current[value].nodes = new Node[SIZE];
+        }
+
+        node = current[value];
+        current = current[value].nodes;
+      }
+    }
+
+    node.object = o;
+
+    if (keys != null)
+    {
+      keys.add(key);
+    }
+  }
+
+
+
+  public void
+  remove(String key)
+  {
+    put(key, null);
+  }
+
+
+
+  private static class Node implements Cloneable
+
+  {
+
+    private Node[]	nodes;
+    private Object	object;
+
+
+
+    protected Object
+    clone()
+    {
+      Node	copy = null;
+
+      try
+      {
+        copy = (Node) super.clone();
+      }
+
+      catch (CloneNotSupportedException e)
+      {
+        return null;
+      }
+
+      if (nodes != null)
+      {
+        copy.nodes = cloneNodes(nodes);
+      }
+
+      return copy;
+    }
+
+  } // Node
+
+} // DigitalTree
diff --git a/src/be/re/util/Equal.java b/src/be/re/util/Equal.java
new file mode 100644
index 0000000..3661395
--- /dev/null
+++ b/src/be/re/util/Equal.java
@@ -0,0 +1,24 @@
+package be.re.util;
+
+/**
+ * The Equal interface is meant for generic algorithms that need to be able to
+ * test two objects for equality. The comparison criterion is kept out of the
+ * algorithm.
+ * It is also kept out of the objects themselves. This would not be the case
+ * if <code> Object.equals </code> were used. That method implies only one
+ * criterion.
+ */
+
+public interface Equal
+{
+  /**
+   * The actual equal method.
+   * @param object the object encountered in the course of the algorithm.
+   * @param refData the reference data the encountered object is compared with.
+   * Normally this data is constant during the algorithm.
+   * @return <code> true </code> if the objects are equal according to the
+   * criterion, <code> false </code> otherwise.
+   */
+
+  public boolean	equal	(Object object, Object refData);
+}
diff --git a/src/be/re/util/UUID.java b/src/be/re/util/UUID.java
new file mode 100644
index 0000000..b5fe933
--- /dev/null
+++ b/src/be/re/util/UUID.java
@@ -0,0 +1,194 @@
+package be.re.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Random;
+
+
+
+/**
+ * Generates a version 1 UUID according to RFC 4122.
+ * @author Werner Donn\u00e9
+ */
+
+public class UUID
+
+{
+
+  private static final Random	random = new SecureRandom();
+  private static final long	startGregorianSinceEpoch =
+    -10010304000000L - // Gregorian in milliseconds since NTP (1/1/1900).
+    2208988800000L; // Epoch in milliseconds since NTP.
+
+  private static long		lastTimestamp = 0;
+  private static long		timestampCounter = 0;
+
+
+
+  static
+  {
+    random.setSeed(System.currentTimeMillis());
+  }
+
+
+
+  public static String
+  format(byte[] uuid)
+  {
+    if (uuid.length != 16)
+    {
+      throw new IllegalArgumentException("UUID should be 16 bytes.");
+    }
+
+    char[]	result = new char[36];
+
+    format(uuid, 0, 4, result, 0); // time_low
+    result[8] = '-';
+    format(uuid, 4, 2, result, 9); // time_mid
+    result[13] = '-';
+    format(uuid, 6, 2, result, 14); // time_hi
+    result[18] = '-';
+    format(uuid, 8, 2, result, 19); // time_hi
+    result[23] = '-';
+    format(uuid, 10, 6, result, 24); // time_hi
+
+    return new String(result);
+  }
+
+
+
+  private static void
+  format(byte[] uuid, int offsetUUID, int len, char[] result, int offsetResult)
+  {
+    for (int i = 0; i < len; ++i)
+    {
+      result[offsetResult + 2 * i] =
+        toHex((0x00ff & uuid[i + offsetUUID]) >>> 4);
+      result[offsetResult + 2 * i + 1] =
+        toHex(0x000f & uuid[i + offsetUUID]);
+    }
+  }
+
+
+
+  public static synchronized byte[]
+  generate()
+  {
+    byte[]	clockSequence = new byte[2];
+    byte[]	node = new byte[6];
+    byte[]	result = new byte[16];
+
+    setTimestamp(result);
+    result[7] &= 0x1f; // version
+    random.nextBytes(clockSequence);
+    clockSequence[0] &= 0xbf; // variant
+    System.arraycopy(clockSequence, 0, result, 8, clockSequence.length);
+    random.nextBytes(node);
+    node[0] |= 0x80; // multicast bit
+    System.arraycopy(node, 0, result, 10, node.length);
+
+    return result;
+  }
+
+
+
+  public static String
+  generateFormatted()
+  {
+    return format(generate());
+  }
+
+
+
+  public static boolean
+  isUUID(String s)
+  {
+    if (s.length() != 36)
+    {
+      return false;
+    }
+
+    for (int i = 0; i < 36; ++i)
+    {
+      if (i == 8 || i == 13 || i == 18 || i == 23)
+      {
+        if (s.charAt(i) != '-')
+        {
+          return false;
+        }
+      }
+      else
+      {
+        char	c = s.charAt(i);
+
+        if
+        (
+          !(
+            (c >= '0' && c <= '9')	||
+            (c >= 'a' && c <= 'f')	||
+            (c >= 'A' && c <= 'F')
+          )
+        )
+        {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static void
+  main(String[] args)
+  {
+    System.out.println(generateFormatted());
+  }
+
+
+
+  private static void
+  setTimestamp(byte[] result)
+  {
+    long	time =
+      (System.currentTimeMillis() + startGregorianSinceEpoch) * 10000;
+
+    if (time != lastTimestamp)
+    {
+      lastTimestamp = time;
+      timestampCounter = 0;
+    }
+
+    time += timestampCounter++;
+
+    ByteArrayOutputStream	out = new ByteArrayOutputStream(8);
+
+    try
+    {
+      new DataOutputStream(out).writeLong(time);
+    }
+
+    catch (IOException e)
+    {
+      throw new RuntimeException(e);
+    }
+
+    byte[]	bytes = out.toByteArray();
+
+    System.arraycopy(bytes, 4, result, 0, 4); // time_low
+    System.arraycopy(bytes, 2, result, 4, 2); // time_mid
+    System.arraycopy(bytes, 0, result, 6, 2); // time_hi
+  }
+
+
+
+  private static char
+  toHex(int i)
+  {
+    return (char) (i + (i < 10 ? 48 : 87));
+  }
+
+} // UUID
diff --git a/src/be/re/util/Util.java b/src/be/re/util/Util.java
new file mode 100644
index 0000000..e6e844b
--- /dev/null
+++ b/src/be/re/util/Util.java
@@ -0,0 +1,1088 @@
+package be.re.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Array;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.regex.Pattern;
+
+
+
+/**
+ * @author Werner Donn\u00e9
+ */
+
+public class Util
+
+{
+
+  private static final String[][]	DATE_FORMATS =
+    {
+      {"yyyy.MM.dd h:mm:ss a", "0"},
+      {"yyyy.MM.dd h:mm a", "0"},
+      {"yyyy.MM.dd HH:mm", "0"},
+      {"yyyy.MM.dd HH:mm:ss", "0"},
+      {"dd.MM.yyyy h:mm a", "0"},
+      {"dd.MM.yyyy h:mm:ss a", "0"},
+      {"dd.MM.yyyy HH:mm", "0"},
+      {"dd.MM.yyyy HH:mm:ss", "0"},
+      {"yyyy/MM/dd h:mm a", "0"},
+      {"yyyy/MM/dd h:mm:ss a", "0"},
+      {"yyyy/MM/dd HH:mm", "0"},
+      {"yyyy/MM/dd HH:mm:ss", "0"},
+      {"dd/MM/yyyy h:mm a", "0"},
+      {"dd/MM/yyyy h:mm:ss a", "0"},
+      {"dd/MM/yyyy HH:mm", "0"},
+      {"dd/MM/yyyy HH:mm:ss", "0"},
+      {"yyyy-MM-dd h:mm a", "0"},
+      {"yyyy-MM-dd h:mm:ss a", "0"},
+      {"yyyy-MM-dd HH:mm", "0"},
+      {"yyyy-MM-dd HH:mm:ss", "0"},
+      {"yyyy-MM-dd'T'h:mm a", "0"},
+      {"yyyy-MM-dd'T'h:mm:ss a", "0"},
+      {"yyyy-MM-dd'T'HH:mm", "0"},
+      {"yyyy-MM-dd'T'HH:mm:ss", "0"},
+      {"yyyy-MM-dd'T'HH:mm:ss.S", "0"},
+      {"yyyy-MM-dd'T'HH:mm:ssZ", "0"},
+      {"yyyy-MM-dd'T'HH:mm:ss.SZ", "0"},
+      {"dd-MM-yyyy h:mm a", "0"},
+      {"dd-MM-yyyy h:mm:ss a", "0"},
+      {"dd-MM-yyyy HH:mm", "0"},
+      {"dd-MM-yyyy HH:mm:ss", "0"},
+      {"h:mm a", "1"},
+      {"h:mm:ss a", "1"},
+      {"HH:mm", "1"},
+      {"HH:mm:ss", "1"},
+      {"yyyy.MM.dd", "2"},
+      {"dd.MM.yyyy", "2"},
+      {"yyyy/MM/dd", "2"},
+      {"dd/MM/yyyy", "2"},
+      {"yyyy-MM-dd", "2"},
+      {"dd-MM-yyyy", "2"}
+    };
+
+  private static Map		bundles = new HashMap();
+  private static Set		countries =
+    new HashSet(Arrays.asList(Locale.getISOCountries()));
+  private static Set		languages =
+    new HashSet(Arrays.asList(Locale.getISOLanguages()));
+  private static Pattern	timeZonePattern =
+    Pattern.compile("\\p{Digit}{2}:?\\p{Digit}{2}");
+  private static Pattern	timestampPattern =
+    Pattern.compile
+    (
+      "\\p{Digit}{4}(-\\p{Digit}{2}(-\\p{Digit}{2}" +
+        "(T\\p{Digit}{2}:\\p{Digit}{2}(:\\p{Digit}{2}(.(\\p{Digit})+)?)?" +
+        "(Z|((\\+|-)\\p{Digit}{2}:?\\p{Digit}{2}))?)?)?)?"
+    );
+
+
+
+  public static void
+  attemptExitVM(int returnCode)
+  {
+    if
+    (
+      Util.class.getClassLoader() == null		||
+        Util.class.getClassLoader().
+          equals(ClassLoader.getSystemClassLoader())
+    )
+    {
+      try
+      {
+        System.exit(returnCode);
+      }
+
+      catch (Throwable e)
+      {
+        // Only attempt.
+      }
+    }
+  }
+
+
+
+  public static String
+  createDate(long time)
+  {
+    return new SimpleDateFormat("yyyy-MM-dd").format(new Date(time));
+  }
+
+
+
+  public static Object[]
+  createSet(Object[] collection)
+  {
+    Set	set = new HashSet();
+
+    for (int i = 0; i < collection.length; ++i)
+    {
+      set.add(collection[i]);
+    }
+
+    return
+      set.toArray
+      (
+        (Object[])
+          Array.newInstance
+          (
+            collection.getClass().getComponentType(),
+            set.size()
+          )
+      );
+  }
+
+
+
+  public static String
+  createTimestamp(long time)
+  {
+    return
+      new SimpleDateFormat
+      (
+        time % 1000 > 0 ?
+          "yyyy-MM-dd'T'HH:mm:ss.SSSZ" : "yyyy-MM-dd'T'HH:mm:ssZ"
+      ).format(new Date(time));
+  }
+
+
+
+  public static String
+  createTimestampUTC(long time)
+  {
+    SimpleDateFormat	format =
+      new SimpleDateFormat
+      (
+        time % 1000 > 0 ?
+          "yyyy-MM-dd'T'HH:mm:ss.SSS" : "yyyy-MM-dd'T'HH:mm:ss"
+      );
+
+    format.setTimeZone(TimeZone.getTimeZone("GMT"));
+
+    return format.format(new Date(time)) + "Z";
+  }
+
+
+
+  public static String
+  enumerate(String[] s, String separator)
+  {
+    StringBuffer	result = new StringBuffer(128);
+
+    for (int i = 0; i < s.length; ++i)
+    {
+      if (i > 0)
+      {
+        result.append(separator);
+      }
+
+      result.append(s[i]);
+    }
+
+    return result.toString();
+  }
+
+
+
+  private static String[]
+  findFormat(String value)
+  {
+    for (int i = 0; i < DATE_FORMATS.length; ++i)
+    {
+      SimpleDateFormat	format = new SimpleDateFormat(DATE_FORMATS[i][0]);
+
+      format.setLenient(false);
+
+      try
+      {
+        if (format.parse(value) != null)
+        {
+          return DATE_FORMATS[i];
+        }
+      }
+
+      catch (ParseException e)
+      {
+      }
+    }
+
+    return null;
+  }
+
+
+
+  public static Throwable
+  getCause(Throwable t, Class theClass)
+  {
+    return
+      t == null ?
+        null :
+        (
+          theClass.isAssignableFrom(t.getClass()) ?
+            t : getCause(t.getCause(), theClass)
+        );
+  }
+
+
+
+  public static String
+  getCommonPrefix(String s1, String s2)
+  {
+    for (int i = 0; i < s1.length() && i < s2.length(); ++i)
+    {
+      if (s1.charAt(i) != s2.charAt(i))
+      {
+        return s1.substring(0, i);
+      }
+    }
+
+    return s1.length() <= s2.length() ? s1 : s2;
+  }
+
+
+
+  public static String
+  getCommonPrefix(String[] s)
+  {
+    if (s.length == 0)
+    {
+      return "";
+    }
+
+    String	result = s[0];
+
+    for (int i = 1; i < s.length; ++i)
+    {
+      result = getCommonPrefix(result, s[i]);
+    }
+
+    return result;
+  }
+
+
+
+  public static String
+  getLanguageTag(Locale locale)
+  {
+    return
+      locale.getLanguage().toLowerCase() +
+        (
+          "".equals(locale.getCountry()) ?
+            "" : ("-" + locale.getCountry().toUpperCase())
+        );
+  }
+
+
+
+  public static Locale
+  getLocale(String languageTag)
+  {
+    return
+      languageTag.indexOf('-') != -1 ?
+        new Locale
+        (
+          languageTag.substring(0, languageTag.indexOf('-')).toLowerCase(),
+          languageTag.substring(languageTag.indexOf('-') + 1).toUpperCase()
+        ) : new Locale(languageTag.toLowerCase());
+  }
+
+
+
+  public static File
+  getPackageStorage(Class c)
+  {
+    File		current = new File(System.getProperty("user.home"));
+    boolean		first = true;
+    StringTokenizer	tokenizer = new StringTokenizer(c.getName(), ".");
+    int			count = tokenizer.countTokens();
+
+    for (int i = 0; i < count - 1; ++i)
+    {
+      current = new File(current, (first ? "." : "") + tokenizer.nextToken());
+      first = false;
+    }
+
+    if (!current.exists())
+    {
+      current.mkdirs();
+    }
+
+    return current;
+  }
+
+
+
+  public static String[]
+  getPathSegments(String path)
+  {
+    return split(path, "/");
+  }
+
+
+
+  public static String
+  getReSystemProperty(String name) throws IOException
+  {
+    InputStream	in =
+      Util.class.getClassLoader().
+        getResourceAsStream("META-INF/services/be/re/" + name);
+
+    if (in != null)
+    {
+      try
+      {
+        String[]      resource = readLineConfig(in);
+
+        if (resource.length == 1)
+        {
+          return resource[0];
+        }
+      }
+
+      finally
+      {
+        in.close();
+      }
+    }
+
+    return null;
+  }
+
+
+
+  public static String
+  getResource(String name)
+  {
+    return getResource(name, Locale.getDefault());
+  }
+
+
+
+  public static String
+  getResource(String name, Locale locale)
+  {
+    return getResourceBundle(locale).getString(name);
+  }
+
+
+
+  public static ResourceBundle
+  getResourceBundle()
+  {
+    return getResourceBundle(Locale.getDefault());
+  }
+
+
+
+  public static ResourceBundle
+  getResourceBundle(Locale locale)
+  {
+    ResourceBundle	bundle = (ResourceBundle) bundles.get(locale);
+
+    if (bundle == null)
+    {
+      bundle = ResourceBundle.getBundle("be.re.util.res.Res", locale);
+      bundles.put(locale, bundle);
+    }
+
+    return bundle;
+  }
+
+
+
+  public static String
+  getStackTrace()
+  {
+    try
+    {
+      throw new Exception("");
+    }
+
+    catch (Exception e)
+    {
+      StringWriter	writer = new StringWriter();
+
+      e.printStackTrace(new PrintWriter(writer));
+
+      return writer.toString();
+    }
+  }
+
+
+
+  public static String
+  getSystemProperty(String propertyName) throws IOException
+  {
+    String	result = System.getProperty(propertyName);
+
+    if (result == null)
+    {
+      InputStream     in =
+        Util.class.getClassLoader().
+          getResourceAsStream("META-INF/services/" + propertyName);
+
+      if (in != null)
+      {
+        String[]      resource = readLineConfig(in);
+
+        if (resource.length == 1)
+        {
+          result = resource[0];
+        }
+      }
+    }
+
+    return result;
+  }
+
+
+
+  public static TimeZone
+  getTimeZone(String s)
+  {
+    return getTimeZone(s, getTimeZoneOffset(s));
+  }
+
+
+
+  private static TimeZone
+  getTimeZone(String s, int offset)
+  {
+    if (offset == -1)
+    {
+      return TimeZone.getDefault();
+    }
+
+    s = s.substring(offset);
+
+    return
+      TimeZone.getTimeZone
+      (
+        "Z".equals(s) ? "GMT" : (s.startsWith("GMT") ? s : ("GMT" + s))
+      );
+  }
+
+
+
+  private static int
+  getTimeZoneOffset(String s)
+  {
+    int	index;
+
+    return
+      s.charAt(s.length() - 1) == 'Z' ?
+        (s.length() - 1) :
+        (
+          (index = s.lastIndexOf("GMT")) != -1 ?
+            index :
+            (
+              (
+                (index = s.lastIndexOf('+')) != -1 ||
+                  (index = s.lastIndexOf('-')) != -1
+              ) && timeZonePattern.matcher(s.substring(index + 1)).matches() ?
+                index : -1
+            )
+        );
+  }
+
+
+
+  public static boolean
+  isDateSet(String dateTime)
+  {
+    return isDateSet(findFormat(dateTime));
+  }
+
+
+
+  private static boolean
+  isDateSet(String[] format)
+  {
+    return format != null && !format[1].equals("1");
+  }
+
+
+
+  public static boolean
+  isDateTime(String s)
+  {
+    return findFormat(s) != null;
+  }
+
+
+
+  public static boolean
+  isDouble(String s)
+  {
+    try
+    {
+      Double.parseDouble(s);
+
+      return true;
+    }
+
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public static boolean
+  isFloat(String s)
+  {
+    try
+    {
+      Float.parseFloat(s);
+
+      return true;
+    }
+
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public static boolean
+  isInteger(String s)
+  {
+    try
+    {
+      Integer.parseInt(s);
+
+      return true;
+    }
+
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public static boolean
+  isInteger(String s, int radix)
+  {
+    try
+    {
+      Integer.parseInt(s, radix);
+
+      return true;
+    }
+
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public static boolean
+  isJCEInstalled()
+  {
+    try
+    {
+      Class.forName("javax.crypto.Cipher").
+        getMethod("getInstance", new Class[] {String.class}).
+          invoke(null, new Object[] {"PBEWithMD5AndDES/CBC/PKCS5Padding"});
+      return true;
+    }
+
+    catch (Throwable e)
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * Supports only the primary and country subtags. (See also RFC 4646.)
+   */
+
+  public static boolean
+  isLanguageTag(String s)
+  {
+    Locale	locale = getLocale(s);
+
+    return
+      languages.contains(locale.getLanguage().toLowerCase()) &&
+        (
+          "".equals(locale.getCountry()) ||
+            countries.contains(locale.getCountry().toUpperCase())
+        );
+  }
+
+
+
+  public static boolean
+  isLong(String s)
+  {
+    try
+    {
+      Long.parseLong(s);
+
+      return true;
+    }
+
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public static boolean
+  isLong(String s, int radix)
+  {
+    try
+    {
+      Long.parseLong(s, radix);
+
+      return true;
+    }
+
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public static boolean
+  isMac()
+  {
+    return System.getProperty("mrj.version") != null;
+  }
+
+
+
+  public static boolean
+  isTimeSet(long time)
+  {
+    Calendar	calendar = Calendar.getInstance();
+
+    calendar.setTimeInMillis(time);
+
+    return
+      calendar.get(Calendar.HOUR_OF_DAY) > 0 ||
+        calendar.get(Calendar.MINUTE) > 0 ||
+        calendar.get(Calendar.SECOND) > 0 ||
+        calendar.get(Calendar.MILLISECOND) > 0;
+  }
+
+
+
+  public static boolean
+  isTimeSet(String dateTime)
+  {
+    return isTimeSet(findFormat(dateTime));
+  }
+
+
+
+  private static boolean
+  isTimeSet(String[] format)
+  {
+    return format != null && !format[1].equals("2");
+  }
+
+
+
+  public static boolean
+  isTimestamp(String s)
+  {
+    try
+    {
+      return parseTimestamp(s) != -1;
+    }
+
+    catch (Exception e)
+    {
+      return false;
+    }
+  }
+
+
+
+  public static boolean
+  isWindows()
+  {
+    return System.getProperty("os.name").toLowerCase().indexOf("windows") != -1;
+  }
+
+
+
+  public static String
+  list(String[] list, String separator)
+  {
+    StringBuilder	builder = new StringBuilder(256);
+
+    for (int i = 0; i < list.length; ++i)
+    {
+      if (i > 0)
+      {
+        builder.append(separator);
+      }
+
+      builder.append(list[i]);
+    }
+
+    return builder.toString();
+  }
+
+
+
+  /**
+   * If the date is not set the current date will be used. If the time is not
+   * set midnight will be used.
+   */
+
+  public static long
+  parseDateTime(String s)
+  {
+    String[]	format = findFormat(s);
+
+    if (format == null)
+    {
+      return -1;
+    }
+
+    long	time = parseTimestamp(s, new String[]{format[0]});
+
+    if (isDateSet(format) && isTimeSet(format))
+    {
+      return time;
+    }
+
+    if (!isDateSet(format))
+    {
+      Calendar	calendar = Calendar.getInstance();
+      Calendar	now = Calendar.getInstance();
+
+      calendar.setTimeInMillis(time);
+      now.setTimeInMillis(System.currentTimeMillis());
+      calendar.set(Calendar.YEAR, now.get(Calendar.YEAR));
+      calendar.set(Calendar.MONTH, now.get(Calendar.MONTH));
+      calendar.set(Calendar.DAY_OF_MONTH, now.get(Calendar.DAY_OF_MONTH));
+      time = calendar.getTime().getTime();
+    }
+
+    if (!isTimeSet(format))
+    {
+      time = setTime(time, 0, 0, 0);
+    }
+
+    return time;
+  }
+
+
+
+  /**
+   * Parse an ISO-8601 date. Returns <code>-1</code> if the date couldn't be
+   * parsed.
+   */
+
+  public static long
+  parseTimestamp(String s)
+  {
+    if (!timestampPattern.matcher(s).matches())
+    {
+      return -1;
+    }
+
+    int		timeZoneOffset = getTimeZoneOffset(s);
+    Calendar	calendar = Calendar.getInstance(getTimeZone(s, timeZoneOffset));
+
+    calendar.setLenient(false);
+    calendar.setTimeInMillis(0);
+    calendar.set(Calendar.YEAR, Integer.parseInt(s.substring(0, 4)));
+
+    if (s.length() > 4)
+    {
+      calendar.set(Calendar.MONTH, Integer.parseInt(s.substring(5, 7)) - 1);
+
+      if (s.length() > 7)
+      {
+        calendar.
+          set(Calendar.DAY_OF_MONTH, Integer.parseInt(s.substring(8, 10)));
+
+        if (s.length() > 10)
+        {
+          calendar.
+            set(Calendar.HOUR_OF_DAY, Integer.parseInt(s.substring(11, 13)));
+          calendar.set(Calendar.MINUTE, Integer.parseInt(s.substring(14, 16)));
+
+          if (s.length() > 16 && s.charAt(16) == ':')
+          {
+            calendar.
+              set(Calendar.SECOND, Integer.parseInt(s.substring(17, 19)));
+
+            if (s.length() > 19 && s.charAt(19) == '.')
+            {
+              calendar.set
+              (
+                Calendar.MILLISECOND,
+                Math.round
+                (
+                  Float.parseFloat
+                  (
+                    "0." +
+                      s.substring
+                      (
+                        20,
+                        timeZoneOffset != -1 ? timeZoneOffset : s.length()
+                      )
+                  ) * 1000
+                )
+              );
+            }
+          }
+        }
+      }
+    }
+
+    return calendar.getTimeInMillis();
+  }
+
+
+
+  public static long
+  parseTimestamp(String s, String[] patterns)
+  {
+    Date	result = null;
+
+    for (int i = 0; i < patterns.length && result == null; ++i)
+    {
+      try
+      {
+        SimpleDateFormat	format = new SimpleDateFormat(patterns[i]);
+
+        format.setLenient(false);
+        result = format.parse(s);
+      }
+
+      catch (ParseException e)
+      {
+      }
+    }
+
+    return result == null ? -1 : result.getTime();
+  }
+
+
+
+  public static String
+  patternToRegexp(String pattern)
+  {
+    return
+      pattern.replaceAll("\\+", "\\+").replaceAll("\\.", "\\.").
+        replaceAll("\\$", "\\$").replaceAll("\\*", ".*");
+  }
+
+
+
+  public static void
+  printStackTrace(Throwable e)
+  {
+    if (System.getProperty("be.re.stack") != null)
+    {
+      e.printStackTrace();
+    }
+  }
+
+
+
+  public static String[]
+  readLineConfig(InputStream in) throws IOException
+  {
+    return readLineConfig(new BufferedReader(new InputStreamReader(in)));
+  }
+
+
+
+  public static String[]
+  readLineConfig(BufferedReader in) throws IOException
+  {
+    String	line = "";
+    List	result = new ArrayList();
+    String	s;
+
+    while ((s = in.readLine()) != null)
+    {
+      if (s.indexOf('#') != -1)
+      {
+        s = s.substring(0, s.indexOf('#'));
+      }
+
+      s = s.trim();
+
+      if (s.length() > 0)
+      {
+        if (s.charAt(s.length() - 1) == '\\')
+        {
+          line += s.substring(0, s.length() - 1);
+        }
+        else
+        {
+          line += s;
+          result.add(line);
+          line = "";
+        }
+      }
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+
+
+  public static long
+  setTime(long date, int hourOfDay, int minute, int second)
+  {
+    Calendar	calendar = Calendar.getInstance();
+
+    calendar.setTimeInMillis(date);
+    calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
+    calendar.set(Calendar.MINUTE, minute);
+    calendar.set(Calendar.SECOND, second);
+    calendar.set(Calendar.MILLISECOND, 0);
+
+    return calendar.getTime().getTime();
+  }
+
+
+
+  public static String[]
+  split(String s, String separator)
+  {
+    List		result = new ArrayList();
+    StringTokenizer	tokenizer = new StringTokenizer(s, separator);
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String	token = tokenizer.nextToken();
+
+      if (!"".equals(token))
+      {
+        result.add(token);
+      }
+    }
+
+    return (String[]) result.toArray(new String[0]);
+  }
+
+
+
+  public static String
+  toRoman(int v)
+  {
+    return
+      v < 1 ?
+        "" :
+        (
+          v < 4 ?
+            ("I" + toRoman(v - 1)) :
+            (
+              v < 5 ?
+                "IV" :
+                 (
+                   v < 9 ?
+                     ("V" + toRoman(v - 5)) :
+                     (
+                       v < 10 ?
+                         "IX" :
+                         (
+                           v < 40 ?
+                             ("X" + toRoman(v - 10)) :
+                             (
+                               v < 50 ?
+                                 ("XL" + toRoman(v - 40)) :
+                                 (
+                                   v < 90 ?
+                                     ("L" + toRoman(v - 50)) :
+                                     (
+                                       v < 100 ?
+                                         ("XC" + toRoman(v - 90)) :
+                                         (
+                                           v < 400 ?
+                                             ("C" + toRoman(v - 100)) :
+                                             (
+                                               v < 500 ?
+                                                 ("CD" + toRoman(v - 400)) :
+                                                 (
+                                                   v < 900 ?
+                                                     (
+                                                       "D" +
+                                                         toRoman(v - 500)
+                                                     ) :
+                                                     (
+                                                       v < 1000 ?
+                                                         (
+                                                           "CM" +
+                                                             toRoman(v - 900)
+                                                         ) :
+                                                         (
+                                                           "M" +
+                                                             toRoman(v - 1000)
+                                                         )
+                                                     )
+                                                 )
+                                             )
+                                         )
+                                     )
+                                 )
+                             )
+                         )
+                     )
+                 )
+            )
+        );
+  }
+
+
+
+  public static int
+  waitFor(Process process)
+  {
+    while (true)
+    {
+      try
+      {
+        return process.waitFor();
+      }
+
+      catch (InterruptedException e)
+      {
+      }
+    }
+  }
+
+} // Util
diff --git a/src/be/re/xml/Accumulator.java b/src/be/re/xml/Accumulator.java
new file mode 100644
index 0000000..09e6dae
--- /dev/null
+++ b/src/be/re/xml/Accumulator.java
@@ -0,0 +1,404 @@
+package be.re.xml;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Stack;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * With this class a SAX stream can be accumulated in a DOM document. An
+ * instance of it can be reused. The client must call
+ * <code>startDocument</code> and <code>endDocument</code>.
+ * @author Werner Donn\u00e9
+ */
+
+public class Accumulator extends XMLFilterImpl
+
+{
+
+  private static final DocumentBuilder	documentBuilder =
+    createDocumentBuilder();
+
+  private Node		currentNode = null;
+  private Document	document = null;
+  private Stack		prefixMappings = new Stack();
+  private Result	result = null;
+
+
+
+  public
+  Accumulator()
+  {
+    this((Document) null, (Result) null);
+  }
+
+
+
+  public
+  Accumulator(XMLReader parent)
+  {
+    this((Document) null, (Result) null, parent);
+  }
+
+
+
+  public
+  Accumulator(Result result)
+  {
+    this((Document) null, result);
+  }
+
+
+
+  public
+  Accumulator(Result result, XMLReader parent)
+  {
+    this((Document) null, result, parent);
+  }
+
+
+
+  public
+  Accumulator(Document document)
+  {
+    this.document = document;
+  }
+
+
+
+  public
+  Accumulator(Document document, XMLReader parent)
+  {
+    super(parent);
+    this.document = document;
+  }
+
+
+
+  public
+  Accumulator(Document document, Result result)
+  {
+    this.document = document;
+    this.result = result;
+  }
+
+
+
+  public
+  Accumulator(Document document, Result result, XMLReader parent)
+  {
+    super(parent);
+    this.document = document;
+    this.result = result;
+  }
+
+
+
+  public void
+  characters(char[] ch, int start, int length) throws SAXException
+  {
+    currentNode.
+      appendChild(document.createTextNode(new String(ch, start, length)));
+  }
+
+
+
+  private static DocumentBuilder
+  createDocumentBuilder()
+  {
+    try
+    {
+      return DocumentBuilderFactory.newInstance().newDocumentBuilder();
+    }
+
+    catch (Exception e)
+    {
+      throw new RuntimeException(e);
+    }
+  }
+
+
+
+  public void
+  endDocument() throws SAXException
+  {
+    prefixMappings.pop();
+    currentNode = null;
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    prefixMappings.pop();
+    currentNode = currentNode.getParentNode();
+
+    if (result != null && currentNode == document)
+    {
+      result.report(this);
+    }
+  }
+
+
+
+  public void
+  endPrefixMapping(String prefix) throws SAXException
+  {
+    ((Map) prefixMappings.peek()).remove(prefix);
+  }
+
+
+
+  public Document
+  getDocument()
+  {
+    return document;
+  }
+
+
+
+  public void
+  ignorableWhitespace(char[] ch, int start, int length) throws SAXException
+  {
+  }
+
+
+
+  /**
+   * This installs an accumulator after <code>filter</code>. You would call it
+   * in <code>startElement</code>, before you let the event go through.
+   */
+
+  public static void
+  postAccumulate(XMLFilter filter, ProcessElement process) throws SAXException
+  {
+    final XMLFilter		f = filter;
+    final ContentHandler	handler =
+      filter.getContentHandler();
+    final ProcessElement	p = process;
+    Accumulator			accumulator =
+      new Accumulator
+      (
+        documentBuilder.newDocument(),
+        new Result()
+        {
+          public void
+          report(Accumulator accumulator) throws SAXException
+          {
+            f.setContentHandler
+            (
+              handler != null ? handler : new XMLFilterImpl()
+            );
+
+            p.process(accumulator.getDocument().getDocumentElement(), f);
+            accumulator.endDocument();
+          }
+        }
+      );
+
+    filter.setContentHandler(accumulator);
+    accumulator.setParent(filter);
+    accumulator.startDocument();
+  }
+
+
+
+  /**
+   * This installs an accumulator before <code>filter</code>. You would call it
+   * in <code>startElement</code>, and initialize it with the incoming event,
+   * which you don't let go through.
+   */
+
+  public static void
+  preAccumulate
+  (
+    String		namespaceURI,
+    String		localName,
+    String		qName,
+    Attributes		atts,
+    XMLFilter		filter,
+    ProcessElement	process
+  ) throws SAXException
+  {
+    if (filter.getParent() == null)
+    {
+      return;
+    }
+
+    final XMLFilter		f = filter;
+    final ContentHandler	handler =
+      filter.getParent().getContentHandler();
+    final ProcessElement	p = process;
+    Accumulator			accumulator =
+      new Accumulator
+      (
+        documentBuilder.newDocument(),
+        new Result()
+        {
+          public void
+          report(Accumulator accumulator) throws SAXException
+          {
+            p.process(accumulator.getDocument().getDocumentElement(), f);
+            accumulator.endDocument();
+            f.getParent().setContentHandler(handler);
+          }
+        }
+      );
+
+    filter.getParent().setContentHandler(accumulator);
+    accumulator.setParent(filter.getParent());
+    accumulator.startDocument();
+    accumulator.startElement(namespaceURI, localName, qName, atts);
+  }
+
+
+
+  public void
+  processingInstruction(String target, String data) throws SAXException
+  {
+    currentNode.appendChild(document.createProcessingInstruction(target, data));
+  }
+
+
+
+  public void
+  setDocumentLocator(Locator locator)
+  {
+  }
+
+
+
+  private static void
+  setAttributes(Element element, Attributes atts)
+  {
+    for (int i = 0; i < atts.getLength(); ++i)
+    {
+      if (atts.getURI(i) == null || "".equals(atts.getURI(i)))
+      {
+        element.setAttribute(atts.getQName(i), atts.getValue(i));
+      }
+      else
+      {
+        element.
+          setAttributeNS(atts.getURI(i), atts.getQName(i), atts.getValue(i));
+      }
+    }
+  }
+
+
+
+  private static void
+  setPrefixMappings(Element element, Map mappings)
+  {
+    for (Iterator i = mappings.keySet().iterator(); i.hasNext();)
+    {
+      String	prefix = (String) i.next();
+
+      element.setAttribute
+      (
+        "".equals(prefix) ? "xmlns" : ("xmlns:" + prefix),
+        (String) mappings.get(prefix)
+      );
+    }
+  }
+
+
+
+  public void
+  skippedEntity(String name) throws SAXException
+  {
+  }
+
+
+
+  public void
+  startDocument() throws SAXException
+  {
+    if (document == null)
+    {
+      try
+      {
+        document = documentBuilder.newDocument();
+      }
+
+      catch (Exception e)
+      {
+        throw new SAXException(e);
+      }
+    }
+    else
+    {
+      if (document.getDocumentElement() != null)
+      {
+        document.removeChild(document.getDocumentElement());
+      }
+    }
+
+    currentNode = document;
+    prefixMappings.push(new HashMap());
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    Element	element =
+      document.
+        createElementNS("".equals(namespaceURI) ? null : namespaceURI, qName);
+
+    setAttributes(element, atts);
+    setPrefixMappings(element, (Map) prefixMappings.peek());
+    prefixMappings.push(new HashMap());
+    currentNode.appendChild(element);
+    currentNode = element;
+  }
+
+
+
+  public void
+  startPrefixMapping(String prefix, String uri) throws SAXException
+  {
+    ((Map) prefixMappings.peek()).put(prefix, uri);
+  }
+
+
+
+  public interface ProcessElement
+
+  {
+
+    public void	process	(Element element, XMLFilter filter) throws SAXException;
+
+  } // ProcessElement
+
+
+
+  public interface Result
+  {
+    public void	report	(Accumulator accumulator) throws SAXException;
+  }
+
+} // Accumulator
diff --git a/src/be/re/xml/CatalogResolver.java b/src/be/re/xml/CatalogResolver.java
new file mode 100644
index 0000000..d3f63b4
--- /dev/null
+++ b/src/be/re/xml/CatalogResolver.java
@@ -0,0 +1,388 @@
+package be.re.xml;
+
+import be.re.io.StreamConnector;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import javax.xml.stream.XMLResolver;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.stream.StreamSource;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+
+/**
+ * This entity resolver uses a catalog as defined by SGML Open Technical
+ * Resolution TR9401:1997. Only PUBLIC and SYSTEM statements are supported at
+ * this time. Relative URLs are resolved using the catalog URL as the base URL.
+ * @author Werner Donn\u00e9
+ */
+
+public class CatalogResolver implements EntityResolver, XMLResolver
+
+{
+
+  // Alphabet.
+
+  final static int		SINGLE_QUOTE = 0;
+  final static int		DOUBLE_QUOTE = 1;
+  final static int		OTHER = 2;
+  final static int		SPACE = 3;
+  final static int		WHITE = 4;
+  final static int		EOF = 5;
+
+  // States.
+
+  final static int		TYP = 0;
+  final static int		SQ1 = 1;
+  final static int		DQ1 = 2;
+  final static int		ID1 = 3;
+  final static int		SQ2 = 4;
+  final static int		DQ2 = 5;
+  final static int		ERR = 6;
+
+  final static int[][][]	FSM =
+    {
+      {{SQ1, 1}, {DQ1, 1}, {TYP, 0}, {TYP, 0}, {TYP, 0}, {TYP, 0}}, // TYP
+      {{ID1, 1}, {SQ1, 0}, {SQ1, 0}, {SQ1, 0}, {ERR, 0}, {ERR, 0}}, // SQ1
+      {{DQ1, 0}, {ID1, 1}, {DQ1, 0}, {DQ1, 0}, {ERR, 0}, {ERR, 0}}, // DQ1
+      {{SQ2, 1}, {DQ2, 1}, {ERR, 0}, {ID1, 0}, {ID1, 0}, {ERR, 0}}, // ID1
+      {{TYP, 1}, {SQ2, 0}, {SQ2, 0}, {SQ2, 0}, {ERR, 0}, {ERR, 0}}, // SQ2
+      {{DQ2, 0}, {TYP, 1}, {DQ2, 0}, {DQ2, 0}, {ERR, 0}, {ERR, 0}}  // DQ2
+    };
+
+  private String	catalogSystemId;
+  private Map		publicIdentifiers = new HashMap();
+  private Map		systemIdentifiers = new HashMap();
+
+
+
+  public
+  CatalogResolver(URL catalogUrl) throws IOException
+  {
+    this(catalogUrl.toString(), null);
+  }
+
+
+
+  public
+  CatalogResolver(String catalogSystemId) throws IOException
+  {
+    this(catalogSystemId, null);
+  }
+
+
+
+  public
+  CatalogResolver(URL catalogUrl, InputStream in) throws IOException
+  {
+    this(catalogUrl.toString(), in);
+  }
+
+
+
+  public
+  CatalogResolver(String catalogSystemId, InputStream in) throws IOException
+  {
+    this.catalogSystemId = catalogSystemId;
+
+    load
+    (
+      in != null ?
+        in :
+        (
+          isUrl(catalogSystemId) ?
+            new URL(catalogSystemId).openStream() :
+            new FileInputStream(catalogSystemId)
+        )
+    );
+  }
+
+
+
+  private static int
+  category(int c)
+  {
+    return
+      c == '\'' ?
+        SINGLE_QUOTE :
+        (
+          c == '\"' ?
+            DOUBLE_QUOTE :
+            (
+              c == ' ' ?
+                SPACE :
+                (
+                  c == '\t' || c == '\n' || c == '\r' ?
+                    WHITE : OTHER
+                )
+            )
+        );
+  }
+
+
+
+  private static void
+  error(int in, int line) throws IOException
+  {
+    if (in == EOF)
+    {
+      throw
+        new IOException
+        (
+          "Line " + String.valueOf(line) + ": premature end of file"
+        );
+    }
+
+    if (in == WHITE)
+    {
+      throw
+        new IOException
+        (
+          "Line " + String.valueOf(line) +
+            ": \\t, \\n and \\r are not allowed in an identifier"
+        );
+    }
+
+    if (in == OTHER)
+    {
+      throw
+        new IOException
+        (
+          "Line " + String.valueOf(line) + ": white space expected"
+        );
+    }
+  }
+
+
+
+  /**
+   * Returns a map from the public identifiers to the resolved URLs.
+   */
+
+  public Map
+  getPublicIdentifierMappings()
+  {
+    return publicIdentifiers;
+  }
+
+
+
+  /**
+   * Returns a map from the public identifiers to the resolved URLs.
+   */
+
+  public Map
+  getSystemIdentifierMappings()
+  {
+    return systemIdentifiers;
+  }
+
+
+
+  private static String
+  getTypeToken(char[] c, int off, int len, int line) throws IOException
+  {
+    StringTokenizer	tokenizer =
+      new StringTokenizer(new String(c, off, len), " \t\n\r");
+
+    if (!tokenizer.hasMoreTokens())
+    {
+      throw
+        new IOException
+        (
+          "Line " + String.valueOf(line) + ": PUBLIC or SYSTEM expected"
+        );
+    }
+
+    String	token = tokenizer.nextToken();
+
+    if (!token.equals("PUBLIC") && !token.equals("SYSTEM"))
+    {
+      throw
+        new IOException
+        (
+          "Line " + String.valueOf(line) + ": PUBLIC or SYSTEM expected"
+        );
+    }
+
+    return token;
+  }
+
+
+
+  private static boolean
+  isUrl(String s)
+  {
+    try
+    {
+      return s != null && new URL(s) != null;
+    }
+
+    catch (MalformedURLException e)
+    {
+      return false;
+    }
+  }
+
+
+
+  private void
+  load(InputStream in) throws IOException
+  {
+    ByteArrayOutputStream	out = new ByteArrayOutputStream();
+
+    StreamConnector.copy(in, out);
+
+    char[]	c = new String(out.toByteArray(), "ASCII").toCharArray();
+    String	from = null;
+    int		line = 1;
+    int		position = 0;
+    int		state = TYP;
+    String	type = null;
+
+    for (int i = 0; i < c.length; ++i)
+    {
+      int[]	next = FSM[state][category(c[i])];
+
+      if (next[0] == ERR)
+      {
+        error(category(c[i]), line);
+      }
+
+      if (next[1] == 1)
+      {
+        Map	map;
+
+        switch (state)
+        {
+          case TYP:
+            type = getTypeToken(c, position, i - position, line);
+            break;
+
+          case SQ1: case DQ1:
+            from = new String(c, position, i - position);
+            break;
+
+          case SQ2: case DQ2:
+            map = type.equals("PUBLIC") ? publicIdentifiers : systemIdentifiers;
+
+            map.put
+            (
+              from,
+              resolveSystemId
+              (
+                catalogSystemId,
+                new String(c, position, i - position)
+              )
+            );
+
+            break;
+        }
+
+        position = i + 1;
+      }
+
+      state = next[0];
+
+      if (c[i] == '\n')
+      {
+        ++line;
+      }
+    }
+
+    if (FSM[state][EOF][0] == ERR)
+    {
+      error(EOF, line);
+    }
+  }
+
+
+
+  public InputSource
+  resolveEntity(String publicId, String systemId)
+    throws IOException, SAXException
+  {
+    InputSource	result =
+      publicId != null && publicIdentifiers.get(publicId) != null ?
+        new InputSource(publicIdentifiers.get(publicId).toString()) :
+        (
+          systemId != null && systemIdentifiers.get(systemId) != null ?
+            new InputSource(systemIdentifiers.get(systemId).toString()) : null
+        );
+
+    if (result != null)
+    {
+      result.setPublicId(publicId);
+    }
+
+    return result;
+  }
+
+
+
+  public Object
+  resolveEntity
+  (
+    String	publicId,
+    String	systemId,
+    String	baseURI,
+    String	namespace
+  ) throws XMLStreamException
+  {
+    try
+    {
+      StreamSource	result =
+        new StreamSource
+        (
+          publicId != null && publicIdentifiers.get(publicId) != null ?
+            publicIdentifiers.get(publicId).toString() :
+            (
+              systemId != null && systemIdentifiers.get(systemId) != null ?
+                systemIdentifiers.get(systemId).toString() :
+                (
+                  baseURI != null && systemId != null ?
+                    resolveSystemId(baseURI, systemId) : null
+                )
+            )
+          );
+
+      result.setPublicId(publicId);
+
+      return result;
+    }
+
+    catch (IOException e)
+    {
+      throw new XMLStreamException(e);
+    }
+  }
+
+
+
+  private static String
+  resolveSystemId(String baseURI, String systemId) throws IOException
+  {
+    return
+      isUrl(baseURI) ?
+        new URL(new URL(baseURI), systemId).toString() :
+        (
+          systemId.charAt(0) == '/' ?
+            systemId :
+            (
+              baseURI.charAt(baseURI.length() - 1) == '/' ?
+                (baseURI + systemId) :
+                (baseURI.substring(0, baseURI.lastIndexOf('/') + 1) + systemId)
+            )
+        );
+  }
+
+} // CatalogResolver
diff --git a/src/be/re/xml/DOMToContentHandler.java b/src/be/re/xml/DOMToContentHandler.java
new file mode 100644
index 0000000..fc97c80
--- /dev/null
+++ b/src/be/re/xml/DOMToContentHandler.java
@@ -0,0 +1,284 @@
+package be.re.xml;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * With this class a DOM document can be fed into a SAX chain.
+ * @author Werner Donn\u00e9
+ */
+
+public class DOMToContentHandler
+
+{
+
+  /**
+   * Extracts the attributes of an element for use in a SAX-environment. It
+   * leaves out namespace declarations, which are emitted as events.
+   */
+
+  public static Attributes
+  createAttributes(Element element)
+  {
+    AttributesImpl	attributes = new AttributesImpl();
+    NamedNodeMap	map = element.getAttributes();
+
+    for (int i = 0; i < map.getLength(); ++i)
+    {
+      Attr	attribute = (Attr) map.item(i);
+
+      if (attribute.getNamespaceURI() != null)
+      {
+        attributes.addAttribute
+        (
+          attribute.getNamespaceURI(),
+          attribute.getLocalName(),
+          attribute.getPrefix() != null ?
+            (attribute.getPrefix() + ":" + attribute.getLocalName()) :
+            attribute.getLocalName(),
+          "CDATA",
+          attribute.getValue()
+        );
+      }
+      else
+      {
+        if
+        (
+          !"xmlns".equals(attribute.getName())		&&
+          !attribute.getName().startsWith("xmlns:")
+        )
+        {
+          attributes.addAttribute
+          (
+            "",
+            attribute.getName(),
+            attribute.getName(),
+            "CDATA",
+            attribute.getValue()
+          );
+        }
+      }
+    }
+
+    return attributes;
+  }
+
+
+
+  /**
+   * Runs a complete DOM-document through a <code>ContentHandler</code>.
+   */
+
+  public static void
+  documentToContentHandler(Document document, ContentHandler handler)
+    throws SAXException
+  {
+    XMLFilterImpl	h = realHandler(handler);
+
+    h.startDocument();
+    elementToContentHandler(document.getDocumentElement(), h);
+    h.endDocument();
+  }
+
+
+
+  /**
+   * Runs a complete DOM-element through a <code>ContentHandler</code>.
+   */
+
+  public static void
+  elementToContentHandler(Element element, ContentHandler handler)
+    throws SAXException
+  {
+    elementToContentHandler(element, realHandler(handler));
+  }
+
+
+
+  private static void
+  elementToContentHandler(Element element, XMLFilterImpl handler)
+    throws SAXException
+  {
+    String[]	prefixes = startPrefixMappings(element, handler);
+
+    startElement(element, handler);
+    siblingsToContentHandler(element.getFirstChild(), handler);
+    endElement(element, handler);
+
+    for (int i = prefixes.length - 1; i >= 0; --i)
+    {
+      handler.endPrefixMapping(prefixes[i]);
+    }
+  }
+
+
+
+  public static void
+  endElement(Element element, ContentHandler handler) throws SAXException
+  {
+    if (element.getNamespaceURI() != null)
+    {
+      handler.endElement
+      (
+        element.getNamespaceURI(),
+        element.getLocalName(),
+        element.getPrefix() != null ?
+          (element.getPrefix() + ":" + element.getLocalName()) :
+          element.getLocalName()
+      );
+    }
+    else
+    {
+      handler.endElement("", element.getTagName(), element.getTagName());
+    }
+  }
+
+
+
+  /**
+   * Puts an <code>XMLFilterImpl</code> in front of <code>handler</code> in
+   * order to provide the possiblity to the latter to insert an
+   * <code>Accumulator</code> dynamically.
+   */
+
+  private static XMLFilterImpl
+  realHandler(ContentHandler handler)
+  {
+    XMLFilterImpl	result = new XMLFilterImpl();
+
+    result.setContentHandler(handler);
+
+    if (handler instanceof XMLFilter)
+    {
+      ((XMLFilter) handler).setParent(result);
+    }
+
+    return result;
+  }
+
+
+
+  /**
+   * Runs a complete sibling list through a <code>ContentHandler</code>
+   * starting with <code>node</code>.
+   */
+
+  public static void
+  siblingsToContentHandler(Node node, ContentHandler handler)
+    throws SAXException
+  {
+    siblingsToContentHandler(node, realHandler(handler));
+  }
+
+
+
+  private static void
+  siblingsToContentHandler(Node node, XMLFilterImpl handler)
+    throws SAXException
+  {
+    if (node == null)
+    {
+      return;
+    }
+
+    switch (node.getNodeType())
+    {
+      case Node.ELEMENT_NODE:
+        elementToContentHandler((Element) node, handler);
+        break;
+
+      case Node.PROCESSING_INSTRUCTION_NODE:
+        handler.processingInstruction
+        (
+          ((ProcessingInstruction) node).getTarget(),
+          ((ProcessingInstruction) node).getData()
+        );
+
+        break;
+
+      case Node.TEXT_NODE:
+        {
+          char[]	chars = ((Text) node).getData().toCharArray();
+
+          handler.characters(chars, 0, chars.length);
+        }
+
+        break;
+    }
+
+    siblingsToContentHandler(node.getNextSibling(), handler);
+  }
+
+
+
+  public static void
+  startElement(Element element, ContentHandler handler) throws SAXException
+  {
+    if (element.getNamespaceURI() != null)
+    {
+      handler.startElement
+      (
+        element.getNamespaceURI(),
+        element.getLocalName(),
+        element.getPrefix() != null ?
+          (element.getPrefix() + ":" + element.getLocalName()) :
+          element.getLocalName(),
+        createAttributes(element)
+      );
+    }
+    else
+    {
+      handler.startElement
+      (
+        "",
+        element.getTagName(),
+        element.getTagName(),
+        createAttributes(element)
+      );
+    }
+  }
+
+
+
+  private static String[]
+  startPrefixMappings(Element element, ContentHandler handler)
+    throws SAXException
+  {
+    NamedNodeMap	map = element.getAttributes();
+    List		result = new ArrayList();
+
+    for (int i = 0; i < map.getLength(); ++i)
+    {
+      Attr	attribute = (Attr) map.item(i);
+      String	name = attribute.getName();
+
+      if (name.startsWith("xmlns"))
+      {
+        String	prefix =
+          name.startsWith("xmlns:") ?
+            name.substring(name.indexOf(':') + 1) : "";
+
+        result.add(prefix);
+        handler.startPrefixMapping(prefix, attribute.getValue());
+      }
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+} // DOMToContentHandler
diff --git a/src/be/re/xml/ExpandedName.java b/src/be/re/xml/ExpandedName.java
new file mode 100644
index 0000000..15b2b67
--- /dev/null
+++ b/src/be/re/xml/ExpandedName.java
@@ -0,0 +1,85 @@
+package be.re.xml;
+
+import javax.xml.namespace.QName;
+import org.w3c.dom.Node;
+
+
+
+/**
+ * A struct to represent the expanded name of an element.
+ * @author Werner Donn\u00e9
+ */
+
+public class ExpandedName
+
+{
+
+  public String	localName;
+
+  /**
+   * The empty string or <code>null</code> indicates absence of a namespace.
+   */
+
+  public String	namespaceURI;
+
+
+
+  public
+  ExpandedName(String namespaceURI, String localName)
+  {
+    this.namespaceURI = namespaceURI == null ? "" : namespaceURI;
+    this.localName = localName;
+  }
+
+
+
+  public
+  ExpandedName(QName name)
+  {
+    this(name.getNamespaceURI(), name.getLocalPart());
+  }
+
+
+
+  public
+  ExpandedName(Node node)
+  {
+    this(node.getNamespaceURI(), node.getLocalName());
+  }
+
+
+
+  public boolean
+  equals(Object o)
+  {
+    return
+      o instanceof ExpandedName &&
+        namespaceURI.equals(((ExpandedName) o).namespaceURI) &&
+        localName.equals(((ExpandedName) o).localName);
+  }
+
+
+
+  public int
+  hashCode()
+  {
+    return toString().hashCode();
+  }
+
+
+
+  public boolean
+  noNamespace()
+  {
+    return namespaceURI == null || namespaceURI.equals("");
+  }
+
+
+
+  public String
+  toString()
+  {
+    return namespaceURI + "#" + localName;
+  }
+
+} // ExpandedName
diff --git a/src/be/re/xml/Util.java b/src/be/re/xml/Util.java
new file mode 100644
index 0000000..6fccf6e
--- /dev/null
+++ b/src/be/re/xml/Util.java
@@ -0,0 +1,1276 @@
+package be.re.xml;
+
+import be.re.xml.sax.ErrorHandler;
+import be.re.util.Equal;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactory;
+import org.w3c.dom.Attr;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.xml.sax.AttributeList;
+import org.xml.sax.DocumentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributeListImpl;
+
+
+
+/**
+ * This class is a collection of untility functions.
+ * @author Werner Donn\u00e9
+ */
+
+public class Util
+
+{
+
+  private static DocumentBuilderFactory	nonValidatingFactory;
+  private static final Equal		elementSelector =
+    new Equal()
+    {
+      public boolean
+      equal(Object object, Object refData)
+      {
+        return object instanceof Element;
+      }
+    };
+  private static final Equal		nameSelector =
+    new Equal()
+    {
+      public boolean
+      equal(Object object, Object refData)
+      {
+        return ((Node) object).getNodeName().equals((String) refData);
+      }
+    };
+  private static final Equal		qnameSelector =
+    new Equal()
+    {
+      public boolean
+      equal(Object object, Object refData)
+      {
+        return
+          (
+            ((ExpandedName) refData).localName == null ||
+              ((ExpandedName) refData).localName.
+                equals(((Node) object).getLocalName())
+          ) &&
+            (
+              ((ExpandedName) refData).namespaceURI.
+                equals(((Node) object).getNamespaceURI()) ||
+                (
+                  "".equals(((ExpandedName) refData).namespaceURI) &&
+                    ((Node) object).getNamespaceURI() == null
+                )
+            );
+      }
+    };
+  private static TransformerFactory	transformerFactory;
+  private static DocumentBuilderFactory	validatingFactory;
+
+
+
+  public static Node
+  clearNode(Node node)
+  {
+    while (node.hasChildNodes())
+    {
+      node.removeChild(node.getFirstChild());
+    }
+
+    return node;
+  }
+
+
+
+  private static void
+  collectChild(Node node, List children)
+  {
+    if (node != null)
+    {
+      children.add(node);
+      collectChild(node.getNextSibling(), children);
+    }
+  }
+
+
+
+  /**
+   * Returns all child nodes of a node as an array.
+   */
+
+  public static Node[]
+  collectChildren(Node node)
+  {
+    List	children = new ArrayList();
+
+    collectChild(node.getFirstChild(), children);
+
+    return (Node[]) children.toArray(new Node[children.size()]);
+  }
+
+
+
+  public static Node
+  copy(Node node, Document owner)
+  {
+    if (node == null)
+    {
+      return null;
+    }
+
+    Node	result = null;
+
+    switch (node.getNodeType())
+    {
+      case Node.ATTRIBUTE_NODE:
+        result = copyAttribute((Attr) node, owner);
+        break;
+
+      case Node.CDATA_SECTION_NODE:
+        result = owner.createCDATASection(((CDATASection) node).getData());
+        break;
+
+      case Node.COMMENT_NODE:
+        result = owner.createComment(((Comment) node).getData());
+        break;
+
+      case Node.DOCUMENT_FRAGMENT_NODE:
+        result = owner.createDocumentFragment();
+        break;
+
+      case Node.ELEMENT_NODE:
+        result = copyElement((Element) node, owner);
+        break;
+
+      case Node.ENTITY_REFERENCE_NODE:
+        result = owner.createEntityReference(node.getNodeName());
+        break;
+
+      case Node.PROCESSING_INSTRUCTION_NODE:
+        result =
+          owner.createProcessingInstruction
+          (
+            ((ProcessingInstruction) node).getTarget(),
+            ((ProcessingInstruction) node).getData()
+          );
+        break;
+
+      case Node.TEXT_NODE:
+        result = owner.createTextNode(((Text) node).getData());
+        break;
+    }
+
+    if (result != null)
+    {
+      for (Node i = node.getFirstChild(); i != null; i = i.getNextSibling())
+      {
+        result.appendChild(copy(i, owner));
+      }
+    }
+
+    return result;
+  }
+
+
+
+  private static Node
+  copyAttribute(Attr attribute, Document owner)
+  {
+    Attr        result =
+      attribute.getNamespaceURI() != null ?
+        owner.
+          createAttributeNS(attribute.getNamespaceURI(), attribute.getName()) :
+        owner.createAttribute(attribute.getName());
+
+    result.setValue(attribute.getValue());
+
+    return result;
+  }
+
+
+
+  private static Node
+  copyElement(Element element, Document owner)
+  {
+    NamedNodeMap	attributes = element.getAttributes();
+    Element		result =
+      element.getNamespaceURI() != null ?
+        owner.createElementNS(element.getNamespaceURI(), element.getTagName()) :
+        owner.createElement(element.getTagName());
+
+    for (int i = 0; i < attributes.getLength(); ++i)
+    {
+      Attr	attribute = (Attr) copy(attributes.item(i), owner);
+
+      if (attribute.getNamespaceURI() != null)
+      {
+        result.setAttributeNodeNS(attribute); // Why does this method exist?
+      }
+      else
+      {
+        result.setAttributeNode(attribute);
+      }
+    }
+
+    return result;
+  }
+
+
+
+  /**
+   * Extracts the attributes of an element for use in a SAX-environment.
+   */
+
+  public static AttributeList
+  createAttributeList(Element element)
+  {
+    AttributeListImpl	attributes = new AttributeListImpl();
+    NamedNodeMap	map = element.getAttributes();
+
+    for (int i = 0; i < map.getLength(); ++i)
+    {
+      Attr	attribute = (Attr) map.item(i);
+
+      attributes.
+        addAttribute(attribute.getName(), "CDATA", attribute.getValue());
+    }
+
+    return attributes;
+  }
+
+
+
+  /**
+   * Runs a complete DOM-element through a <code>DocumentHandler</code>.
+   */
+
+  public static void
+  elementToDocumentHandler(Element element, DocumentHandler handler)
+    throws SAXException
+  {
+    handler.startElement(element.getTagName(), createAttributeList(element));
+    elementToDocumentHandlerChild(element.getFirstChild(), handler);
+    handler.endElement(element.getTagName());
+  }
+
+
+
+  private static void
+  elementToDocumentHandlerChild(Node node, DocumentHandler handler)
+    throws SAXException
+  {
+    if (node == null)
+    {
+      return;
+    }
+
+    switch (node.getNodeType())
+    {
+      case Node.ELEMENT_NODE:
+        elementToDocumentHandler((Element) node, handler);
+        break;
+
+      case Node.PROCESSING_INSTRUCTION_NODE:
+        processingInstructionToDocumentHandler
+        (
+          (ProcessingInstruction) node,
+          handler
+        );
+        break;
+
+      case Node.TEXT_NODE:
+        textToDocumentHandler((Text) node, handler);
+        break;
+    }
+
+    elementToDocumentHandlerChild(node.getNextSibling(), handler);
+  }
+
+
+
+  public static String
+  escapeText(String value)
+  {
+    return escapeText(value, true);
+  }
+
+
+
+  public static String
+  escapeText(String value, boolean minimum)
+  {
+    StringBuffer	buffer = new StringBuffer((int) (value.length() * 1.1));
+
+    for (int i = 0; i < value.length(); ++i)
+    {
+      if (value.charAt(i) == '&')
+      {
+        buffer.append("&");
+      }
+      else
+      {
+        if (value.charAt(i) == '\'' && !minimum)
+        {
+          buffer.append("'");
+        }
+        else
+        {
+          if (value.charAt(i) == '>' && !minimum)
+          {
+            buffer.append(">");
+          }
+          else
+          {
+            if (value.charAt(i) == '<')
+            {
+              buffer.append("<");
+            }
+            else
+            {
+              if (value.charAt(i) == '"' && !minimum)
+              {
+                buffer.append(""");
+              }
+              else
+              {
+                buffer.append(value.charAt(i));
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return buffer.toString();
+  }
+
+
+
+  /**
+   * Searches the ancestor chain of a node for a node called
+   * <code>name</code>. If no such node is found <code>null</code> is returned.
+   */
+
+  public static Node
+  findAncestor(Node node, String name)
+  {
+    return
+      node == null ?
+        null :
+        (
+          node.getNodeName().equalsIgnoreCase(name) ?
+            node : findAncestor(node.getParentNode(), name)
+        );
+  }
+
+
+
+  /**
+   * Does the same as <code>findAncestor</code> except that the search stops
+   * when a node called <code>limitingElement</code> is encountered.
+   * @see #findAncestor
+   */
+
+  public static Node
+  findAncestorNotBeyond(Node node, String name, String limitingElement)
+  {
+    return
+      node == null ?
+        null :
+        (
+          node.getNodeName().equalsIgnoreCase(name) ?
+            node :
+            (
+              node.getNodeName().equalsIgnoreCase(limitingElement) ?
+                null :
+                findAncestorNotBeyond
+                (
+                  node.getParentNode(),
+                  name,
+                  limitingElement
+                )
+            )
+        );
+  }
+
+
+
+  public static DocumentBuilder
+  getDocumentBuilder(URL catalog, boolean validating)
+    throws IOException, ParserConfigurationException
+  {
+    return getDocumentBuilder(newDocumentBuilderFactory(validating), catalog);
+  }
+
+
+
+  public static DocumentBuilder
+  getDocumentBuilder(DocumentBuilderFactory factory, URL catalog)
+    throws IOException, ParserConfigurationException
+  {
+    DocumentBuilder	result = factory.newDocumentBuilder();
+
+    result.setErrorHandler(new ErrorHandler(false));
+
+    if (catalog != null)
+    {
+      result.setEntityResolver(new CatalogResolver(catalog));
+    }
+
+    return result;
+  }
+
+
+
+  public static DocumentBuilderFactory
+  getDocumentBuilderFactory(boolean validating)
+    throws ParserConfigurationException
+  {
+    if (validating && validatingFactory != null)
+    {
+      return validatingFactory;
+    }
+
+    if (!validating && nonValidatingFactory != null)
+    {
+      return nonValidatingFactory;
+    }
+
+    DocumentBuilderFactory	factory = newDocumentBuilderFactory(validating);
+
+    if (validating)
+    {
+      validatingFactory = factory;
+    }
+    else
+    {
+      nonValidatingFactory = factory;
+    }
+
+    return factory;
+  }
+
+
+
+  public static Element
+  getNextSiblingElement(Node node, String namespaceURI, String localName)
+  {
+    if (node == null)
+    {
+      return null;
+    }
+
+    return
+      node instanceof Element &&
+        new ExpandedName(node.getNamespaceURI(), node.getLocalName()).
+          equals(new ExpandedName(namespaceURI, localName)) ?
+        (Element) node :
+        getNextSiblingElement(node.getNextSibling(), namespaceURI, localName);
+  }
+
+
+
+  public static Element
+  getPreviousSiblingElement(Node node, String namespaceURI, String localName)
+  {
+    if (node == null)
+    {
+      return null;
+    }
+
+    return
+      node instanceof Element &&
+        new ExpandedName(node.getNamespaceURI(), node.getLocalName()).
+          equals(new ExpandedName(namespaceURI, localName)) ?
+        (Element) node :
+        getPreviousSiblingElement
+        (
+          node.getPreviousSibling(),
+          namespaceURI,
+          localName
+        );
+  }
+
+
+
+  public static QName
+  getQName(Node node)
+  {
+    return
+      node.getPrefix() != null ?
+        new QName
+        (
+          node.getNamespaceURI(),
+          node.getLocalName(),
+          node.getPrefix()
+        ) :
+        (
+          node.getNamespaceURI() != null ?
+            new QName(node.getNamespaceURI(), node.getLocalName()) :
+            new QName(node.getLocalName())
+        );
+  }
+
+
+
+  /**
+   * Assumes pure #PCDATA, no mixed content.
+   */
+
+  public static String
+  getText(Node node)
+  {
+    if (node == null)
+    {
+      return null;
+    }
+
+    NodeList	list = node.getChildNodes();
+    int		position = 0;
+    int		size = getTextSize(list);
+    char[]	array = new char[size];
+
+    for (int i = 0; i < list.getLength(); ++i)
+    {
+      if (list.item(i) instanceof Text)
+      {
+        char[]	chars = ((Text) list.item(i)).getData().toCharArray();
+
+        System.arraycopy(chars, 0, array, position, chars.length);
+        position += chars.length;
+      }
+    }
+
+    return new String(array);
+  }
+
+
+
+  private static int
+  getTextSize(NodeList list)
+  {
+    int	result = 0;
+
+    for (int i = 0; i < list.getLength(); ++i)
+    {
+      if (list.item(i) instanceof Text)
+      {
+        result += ((Text) list.item(i)).getLength();
+      }
+    }
+
+    return result;
+  }
+
+
+
+  public static TransformerFactory
+  getTransformerFactory() throws TransformerConfigurationException
+  {
+    if (transformerFactory == null)
+    {
+      transformerFactory = newTransformerFactory();
+    }
+
+    return transformerFactory;
+  }
+
+
+
+  private static boolean
+  isWhiteSpace(String s)
+  {
+    for (int i = 0; i < s.length(); ++i)
+    {
+      if (!Character.isWhitespace(s.charAt(i)))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static boolean
+  isXml(String mimeType)
+  {
+    mimeType = stripMimeTypeParameters(mimeType).toLowerCase();
+
+    return
+      "text/xml".equals(mimeType) || "application/xml".equals(mimeType) ||
+        mimeType.endsWith("+xml");
+  }
+
+
+
+  public static boolean
+  isXmlChar(char c)
+  {
+    return
+      c == 0x9 || c == 0xa || c == 0xd || (c >= 0x20 && c <= 0xd7ff) ||
+        (c >= 0xe000 && c <= 0xfffd);
+  }
+
+
+
+  public static boolean
+  isXmlText(String s)
+  {
+    return isXmlText(s.toCharArray());
+  }
+
+
+
+  public static boolean
+  isXmlText(char[] c)
+  {
+    for (int i = 0; i < c.length; ++i)
+    {
+      if (!isXmlChar(c[i]))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  public static DocumentBuilderFactory
+  newDocumentBuilderFactory(boolean validating)
+    throws ParserConfigurationException
+  {
+    try
+    {
+      String			className =
+        be.re.util.Util.
+          getSystemProperty("javax.xml.parsers.DocumentBuilderFactory");
+      DocumentBuilderFactory	factory =
+        className != null ?
+          (DocumentBuilderFactory) Class.forName(className).newInstance() :
+          DocumentBuilderFactory.newInstance();
+
+      factory.setNamespaceAware(true);
+      factory.setValidating(validating);
+
+      return factory;
+    }
+
+    catch (Exception e)
+    {
+      throw new ParserConfigurationException(e.getMessage());
+    }
+  }
+
+
+
+  public static TransformerFactory
+  newTransformerFactory() throws TransformerConfigurationException
+  {
+    try
+    {
+      String	className =
+        be.re.util.Util.
+          getSystemProperty("javax.xml.transform.TransformerFactory");
+
+      return
+        className != null ?
+          (TransformerFactory) Class.forName(className).newInstance() :
+          TransformerFactory.newInstance();
+    }
+
+    catch (Exception e)
+    {
+      throw new TransformerConfigurationException(e);
+    }
+  }
+
+
+
+  /**
+   * Passes a DOM-processing-instruction to a <code>DocumentHandler</code>.
+   */
+
+  public static void
+  processingInstructionToDocumentHandler
+  (
+    ProcessingInstruction	processingInstruction,
+    DocumentHandler		handler
+  ) throws SAXException
+  {
+    handler.processingInstruction
+    (
+      processingInstruction.getTarget(),
+      processingInstruction.getData()
+    );
+  }
+
+
+
+  public static void
+  removeChildren(Node node)
+  {
+    while (node.hasChildNodes())
+    {
+      node.removeChild(node.getLastChild());
+    }
+  }
+
+
+
+  public static void
+  removeIgnorableSpace(Element element)
+  {
+    element.normalize();
+    removeIgnorableSpace(element.getFirstChild());
+  }
+
+
+
+  private static void
+  removeIgnorableSpace(Node node)
+  {
+    if (node == null)
+    {
+      return;
+    }
+
+    removeIgnorableSpace(node.getNextSibling());
+    removeIgnorableSpace(node.getFirstChild());
+
+    if (node instanceof Text && isWhiteSpace(((Text) node).getData()))
+    {
+      node.getParentNode().removeChild(node);
+    }
+  }
+
+
+
+  private static void
+  selectChild(Node node, Equal equal, Object refData, List children)
+  {
+    if (node != null)
+    {
+      if (equal.equal(node, refData))
+      {
+        children.add(node);
+      }
+
+      selectChild(node.getNextSibling(), equal, refData, children);
+    }
+  }
+
+
+
+  private static void
+  selectChildNotBeyond
+  (
+    Node	node,
+    Equal	equal,
+    Object	refData,
+    Equal	equalLimit,
+    Object	refDataLimit,
+    List	children
+  )
+  {
+    if (node != null)
+    {
+      if (equal.equal(node, refData))
+      {
+        children.add(node);
+      }
+
+      if (!equalLimit.equal(node, refDataLimit))
+      {
+        selectChildNotBeyond
+        (
+          node.getNextSibling(),
+          equal,
+          refData,
+          equalLimit,
+          refDataLimit,
+          children
+        );
+      }
+    }
+  }
+
+
+
+  /**
+   * Returns all direct child nodes of <code>node</code> that meet the
+   * condition expressed by <code>equal</code> and <code>refData</code>.
+   * @see be.re.util.Equal
+   */
+
+  public static Node[]
+  selectChildren(Node node, Equal equal, Object refData)
+  {
+    List	children = new ArrayList();
+
+    selectChild(node.getFirstChild(), equal, refData, children);
+
+    return (Node[]) children.toArray(new Node[children.size()]);
+  }
+
+
+
+  /**
+   * Returns all direct child nodes of <code>node</code> called
+   * <code>name</code>.
+   */
+
+  public static Node[]
+  selectChildren(Node node, String name)
+  {
+    return selectChildren(node, nameSelector, name);
+  }
+
+
+
+  /**
+   * Returns all direct child nodes of <code>node</code> with namespace
+   * <code>namespaceURI</code> and local name <code>localName</code>.
+   * <code>localName</code> may be null;
+   */
+
+  public static Node[]
+  selectChildren(Node node, String namespaceURI, String localName)
+  {
+    return
+      selectChildren
+      (
+        node,
+        qnameSelector,
+        new ExpandedName(namespaceURI, localName)
+      );
+  }
+
+
+
+  /**
+   * Returns all direct child nodes of <code>node</code> that meet the
+   * condition expressed by <code>equal</code> and <code>refData</code>. The
+   * search stops if a node meeting the condition expressed by
+   * <code>equalLimit</code> and <code>refDataLimit</code> is encountered.
+   * @see be.re.util.Equal
+   */
+
+  public static Node[]
+  selectChildrenNotBeyond
+  (
+    Node	node,
+    Equal	equal,
+    Object	refData,
+    Equal	equalLimit,
+    Object	refDataLimit
+  )
+  {
+    List	children = new ArrayList();
+
+    selectChildNotBeyond
+    (
+      node.getFirstChild(),
+      equal,
+      refData,
+      equalLimit,
+      refDataLimit,
+      children
+    );
+
+    return (Node[]) children.toArray(new Node[children.size()]);
+  }
+
+
+
+  /**
+   * Returns all direct child nodes of <code>node</code> called
+   * <code>name</code>. The search stops is a node called <code>limit</code> is
+   * encountered.
+   */
+
+  public static Node[]
+  selectChildrenNotBeyond(Node node, String name, String limit)
+  {
+    return
+      selectChildrenNotBeyond(node, nameSelector, name, nameSelector, limit);
+  }
+
+
+
+  /**
+   * Returns an element if there is exactly one that matches and
+   * <code>null</code> otherwise.
+   */
+
+  public static Element
+  selectElement(Node node, ExpandedName[] path)
+  {
+    List	result = new ArrayList();
+
+    selectElements(node, path, 0, result);
+
+    return result.size() == 1 ? (Element) result.get(0) : null;
+  }
+
+
+
+  public static Element[]
+  selectElements(Node node)
+  {
+    List	children = new ArrayList();
+
+    selectChild(node.getFirstChild(), elementSelector, null, children);
+
+    return (Element[]) children.toArray(new Element[children.size()]);
+  }
+
+
+
+  /**
+   * Returns all elements that match the path.
+   */
+
+  public static Element[]
+  selectElements(Node node, ExpandedName[] path)
+  {
+    List	result = new ArrayList();
+
+    selectElements(node, path, 0, result);
+
+    return (Element[]) result.toArray(new Element[0]);
+  }
+
+
+
+  private static void
+  selectElements(Node node, ExpandedName[] path, int position, List result)
+  {
+    if (position == path.length)
+    {
+      return;
+    }
+
+    Node[]	children =
+      selectChildren
+      (
+        node,
+        path[position].namespaceURI,
+        path[position].localName
+      );
+
+    for (int i = 0; i < children.length; ++i)
+    {
+      if (position < path.length - 1)
+      {
+        selectElements(children[i], path, position + 1, result);
+      }
+      else
+      {
+        result.add((Element) children[i]);
+      }
+    }
+  }
+
+
+
+  /**
+   * Returns the first direct child of <code>node</code> that meets the
+   * condition expressed by <code>equal</code> and <code>refData</code>.
+   * @see be.re.util.Equal
+   */
+
+  public static Node
+  selectFirstChild(Node node, Equal equal, Object refData)
+  {
+    return
+     node == null ?
+       null : selectNextSiblingSibling(node.getFirstChild(), equal, refData);
+  }
+
+
+
+  /**
+   * Returns the first direct child of <code>node</code> called
+   * <code>name</code>.
+   */
+
+  public static Node
+  selectFirstChild(Node node, String name)
+  {
+    return selectFirstChild(node, nameSelector, name);
+  }
+
+
+
+  /**
+   * Returns the first direct child of <code>node</code> with namespace
+   * <code>namespaceURI</code> and local name <code>localName</code>.
+   */
+
+  public static Node
+  selectFirstChild(Node node, String namespaceURI, String localName)
+  {
+    return
+      selectFirstChild
+      (
+        node,
+        qnameSelector,
+        new ExpandedName(namespaceURI, localName)
+      );
+  }
+
+
+
+  /**
+   * Returns the first direct child element of <code>node</code>.
+   */
+
+  public static Element
+  selectFirstElement(Node node)
+  {
+    return (Element) selectFirstChild(node, elementSelector, null);
+  }
+
+
+
+  /**
+   * Returns the last direct child of <code>node</code> that meets the
+   * condition expressed by <code>equal</code> and <code>refData</code>.
+   * @see be.re.util.Equal
+   */
+
+  public static Node
+  selectLastChild(Node node, Equal equal, Object refData)
+  {
+    return
+      node == null ?
+        null :
+        selectPreviousSiblingSibling(node.getLastChild(), equal, refData);
+  }
+
+
+
+  /**
+   * Returns the last direct child of <code>node</code> called
+   * <code>name</code>.
+   */
+
+  public static Node
+  selectLastChild(Node node, String name)
+  {
+    return selectLastChild(node, nameSelector, name);
+  }
+
+
+
+  /**
+   * Returns the last direct child of <code>node</code> with namespace
+   * <code>namespaceURI</code> and local name <code>localName</code>.
+   */
+
+  public static Node
+  selectLastChild(Node node, String namespaceURI, String localName)
+  {
+    return
+      selectLastChild
+      (
+        node,
+        qnameSelector,
+        new ExpandedName(namespaceURI, localName)
+      );
+  }
+
+
+
+  /**
+   * Returns the next sibling of <code>node</code> that meets the
+   * condition expressed by <code>equal</code> and <code>refData</code>.
+   * @see be.re.util.Equal
+   */
+
+  public static Node
+  selectNextSibling(Node node, Equal equal, Object refData)
+  {
+    return
+      node == null ?
+        null :
+        selectNextSiblingSibling(node.getNextSibling(), equal, refData);
+  }
+
+
+
+  /**
+   * Returns the next sibling of <code>node</code> called <code>name</code>.
+   */
+
+  public static Node
+  selectNextSibling(Node node, String name)
+  {
+    return selectNextSibling(node, nameSelector, name);
+  }
+
+
+
+  /**
+   * Returns the next sibling of <code>node</code> with namspace
+   * <code>namespaceURI</code> and local name <code>localName</code>.
+   */
+
+  public static Node
+  selectNextSibling(Node node, String namespaceURI, String localName)
+  {
+    return
+      selectNextSibling
+      (
+        node,
+        qnameSelector,
+        new ExpandedName(namespaceURI, localName)
+      );
+  }
+
+
+
+  private static Node
+  selectNextSiblingSibling(Node node, Equal equal, Object refData)
+  {
+    return
+      node == null ?
+        null :
+        (
+          equal.equal(node, refData) ?
+            node :
+            selectNextSiblingSibling(node.getNextSibling(), equal, refData)
+        );
+  }
+
+
+
+  /**
+   * Returns the previous sibling of <code>node</code> that meets the
+   * condition expressed by <code>equal</code> and <code>refData</code>.
+   * @see be.re.util.Equal
+   */
+
+  public static Node
+  selectPreviousSibling(Node node, Equal equal, Object refData)
+  {
+    return
+      node == null ?
+        null :
+        selectPreviousSiblingSibling(node.getPreviousSibling(), equal, refData);
+  }
+
+
+
+  /**
+   * Returns the previous sibling of <code>node</code> called <code>name</code>.
+   */
+
+  public static Node
+  selectPreviousSibling(Node node, String name)
+  {
+    return selectPreviousSibling(node, nameSelector, name);
+  }
+
+
+
+  /**
+   * Returns the previous sibling of <code>node</code> with namespace
+   * <code>namespaceURI</code> and local name <code>localName</code>.
+   */
+
+  public static Node
+  selectPreviousSibling(Node node, String namespaceURI, String localName)
+  {
+    return
+      selectPreviousSibling
+      (
+        node,
+        qnameSelector,
+        new ExpandedName(namespaceURI, localName)
+      );
+  }
+
+
+
+  private static Node
+  selectPreviousSiblingSibling(Node node, Equal equal, Object refData)
+  {
+    return
+      node == null ?
+        null :
+        (
+          equal.equal(node, refData) ?
+            node :
+            selectPreviousSiblingSibling
+            (
+              node.getPreviousSibling(),
+              equal,
+              refData
+            )
+        );
+  }
+
+
+
+  public static void
+  setTransformerParameters(Transformer transformer, Map parameters)
+  {
+    for (Iterator i = parameters.entrySet().iterator(); i.hasNext();)
+    {
+      Map.Entry	entry = (Map.Entry) i.next();
+
+      transformer.
+        setParameter((String) entry.getKey(), (String) entry.getValue());
+    }
+  }
+
+
+
+  private static String
+  stripMimeTypeParameters(String mimeType)
+  {
+    int	index = mimeType.indexOf(';');
+
+    return index != -1 ? mimeType.substring(0, index) : mimeType;
+  }
+
+
+
+  /**
+   * Passes a DOM-text-node to a <code>DocumentHandler</code>.
+   */
+
+  public static void
+  textToDocumentHandler(Text text, DocumentHandler handler) throws SAXException
+  {
+    String	data = text.getData();
+    char[]	chars = new char[data.length()];
+
+    data.getChars(0, chars.length, chars, 0);
+    handler.characters(chars, 0, chars.length);
+  }
+
+} // Util
diff --git a/src/be/re/xml/sax/BalanceChecker.java b/src/be/re/xml/sax/BalanceChecker.java
new file mode 100644
index 0000000..ff37dae
--- /dev/null
+++ b/src/be/re/xml/sax/BalanceChecker.java
@@ -0,0 +1,157 @@
+package be.re.xml.sax;
+
+import be.re.xml.ExpandedName;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Stack;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+public class BalanceChecker extends XMLFilterImpl
+
+{
+
+  private Stack		elements = new Stack();
+  private File		file;
+  private int		indent = 0;
+  private PrintStream	out;
+
+
+
+  public
+  BalanceChecker(File file)
+  {
+    this.file = file;
+  }
+
+
+
+  public
+  BalanceChecker(File file, XMLReader parent)
+  {
+    super(parent);
+    this.file = file;
+  }
+
+
+
+  public void
+  endDocument() throws SAXException
+  {
+    for (int i = elements.size() - 1; i >=0; --i)
+    {
+      write("Element " + elements.get(i).toString() + " is not closed.");
+    }
+
+    out.close();
+    super.endDocument();
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    ExpandedName	element = new ExpandedName(namespaceURI, localName);
+
+    if (elements.isEmpty())
+    {
+      write
+      (
+        "Closing " + element.toString() + " while no open elements are left."
+      );
+    }
+    else
+    {
+      ExpandedName	name = (ExpandedName) elements.pop();
+
+      if (!name.equals(element))
+      {
+        write
+        (
+          "Closing " + element.toString() + " while expecting " +
+            name.toString() + "."
+        );
+      }
+    }
+
+    indent -= 2;
+    write("</" + element.toString() + ">");
+    super.endElement(namespaceURI, localName, qName);
+  }
+
+
+
+  private void
+  openWriter() throws SAXException
+  {
+    if (out == null)
+    {
+      try
+      {
+        out = new PrintStream(new FileOutputStream(file));
+      }
+
+      catch (IOException e)
+      {
+        throw new SAXException(e);
+      }
+    }
+  }
+
+
+
+  public void
+  startDocument() throws SAXException
+  {
+    openWriter();
+    super.startDocument();
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    openWriter();
+
+    ExpandedName	name = new ExpandedName(namespaceURI, localName);
+
+    write("<" + name.toString() + ">");
+    indent += 2;
+    elements.push(name);
+    super.startElement(namespaceURI, localName, qName, atts);
+  }
+
+
+
+  private void
+  write(String s)
+  {
+    if (indent > 0)
+    {
+      char[]	c = new char[indent];
+
+      Arrays.fill(c, ' ');
+      out.print(c);
+    }
+
+    out.println(s);
+    out.flush();
+  }
+
+} // BalanceChecker
diff --git a/src/be/re/xml/sax/ErrorHandler.java b/src/be/re/xml/sax/ErrorHandler.java
new file mode 100644
index 0000000..464137e
--- /dev/null
+++ b/src/be/re/xml/sax/ErrorHandler.java
@@ -0,0 +1,68 @@
+package be.re.xml.sax;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+
+
+public class ErrorHandler implements org.xml.sax.ErrorHandler
+
+{
+
+  private boolean	warnings;
+
+
+
+  public
+  ErrorHandler()
+  {
+    this(false);
+  }
+
+
+
+  public
+  ErrorHandler(boolean warnings)
+  {
+    this.warnings = warnings;
+  }
+
+
+
+  public void
+  error(SAXParseException exception) throws SAXException
+  {
+    System.err.println("Error: " + getMessage(exception));
+  }
+
+
+
+  public void
+  fatalError(SAXParseException exception) throws SAXException
+  {
+    System.err.println("Fatal error: " + getMessage(exception));
+  }
+
+
+
+  private String
+  getMessage(SAXParseException e)
+  {
+    return
+      (e.getPublicId() != null ? (e.getPublicId() + ": ") : "") +
+        (e.getSystemId() != null ? (e.getSystemId() + ": ") : "") + "Line " +
+        String.valueOf(e.getLineNumber()) + ": " + e.getMessage();
+  }
+
+
+
+  public void
+  warning(SAXParseException exception) throws SAXException
+  {
+    if (warnings)
+    {
+      System.err.println("Warning: " + getMessage(exception));
+    }
+  }
+
+} // ErrorHandler
diff --git a/src/be/re/xml/sax/FilterOfFilters.java b/src/be/re/xml/sax/FilterOfFilters.java
new file mode 100644
index 0000000..0845f4e
--- /dev/null
+++ b/src/be/re/xml/sax/FilterOfFilters.java
@@ -0,0 +1,343 @@
+package be.re.xml.sax;
+
+import be.re.io.FlushOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.UndeclaredThrowableException;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * The given array of filters is connected into a filter-chain and encapsulated
+ * in this filter, so you can insert the whole in a chain as one filter. The
+ * wiring of the event chains will be interrupted for those filters that don't
+ * implement the corresponding handler interfaces.
+ * @author Werner Donn\u00e9
+ */
+
+public class FilterOfFilters extends XMLFilterImpl
+
+{
+
+  private XMLFilter	first;
+  private XMLFilter	last;
+
+
+
+  public
+  FilterOfFilters(XMLFilter[] filters)
+  {
+    this(filters, false);
+  }
+
+
+
+  public
+  FilterOfFilters(XMLFilter[] filters, boolean debug)
+  {
+    setupChain(debug ? addDebug(filters) : filters);
+  }
+
+
+
+  public
+  FilterOfFilters(XMLFilter[] filters, XMLReader parent)
+  {
+    this(filters, false, parent);
+  }
+
+
+
+  public
+  FilterOfFilters(XMLFilter[] filters, boolean debug, XMLReader parent)
+  {
+    setupChain(debug ? addDebug(filters) : filters);
+    setParent(parent);
+  }
+
+
+
+  private XMLFilter[]
+  addDebug(XMLFilter[] filters)
+  {
+    XMLFilter[]	result = new XMLFilter[filters.length * 2 + 1];
+
+    result[0] =
+      new Tee
+      (
+        new ContentHandler[]
+        {
+          outputHandler(toString() + "_input.xml")
+        }
+      );
+
+    for (int i = 0; i < filters.length; ++i)
+    {
+      result[i * 2 + 1] = filters[i];
+      result[i * 2 + 2] =
+        new Tee
+        (
+          new ContentHandler[]
+          {
+            new BalanceChecker
+            (
+              new File(toString() + "_" + filters[i].toString() + ".balance")
+            ),
+            outputHandler(toString() + "_" + filters[i].toString())
+          }
+        );
+    }
+
+    return result;
+  }
+
+
+
+  public boolean
+  getFeature(String name)
+    throws SAXNotRecognizedException, SAXNotSupportedException
+  {
+    return first != null ? first.getFeature(name) : super.getFeature(name);
+  }
+
+
+
+  public XMLReader
+  getParent()
+  {
+    return first != null ? first.getParent() : super.getParent();
+  }
+
+
+
+  public Object
+  getProperty(String name)
+    throws SAXNotRecognizedException, SAXNotSupportedException
+  {
+    return first != null ? first.getProperty(name) : super.getProperty(name);
+  }
+
+
+
+  private static ContentHandler
+  outputHandler(String filename)
+  {
+    try
+    {
+      TransformerHandler	handler =
+        Util.newSAXTransformerFactory().newTransformerHandler();
+
+      handler.setResult
+      (
+        new StreamResult(new FlushOutputStream(new FileOutputStream(filename)))
+      );
+
+      return handler;
+    }
+
+    catch (Exception e)
+    {
+      throw new UndeclaredThrowableException(e);
+    }
+  }
+
+
+
+  public void
+  parse(InputSource input) throws IOException, SAXException
+  {
+    if (last != null)
+    {
+      last.parse(input);
+    }
+    else
+    {
+      super.parse(input);
+    }
+  }
+
+
+
+  public void
+  setContentHandler(ContentHandler handler)
+  {
+    if (last != null)
+    {
+      last.setContentHandler(handler);
+    }
+    else
+    {
+      super.setContentHandler(handler);
+    }
+  }
+
+
+
+  public void
+  setDTDHandler(DTDHandler handler)
+  {
+    if (last != null)
+    {
+      last.setDTDHandler(handler);
+    }
+    else
+    {
+      super.setDTDHandler(handler);
+    }
+  }
+
+
+
+  public void
+  setEntityResolver(EntityResolver resolver)
+  {
+    if (last != null)
+    {
+      last.setEntityResolver(resolver);
+    }
+    else
+    {
+      super.setEntityResolver(resolver);
+    }
+  }
+
+
+
+  public void
+  setErrorHandler(ErrorHandler handler)
+  {
+    if (last != null)
+    {
+      last.setErrorHandler(handler);
+    }
+    else
+    {
+      super.setErrorHandler(handler);
+    }
+  }
+
+
+
+  public void
+  setFeature(String name, boolean value)
+    throws SAXNotRecognizedException, SAXNotSupportedException
+  {
+    if (first != null)
+    {
+      first.setFeature(name, value);
+    }
+    else
+    {
+      super.setFeature(name, value);
+    }
+  }
+
+
+
+  public void
+  setParent(XMLReader parent)
+  {
+    if (first != null)
+    {
+      first.setParent(parent);
+    }
+    else
+    {
+      super.setParent(parent);
+    }
+  }
+
+
+
+  public void
+  setProperty(String name, Object value)
+    throws SAXNotRecognizedException, SAXNotSupportedException
+  {
+    if (first != null)
+    {
+      first.setProperty(name, value);
+    }
+    else
+    {
+      super.setProperty(name, value);
+    }
+  }
+
+
+
+  private void
+  setupChain(XMLFilter[] filters)
+  {
+    if (filters.length == 0)
+    {
+      return;
+    }
+
+    for (int i = filters.length - 1; i > 0; --i)
+    {
+      filters[i].setParent(filters[i - 1]);
+
+      // The following connections make it work also when this filter is
+      // inserted in a chain that is already running, i.e. for which parse is
+      // already called.
+
+      if (filters[i] instanceof ContentHandler)
+      {
+        filters[i - 1].setContentHandler((ContentHandler) filters[i]);
+      }
+
+      if (filters[i] instanceof DTDHandler)
+      {
+        filters[i - 1].setDTDHandler((DTDHandler) filters[i]);
+      }
+
+      if (filters[i] instanceof EntityResolver)
+      {
+        filters[i - 1].setEntityResolver((EntityResolver) filters[i]);
+      }
+
+      if (filters[i] instanceof ErrorHandler)
+      {
+        filters[i - 1].setErrorHandler((ErrorHandler) filters[i]);
+      }
+    }
+
+    first = filters[0];
+    last = filters[filters.length - 1];
+
+    if (first instanceof ContentHandler)
+    {
+      super.setContentHandler((ContentHandler) first);
+    }
+
+    if (first instanceof DTDHandler)
+    {
+      super.setDTDHandler((DTDHandler) first);
+    }
+
+    if (first instanceof EntityResolver)
+    {
+      super.setEntityResolver((EntityResolver) first);
+    }
+
+    if (first instanceof ErrorHandler)
+    {
+      super.setErrorHandler((ErrorHandler) first);
+    }
+  }
+
+} // FilterOfFilters
diff --git a/src/be/re/xml/sax/GobbleDocumentEvents.java b/src/be/re/xml/sax/GobbleDocumentEvents.java
new file mode 100644
index 0000000..2fb7ac7
--- /dev/null
+++ b/src/be/re/xml/sax/GobbleDocumentEvents.java
@@ -0,0 +1,46 @@
+package be.re.xml.sax;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Blocks the document events, which is useful for inlining another SAX source
+ * in a SAX result.
+ * @author Werner Donn\u00e9
+ */
+
+public class GobbleDocumentEvents extends XMLFilterImpl
+
+{
+
+  public
+  GobbleDocumentEvents()
+  {
+  }
+
+
+
+  public
+  GobbleDocumentEvents(XMLReader parent)
+  {
+    super(parent);
+  }
+
+
+
+  public void
+  endDocument() throws SAXException
+  {
+  }
+
+
+
+  public void
+  startDocument() throws SAXException
+  {
+  }
+
+} // GobbleDocumentEvents
diff --git a/src/be/re/xml/sax/ProtectEventHandlerFilter.java b/src/be/re/xml/sax/ProtectEventHandlerFilter.java
new file mode 100644
index 0000000..e8660e8
--- /dev/null
+++ b/src/be/re/xml/sax/ProtectEventHandlerFilter.java
@@ -0,0 +1,91 @@
+package be.re.xml.sax;
+
+import java.io.IOException;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * If the parent already has an entity resolver on it, it will not be
+ * replaced by this filter.
+ * @author Werner Donn\u00e9
+ */
+
+public class ProtectEventHandlerFilter extends XMLFilterImpl
+
+{
+
+  private boolean	entityResolver;
+  private boolean	errorHandler;
+
+
+
+  public
+  ProtectEventHandlerFilter(boolean entityResolver, boolean errorHandler)
+  {
+    this.entityResolver = entityResolver;
+    this.errorHandler = errorHandler;
+  }
+
+
+
+  public
+  ProtectEventHandlerFilter
+  (
+    boolean	entityResolver,
+    boolean	errorHandler,
+    XMLReader	parent
+  )
+  {
+    super(parent);
+    this.entityResolver = entityResolver;
+    this.errorHandler = errorHandler;
+  }
+
+
+
+  public void
+  parse(InputSource input) throws IOException, SAXException
+  {
+    if (getParent() != null)
+    {
+      setupParse();
+      getParent().parse(input);
+    }
+  }
+
+
+
+  public void
+  parse(String systemId) throws IOException, SAXException
+  {
+    if (getParent() != null)
+    {
+      setupParse();
+      getParent().parse(systemId);
+    }
+  }
+
+
+
+  private void
+  setupParse()
+  {
+    if (!entityResolver || getParent().getEntityResolver() == null)
+    {
+      getParent().setEntityResolver(this);
+    }
+
+    getParent().setContentHandler(this);
+    getParent().setDTDHandler(this);
+
+    if (!errorHandler || getParent().getErrorHandler() == null)
+    {
+      getParent().setErrorHandler(this);
+    }
+  }
+
+} // ProtectEventHandlerFilter
diff --git a/src/be/re/xml/sax/Tee.java b/src/be/re/xml/sax/Tee.java
new file mode 100644
index 0000000..3e3ec8d
--- /dev/null
+++ b/src/be/re/xml/sax/Tee.java
@@ -0,0 +1,190 @@
+package be.re.xml.sax;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * This XML filter delegates everything to multiple other handlers.
+ * @author Werner Donn\u00e9
+ */
+
+public class Tee extends XMLFilterImpl
+
+{
+
+  private ContentHandler[]	tubes;
+
+
+
+  public
+  Tee(ContentHandler[] tubes)
+  {
+    this.tubes = tubes;
+  }
+
+
+
+  public
+  Tee(ContentHandler[] tubes, XMLReader parent)
+  {
+    super(parent);
+    this.tubes = tubes;
+  }
+
+
+
+  public void
+  characters(char[] ch, int start, int length) throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].characters(ch, start, length);
+    }
+
+    super.characters(ch, start, length);
+  }
+
+
+
+  public void
+  endDocument() throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].endDocument();
+    }
+
+    super.endDocument();
+  }
+
+
+
+  public void
+  endElement(String namespaceURI, String localName, String qName)
+    throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].endElement(namespaceURI, localName, qName);
+    }
+
+    super.endElement(namespaceURI, localName, qName);
+  }
+
+
+
+  public void
+  endPrefixMapping(String prefix) throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].endPrefixMapping(prefix);
+    }
+
+    super.endPrefixMapping(prefix);
+  }
+
+
+
+  public void
+  ignorableWhitespace(char[] ch, int start, int length) throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].ignorableWhitespace(ch, start, length);
+    }
+
+    super.ignorableWhitespace(ch, start,length);
+  }
+
+
+
+  public void
+  processingInstruction(String target, String data) throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].processingInstruction(target, data);
+    }
+
+    super.processingInstruction(target, data);
+  }
+
+
+
+  public void
+  setDocumentLocator(Locator locator)
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].setDocumentLocator(locator);
+    }
+
+    super.setDocumentLocator(locator);
+  }
+
+
+
+  public void
+  skippedEntity(String name) throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].skippedEntity(name);
+    }
+
+    super.skippedEntity(name);
+  }
+
+
+
+  public void
+  startDocument() throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].startDocument();
+    }
+
+    super.startDocument();
+  }
+
+
+
+  public void
+  startElement
+  (
+    String	namespaceURI,
+    String	localName,
+    String	qName,
+    Attributes	atts
+  ) throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].startElement(namespaceURI, localName, qName, atts);
+    }
+
+    super.startElement(namespaceURI, localName, qName, atts);
+  }
+
+
+
+  public void
+  startPrefixMapping(String prefix, String uri) throws SAXException
+  {
+    for (int i = 0; i < tubes.length; ++i)
+    {
+      tubes[i].startPrefixMapping(prefix, uri);
+    }
+
+    super.startPrefixMapping(prefix, uri);
+  }
+
+} // Tee
diff --git a/src/be/re/xml/sax/TransformerHandlerFilter.java b/src/be/re/xml/sax/TransformerHandlerFilter.java
new file mode 100644
index 0000000..1fe0e20
--- /dev/null
+++ b/src/be/re/xml/sax/TransformerHandlerFilter.java
@@ -0,0 +1,49 @@
+package be.re.xml.sax;
+
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.TransformerHandler;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+
+
+/**
+ * Wraps a <code>TransformerHandler</code> in a filter.
+ * @author Werner Donn\u00e9
+ */
+
+public class TransformerHandlerFilter extends XMLFilterImpl
+
+{
+
+  private TransformerHandler	handler;
+
+
+
+  public
+  TransformerHandlerFilter(TransformerHandler handler)
+  {
+    this.handler = handler;
+    super.setContentHandler(handler);
+  }
+
+
+
+  public
+  TransformerHandlerFilter(TransformerHandler handler, XMLReader parent)
+  {
+    super(parent);
+    this.handler = handler;
+    super.setContentHandler(handler);
+  }
+
+
+
+  public void
+  setContentHandler(ContentHandler value)
+  {
+    handler.setResult(new SAXResult(value));
+  }
+
+} // TransformerHandlerFilter
diff --git a/src/be/re/xml/sax/Util.java b/src/be/re/xml/sax/Util.java
new file mode 100644
index 0000000..0969d2e
--- /dev/null
+++ b/src/be/re/xml/sax/Util.java
@@ -0,0 +1,267 @@
+package be.re.xml.sax;
+
+import be.re.xml.CatalogResolver;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.XMLReader;
+
+
+
+public class Util
+
+{
+
+  public static XMLReader
+  getParser(URL catalog, boolean validating) throws SAXException
+  {
+    try
+    {
+      return getParser(newSAXParserFactory(validating), catalog);
+    }
+
+    catch (ParserConfigurationException e)
+    {
+      throw new SAXException(e);
+    }
+  }
+
+
+
+  public static XMLReader
+  getParser(SAXParserFactory factory, URL catalog)
+    throws SAXException
+  {
+    try
+    {
+      XMLReader	parser = factory.newSAXParser().getXMLReader();
+
+      parser.setErrorHandler(new ErrorHandler(false));
+
+      if (catalog != null)
+      {
+        CatalogResolver	resolver = new CatalogResolver(catalog);
+
+        parser.setEntityResolver(resolver);
+        trySchemaLocation(parser, resolver);
+      }
+
+      return parser;
+    }
+
+    catch (SAXException e)
+    {
+      throw e;
+    }
+
+    catch (Exception e)
+    {
+      throw new SAXException(e);
+    }
+  }
+
+
+
+  public static SAXParserFactory
+  newSAXParserFactory(boolean validating) throws ParserConfigurationException
+  {
+    try
+    {
+      String		className =
+        be.re.util.Util.getSystemProperty("javax.xml.parsers.SAXParserFactory");
+      SAXParserFactory	factory =
+        className != null ?
+          (SAXParserFactory) Class.forName(className).newInstance() :
+          SAXParserFactory.newInstance();
+
+      factory.setNamespaceAware(true);
+      factory.setValidating(validating);
+      tryFactoryProperties(factory, validating);
+
+      return factory;
+    }
+
+    catch (Exception e)
+    {
+      throw new ParserConfigurationException(e.getMessage());
+    }
+  }
+
+
+
+  public static SAXTransformerFactory
+  newSAXTransformerFactory() throws TransformerConfigurationException
+  {
+    return (SAXTransformerFactory) be.re.xml.Util.newTransformerFactory();
+  }
+
+
+
+  public static XMLFilter
+  newTemplatesFilter
+  (
+    Templates			templates,
+    Map				parameters,
+    SAXTransformerFactory	factory
+  ) throws TransformerConfigurationException
+  {
+    if
+    (
+      !"net.sf.saxon.PreparedStylesheet".equals(templates.getClass().getName())
+    )
+    {
+      return
+        factory.
+          newXMLFilter(new ParameterizableTemplate(templates, parameters));
+    }
+
+    XMLFilter	result = factory.newXMLFilter(templates);
+
+    try
+    {
+      be.re.xml.Util.setTransformerParameters
+      (
+        (Transformer)
+          result.getClass().getMethod("getTransformer", new Class[0]).
+            invoke(result, new Object[0]),
+        parameters
+      );
+    }
+
+    catch (Exception e)
+    {
+      throw new TransformerConfigurationException(e);
+    }
+
+    return result;
+  }
+
+
+
+  public static TransformerHandler
+  newTemplatesHandler
+  (
+    Templates			templates,
+    Map				parameters,
+    SAXTransformerFactory	factory
+  ) throws TransformerConfigurationException
+  {
+    TransformerHandler	result = factory.newTransformerHandler(templates);
+
+    be.re.xml.Util.
+      setTransformerParameters(result.getTransformer(), parameters);
+
+    return result;
+  }
+
+
+
+  private static void
+  tryFactoryProperties(SAXParserFactory factory, boolean validating)
+  {
+    try
+    {
+      factory.setFeature
+      (
+        "http://apache.org/xml/features/validation/schema",
+        validating
+      );
+
+      factory.setFeature
+      (
+        "http://apache.org/xml/features/validation/schema-full-checking",
+        validating
+      );
+    }
+
+    catch (Exception e)
+    {
+    }
+  }
+
+
+
+  private static void
+  trySchemaLocation(XMLReader parser, CatalogResolver resolver)
+  {
+    try
+    {
+      String		schemaLocation = "";
+
+      for
+      (
+        Iterator i = resolver.getSystemIdentifierMappings().keySet().iterator();
+        i.hasNext();
+      )
+      {
+        String	key = (String) i.next();
+
+        schemaLocation +=
+          key + " " + key + " ";
+          //key + " " + resolver.getSystemIdentifierMappings().get(key) + " ";
+          // The postman always rings twice.
+      }
+
+      parser.setProperty
+      (
+        "http://apache.org/xml/properties/schema/external-schemaLocation",
+        schemaLocation
+      );
+    }
+
+    catch (Exception e)
+    {
+    }
+  }
+
+
+
+  private static class ParameterizableTemplate implements Templates
+
+  {
+
+    private Templates	delegate;
+    private Map		parameters;
+
+
+
+    private
+    ParameterizableTemplate(Templates delegate, Map parameters)
+    {
+      this.delegate = delegate;
+      this.parameters = parameters;
+    }
+
+
+
+    public Properties
+    getOutputProperties()
+    {
+      return delegate.getOutputProperties();
+    }
+
+
+
+    public Transformer
+    newTransformer() throws TransformerConfigurationException
+    {
+      Transformer	transformer = delegate.newTransformer();
+
+      be.re.xml.Util.setTransformerParameters(transformer, parameters);
+
+      return transformer;
+    }
+
+  } // ParameterizableTemplate
+
+} // Util
diff --git a/src/catalog b/src/catalog
new file mode 100644
index 0000000..898232f
--- /dev/null
+++ b/src/catalog
@@ -0,0 +1,9 @@
+PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+       "xhtml1/xhtml1-strict.dtd"
+SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+       "xhtml1/xhtml1-strict.dtd"
+
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+       "xhtml1/xhtml1-transitional.dtd"
+SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
+       "xhtml1/xhtml1-transitional.dtd"
diff --git a/src/xhtml1/xhtml-lat1.ent b/src/xhtml1/xhtml-lat1.ent
new file mode 100644
index 0000000..ffee223
--- /dev/null
+++ b/src/xhtml1/xhtml-lat1.ent
@@ -0,0 +1,196 @@
+<!-- Portions (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+<!-- Character entity set. Typical invocation:
+    <!ENTITY % HTMLlat1 PUBLIC
+       "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+       "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent">
+    %HTMLlat1;
+-->
+
+<!ENTITY nbsp   " "> <!-- no-break space = non-breaking space,
+                                  U+00A0 ISOnum -->
+<!ENTITY iexcl  "¡"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+<!ENTITY cent   "¢"> <!-- cent sign, U+00A2 ISOnum -->
+<!ENTITY pound  "£"> <!-- pound sign, U+00A3 ISOnum -->
+<!ENTITY curren "¤"> <!-- currency sign, U+00A4 ISOnum -->
+<!ENTITY yen    "¥"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+<!ENTITY brvbar "¦"> <!-- broken bar = broken vertical bar,
+                                  U+00A6 ISOnum -->
+<!ENTITY sect   "§"> <!-- section sign, U+00A7 ISOnum -->
+<!ENTITY uml    "¨"> <!-- diaeresis = spacing diaeresis,
+                                  U+00A8 ISOdia -->
+<!ENTITY copy   "©"> <!-- copyright sign, U+00A9 ISOnum -->
+<!ENTITY ordf   "ª"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+<!ENTITY laquo  "«"> <!-- left-pointing double angle quotation mark
+                                  = left pointing guillemet, U+00AB ISOnum -->
+<!ENTITY not    "¬"> <!-- not sign = angled dash,
+                                  U+00AC ISOnum -->
+<!ENTITY shy    "­"> <!-- soft hyphen = discretionary hyphen,
+                                  U+00AD ISOnum -->
+<!ENTITY reg    "®"> <!-- registered sign = registered trade mark sign,
+                                  U+00AE ISOnum -->
+<!ENTITY macr   "¯"> <!-- macron = spacing macron = overline
+                                  = APL overbar, U+00AF ISOdia -->
+<!ENTITY deg    "°"> <!-- degree sign, U+00B0 ISOnum -->
+<!ENTITY plusmn "±"> <!-- plus-minus sign = plus-or-minus sign,
+                                  U+00B1 ISOnum -->
+<!ENTITY sup2   "²"> <!-- superscript two = superscript digit two
+                                  = squared, U+00B2 ISOnum -->
+<!ENTITY sup3   "³"> <!-- superscript three = superscript digit three
+                                  = cubed, U+00B3 ISOnum -->
+<!ENTITY acute  "´"> <!-- acute accent = spacing acute,
+                                  U+00B4 ISOdia -->
+<!ENTITY micro  "µ"> <!-- micro sign, U+00B5 ISOnum -->
+<!ENTITY para   "¶"> <!-- pilcrow sign = paragraph sign,
+                                  U+00B6 ISOnum -->
+<!ENTITY middot "·"> <!-- middle dot = Georgian comma
+                                  = Greek middle dot, U+00B7 ISOnum -->
+<!ENTITY cedil  "¸"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+<!ENTITY sup1   "¹"> <!-- superscript one = superscript digit one,
+                                  U+00B9 ISOnum -->
+<!ENTITY ordm   "º"> <!-- masculine ordinal indicator,
+                                  U+00BA ISOnum -->
+<!ENTITY raquo  "»"> <!-- right-pointing double angle quotation mark
+                                  = right pointing guillemet, U+00BB ISOnum -->
+<!ENTITY frac14 "¼"> <!-- vulgar fraction one quarter
+                                  = fraction one quarter, U+00BC ISOnum -->
+<!ENTITY frac12 "½"> <!-- vulgar fraction one half
+                                  = fraction one half, U+00BD ISOnum -->
+<!ENTITY frac34 "¾"> <!-- vulgar fraction three quarters
+                                  = fraction three quarters, U+00BE ISOnum -->
+<!ENTITY iquest "¿"> <!-- inverted question mark
+                                  = turned question mark, U+00BF ISOnum -->
+<!ENTITY Agrave "À"> <!-- latin capital letter A with grave
+                                  = latin capital letter A grave,
+                                  U+00C0 ISOlat1 -->
+<!ENTITY Aacute "Á"> <!-- latin capital letter A with acute,
+                                  U+00C1 ISOlat1 -->
+<!ENTITY Acirc  "Â"> <!-- latin capital letter A with circumflex,
+                                  U+00C2 ISOlat1 -->
+<!ENTITY Atilde "Ã"> <!-- latin capital letter A with tilde,
+                                  U+00C3 ISOlat1 -->
+<!ENTITY Auml   "Ä"> <!-- latin capital letter A with diaeresis,
+                                  U+00C4 ISOlat1 -->
+<!ENTITY Aring  "Å"> <!-- latin capital letter A with ring above
+                                  = latin capital letter A ring,
+                                  U+00C5 ISOlat1 -->
+<!ENTITY AElig  "Æ"> <!-- latin capital letter AE
+                                  = latin capital ligature AE,
+                                  U+00C6 ISOlat1 -->
+<!ENTITY Ccedil "Ç"> <!-- latin capital letter C with cedilla,
+                                  U+00C7 ISOlat1 -->
+<!ENTITY Egrave "È"> <!-- latin capital letter E with grave,
+                                  U+00C8 ISOlat1 -->
+<!ENTITY Eacute "É"> <!-- latin capital letter E with acute,
+                                  U+00C9 ISOlat1 -->
+<!ENTITY Ecirc  "Ê"> <!-- latin capital letter E with circumflex,
+                                  U+00CA ISOlat1 -->
+<!ENTITY Euml   "Ë"> <!-- latin capital letter E with diaeresis,
+                                  U+00CB ISOlat1 -->
+<!ENTITY Igrave "Ì"> <!-- latin capital letter I with grave,
+                                  U+00CC ISOlat1 -->
+<!ENTITY Iacute "Í"> <!-- latin capital letter I with acute,
+                                  U+00CD ISOlat1 -->
+<!ENTITY Icirc  "Î"> <!-- latin capital letter I with circumflex,
+                                  U+00CE ISOlat1 -->
+<!ENTITY Iuml   "Ï"> <!-- latin capital letter I with diaeresis,
+                                  U+00CF ISOlat1 -->
+<!ENTITY ETH    "Ð"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+<!ENTITY Ntilde "Ñ"> <!-- latin capital letter N with tilde,
+                                  U+00D1 ISOlat1 -->
+<!ENTITY Ograve "Ò"> <!-- latin capital letter O with grave,
+                                  U+00D2 ISOlat1 -->
+<!ENTITY Oacute "Ó"> <!-- latin capital letter O with acute,
+                                  U+00D3 ISOlat1 -->
+<!ENTITY Ocirc  "Ô"> <!-- latin capital letter O with circumflex,
+                                  U+00D4 ISOlat1 -->
+<!ENTITY Otilde "Õ"> <!-- latin capital letter O with tilde,
+                                  U+00D5 ISOlat1 -->
+<!ENTITY Ouml   "Ö"> <!-- latin capital letter O with diaeresis,
+                                  U+00D6 ISOlat1 -->
+<!ENTITY times  "×"> <!-- multiplication sign, U+00D7 ISOnum -->
+<!ENTITY Oslash "Ø"> <!-- latin capital letter O with stroke
+                                  = latin capital letter O slash,
+                                  U+00D8 ISOlat1 -->
+<!ENTITY Ugrave "Ù"> <!-- latin capital letter U with grave,
+                                  U+00D9 ISOlat1 -->
+<!ENTITY Uacute "Ú"> <!-- latin capital letter U with acute,
+                                  U+00DA ISOlat1 -->
+<!ENTITY Ucirc  "Û"> <!-- latin capital letter U with circumflex,
+                                  U+00DB ISOlat1 -->
+<!ENTITY Uuml   "Ü"> <!-- latin capital letter U with diaeresis,
+                                  U+00DC ISOlat1 -->
+<!ENTITY Yacute "Ý"> <!-- latin capital letter Y with acute,
+                                  U+00DD ISOlat1 -->
+<!ENTITY THORN  "Þ"> <!-- latin capital letter THORN,
+                                  U+00DE ISOlat1 -->
+<!ENTITY szlig  "ß"> <!-- latin small letter sharp s = ess-zed,
+                                  U+00DF ISOlat1 -->
+<!ENTITY agrave "à"> <!-- latin small letter a with grave
+                                  = latin small letter a grave,
+                                  U+00E0 ISOlat1 -->
+<!ENTITY aacute "á"> <!-- latin small letter a with acute,
+                                  U+00E1 ISOlat1 -->
+<!ENTITY acirc  "â"> <!-- latin small letter a with circumflex,
+                                  U+00E2 ISOlat1 -->
+<!ENTITY atilde "ã"> <!-- latin small letter a with tilde,
+                                  U+00E3 ISOlat1 -->
+<!ENTITY auml   "ä"> <!-- latin small letter a with diaeresis,
+                                  U+00E4 ISOlat1 -->
+<!ENTITY aring  "å"> <!-- latin small letter a with ring above
+                                  = latin small letter a ring,
+                                  U+00E5 ISOlat1 -->
+<!ENTITY aelig  "æ"> <!-- latin small letter ae
+                                  = latin small ligature ae, U+00E6 ISOlat1 -->
+<!ENTITY ccedil "ç"> <!-- latin small letter c with cedilla,
+                                  U+00E7 ISOlat1 -->
+<!ENTITY egrave "è"> <!-- latin small letter e with grave,
+                                  U+00E8 ISOlat1 -->
+<!ENTITY eacute "é"> <!-- latin small letter e with acute,
+                                  U+00E9 ISOlat1 -->
+<!ENTITY ecirc  "ê"> <!-- latin small letter e with circumflex,
+                                  U+00EA ISOlat1 -->
+<!ENTITY euml   "ë"> <!-- latin small letter e with diaeresis,
+                                  U+00EB ISOlat1 -->
+<!ENTITY igrave "ì"> <!-- latin small letter i with grave,
+                                  U+00EC ISOlat1 -->
+<!ENTITY iacute "í"> <!-- latin small letter i with acute,
+                                  U+00ED ISOlat1 -->
+<!ENTITY icirc  "î"> <!-- latin small letter i with circumflex,
+                                  U+00EE ISOlat1 -->
+<!ENTITY iuml   "ï"> <!-- latin small letter i with diaeresis,
+                                  U+00EF ISOlat1 -->
+<!ENTITY eth    "ð"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+<!ENTITY ntilde "ñ"> <!-- latin small letter n with tilde,
+                                  U+00F1 ISOlat1 -->
+<!ENTITY ograve "ò"> <!-- latin small letter o with grave,
+                                  U+00F2 ISOlat1 -->
+<!ENTITY oacute "ó"> <!-- latin small letter o with acute,
+                                  U+00F3 ISOlat1 -->
+<!ENTITY ocirc  "ô"> <!-- latin small letter o with circumflex,
+                                  U+00F4 ISOlat1 -->
+<!ENTITY otilde "õ"> <!-- latin small letter o with tilde,
+                                  U+00F5 ISOlat1 -->
+<!ENTITY ouml   "ö"> <!-- latin small letter o with diaeresis,
+                                  U+00F6 ISOlat1 -->
+<!ENTITY divide "÷"> <!-- division sign, U+00F7 ISOnum -->
+<!ENTITY oslash "ø"> <!-- latin small letter o with stroke,
+                                  = latin small letter o slash,
+                                  U+00F8 ISOlat1 -->
+<!ENTITY ugrave "ù"> <!-- latin small letter u with grave,
+                                  U+00F9 ISOlat1 -->
+<!ENTITY uacute "ú"> <!-- latin small letter u with acute,
+                                  U+00FA ISOlat1 -->
+<!ENTITY ucirc  "û"> <!-- latin small letter u with circumflex,
+                                  U+00FB ISOlat1 -->
+<!ENTITY uuml   "ü"> <!-- latin small letter u with diaeresis,
+                                  U+00FC ISOlat1 -->
+<!ENTITY yacute "ý"> <!-- latin small letter y with acute,
+                                  U+00FD ISOlat1 -->
+<!ENTITY thorn  "þ"> <!-- latin small letter thorn,
+                                  U+00FE ISOlat1 -->
+<!ENTITY yuml   "ÿ"> <!-- latin small letter y with diaeresis,
+                                  U+00FF ISOlat1 -->
diff --git a/src/xhtml1/xhtml-special.ent b/src/xhtml1/xhtml-special.ent
new file mode 100644
index 0000000..ca358b2
--- /dev/null
+++ b/src/xhtml1/xhtml-special.ent
@@ -0,0 +1,80 @@
+<!-- Special characters for XHTML -->
+
+<!-- Character entity set. Typical invocation:
+     <!ENTITY % HTMLspecial PUBLIC
+        "-//W3C//ENTITIES Special for XHTML//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent">
+     %HTMLspecial;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+     New names (i.e., not in ISO 8879 list) do not clash with any
+     existing ISO 8879 entity names. ISO 10646 character numbers
+     are given for each character, in hex. values are decimal
+     conversions of the ISO 10646 values and refer to the document
+     character set. Names are Unicode names. 
+-->
+
+<!-- C0 Controls and Basic Latin -->
+<!ENTITY quot    """> <!--  quotation mark, U+0022 ISOnum -->
+<!ENTITY amp     "&#38;"> <!--  ampersand, U+0026 ISOnum -->
+<!ENTITY lt      "&#60;"> <!--  less-than sign, U+003C ISOnum -->
+<!ENTITY gt      ">"> <!--  greater-than sign, U+003E ISOnum -->
+<!ENTITY apos	 "'"> <!--  apostrophe = APL quote, U+0027 ISOnum -->
+
+<!-- Latin Extended-A -->
+<!ENTITY OElig   "Œ"> <!--  latin capital ligature OE,
+                                    U+0152 ISOlat2 -->
+<!ENTITY oelig   "œ"> <!--  latin small ligature oe, U+0153 ISOlat2 -->
+<!-- ligature is a misnomer, this is a separate character in some languages -->
+<!ENTITY Scaron  "Š"> <!--  latin capital letter S with caron,
+                                    U+0160 ISOlat2 -->
+<!ENTITY scaron  "š"> <!--  latin small letter s with caron,
+                                    U+0161 ISOlat2 -->
+<!ENTITY Yuml    "Ÿ"> <!--  latin capital letter Y with diaeresis,
+                                    U+0178 ISOlat2 -->
+
+<!-- Spacing Modifier Letters -->
+<!ENTITY circ    "ˆ"> <!--  modifier letter circumflex accent,
+                                    U+02C6 ISOpub -->
+<!ENTITY tilde   "˜"> <!--  small tilde, U+02DC ISOdia -->
+
+<!-- General Punctuation -->
+<!ENTITY ensp    " "> <!-- en space, U+2002 ISOpub -->
+<!ENTITY emsp    " "> <!-- em space, U+2003 ISOpub -->
+<!ENTITY thinsp  " "> <!-- thin space, U+2009 ISOpub -->
+<!ENTITY zwnj    "‌"> <!-- zero width non-joiner,
+                                    U+200C NEW RFC 2070 -->
+<!ENTITY zwj     "‍"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+<!ENTITY lrm     "‎"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+<!ENTITY rlm     "‏"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+<!ENTITY ndash   "–"> <!-- en dash, U+2013 ISOpub -->
+<!ENTITY mdash   "—"> <!-- em dash, U+2014 ISOpub -->
+<!ENTITY lsquo   "‘"> <!-- left single quotation mark,
+                                    U+2018 ISOnum -->
+<!ENTITY rsquo   "’"> <!-- right single quotation mark,
+                                    U+2019 ISOnum -->
+<!ENTITY sbquo   "‚"> <!-- single low-9 quotation mark, U+201A NEW -->
+<!ENTITY ldquo   "“"> <!-- left double quotation mark,
+                                    U+201C ISOnum -->
+<!ENTITY rdquo   "”"> <!-- right double quotation mark,
+                                    U+201D ISOnum -->
+<!ENTITY bdquo   "„"> <!-- double low-9 quotation mark, U+201E NEW -->
+<!ENTITY dagger  "†"> <!-- dagger, U+2020 ISOpub -->
+<!ENTITY Dagger  "‡"> <!-- double dagger, U+2021 ISOpub -->
+<!ENTITY permil  "‰"> <!-- per mille sign, U+2030 ISOtech -->
+<!ENTITY lsaquo  "‹"> <!-- single left-pointing angle quotation mark,
+                                    U+2039 ISO proposed -->
+<!-- lsaquo is proposed but not yet ISO standardized -->
+<!ENTITY rsaquo  "›"> <!-- single right-pointing angle quotation mark,
+                                    U+203A ISO proposed -->
+<!-- rsaquo is proposed but not yet ISO standardized -->
+
+<!-- Currency Symbols -->
+<!ENTITY euro   "€"> <!--  euro sign, U+20AC NEW -->
diff --git a/src/xhtml1/xhtml-symbol.ent b/src/xhtml1/xhtml-symbol.ent
new file mode 100644
index 0000000..63c2abf
--- /dev/null
+++ b/src/xhtml1/xhtml-symbol.ent
@@ -0,0 +1,237 @@
+<!-- Mathematical, Greek and Symbolic characters for XHTML -->
+
+<!-- Character entity set. Typical invocation:
+     <!ENTITY % HTMLsymbol PUBLIC
+        "-//W3C//ENTITIES Symbols for XHTML//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent">
+     %HTMLsymbol;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+     New names (i.e., not in ISO 8879 list) do not clash with any
+     existing ISO 8879 entity names. ISO 10646 character numbers
+     are given for each character, in hex. values are decimal
+     conversions of the ISO 10646 values and refer to the document
+     character set. Names are Unicode names. 
+-->
+
+<!-- Latin Extended-B -->
+<!ENTITY fnof     "ƒ"> <!-- latin small letter f with hook = function
+                                    = florin, U+0192 ISOtech -->
+
+<!-- Greek -->
+<!ENTITY Alpha    "Α"> <!-- greek capital letter alpha, U+0391 -->
+<!ENTITY Beta     "Β"> <!-- greek capital letter beta, U+0392 -->
+<!ENTITY Gamma    "Γ"> <!-- greek capital letter gamma,
+                                    U+0393 ISOgrk3 -->
+<!ENTITY Delta    "Δ"> <!-- greek capital letter delta,
+                                    U+0394 ISOgrk3 -->
+<!ENTITY Epsilon  "Ε"> <!-- greek capital letter epsilon, U+0395 -->
+<!ENTITY Zeta     "Ζ"> <!-- greek capital letter zeta, U+0396 -->
+<!ENTITY Eta      "Η"> <!-- greek capital letter eta, U+0397 -->
+<!ENTITY Theta    "Θ"> <!-- greek capital letter theta,
+                                    U+0398 ISOgrk3 -->
+<!ENTITY Iota     "Ι"> <!-- greek capital letter iota, U+0399 -->
+<!ENTITY Kappa    "Κ"> <!-- greek capital letter kappa, U+039A -->
+<!ENTITY Lambda   "Λ"> <!-- greek capital letter lamda,
+                                    U+039B ISOgrk3 -->
+<!ENTITY Mu       "Μ"> <!-- greek capital letter mu, U+039C -->
+<!ENTITY Nu       "Ν"> <!-- greek capital letter nu, U+039D -->
+<!ENTITY Xi       "Ξ"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+<!ENTITY Omicron  "Ο"> <!-- greek capital letter omicron, U+039F -->
+<!ENTITY Pi       "Π"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+<!ENTITY Rho      "Ρ"> <!-- greek capital letter rho, U+03A1 -->
+<!-- there is no Sigmaf, and no U+03A2 character either -->
+<!ENTITY Sigma    "Σ"> <!-- greek capital letter sigma,
+                                    U+03A3 ISOgrk3 -->
+<!ENTITY Tau      "Τ"> <!-- greek capital letter tau, U+03A4 -->
+<!ENTITY Upsilon  "Υ"> <!-- greek capital letter upsilon,
+                                    U+03A5 ISOgrk3 -->
+<!ENTITY Phi      "Φ"> <!-- greek capital letter phi,
+                                    U+03A6 ISOgrk3 -->
+<!ENTITY Chi      "Χ"> <!-- greek capital letter chi, U+03A7 -->
+<!ENTITY Psi      "Ψ"> <!-- greek capital letter psi,
+                                    U+03A8 ISOgrk3 -->
+<!ENTITY Omega    "Ω"> <!-- greek capital letter omega,
+                                    U+03A9 ISOgrk3 -->
+
+<!ENTITY alpha    "α"> <!-- greek small letter alpha,
+                                    U+03B1 ISOgrk3 -->
+<!ENTITY beta     "β"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+<!ENTITY gamma    "γ"> <!-- greek small letter gamma,
+                                    U+03B3 ISOgrk3 -->
+<!ENTITY delta    "δ"> <!-- greek small letter delta,
+                                    U+03B4 ISOgrk3 -->
+<!ENTITY epsilon  "ε"> <!-- greek small letter epsilon,
+                                    U+03B5 ISOgrk3 -->
+<!ENTITY zeta     "ζ"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+<!ENTITY eta      "η"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+<!ENTITY theta    "θ"> <!-- greek small letter theta,
+                                    U+03B8 ISOgrk3 -->
+<!ENTITY iota     "ι"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+<!ENTITY kappa    "κ"> <!-- greek small letter kappa,
+                                    U+03BA ISOgrk3 -->
+<!ENTITY lambda   "λ"> <!-- greek small letter lamda,
+                                    U+03BB ISOgrk3 -->
+<!ENTITY mu       "μ"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+<!ENTITY nu       "ν"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+<!ENTITY xi       "ξ"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+<!ENTITY omicron  "ο"> <!-- greek small letter omicron, U+03BF NEW -->
+<!ENTITY pi       "π"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+<!ENTITY rho      "ρ"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+<!ENTITY sigmaf   "ς"> <!-- greek small letter final sigma,
+                                    U+03C2 ISOgrk3 -->
+<!ENTITY sigma    "σ"> <!-- greek small letter sigma,
+                                    U+03C3 ISOgrk3 -->
+<!ENTITY tau      "τ"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+<!ENTITY upsilon  "υ"> <!-- greek small letter upsilon,
+                                    U+03C5 ISOgrk3 -->
+<!ENTITY phi      "φ"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+<!ENTITY chi      "χ"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+<!ENTITY psi      "ψ"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+<!ENTITY omega    "ω"> <!-- greek small letter omega,
+                                    U+03C9 ISOgrk3 -->
+<!ENTITY thetasym "ϑ"> <!-- greek theta symbol,
+                                    U+03D1 NEW -->
+<!ENTITY upsih    "ϒ"> <!-- greek upsilon with hook symbol,
+                                    U+03D2 NEW -->
+<!ENTITY piv      "ϖ"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+<!-- General Punctuation -->
+<!ENTITY bull     "•"> <!-- bullet = black small circle,
+                                     U+2022 ISOpub  -->
+<!-- bullet is NOT the same as bullet operator, U+2219 -->
+<!ENTITY hellip   "…"> <!-- horizontal ellipsis = three dot leader,
+                                     U+2026 ISOpub  -->
+<!ENTITY prime    "′"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+<!ENTITY Prime    "″"> <!-- double prime = seconds = inches,
+                                     U+2033 ISOtech -->
+<!ENTITY oline    "‾"> <!-- overline = spacing overscore,
+                                     U+203E NEW -->
+<!ENTITY frasl    "⁄"> <!-- fraction slash, U+2044 NEW -->
+
+<!-- Letterlike Symbols -->
+<!ENTITY weierp   "℘"> <!-- script capital P = power set
+                                     = Weierstrass p, U+2118 ISOamso -->
+<!ENTITY image    "ℑ"> <!-- black-letter capital I = imaginary part,
+                                     U+2111 ISOamso -->
+<!ENTITY real     "ℜ"> <!-- black-letter capital R = real part symbol,
+                                     U+211C ISOamso -->
+<!ENTITY trade    "™"> <!-- trade mark sign, U+2122 ISOnum -->
+<!ENTITY alefsym  "ℵ"> <!-- alef symbol = first transfinite cardinal,
+                                     U+2135 NEW -->
+<!-- alef symbol is NOT the same as hebrew letter alef,
+     U+05D0 although the same glyph could be used to depict both characters -->
+
+<!-- Arrows -->
+<!ENTITY larr     "←"> <!-- leftwards arrow, U+2190 ISOnum -->
+<!ENTITY uarr     "↑"> <!-- upwards arrow, U+2191 ISOnum-->
+<!ENTITY rarr     "→"> <!-- rightwards arrow, U+2192 ISOnum -->
+<!ENTITY darr     "↓"> <!-- downwards arrow, U+2193 ISOnum -->
+<!ENTITY harr     "↔"> <!-- left right arrow, U+2194 ISOamsa -->
+<!ENTITY crarr    "↵"> <!-- downwards arrow with corner leftwards
+                                     = carriage return, U+21B5 NEW -->
+<!ENTITY lArr     "⇐"> <!-- leftwards double arrow, U+21D0 ISOtech -->
+<!-- Unicode does not say that lArr is the same as the 'is implied by' arrow
+    but also does not have any other character for that function. So lArr can
+    be used for 'is implied by' as ISOtech suggests -->
+<!ENTITY uArr     "⇑"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+<!ENTITY rArr     "⇒"> <!-- rightwards double arrow,
+                                     U+21D2 ISOtech -->
+<!-- Unicode does not say this is the 'implies' character but does not have 
+     another character with this function so rArr can be used for 'implies'
+     as ISOtech suggests -->
+<!ENTITY dArr     "⇓"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+<!ENTITY hArr     "⇔"> <!-- left right double arrow,
+                                     U+21D4 ISOamsa -->
+
+<!-- Mathematical Operators -->
+<!ENTITY forall   "∀"> <!-- for all, U+2200 ISOtech -->
+<!ENTITY part     "∂"> <!-- partial differential, U+2202 ISOtech  -->
+<!ENTITY exist    "∃"> <!-- there exists, U+2203 ISOtech -->
+<!ENTITY empty    "∅"> <!-- empty set = null set, U+2205 ISOamso -->
+<!ENTITY nabla    "∇"> <!-- nabla = backward difference,
+                                     U+2207 ISOtech -->
+<!ENTITY isin     "∈"> <!-- element of, U+2208 ISOtech -->
+<!ENTITY notin    "∉"> <!-- not an element of, U+2209 ISOtech -->
+<!ENTITY ni       "∋"> <!-- contains as member, U+220B ISOtech -->
+<!ENTITY prod     "∏"> <!-- n-ary product = product sign,
+                                     U+220F ISOamsb -->
+<!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though
+     the same glyph might be used for both -->
+<!ENTITY sum      "∑"> <!-- n-ary summation, U+2211 ISOamsb -->
+<!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+     though the same glyph might be used for both -->
+<!ENTITY minus    "−"> <!-- minus sign, U+2212 ISOtech -->
+<!ENTITY lowast   "∗"> <!-- asterisk operator, U+2217 ISOtech -->
+<!ENTITY radic    "√"> <!-- square root = radical sign,
+                                     U+221A ISOtech -->
+<!ENTITY prop     "∝"> <!-- proportional to, U+221D ISOtech -->
+<!ENTITY infin    "∞"> <!-- infinity, U+221E ISOtech -->
+<!ENTITY ang      "∠"> <!-- angle, U+2220 ISOamso -->
+<!ENTITY and      "∧"> <!-- logical and = wedge, U+2227 ISOtech -->
+<!ENTITY or       "∨"> <!-- logical or = vee, U+2228 ISOtech -->
+<!ENTITY cap      "∩"> <!-- intersection = cap, U+2229 ISOtech -->
+<!ENTITY cup      "∪"> <!-- union = cup, U+222A ISOtech -->
+<!ENTITY int      "∫"> <!-- integral, U+222B ISOtech -->
+<!ENTITY there4   "∴"> <!-- therefore, U+2234 ISOtech -->
+<!ENTITY sim      "∼"> <!-- tilde operator = varies with = similar to,
+                                     U+223C ISOtech -->
+<!-- tilde operator is NOT the same character as the tilde, U+007E,
+     although the same glyph might be used to represent both  -->
+<!ENTITY cong     "≅"> <!-- approximately equal to, U+2245 ISOtech -->
+<!ENTITY asymp    "≈"> <!-- almost equal to = asymptotic to,
+                                     U+2248 ISOamsr -->
+<!ENTITY ne       "≠"> <!-- not equal to, U+2260 ISOtech -->
+<!ENTITY equiv    "≡"> <!-- identical to, U+2261 ISOtech -->
+<!ENTITY le       "≤"> <!-- less-than or equal to, U+2264 ISOtech -->
+<!ENTITY ge       "≥"> <!-- greater-than or equal to,
+                                     U+2265 ISOtech -->
+<!ENTITY sub      "⊂"> <!-- subset of, U+2282 ISOtech -->
+<!ENTITY sup      "⊃"> <!-- superset of, U+2283 ISOtech -->
+<!ENTITY nsub     "⊄"> <!-- not a subset of, U+2284 ISOamsn -->
+<!ENTITY sube     "⊆"> <!-- subset of or equal to, U+2286 ISOtech -->
+<!ENTITY supe     "⊇"> <!-- superset of or equal to,
+                                     U+2287 ISOtech -->
+<!ENTITY oplus    "⊕"> <!-- circled plus = direct sum,
+                                     U+2295 ISOamsb -->
+<!ENTITY otimes   "⊗"> <!-- circled times = vector product,
+                                     U+2297 ISOamsb -->
+<!ENTITY perp     "⊥"> <!-- up tack = orthogonal to = perpendicular,
+                                     U+22A5 ISOtech -->
+<!ENTITY sdot     "⋅"> <!-- dot operator, U+22C5 ISOamsb -->
+<!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+<!-- Miscellaneous Technical -->
+<!ENTITY lceil    "⌈"> <!-- left ceiling = APL upstile,
+                                     U+2308 ISOamsc  -->
+<!ENTITY rceil    "⌉"> <!-- right ceiling, U+2309 ISOamsc  -->
+<!ENTITY lfloor   "⌊"> <!-- left floor = APL downstile,
+                                     U+230A ISOamsc  -->
+<!ENTITY rfloor   "⌋"> <!-- right floor, U+230B ISOamsc  -->
+<!ENTITY lang     "〈"> <!-- left-pointing angle bracket = bra,
+                                     U+2329 ISOtech -->
+<!-- lang is NOT the same character as U+003C 'less than sign' 
+     or U+2039 'single left-pointing angle quotation mark' -->
+<!ENTITY rang     "〉"> <!-- right-pointing angle bracket = ket,
+                                     U+232A ISOtech -->
+<!-- rang is NOT the same character as U+003E 'greater than sign' 
+     or U+203A 'single right-pointing angle quotation mark' -->
+
+<!-- Geometric Shapes -->
+<!ENTITY loz      "◊"> <!-- lozenge, U+25CA ISOpub -->
+
+<!-- Miscellaneous Symbols -->
+<!ENTITY spades   "♠"> <!-- black spade suit, U+2660 ISOpub -->
+<!-- black here seems to mean filled as opposed to hollow -->
+<!ENTITY clubs    "♣"> <!-- black club suit = shamrock,
+                                     U+2663 ISOpub -->
+<!ENTITY hearts   "♥"> <!-- black heart suit = valentine,
+                                     U+2665 ISOpub -->
+<!ENTITY diams    "♦"> <!-- black diamond suit, U+2666 ISOpub -->
diff --git a/src/xhtml1/xhtml1-frameset.dtd b/src/xhtml1/xhtml1-frameset.dtd
new file mode 100644
index 0000000..d128f2e
--- /dev/null
+++ b/src/xhtml1/xhtml1-frameset.dtd
@@ -0,0 +1,1235 @@
+<!--
+   Extensible HTML version 1.0 Frameset DTD
+
+   This is the same as HTML 4 Frameset except for
+   changes due to the differences between XML and SGML.
+
+   Namespace = http://www.w3.org/1999/xhtml
+
+   For further information, see: http://www.w3.org/TR/xhtml1
+
+   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+   All Rights Reserved. 
+
+   This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+   PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+   SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"
+
+   $Revision: 1.2 $
+   $Date: 2002/08/01 18:37:55 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+   "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+   "xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+   "-//W3C//ENTITIES Symbols for XHTML//EN"
+   "xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+   "-//W3C//ENTITIES Special for XHTML//EN"
+   "xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+    <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+    <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+    <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+    <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+    <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+    <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+    <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+    <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+    <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+    <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+    <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+    <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+    <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+    <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+    <!-- used for titles etc. -->
+
+<!ENTITY % FrameTarget "NMTOKEN">
+    <!-- render in this frame -->
+
+<!ENTITY % Length "CDATA">
+    <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+    <!-- pixel, percentage, or relative -->
+
+<!ENTITY % MultiLengths "CDATA">
+    <!-- comma-separated list of MultiLength -->
+
+<!ENTITY % Pixels "CDATA">
+    <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+    <!-- comma separated list of lengths -->
+
+<!-- used for object, applet, img, input and iframe -->
+<!ENTITY % ImgAlign "(top|middle|bottom|left|right)">
+
+<!-- a color using sRGB: #RRGGBB as Hex values -->
+<!ENTITY % Color "CDATA">
+
+<!-- There are also 16 widely known color names with their sRGB values:
+
+    Black  = #000000    Green  = #008000
+    Silver = #C0C0C0    Lime   = #00FF00
+    Gray   = #808080    Olive  = #808000
+    White  = #FFFFFF    Yellow = #FFFF00
+    Maroon = #800000    Navy   = #000080
+    Red    = #FF0000    Blue   = #0000FF
+    Purple = #800080    Teal   = #008080
+    Fuchsia= #FF00FF    Aqua   = #00FFFF
+-->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+  id       document-wide unique id
+  class    space separated list of classes
+  style    associated style info
+  title    advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id          ID             #IMPLIED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED"
+  >
+
+<!-- internationalization attributes
+  lang        language code (backwards compatible)
+  xml:lang    language code (as per XML 1.0 spec)
+  dir         direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #IMPLIED"
+  >
+
+<!-- attributes for common UI events
+  onclick     a pointer button was clicked
+  ondblclick  a pointer button was double clicked
+  onmousedown a pointer button was pressed down
+  onmouseup   a pointer button was released
+  onmousemove a pointer was moved onto the element
+  onmouseout  a pointer was moved away from the element
+  onkeypress  a key was pressed and released
+  onkeydown   a key was pressed down
+  onkeyup     a key was released
+-->
+<!ENTITY % events
+ "onclick     %Script;       #IMPLIED
+  ondblclick  %Script;       #IMPLIED
+  onmousedown %Script;       #IMPLIED
+  onmouseup   %Script;       #IMPLIED
+  onmouseover %Script;       #IMPLIED
+  onmousemove %Script;       #IMPLIED
+  onmouseout  %Script;       #IMPLIED
+  onkeypress  %Script;       #IMPLIED
+  onkeydown   %Script;       #IMPLIED
+  onkeyup     %Script;       #IMPLIED"
+  >
+
+<!-- attributes for elements that can get the focus
+  accesskey   accessibility key character
+  tabindex    position in tabbing order
+  onfocus     the element got the focus
+  onblur      the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey   %Character;    #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED"
+  >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!-- text alignment for p, div, h1-h6. The default is
+     align="left" for ltr headings, "right" for rtl -->
+
+<!ENTITY % TextAlign "align (left|center|right|justify) #IMPLIED">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.extra
+   "object | applet | img | map | iframe">
+	
+<!ENTITY % special.basic
+	"br | span | bdo">
+
+<!ENTITY % special
+   "%special.basic; | %special.extra;">
+
+<!ENTITY % fontstyle.extra "big | small | font | basefont">
+
+<!ENTITY % fontstyle.basic "tt | i | b | u
+                      | s | strike ">
+
+<!ENTITY % fontstyle "%fontstyle.basic; | %fontstyle.extra;">
+
+<!ENTITY % phrase.extra "sub | sup">
+<!ENTITY % phrase.basic "em | strong | dfn | code | q |
+                   samp | kbd | var | cite | abbr | acronym">
+
+<!ENTITY % phrase "%phrase.basic; | %phrase.extra;">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl | menu | dir">
+<!ENTITY % blocktext "pre | hr | blockquote | address | center">
+
+<!ENTITY % block
+    "p | %heading; | div | %lists; | %blocktext; | isindex | fieldset | table">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+   "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding img, object, applet, big, small,
+     sub, sup, font, or basefont -->
+
+<!ENTITY % pre.content
+   "(#PCDATA | a | %special.basic; | %fontstyle.basic; | %phrase.basic; |
+	   %inline.forms; | %misc.inline;)*">
+
+
+<!-- form uses %Flow; excluding form -->
+
+<!ENTITY % form.content "(#PCDATA | %block; | %inline; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form, form controls, iframe -->
+
+<!ENTITY % button.content
+   "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+      table | br | span | bdo | object | applet | img | map |
+      %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, frameset)>
+<!ATTLIST html
+  %i18n;
+  id          ID             #IMPLIED
+  xmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'
+  >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object|isindex)*">
+
+<!-- content model is %head.misc; combined with a single
+     title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+     ((title, %head.misc;, (base, %head.misc;)?) |
+      (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+  %i18n;
+  id          ID             #IMPLIED
+  profile     %URI;          #IMPLIED
+  >
+
+<!-- The title element is not considered part of the flow of text.
+       It should be displayed, for example as the page header or
+       window title. Exactly one title is required per document.
+    -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title 
+  %i18n;
+  id          ID             #IMPLIED
+  >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+  id          ID             #IMPLIED
+  href        %URI;          #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+  %i18n;
+  id          ID             #IMPLIED
+  http-equiv  CDATA          #IMPLIED
+  name        CDATA          #IMPLIED
+  content     CDATA          #REQUIRED
+  scheme      CDATA          #IMPLIED
+  >
+
+<!--
+  Relationship values can be used in principle:
+
+   a) for document specific toolbars/menus when used
+      with the link element in document head e.g.
+        start, contents, previous, next, index, end, help
+   b) to link to a separate style sheet (rel="stylesheet")
+   c) to make a link to a script (rel="script")
+   d) by stylesheets to control how collections of
+      html nodes are rendered into printed documents
+   e) to make a link to a printable version of this document
+      e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+  %attrs;
+  charset     %Charset;      #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  type        %ContentType;  #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  media       %MediaDesc;    #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+  %i18n;
+  id          ID             #IMPLIED
+  type        %ContentType;  #REQUIRED
+  media       %MediaDesc;    #IMPLIED
+  title       %Text;         #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+  id          ID             #IMPLIED
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #REQUIRED
+  language    CDATA          #IMPLIED
+  src         %URI;          #IMPLIED
+  defer       (defer)        #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Flow;>
+<!ATTLIST noscript
+  %attrs;
+  >
+
+<!--======================= Frames =======================================-->
+
+<!-- only one noframes element permitted per document -->
+
+<!ELEMENT frameset (frameset|frame|noframes)*>
+<!ATTLIST frameset
+  %coreattrs;
+  rows        %MultiLengths; #IMPLIED
+  cols        %MultiLengths; #IMPLIED
+  onload      %Script;       #IMPLIED
+  onunload    %Script;       #IMPLIED
+  >
+
+<!-- reserved frame names start with "_" otherwise starts with letter -->
+
+<!-- tiled window within frameset -->
+
+<!ELEMENT frame EMPTY>
+<!ATTLIST frame
+  %coreattrs;
+  longdesc    %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  src         %URI;          #IMPLIED
+  frameborder (1|0)          "1"
+  marginwidth %Pixels;       #IMPLIED
+  marginheight %Pixels;      #IMPLIED
+  noresize    (noresize)     #IMPLIED
+  scrolling   (yes|no|auto)  "auto"
+  >
+
+<!-- inline subwindow -->
+
+<!ELEMENT iframe %Flow;>
+<!ATTLIST iframe
+  %coreattrs;
+  longdesc    %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  src         %URI;          #IMPLIED
+  frameborder (1|0)          "1"
+  marginwidth %Pixels;       #IMPLIED
+  marginheight %Pixels;      #IMPLIED
+  scrolling   (yes|no|auto)  "auto"
+  align       %ImgAlign;     #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  >
+
+<!-- alternate content container for non frame-based rendering -->
+
+<!ELEMENT noframes (body)>
+<!ATTLIST noframes
+  %attrs;
+  >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Flow;>
+<!ATTLIST body
+  %attrs;
+  onload      %Script;       #IMPLIED
+  onunload    %Script;       #IMPLIED
+  background  %URI;          #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  text        %Color;        #IMPLIED
+  link        %Color;        #IMPLIED
+  vlink       %Color;        #IMPLIED
+  alink       %Color;        #IMPLIED
+  >
+
+<!ELEMENT div %Flow;>  <!-- generic language/style container -->
+<!ATTLIST div
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Headings =========================================-->
+
+<!--
+  There are six levels of headings from h1 (the most important)
+  to h6 (the least important).
+-->
+
+<!ELEMENT h1  %Inline;>
+<!ATTLIST h1
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list bullet styles -->
+
+<!ENTITY % ULStyle "(disc|square|circle)">
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+  %attrs;
+  type        %ULStyle;     #IMPLIED
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- Ordered list numbering style
+
+    1   arabic numbers      1, 2, 3, ...
+    a   lower alpha         a, b, c, ...
+    A   upper alpha         A, B, C, ...
+    i   lower roman         i, ii, iii, ...
+    I   upper roman         I, II, III, ...
+
+    The style is applied to the sequence number which by default
+    is reset to 1 for the first list item in an ordered list.
+-->
+<!ENTITY % OLStyle "CDATA">
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+  %attrs;
+  type        %OLStyle;      #IMPLIED
+  compact     (compact)      #IMPLIED
+  start       %Number;       #IMPLIED
+  >
+
+<!-- single column list (DEPRECATED) --> 
+<!ELEMENT menu (li)+>
+<!ATTLIST menu
+  %attrs;
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- multiple column list (DEPRECATED) --> 
+<!ELEMENT dir (li)+>
+<!ATTLIST dir
+  %attrs;
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- LIStyle is constrained to: "(%ULStyle;|%OLStyle;)" -->
+<!ENTITY % LIStyle "CDATA">
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+  %attrs;
+  type        %LIStyle;      #IMPLIED
+  value       %Number;       #IMPLIED
+  >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+  %attrs;
+  compact     (compact)      #IMPLIED
+  >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+  %attrs;
+  >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+  %attrs;
+  >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address (#PCDATA | %inline; | %misc.inline; | p)*>
+<!ATTLIST address
+  %attrs;
+  >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+  %attrs;
+  align       (left|center|right) #IMPLIED
+  noshade     (noshade)      #IMPLIED
+  size        %Pixels;       #IMPLIED
+  width       %Length;       #IMPLIED
+  >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding 
+        "img|object|applet|big|small|sub|sup|font|basefont" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+  %attrs;
+  width       %Number;      #IMPLIED
+  xml:space   (preserve)    #FIXED 'preserve'
+  >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Flow;>
+<!ATTLIST blockquote
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!--=================== Text alignment ===================================-->
+
+<!-- center content -->
+<!ELEMENT center %Flow;>
+<!ATTLIST center
+  %attrs;
+  >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+
+<!--
+  ins/del are allowed in block and inline content, but its
+  inappropriate to include block content within an ins element
+  occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+  %attrs;
+  %focus;
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+  %attrs;
+  >
+
+<!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+  %coreattrs;
+  %events;
+  lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #REQUIRED
+  >
+
+<!ELEMENT br EMPTY>   <!-- forced line break -->
+<!ATTLIST br
+  %coreattrs;
+  clear       (left|all|right|none) "none"
+  >
+
+<!ELEMENT em %Inline;>   <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;>   <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;>   <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;>   <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;>   <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;>  <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;>   <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;>   <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;>   <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;>   <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;>   <!-- inlined quote -->
+<!ATTLIST q
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;>   <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;>   <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;>   <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;>   <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;>   <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!ELEMENT u %Inline;>   <!-- underline -->
+<!ATTLIST u %attrs;>
+
+<!ELEMENT s %Inline;>   <!-- strike-through -->
+<!ATTLIST s %attrs;>
+
+<!ELEMENT strike %Inline;>   <!-- strike-through -->
+<!ATTLIST strike %attrs;>
+
+<!ELEMENT basefont EMPTY>  <!-- base font size -->
+<!ATTLIST basefont
+  id          ID             #IMPLIED
+  size        CDATA          #REQUIRED
+  color       %Color;        #IMPLIED
+  face        CDATA          #IMPLIED
+  >
+
+<!ELEMENT font %Inline;> <!-- local change to font -->
+<!ATTLIST font
+  %coreattrs;
+  %i18n;
+  size        CDATA          #IMPLIED
+  color       %Color;        #IMPLIED
+  face        CDATA          #IMPLIED
+  >
+
+<!--==================== Object ======================================-->
+<!--
+  object is used to embed objects as part of HTML pages.
+  param elements should precede other content. Parameters
+  can also be expressed as attribute/value pairs on the
+  object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form |%inline; | %misc;)*>
+<!ATTLIST object
+  %attrs;
+  declare     (declare)      #IMPLIED
+  classid     %URI;          #IMPLIED
+  codebase    %URI;          #IMPLIED
+  data        %URI;          #IMPLIED
+  type        %ContentType;  #IMPLIED
+  codetype    %ContentType;  #IMPLIED
+  archive     %UriList;      #IMPLIED
+  standby     %Text;         #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  border      %Pixels;       #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!--
+  param is used to supply a named property value.
+  In XML it would seem natural to follow RDF and support an
+  abbreviated syntax where the param elements are replaced
+  by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+  id          ID             #IMPLIED
+  name        CDATA          #REQUIRED
+  value       CDATA          #IMPLIED
+  valuetype   (data|ref|object) "data"
+  type        %ContentType;  #IMPLIED
+  >
+
+<!--=================== Java applet ==================================-->
+<!--
+  One of code or object attributes must be present.
+  Place param elements before other content.
+-->
+<!ELEMENT applet (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST applet
+  %coreattrs;
+  codebase    %URI;          #IMPLIED
+  archive     CDATA          #IMPLIED
+  code        CDATA          #IMPLIED
+  object      CDATA          #IMPLIED
+  alt         %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  width       %Length;       #REQUIRED
+  height      %Length;       #REQUIRED
+  align       %ImgAlign;     #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!--=================== Images ===========================================-->
+
+<!--
+   To avoid accessibility problems for people who aren't
+   able to see the image, you should provide a text
+   description using the alt and longdesc attributes.
+   In addition, avoid the use of server-side image maps.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+  %attrs;
+  src         %URI;          #REQUIRED
+  alt         %Text;         #REQUIRED
+  name        NMTOKEN        #IMPLIED
+  longdesc    %URI;          #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  ismap       (ismap)        #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  border      %Pixels;       #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!-- usemap points to a map element which may be in this document
+  or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+     separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+  %i18n;
+  %events;
+  id          ID             #REQUIRED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+  %attrs;
+  %focus;
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  href        %URI;          #IMPLIED
+  nohref      (nohref)       #IMPLIED
+  alt         %Text;         #REQUIRED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--================ Forms ===============================================-->
+
+<!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+  %attrs;
+  action      %URI;          #REQUIRED
+  method      (get|post)     "get"
+  name        NMTOKEN        #IMPLIED
+  enctype     %ContentType;  "application/x-www-form-urlencoded"
+  onsubmit    %Script;       #IMPLIED
+  onreset     %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  accept-charset %Charsets;  #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--
+  Each label must not contain more than ONE field
+  Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+  %attrs;
+  for         IDREF          #IMPLIED
+  accesskey   %Character;    #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  >
+
+<!ENTITY % InputType
+  "(text | password | checkbox |
+    radio | submit | reset |
+    file | hidden | image | button)"
+   >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY>     <!-- form control -->
+<!ATTLIST input
+  %attrs;
+  %focus;
+  type        %InputType;    "text"
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  checked     (checked)      #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  size        CDATA          #IMPLIED
+  maxlength   %Number;       #IMPLIED
+  src         %URI;          #IMPLIED
+  alt         CDATA          #IMPLIED
+  usemap      %URI;          #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  >
+
+<!ELEMENT select (optgroup|option)+>  <!-- option selector -->
+<!ATTLIST select
+  %attrs;
+  name        CDATA          #IMPLIED
+  size        %Number;       #IMPLIED
+  multiple    (multiple)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!ELEMENT optgroup (option)+>   <!-- option group -->
+<!ATTLIST optgroup
+  %attrs;
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #REQUIRED
+  >
+
+<!ELEMENT option (#PCDATA)>     <!-- selectable choice -->
+<!ATTLIST option
+  %attrs;
+  selected    (selected)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #IMPLIED
+  value       CDATA          #IMPLIED
+  >
+
+<!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->
+<!ATTLIST textarea
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  rows        %Number;       #REQUIRED
+  cols        %Number;       #REQUIRED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!--
+  The fieldset element is used to group form fields.
+  Only one legend element should occur in the content
+  and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+  %attrs;
+  >
+
+<!ENTITY % LAlign "(top|bottom|left|right)">
+
+<!ELEMENT legend %Inline;>     <!-- fieldset label -->
+<!ATTLIST legend
+  %attrs;
+  accesskey   %Character;    #IMPLIED
+  align       %LAlign;       #IMPLIED
+  >
+
+<!--
+ Content is %Flow; excluding a, form, form controls, iframe
+--> 
+<!ELEMENT button %button.content;>  <!-- push button -->
+<!ATTLIST button
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  type        (button|submit|reset) "submit"
+  disabled    (disabled)     #IMPLIED
+  >
+
+<!-- single-line text input control (DEPRECATED) -->
+<!ELEMENT isindex EMPTY>
+<!ATTLIST isindex
+  %coreattrs;
+  %i18n;
+  prompt      %Text;         #IMPLIED
+  >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+     "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+  
+<!-- horizontal placement of table relative to document -->
+<!ENTITY % TAlign "(left|center|right)">
+
+<!-- horizontal alignment attributes for cell contents
+
+  char        alignment char, e.g. char=":"
+  charoff     offset for alignment char
+-->
+<!ENTITY % cellhalign
+  "align      (left|center|right|justify|char) #IMPLIED
+   char       %Character;    #IMPLIED
+   charoff    %Length;       #IMPLIED"
+  >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+  "valign     (top|middle|bottom|baseline) #IMPLIED"
+  >
+
+<!ELEMENT table
+     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption  %Inline;>
+<!ELEMENT thead    (tr)+>
+<!ELEMENT tfoot    (tr)+>
+<!ELEMENT tbody    (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col      EMPTY>
+<!ELEMENT tr       (th|td)+>
+<!ELEMENT th       %Flow;>
+<!ELEMENT td       %Flow;>
+
+<!ATTLIST table
+  %attrs;
+  summary     %Text;         #IMPLIED
+  width       %Length;       #IMPLIED
+  border      %Pixels;       #IMPLIED
+  frame       %TFrame;       #IMPLIED
+  rules       %TRules;       #IMPLIED
+  cellspacing %Length;       #IMPLIED
+  cellpadding %Length;       #IMPLIED
+  align       %TAlign;       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  >
+
+<!ENTITY % CAlign "(top|bottom|left|right)">
+
+<!ATTLIST caption
+  %attrs;
+  align       %CAlign;       #IMPLIED
+  >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+     width=64        width in screen pixels
+     width=0.5*      relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+    Use thead to duplicate headers when breaking table
+    across page boundaries, or for static headers when
+    tbody sections are rendered in scrolling panel.
+
+    Use tfoot to duplicate footers when breaking table
+    across page boundaries, or for static footers when
+    tbody sections are rendered in scrolling panel.
+
+    Use multiple tbody sections when rules are needed
+    between groups of table rows.
+-->
+<!ATTLIST thead
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tfoot
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tbody
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tr
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  bgcolor     %Color;        #IMPLIED
+  >
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  nowrap      (nowrap)       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  width       %Pixels;       #IMPLIED
+  height      %Pixels;       #IMPLIED
+  >
+
+<!ATTLIST td
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  nowrap      (nowrap)       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  width       %Pixels;       #IMPLIED
+  height      %Pixels;       #IMPLIED
+  >
+
diff --git a/src/xhtml1/xhtml1-strict.dtd b/src/xhtml1/xhtml1-strict.dtd
new file mode 100644
index 0000000..2927b9e
--- /dev/null
+++ b/src/xhtml1/xhtml1-strict.dtd
@@ -0,0 +1,978 @@
+<!--
+   Extensible HTML version 1.0 Strict DTD
+
+   This is the same as HTML 4 Strict except for
+   changes due to the differences between XML and SGML.
+
+   Namespace = http://www.w3.org/1999/xhtml
+
+   For further information, see: http://www.w3.org/TR/xhtml1
+
+   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+   All Rights Reserved. 
+
+   This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+   PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+
+   $Revision: 1.1 $
+   $Date: 2002/08/01 13:56:03 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+   "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+   "xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+   "-//W3C//ENTITIES Symbols for XHTML//EN"
+   "xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+   "-//W3C//ENTITIES Special for XHTML//EN"
+   "xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+    <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+    <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+    <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+    <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+    <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+    <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+    <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+    <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+    <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+    <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+    <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+    <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+    <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+    <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+    <!-- used for titles etc. -->
+
+<!ENTITY % Length "CDATA">
+    <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+    <!-- pixel, percentage, or relative -->
+
+<!ENTITY % Pixels "CDATA">
+    <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+    <!-- comma separated list of lengths -->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+  id       document-wide unique id
+  class    space separated list of classes
+  style    associated style info
+  title    advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id          ID             #IMPLIED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED"
+  >
+
+<!-- internationalization attributes
+  lang        language code (backwards compatible)
+  xml:lang    language code (as per XML 1.0 spec)
+  dir         direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #IMPLIED"
+  >
+
+<!-- attributes for common UI events
+  onclick     a pointer button was clicked
+  ondblclick  a pointer button was double clicked
+  onmousedown a pointer button was pressed down
+  onmouseup   a pointer button was released
+  onmousemove a pointer was moved onto the element
+  onmouseout  a pointer was moved away from the element
+  onkeypress  a key was pressed and released
+  onkeydown   a key was pressed down
+  onkeyup     a key was released
+-->
+<!ENTITY % events
+ "onclick     %Script;       #IMPLIED
+  ondblclick  %Script;       #IMPLIED
+  onmousedown %Script;       #IMPLIED
+  onmouseup   %Script;       #IMPLIED
+  onmouseover %Script;       #IMPLIED
+  onmousemove %Script;       #IMPLIED
+  onmouseout  %Script;       #IMPLIED
+  onkeypress  %Script;       #IMPLIED
+  onkeydown   %Script;       #IMPLIED
+  onkeyup     %Script;       #IMPLIED"
+  >
+
+<!-- attributes for elements that can get the focus
+  accesskey   accessibility key character
+  tabindex    position in tabbing order
+  onfocus     the element got the focus
+  onblur      the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey   %Character;    #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED"
+  >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.pre
+   "br | span | bdo | map">
+
+
+<!ENTITY % special
+   "%special.pre; | object | img ">
+
+<!ENTITY % fontstyle "tt | i | b | big | small ">
+
+<!ENTITY % phrase "em | strong | dfn | code | q |
+                   samp | kbd | var | cite | abbr | acronym | sub | sup ">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl">
+<!ENTITY % blocktext "pre | hr | blockquote | address">
+
+<!ENTITY % block
+     "p | %heading; | div | %lists; | %blocktext; | fieldset | table">
+
+<!ENTITY % Block "(%block; | form | %misc;)*">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+   "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding big, small, sup or sup -->
+
+<!ENTITY % pre.content
+   "(#PCDATA | a | %fontstyle; | %phrase; | %special.pre; | %misc.inline;
+      | %inline.forms;)*">
+
+<!-- form uses %Block; excluding form -->
+
+<!ENTITY % form.content "(%block; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form and form controls -->
+
+<!ENTITY % button.content
+   "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+    table | %special; | %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, body)>
+<!ATTLIST html
+  %i18n;
+  id          ID             #IMPLIED
+  xmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'
+  >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object)*">
+
+<!-- content model is %head.misc; combined with a single
+     title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+     ((title, %head.misc;, (base, %head.misc;)?) |
+      (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+  %i18n;
+  id          ID             #IMPLIED
+  profile     %URI;          #IMPLIED
+  >
+
+<!-- The title element is not considered part of the flow of text.
+       It should be displayed, for example as the page header or
+       window title. Exactly one title is required per document.
+    -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title 
+  %i18n;
+  id          ID             #IMPLIED
+  >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+  href        %URI;          #REQUIRED
+  id          ID             #IMPLIED
+  >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+  %i18n;
+  id          ID             #IMPLIED
+  http-equiv  CDATA          #IMPLIED
+  name        CDATA          #IMPLIED
+  content     CDATA          #REQUIRED
+  scheme      CDATA          #IMPLIED
+  >
+
+<!--
+  Relationship values can be used in principle:
+
+   a) for document specific toolbars/menus when used
+      with the link element in document head e.g.
+        start, contents, previous, next, index, end, help
+   b) to link to a separate style sheet (rel="stylesheet")
+   c) to make a link to a script (rel="script")
+   d) by stylesheets to control how collections of
+      html nodes are rendered into printed documents
+   e) to make a link to a printable version of this document
+      e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+  %attrs;
+  charset     %Charset;      #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  type        %ContentType;  #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  media       %MediaDesc;    #IMPLIED
+  >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+  %i18n;
+  id          ID             #IMPLIED
+  type        %ContentType;  #REQUIRED
+  media       %MediaDesc;    #IMPLIED
+  title       %Text;         #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+  id          ID             #IMPLIED
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #REQUIRED
+  src         %URI;          #IMPLIED
+  defer       (defer)        #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Block;>
+<!ATTLIST noscript
+  %attrs;
+  >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Block;>
+<!ATTLIST body
+  %attrs;
+  onload          %Script;   #IMPLIED
+  onunload        %Script;   #IMPLIED
+  >
+
+<!ELEMENT div %Flow;>  <!-- generic language/style container -->
+<!ATTLIST div
+  %attrs;
+  >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+  %attrs;
+  >
+
+<!--=================== Headings =========================================-->
+
+<!--
+  There are six levels of headings from h1 (the most important)
+  to h6 (the least important).
+-->
+
+<!ELEMENT h1  %Inline;>
+<!ATTLIST h1
+   %attrs;
+   >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+   %attrs;
+   >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+   %attrs;
+   >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+   %attrs;
+   >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+   %attrs;
+   >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+   %attrs;
+   >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+  %attrs;
+  >
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+  %attrs;
+  >
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+  %attrs;
+  >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+  %attrs;
+  >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+  %attrs;
+  >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+  %attrs;
+  >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address %Inline;>
+<!ATTLIST address
+  %attrs;
+  >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+  %attrs;
+  >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding "img|object|big|small|sub|sup" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+  %attrs;
+  xml:space (preserve) #FIXED 'preserve'
+  >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Block;>
+<!ATTLIST blockquote
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+<!--
+  ins/del are allowed in block and inline content, but its
+  inappropriate to include block content within an ins element
+  occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+  %attrs;
+  %focus;
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+  %attrs;
+  >
+
+<!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+  %coreattrs;
+  %events;
+  lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #REQUIRED
+  >
+
+<!ELEMENT br EMPTY>   <!-- forced line break -->
+<!ATTLIST br
+  %coreattrs;
+  >
+
+<!ELEMENT em %Inline;>   <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;>   <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;>   <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;>   <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;>   <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;>  <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;>   <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;>   <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;>   <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;>   <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;>   <!-- inlined quote -->
+<!ATTLIST q
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;>   <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;>   <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;>   <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;>   <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;>   <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!--==================== Object ======================================-->
+<!--
+  object is used to embed objects as part of HTML pages.
+  param elements should precede other content. Parameters
+  can also be expressed as attribute/value pairs on the
+  object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST object
+  %attrs;
+  declare     (declare)      #IMPLIED
+  classid     %URI;          #IMPLIED
+  codebase    %URI;          #IMPLIED
+  data        %URI;          #IMPLIED
+  type        %ContentType;  #IMPLIED
+  codetype    %ContentType;  #IMPLIED
+  archive     %UriList;      #IMPLIED
+  standby     %Text;         #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  >
+
+<!--
+  param is used to supply a named property value.
+  In XML it would seem natural to follow RDF and support an
+  abbreviated syntax where the param elements are replaced
+  by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+  id          ID             #IMPLIED
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  valuetype   (data|ref|object) "data"
+  type        %ContentType;  #IMPLIED
+  >
+
+<!--=================== Images ===========================================-->
+
+<!--
+   To avoid accessibility problems for people who aren't
+   able to see the image, you should provide a text
+   description using the alt and longdesc attributes.
+   In addition, avoid the use of server-side image maps.
+   Note that in this DTD there is no name attribute. That
+   is only available in the transitional and frameset DTD.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+  %attrs;
+  src         %URI;          #REQUIRED
+  alt         %Text;         #REQUIRED
+  longdesc    %URI;          #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  ismap       (ismap)        #IMPLIED
+  >
+
+<!-- usemap points to a map element which may be in this document
+  or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+     separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+  %i18n;
+  %events;
+  id          ID             #REQUIRED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+  %attrs;
+  %focus;
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  href        %URI;          #IMPLIED
+  nohref      (nohref)       #IMPLIED
+  alt         %Text;         #REQUIRED
+  >
+
+<!--================ Forms ===============================================-->
+<!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+  %attrs;
+  action      %URI;          #REQUIRED
+  method      (get|post)     "get"
+  enctype     %ContentType;  "application/x-www-form-urlencoded"
+  onsubmit    %Script;       #IMPLIED
+  onreset     %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  accept-charset %Charsets;  #IMPLIED
+  >
+
+<!--
+  Each label must not contain more than ONE field
+  Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+  %attrs;
+  for         IDREF          #IMPLIED
+  accesskey   %Character;    #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  >
+
+<!ENTITY % InputType
+  "(text | password | checkbox |
+    radio | submit | reset |
+    file | hidden | image | button)"
+   >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY>     <!-- form control -->
+<!ATTLIST input
+  %attrs;
+  %focus;
+  type        %InputType;    "text"
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  checked     (checked)      #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  size        CDATA          #IMPLIED
+  maxlength   %Number;       #IMPLIED
+  src         %URI;          #IMPLIED
+  alt         CDATA          #IMPLIED
+  usemap      %URI;          #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  >
+
+<!ELEMENT select (optgroup|option)+>  <!-- option selector -->
+<!ATTLIST select
+  %attrs;
+  name        CDATA          #IMPLIED
+  size        %Number;       #IMPLIED
+  multiple    (multiple)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!ELEMENT optgroup (option)+>   <!-- option group -->
+<!ATTLIST optgroup
+  %attrs;
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #REQUIRED
+  >
+
+<!ELEMENT option (#PCDATA)>     <!-- selectable choice -->
+<!ATTLIST option
+  %attrs;
+  selected    (selected)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #IMPLIED
+  value       CDATA          #IMPLIED
+  >
+
+<!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->
+<!ATTLIST textarea
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  rows        %Number;       #REQUIRED
+  cols        %Number;       #REQUIRED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!--
+  The fieldset element is used to group form fields.
+  Only one legend element should occur in the content
+  and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+  %attrs;
+  >
+
+<!ELEMENT legend %Inline;>     <!-- fieldset label -->
+<!ATTLIST legend
+  %attrs;
+  accesskey   %Character;    #IMPLIED
+  >
+
+<!--
+ Content is %Flow; excluding a, form and form controls
+--> 
+<!ELEMENT button %button.content;>  <!-- push button -->
+<!ATTLIST button
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  type        (button|submit|reset) "submit"
+  disabled    (disabled)     #IMPLIED
+  >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+     "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+  
+<!-- horizontal alignment attributes for cell contents
+
+  char        alignment char, e.g. char=':'
+  charoff     offset for alignment char
+-->
+<!ENTITY % cellhalign
+  "align      (left|center|right|justify|char) #IMPLIED
+   char       %Character;    #IMPLIED
+   charoff    %Length;       #IMPLIED"
+  >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+  "valign     (top|middle|bottom|baseline) #IMPLIED"
+  >
+
+<!ELEMENT table
+     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption  %Inline;>
+<!ELEMENT thead    (tr)+>
+<!ELEMENT tfoot    (tr)+>
+<!ELEMENT tbody    (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col      EMPTY>
+<!ELEMENT tr       (th|td)+>
+<!ELEMENT th       %Flow;>
+<!ELEMENT td       %Flow;>
+
+<!ATTLIST table
+  %attrs;
+  summary     %Text;         #IMPLIED
+  width       %Length;       #IMPLIED
+  border      %Pixels;       #IMPLIED
+  frame       %TFrame;       #IMPLIED
+  rules       %TRules;       #IMPLIED
+  cellspacing %Length;       #IMPLIED
+  cellpadding %Length;       #IMPLIED
+  >
+
+<!ATTLIST caption
+  %attrs;
+  >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+     width=64        width in screen pixels
+     width=0.5*      relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+    Use thead to duplicate headers when breaking table
+    across page boundaries, or for static headers when
+    tbody sections are rendered in scrolling panel.
+
+    Use tfoot to duplicate footers when breaking table
+    across page boundaries, or for static footers when
+    tbody sections are rendered in scrolling panel.
+
+    Use multiple tbody sections when rules are needed
+    between groups of table rows.
+-->
+<!ATTLIST thead
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tfoot
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tbody
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tr
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST td
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  >
+
diff --git a/src/xhtml1/xhtml1-transitional.dtd b/src/xhtml1/xhtml1-transitional.dtd
new file mode 100644
index 0000000..628f27a
--- /dev/null
+++ b/src/xhtml1/xhtml1-transitional.dtd
@@ -0,0 +1,1201 @@
+<!--
+   Extensible HTML version 1.0 Transitional DTD
+
+   This is the same as HTML 4 Transitional except for
+   changes due to the differences between XML and SGML.
+
+   Namespace = http://www.w3.org/1999/xhtml
+
+   For further information, see: http://www.w3.org/TR/xhtml1
+
+   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+   All Rights Reserved. 
+
+   This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+   PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+   SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
+
+   $Revision: 1.2 $
+   $Date: 2002/08/01 18:37:55 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+   "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+   "xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+   "-//W3C//ENTITIES Symbols for XHTML//EN"
+   "xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+   "-//W3C//ENTITIES Special for XHTML//EN"
+   "xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+    <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+    <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+    <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+    <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+    <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+    <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+    <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+    <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+    <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+    <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+    <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+    <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+    <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+    <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+    <!-- used for titles etc. -->
+
+<!ENTITY % FrameTarget "NMTOKEN">
+    <!-- render in this frame -->
+
+<!ENTITY % Length "CDATA">
+    <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+    <!-- pixel, percentage, or relative -->
+
+<!ENTITY % Pixels "CDATA">
+    <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+    <!-- comma separated list of lengths -->
+
+<!-- used for object, applet, img, input and iframe -->
+<!ENTITY % ImgAlign "(top|middle|bottom|left|right)">
+
+<!-- a color using sRGB: #RRGGBB as Hex values -->
+<!ENTITY % Color "CDATA">
+
+<!-- There are also 16 widely known color names with their sRGB values:
+
+    Black  = #000000    Green  = #008000
+    Silver = #C0C0C0    Lime   = #00FF00
+    Gray   = #808080    Olive  = #808000
+    White  = #FFFFFF    Yellow = #FFFF00
+    Maroon = #800000    Navy   = #000080
+    Red    = #FF0000    Blue   = #0000FF
+    Purple = #800080    Teal   = #008080
+    Fuchsia= #FF00FF    Aqua   = #00FFFF
+-->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+  id       document-wide unique id
+  class    space separated list of classes
+  style    associated style info
+  title    advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id          ID             #IMPLIED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED"
+  >
+
+<!-- internationalization attributes
+  lang        language code (backwards compatible)
+  xml:lang    language code (as per XML 1.0 spec)
+  dir         direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #IMPLIED"
+  >
+
+<!-- attributes for common UI events
+  onclick     a pointer button was clicked
+  ondblclick  a pointer button was double clicked
+  onmousedown a pointer button was pressed down
+  onmouseup   a pointer button was released
+  onmousemove a pointer was moved onto the element
+  onmouseout  a pointer was moved away from the element
+  onkeypress  a key was pressed and released
+  onkeydown   a key was pressed down
+  onkeyup     a key was released
+-->
+<!ENTITY % events
+ "onclick     %Script;       #IMPLIED
+  ondblclick  %Script;       #IMPLIED
+  onmousedown %Script;       #IMPLIED
+  onmouseup   %Script;       #IMPLIED
+  onmouseover %Script;       #IMPLIED
+  onmousemove %Script;       #IMPLIED
+  onmouseout  %Script;       #IMPLIED
+  onkeypress  %Script;       #IMPLIED
+  onkeydown   %Script;       #IMPLIED
+  onkeyup     %Script;       #IMPLIED"
+  >
+
+<!-- attributes for elements that can get the focus
+  accesskey   accessibility key character
+  tabindex    position in tabbing order
+  onfocus     the element got the focus
+  onblur      the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey   %Character;    #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED"
+  >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!-- text alignment for p, div, h1-h6. The default is
+     align="left" for ltr headings, "right" for rtl -->
+
+<!ENTITY % TextAlign "align (left|center|right|justify) #IMPLIED">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.extra
+   "object | applet | img | map | iframe">
+	
+<!ENTITY % special.basic
+	"br | span | bdo">
+
+<!ENTITY % special
+   "%special.basic; | %special.extra;">
+
+<!ENTITY % fontstyle.extra "big | small | font | basefont">
+
+<!ENTITY % fontstyle.basic "tt | i | b | u
+                      | s | strike ">
+
+<!ENTITY % fontstyle "%fontstyle.basic; | %fontstyle.extra;">
+
+<!ENTITY % phrase.extra "sub | sup">
+<!ENTITY % phrase.basic "em | strong | dfn | code | q |
+                   samp | kbd | var | cite | abbr | acronym">
+
+<!ENTITY % phrase "%phrase.basic; | %phrase.extra;">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl | menu | dir">
+<!ENTITY % blocktext "pre | hr | blockquote | address | center | noframes">
+
+<!ENTITY % block
+    "p | %heading; | div | %lists; | %blocktext; | isindex |fieldset | table">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+   "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding img, object, applet, big, small,
+     font, or basefont -->
+
+<!ENTITY % pre.content
+   "(#PCDATA | a | %special.basic; | %fontstyle.basic; | %phrase.basic; |
+	   %inline.forms; | %misc.inline;)*">
+
+<!-- form uses %Flow; excluding form -->
+
+<!ENTITY % form.content "(#PCDATA | %block; | %inline; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form, form controls, iframe -->
+
+<!ENTITY % button.content
+   "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+      table | br | span | bdo | object | applet | img | map |
+      %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, body)>
+<!ATTLIST html
+  %i18n;
+  id          ID             #IMPLIED
+  xmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'
+  >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object|isindex)*">
+
+<!-- content model is %head.misc; combined with a single
+     title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+     ((title, %head.misc;, (base, %head.misc;)?) |
+      (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+  %i18n;
+  id          ID             #IMPLIED
+  profile     %URI;          #IMPLIED
+  >
+
+<!-- The title element is not considered part of the flow of text.
+       It should be displayed, for example as the page header or
+       window title. Exactly one title is required per document.
+    -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title 
+  %i18n;
+  id          ID             #IMPLIED
+  >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+  id          ID             #IMPLIED
+  href        %URI;          #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+  %i18n;
+  id          ID             #IMPLIED
+  http-equiv  CDATA          #IMPLIED
+  name        CDATA          #IMPLIED
+  content     CDATA          #REQUIRED
+  scheme      CDATA          #IMPLIED
+  >
+
+<!--
+  Relationship values can be used in principle:
+
+   a) for document specific toolbars/menus when used
+      with the link element in document head e.g.
+        start, contents, previous, next, index, end, help
+   b) to link to a separate style sheet (rel="stylesheet")
+   c) to make a link to a script (rel="script")
+   d) by stylesheets to control how collections of
+      html nodes are rendered into printed documents
+   e) to make a link to a printable version of this document
+      e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+  %attrs;
+  charset     %Charset;      #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  type        %ContentType;  #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  media       %MediaDesc;    #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+  %i18n;
+  id          ID             #IMPLIED
+  type        %ContentType;  #REQUIRED
+  media       %MediaDesc;    #IMPLIED
+  title       %Text;         #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+  id          ID             #IMPLIED
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #REQUIRED
+  language    CDATA          #IMPLIED
+  src         %URI;          #IMPLIED
+  defer       (defer)        #IMPLIED
+  xml:space   (preserve)     #FIXED 'preserve'
+  >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Flow;>
+<!ATTLIST noscript
+  %attrs;
+  >
+
+<!--======================= Frames =======================================-->
+
+<!-- inline subwindow -->
+
+<!ELEMENT iframe %Flow;>
+<!ATTLIST iframe
+  %coreattrs;
+  longdesc    %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  src         %URI;          #IMPLIED
+  frameborder (1|0)          "1"
+  marginwidth %Pixels;       #IMPLIED
+  marginheight %Pixels;      #IMPLIED
+  scrolling   (yes|no|auto)  "auto"
+  align       %ImgAlign;     #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  >
+
+<!-- alternate content container for non frame-based rendering -->
+
+<!ELEMENT noframes %Flow;>
+<!ATTLIST noframes
+  %attrs;
+  >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Flow;>
+<!ATTLIST body
+  %attrs;
+  onload      %Script;       #IMPLIED
+  onunload    %Script;       #IMPLIED
+  background  %URI;          #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  text        %Color;        #IMPLIED
+  link        %Color;        #IMPLIED
+  vlink       %Color;        #IMPLIED
+  alink       %Color;        #IMPLIED
+  >
+
+<!ELEMENT div %Flow;>  <!-- generic language/style container -->
+<!ATTLIST div
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Headings =========================================-->
+
+<!--
+  There are six levels of headings from h1 (the most important)
+  to h6 (the least important).
+-->
+
+<!ELEMENT h1  %Inline;>
+<!ATTLIST h1
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+  %attrs;
+  %TextAlign;
+  >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+  %attrs;
+  %TextAlign;
+  >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list bullet styles -->
+
+<!ENTITY % ULStyle "(disc|square|circle)">
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+  %attrs;
+  type        %ULStyle;     #IMPLIED
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- Ordered list numbering style
+
+    1   arabic numbers      1, 2, 3, ...
+    a   lower alpha         a, b, c, ...
+    A   upper alpha         A, B, C, ...
+    i   lower roman         i, ii, iii, ...
+    I   upper roman         I, II, III, ...
+
+    The style is applied to the sequence number which by default
+    is reset to 1 for the first list item in an ordered list.
+-->
+<!ENTITY % OLStyle "CDATA">
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+  %attrs;
+  type        %OLStyle;      #IMPLIED
+  compact     (compact)      #IMPLIED
+  start       %Number;       #IMPLIED
+  >
+
+<!-- single column list (DEPRECATED) --> 
+<!ELEMENT menu (li)+>
+<!ATTLIST menu
+  %attrs;
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- multiple column list (DEPRECATED) --> 
+<!ELEMENT dir (li)+>
+<!ATTLIST dir
+  %attrs;
+  compact     (compact)     #IMPLIED
+  >
+
+<!-- LIStyle is constrained to: "(%ULStyle;|%OLStyle;)" -->
+<!ENTITY % LIStyle "CDATA">
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+  %attrs;
+  type        %LIStyle;      #IMPLIED
+  value       %Number;       #IMPLIED
+  >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+  %attrs;
+  compact     (compact)      #IMPLIED
+  >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+  %attrs;
+  >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+  %attrs;
+  >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address (#PCDATA | %inline; | %misc.inline; | p)*>
+<!ATTLIST address
+  %attrs;
+  >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+  %attrs;
+  align       (left|center|right) #IMPLIED
+  noshade     (noshade)      #IMPLIED
+  size        %Pixels;       #IMPLIED
+  width       %Length;       #IMPLIED
+  >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding 
+        "img|object|applet|big|small|sub|sup|font|basefont" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+  %attrs;
+  width       %Number;      #IMPLIED
+  xml:space   (preserve)    #FIXED 'preserve'
+  >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Flow;>
+<!ATTLIST blockquote
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!--=================== Text alignment ===================================-->
+
+<!-- center content -->
+<!ELEMENT center %Flow;>
+<!ATTLIST center
+  %attrs;
+  >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+<!--
+  ins/del are allowed in block and inline content, but its
+  inappropriate to include block content within an ins element
+  occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+  %attrs;
+  cite        %URI;          #IMPLIED
+  datetime    %Datetime;     #IMPLIED
+  >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+  %attrs;
+  %focus;
+  charset     %Charset;      #IMPLIED
+  type        %ContentType;  #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  href        %URI;          #IMPLIED
+  hreflang    %LanguageCode; #IMPLIED
+  rel         %LinkTypes;    #IMPLIED
+  rev         %LinkTypes;    #IMPLIED
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+  %attrs;
+  >
+
+<!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+  %coreattrs;
+  %events;
+  lang        %LanguageCode; #IMPLIED
+  xml:lang    %LanguageCode; #IMPLIED
+  dir         (ltr|rtl)      #REQUIRED
+  >
+
+<!ELEMENT br EMPTY>   <!-- forced line break -->
+<!ATTLIST br
+  %coreattrs;
+  clear       (left|all|right|none) "none"
+  >
+
+<!ELEMENT em %Inline;>   <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;>   <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;>   <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;>   <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;>   <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;>  <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;>   <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;>   <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;>   <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;>   <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;>   <!-- inlined quote -->
+<!ATTLIST q
+  %attrs;
+  cite        %URI;          #IMPLIED
+  >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;>   <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;>   <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;>   <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;>   <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;>   <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!ELEMENT u %Inline;>   <!-- underline -->
+<!ATTLIST u %attrs;>
+
+<!ELEMENT s %Inline;>   <!-- strike-through -->
+<!ATTLIST s %attrs;>
+
+<!ELEMENT strike %Inline;>   <!-- strike-through -->
+<!ATTLIST strike %attrs;>
+
+<!ELEMENT basefont EMPTY>  <!-- base font size -->
+<!ATTLIST basefont
+  id          ID             #IMPLIED
+  size        CDATA          #REQUIRED
+  color       %Color;        #IMPLIED
+  face        CDATA          #IMPLIED
+  >
+
+<!ELEMENT font %Inline;> <!-- local change to font -->
+<!ATTLIST font
+  %coreattrs;
+  %i18n;
+  size        CDATA          #IMPLIED
+  color       %Color;        #IMPLIED
+  face        CDATA          #IMPLIED
+  >
+
+<!--==================== Object ======================================-->
+<!--
+  object is used to embed objects as part of HTML pages.
+  param elements should precede other content. Parameters
+  can also be expressed as attribute/value pairs on the
+  object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST object
+  %attrs;
+  declare     (declare)      #IMPLIED
+  classid     %URI;          #IMPLIED
+  codebase    %URI;          #IMPLIED
+  data        %URI;          #IMPLIED
+  type        %ContentType;  #IMPLIED
+  codetype    %ContentType;  #IMPLIED
+  archive     %UriList;      #IMPLIED
+  standby     %Text;         #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  border      %Pixels;       #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!--
+  param is used to supply a named property value.
+  In XML it would seem natural to follow RDF and support an
+  abbreviated syntax where the param elements are replaced
+  by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+  id          ID             #IMPLIED
+  name        CDATA          #REQUIRED
+  value       CDATA          #IMPLIED
+  valuetype   (data|ref|object) "data"
+  type        %ContentType;  #IMPLIED
+  >
+
+<!--=================== Java applet ==================================-->
+<!--
+  One of code or object attributes must be present.
+  Place param elements before other content.
+-->
+<!ELEMENT applet (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST applet
+  %coreattrs;
+  codebase    %URI;          #IMPLIED
+  archive     CDATA          #IMPLIED
+  code        CDATA          #IMPLIED
+  object      CDATA          #IMPLIED
+  alt         %Text;         #IMPLIED
+  name        NMTOKEN        #IMPLIED
+  width       %Length;       #REQUIRED
+  height      %Length;       #REQUIRED
+  align       %ImgAlign;     #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!--=================== Images ===========================================-->
+
+<!--
+   To avoid accessibility problems for people who aren't
+   able to see the image, you should provide a text
+   description using the alt and longdesc attributes.
+   In addition, avoid the use of server-side image maps.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+  %attrs;
+  src         %URI;          #REQUIRED
+  alt         %Text;         #REQUIRED
+  name        NMTOKEN        #IMPLIED
+  longdesc    %URI;          #IMPLIED
+  height      %Length;       #IMPLIED
+  width       %Length;       #IMPLIED
+  usemap      %URI;          #IMPLIED
+  ismap       (ismap)        #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  border      %Length;       #IMPLIED
+  hspace      %Pixels;       #IMPLIED
+  vspace      %Pixels;       #IMPLIED
+  >
+
+<!-- usemap points to a map element which may be in this document
+  or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+     separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+  %i18n;
+  %events;
+  id          ID             #REQUIRED
+  class       CDATA          #IMPLIED
+  style       %StyleSheet;   #IMPLIED
+  title       %Text;         #IMPLIED
+  name        CDATA          #IMPLIED
+  >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+  %attrs;
+  %focus;
+  shape       %Shape;        "rect"
+  coords      %Coords;       #IMPLIED
+  href        %URI;          #IMPLIED
+  nohref      (nohref)       #IMPLIED
+  alt         %Text;         #REQUIRED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--================ Forms ===============================================-->
+
+<!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+  %attrs;
+  action      %URI;          #REQUIRED
+  method      (get|post)     "get"
+  name        NMTOKEN        #IMPLIED
+  enctype     %ContentType;  "application/x-www-form-urlencoded"
+  onsubmit    %Script;       #IMPLIED
+  onreset     %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  accept-charset %Charsets;  #IMPLIED
+  target      %FrameTarget;  #IMPLIED
+  >
+
+<!--
+  Each label must not contain more than ONE field
+  Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+  %attrs;
+  for         IDREF          #IMPLIED
+  accesskey   %Character;    #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  >
+
+<!ENTITY % InputType
+  "(text | password | checkbox |
+    radio | submit | reset |
+    file | hidden | image | button)"
+   >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY>     <!-- form control -->
+<!ATTLIST input
+  %attrs;
+  %focus;
+  type        %InputType;    "text"
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  checked     (checked)      #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  size        CDATA          #IMPLIED
+  maxlength   %Number;       #IMPLIED
+  src         %URI;          #IMPLIED
+  alt         CDATA          #IMPLIED
+  usemap      %URI;          #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  accept      %ContentTypes; #IMPLIED
+  align       %ImgAlign;     #IMPLIED
+  >
+
+<!ELEMENT select (optgroup|option)+>  <!-- option selector -->
+<!ATTLIST select
+  %attrs;
+  name        CDATA          #IMPLIED
+  size        %Number;       #IMPLIED
+  multiple    (multiple)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  tabindex    %Number;       #IMPLIED
+  onfocus     %Script;       #IMPLIED
+  onblur      %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!ELEMENT optgroup (option)+>   <!-- option group -->
+<!ATTLIST optgroup
+  %attrs;
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #REQUIRED
+  >
+
+<!ELEMENT option (#PCDATA)>     <!-- selectable choice -->
+<!ATTLIST option
+  %attrs;
+  selected    (selected)     #IMPLIED
+  disabled    (disabled)     #IMPLIED
+  label       %Text;         #IMPLIED
+  value       CDATA          #IMPLIED
+  >
+
+<!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->
+<!ATTLIST textarea
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  rows        %Number;       #REQUIRED
+  cols        %Number;       #REQUIRED
+  disabled    (disabled)     #IMPLIED
+  readonly    (readonly)     #IMPLIED
+  onselect    %Script;       #IMPLIED
+  onchange    %Script;       #IMPLIED
+  >
+
+<!--
+  The fieldset element is used to group form fields.
+  Only one legend element should occur in the content
+  and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+  %attrs;
+  >
+
+<!ENTITY % LAlign "(top|bottom|left|right)">
+
+<!ELEMENT legend %Inline;>     <!-- fieldset label -->
+<!ATTLIST legend
+  %attrs;
+  accesskey   %Character;    #IMPLIED
+  align       %LAlign;       #IMPLIED
+  >
+
+<!--
+ Content is %Flow; excluding a, form, form controls, iframe
+--> 
+<!ELEMENT button %button.content;>  <!-- push button -->
+<!ATTLIST button
+  %attrs;
+  %focus;
+  name        CDATA          #IMPLIED
+  value       CDATA          #IMPLIED
+  type        (button|submit|reset) "submit"
+  disabled    (disabled)     #IMPLIED
+  >
+
+<!-- single-line text input control (DEPRECATED) -->
+<!ELEMENT isindex EMPTY>
+<!ATTLIST isindex
+  %coreattrs;
+  %i18n;
+  prompt      %Text;         #IMPLIED
+  >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+     "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+  
+<!-- horizontal placement of table relative to document -->
+<!ENTITY % TAlign "(left|center|right)">
+
+<!-- horizontal alignment attributes for cell contents
+
+  char        alignment char, e.g. char=':'
+  charoff     offset for alignment char
+-->
+<!ENTITY % cellhalign
+  "align      (left|center|right|justify|char) #IMPLIED
+   char       %Character;    #IMPLIED
+   charoff    %Length;       #IMPLIED"
+  >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+  "valign     (top|middle|bottom|baseline) #IMPLIED"
+  >
+
+<!ELEMENT table
+     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption  %Inline;>
+<!ELEMENT thead    (tr)+>
+<!ELEMENT tfoot    (tr)+>
+<!ELEMENT tbody    (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col      EMPTY>
+<!ELEMENT tr       (th|td)+>
+<!ELEMENT th       %Flow;>
+<!ELEMENT td       %Flow;>
+
+<!ATTLIST table
+  %attrs;
+  summary     %Text;         #IMPLIED
+  width       %Length;       #IMPLIED
+  border      %Pixels;       #IMPLIED
+  frame       %TFrame;       #IMPLIED
+  rules       %TRules;       #IMPLIED
+  cellspacing %Length;       #IMPLIED
+  cellpadding %Length;       #IMPLIED
+  align       %TAlign;       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  >
+
+<!ENTITY % CAlign "(top|bottom|left|right)">
+
+<!ATTLIST caption
+  %attrs;
+  align       %CAlign;       #IMPLIED
+  >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+     width=64        width in screen pixels
+     width=0.5*      relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+  %attrs;
+  span        %Number;       "1"
+  width       %MultiLength;  #IMPLIED
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!--
+    Use thead to duplicate headers when breaking table
+    across page boundaries, or for static headers when
+    tbody sections are rendered in scrolling panel.
+
+    Use tfoot to duplicate footers when breaking table
+    across page boundaries, or for static footers when
+    tbody sections are rendered in scrolling panel.
+
+    Use multiple tbody sections when rules are needed
+    between groups of table rows.
+-->
+<!ATTLIST thead
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tfoot
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tbody
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  >
+
+<!ATTLIST tr
+  %attrs;
+  %cellhalign;
+  %cellvalign;
+  bgcolor     %Color;        #IMPLIED
+  >
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  nowrap      (nowrap)       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  width       %Length;       #IMPLIED
+  height      %Length;       #IMPLIED
+  >
+
+<!ATTLIST td
+  %attrs;
+  abbr        %Text;         #IMPLIED
+  axis        CDATA          #IMPLIED
+  headers     IDREFS         #IMPLIED
+  scope       %Scope;        #IMPLIED
+  rowspan     %Number;       "1"
+  colspan     %Number;       "1"
+  %cellhalign;
+  %cellvalign;
+  nowrap      (nowrap)       #IMPLIED
+  bgcolor     %Color;        #IMPLIED
+  width       %Length;       #IMPLIED
+  height      %Length;       #IMPLIED
+  >
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/css2xslfo.git



More information about the pkg-java-commits mailing list