【開発】C#で頑張ってRSS1.0を読んでみる
標準のSyndicateではRSS1.0を扱えないので、XMLとして扱わないといけないので、色々とやってみた。
最初は、無難にXPathでやってたんだが、RSSを配信してる各サイトでテストしてると、書き方があまりにバラけてて、どうにも名前空間定義でぶつかってしまう。
他に良い方法があるかなぁ・・と試行錯誤。
そういや、Linq for XMLがあったのを思い出し(ついつい泥臭いプログラム嗜好になってしまう悪いク癖がある)、Linq構文内でデバッグ止められないですよね?自分が所有するVS2008だけの話?VS2010はOKなのか??なら、買うぞ。金無いけど。
コードがすっきりするんだけどデバッグで止められないのが嫌で、避けがちになってしまう。
色々なRSS1.0を読み込ませながらテストし、対策を繰り返してたら、もう、コードがグチャグチャに。Titleを取るだけなのに、こんな事になってしまう。だって、そういうRSSあるんだもん(;・∀・)
最終的に読める事にはなったが、こんな感じで、ダラダラと長いコードになってしまった。
try
{ rssInfo.Title = xmlDoc.SelectSingleNode("/rdf:RDF/rss:channel/rss:title", nsmgr).InnerText;
}
catch
{
rssInfo.Title = xmlDoc.SelectSingleNode("/rdf:RDF//title", nsmgr).InnerText;
}
なんとかなんないかなぁ・・とLinqでいっそ全書き直しをやってみた。趣味プログラミングだから許される。業務でやったら始末書もんだ。
んで、Linqで書いたら、こんな感じに。スッキリ書けてビックリ。名前空間がグチャグチャ指定されてる恐ろしいRSS文書でも、ちゃんと動作します。
(下:PREタグで書くと、コード編集すんのメンドイので、そのままコピペ)
public static RssInfo ReadRSS1(Uri uri,string strXML)
{
var doc = XDocument.Load(uri.ToString());//var doc = XElement.Load(uri.ToString());
//Loadが失敗したら、strXMLを使うんだぞXNamespace rss = "http://purl.org/rss/1.0/";
XNamespace rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
XNamespace dc = "http://purl.org/dc/elements/1.1/";
XNamespace content = "http://purl.org/rss/1.0/modules/content/";
XNamespace sy = "http://purl.org/rss/1.0/modules/syndication/";
XNamespace cc = "http://web.resource.org/cc/";
//channel
var channel = (from n in doc.Descendants()where n.Name.LocalName == "channel"
select n).FirstOrDefault();
if (channel == null) throw (new Exception());
var title = (from n in channel.Descendants()
where n.Name.LocalName == "title"
select n).FirstOrDefault();
var description = (from n in channel.Descendants()
where n.Name.LocalName == "description"
select n).FirstOrDefault();
var image = (from n in doc.Descendants() //channel外あり得る
where n.Name.LocalName == "image"
select n).FirstOrDefault();
var itList = (from n in channel.Descendants(rdf + "li")
where n.HasAttributes
select n);
var itDocs = (from n in doc.Descendants()
where n.Name.LocalName == "item"
&& n.HasElements && n.HasAttributes && n.Parent.Name.LocalName == "RDF"
select n);
RssInfo riInfo = new RssInfo();
riInfo.RssVer = "RSS1.0";riInfo.Title = title == null ? "" : title.Value;
riInfo.Description = description == null ? "" : description.Value;
riInfo.ImageUrl = "";
if (image != null)
{
if (image.Attribute(rdf + "resource") != null)
riInfo.ImageUrl = image.Attribute(rdf + "resource").Value;
else
if(image.Attribute(rdf + "about") != null)
riInfo.ImageUrl = image.Attribute(rdf + "about").Value;
}
List<RssItem> riItems = new List<RssItem>();
foreach (var it in itList){
//名前空間無視で、合致するitemを取得
try
{
var item = (from n in itDocs
where
(from at in n.Attributes() where at.Name.LocalName == "about" select at.Value).FirstOrDefault()
== (from at in it.Attributes() where at.Name.LocalName == "resource" select at.Value).FirstOrDefault()
select n).FirstOrDefault();
RssItem ri = new RssItem();
ri.Title = item.Element(rss + "title") == null ?
"" : item.Element(rss + "title").Value;
ri.Description = item.Element(rss + "description") == null ?
"" : item.Element(rss + "description").Value;
ri.Link = item.Element(rss + "link") == null ?
null : new Uri(item.Element(rss + "link").Value);
ri.Publisher = item.Element(dc + "publisher") == null ?
"" : item.Element(dc + "publisher").Value;
ri.Auther = item.Element(dc + "creator") == null ?
"" : item.Element(dc + "creator").Value;
ri.Category = item.Element(dc + "subject") == null ?
"" : item.Element(dc + "subject").Value;
ri.Content = item.Element(content + "encoded") == null ?
"" : item.Element(content + "encoded").Value;
ri.Date = stringToDateTime(item.Element(dc + "date").Value);
riItems.Add(ri);
}
catch
{
throw (new Exception("RSS文書が正常で無い可能性があります"));
}
}
riInfo.Items = riItems.ToArray();
return riInfo;
}
もちろん、全文Linqで書けるんでしょうけど、あえてやってません。ブレークできないので。
Itemをゲットするなら、普通なら、elem.Descendants(rss + “item”)と書いばOKなところなんですが、名前空間がどうなるか分からないので、以下のようにその都度、localnameだけで引っ張り出してます。でも、変なやつまでも引っ張らないように、可能な限り条件を指定します。parent要素とか。
(from n in doc.Descendants()
where n.Name.LocalName == "item"&& n.HasElements && n.HasAttributes && n.Parent.Name.LocalName == "RDF"
select n);
その最たるところが、rdf/channel/seq/liと、rdf/itemを引っ掛ける所です。ここも、名前空間ぐちゃ指定のところがあったので、単純に属性値だけでヒットするようにします。
var item = (from n in itDocs
where(from at in n.Attributes() where at.Name.LocalName == "about" select at.Value).FirstOrDefault()
== (from at in it.Attributes() where at.Name.LocalName == "resource" select at.Value).FirstOrDefault()
select n).FirstOrDefault();
やりたい事は、単純にresource値 == about値なんですけどね。ここだけは致し方ない。
なにが標準なのかどうか知りたくて、結局、RSS仕様書を眺める事になってしまった。標準を逸脱してるRSS1.0文書は想像以上に結構あります。RSSリーダーを作ってる方は、よほど苦労されているはずです。(RSS仕様だけに限らず、日付RFC指定方法とか)
さまざまなサイトのRSS1.0文書をダウンロードしまくったので、デスクトップがアイコンで埋まってしまった(;・∀・)
ディスカッション
コメント一覧
まだ、コメントがありません