C#および.net 3.5でRSSを読み取る際の問題
-
03-07-2019 - |
質問
System.ServiceModel.Syndicationで使用可能な新しいルーチンを使用してRSSおよびATOMフィードを読み取るためのいくつかのルーチンを作成しようとしましたが、残念ながらRss20FeedFormatterは約半分のフィードで爆弾を吐き出します。
An error was encountered when parsing a DateTime value in the XML.
これは、RSSフィードが次の形式で公開日を表すたびに発生するようです:
木、08 10月16日14:23:26 -0700
フィードで公開日がGMTとして表現されている場合、問題はありません:
木、08 Oct 10 21:23:26 GMT
XMLReaderSettingsでこれを回避する方法がある場合、私はそれを見つけていません。誰でも支援できますか?
解決
RSS 2.0形式のシンジケーションフィードは、シリアル化するときに RFC 822日時指定を利用します pubDate や lastBuildDate などの要素。残念ながら、RFC 822の日時仕様は、DateTimeのタイムゾーンコンポーネントを表現するための非常に「柔軟な」構文です。
タイムゾーンはいくつかの方法で示されます。 " UT"は世界時(以前は「グリニッジ標準時」と呼ばれていました)です。 " GMT"世界時への参照として許可されています。軍事基準では、ゾーンごとに1つのキャラクターを使用しています。 " Z"世界時です。 " A"は1時間前を示し、「M」は12時間前を示します。 " N" 1時間後、「Y」は12時間後です。 「J」という文字使用されません。残りの2つの形式は、ANSI標準X3.51-1975から取得されます。 UTからのオフセットの量を明示的に示すことができます。もう1つは、北米のタイムゾーンを示すために一般的な3文字の文字列を使用しています。
この問題には、RFC 822日時値の zone コンポーネントの処理方法が関係していると思います。フィードフォーマッタは、ローカル差分を使用してタイムゾーンを示す日時を処理していないようです。
RFC 1123がRFC 822仕様を拡張しているため、 DateTimeFormatInfo.RFC1123Pattern (" r")を使用して、実際の日付時刻の変換を処理するか、RFC 822形式の日付の独自の解析コードを記述します。別のオプションは、System.ServiceModel.Syndication名前空間クラスの代わりにサードパーティフレームワークを使用することです。
日付に関する既知の問題があるようです。マイクロソフトによる対応の過程にある時間解析とRss20FeedFormatter。
他のヒント
バグレポートに投稿された回避策に基づくこれについてマイクロソフトに、標準外の日付を持つSyndicationFeedsを読み取るためのXmlReaderを作成しました。
以下のコードは、Microsoftのサイトでの回避策のコードとわずかに異なります。また、異議申し立てのアドバイスも必要です。 RFC 1123パターンを使用します。
XmlReader.Create()を呼び出すだけでなく、StreamからXmlReaderを作成する必要があります。 WebClientクラスを使用してそのストリームを取得します。
WebClient client = new WebClient();
using (XmlReader reader = new SyndicationFeedXmlReader(client.OpenRead(feedUrl)))
{
SyndicationFeed feed = SyndicationFeed.Load(reader);
....
//do things with the feed
....
}
以下はSyndicationFeedXmlReaderのコードです。
public class SyndicationFeedXmlReader : XmlTextReader
{
readonly string[] Rss20DateTimeHints = { "pubDate" };
readonly string[] Atom10DateTimeHints = { "updated", "published", "lastBuildDate" };
private bool isRss2DateTime = false;
private bool isAtomDateTime = false;
public SyndicationFeedXmlReader(Stream stream) : base(stream) { }
public override bool IsStartElement(string localname, string ns)
{
isRss2DateTime = false;
isAtomDateTime = false;
if (Rss20DateTimeHints.Contains(localname)) isRss2DateTime = true;
if (Atom10DateTimeHints.Contains(localname)) isAtomDateTime = true;
return base.IsStartElement(localname, ns);
}
public override string ReadString()
{
string dateVal = base.ReadString();
try
{
if (isRss2DateTime)
{
MethodInfo objMethod = typeof(Rss20FeedFormatter).GetMethod("DateFromString", BindingFlags.NonPublic | BindingFlags.Static);
Debug.Assert(objMethod != null);
objMethod.Invoke(null, new object[] { dateVal, this });
}
if (isAtomDateTime)
{
MethodInfo objMethod = typeof(Atom10FeedFormatter).GetMethod("DateFromString", BindingFlags.NonPublic | BindingFlags.Instance);
Debug.Assert(objMethod != null);
objMethod.Invoke(new Atom10FeedFormatter(), new object[] { dateVal, this });
}
}
catch (TargetInvocationException)
{
DateTimeFormatInfo dtfi = CultureInfo.CurrentCulture.DateTimeFormat;
return DateTimeOffset.UtcNow.ToString(dtfi.RFC1123Pattern);
}
return dateVal;
}
}
繰り返しますが、これは、上記のリンクのMicrosoftサイトに投稿された回避策からほぼ正確にコピーされます。 ...ただし、これは私のために機能し、Microsoftに投稿されたものは機能しませんでした。
注:クラスの開始時に2つの配列をカスタマイズする必要がある場合があります。非標準フィードが追加する可能性のある無関係なフィールドによっては、それらの配列にさらにアイテムを追加する必要があります。
興味深い。日付時刻の書式設定は、日付時刻パーサーが当然期待するものの1つではないようです。フィードクラスを確認した後、パーサーの独自の書式設定規則を挿入できるようには見えず、フィールを検証するために特定のスキームを使用する可能性があります。
カルチャを変更することにより、日付時刻パーサーの動作を変更できる場合があります。 。私は前にそれをやったことがないので、それがうまくいくと確信することはできません。
もう1つの解決策は、最初に読み込もうとしているフィードを変換することです。おそらく最大ではありませんが、問題を回避できる可能性があります。
がんばって。
.NET 4.0でも同様の問題が解決しないため、 SyndicationFeed を直接呼び出す代わりに、 XDocument を使用することにしました。適用した方法を説明しました(私のプロジェクトこちらに固有)。それが最善の解決策であるとは言えませんが、確かに「バックアップ計画」と考えることができます。 SyndicationFeed が失敗した場合。