Transforming arbitrary XML to XHTML

exercise No. 59

Q:

XSL allows for generic transformation of arbitrary XML code. Consider the following example document instance:

<book>
    <title>XML Introduction</title>
    <chapter id="intro" xml:lang="de">
        <title>Some examples</title>
        <para>Let's start with an <xref linkend="intro"/>.</para>
    </chapter>
</book>

Write an XSL style sheet visualizing such arbitrary well-formed XML in XHTML:

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Visualizing a &lt;book&gt; instance.</title>
    </head>
    <body><pre>&lt;<span style="color: Aqua;">book</span> &gt;
    &lt;<span style="color: Aqua;">title</span> &gt;XML Introduction&lt;/<span style="color: Blue;">title</span>&gt;
    &lt;<span style="color: Aqua;">chapter</span> <span style="color: Red;">id</span>="intro" ...
        &lt;<span style="color: Aqua;">title</span> &gt;Some examples&lt;/<span style="color: Blue;">title</span>&gt;
        &lt;<span style="color: Aqua;">para</span> &gt;Let's start with an &lt;<span style="color: Aqua;">xref</span> ...
    &lt;/<span style="color: Blue;">chapter</span>&gt;
&lt;/<span style="color: Blue;">book</span>&gt;</pre></body>
</html>

This represents a web browser capable visualization of arbitrary XML document instances:

Figure 894. Visualizing arbitrary XML input data. Create comment in forum
Visualizing arbitrary XML input data.

Tip

Notice the marked empty <xref linkend="..."/> element not being represented as (traditional) HTML style <xref linkend="..."></xref>. Notice as well the tab's Visualizing a <book> instance. descriptive title.

The following XPath based constructs are useful:

Iteration over all element sub nodes

<xsl:template match="*">

XSL identity transformation

The following construct allows for a generic depth-first traversal of arbitrary XML input documents.

<xsl:template match="/ | @* | node()">
   <xsl:copy>
       <xsl:apply-templates select="@* | node()" />
   </xsl:copy>
</xsl:template>
Matching mixed content / arbitrary nodes

<xsl:apply-templates select="node()|text()"/>

Iteration over the set of all attributes

<xsl:template match="@*">

Retrieving the name of an element or attribute

<xsl:value-of select="name(...)">

Testing for the existence of sub nodes

node()

Use the HTML <pre> tag controlling newlines and indentation.

A:

The following code uses two templates <xsl:template match="*[node()]"> and <xsl:template match="*"> separating non-empty from empty element nodes using their matching precedences. You may however start from a single template as well using <xsl:choose><xsl:when test="node()">... conditionals inside.

    <xsl:output method="xhtml" indent="no"/>

    <xsl:template match="/">
        <html xmlns="http://www.w3.org/1999/xhtml">
            <head>
                <title>Visualizing a &lt;<xsl:value-of select="name(*)"/>&gt; instance.</title>
            </head>
            <body>
                <pre>
                    <xsl:apply-templates select="node()|text()"/>
                </pre>
            </body>
        </html>
    </xsl:template>

    <!-- Matching elements containing text or other nodes -->
    <xsl:template match="*[node()]">
        <xsl:text>&lt;</xsl:text>
        <span style="color: Aqua;">
            <xsl:value-of select="name(.)"/>
        </span>
        <xsl:apply-templates select="@*"/>

        <xsl:text>&gt;</xsl:text>
        <xsl:apply-templates select="node()|text()"/>

        <xsl:text>&lt;/</xsl:text>
        <span style="color: Blue;">
            <xsl:value-of select="name(.)"/>
        </span>
        <xsl:text>&gt;</xsl:text>
    </xsl:template>

    <!-- Matching empty elements -->
    <xsl:template match="*">
        <xsl:text>&lt;</xsl:text>
        <span style="color: Aqua;">
            <xsl:value-of select="name(.)"/>
        </span>
        <xsl:apply-templates select="@*"/>

        <span style="color: Aqua;">
            <xsl:text>/&gt;</xsl:text>
        </span>
    </xsl:template>

    <!-- Matching attributes -->
    <xsl:template match="@*">
        <xsl:text> </xsl:text>
        <span style="color: Red;">
            <xsl:value-of select="name(.)"/>
        </span>
        <xsl:text>="</xsl:text>
        <xsl:value-of select="."/>
        <xsl:text>"</xsl:text>
    </xsl:template>