Introduction to XSLT 2.0

(for XSLT 1.0 users)

Norman Walsh

Sun Microsystems, Inc.


Norman Walsh…

  • Elected member of the W3C Technical Architecture Group; chair of the XML Processing Model Working Group; co-chair of the XML Core WG; member of the XSL WG. Joint editor of several XSL/XML Query Specs.

  • Chair of the OASIS DocBook Technical Committee, member of the Entity Resolution TC and the RELAX NG TC.

  • Specification Lead for JSR 206: Java API for XML Processing

Background Material


Seven core specifications; most recent Working Drafts published in June, 2006. We've reached “Candidate Recommendation” stage.

  • XQuery 1.0 and XPath 2.0 Data Model

  • XQuery 1.0 and XPath 2.0 Functions and Operators

  • XQuery 1.0 and XPath 2.0 Formal Semantics

  • XML Path Language (XPath) 2.0

  • XSL Transformations (XSLT) Version 2.0

  • XSLT 2.0 and XQuery 1.0 Serialization

  • XQuery 1.0: An XML Query Language

  • Plus XQueryX, Updates, Full Text, …

Fitting the Pieces Together

The family of XSL and XML Query specifications are closely related. Many of the specifications depend on each other.

Specification dependency diagram

A subset of the specifications and some of their relationships.

Data Model

  • XPath 2.0 has nodes and typed values. Colloquially, there are three kinds of things: nodes, simple or atomic values, and items. An item is either a node or an atomic value.

  • XPath 2.0 has sequences where XPath 1.0 had node sets:

    • Sequences can be in arbitrary order

    • Sequences can contain duplicates

    • Sequences can be heterogenous

Functions and Operators

Functions. Lots of functions.

  • String and numeric functions

  • Date, time and duration functions

  • QName and namespace functions

  • Regex functions

  • Sequence manipulation functions

  • Casting and type-related functions

XPath 2.0

  • XSLT 2.0 uses XPath 2.0

  • This tutorial doesn't really cover XPath 2.0

  • But naturally, we'll be using it all over the place.


  • Serialization is a separate specification.

  • XSLT users can influence serialization with xsl:output attributes.

Language Semantics

  • XPath 2.0 has both static and dynamic semantics.

  • Support for static analysis is optional.

  • The Formal Semantics specification describes the static semantics of XPath.

  • The XPath 2.0 specification describes the dynamic semantics of XPath.

  • The XSLT 2.0 specification describes all of the semantics of XSLT.

A Running Example

Everyone’s third favorite toy example, the recipe collection. Our recipe list is defined by the schema described on the following slides.

Do You Need a Schema?

  • Do you need to perform W3C XML Schema validation to use XPath 2.0/XSLT 2.0?


  • Do some features require validation?


  • Can I use some other validation technology?

    Maybe. Yes, in principle, but not yet in practice.


<xs:schema xmlns:xs=""

  <xs:complexType name="RecipeList">
      <xs:element ref="r:recipe" minOccurs="1" maxOccurs="unbounded"/>

  <xs:element name="recipeList" type="r:RecipeList"/>


<xs:simpleType name="Servings">
  <xs:restriction base="xs:integer">
    <xs:minInclusive value="1"/>
    <xs:maxInclusive value="12"/>


<xs:complexType name="Recipe">
    <xs:element ref="r:name"/>
    <xs:element ref="r:source" minOccurs="0" maxOccurs="1"/>
    <xs:element ref="r:description" minOccurs="0" maxOccurs="1"/>
    <xs:element ref="r:ingredientList" minOccurs="1" maxOccurs="unbounded"/>
    <xs:element ref="r:preparation"/>
  <xs:attribute name="servings" type="r:Servings"/>
  <xs:attribute name="time" type="xs:duration"/>
  <xs:attribute name="calories" type="xs:positiveInteger"/>

Recipe Subtypes

<xs:complexType name="FoodRecipe">
    <xs:extension base="r:Recipe"/>

<xs:complexType name="CandyRecipe">
    <xs:extension base="r:FoodRecipe">
      <xs:attribute name="sugarfree" type="xs:boolean"/>

<xs:complexType name="DrinkRecipe">
    <xs:extension base="r:Recipe">
      <xs:attribute name="virgin" type="xs:boolean"/>

Recipe Elements

