XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

An Atom-Powered Wiki
by Joe Gregorio | Pages: 1, 2

PostURI

The PostURI is used for creating new WikiWord entries.

def create_atom_entry(body):
  wikiword = extractWikiWord(body)
  if wikiword:
    if wikiwordExists(wikiword):
      ret = report_status(409, "Conflict", 
        "An entry with that name already exists.")
    else:
      ret = put_atom_entry(wikiword, body)
    if (ret[0] == 200):
      ret = (201, CREATED_RESP % 
        {'base_uri': base_uri, 
         'atom_base_uri': atom_base_uri, 
         'wikiword': wikiword
        })
  else:
    ret = report_status(409, "Conflict", 
      "Not enough information to form a wiki word.")
  return ret

The function 'extractWikiWord' pulls out the contents of the title element and converts it into a WikiWord. If we have a good WikiWord and it doesn't already exist, then we use 'put_atom_entry' to create it. Otherwise we respond with an HTTP status code of 409 to indicate that we won't let a POST overwrite an already existing WikiWord.

FeedURI

The FeedURI is the last piece we need to implement. The FeedURI is used by clients to locate the PostURI for creating new entries and the EditURIs for editing each entry. The format of the FeedURI is exactly that of an Atom feed. This is different from the Atom we use with the PostURI and the EditURI, which is just the 'entry' element from Atom. Since the format of the FeedURI is the same as that for a regular feed, you might be tempted to have the same feed for both aggregation and editing. This might work in the case of wiki but not for a general site. The reason is that you may have entries in draft or unpublished form which must appear at the FeedURI so you can edit them, but must not appear in your aggregation feed. Given that this is for a publicly editable wiki, we don't have such a constraint so we can use this feed for both purposes.

The FeedURI is implemented as a separate script, atomfeed.cgi, that builds a feed. The code, which is bit too long to include here, builds an Atom feed by sorting all the files that contain WikiWord definitions in reverse chronological order, then takes the WikiWord and associated content, and formats it in an Atom entry. The entries are concatenated together and placed in an Atom feed. The only special additions are the link elements that contain the PostURI and the EditURIs, which are denoted with attributes rel="service.post" and rel="service.edit" respectively. Here is a snippet from the Atom feed produced by atomfeed.cgi.

<?xml version="1.0" encoding="utf-8"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
  <title>PikiPiki</title>
  <link rel="alternate" type="text/html" 
     href="http:/.bitworking.org.cgi"/>
  <link rel="service.post" type="application/atom+xml" 
      href="http:/.bitworking.org/atom.cgi"/>
  <link rel="next" type="application/atom+xml" 
      href="http:/.bitworking.org/atomfeed.cgi/10"/>
  
  <modified>2004-03-09T21:32:58-05:00</modified>
  <author>
      <name>Joe Gregorio</name>
      <url>http://bitworking.org/</url>
  </author> 
  <entry>
    <title>JustTesting</title> 
    <link rel="service.edit" type="application/atom+xml" 
        href="http:/.bitworking.org/atom.cgi/JustTesting" />
    <link rel="alternate" type="text/html" 
        href="http:/.bitworking.org.cgi/JustTesting" />
    <id>tag:piki.bitworking.org,2004:JustTesting</id>
    <issued>2004-03-09T21:32:58-05:00</issued> 
    <modified>2004-03-09T21:32:58-05:00</modified> 
    <content type="text/plain">
      This is content posted from an AtomAPI client.
    </content>  
  </entry>
  <entry>
    <title>PikiSandBox</title> 
    <link rel="service.edit" type="application/atom+xml" 
      href="http:/.bitworking.org/atom.cgi/PikiSandBox" />
    <link rel="alternate" type="text/html" 
      href="http:/.bitworking.org.cgi/PikiSandBox" />
    <id>tag:piki.bitworking.org,2004:PikiSandBox</id>
    <issued>2004-03-04T21:49:03-05:00</issued> 
    <modified>2004-03-04T21:49:03-05:00</modified> 
    <content type="text/plain">
      '''I dare you''': press the Edit button and add 
      something to this page. 

    -- MartinPool
    </content>  
    </entry>
 

This feed also contains one more link element of a type we haven't talked about yet. The second link, the one with rel="next", points to the next set of entries. That is, when we produce a FeedURI you don't want to put all the entries into a single feed. That could end up being hundreds if not thousands of entries which would be impractical to handle. Instead put in a fixed number, like 20, and then the 'next' link points to another feed, with the next 20 entries. If a feed is in the middle of such a chain then it also contains a link with rel="prev" which points to the set of entries previous to the current one. In this way clients can navigate around the list of entries in manageable sized sets. It should be noted here that the client code that comes with this implementation does not implement traversing 'next' and 'prev' links in a feed.

The Client

An AtomAPI enabled wiki wouldn't be worth much if there wasn't a client available, so I've included a wxPython client that allows you to create new entries on the wiki and to edit old entries.

Remember how careful we were when specifying and using the character encoding? There isn't much code involved in supporting and processing everything in UTF-8, but careful planning ahead pays dividends. Here is a screenshot of the client editing one of the pages on a wiki with some unicode characters in it:

Screenshot of the 'wxAtomClient' application editing an entry full of chess characters

All of the source for both the client and the server can be downloaded from the EditableWebWiki, which is running the code described above. Note that the client is a GUI application written in Python. You must use the version of wxPython that is compiled with Unicode support. Lastly, for your platform you'll have to ensure that you have fonts available to display the Unicode characters you are going to be using.

Rough Spots

One of the reasons we started using the AtomAPI on a wiki was to stretch the API and see where things broke down. Nothing really awful showed up, though we did find some rough spots. The first rough spot cropped up when doing a GET on the EditURI, where we encounter a slight mismatch between the formulation of the AtomAPI and this wiki implementation. The problem is that according to version 9 the draft AtomAPI, when doing a GET on an EditURI, the issued element is required. Since PikiPiki only stores the raw contents in a file, and doesn't store any other data, we are limited to using the last modified date stored in the file system for each file, which isn't the same as the issued element.

The second rough spot is in the area of content. The only only type of content we accept is 'text/plain', but that isn't the only type of content that a client could post. In fact, most may be able to produce 'text/html' and some may even be able to produce 'application/xhtml+xml'. Now we may be able to add code to this implementation to convert HTML into WikiML, but the broader question still stands: how does a client know what kinds of content, i.e. which mime-types, an AtomAPI server will accept? This is an open question as of today.

Summary

Using Python and the XPath facilities of libxml2, it was straightforward to build an AtomAPI implementation for a wiki. There isn't even very much code: atom.cgi is just 146 lines of code, while atomfeed.cgi is just 122 lines.

This is just a basic client that does the minimum to support the AtomAPI. In a future article the way the server handles HTTP can be enhanced to provide significant performance boosts by using the full capabilities of HTTP. In addition, the SOAP enabling of the server will require some changes. After that we can add the ability to edit the wiki's templates.