メンバページ: tsu

行き当たりばったり指向プログラミング

このページは、行き当たりばったりにテーマを決めて、 マイペースで書いていこうと思っています。
といっても、プログラミング回りの話が中心になると思います。
特に、オブジェクト指向が嫌いな方にはおすすめです(嘘かもしれない)。

まずは、RSS 1.0 を対象に RSS リーダーを作ってみることにします。

一般的に RSS 1.0 の rdf ファイルは、以下のような形式をしています。
(実際には、もっといろんなバリエーションが可能なのですが、ここでは簡単のために最も一般的な例を示しています。)

<?xml version="1.0" encoding="UTF-8"?>

<rdf:RDF
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
  xmlns:admin="http://webns.net/mvcb/"
  xmlns="http://purl.org/rss/1.0/">

<channel rdf:about="RSS の対象となる Web ページの URL">
<title>RSS の対象となる Web ページのタイトル</title>
<link>RSS の対象となる Web ページの URL</link>
<description>RSS の対象となる Web ページの概要</description>
<dc:language>言語(日本語の場合は ja)</dc:language>
<dc:creator>RSS の対象となる Web ページの著作者</dc:creator>
<dc:date>RSS の対象となる Web ページの最終更新日時</dc:date>
<admin:generatorAgent rdf:resource="RSS生成に使用したツールの URL" />

<items>
<rdf:Seq>
<rdf:li rdf:resource="RSS 生成対象の 最初  の記事の URL" />
<rdf:li rdf:resource="RSS 生成対象の 2番目 の記事の URL" />
   ・・・・・・・・・・・・(略)・・・・・・・・・・・・
<rdf:li rdf:resource="RSS 生成対象の n番目 の記事の URL" />
</rdf:Seq>
</items>

<item rdf:about="RSS 生成対象の 最初  の記事の URL">
<title>RSS 生成対象の 最初  の記事のタイトル</title>
<link>RSS 生成対象の 最初  の記事の URL</link>
<description>RSS 生成対象の 最初  の記事の概要</description>
<dc:subject>RSS 生成対象の 最初  の記事の分類</dc:subject>
<dc:creator>RSS 生成対象の 最初  の記事の著作者</dc:creator>
<dc:date>RSS 生成対象の 最初  の記事の作成日時</dc:date>
</item>
<item rdf:about="RSS 生成対象の 2番目 の記事の URL">
<title>RSS 生成対象の 2番目 の記事のタイトル</title>
<link>RSS 生成対象の 2番目 の記事の URL</link>
<description>RSS 生成対象の 2番目 の記事の概要</description>
<dc:subject>RSS 生成対象の 2番目 の記事の分類</dc:subject>
<dc:creator>RSS 生成対象の 2番目 の記事の著作者</dc:creator>
<dc:date>RSS 生成対象の 2番目 の記事の作成日時</dc:date>
</item>
   ・・・・・・・・・・・・(略)・・・・・・・・・・・・
<item rdf:about="RSS 生成対象の n番目 の記事の URL">
<title>RSS 生成対象の n番目 の記事のタイトル</title>
<link>RSS 生成対象の n番目 の記事の URL</link>
<description>RSS 生成対象の n番目 の記事の概要</description>
<dc:subject>RSS 生成対象の n番目 の記事の分類</dc:subject>
<dc:creator>RSS 生成対象の n番目 の記事の著作者</dc:creator>
<dc:date>RSS 生成対象の n番目 の記事の作成日時</dc:date>
</item>
</channel>

</rdf:RDF>

とりあえず、方針としては、RSS 1.0 形式の rdf ファイルを入力とし、html に変換して出力するコンソールアプリケーションを作ることにします。入力ファイルに

xmlns="http://purl.org/rss/1.0/"

という文字列が含まれていたら、RSS 1.0 と見なすことにします。
大雑把にコードを書くと、以下のような感じになります。

