ホーム > 製品情報 > SophiaFramework UNIVERSE > チュートリアル > BREW RSS リーダー

RSS リーダー 〜 HTTP と XML の BREW C++ プログラミング 〜

RSS データの解析

RssReader のデータ構造

// RSSItem 構造体 ( アイテム 1 件 ) 
SFMTYPEDEFSTRUCT(RSSItem)
struct RSSItem {
    SFXAnsiString title;
    SFXAnsiString description;
    SFXAnsiString topic;
    SFXDate date;     // 日付をSFXDate形式で格納
    Bool alreadyRead; // 既読かどうかを表すフラグ

    RSSItem(SFXAnsiStringConstRef rsstitle,
        SFXAnsiStringConstRef rssdescription,
        SFXAnsiStringConstRef rsstopic, SFXDateConstRef rssdate,
        Bool rssalreadyRead = false);
};

// RSSFeed クラス ( フィード 1 件 )
SFMTYPEDEFCLASS(RSSFeed)
class RSSFeed {
public:
    SFXAnsiString _title;                // サイトのタイトル
    SFXAnsiString _url;                  // サイトの URL
    SFXDate _date;                       // 最終更新日時
private:
    SFXAnsiString _filename;             // RSS を保存するファイル名
    SFXList<RSSItemPtr> _itemList;       // 複数の RSS アイテムを保存

public:
    RSSFeed(SFXAnsiStringConstRef title,
        SFXAnsiStringConstRef url, SFXDateConstRef date = SFXDate());
    ~RSSFeed(Void);
    SFCError Parse(SFXAnsiStringConstRef xml);
    SFCError SaveToFile(Void);
    Void LoadFromFile(Void);
    Bool Find(RSSItemConstRef item);
    RSSItemPtr GetItem(SInt32 index) { return _itemList.Get(index); }
    SFXAnsiStringConstRef GetFilename(Void) { return _filename; }
    Void SetFilename(SFXAnsiStringConstRef filename) { _filename = filename; }
    SInt32 GetSize(Void) { return _itemList.GetSize(); }

    friend class RSSFeedList;
};

// RSSFeedList クラス ( フィードのリスト ) 
SFMTYPEDEFCLASS(RSSFeedList)
class RSSFeedList {
private:
    SFXArray<RSSFeedPtr> _feedArray; // 複数の RSS フィードを保存

public:
    ~RSSFeedList(Void);
    Bool Add(RSSFeedPtr feed);
    SFCError SaveToFile(Void);
    Void LoadFromFile(Void);
    SInt32 GetSize(Void) { return _feedArray.GetSize(); }
    SInt32 IndexOf(RSSFeedPtr item) { return _feedArray.IndexOf(item); }
    RSSFeedPtr operator[](SInt32 index) { return _feedArray[index]; }
};
※ "SFXList<RSSItemPtr> _itemList;" について

SophiaFramework UNIVERSE の コレクションクラスには 格納できるオブジェクトのサイズが 4 バイトまでなので、 SFXList は、RSSItem ではなく、 RSSItemPtr ( ポインタ ) を格納するリストにしています。

RSSItemPtr への要素の追加
RSSItemPtr item;

// 要素を new で生成

item = ::new RSSItem("title", "desc", "topic", SFXDate::CurrentDate());

// 要素の格納

_itemList.Append(item);
RSSItemPtr への要素の参照
// 最初の要素を参照

_itemList.Get(0);

RSSFeed のメンバ変数のファイルへの書き込み

// RSSFeed クラスをファイル保存するときのデータフォーマット

title<>description<>topic<>date<>alreadyRead\r\n
title<>description<>topic<>date<>alreadyRead\r\n
...

date は UInt32 型の整数、alreadyRead は true なら 1false なら 0 とします。

#define DIRECTORY_NAME "/data"
#define DELIMITER_STR "<>"
#define DELIMITER_LEN 2