<xs:element name="recipe" type="r:Recipe" abstract="true"/>
<xs:element name="beverage" type="r:DrinkRecipe" substitutionGroup="r:recipe"/>
<xs:element name="appetizer" type="r:FoodRecipe" substitutionGroup="r:recipe"/>
<xs:element name="entrée" type="r:FoodRecipe" substitutionGroup="r:recipe"/>
<xs:element name="sidedish" type="r:FoodRecipe" substitutionGroup="r:recipe"/>
<xs:element name="dessert" type="r:FoodRecipe" substitutionGroup="r:recipe"/>
<xs:element name="candy" type="r:CandyRecipe" substitutionGroup="r:recipe"/>

Prose Types

<xs:complexType name="Prose">
  <xs:choice minOccurs="1" maxOccurs="unbounded">
    <xs:element ref="r:p"/>
    <xs:element ref="r:list"/>

<xs:complexType name="Para" mixed="true">
  <xs:choice minOccurs="0" maxOccurs="unbounded">
    <xs:element name="em" type="xs:string"/>

<xs:complexType name="NumberedList">
    <xs:element name="item" minOccurs="1" maxOccurs="unbounded" type="r:Prose"/>

Prose Elements

<xs:element name="description" type="r:Prose"/>
<xs:element name="preparation" type="r:Prose"/>
<xs:element name="p" type="r:Para"/>
<xs:element name="list" type="r:NumberedList"/>
<xs:element name="title" type="r:Para"/>
<xs:element name="name" type="r:Para"/>
<xs:element name="source" type="r:Para" nillable="true"/>


<xs:complexType name="IngredientList">
    <xs:element ref="r:title" minOccurs='0' maxOccurs='1'/>
    <xs:element ref="r:ingredient" minOccurs='1' maxOccurs='unbounded'/>

<xs:element name="ingredientList" type="r:IngredientList"/>


<xs:complexType name="Ingredient">
    <xs:element name="quantity" minOccurs="0" maxOccurs="1">
            <xs:extension base="xs:double">
              <xs:attribute name="units"/> 
    <xs:element name="name" type="xs:string"/>

<xs:element name="ingredient" type="r:Ingredient"/>

Schemas and Types

Schema Support

  • Many of the W3C XML Schema simple types are always available.

  • In order to refer to additional types, you must import the schema that defines them:


    You must specify at least the namespace or the schema location, if not both.


XSLT 2.0 allows you to declare:

  • The type of variables.

  • The return type of templates.

  • The type of sequences (constructed with xsl:sequence)

  • The return type of (user-declared) functions.

  • Both the type and required type of parameters.

Declaring Types

The “as” attribute is used to declare types.

<xsl:variable name="i" select="1"/>

is the integer value 1.

<xsl:variable name="fp" select="1"

is the double value 1.0.

Declaring Types (Continued)

<xsl:variable name="date"

is the string “2003-11-20”.

<xsl:variable name="date"

is the date November 20, 2003.

<xsl:variable name="date" as="xs:date"

is an error.

Declaring element types

<xsl:variable name="rtf">
<xsl:variable name="elemlist"
<xsl:variable name="elemlist"
              as="element(r:recipe)+"> or contruct Recipes...

Constructor Functions vs. “as”

  • The constructor functions, xs:type(string), attempt to construct the typed value from the lexical form provided.

  • The as attribute asserts that the value must have the required type. It performs simple type promotions but doesn’t, for example, implicitly cast as the requested type.

Type Errors

If a type cannot be cast to another type, attmpting the cast will generate a type error. Some (perhaps many) of the things you’re used to doing in XPath 1.0 will generate type errors in XPath 2.0:

  • Math operations on strings (@attr + 1 if attr is validated as a string type).

  • Invalid lexical representations (“12/08/2003” is not a valid lexical form for dates, use “2003-12-08” instead).

  • Incompatible casts (100 cast as r:Servings).

Implicit Casting

  • From subtypes to supertypes (xs:NMTOKEN where xs:string is required).

  • Between numeric types (xs:decimal to xs:double, etc.)

  • Computing effective boolean values (“NaN” to false())

  • From “untyped” values



The <xsl:sequence> instruction is used to construct sequences of nodes or atomic values (or both).

  • <xsl:sequence select='(1,2,3,4)'/>

    returns a sequence of integers.

  • <xsl:sequence select='(1,2,3,4)' as="xs:double"/>

    returns a sequence of doubles.

