【開発】C#で頑張ってRSS1.0を読んでみる

開発ざれごとc#,feed,linq,RSS1.0,xml,シンジケート

feeder

標準の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文書をダウンロードしまくったので、デスクトップがアイコンで埋まってしまった(;・∀・)

開発ざれごとc#,feed,linq,RSS1.0,xml,シンジケート

Posted by nabe