SFCError RSSFeed::SaveToFile(Void)
{
    SFCError error;
    SFXPath temp;
    SFXFile file;
    SFXAnsiStringStreamWriter writer;
    RSSItemPtr item;
    SFXPath filename(DIRECTORY_NAME + _filename);
    SFXPath dir(DIRECTORY_NAME);

    // 一時保存のためのパスを取得
    error = SFXFile::GetTemporaryPath(dir, "temp", "", &temp);

    if (error == SFERR_NO_ERROR) {
        error = file.OpenReadWrite(temp); // ファイルを開く
        if (error == SFERR_NO_ERROR) {
            // 書き込みストリームを取得
            error = file.GetStreamWriter(4096, &writer);
            if (error == SFERR_NO_ERROR) {
                // イテレータを取得
                SFXList<RSSItemPtr>::Iterator itor(_itemList.GetFirstIterator());
                while (itor.HasNext()) {  
                    // RSSItemをファイルに書き出す
                    item = itor.GetNext();                   
                    writer << item->title << DELIMITER_STR << item->description
                        << DELIMITER_STR << item->topic << DELIMITER_STR
                        << SFXAnsiString::Format("%u", item->date.AsUInt32())
                        << DELIMITER_STR << ((item->alreadyRead) ? "1" : "0")
                        << "\r\n";
                    // Flush 関数を呼ぶとファイルに書き込まれる
                    writer.Flush();
                }
            }
            file.Close();
        }
    }
    if (error == SFERR_NO_ERROR) {
        // ファイル消去
        SFXFile::Remove(filename);
        // ファイル名変更
        error = SFXFile::Rename(temp, filename);
    }
    return error;
}

SFXFile::GetTemporaryPath 関数

SFXFile::GetTemporaryPath 関数で一時ファイル名を取得しています。

一時的なファイルにデータを書き込んでから本来のファイル名にしています。 このようにすれば、ファイルへの書き込みが途中で失敗しても安全です。

ファイルからの読み込み

Void RSSFeed::LoadFromFile(Void)
{
    SFCError error;
    SFXFile file;
    SFXAnsiStringStreamReader reader;
    SFXAnsiString string;
    SFXPath path(DIRECTORY_NAME + _filename);
    UInt32 size;

    SFXFile::GetSize(path, &size); // ファイルサイズの取得
    error = file.OpenReadOnly(path); // ファイルオープン
    if (error == SFERR_NO_ERROR) { // ファイルオープンに成功したなら
        // 読み取りストリームを取得
        error = file.GetStreamReader(size + 1, &reader);
        if (error == SFERR_NO_ERROR) {
            // ファイルから読み取り
            reader.Fetch();
            // string に格納
            reader.ReadSFXAnsiString(&string);

            SInt32 c1 = 0;
            SInt32 c2, c3, c4, c5, c6;
            while (true) {
                // 行の最初の区切りを探す
                c2 = string.IndexOf(DELIMITER_STR, c1);
                if (c2 < 0) { // 見つからなかったら行がないので終了
                    break;
                }
                c3 = string.IndexOf(DELIMITER_STR, c2 + 1);
                c4 = string.IndexOf(DELIMITER_STR, c3 + 1);
                c5 = string.IndexOf(DELIMITER_STR, c4 + 1);
                c6 = string.IndexOf('\n', c5) + 1;
                SFXAnsiString dateNumber = string.Substring(c4
                    + DELIMITER_LEN, c5);
                SFXDate date;
                // 日時は数字で格納されているので、それを取り込む
                date.Set(dateNumber.ToUInt32());
                RSSItemPtr item = new RSSItem(string.Substring(c1, c2),
                    string.Substring(c2 + DELIMITER_LEN, c3),
                    string.Substring(c3 + DELIMITER_LEN, c4), date,
                    string.Substring(c5 + DELIMITER_LEN, c6).Trim() == "1");
                // リストに加える
                if (_itemList.Append(item) != SFERR_NO_ERROR) {
                    ::delete item;
                    break;
                }
                c1 = c6;
            }
        }
        file.Close();
    }
    return;
}

※ ファイルの場合、Fetch 関数の処理はすぐ完了するので、コールバック関数の登録は不要です。

RSS データの解析

DOM 方式の XML パーサー SFXXMLDOMParser を使います。

SFXXMLDOMParserParse 関数で XML 文書 を解析した結果を GetDocument 関数で取得します。

