Book HomeXSLSearch this book

3.5. The XPath View of an XML Document

Before we leave the subject of XPath, we'll look at a stylesheet that generates a pictorial view of a document. The stylesheet has to distinguish between all of the different XPath node types, including any rarely used namespace nodes.

3.5.1. Output View

Figure 3-1 shows the output of our stylesheet. In this graphical view of the document, the nested HTML tables illustrate which nodes are contained inside of others, as well as the sequence in which these nodes occur in the original document. In the section of the document visible in Figure 3-1, the root of the document contains, in order, two processing instructions and two comments, followed by the <sonnet> element. The <sonnet> element, in turn, contains two attributes and an <auth:author> element. The <auth:author> element contains a namespace node and an element. Be aware that this stylesheet has its limitations; if you throw a very large XML document at it, it will generate an HTML file with many levels of nested tables -- probably more levels than your browser can handle.

Figure 3-1

Figure 3-1. XPath tree view of an XML document

3.5.2. The Stylesheet

Now we'll take a look at the stylesheet and how it works. The stylesheet creates a number of nested tables to illustrate the XPath view of the document. We begin by writing the basic HTML elements to the output stream and creating a legend for our nested tree view:

  <xsl:template match="/">
    <html>
      <head>
        <title>XPath view of your document</title>
        <style type="text/css">
          <xsl:comment>
            span.literal         { font-family: Courier, monospace; }
          </xsl:comment>
        </style>
      </head>
      <body>
        <h1>XPath view of your document</h1>
        <p>The structure of your document (as defined by 
           the XPath standard) is outlined below.</p>
        <table cellspacing="5" cellpadding="2" border="0">
          <tr>
            <td colspan="7">
              <b>Node types:</b>
            </td>
          </tr>
          <tr>
            <td bgcolor="#99CCCC"><b>root</b></td>
            <td bgcolor="#CCCC99"><b>element</b></td>
            <td bgcolor="#FFFF99"><b>attribute</b></td>
            <td bgcolor="#FFCC99"><b>text</b></td>
            <td bgcolor="#CCCCFF"><b>comment</b></td>
            <td bgcolor="#99FF99"><b>processing instruction</b></td>
            <td bgcolor="#CC99CC"><b>namespace</b></td>
          </tr>
        </table>
        <br />

Having created the legend for our document, we select all the different types of nodes and represent them:

<xsl:for-each select="namespace::*">
  ...
</xsl:for-each>
<xsl:for-each select="*|comment()|processing-instruction()|text()">
            ...
          </xsl:for-each>

