メンバページ: tsu

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

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

今回は、RSS 2.0 を扱うための処理を RSS リーダーに組み込んでみることにします。なぜ、RSS 0.92 や 0.93 ではなく、いきなり 2.0 かというと、0.92 や 0.93 は 2.0 のサブセットと見なせますので、2.0 のリーダを作れば、0.92 や 0.93 準拠の RSS ファイルにも自動的に対応したことになるからです。2.0 のリーダを使えば 0.91 もそのまま読めるんでは? という意見もあるかとは思いますが、0.91 と 0.92 以降で、微妙に取り扱いが変わった要素(elemnt)がありますので、今後のことを考えて、あえて 0.91 用のリーダは別に用意しました。

一般的に RSS 2.0 の xml ファイルは、以下のような形式をしています (注意: 2.0 は rdf ではありません)。 RSS 1.0 と比べると記述できる情報の種類が大幅に増えています。また、RSS 0.91 に存在した項目数(最大15項目)や文字数(1項目最大500バイト)に関する制限が撤廃されています。
また、RSS 1.0 では日時は ISO8601 形式ですが、RSS 0.92, 0.93, 2.0 では RFC822 形式です。
(実際には、もっといろんなバリエーションが可能なのですが、ここでは簡単のために最も一般的な例を示しています。)

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">
<channel>
  <title>RSS の対象となる Web ページのタイトル</title>
  <link>RSS の対象となる Web ページの URL</link> 
  <description>RSS の対象となる Web ページの概要</description> 
  <language>言語(日本語の場合は ja-jp)</language> 
  <copyright>RSS の対象となる Web ページに関する著作権に関する情報</copyright> 
  <pubDate>RSS の対象となる Web ページの最終更新日時</pubDate> 
  <lastBuildDate>RSS ファイルの生成日時</lastBuildDate> 
  <docs>RSS ファイルの URL</docs>
  <generator>RSS ファイル生成ツール名</generator> 
  <managingEditor>RSS の対象となる Web ページの編集者のemailアドレス</managingEditor>
  <webMaster>RSS の対象となる Web ページの管理者のemailアドレス</webMaster>
  <image>
    <url>当該Web ページ、もしくは主宰者にちなむ画像のURL</url>
    <link&Web ページの URLgt;</link>
    <title>Web ページのタイトルもしくは主宰者の名前、など</title>
  </image>
  <item>
    <title>RSS 生成対象の 最初 の記事の 題名</title> 
    <link>RSS 生成対象の 最初 の記事の URL</link> 
    <description>RSS 生成対象の 最初 の記事の 概要</description> 
    <pubDate>RSS 生成対象の 最初 の記事の 最終更新日時</pubDate>
    <guid>RSS 生成対象の 最初 の記事を特定するための 識別子 または 固定リンク</guid>
  </item>
  <item>
    <title>RSS 生成対象の 2番目 の記事の 題名</title> 
    <link>RSS 生成対象の 2番目 の記事の URL</link> 
    <description>RSS 生成対象の 2番目 の記事の 概要</description> 
    <pubDate>RSS 生成対象の 2番目 の記事の 最終更新日時</pubDate>
    <guid>RSS 生成対象の 2番目 の記事を特定するための 識別子 または 固定リンク</guid>
  </item>
  ・・・・・・・・・・・・(略)・・・・・・・・・・・・
  <item>
    <title>RSS 生成対象の n番目 の記事の 題名</title> 
    <link>RSS 生成対象の n番目 の記事の URL</link> 
    <description>RSS 生成対象の n番目 の記事の 概要</description> 
    <pubDate>RSS 生成対象の n番目 の記事の 最終更新日時</pubDate>
    <guid>RSS 生成対象の n番目 の記事を特定するための 識別子 または 固定リンク</guid>
  </item>
</channel>
</rss>

入力ファイルに

<rss version="0.92">
<rss version="0.93">
<rss version="2.0">

という文字列のいずれかが含まれていたら、RSS 2.0 用のリーダで処理することにします。