ノードの処理では、最初の子ノードを取得する GetFirstChild 関数、兄弟ノードを取得する GetNextSibling 関数、ノード名を取得する GetNodeName 関数 などを使います。

SFCError RSSFeed::Parse(SFXAnsiStringConstRef xml)
{
    SFCError error;
    SFXXMLDOMParser parser;
    SFXXMLDocumentPtr root;
    SFXXMLNodePtr child;
    SFXAnsiString title;
    SFXAnsiString description;
    SFXDate date;
    RSSItemPtr item;
    SInt32 listcount;
    SInt32 count = 0;
    SFXAnsiString temp;
    // XML を解析する
    error = parser.Parse(xml);
    // 解析が失敗した場合
    if (error != SFERR_NO_ERROR) return SFERR_FAILED;
    root = parser.GetDocument(); // ルートを取得する
    if (root == null) return SFERR_FAILED;
    // Item タグを探す
    SFXList<SFXXMLNodePtr>* list = root->GetElementsByTagName("channel");
    // Item タグがあった場合
    if (list != null && list->GetSize() > 0) 
    {
        // 要素を取得する
        child = list->Get(0); 
        for (child = child->GetFirstChild() ; 
             child != null && error == SFERR_NO_ERROR ; 
             child = child->GetNextSibling()) 
        {
            if (SFXAnsiString(child->GetNodeName()) == "title") 
            {
                temp = child->GetText();
                if (_title.IsEmptyCString()) 
                {
                    error = SFXTextEncoding::UTF8ToShiftJIS(temp, &_title);
                }
            }
            else if (SFXAnsiString(child->GetNodeName()) == "dc:date") 
            {
                 // 文字列を解析して日付に変換
                 date.Parse("YYYY-MM-DD%Thh:mm:ss+",
                 child->GetText());
            }
        }

        SFXList<SFXXMLNodePtr>* list = root->GetElementsByTagName("item");
        if (list != null && list->GetSize() > 0) 
        {
            for (listcount=0; listcount < (list->GetSize()); listcount++)  
            {
                child = list->Get(listcount); 
                for (child = child->GetFirstChild() ; 
                     child != null && error == SFERR_NO_ERROR ; 
                     child = child->GetNextSibling()) 
                {
                    // title タグなら
                    if (SFXAnsiString(child->GetNodeName()) == "title") 
                    {
                        // ノードのテキストを取得する
                        temp = child->GetText();
                        // UTF-8 を ShiftJIS に変換する
                        error = SFXTextEncoding::UTF8ToShiftJIS(temp, &title);
                    } 
                    // description タグなら
                    else if (SFXAnsiString(child->GetNodeName()) == "description") 
                    {
                        // ノードのテキストを取得する
                        temp = child->GetText();
                        // UTF-8 を ShiftJIS に変換する
                        error = SFXTextEncoding::UTF8ToShiftJIS(temp, &description);
                    }
                    // dc: date タグなら
                    else if (SFXAnsiString(child->GetNodeName()) == "dc: date") 
                    {
                        // 文字列を解析して日付に変換する
                        date.Parse("YYYY-MM-DD%Thh:mm:ss+",
                        child->GetText());
                    }
                }
                item = ::new RSSItem(title, description, date);
                if (item != null) 
                {
                    // 重複がないなら
                    if (!Find(*item)) 
                    {
                        // 要素を挿入
                        error = _itemList.Insert(count, item);
                        if (error == SFERR_NO_ERROR) 
                        {
                            ++count;
                        }
                        else 
                        {
                            ::delete item;
                        }
                    }
                    else 
                    {
                        ::delete item;
                    }
                }
                else 
                {
                    error = SFERR_NO_MEMORY;
                }
            }
        }
    }
    else 
    {
        TRACE ("list empty");
    }
    // エラー処理
    if (error != SFERR_NO_ERROR) 
    {
        _title = "解析失敗";
    }
    SFXHelper::dbgprintf("[SGXAWSParser] ParseXML END %d", error);
    if (error == SFERR_NO_ERROR) 
    {
        // ファイルに保存
        error = RSSFeed::SaveToFile();
    }
    return error;
}