The only difficult thing here was remembering to get all of the namespace nodes. These nodes are rarely used (with the exception of this example, I've never needed them), and they can only be selected with the namespace:: axis. Also, we process the attribute nodes when we process their element node parents; that's why the select attribute just shown doesn't have @* in it.

Here's the complete stylesheet:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="html"/>

  <xsl:template match="/">
    <html>
      <head>
        <title>XPath view of your document</title>
        <style type="text/css">
          <xsl:comment>
            span.literal         { font-family: Courier, monospace; }
          </xsl:comment>
        </style>
      </head>
      <body>
        <h1>XPath view of your document</h1>
        <p>The structure of your document (as defined by 
          the XPath standard) is outlined below.</p>
        <table cellspacing="5" cellpadding="2" border="0">
          <tr>
            <td colspan="7">
              <b>Node types:</b>
            </td>
          </tr>
          <tr>
            <td bgcolor="#99CCCC"><b>root</b></td>
            <td bgcolor="#CCCC99"><b>element</b></td>
            <td bgcolor="#FFFF99"><b>attribute</b></td>
            <td bgcolor="#FFCC99"><b>text</b></td>
            <td bgcolor="#CCCCFF"><b>comment</b></td>
            <td bgcolor="#99FF99"><b>processing instruction</b></td>
            <td bgcolor="#CC99CC"><b>namespace</b></td>
          </tr>
        </table>
        <br />
        <table width="100%" border="1" bgcolor="#99CCCC" cellspacing="2">
          <tr bgcolor="#99CCCC">
            <td colspan="2">
              <b>root:</b>
            </td>
          </tr>
          <xsl:for-each select="namespace::*">
            <tr bgcolor="#CC99CC">
              <td width="15">   </td>
              <td>
                <xsl:call-template name="namespace-node"/>
              </td>
            </tr>
          </xsl:for-each>
          <xsl:for-each select="*|comment()|processing-instruction()|text()">
            <tr bgcolor="#99CCCC">
              <td width="15">   </td>
              <td>
                <xsl:apply-templates select="."/>
              </td>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="comment()">
    <table width="100%" cellspacing="2">
      <tr>
        <td bgcolor="#CCCCFF">
          <b>comment: </b>
          <span class="literal">
            <xsl:value-of select="."/>
          </span>
        </td>
      </tr>
    </table>
  </xsl:template>

  <xsl:template match="processing-instruction()">
    <table border="0" width="100%" cellspacing="2">
      <tr>
        <td bgcolor="#99FF99">
          <b>processing instruction: </b>
          <span class="literal">
            <xsl:text>&lt;?</xsl:text>
            <xsl:value-of select="name()"/>
            <xsl:text>?&gt;</xsl:text>
            <br />
            <xsl:value-of select="."/>
          </span>
        </td>
      </tr>
    </table>
  </xsl:template>

  <xsl:template match="text()">
    <xsl:if test="string-length(normalize-space(.))">
      <tr>
        <td bgcolor="#CCCC99" width="15">    </td>
        <td bgcolor="#FFCC99" width="100%">
          <b>text: </b>
          <span class="literal">
            <xsl:value-of select="."/>
          </span>
        </td>
      </tr>
    </xsl:if>
  </xsl:template>

  <xsl:template name="namespace-node">
    <table border="0" width="100%" cellspacing="2">
      <tr>
        <td bgcolor="#CC99CC">
          <b>namespace: </b>
          <span class="literal">
            <xsl:value-of select="name()"/>
          </span>
          <br />
          <span class="literal">
            <xsl:value-of select="."/>
          </span>
        </td>
      </tr>
    </table>
  </xsl:template>

  <xsl:template match="*">
    <table border="1" width="100%" cellspacing="2">
      <xsl:choose>
        <xsl:when test="count(@*) &gt; 0">
          <tr>
            <td bgcolor="#CCCC99" colspan="2">
              <b>element: </b>
              <span class="literal">
                <xsl:text>&lt;</xsl:text>
                <xsl:value-of select="name()"/>
                <xsl:text>&gt;</xsl:text>
              </span>
              <table border="0" width="100%" cellspacing="2">
                <tr> 
                  <td bgcolor="#CCCC99" width="15">   </td>
                  <td bgcolor="#FFFF99" width="20%">
                    <b>attribute name</b>
                  </td>
                  <td bgcolor="#FFFF99">
                    <b>value</b>
                  </td>
                </tr>
                <xsl:for-each select="@*">
                  <tr>
                    <td bgcolor="#CCCC99" width="15">   </td>
                    <td bgcolor="#FFFF99" width="20%">
                      <span class="literal">
                        <xsl:value-of select="name()"/>
                      </span>
                    </td>
                    <td bgcolor="#FFFF99">
                      <span class="literal">
                        <xsl:value-of select="."/>
                      </span>
                    </td>
                  </tr>
                </xsl:for-each>
              </table>
            </td>
          </tr>
        </xsl:when>
        <xsl:otherwise>
          <tr>
            <td bgcolor="#CCCC99" colspan="2">
              <b>element: </b>
              <span class="literal">
                <xsl:text>&lt;</xsl:text>
                <xsl:value-of select="name()"/>
                <xsl:text>&gt;</xsl:text>
              </span>
            </td>
          </tr>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:for-each select="namespace::*">
        <tr>
          <td bgcolor="#CCCC99" width="15">    </td>
          <td bgcolor="#CC99CC">
            <xsl:call-template name="namespace-node"/>
          </td>
        </tr>
      </xsl:for-each>
      <xsl:for-each select="*|comment()|processing-instruction()|text()">
        <tr bgcolor="#CCCC99">
          <td width="15">   </td>
          <td>
            <xsl:apply-templates select="."/>
          </td>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:template>

</xsl:stylesheet>

Before we leave this example, there are a couple of other techniques worth mentioning here. First, notice that we used CSS to format some of the output. XSLT and CSS aren't mutually exclusive; you can use XSLT to generate CSS as part of an HTML page, as we demonstrated here. Second, we used wildcard expressions like * and @* to process all the elements and attributes in our document. Use of these expressions allows us to apply this stylesheet to any XML document, regardless of the tags it uses. Because we use these wildcard expressions, we have to use the name() function to get the name of the element or attribute we're currently working with. Third, notice that we used conditional logic and the expression count(@*) &gt; 0 to determine whether a given element has attributes. We'll talk more about conditional logic in the next chapter.



Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.