Sequences of elements

Suppose we want to compute the prices of a list of products:

<xsl:variable name="products">
  <product cost="90" price="100"/>
  <product cost="100"/>
  <product cost="100" price="90"/>

If a product has a price, then that's its price, otherwise, its price is 150% of its cost.

Sequence example

The following code defines $prices to contain a sequence of decimal values computed from the prices of each product.

<xsl:variable name="prices">
  <xsl:for-each select="$products/product">
      <xsl:when test="@price">
        <xsl:sequence select="xs:decimal(@price)"/>
        <xsl:sequence select="xs:decimal(@cost) * 1.5"/>

Sequence example (alternate)

So does this:

     select="for $p in products return
               if ($p/@price) 
               then xs:decimal($p/@price)
               else xs:decimal($p/@cost) * 1.5"/>

Sequences considered

Why is the former better? Because if you run the latter stylesheet through a processor, you’ll get:

<xsl:value-of select="for $p in products return if ($p/@price) then xs:decimal($p/@price) else (xs:decimal($p/@cost) * 1.5)"/>

Which I find a little hard to read. My recommendation: use XSLT whenever you can.

Values, Copies, and Sequences

What’s the difference between xsl:value-of, xsl:copy-of, and xsl:sequence?

  • xsl:value-of always creates a text node.

  • xsl:copy-of always creates a copy.

  • xsl:sequence returns the nodes selected, subject possibly to atomization. Sequences can be extended with xsl:sequence.

Sorting, Collation, and Grouping

Sorting and Collation

  • XPath 2.0 and XSLT 2.0 have collations

  • Collations determine how sorting and collation work

  • Collations are identified by URI

  • All processors support “Unicode code-point collation”


Grouping in XSLT 1.0 is hard. XSLT 2.0 provides a new, flexible grouping instruction for grouping…

  • on a specific key

  • by transitions at the start of each group

  • by transitions at the end of each group

  • by adjacent key values

Groups can also be sorted.

Grouping By Key (Data)

Group the following data by country:

  <city name="Milano"  country="Italia"/>
  <city name="Paris"   country="France"/>
  <city name="München" country="Deutschland"/>
  <city name="Lyon"    country="France"/>
  <city name="Venezia" country="Italia"/>

Grouping By Key (Code)

<xsl:for-each-group select="cities/city"
   <td><xsl:value-of select="position()"/></td>
   <td><xsl:value-of select="@country"/></td>
    <xsl:value-of select="current-group()/@name"
                  separator=", "/>

Grouping By Key (Results)

   <td>Milano, Venezia</td>
   <td>Paris, Lyon</td>

Grouping By Starting Value (Data)

Group the following data so that the implicit divisions created by each h1 are explicit:

  <p>XSLT is used to write stylesheets.</p>
  <p>XQuery is used to query XML databases.</p>
  <h1>What is a stylesheet?</h1>
  <p>A stylesheet is an XML document used to define a transformation.</p>
  <p>Stylesheets may be written in XSLT.</p>
  <p>XSLT 2.0 introduces new grouping constructs.</p>

Grouping By Starting Value (Code)

<xsl:for-each-group select="*"

Grouping By Starting Value (Results)

   <p>XSLT is used to write stylesheets.</p>
   <p>XQuery is used to query XML databases.</p>
   <h1>What is a stylesheet?</h1>
   <p>A stylesheet is an XML document used to define a transformation.</p>
   <p>Stylesheets may be written in XSLT.</p>
   <p>XSLT 2.0 introduces new grouping constructs.</p>

Grouping By Ending Value (Data)

Group the following data so that continued pages are contained in a pageset:

  <page continued="yes">Some text</page>
  <page continued="yes">More text</page>    
  <page>Yet more text</page>
  <page continued="yes">Some words</page>
  <page continued="yes">More words</page>    
  <page>Yet more words</page>        

Grouping By Ending Value (Code)

<xsl:for-each-group select="*" 
    <xsl:for-each select="current-group()">
      <page><xsl:value-of select="."/></page>

Grouping By Ending Value (Results)

      <page>Some text</page>
      <page>More text</page>
      <page>Yet more text</page>
      <page>Some words</page>
      <page>More words</page>
      <page>Yet more words</page>

