PubSub Framework Recipes: Feed Auto-Discovery

Apple’s Publication Subscription (“PubSub”) Framework is not among the best-documented frameworks. There is a small programming guide as well as a framework reference, but all in all the documentation is scarce. Among the lesser known features of this framework is the possibility to let the framework auto-discover RSS/Atom feeds for any web page you throw at it.

This works by linking your application to the framework (and of course by adding the framework to the application target):

#import 

Then create a new PSFeed object and add this to your PSClient object:

client = [PSClient applicationClient];
[client setDelegate:self];
PSFeed *psFeed = [client addFeedWithURL:feedURL];
[psFeed refresh:nil]

“feedURL” can be any URL to any web page, for example http://www.guardian.co.uk or http://www.nytimes.com. Please note that PSClient expects to receive a properly formatted URL. E.g., if you miss out the http:// part, it won’t work.

As we’ve set ourself as PSClient’s delegate, we will be informed once PSClient has tried to fetched the feed if we implement this delegate method:

- (void) feedDidEndRefresh:(PSFeed *)feed

If we stick to our example and use the Guardian web page, of course PSClient won’t be able to find a feed there – the landing page of the Guardian website is just a web page. But instead the framework will deliver to us all links to RSS/Atom feeds contained in that page. This is most useful as very often landing pages will contain such links. Browsers like Safari evaluate these links to show the little “RSS” logo in the address bar, and we can do that, too. The links will be contained in the links property of the PSFeed object and are instances of the PSLink class. You can retrieve them like this:

- (void) feedDidEndRefresh:(PSFeed *)feed
{
    if ([[feed URL] isEqualToURL:feedURL]) { // In case we're subscribing to multiple feeds
        NSError *error;
        error = [feed lastError];
        if( [[error domain] isEqualToString: PSErrorDomain] && [error code]==PSNotAFeedError) {
            for( PSLink *link in [feed links] ) {
                if (([link linkKind] == PSLinkToAtom) || ([link linkKind] == PSLinkToRSS)) {
                    NSLog (@"Found link to feed with title %@ and URL %@", [link title], [link URL]);
                }
            // ...
        }
    }
}

There’s one problem, though, as due to some framework-internal error I don’t quite understand yet the title string contained in [link title] of the PSLink object will not be encoded properly for most non-ASCII characters. Already German umlauts ä, ö, ü are illegible and text in languages using non-latin characters like in Russian is not readable at all. I’ve filed that as a bug with Apple and use a workaround by manually loading the RSS/Atom feed following the URL contained in [link URL] and parsing it to get a properly encoded title string:

NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:[link URL]];
if (parser) {
    [parser setDelegate:self];
    [parser parse];
}

Once the feed is loading and is parsed by the parser object, it will call its delegate methods, of which we need to implement two:

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

    if (!xmlCharacters) {
        xmlCharacters = [[NSMutableString alloc] initWithCapacity:0];
    }
    [xmlCharacters appendString:string];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:@"title"]) {
        [parser abortParsing];
        NSLog (@"title of the feed is %@", xmlCharacters);
    } else
    {
        [xmlCharacters setString:@""];
    }
}

After parsing the feed the xmlCharacters string will contain the title of the feed, encoded in a proper way.