RSS 2.0 のリーダー readRSS_2_00() を、これまた やっつけ (^^; で作ると、 以下のようになります。

int readRSS_2_00( FILE *input, FILE *output, int mode, const char *encoding )
{
    char    buf[BUFSIZ], *p, *q, *r;
    int     ret = 0;
    char    title[BUFSIZ];
    char    link[BUFSIZ];
    char    description[BUFSIZ];
    char    language[BUFSIZ];
    char    subject[BUFSIZ];
    char    date[BUFSIZ];
    char    imageUrl[BUFSIZ];
    char    imageLink[BUFSIZ];
    char    imageTitle[BUFSIZ];
    int     cnt   = 0;
    int     image = 0;

    language[0]   = '\0';
    imageUrl[0]   = '\0';
    imageLink[0]  = '\0';
    imageTitle[0] = '\0';

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

        if ( image ) {
            if ( !strncmp( p, "<url>", 5 ) ) {
                q = strchr( p + 5, '<' );
                if ( q ) {
                    strncpy( imageUrl, p + 5, q - (p + 5) );
                    imageUrl[q - (p + 5)] = '\0';
                }
                continue;
            }

            if ( !strncmp( p, "<link>", 6 ) ) {
                q = strchr( p + 6, '<' );
                if ( q ) {
                    strncpy( imageLink, p + 6, q - (p + 6) );
                    imageLink[q - (p + 6)] = '\0';
                }
                continue;
            }

            if ( !strncmp( p, "<title>", 7 ) ) {
                q = strchr( p + 7, '<' );
                if ( q ) {
                    strncpy( imageTitle, p + 7, q - (p + 7) );
                    imageTitle[q - (p + 7)] = '\0';
                }
                continue;
            }
        }

        if ( !strncmp( p, "<title>", 7 ) ) {
            q = strchr( p + 7, '<' );
            if ( q ) {
                strncpy( title, p + 7, q - (p + 7) );
                title[q - (p + 7)] = '\0';
            }
            else {
                strcpy( title, p + 13 );
                while ( ( p = fgets( buf, BUFSIZ - 1, input ) ) != NULL ) {
                    while ( (*p == ' ') || (*p == '\t') )
                        p++;

                    strcat( title, " " );
                    strcat( title, p );
                    if ( ( q = strchr( title, '<' ) ) != NULL ) {
                        *q = '\0';
                        break;
                    }
                }
            }
        }
        else
        if ( !strncmp( p, "<link>", 6 ) ) {
            q = strchr( p + 6, '<' );
            if ( q ) {
                strncpy( link, p + 6, q - (p + 6) );
                link[q - (p + 6)] = '\0';
            }
        }
        else
        if ( !strncmp( p, "<description>", 13 ) ) {
            q = strchr( p + 13, '<' );
            if ( q ) {
                strncpy( description, p + 13, q - (p + 13) );
                description[q - (p + 13)] = '\0';
            }
            else {
                strcpy( description, p + 13 );
                while ( ( p = fgets( buf, BUFSIZ - 1, input ) ) != NULL ) {
                    while ( (*p == ' ') || (*p == '\t') )
                        p++;

                    strcat( description, " " );
                    strcat( description, p );
                    if ( ( q = strchr( description, '<' ) ) != NULL ) {
                        *q = '\0';
                        break;
                    }
                }
            }

            if ( description[0] && (strchr(description, '&') != NULL) ) {
                // 実体参照が含まれる場合
                p = description;
                while ( ( ( q = strchr( p, '&' ) ) != NULL ) &&
                        ( ( r = strchr( q, ';' ) ) != NULL )    ) {
                    if ( !strncmp( q + 1, "lt", 2 ) ) {
                        *q = '<';
                        strcpy( q + 1, r + 1 );
                    }
                    else if ( !strncmp( q + 1, "gt", 2 ) ) {
                        *q = '>';
                        strcpy( q + 1, r + 1 );
                    }
                    else if ( !strncmp( q + 1, "amp", 3 ) ) {
                        *q = '&';
                        strcpy( q + 1, r + 1 );
                    }
                    else if ( !strncmp( q + 1, "nbsp", 4 ) ) {
                        *q = ' ';
                        strcpy( q + 1, r + 1 );
                    }
                    else {
                        strcpy( q, r + 1 );
                    }
                }
            }
        }
        else
        if ( !strncmp( p, "<language>", 10 ) ) {
            q = strchr( p + 10, '<' );
            if ( q ) {
                strncpy( language, p + 10, q - (p + 10) );
                language[q - (p + 10)] = '\0';
            }
        }
        else
        if ( !strncmp( p, "<image>", 7 ) )
            image = 1;
        else
        if ( !strncmp( p, "</image>", 8 ) )
            image = 0;
        else if ( !strncmp( p, "<item>", 6 ) )
            break;
    }

    if ( !(language[0]) ) {
        /*
        if ( !strcmp( encoding, "ISO-8859-1" )  ||
             !strcmp( encoding, "ISO-2022-JP" ) ||
             !strcmp( encoding, "Shift_JIS" )   ||
             !strcmp( encoding, "EUC-JP" )      ||
             !strcmp( encoding, "UTF-8" )          )
            strcpy( language, "ja_jp" );
        else
            strcpy( language, "en_us" );
        */

        strcpy( language, "ja_jp" );
    }

    fprintf( output,
             "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD "
             "HTML 4.01 Transitional//EN\">\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",
             !strcmp( encoding, "ISO-8859-1" ) ? "Shift_JIS"
                                               : encoding    );
    fprintf( output, "<title>%s</title>\n", title );
    fprintf( output, "</head>\n\n<body>\n" );

    if ( imageUrl[0] )
        fprintf( output,
                 "<a href=\"%s\"><img src=\"%s\" alt=\"%s\" "
                 "border=\"0\" align=\"right\"></a>\n",
                 imageLink[0]  ? imageLink  : link,
                 imageUrl,
                 imageTitle[0] ? imageTitle : title );
    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>", 6 ) || (cnt == 0) ) {
            cnt++;

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

            if ( cnt > 1 )
                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, '<' );
                if ( q && !strncmpi( q, "<![CDATA[", 9 ) ) {
                    p += 9;
                    r = strchr( q, '>' );
                    if ( r ) {
                        *r = '\0';
                        while ( *--r == ']' )
                            *r = '\0';
                    }
                    q += 9;
                    strcpy( description, q );
                }
                else if ( q ) {
                    strncpy( description, p + 13, q - (p + 13) );
                    description[q - (p + 13)] = '\0';
                }

                if ( description[0] && (strchr(description, '&') != NULL) ) {
                    // 実体参照が含まれる場合
                    p = description;
                    while ( ( ( q = strchr( p, '&' ) ) != NULL ) &&
                            ( ( r = strchr( q, ';' ) ) != NULL )    ) {
                        if ( !strncmp( q + 1, "lt", 2 ) ) {
                            *q = '<';
                            strcpy( q + 1, r + 1 );
                        }
                        else if ( !strncmp( q + 1, "gt", 2 ) ) {
                            *q = '>';
                            strcpy( q + 1, r + 1 );
                        }
                        else if ( !strncmp( q + 1, "amp", 3 ) ) {
                            *q = '&';
                            strcpy( q + 1, r + 1 );
                        }
                        else if ( !strncmp( q + 1, "nbsp", 4 ) ) {
                            *q = ' ';
                            strcpy( q + 1, r + 1 );
                        }
                        else {
                            strcpy( q, r + 1 );
                        }
                    }
                }
            }
            else
            if ( !strncmp( p, "<category>", 10 ) ) {
                q = strchr( p + 10, '<' );
                strncpy( subject, p + 10, q - (p + 10) );
                subject[q - (p + 10)] = '\0';
            }
            else
            if ( !strncmp( p, "<pubDate>", 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 );
}

readRSS() の RSS 版数判定部分を以下のように書き換えて、 今回作った readRSS_2_00() を組み込めば OK。

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

        if ( !strncmp( p, "<rss version=\"0.91\">", 20 ) ) {
            version = VER_RSS_0_91;
            ret = readRSS_0_91( input, output, mode, encoding );
            break;
        }

        if ( !strncmp( p, "<rss version=\"2.0\">",  19 ) ||
             !strncmp( p, "<rss version=\"0.92\">", 20 ) ||
             !strncmp( p, "<rss version=\"0.93\">", 20 )    ) {
            version = VER_RSS_2_00;
            ret = readRSS_2_00( input, output, mode, encoding );
            break;
        }

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

(つづく)

目次

第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) 2004, 2010 Oki Software Co., Ltd.