[C#] Rss情報読み込み時にエラーが発生する場合の対応


はじめに

前回のRSS情報読み込み処理で、読み込みエラー(XMLExceptionエラー)が発生し、読み込めないサイトがありました。

C#を利用してRSSフィードを読み込む | FEELD BLOG

「追加情報:‘’ (0xXX) は無効な文字です。行YY, 位置ZZ。」

今回は、読み込み時に「System.Xml.XmlExcepion」エラーが発生する場合の対応を今回はメモしておきます。

XMlの無効な文字と有効な文字

今回発生した、例外エラーはXML内に改行やタブ以外の制御文字などが含まれていることで発生する現象となります。

XMLでは以下の文字が有効な文字となります。

・#x9
・#xA
・#xD
・#x20-#D7FF
・#xE000-#xFFFD
・#x10000-#x10FFFF

対応策

エラーを発生させないことが目的なので、利用できない文字列削除して読み込みを行うようにします。
まずは、取得したXMLデータ(文字列)をChar方で読み込み、対応するコード以外を切り落としていきます。

/// <summary>
/// XMLの無効な文字を削除する
/// </summary>
/// <param name="xml">xmlデータ</param>
/// <returns>サニタイズ済みXMLデータ</returns>
private static string XmlSanitize(string xml)
{
    StringBuilder sb = new StringBuilder();
    if (string.IsNullOrEmpty(xml) == false)
    {
        foreach (Char c in xml)
        {
            int code = (int)c;
            if (code == 0x9 || code == 0xa || code == 0xd ||
                (0x20 <= code && code <= 0xd7ff) ||
                (0xe000 <= code && code <= 0xfffd) ||
                (0x10000 <= code && code <= 0x10ffff))
            {
                sb.Append(c);
            }
        }
    }
    return sb.ToString();
}

制御可能な文字列のみのXMLを再作成したところで、実際にXML形式で読み込みを行なってみます。

前回の部分を一部変更します。

string rss = "https://feeld-uni.com/feed";

// ここを変更します。
//XElement element = XElement.Load(rss);

// 変更後
WebClient wc = new WebClient();
wc.Encoding = Encoding.UTF8;
string res = wc.DownloadString(rss);
XDocument xd = XDocument.Parse(XmlSanitize(res));
XElement element = xd.Root;

直接URLをXElement.Loadで取得せず、一度文字列として情報を取得します。
その後取得したXMLデータをサニタイズしXDocumentとしてパースします。
パースしたXDocumentのルートをXElementとして利用します。

以下が変更した結果のソースとなります。

/// <summary>
/// RSS基本情報
/// </summary>
private class RssInfo
{
    public string Title { get; set; }

    public string Description { get; set; }

    public string Link { get; set; }

    public string LastBuildDate { get; set; }
}

/// <summary>
/// RSSのITEMセクション情報プロパティ
/// </summary>
private class RssItemInfo
{
    public string Title { get; set; }

    public string Link { get; set; }

    public string PubDate { get; set; }
}

static void Main(string[] args)
{
    Console.WriteLine($"RSS情報を読み込む");

    // 取得する記事の件数
    int readCnt = 3;

    try
    {
        string rss = "http://jp.sake-times.com/feed";

        WebClient wc = new WebClient();
        wc.Encoding = Encoding.UTF8;
        string res = wc.DownloadString(rss);
        XDocument xd = XDocument.Parse(XmlSanitize(res));

        XElement element = xd.Root;

        // channelを取得する・・・(1)
        XElement channelElement = element.Element("channel");

        RssInfo rssInfo = new RssInfo();
        rssInfo.Title = channelElement.Element("title").Value;
        rssInfo.Description = channelElement.Element("description").Value;
        rssInfo.Link = channelElement.Element("link").Value;
        rssInfo.LastBuildDate = channelElement.Element("lastBuildDate").Value;

        // itemを取得する・・・(2)
        IEnumerable<XElement> elementItems = channelElement.Elements("item");

        List<RssItemInfo> rssItemInfos = new List<RssItemInfo>();
        int cnt = 0;
        foreach(XElement elmItem in elementItems)
        {
            // 取得記事数が指定数に到達したらループ処理終了
            if (cnt == readCnt)
            {
                break;
            }
            RssItemInfo itemInfo = new RssItemInfo();
            itemInfo.Title = elmItem.Element("title").Value;
            itemInfo.Link = elmItem.Element("link").Value;
            itemInfo.PubDate = elmItem.Element("pubDate").Value;
            rssItemInfos.Add(itemInfo);
            cnt++;
        }

        // RSS情報を出力・・・(3)
        Console.WriteLine("------------------------------------------------");
        Console.WriteLine("RSS Information");
        Console.WriteLine("------------------------------------------------");
        Console.WriteLine($"Title         : {rssInfo.Title}");
        Console.WriteLine($"Description   : {rssInfo.Description}");
        Console.WriteLine($"Link          : {rssInfo.Link}");
        Console.WriteLine($"LastBuildDate : {rssInfo.LastBuildDate}");
        Console.WriteLine();

        Console.WriteLine("------------------------------------------------");
        Console.WriteLine("RSS Item Information");
        Console.WriteLine("------------------------------------------------");

        foreach(RssItemInfo itemInfo in rssItemInfos)
        {
            Console.WriteLine($"Title         : {itemInfo.Title}");
            Console.WriteLine($"PubDate       : {itemInfo.PubDate}");
            Console.WriteLine($"Link          : {itemInfo.Link}");
            Console.WriteLine();
        }

    }
    catch(Exception e)
    {
        Console.WriteLine(e.Message);
        Console.WriteLine(e.StackTrace);
    }
}

/// <summary>
/// XMLの無効な文字を削除する
/// </summary>
/// <param name="xml">xmlデータ</param>
/// <returns>サニタイズ済みXMLデータ</returns>
private static string XmlSanitize(string xml)
{
    StringBuilder sb = new StringBuilder();
    if (string.IsNullOrEmpty(xml) == false)
    {
        foreach (Char c in xml)
        {
            int code = (int)c;
            if (code == 0x9 || code == 0xa || code == 0xd ||
                (0x20 <= code && code <= 0xd7ff) ||
                (0xe000 <= code && code <= 0xfffd) ||
                (0x10000 <= code && code <= 0x10ffff))
            {
                sb.Append(c);
            }
        }
    }
    return sb.ToString();
}

結果は前回と同じ結果で取得ができます。

RSS情報を読み込む
------------------------------------------------
RSS Information
------------------------------------------------
Title         : FEELD BLOG
Description   : FEEL + FIELD = FEELD. 感じたままビジネスからプログラミング・デザイン等 日々興味を持った分野の情報を配信しています
Link          : https://feeld-uni.com
LastBuildDate : Sat, 21 Dec 2019 13:43:10 +0000

------------------------------------------------
RSS Item Information
------------------------------------------------
Title         : Visual Studio for MacでC#コンソールアプリを作るまで
PubDate       : Sat, 21 Dec 2019 13:43:10 +0000
Link          : https://feeld-uni.com/entry/2019/12/21/224310

Title         : XMLデータをDataSetに読み込む方法
PubDate       : Sun, 15 Dec 2019 11:48:33 +0000
Link          : https://feeld-uni.com/entry/2019/12/15/204833

Title         : [C#] List型データをSqlBulkCopyする
PubDate       : Wed, 06 Nov 2019 12:29:58 +0000
Link          : https://feeld-uni.com/entry/2019/11/06/212958

これで、ほとんどのRSS Feedデータが読み込めるようになったかと思います。
次回は、RSS情報を標準出力ではなく、別の形でアウトプットしようと思います。