int readRSS(
          FILE *input,  /* 入力 RSS ファイル */
          FILE *output  /* 出力 htmlファイル */
      )
{
    char    buf[BUFSIZ], *p, *q, *r;
    char    encoding[80];
    int     version = VER_UNDEFINED;
    int     ret = 0;

    while ( ( p = fgets( buf, BUFSIZ - 1, input ) ) != NULL ) {
        while ( (*p == ' ') || (*p == '\t') )
            p++;

        /* encoding を判定 */
        if ( !strncmp( p, "<?xml ", 6 ) ) {
            if ( ( q = strstr( p, "encoding=" ) ) != NULL ) {
                r = strchr( q + 10, '\"' );
                strncpy( encoding, q + 10, r - (q + 10) );
                encoding[r - (q + 10)] = '\0';
            }

            continue;
        }

        /* RSS の版数を判定し、対応する RSS リーダーを呼び出す */
        if ( !strncmp( p, "xmlns=\"http://purl.org/rss/1.0/\"", 32 ) ) {
            version = VER_RSS_1_00;
            ret = readRSS_1_00( input, output, encoding );
            break;
        }
    }

    return ( ret );
}

で、RSS 1.0 のリーダー readRSS_1_00() は、以下のような感じでしょうか?

int readRSS_1_00( FILE *input, FILE *output, const char *encoding )
          FILE *input,  /* 入力 RSS ファイル */
          FILE *output, /* 出力 htmlファイル */
          const char *encoding /* エンコード */
      )
{
    char    buf[BUFSIZ], *p, *q;
    int     ret = 0;
    char    title[BUFSIZ];
    char    link[BUFSIZ];
    char    description[BUFSIZ];
    char    language[BUFSIZ];
    char    subject[BUFSIZ];
    char    date[BUFSIZ];
    int     cnt = 0;

    language[0] = '\0';

    while ( ( p = fgets( buf, BUFSIZ - 1, input ) ) != NULL ) {
        while ( (*p == ' ') || (*p == '\t') )
            p++;

        if ( !strncmp( p, "<title>", 7 ) ) {
            q = strchr( p + 7, '<' );
            strncpy( title, p + 7, q - (p + 7) );
            title[q - (p + 7)] = '\0';
        }
        else
        if ( !strncmp( p, "<link>", 6 ) ) {
            q = strchr( p + 6, '<' );
            strncpy( link, p + 6, q - (p + 6) );
            link[q - (p + 6)] = '\0';
        }
        else
        if ( !strncmp( p, "<description>", 13 ) ) {
            q = strchr( p + 13, '<' );
            strncpy( description, p + 13, q - (p + 13) );
            description[q - (p + 13)] = '\0';
        }
        else
        if ( !strncmp( p, "<dc:language>", 13 ) ) {
            q = strchr( p + 13, '<' );
            strncpy( language, p + 13, q - (p + 13) );
            language[q - (p + 13)] = '\0';
        }
        else if ( !strncmp( p, "</items>", 8 ) )
            break;
    }

    fprintf( output,
             "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD "
             "HTML 4.01 Transitional//EN\"gt;\n" );

    if ( language[0] )
        fprintf( output, "<html lang=\"%s\">\n<head>\n", language );
    else
        fprintf( output, "<html>\n<head>\n" );

    fprintf( output,
             "<meta http-equiv=\"Content-Type\" content=\"text/html; "
             "charset=%s\">\n", encoding );
    fprintf( output, "<title>%s</title>\n", title );
    fprintf( output, "</head>\n\n<body>\n" );

    fprintf( output, "<h1><a href=\"%s\">%s</a></h1>\n", link, title );
    fprintf( output, "<h2>%s</h2>\n", description );

    while ( ( p = fgets( buf, BUFSIZ - 1, input ) ) != NULL ) {
        while ( (*p == ' ') || (*p == '\t') )
            p++;
        if ( !strncmp( p, "<item rdf:about=\"", 17 ) ) {
            cnt++;

            if ( cnt == 1 )
                fputs( "<ul>\n", output );
            title[0]       = '\0';
            link[0]        = '\0';
            description[0] = '\0';
            subject[0]     = '\0';
            date[0]        = '\0';

            continue;
        }

        if ( cnt >= 1 ) {
            if ( !strncmp( p, "<title>", 7 ) ) {
                q = strchr( p + 7, '<' );
                strncpy( title, p + 7, q - (p + 7) );
                title[q - (p + 7)] = '\0';
            }
            else
            if ( !strncmp( p, "<link>", 6 ) ) {
                q = strchr( p + 6, '<' );
                strncpy( link, p + 6, q - (p + 6) );
                link[q - (p + 6)] = '\0';
            }
            else
            if ( !strncmp( p, "<description>", 13 ) ) {
                q = strchr( p + 13, '<' );
                strncpy( description, p + 13, q - (p + 13) );
                description[q - (p + 13)] = '\0';
            }
            else
            if ( !strncmp( p, "<dc:subject>", 12 ) ) {
                q = strchr( p + 12, '<' );
                strncpy( subject, p + 12, q - (p + 12) );
                subject[q - (p + 12)] = '\0';
            }
            else
            if ( !strncmp( p, "<dc:date>", 9 ) ) {
                q = strchr( p + 9, '<' );
                strncpy( date, p + 9, q - (p + 9) );
                date[q - (p + 9)] = '\0';
            }
            else
            if ( !strncmp( p, "</item>", 7 ) ) {
                if ( title[0] && link[0] && description[0] ) {
                    fprintf( output,
                             "<li><a href=\"%s\">%s</a><br>%s\n",
                             link, title, description );

                    if ( subject[0] ) {
                        fprintf( output, "<br>[%s%s]",
                                 categoryString( encoding ), subject );
                        if ( date[0] )
                            fprintf( output, " (%s)",
                                     dateString( date, encoding ) );
                    }
                    else
                    if ( date[0] )
                        fprintf( output, "<br>(%s)",
                                 dateString( date, encoding ) );

                    fputs( "</li>\n", output );

                    title[0]       = '\0';
                    link[0]        = '\0';
                    description[0] = '\0';
                    subject[0]     = '\0';
                    date[0]        = '\0';
                }
            }
        }
    }

    if ( cnt >= 1 )
        fputs( "</ul>\n", output );

    fprintf( output, "</body>\n</html>\n" );

    return ( ret );
}

