Проблемы с чтением RSS с С# и .net 3.5
Я пытаюсь написать некоторые подпрограммы для чтения RSS-каналов и фидов ATOM с использованием новых подпрограмм, доступных в System.ServiceModel.Syndication, но, к сожалению, Rss20FeedFormatter запускает примерно половину каналов, которые я пытаюсь сделать со следующим исключением:
An error was encountered when parsing a DateTime value in the XML.
Это происходит, когда канал RSS выражает дату публикации в следующем формате:
Thu, 16 Oct 08 14:23:26 -0700
Если канал выражает дату публикации как GMT, все идет хорошо:
Thu, 16 Oct 08 21:23:26 GMT
Если есть способ обойти это с помощью XMLReaderSettings, я его не нашел. Может ли кто-нибудь помочь?
Ответы
Ответ 1
RSS 2.0 форматированные каналы синдикации используют спецификацию даты RFC 822 при сериализации таких элементов, как pubDate и lastBuildDate. Спецификация даты и времени RFC 822, к сожалению, является очень "гибким" синтаксисом для выражения компонента часового пояса DateTime.
Часовой пояс может указываться несколькими способами. "UT" - это универсальное время (ранее называемое "средним временем по Гринвичу" ); "GMT" разрешено в качестве ссылки на Universal Time. Военный стандарт использует один символ для каждой зоны. "Z" - это универсальное время. "A" указывает на час раньше, а "M" указывает на 12 часов раньше; "N" - через час, а "Y" - через 12 часов. Буква "J" не используется. Остальные две формы взяты из стандарта ANSI X3.51-1975. Один позволяет явно указывать величину смещения от UT; другой использует обычные 3-символьные строки для указания часовых поясов в Северной Америке.
Я полагаю, что проблема связана с тем, как обрабатывается компонент zone значения даты и времени RFC 822. Форматирование фида, похоже, не обрабатывает дату-время, в котором используется локальный дифференциал, чтобы указать часовой пояс.
Поскольку RFC 1123 расширяет спецификацию RFC 822, вы можете попробовать использовать DateTimeFormatInfo.RFC1123Pattern ( "r" ) для обработки конвертирования пробламатической даты- или написать собственный код анализа для даты форматирования RFC 822. Другим вариантом будет использование сторонней структуры вместо классов пространства имен System.ServiceModel.Syndication.
Похоже, существуют некоторые известные проблемы с синтаксисом даты и времени и Rss20FeedFormatter, которые находятся в процессе обращения Microsoft.
Ответ 2
Основываясь на обходном пути, опубликованном в отчете об ошибках в Microsoft об этом, я создал XmlReader специально для чтения SyndicationFeeds, которые имеют нестандартные даты,
Код ниже немного отличается от кода в обходном пути на сайте Microsoft. Он также принимает консультативный совет по использованию шаблона RFC 1123.
Вместо простого вызова XmlReader.Create() вам нужно создать XmlReader из Stream. Я использую класс 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, не сделал.
ПРИМЕЧАНИЕ. Один бит настройки, который вам может потребоваться, - это два массива в начале класса. В зависимости от любых посторонних полей, которые может добавить ваш нестандартный канал, вам может потребоваться добавить дополнительные элементы в эти массивы.
Ответ 3
Интересно. Было бы похоже, что форматирование даты и времени не является одним из тех, которые, естественно, ожидаются парсером datetime. Изучив классы фидов, похоже, что вы не можете вставлять в свое собственное соглашение о форматировании для синтаксического анализатора, и они, скорее всего, используют определенную схему проверки подлинности.
Возможно, вы сможете изменить поведение парсера datetime, изменив культуру. Я никогда не делал этого раньше, поэтому не могу сказать, что это сработает.
Еще одна ночь решения - сначала преобразовать корм, который вы пытаетесь прочитать. Вероятно, не самый большой, но он может помочь вам решить проблему.
Удачи.
Ответ 4
Аналогичная проблема сохраняется в .NET 4.0, и я решил работать с XDocument вместо прямого вызова SyndicationFeed. Я описал применяемый метод (специфический для моего проекта здесь). Не могу сказать, что это лучшее решение, но его можно считать "резервным планом" в случае сбоя SyndicationFeed.