簡易RSSリーダーを作ってみた 〜 C編 〜

確か、2〜3年くらい前にCで書いた簡易RSSリーダー。IBMの[developerWorks](http://www.ibm.com/developerworks/jp/opensource/library/os-curl/)の記事を参考に、Linuxで[libcurl](http://curl.haxx.se/libcurl/)を使ってダウンロードしたRSSフィードを[libxml2](http://xmlsoft.org)を使って解析しながら記事のタイトルとリンク先を表示するというのをやってXMLを学習したような気がするけど、XMLの解析処理をどうやって書いたかさっぱり思い出せないorz

#サンプルコード

libcurlによるRSSフィードのダウンロードはIBMのサンプルをほぼそのまま使って、ダウンロード結果を表示する所を、アップルのRSSフィードのサマリが出るようにしています。

	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>
	#include <curl/curl.h>
	#include <sys/types.h>
	#include <errno.h>
	#include <libxml/xmlerror.h>
	#include <libxml/parser.h>
	#include <libxml/tree.h>
	#include <libxml/xpath.h>
	
	#define MAX_BUF	65536
	
	char wr_buf[MAX_BUF+1];
	int  wr_index;
	
	#define PATH "http://www.apple.com/jp/main/rss/hotnews/hotnews.rss"
	
	/*
	 * Write data callback function (called within the context of 
	 * curl_easy_perform.
	 */
	
	size_t write_data( void *buffer, size_t size, size_t nmemb, void *userp ) {
	    int segsize = size * nmemb;
	
	    if (wr_index + segsize > MAX_BUF) {
	        *(int *)userp = 1;
	        return 0;
	    }
	
	    memcpy( (void *)&wr_buf[wr_index], buffer, (size_t)segsize );
	    wr_index += segsize;
	    wr_buf[wr_index] = 0;
	    return segsize;
	}
	
	/*
	 * Parse RSS and print summary.
	 */
	
	int
	print_rss(char* text) {
	    int     i;
	    int     size;
	    xmlDoc  *doc;
	    xmlXPathContextPtr  xpathCtx;
	    xmlXPathObjectPtr   xpathObj;
	    xmlNodeSetPtr       nodes;
	    xmlNode             *node;
	    xmlChar             *textcount;
	
	    /* parse an XML in-memory document and build a tree. */
	    doc = xmlReadMemory(text, strlen(text), "noname.xml", NULL, 0);
	    if (doc == NULL) {
	        perror("xmlReadFile");
	        return -1;
	    }
	
	    /* Create a new xmlXPathContext */
	    xpathCtx = xmlXPathNewContext(doc);
	    if (xpathCtx == NULL) {
	        perror("xmlXPathNewContext");
	        xmlFreeDoc(doc);
	        xmlCleanupParser();
	        return -1;
	    }
	
	    /* Get Node list of RSS channel */
	    xpathObj = xmlXPathEvalExpression(BAD_CAST "/rss/channel", xpathCtx);
	    nodes = xpathObj->nodesetval;
	    if (nodes == NULL) {
	        xmlXPathFreeContext(xpathCtx);
	        xmlFreeDoc(doc);
	        xmlCleanupParser();
	        return -1;
	    }
	    
	    /* Get and print Title of RSS Feed. */
	    size = nodes->nodeNr;
	    for (i = 0; i < size; i++) {
	        node = xmlFirstElementChild(nodes->nodeTab[i]);
	        if (node == NULL) {
	            continue;
	        }
	        for (; node; node = xmlNextElementSibling(node)) {
	            if (strcmp((char*)node->name, "title")) {
	                    continue;
	            }
	            textcount = xmlNodeGetContent(node);
	            if (textcount) {
	                printf("\nTitle: %s\n\n", textcount);
	                xmlFree(textcount);
	            }
	        }
	    }
	    xmlXPathFreeObject(xpathObj);
	
	    /* Get Node list of RSS items */
	    xpathObj = xmlXPathEvalExpression(BAD_CAST "/rss/channel/item", xpathCtx);
	    nodes = xpathObj->nodesetval;
	    if (nodes == NULL) {
	        xmlXPathFreeContext(xpathCtx);
	        xmlFreeDoc(doc);
	        xmlCleanupParser();
	        return -1;
	    }
	
	    /* Get and print title and link of each items. */
	    size = nodes->nodeNr;
	    for (i = 0; i < size; i++) {
	        node = xmlFirstElementChild(nodes->nodeTab[i]);
	        if (node == NULL) {
	            continue;
	        }
	        for (; node; node = xmlNextElementSibling(node)) {
	            if (strcmp((char*)node->name, "title") != 0 &&
	                strcmp((char*)node->name, "link") != 0) {
	                    continue;
	            }
	            textcount = xmlNodeGetContent(node);
	            if (textcount) {
	                printf(" %s:\t%s\n", node->name, textcount);
	                xmlFree(textcount);
	            }
	        }
	        printf("\n");
	    }
	
	    xmlXPathFreeObject(xpathObj);
	    xmlXPathFreeContext(xpathCtx);
	    xmlFreeDoc(doc);
	    xmlCleanupParser();
	    return 0;
	}
	
	/*
	 * Simple curl application to read the rss feed from a Web site.
	 */
	
	int main( void )
	{
	    CURL *curl;
	    CURLcode ret;
	    int  wr_error;
	
	    wr_error = 0;
	    wr_index = 0;
	
	    curl = curl_easy_init();
	    if (!curl) {
	        printf("couldn't init curl\n");
	        return 0;
	    }
	
	    curl_easy_setopt(curl, CURLOPT_URL, PATH);
	    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&wr_error);
	    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
	
	    ret = curl_easy_perform(curl);
	    if ( ret == 0 ) {
	        //
	        print_rss(wr_buf);
	    } else {
	        printf( "ret = %d (write_error = %d)\n", ret, wr_error );
	        curl_easy_cleanup(curl);
	        return 1;
	    }
	
	    curl_easy_cleanup(curl);
	    return 0;
	}


#コンパイル

コンパイルには libcurl-devel と libxml2-devel が必要なので事前にインストールしておきます(CentOSの場合)。コンパイル時は -I オプションによるlibxml2のヘッダファイルの指定と -l オプションによるライブラリの指定が必要です。

    $ cc -I/usr/include/libxml2 getrss.c -o getrss -lcurl -lxml2

#実行結果

「アップル - ホットニュース」のサマリが表示されました。

	$ ./getrss
	
	Title: アップル - ホットニュース
	
	 title:	Apple、感圧タッチトラックパッドを搭載した15インチのMacBook Pro、238,800円の新しいiMac Retina 5Kディスプレイモデルを発売
	 link:	http://www.apple.com/jp/pr/library/2015/05/19Apple-Introduces-15-inch-MacBook-Pro-with-Force-Touch-Trackpad-New-1-999-iMac-with-Retina-5K-Display.html
	
	 title:	日本郵政グループ、IBM、Apple、日本の高齢者がサービスを通じて家族・地域コミュニティーとつながるために、iPadと専用アプリケーションを提供
	 link:	http://www.apple.com/jp/pr/library/2015/04/30Japan-Post-Group-IBM-and-Apple-Deliver-iPads-and-Custom-Apps-to-Connect-Elderly-in-Japan-to-Services-Family-and-Community.html
	
	 title:	Apple、第2四半期の業績を発表
	 link:	http://www.apple.com/jp/pr/library/2015/04/27Apple-Reports-Record-Second-Quarter-Results.html
	
        ・
        ・
        ・