robert hahn

a darn good web developer

March 15, 2007

Printing XML from Camping

In a recent client project, I thought it might be a cool idea to set up a mini-blog chronicling my work on their site, so I started with the blog included in the Camping microframework, and hacked it to meet my needs.

In short order, I ran into one problem that I’m surprised didn’t get more coverage: how to print XML (or anything, really), not HTML from a Camping app. After going over the problem with Evan Weaver on #camping, I tried out a couple of approaches, and found one that seems to work well.

Suppose you want to generate an Atom feed for your app. First thing you need to do is load up Builder, a handy library for generating XML documents.

require 'builder'

The next thing you want to do is add a controller for getting a feed:

module MyApp::Controllers
  class Feed < R '/feed/myfeed.atom'
    def get
      @headers['Content-type'] = 'application/atom+xml'
      @entries = Models::Posts.find :all, :order => 'date DESC', :limit => 20
      render :feed
    end
  end
end

Your @entries will obviously look different; I’m just collecting the 20 most recent posts for my Atom feed. You’ll also note that I’m setting the Content-type of the document as well; if you don’t do that, Camping will assume the Content-type is text/html.

Now we get to the interesting part. As you know, Camping only prints out HTML by default, and provides no obvious means of specifiying any other type of markup (at least from what I can tell). Here’s the solution:

 module Blog::Views
   def layout
      case @headers['Content-type']
      when 'application/atom+xml'
        self << yield # no template, please!
      else
        # html layout goes here
      end
    end

    def feed
      b = Builder::XmlMarkup.new(:indent => 2)
      xml = b.feed({ :xmlns => "http://www.w3.org/2005/Atom" }) do |x|
        x.title("sample feed")
        x.link( { :href => "http://localhost:3301/feed/myfeed.atom"})
        x.updated(Time.now.strftime("%Y-%m-%dT%H:%M:%SZ"))
        x.author do
          x.name("Robert Hahn")
        end
        x.id("http://localhost:3301/feed/myfeed.atom")

        # generate the feed entries
        @entries.each do |e|
          x.entry do
            x.title(e.title)
            x.link({ :href => "http://localhost:3301//view/#{e.post_id}"})
            x.id({ :href => "http://localhost:3301//view/#{e.post_id}"})
            x.updated(e.date.strftime("%Y-%m-%dT%H:%M:%SZ"))
            x.content({ :type => 'text'}) do
              x.text! e.body
            end
          end
        end
      end
    end
    text xml #print the Atom feed
 end

So we needed to do a couple of things: first, in the layout, we check the Content-Type. If it’s application/atom+xml. , then don’t print the contents of the layout method. Next, we generate our Atom feed. The last thing to do is print it.

This is probably the best way to do this for the time being. If I get some time, I’ll look at seeing how to modify Camping to support multiple layouts.

decorative image of trees

Copyright © 2009
Robert Hahn.
All Rights Reserved unless otherwise indicated.