Grouping By Adjacent Key Values (Data)

Group the following data so that lists do not occur inside paragraphs:

<p>Do <em>not</em>:
    <li>eat, or</li>
    <li>use your mobile telephone</li>
while you are in the cinema.</p>

Grouping By Adjacent Key Values (Code)

<xsl:for-each-group select="node()" 
     group-adjacent="self::ul or self::ol">
    <xsl:when test="current-grouping-key()">
      <xsl:copy-of select="current-group()"/>  

Grouping By Adjacent Key Values (Results)

<p>Do <em>not</em>:
   <li>eat, or</li>
   <li>use your mobile telephone</li>
   while you are in the cinema.

Regular Expressions

Regular Expression Functions

There are three regular-expression functions that operate on strings:

  • matches() tests if a regular expression matches a string.

  • replace() uses regular expressions to replace portions of a string.

  • tokenize() returns a sequence of strings formed by breaking a supplied input string at any separator that matches a given regular expression.

Regular Expression Instructions

The xsl:analyze-string instruction uses regular expressions to apply markup to a string.

<xsl:analyze-string select="…" regex="…">

The regex-group() function allows you to look back at matching substrings.

Regular Expression Example

These instructions transform dates of the form “12/8/2003” into ISO 8601 standard form: “2003-12-08” using the regular expression instructions.

<xsl:analyze-string select="$date" 
    <xsl:number value="regex-group(3)"
    <xsl:text>-</xsl:text> ...

