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 なら 1、false なら 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 を使います。
SFXXMLDOMParser の Parse 関数で 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;
}



















