Generating API Documentation from XML using XSLT

by vivin

I work at Infusionsoft, and we offer our customers API access. Visibility and access to the various tables and their fields is controlled by an XML file on our end. Naturally, our customers require user-friendly documentation that tells them what tables and fields they can access and in what manner. Previously, a former developer had written a maven goal that would generate the API documentation from the XML file. Unfortunately this was something that he did on his own and the code wasn’t in our subversion repository. When that developer left, we decided to take a look at the code to see if we could continue generating the documentation using the maven goal. We determined that the original solution though helpful, involved a lot of work in Java simply to generate API documentation. This was when I suggested using XSLT as it would be a remarkably lightweight solution and it is also perfectly suited to this task. My colleagues agreed and so I decided to go ahead with the task. There was one slight problem though. I had very little experience with XSLT! But how hard could it be? I love learning new things anyway!

As it turned out, it wasn’t all that hard (this doesn’t mean I am an expert; I’m still a novice!). I went through a few tutorials and examples and in about an hour or so I had a basic idea of the language (especially its functional aspect). It took me about a day to write out the entire XSLT document (tweaks and all). Most the time was spent looking up instructions in XSLT to do certain things (string operations, date formatting, etc.).

Here’s an example of what I had to work with, from the XML document (this is not the exact document; it’s just an example that follows the structure):

<tables>
 <table name="Contact" access="EDAR">
  <fields>
   <field name="FirstName" />
   <field name="LastName" />
   ...
   <field name="LastUpdated" type="DateTime" access="R" />
  </fields>
 </table>
</tables>

As you can see, the structure is pretty simple. As far as the resulting API documentation was concerned, what I wanted was an index.html that contained links to other pages. These other pages should be structured so that there is an HTML page for each table. This page will contain information about the table fields, as well as their access modes (E: Edit, D: Delete, A: Add, R: Read). As it turns out, in XSLT 1.0, you cannot create separate documents. You can only transform one document into another document. In XSLT 2.0 however, you can transform one document into multiple documents using the xsl:result-document instruction.

The XSLT that I’ve written is pretty straightforward (if you know XSLT; even if you don’t, you can kinda get the gist of it from the instructions). The only thing I had trouble with was translating the access-mode letters into their expanded forms. To do this, I had to write a recursive template that looks at each character (there are other ways). This is where the functional aspect of XSLT comes into play. Once I started thinking of XSLT like Lisp, it became much easier to write my solution because I wasn’t fighting the language. Anyway, here is my solution:

