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:

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.