ちょっと安直なコードですが、直感的にはわかりやすいと思います。 ソースがある程度書き進んだところで、 リファクタリングに挑戦することにしましょう。

なお、dateString() は RDF の datetime型 の日時表現 (例: 2003-12-16T13:00:00+9:00) を 2003年12月16日 13時 0分 0秒 +9:00 のような形式に変換する関数とします (別途、用意します)。
また、categoryString() の方は「分類」を表示するときの見出しを encoding にしたがった文字コードで出力する関数とします。

文字コードの変換は GNU の iconv ライブラリ を使えば、わざわざ、新規に作る必要はありません。 すでにあるものは使え、ってことです。

今回、作成した関数のプロトタイプ宣言をヘッダファイルに書いておきます。

/*
 *  readRSS
 *      RSS ファイルを元に html ファイルを生成する
 *      written by H.Tsujimura   15 Dec 2003
 *
 * History:
 * $Log: readRSS.h $
 * Revision 1.1  2003/12/15  10:50:51  tsujimura543
 * Initial revision
 *
 */

#ifndef __READRSS_H__
#define __READRSS_H__

#ifdef   __READRSS_C__
#ifndef	lint
static char	*rcs_id_header =
"$Header: C:/user/local/src/readRSS/RCS/readRSS.h 1.1 2003/12/15 10:50:51 tsujimura543 Exp tsujimura543 $";
#endif
#endif


/* RSS version */
#define VER_UNDEFINED   0
#define VER_RSS_1_00    0x01


int readRSS( FILE *input, FILE *output );

int readRSS_1_00( FILE *input, FILE *output, const char *encoding );

char    *dateString( const char *date, const char *encoding );
char    *categoryString( const char *encoding );

#endif  /* __READRSS_H__ */

次回は RSS 0.91 のリーダーを作ってみることにします。

(つづく)

目次

第1回 RSSリーダを作ってみる 巻の1
まずは所信表明。
第2回 RSSリーダを作ってみる 巻の2
RSS 1.0 のリーダーを作成します
第3回 RSSリーダを作ってみる 巻の3
RSS 0.91 のリーダーを作成します
第4回 RSSリーダを作ってみる 巻の4
RSS 2.0 のリーダーを作成します
第5回 RSSリーダを作ってみる 巻の5
Atom 0.3 のリーダーを作成します
第6回 RSSリーダを作ってみる 巻の6
ユーティリティ関数を用意します
第7回 RSSリーダを作ってみる 巻の7
リファクタリングを開始します

RSS とは?

今後の予定

第8回 RSSリーダを作ってみる 巻の8
さらにリファクタリングしてみます
第9回 RSSリーダを作ってみる 巻の9
どんどんリファクタリングします


Copyright (c) 2003, 2010 Oki Software Co., Ltd.