<?xml version="1.0"?>
<xsl:stylesheet version="2.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
 <!--
  XSLT document that transforms Infusionsoft's API Field-Access XML into HTML pages
  Author: Vivin Suresh Paliath
  Date: 4/11/2011
 -->

 <xsl:output method="text" />
 <xsl:output method="html" indent="yes" name="html" />

 <xsl:template name="translateAccessModes">

  <xsl:param name="accessModes" />

  <xsl:if test="string-length($accessModes) > 0">

   <xsl:variable name="accessMode" select="substring($accessModes, 1, 1)" />
   <xsl:choose>
    <xsl:when test="$accessMode='E'">Edit </xsl:when>
    <xsl:when test="$accessMode='D'">Delete </xsl:when>
    <xsl:when test="$accessMode='A'">Add </xsl:when>
    <xsl:when test="$accessMode='R'">Read </xsl:when>
    <xsl:otherwise>Unrecognized Access Mode: <xsl:value-of select="$accessMode" /> </xsl:otherwise>
   </xsl:choose>

   <xsl:call-template name="translateAccessModes">
    <xsl:with-param name="accessModes" select="substring($accessModes, 2, string-length($accessModes))" />
   </xsl:call-template>

  </xsl:if>

 </xsl:template>

 <xsl:template match="/">
  <xsl:result-document href="tables/index.html" format="html">
   <html>
    <xsl:comment> Generated via XSLT on <xsl:value-of select="format-dateTime(current-dateTime(), '&#91;F&#93;, &#91;MNn&#93; &#91;D&#93;, &#91;Y&#93; at &#91;h&#93;:&#91;m01&#93; &#91;P&#93;')" /> </xsl:comment>
    <head>
     <title>Infusionsoft Table-Access Documentation</title>
     <link rel="stylesheet" href="styles.css" />
    </head>
    <body>
     <h2>Database Tables &amp; Fields</h2>
     <div id="tables">
      <ul>
       <xsl:for-each select="//table">
        <xsl:variable name="filename" select="concat('tables/', @name, '.html')" />
        <xsl:variable name="tableAccessModes">
         <xsl:choose>
          <xsl:when test="@access!=''">
           <xsl:call-template name="translateAccessModes">
            <xsl:with-param name="accessModes" select="@access" />
           </xsl:call-template>
          </xsl:when>
          <xsl:otherwise>Edit Delete Add Read</xsl:otherwise>
         </xsl:choose>
        </xsl:variable>
        <li>
         <xsl:element name="a">
          <xsl:attribute name="href"><xsl:value-of select="concat(@name, '.html')" /></xsl:attribute>
          <xsl:value-of select="@name" />
         </xsl:element>
        </li>

        <xsl:result-document href="{$filename}" format="html">
         <html>
          <xsl:comment> Generated via XSLT on <xsl:value-of select="format-dateTime(current-dateTime(), '&#91;F&#93;, &#91;MNn&#93; &#91;D&#93;, &#91;Y&#93; at &#91;h&#93;:&#91;m01&#93; &#91;P&#93;')" /> </xsl:comment>
          <head>
           <title><xsl:value-of select="@name" /> Table (Access Modes: <xsl:value-of select="substring($tableAccessModes, 1, string-length($tableAccessModes) - 1)" />)</title>
           <link rel="stylesheet" href="styles.css" />
          </head>
          <body>
           <h2><xsl:value-of select="@name" /></h2>
           <table>
            <tr>
             <th>Field Name</th>
             <th>Type</th>
             <th>Access</th>
            </tr>

            <xsl:for-each select="./fields/field">

             <xsl:variable name="fieldType"> 
              <xsl:choose>
               <xsl:when test="@type!=''"><xsl:value-of select="@type" /></xsl:when>
               <xsl:otherwise>String</xsl:otherwise>
              </xsl:choose>
             </xsl:variable>
     
             <xsl:variable name="accessModes">
              <xsl:choose>
               <xsl:when test="@access!=''">
                <xsl:call-template name="translateAccessModes">
                 <xsl:with-param name="accessModes" select="@access" />
                </xsl:call-template>
               </xsl:when>
               <xsl:otherwise><xsl:value-of select="$tableAccessModes" /></xsl:otherwise>
              </xsl:choose>
             </xsl:variable>

             <xsl:variable name="rowClass">
              <xsl:choose>
               <xsl:when test="(position() mod 2) = 0">even</xsl:when>
               <xsl:otherwise>odd</xsl:otherwise>
              </xsl:choose>
             </xsl:variable>
     
             <xsl:element name="tr">
              <xsl:attribute name="class"><xsl:value-of select="$rowClass" /></xsl:attribute>
              <td><xsl:value-of select="@name" /></td>
              <td><xsl:value-of select="$fieldType" /></td>
              <td><xsl:value-of select="$accessModes" /></td>
             </xsl:element>
            </xsl:for-each>

           </table>
           
           <h2><a href = "index.html">Back to list of tables</a></h2>
 
          </body>
         </html>
        </xsl:result-document>

       </xsl:for-each>
      </ul>
     </div>
    </body>
   </html>
  </xsl:result-document>
 </xsl:template>
</xsl:stylesheet>

That’s all there is to it! To actually use the XSLT document to generate the API HTML files, I used Saxon, which supports XSLT 2.0. This is how I used it:

java -cp saxon9he.jar net.sf.saxon.Transform -t -s:api_field_access.xml -xsl:api_doc.xslt

So if you’re an Infusionsoft customer or a developer who is reading the API documentation, you now know how it was generated!