Note that the curly braces are doubled in the regular expression. The regex attribute is an attribute value template, so to get a single “{” in the attribute value, you must use “{{” in the stylesheet.

Generating XML (Data)

Let's combine what we've learned about regular expressions and grouping to solve a real (looking) problem.


Generating XML (Result)

<date>May 21, 2006</date>

Generating XML Step 1

  • Use xsl:analyze-string to find the dates.

Generating XML Step 2

  • Use format-date to fix the dates.

Generating XML Step 3

  • Use xsl:analyze-string to fix the end-of-group marker.

Generating XML Step 4

  • Use xsl:for-each-group to group by starting item.

Generating XML Step 5

  • Use xsl:for-each-group to group by ending item.

Generating XML Step 6

  • Use nested xsl:analyze-strings to break on newlines and add number markup.

Extending XSLT

XSLT Functions

At the instruction level, XSLT 1.0 and 2.0 provide mechanisms for calling named templates. They also provide mechanisms for calling user-defined extension functions. What’s new is the ability to declare user-defined functions in XSLT.

Function Example

<xsl:function name="str:reverse" as="xs:string">
  <xsl:param name="sentence" as="xs:string"/>
  <xsl:value-of separator=" ">
      <xsl:when test="contains($sentence, ' ')">
	<xsl:sequence select="str:reverse(substring-after($sentence, ' '))"/>
	<xsl:sequence select="substring-before($sentence, ' ')"/>
	<xsl:sequence select="$sentence"/>

This can be called from XPath: select="str:reverse('DOG BITES MAN')".

Extension Functions

  • Extend the function library.

  • Most XSLT 1.0 processors support extension functions.

  • Saxon 8.x supports extension functions.

Great Circle Distance

The distance between two points on the earth is Δσ × Rl where:

  • Rl is the radius at the appropriate geocentric latitude:

    Radius at a given geocentric latitude

    where a is the equatorial radius and b is the polar radius.

  • And Δσ is the angular distance between the two points:

    Great circle distance

    where ϕ1, λ1; ϕ2, λ2 are the latitude and longitude of the two points, respectively, and Δλ is the longitudinal difference between the points.

It is technically possible to compute this distance in XSLT, but that's perhaps not the easiest way.

Great Circle Distance Extension Function

I'd do it in Java:

package com.nwalsh.xslt.GreatCircle;

public class Distance {

    public static double distance(double lat1, double lon1, double lat2, double lon2) {
        // Math goes here

Extension Elements

  • Extend the instruction library.

  • Most XSLT 1.0 processors support extension elements.

  • Saxon 8.x supports extension elements.

Using use-when

  • Conditional inclusion of XSL elements.

  • Very constrained evaluation context.

  • Essentially: compile time expressions.

  • Use-when effectively replaces function-available and element-available.

Stylesheet Modularity

Stylesheet Modularity

  • Stylesheets can be included or imported:

  • <xsl:include> provides source code modularity.

  • <xsl:import> provides logical modularity.

  • <xsl:apply-imports> allows an importing stylesheet to apply templates from an imported stylesheet.

  • What’s new is <xsl:next-match>

Next Match

  • If several templates match, they are sorted by priority

  • The highest priority template is executed, the others are not

  • <xsl:next-match> allows a template to evaluate the next highest priority template.

  • This is independent of stylesheet import precedence

Serializing the Results

Result Documents

  • The <xsl:result-document> instruction can create additional result documents.

  • Provides support for validation.

  • Gotcha: you can't ouput a result document inside a result document.

Character Maps

Character maps give you greater control over serialization. They map a Unicode character to any string in the serialized document.

  • For XML and HTML output methods, the resulting character stream does not have to be well-formed.

  • The mapping occurs only at serialization: it is not present in result tree fragments.

  • This facility can be used instead of “disabled output escaping” in most cases.

Creating Entities

Suppose you want to construct an XHTML document that uses &nbsp; for non-breaking spaces, &eacute; for “é”, etc.

<xsl:output method="xml"
            doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"

<xsl:character-map name="example-map">
  <xsl:output-character character="&#233;"
  <xsl:output-character character="&#160;"

Avoiding Disable Output Escaping

Suppose you want to construct a JSP page that contains:

<jsp:setProperty name="user" property="id"
                 value="'<%= "id" + idValue %>'"/>

Pick some otherwise unused Unicode characters to represent the character sequences that aren’t valid XML. For example, “«” for “<%=”, “»” for “%>”, and “·” for the explicit double quotes:

<jsp:setProperty name="user" property="id"
                 value='« ·id· + idValue »'/>

Avoiding D-O-E (Continued)

Construct a character map that turns those characters back into the invalid strings:

<xsl:character-map name="jsp">
  <xsl:output-character character="«"
  <xsl:output-character character="»"
  <xsl:output-character character="·"

Libraries and Multi-Stage Pipelines

Writing Library Modules

  • Break libraries into useful units.

  • Break along schema-dependent boundaries.

  • Document the functions.

  • Provide unit tests.

Multi-Stage Pipelines

  • Staging in XSLT:

    • Modes

    • Explicit calls

    • Priority and next-match

  • Staging with a framework (like XProc :-)

Multi-Stage Pipelines with Modes

<xsl:template match="/">
  <xsl:apply-templates select="." mode="mode1"/>

<xsl:template match="/" mode="mode1">
  <xsl:apply-templates select="." mode="mode2"/>

<xsl:template match="/" mode="mode2">

Multi-Stage Pipelines with Modes (Continued)

  • Appears “ordinary” to users

  • Can be easily extended

  • Requires more familiarity to write extensions

Multi-Stage Pipelines with Explicit Calls

<xsl:template name="start-here">
  <xsl:call-template name="stage1"/>

<xsl:template name="stage1">
  <xsl:call-template name="stage2"/>

<xsl:template name="stage2">

Multi-Stage Pipelines with Explicit Calls (Continued)

  • Users must supply the starting template

  • Extension requires replacing existing templates

  • Requires more familiarity to write extensions

Multi-Stage Pipelines with Priority and Next Match

<xsl:template match="/" priority="10000">

<xsl:template match="/" priority="9000">

<xsl:template match="/" priority="8000">

Multi-Stage Pipelines with Priority and Next Match (Continued)

  • Appears “ordinary” to users (but import precedence trumps priority)

  • Can be extended

  • Requires more familiarity to write extensions

Multi-Stage Pipeline Considerations

  • Do you think in terms of push or pull?

  • Do you need to apply the stages to lots of different elements?

  • Do you care about user customization or extension?

  • Are you going to xsl:include the base, or xsl:import it?

Pipeline Frameworks

  • Allow you to write entirely separate stylesheets

  • Support modularity at a higher level

  • But do you have one?

  • Watch this space!



  • An XSLT 1.0 processor handles 2.0 stylesheets in forwards compatibility mode

  • An XSLT 2.0 processor handles 1.0 stylesheets:

    • As a 1.0 processor, or

    • in backwards compatibility mode

  • A mixed-mode stylesheet uses the appropriate mode.

Although the goal is that a 2.0 processor running a 1.0 stylesheet in backwards compatibility mode should produce the same results as a 1.0 processor, this is not guaranteed to be the case for all stylesheets.


Q: Counting Elements

What does this stylesheet produce?

<xsl:stylesheet xmlns:xsl=""

  <xsl:output method="text"/>

  <xsl:param name="doc">

  <xsl:template match="/">
    <xsl:text>There are </xsl:text>
    <xsl:value-of select="count($doc//p)"/>
    <xsl:text> paras.&#10;</xsl:text>


A: Counting Elements

It produces:

There are 0 paras.


E: Counting Elements

Because “doc” is in the default namespace and “//p” matches elements in no namespace.

This would work:

<xsl:stylesheet xmlns:xsl=""

So would this:

<xsl:value-of xmlns:x=""

Q: Matching Elements

Write an XSLT stylesheet function that returns true if two elements “match”:

f:element-matches($srcElem, $targetElem)

Two elements match if:

  1. They have the same name (use the node-name function).

  2. Every attribute on $srcElem that is not in a namespace is also on $targetElem and has the same value.

  3. Namespace qualified attributes on $srcElem are ignored.

  4. Extra attributes are allowed on $targetElem.

A: Matching Elements

Here’s one approach:

<xsl:function name="f:element-matches" as="xs:boolean">
  <xsl:param name="srcElement" as="element()"/>
  <xsl:param name="targetElem" as="element()"/>
    <xsl:when test="node-name($srcElement) = node-name($targetElem)">
      <xsl:variable name="attrMatch">
        <xsl:for-each select="$srcElement/@*[namespace-uri(.) = '']">
          <xsl:variable name="aname" select="local-name(.)"/>
          <xsl:variable name="attr" select="$targetElem/@*[local-name(.) = $aname]"/>
            <xsl:when test="$attr = .">1</xsl:when>
      <xsl:value-of select="not(contains($attrMatch, '0'))"/>
    <xsl:otherwise><xsl:value-of select="false()"/></xsl:otherwise>

Q: Matching Elements 2

That function can be replaced by a single XPath 2.0 expression. What is it?

A: Matching Elements 2

(node-name($srcElem) = node-name($targetElem))
and (every $i in $srcElem/@*[namespace-uri(.) = '']
     for $j in $targetElem/@*[local-name(.) = local-name($i)]
     return $i = $j

Closing Thoughts

Is it Worth It?

XPath 2.0 and XSLT 2.0 are larger and more complex than their predecessors. Schema support and “strong” typing will catch errors, but they will also force more explicit casting.

Does it make sense to start developing for 2.0?

Yes, I think so. Grouping, regular expressions, user-defined functions, character maps, standardized access to result documents and multiple result documents are all going to make stylesheet writing easier.


Many of the examples in this tutorial are taken directly from the XSLT 2.0 Specification.

Michael Kay generously shared a number of examples that he uses in his own tutorials.

David Carlisle suggested the XPath replacement for matching elements.


XQuery 1.0 and XPath 2.0 Data Model, Mary Fernández, Ashok Malhotra, Jonathan Marsh, et. al., editors. World Wide Web Consortium. 2006.

XQuery 1.0 and XPath 2.0 Functions and Operators, Ashok Malhotra, Jim Melton, and Norman Walsh, editors. World Wide Web Consortium. 2006.

XQuery 1.0 and XPath 2.0 Formal Semantics, Denise Draper, Peter Frankhauser, Mary Fernández, et. al., editors. World Wide Web Consortium. 2006.

XML Path Language (XPath) 2.0, Anders Berglund, Scott Boag, Don Chamberlin, et. al., editors. World Wide Web Consortium. 2006.

XQuery 1.0: An XML Query Language, Scott Boag, Don Chamberlin, Mary Fernández, et. al., editors. World Wide Web Consortium. 2006.

XSL Transformations (XSLT) Version 2.0, Michael Kay, editor. World Wide Web Consortium. 2006.

XSLT 2.0 and XQuery 1.0 Serialization, Michael Kay, Norman Walsh, and Henry Zongaro, editors. World Wide Web Consortium. 2006.

Typing in Transformations, Jeni Tennison. Extreme Markup Languages. 2003.

Additional References