Implementing WWW::LastFM with XML::Rabbit - Part 4

In the previous article we finished implementing the geo.getMetros Last.FM API call with XML::Rabbit. That particular API call doesn't include a lot of interesting information, but it is useful to know which worldwide locations the Last.FM service knows about now that we're going to implement the geo.getEvents API call.

Implementing the geo.getEvents API call

Now that we know which locations are valid, we can actually ask for events for a specific location. Add another method towards the end of WWW::LastFM::API::Geo, like this:

# http://www.last.fm/api/show?service=270
# All strings must be binary encoded utf8 strings
#
# lat (Optional) : Specifies a latitude value to retrieve events for (service returns nearby events by default)
# location (Optional) : Specifies a location to retrieve events for (service returns nearby events by default)
# long (Optional) : Specifies a longitude value to retrieve events for (service returns nearby events by default)
# distance (Optional) : Find events within a specified radius (in kilometres)
# limit (Optional) : The number of results to fetch per page. Defaults to 10.
# page (Optional) : The page number to fetch. Defaults to first page.

sub get_events {
    my ($self, %opts) = @_;
    my $params = "";
    foreach my $key ( keys %opts ) {
        $params .= '&' . $key . '=' . uri_escape( $opts{$key} );
    }
    my $xml = $self->lastfm->get(
           $self->lastfm->api_root_url
        . '?method=geo.getEvents'
        . $params
        . '&api_key=' . $self->lastfm->api_key
    );
    return WWW::LastFM::Response->new( xml => $xml );
}

If you've paid attention, you will notice that this method is quite similar to get_metros. We will not go into refactoring it in this article, but it should be quite easy to refactor the common parts into a generic method on the (non-existing) WWW::LastFM::API class and turn that into either a role or a base class for the rest of the WWW::LastFM::API::* classes we might create in the future.

Implementing the <events/> XML chunk extractor

So let's try it out and see what we get. Again, we will use a fairly simple one-liner.

$ perl -Ilib -MWWW::LastFM -E 'STDOUT->binmode(":utf8"); say WWW::LastFM->new->geo->get_events( limit => 1 )->dump_document_xml()'
<?xml version="1.0" encoding="utf-8"?>
<lfm status="ok">
<events xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" location="Oslo, Norway"
        page="1" perPage="1" totalPages="163" total="163" festivalsonly="0">
    <event xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
  <id>1996187</id>
  <title>The Jezabels</title>
  <artists>
    <artist>The Jezabels</artist>
    <headliner>The Jezabels</headliner>
  </artists>
    <venue>
    <id>9014441</id>
    <name>John Dee</name>
    <location>
      <city>Oslo</city>
      <country>Norway</country>
      <street>Torggata 16</street>
      <postalcode>0181</postalcode>
      <geo:point>
         <geo:lat>59.915823</geo:lat>
         <geo:long>10.751034</geo:long>
      </geo:point>
    </location>
    <url>http://www.last.fm/venue/9014441+John+Dee</url>
    <website>http://www.rockefeller.no/</website>
    <phonenumber>22 20 32 32</phonenumber>
    <image size="small"/>
        <image size="medium"/>
        <image size="large"/>
        <image size="extralarge"/>
        <image size="mega"/>
  </venue>    <startDate>Sun, 25 Sep 2011 20:00:00</startDate>
  <description><![CDATA[<div class="bbcode">The Jezabels begynte å spille sammen i 2007
  etter å ha møttes på Universitet i Sydney, Australia, og har etter det gitt ut tre
  ep-plater som har fått god mottagelse over alt i verden. Bandet har blant annet vært
  nominert til Best Breakthrough Artist og Best Single / EP på Independent Music Awards
  i 2010 og vært support til Tegan &amp; Sara. Det er litt vanskelig å beskrive popmusikken
  deres; det er som en blanding mellom indiepop og indierock, med litt disko. Bra er det
  i hvert fall! The Jezabels har ikke gitt ut noen skive i Norge ennå, men hvis du har
  sett Imsdals (forferdlige) reklamefilme, har du hørt låten ”Easy To Love”. Debutskiven
  kommer under 2011 og den 25. september kan du se bandet på John DEE!<br /><br />Billetter
  er i salg via Billettservice!</div>]]></description>
  <image size="small">http://userserve-ak.last.fm/serve/34/48219593.png</image>
  <image size="medium">http://userserve-ak.last.fm/serve/64/48219593.png</image>
  <image size="large">http://userserve-ak.last.fm/serve/126/48219593.png</image>
  <image size="extralarge">http://userserve-ak.last.fm/serve/252/48219593.png</image>
  <attendance>6</attendance>
  <reviews>0</reviews>
  <tag>lastfm:event=1996187</tag>
  <url>http://www.last.fm/event/1996187+The+Jezabels</url>
  <website>https://www.facebook.com/event.php?eid=172860196109875</website>
    <tickets>
  </tickets>
    <cancelled>0</cancelled>
    <tags>
      <tag>australian</tag>
      <tag>indie</tag>
      <tag>rock</tag>
      <tag>female vocalists</tag>
    </tags>
    </event></events></lfm>

Now we're starting to see what we're interested in!

But wait, what is that xmlns:geo attribute at the top there? Will that cause a problem? Yes it will. - That is an XML namespace, and if we want to be able to pull out information from those <geo:point/> tags we'll need to specify that we want to deal with this particular namespace. Luckily, dealing with XML namespaces is quite easy with XML::Rabbit. The first thing we need to do is to declare that we want to create queries that use this namespace. Add the following piece of code before the BUILD method in lib/WWW/LastFM/Response.pm to take care of that challenge:

add_xpath_namespace 'geo' => 'http://www.w3.org/2003/01/geo/wgs84_pos#';

Now we can add a new accessor for the events in WWW::LastFM::Response. Add the following piece of code at the bottom of lib/WWW/LastFM/Response.pm:

has_xpath_object 'events' => '/lfm/events' => 'WWW::LastFM::Response::EventList';

As this points to another class, we'll have to create that one too. The contents of lib/WWW/LastFM/Response/EventList.pm should be this:

package WWW::LastFM::Response::EventList;
use XML::Rabbit;

has_xpath_value 'page'       => './@page';
has_xpath_value 'page_limit' => './@perPage';
has_xpath_value 'page_count' => './@totalPages';

has_xpath_value 'location'       => './@location';
has_xpath_value 'festivals_only' => './@festivalsonly';

has_xpath_object_map 'map' => './event',
    './id'  => 'WWW::LastFM::Response::Event',
    handles => {
        'get' => 'get',
        'ids' => 'keys',
        'all' => 'values',
    },
;

finalize_class();

Everything here should be easy to understand, as we're only declaring simple values. The has_xpath_object_map declaration might require some clarification, though. We're declaring a Moose attribute called map which is a hash reference where the event id from the XML document is the key, and an object of class WWW::LastFM::Response::Event is the value. The native trait delegations for hashes are somewhat different than the ones for arrays. As this object is not designed for data modification, we've only added the typical read accessors. Whether you will use ids and get or all depends a bit on your application needs. We're only going to use all in our application.

In the next and final article we'll flesh out the implementation of the Event class. We will also create a small application that allows us to display events for a specific location in a compact format. See you soon!