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(), '[F], [MNn] [D], [Y] at [h]:[m01] [P]')" /> </xsl:comment> <head> <title>Infusionsoft Table-Access Documentation</title> <link rel="stylesheet" href="styles.css" /> </head> <body> <h2>Database Tables & 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(), '[F], [MNn] [D], [Y] at [h]:[m01] [P]')" /> </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!
Hey Vivin,
Is the latest on developers.infusionsoft.com from this method? I was using it the other day and ran into several fields that were reported as Strings instead of their proper types. Not sure if it was my code or yours.
Here is what I sent Justing Gourley.
I think there may be a mistake in the database documentation… It says that OrderItem Qty, PPU, and CPU are all Strings… I just got an error in my java sdk I wrote for a client that the Qty is actually coming from Infusionsoft as an Integer and not a string… It’s possible that this is a problem on my end, but I think Infusionsoft is returning it as an Integer instead of a string…
I’m getting the same error on Contact.Validated
The CPU and PPU fields appear to be Doubles
And OrderITem.itemType, Integer instead of String
The documentation is so helpful, I’m glad your maintaining it. Yeah, if I’d gotten permission to put it on a backlog, it would have been a lot cleaner.
Hey Joey,
I made a few tweaks to the XSLT as I was writing this post. What I had before just duplicated what your Java code did. If a type is not specified in the field access XML file, it just assumes that it is a String. The only other option is to leave it blank. I will talk to Gourley about this to see what we can do (we might have to annotate everything in the field-access file).
Another thing I noticed is that in the original code, table access-permissions were not inherited. So even if a table was marked “R”, the fields were listed as “EDAR”. I fixed this in the stylesheet.
Update #1: In 22.10, the API documentation will be more accurate. I’ve defined a schema for the XML file using XSD and XML validation is part of the build process. This should take care of the type errors that you’re seeing.
Update #2: As far as mavenizing the process, it’s irrelevant as to its impact on the consumers of our API. We didn’t mavenize the API-generation process since it doesn’t make sense to include the documentation itself in the code for the application.
Out of curiosity do you code your xml by hand ie notepad or ms xml editor or do you use any commercuial xml editors? I am looking to purchase liquid xml editor (http://www.liquid-technologies.com/xml-editor.aspx) and would be interested in your feedback.
@michael
I actually just write my code in vi 🙂 Sometimes I use IntelliJ when I’m at work. It does offer auto-complete and other such features. Then again, I don’t write XML on a regular basis and so vi is sufficient for me.
This is pretty cool!