1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
4 #pragma warning disable 1634, 1691
5 namespace System.ServiceModel.Syndication
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Diagnostics.CodeAnalysis;
11 using System.Globalization;
13 using System.ServiceModel.Diagnostics;
16 using System.Xml.Schema;
17 using System.Xml.Serialization;
18 using DiagnosticUtility = System.ServiceModel.DiagnosticUtility;
19 using System.Runtime.CompilerServices;
21 [TypeForwardedFrom("System.ServiceModel.Web, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
22 [XmlRoot(ElementName = Rss20Constants.RssTag, Namespace = Rss20Constants.Rss20Namespace)]
23 public class Rss20FeedFormatter : SyndicationFeedFormatter, IXmlSerializable
25 static readonly XmlQualifiedName Rss20Domain = new XmlQualifiedName(Rss20Constants.DomainTag, string.Empty);
26 static readonly XmlQualifiedName Rss20Length = new XmlQualifiedName(Rss20Constants.LengthTag, string.Empty);
27 static readonly XmlQualifiedName Rss20Type = new XmlQualifiedName(Rss20Constants.TypeTag, string.Empty);
28 static readonly XmlQualifiedName Rss20Url = new XmlQualifiedName(Rss20Constants.UrlTag, string.Empty);
29 const string Rfc822OutputLocalDateTimeFormat = "ddd, dd MMM yyyy HH:mm:ss zzz";
30 const string Rfc822OutputUtcDateTimeFormat = "ddd, dd MMM yyyy HH:mm:ss Z";
32 Atom10FeedFormatter atomSerializer;
35 bool preserveAttributeExtensions;
36 bool preserveElementExtensions;
37 bool serializeExtensionsAsAtom;
39 public Rss20FeedFormatter()
40 : this(typeof(SyndicationFeed))
44 public Rss20FeedFormatter(Type feedTypeToCreate)
47 if (feedTypeToCreate == null)
49 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("feedTypeToCreate");
51 if (!typeof(SyndicationFeed).IsAssignableFrom(feedTypeToCreate))
53 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("feedTypeToCreate",
54 SR.GetString(SR.InvalidObjectTypePassed, "feedTypeToCreate", "SyndicationFeed"));
56 this.serializeExtensionsAsAtom = true;
57 this.maxExtensionSize = int.MaxValue;
58 this.preserveElementExtensions = true;
59 this.preserveAttributeExtensions = true;
60 this.atomSerializer = new Atom10FeedFormatter(feedTypeToCreate);
61 this.feedType = feedTypeToCreate;
64 public Rss20FeedFormatter(SyndicationFeed feedToWrite)
65 : this(feedToWrite, true)
69 public Rss20FeedFormatter(SyndicationFeed feedToWrite, bool serializeExtensionsAsAtom)
72 // No need to check that the parameter passed is valid - it is checked by the c'tor of the base class
73 this.serializeExtensionsAsAtom = serializeExtensionsAsAtom;
74 this.maxExtensionSize = int.MaxValue;
75 this.preserveElementExtensions = true;
76 this.preserveAttributeExtensions = true;
77 this.atomSerializer = new Atom10FeedFormatter(this.Feed);
78 this.feedType = feedToWrite.GetType();
81 public bool PreserveAttributeExtensions
83 get { return this.preserveAttributeExtensions; }
84 set { this.preserveAttributeExtensions = value; }
87 public bool PreserveElementExtensions
89 get { return this.preserveElementExtensions; }
90 set { this.preserveElementExtensions = value; }
93 public bool SerializeExtensionsAsAtom
95 get { return this.serializeExtensionsAsAtom; }
96 set { this.serializeExtensionsAsAtom = value; }
99 public override string Version
101 get { return SyndicationVersions.Rss20; }
104 protected Type FeedType
108 return this.feedType;
112 public override bool CanRead(XmlReader reader)
116 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
118 return reader.IsStartElement(Rss20Constants.RssTag, Rss20Constants.Rss20Namespace);
121 [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "The IXmlSerializable implementation is only for exposing under WCF DataContractSerializer. The funcionality is exposed to derived class through the ReadFrom\\WriteTo methods")]
122 XmlSchema IXmlSerializable.GetSchema()
127 [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "The IXmlSerializable implementation is only for exposing under WCF DataContractSerializer. The funcionality is exposed to derived class through the ReadFrom\\WriteTo methods")]
128 void IXmlSerializable.ReadXml(XmlReader reader)
132 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
134 TraceFeedReadBegin();
139 [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "The IXmlSerializable implementation is only for exposing under WCF DataContractSerializer. The funcionality is exposed to derived class through the ReadFrom\\WriteTo methods")]
140 void IXmlSerializable.WriteXml(XmlWriter writer)
144 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
146 TraceFeedWriteBegin();
151 public override void ReadFrom(XmlReader reader)
153 TraceFeedReadBegin();
154 if (!CanRead(reader))
156 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.UnknownFeedXml, reader.LocalName, reader.NamespaceURI)));
162 public override void WriteTo(XmlWriter writer)
166 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
168 TraceFeedWriteBegin();
169 writer.WriteStartElement(Rss20Constants.RssTag, Rss20Constants.Rss20Namespace);
171 writer.WriteEndElement();
175 protected internal override void SetFeed(SyndicationFeed feed)
178 this.atomSerializer.SetFeed(this.Feed);
181 internal static void TraceExtensionsIgnoredOnWrite(string message)
183 if (DiagnosticUtility.ShouldTraceInformation)
185 TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.SyndicationProtocolElementIgnoredOnWrite, SR.GetString(message));
189 internal void ReadItemFrom(XmlReader reader, SyndicationItem result)
191 ReadItemFrom(reader, result, null);
194 internal void WriteItemContents(XmlWriter writer, SyndicationItem item)
196 WriteItemContents(writer, item, null);
199 protected override SyndicationFeed CreateFeedInstance()
201 return SyndicationFeedFormatter.CreateFeedInstance(this.feedType);
204 protected virtual SyndicationItem ReadItem(XmlReader reader, SyndicationFeed feed)
208 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("feed");
212 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
214 SyndicationItem item = CreateItem(feed);
215 TraceItemReadBegin();
216 ReadItemFrom(reader, item, feed.BaseUri);
221 [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "The out parameter is needed to enable implementations that read in items from the stream on demand")]
222 protected virtual IEnumerable<SyndicationItem> ReadItems(XmlReader reader, SyndicationFeed feed, out bool areAllItemsRead)
226 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("feed");
230 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
232 NullNotAllowedCollection<SyndicationItem> items = new NullNotAllowedCollection<SyndicationItem>();
233 while (reader.IsStartElement(Rss20Constants.ItemTag, Rss20Constants.Rss20Namespace))
235 items.Add(ReadItem(reader, feed));
237 areAllItemsRead = true;
241 protected virtual void WriteItem(XmlWriter writer, SyndicationItem item, Uri feedBaseUri)
243 TraceItemWriteBegin();
244 writer.WriteStartElement(Rss20Constants.ItemTag, Rss20Constants.Rss20Namespace);
245 WriteItemContents(writer, item, feedBaseUri);
246 writer.WriteEndElement();
250 protected virtual void WriteItems(XmlWriter writer, IEnumerable<SyndicationItem> items, Uri feedBaseUri)
256 foreach (SyndicationItem item in items)
258 this.WriteItem(writer, item, feedBaseUri);
262 static DateTimeOffset DateFromString(string dateTimeString, XmlReader reader)
264 StringBuilder dateTimeStringBuilder = new StringBuilder(dateTimeString.Trim());
265 if (dateTimeStringBuilder.Length < 18)
267 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
268 new XmlException(FeedUtils.AddLineInfo(reader,
269 SR.ErrorParsingDateTime)));
271 if (dateTimeStringBuilder[3] == ',')
273 // There is a leading (e.g.) "Tue, ", strip it off
274 dateTimeStringBuilder.Remove(0, 4);
275 // There's supposed to be a space here but some implementations dont have one
276 RemoveExtraWhiteSpaceAtStart(dateTimeStringBuilder);
278 ReplaceMultipleWhiteSpaceWithSingleWhiteSpace(dateTimeStringBuilder);
279 if (char.IsDigit(dateTimeStringBuilder[1]))
281 // two-digit day, we are good
285 dateTimeStringBuilder.Insert(0, '0');
287 if (dateTimeStringBuilder.Length < 19)
289 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
290 new XmlException(FeedUtils.AddLineInfo(reader,
291 SR.ErrorParsingDateTime)));
293 bool thereAreSeconds = (dateTimeStringBuilder[17] == ':');
294 int timeZoneStartIndex;
297 timeZoneStartIndex = 21;
301 timeZoneStartIndex = 18;
303 string timeZoneSuffix = dateTimeStringBuilder.ToString().Substring(timeZoneStartIndex);
304 dateTimeStringBuilder.Remove(timeZoneStartIndex, dateTimeStringBuilder.Length - timeZoneStartIndex);
306 dateTimeStringBuilder.Append(NormalizeTimeZone(timeZoneSuffix, out isUtc));
307 string wellFormattedString = dateTimeStringBuilder.ToString();
309 DateTimeOffset theTime;
313 parseFormat = "dd MMM yyyy HH:mm:ss zzz";
317 parseFormat = "dd MMM yyyy HH:mm zzz";
319 if (DateTimeOffset.TryParseExact(wellFormattedString, parseFormat,
320 CultureInfo.InvariantCulture.DateTimeFormat,
321 (isUtc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), out theTime))
325 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
326 new XmlException(FeedUtils.AddLineInfo(reader,
327 SR.ErrorParsingDateTime)));
330 static string NormalizeTimeZone(string rfc822TimeZone, out bool isUtc)
333 // return a string in "-08:00" format
334 if (rfc822TimeZone[0] == '+' || rfc822TimeZone[0] == '-')
336 // the time zone is supposed to be 4 digits but some feeds omit the initial 0
337 StringBuilder result = new StringBuilder(rfc822TimeZone);
338 if (result.Length == 4)
340 // the timezone is +/-HMM. Convert to +/-HHMM
341 result.Insert(1, '0');
343 result.Insert(3, ':');
344 return result.ToString();
346 switch (rfc822TimeZone)
415 static void RemoveExtraWhiteSpaceAtStart(StringBuilder stringBuilder)
418 while (i < stringBuilder.Length)
420 if (!char.IsWhiteSpace(stringBuilder[i]))
428 stringBuilder.Remove(0, i);
432 static void ReplaceMultipleWhiteSpaceWithSingleWhiteSpace(StringBuilder builder)
435 int whiteSpaceStart = -1;
436 while (index < builder.Length)
438 if (char.IsWhiteSpace(builder[index]))
440 if (whiteSpaceStart < 0)
442 whiteSpaceStart = index;
443 // normalize all white spaces to be ' ' so that the date time parsing works
444 builder[index] = ' ';
447 else if (whiteSpaceStart >= 0)
449 if (index > whiteSpaceStart + 1)
451 // there are at least 2 spaces... replace by 1
452 builder.Remove(whiteSpaceStart, index - whiteSpaceStart - 1);
453 index = whiteSpaceStart + 1;
455 whiteSpaceStart = -1;
459 // we have already trimmed the start and end so there cannot be a trail of white spaces in the end
460 Fx.Assert(builder.Length == 0 || builder[builder.Length - 1] != ' ', "The string builder doesnt end in a white space");
463 string AsString(DateTimeOffset dateTime)
465 if (dateTime.Offset == Atom10FeedFormatter.zeroOffset)
467 return dateTime.ToUniversalTime().ToString(Rfc822OutputUtcDateTimeFormat, CultureInfo.InvariantCulture);
471 StringBuilder sb = new StringBuilder(dateTime.ToString(Rfc822OutputLocalDateTimeFormat, CultureInfo.InvariantCulture));
472 // the zzz in Rfc822OutputLocalDateTimeFormat makes the timezone e.g. "-08:00" but we require e.g. "-0800" without the ':'
473 sb.Remove(sb.Length - 3, 1);
474 return sb.ToString();
479 SyndicationLink ReadAlternateLink(XmlReader reader, Uri baseUri)
481 SyndicationLink link = new SyndicationLink();
482 link.BaseUri = baseUri;
483 link.RelationshipType = Atom10Constants.AlternateTag;
484 if (reader.HasAttributes)
486 while (reader.MoveToNextAttribute())
488 if (reader.LocalName == "base" && reader.NamespaceURI == Atom10FeedFormatter.XmlNs)
490 link.BaseUri = FeedUtils.CombineXmlBase(link.BaseUri, reader.Value);
492 else if (!FeedUtils.IsXmlns(reader.LocalName, reader.NamespaceURI))
494 if (this.PreserveAttributeExtensions)
496 link.AttributeExtensions.Add(new XmlQualifiedName(reader.LocalName, reader.NamespaceURI), reader.Value);
500 TraceSyndicationElementIgnoredOnRead(reader);
505 string uri = reader.ReadElementString();
506 link.Uri = new Uri(uri, UriKind.RelativeOrAbsolute);
510 SyndicationCategory ReadCategory(XmlReader reader, SyndicationFeed feed)
512 SyndicationCategory result = CreateCategory(feed);
513 ReadCategory(reader, result);
517 SyndicationCategory ReadCategory(XmlReader reader, SyndicationItem item)
519 SyndicationCategory result = CreateCategory(item);
520 ReadCategory(reader, result);
524 void ReadCategory(XmlReader reader, SyndicationCategory category)
526 bool isEmpty = reader.IsEmptyElement;
527 if (reader.HasAttributes)
529 while (reader.MoveToNextAttribute())
531 string ns = reader.NamespaceURI;
532 string name = reader.LocalName;
533 if (FeedUtils.IsXmlns(name, ns))
537 string val = reader.Value;
538 if (name == Rss20Constants.DomainTag && ns == Rss20Constants.Rss20Namespace)
540 category.Scheme = val;
542 else if (!TryParseAttribute(name, ns, val, category, this.Version))
544 if (this.preserveAttributeExtensions)
546 category.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
550 TraceSyndicationElementIgnoredOnRead(reader);
555 reader.ReadStartElement(Rss20Constants.CategoryTag, Rss20Constants.Rss20Namespace);
558 category.Name = reader.ReadString();
559 reader.ReadEndElement();
563 void ReadFeed(XmlReader reader)
565 SetFeed(CreateFeedInstance());
566 ReadXml(reader, this.Feed);
569 void ReadItemFrom(XmlReader reader, SyndicationItem result, Uri feedBaseUri)
573 result.BaseUri = feedBaseUri;
574 reader.MoveToContent();
575 bool isEmpty = reader.IsEmptyElement;
576 if (reader.HasAttributes)
578 while (reader.MoveToNextAttribute())
580 string ns = reader.NamespaceURI;
581 string name = reader.LocalName;
582 if (name == "base" && ns == Atom10FeedFormatter.XmlNs)
584 result.BaseUri = FeedUtils.CombineXmlBase(result.BaseUri, reader.Value);
587 if (FeedUtils.IsXmlns(name, ns) || FeedUtils.IsXmlSchemaType(name, ns))
591 string val = reader.Value;
592 if (!TryParseAttribute(name, ns, val, result, this.Version))
594 if (this.preserveAttributeExtensions)
596 result.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
600 TraceSyndicationElementIgnoredOnRead(reader);
605 reader.ReadStartElement();
608 string fallbackAlternateLink = null;
609 XmlDictionaryWriter extWriter = null;
610 bool readAlternateLink = false;
613 XmlBuffer buffer = null;
614 while (reader.IsStartElement())
616 if (reader.IsStartElement(Rss20Constants.TitleTag, Rss20Constants.Rss20Namespace))
618 result.Title = new TextSyndicationContent(reader.ReadElementString());
620 else if (reader.IsStartElement(Rss20Constants.LinkTag, Rss20Constants.Rss20Namespace))
622 result.Links.Add(ReadAlternateLink(reader, result.BaseUri));
623 readAlternateLink = true;
625 else if (reader.IsStartElement(Rss20Constants.DescriptionTag, Rss20Constants.Rss20Namespace))
627 result.Summary = new TextSyndicationContent(reader.ReadElementString());
629 else if (reader.IsStartElement(Rss20Constants.AuthorTag, Rss20Constants.Rss20Namespace))
631 result.Authors.Add(ReadPerson(reader, result));
633 else if (reader.IsStartElement(Rss20Constants.CategoryTag, Rss20Constants.Rss20Namespace))
635 result.Categories.Add(ReadCategory(reader, result));
637 else if (reader.IsStartElement(Rss20Constants.EnclosureTag, Rss20Constants.Rss20Namespace))
639 result.Links.Add(ReadMediaEnclosure(reader, result.BaseUri));
641 else if (reader.IsStartElement(Rss20Constants.GuidTag, Rss20Constants.Rss20Namespace))
643 bool isPermalink = true;
644 string permalinkString = reader.GetAttribute(Rss20Constants.IsPermaLinkTag, Rss20Constants.Rss20Namespace);
645 if ((permalinkString != null) && (permalinkString.ToUpperInvariant() == "FALSE"))
649 result.Id = reader.ReadElementString();
652 fallbackAlternateLink = result.Id;
655 else if (reader.IsStartElement(Rss20Constants.PubDateTag, Rss20Constants.Rss20Namespace))
657 bool canReadContent = !reader.IsEmptyElement;
658 reader.ReadStartElement();
661 string str = reader.ReadString();
662 if (!string.IsNullOrEmpty(str))
664 result.PublishDate = DateFromString(str, reader);
666 reader.ReadEndElement();
669 else if (reader.IsStartElement(Rss20Constants.SourceTag, Rss20Constants.Rss20Namespace))
671 SyndicationFeed feed = new SyndicationFeed();
672 if (reader.HasAttributes)
674 while (reader.MoveToNextAttribute())
676 string ns = reader.NamespaceURI;
677 string name = reader.LocalName;
678 if (FeedUtils.IsXmlns(name, ns))
682 string val = reader.Value;
683 if (name == Rss20Constants.UrlTag && ns == Rss20Constants.Rss20Namespace)
685 feed.Links.Add(SyndicationLink.CreateSelfLink(new Uri(val, UriKind.RelativeOrAbsolute)));
687 else if (!FeedUtils.IsXmlns(name, ns))
689 if (this.preserveAttributeExtensions)
691 feed.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
695 TraceSyndicationElementIgnoredOnRead(reader);
700 string feedTitle = reader.ReadElementString();
701 feed.Title = new TextSyndicationContent(feedTitle);
702 result.SourceFeed = feed;
706 bool parsedExtension = this.serializeExtensionsAsAtom && this.atomSerializer.TryParseItemElementFrom(reader, result);
707 if (!parsedExtension)
709 parsedExtension = TryParseElement(reader, result, this.Version);
711 if (!parsedExtension)
713 if (this.preserveElementExtensions)
715 CreateBufferIfRequiredAndWriteNode(ref buffer, ref extWriter, reader, this.maxExtensionSize);
719 TraceSyndicationElementIgnoredOnRead(reader);
725 LoadElementExtensions(buffer, extWriter, result);
729 if (extWriter != null)
731 ((IDisposable) extWriter).Dispose();
734 reader.ReadEndElement(); // item
735 if (!readAlternateLink && fallbackAlternateLink != null)
737 result.Links.Add(SyndicationLink.CreateAlternateLink(new Uri(fallbackAlternateLink, UriKind.RelativeOrAbsolute)));
738 readAlternateLink = true;
741 // if there's no content and no alternate link set the summary as the item content
742 if (result.Content == null && !readAlternateLink)
744 result.Content = result.Summary;
745 result.Summary = null;
749 catch (FormatException e)
751 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingItem), e));
753 catch (ArgumentException e)
755 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingItem), e));
759 SyndicationLink ReadMediaEnclosure(XmlReader reader, Uri baseUri)
761 SyndicationLink link = new SyndicationLink();
762 link.BaseUri = baseUri;
763 link.RelationshipType = Rss20Constants.EnclosureTag;
764 bool isEmptyElement = reader.IsEmptyElement;
765 if (reader.HasAttributes)
767 while (reader.MoveToNextAttribute())
769 string ns = reader.NamespaceURI;
770 string name = reader.LocalName;
771 if (name == "base" && ns == Atom10FeedFormatter.XmlNs)
773 link.BaseUri = FeedUtils.CombineXmlBase(link.BaseUri, reader.Value);
776 if (FeedUtils.IsXmlns(name, ns))
780 string val = reader.Value;
781 if (name == Rss20Constants.UrlTag && ns == Rss20Constants.Rss20Namespace)
783 link.Uri = new Uri(val, UriKind.RelativeOrAbsolute);
785 else if (name == Rss20Constants.TypeTag && ns == Rss20Constants.Rss20Namespace)
787 link.MediaType = val;
789 else if (name == Rss20Constants.LengthTag && ns == Rss20Constants.Rss20Namespace)
791 link.Length = !string.IsNullOrEmpty(val) ? Convert.ToInt64(val, CultureInfo.InvariantCulture.NumberFormat) : 0;
793 else if (!FeedUtils.IsXmlns(name, ns))
795 if (this.preserveAttributeExtensions)
797 link.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
801 TraceSyndicationElementIgnoredOnRead(reader);
806 reader.ReadStartElement(Rss20Constants.EnclosureTag, Rss20Constants.Rss20Namespace);
809 reader.ReadEndElement();
814 SyndicationPerson ReadPerson(XmlReader reader, SyndicationFeed feed)
816 SyndicationPerson result = CreatePerson(feed);
817 ReadPerson(reader, result);
821 SyndicationPerson ReadPerson(XmlReader reader, SyndicationItem item)
823 SyndicationPerson result = CreatePerson(item);
824 ReadPerson(reader, result);
828 void ReadPerson(XmlReader reader, SyndicationPerson person)
830 bool isEmpty = reader.IsEmptyElement;
831 if (reader.HasAttributes)
833 while (reader.MoveToNextAttribute())
835 string ns = reader.NamespaceURI;
836 string name = reader.LocalName;
837 if (FeedUtils.IsXmlns(name, ns))
841 string val = reader.Value;
842 if (!TryParseAttribute(name, ns, val, person, this.Version))
844 if (this.preserveAttributeExtensions)
846 person.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
850 TraceSyndicationElementIgnoredOnRead(reader);
855 reader.ReadStartElement();
858 string email = reader.ReadString();
859 reader.ReadEndElement();
860 person.Email = email;
864 void ReadXml(XmlReader reader, SyndicationFeed result)
868 string baseUri = null;
869 reader.MoveToContent();
870 string version = reader.GetAttribute(Rss20Constants.VersionTag, Rss20Constants.Rss20Namespace);
871 if (version != Rss20Constants.Version)
873 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(FeedUtils.AddLineInfo(reader, (SR.GetString(SR.UnsupportedRssVersion, version)))));
875 if (reader.AttributeCount > 1)
877 string tmp = reader.GetAttribute("base", Atom10FeedFormatter.XmlNs);
878 if (!string.IsNullOrEmpty(tmp))
883 reader.ReadStartElement();
884 reader.MoveToContent();
885 if (reader.HasAttributes)
887 while (reader.MoveToNextAttribute())
889 string ns = reader.NamespaceURI;
890 string name = reader.LocalName;
891 if (name == "base" && ns == Atom10FeedFormatter.XmlNs)
893 baseUri = reader.Value;
896 if (FeedUtils.IsXmlns(name, ns) || FeedUtils.IsXmlSchemaType(name, ns))
900 string val = reader.Value;
901 if (!TryParseAttribute(name, ns, val, result, this.Version))
903 if (this.preserveAttributeExtensions)
905 result.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
909 TraceSyndicationElementIgnoredOnRead(reader);
914 if (!string.IsNullOrEmpty(baseUri))
916 result.BaseUri = new Uri(baseUri, UriKind.RelativeOrAbsolute);
918 bool areAllItemsRead = true;
919 bool readItemsAtLeastOnce = false;
920 reader.ReadStartElement(Rss20Constants.ChannelTag, Rss20Constants.Rss20Namespace);
922 XmlBuffer buffer = null;
923 XmlDictionaryWriter extWriter = null;
926 while (reader.IsStartElement())
928 if (reader.IsStartElement(Rss20Constants.TitleTag, Rss20Constants.Rss20Namespace))
930 result.Title = new TextSyndicationContent(reader.ReadElementString());
932 else if (reader.IsStartElement(Rss20Constants.LinkTag, Rss20Constants.Rss20Namespace))
934 result.Links.Add(ReadAlternateLink(reader, result.BaseUri));
936 else if (reader.IsStartElement(Rss20Constants.DescriptionTag, Rss20Constants.Rss20Namespace))
938 result.Description = new TextSyndicationContent(reader.ReadElementString());
940 else if (reader.IsStartElement(Rss20Constants.LanguageTag, Rss20Constants.Rss20Namespace))
942 result.Language = reader.ReadElementString();
944 else if (reader.IsStartElement(Rss20Constants.CopyrightTag, Rss20Constants.Rss20Namespace))
946 result.Copyright = new TextSyndicationContent(reader.ReadElementString());
948 else if (reader.IsStartElement(Rss20Constants.ManagingEditorTag, Rss20Constants.Rss20Namespace))
950 result.Authors.Add(ReadPerson(reader, result));
952 else if (reader.IsStartElement(Rss20Constants.LastBuildDateTag, Rss20Constants.Rss20Namespace))
954 bool canReadContent = !reader.IsEmptyElement;
955 reader.ReadStartElement();
958 string str = reader.ReadString();
959 if (!string.IsNullOrEmpty(str))
961 result.LastUpdatedTime = DateFromString(str, reader);
963 reader.ReadEndElement();
966 else if (reader.IsStartElement(Rss20Constants.CategoryTag, Rss20Constants.Rss20Namespace))
968 result.Categories.Add(ReadCategory(reader, result));
970 else if (reader.IsStartElement(Rss20Constants.GeneratorTag, Rss20Constants.Rss20Namespace))
972 result.Generator = reader.ReadElementString();
974 else if (reader.IsStartElement(Rss20Constants.ImageTag, Rss20Constants.Rss20Namespace))
976 reader.ReadStartElement();
977 while (reader.IsStartElement())
979 if (reader.IsStartElement(Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace))
981 result.ImageUrl = new Uri(reader.ReadElementString(), UriKind.RelativeOrAbsolute);
985 // ignore other content
986 TraceSyndicationElementIgnoredOnRead(reader);
990 reader.ReadEndElement(); // image
992 else if (reader.IsStartElement(Rss20Constants.ItemTag, Rss20Constants.Rss20Namespace))
994 if (readItemsAtLeastOnce)
996 throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new InvalidOperationException(SR.GetString(SR.FeedHasNonContiguousItems, this.GetType().ToString())));
998 result.Items = ReadItems(reader, result, out areAllItemsRead);
999 readItemsAtLeastOnce = true;
1000 // if the derived class is reading the items lazily, then stop reading from the stream
1001 if (!areAllItemsRead)
1008 bool parsedExtension = this.serializeExtensionsAsAtom && this.atomSerializer.TryParseFeedElementFrom(reader, result);
1009 if (!parsedExtension)
1011 parsedExtension = TryParseElement(reader, result, this.Version);
1013 if (!parsedExtension)
1015 if (preserveElementExtensions)
1017 CreateBufferIfRequiredAndWriteNode(ref buffer, ref extWriter, reader, this.maxExtensionSize);
1021 TraceSyndicationElementIgnoredOnRead(reader);
1027 LoadElementExtensions(buffer, extWriter, result);
1031 if (extWriter != null)
1033 ((IDisposable) extWriter).Dispose();
1036 if (areAllItemsRead)
1038 reader.ReadEndElement(); // channel
1039 reader.ReadEndElement(); // rss
1042 catch (FormatException e)
1044 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingFeed), e));
1046 catch (ArgumentException e)
1048 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingFeed), e));
1052 void WriteAlternateLink(XmlWriter writer, SyndicationLink link, Uri baseUri)
1054 writer.WriteStartElement(Rss20Constants.LinkTag, Rss20Constants.Rss20Namespace);
1055 Uri baseUriToWrite = FeedUtils.GetBaseUriToWrite(baseUri, link.BaseUri);
1056 if (baseUriToWrite != null)
1058 writer.WriteAttributeString("xml", "base", Atom10FeedFormatter.XmlNs, FeedUtils.GetUriString(baseUriToWrite));
1060 link.WriteAttributeExtensions(writer, SyndicationVersions.Rss20);
1061 writer.WriteString(FeedUtils.GetUriString(link.Uri));
1062 writer.WriteEndElement();
1065 void WriteCategory(XmlWriter writer, SyndicationCategory category)
1067 if (category == null)
1071 writer.WriteStartElement(Rss20Constants.CategoryTag, Rss20Constants.Rss20Namespace);
1072 WriteAttributeExtensions(writer, category, this.Version);
1073 if (!string.IsNullOrEmpty(category.Scheme) && !category.AttributeExtensions.ContainsKey(Rss20Domain))
1075 writer.WriteAttributeString(Rss20Constants.DomainTag, Rss20Constants.Rss20Namespace, category.Scheme);
1077 writer.WriteString(category.Name);
1078 writer.WriteEndElement();
1081 void WriteFeed(XmlWriter writer)
1083 if (this.Feed == null)
1085 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.FeedFormatterDoesNotHaveFeed)));
1087 if (this.serializeExtensionsAsAtom)
1089 writer.WriteAttributeString("xmlns", Atom10Constants.Atom10Prefix, null, Atom10Constants.Atom10Namespace);
1091 writer.WriteAttributeString(Rss20Constants.VersionTag, Rss20Constants.Version);
1092 writer.WriteStartElement(Rss20Constants.ChannelTag, Rss20Constants.Rss20Namespace);
1093 if (this.Feed.BaseUri != null)
1095 writer.WriteAttributeString("xml", "base", Atom10FeedFormatter.XmlNs, FeedUtils.GetUriString(this.Feed.BaseUri));
1097 WriteAttributeExtensions(writer, this.Feed, this.Version);
1098 string title = this.Feed.Title != null ? this.Feed.Title.Text : string.Empty;
1099 writer.WriteElementString(Rss20Constants.TitleTag, Rss20Constants.Rss20Namespace, title);
1101 SyndicationLink alternateLink = null;
1102 for (int i = 0; i < this.Feed.Links.Count; ++i)
1104 if (this.Feed.Links[i].RelationshipType == Atom10Constants.AlternateTag)
1106 alternateLink = this.Feed.Links[i];
1107 WriteAlternateLink(writer, alternateLink, this.Feed.BaseUri);
1112 string description = this.Feed.Description != null ? this.Feed.Description.Text : string.Empty;
1113 writer.WriteElementString(Rss20Constants.DescriptionTag, Rss20Constants.Rss20Namespace, description);
1115 if (this.Feed.Language != null)
1117 writer.WriteElementString(Rss20Constants.LanguageTag, this.Feed.Language);
1120 if (this.Feed.Copyright != null)
1122 writer.WriteElementString(Rss20Constants.CopyrightTag, Rss20Constants.Rss20Namespace, this.Feed.Copyright.Text);
1125 // if there's a single author with an email address, then serialize as the managingEditor
1126 // else serialize the authors as Atom extensions
1127 #pragma warning disable 56506 // Microsoft: this.Feed.Authors is never null
1128 if ((this.Feed.Authors.Count == 1) && (this.Feed.Authors[0].Email != null))
1129 #pragma warning restore 56506
1131 WritePerson(writer, Rss20Constants.ManagingEditorTag, this.Feed.Authors[0]);
1135 if (serializeExtensionsAsAtom)
1137 this.atomSerializer.WriteFeedAuthorsTo(writer, this.Feed.Authors);
1141 TraceExtensionsIgnoredOnWrite(SR.FeedAuthorsIgnoredOnWrite);
1145 if (this.Feed.LastUpdatedTime > DateTimeOffset.MinValue)
1147 writer.WriteStartElement(Rss20Constants.LastBuildDateTag);
1148 writer.WriteString(AsString(this.Feed.LastUpdatedTime));
1149 writer.WriteEndElement();
1152 #pragma warning disable 56506 // Microsoft: this.Feed.Categories is never null
1153 for (int i = 0; i < this.Feed.Categories.Count; ++i)
1154 #pragma warning restore 56506
1156 WriteCategory(writer, this.Feed.Categories[i]);
1159 if (!string.IsNullOrEmpty(this.Feed.Generator))
1161 writer.WriteElementString(Rss20Constants.GeneratorTag, this.Feed.Generator);
1164 #pragma warning disable 56506 // Microsoft: this.Feed.Contributors is never null
1165 if (this.Feed.Contributors.Count > 0)
1166 #pragma warning restore 56506
1168 if (serializeExtensionsAsAtom)
1170 this.atomSerializer.WriteFeedContributorsTo(writer, this.Feed.Contributors);
1174 TraceExtensionsIgnoredOnWrite(SR.FeedContributorsIgnoredOnWrite);
1178 if (this.Feed.ImageUrl != null)
1180 writer.WriteStartElement(Rss20Constants.ImageTag);
1181 writer.WriteElementString(Rss20Constants.UrlTag, FeedUtils.GetUriString(this.Feed.ImageUrl));
1182 writer.WriteElementString(Rss20Constants.TitleTag, Rss20Constants.Rss20Namespace, title);
1183 string imgAlternateLink = (alternateLink != null) ? FeedUtils.GetUriString(alternateLink.Uri) : string.Empty;
1184 writer.WriteElementString(Rss20Constants.LinkTag, Rss20Constants.Rss20Namespace, imgAlternateLink);
1185 writer.WriteEndElement(); // image
1188 if (serializeExtensionsAsAtom)
1190 this.atomSerializer.WriteElement(writer, Atom10Constants.IdTag, this.Feed.Id);
1192 // dont write out the 1st alternate link since that would have been written out anyway
1193 bool isFirstAlternateLink = true;
1194 for (int i = 0; i < this.Feed.Links.Count; ++i)
1196 if (this.Feed.Links[i].RelationshipType == Atom10Constants.AlternateTag && isFirstAlternateLink)
1198 isFirstAlternateLink = false;
1201 this.atomSerializer.WriteLink(writer, this.Feed.Links[i], this.Feed.BaseUri);
1206 if (this.Feed.Id != null)
1208 TraceExtensionsIgnoredOnWrite(SR.FeedIdIgnoredOnWrite);
1210 if (this.Feed.Links.Count > 1)
1212 TraceExtensionsIgnoredOnWrite(SR.FeedLinksIgnoredOnWrite);
1216 WriteElementExtensions(writer, this.Feed, this.Version);
1217 WriteItems(writer, this.Feed.Items, this.Feed.BaseUri);
1218 writer.WriteEndElement(); // channel
1221 void WriteItemContents(XmlWriter writer, SyndicationItem item, Uri feedBaseUri)
1223 Uri baseUriToWrite = FeedUtils.GetBaseUriToWrite(feedBaseUri, item.BaseUri);
1224 if (baseUriToWrite != null)
1226 writer.WriteAttributeString("xml", "base", Atom10FeedFormatter.XmlNs, FeedUtils.GetUriString(baseUriToWrite));
1228 WriteAttributeExtensions(writer, item, this.Version);
1229 string guid = item.Id ?? string.Empty;
1230 bool isPermalink = false;
1231 SyndicationLink firstAlternateLink = null;
1232 for (int i = 0; i < item.Links.Count; ++i)
1234 if (item.Links[i].RelationshipType == Atom10Constants.AlternateTag)
1236 if (firstAlternateLink == null)
1238 firstAlternateLink = item.Links[i];
1240 if (guid == FeedUtils.GetUriString(item.Links[i].Uri))
1247 if (!string.IsNullOrEmpty(guid))
1249 writer.WriteStartElement(Rss20Constants.GuidTag);
1252 writer.WriteAttributeString(Rss20Constants.IsPermaLinkTag, "true");
1256 writer.WriteAttributeString(Rss20Constants.IsPermaLinkTag, "false");
1258 writer.WriteString(guid);
1259 writer.WriteEndElement();
1261 if (firstAlternateLink != null)
1263 WriteAlternateLink(writer, firstAlternateLink, (item.BaseUri != null ? item.BaseUri : feedBaseUri));
1266 #pragma warning disable 56506 // Microsoft, item.Authors is never null
1267 if (item.Authors.Count == 1 && !string.IsNullOrEmpty(item.Authors[0].Email))
1268 #pragma warning restore 56506
1270 WritePerson(writer, Rss20Constants.AuthorTag, item.Authors[0]);
1274 if (serializeExtensionsAsAtom)
1276 this.atomSerializer.WriteItemAuthorsTo(writer, item.Authors);
1280 TraceExtensionsIgnoredOnWrite(SR.ItemAuthorsIgnoredOnWrite);
1284 #pragma warning disable 56506 // Microsoft, item.Categories is never null
1285 for (int i = 0; i < item.Categories.Count; ++i)
1286 #pragma warning restore 56506
1288 WriteCategory(writer, item.Categories[i]);
1291 bool serializedTitle = false;
1292 if (item.Title != null)
1294 writer.WriteElementString(Rss20Constants.TitleTag, item.Title.Text);
1295 serializedTitle = true;
1298 bool serializedContentAsDescription = false;
1299 TextSyndicationContent summary = item.Summary;
1300 if (summary == null)
1302 summary = (item.Content as TextSyndicationContent);
1303 serializedContentAsDescription = (summary != null);
1305 // the spec requires the wire to have a title or a description
1306 if (!serializedTitle && summary == null)
1308 summary = new TextSyndicationContent(string.Empty);
1310 if (summary != null)
1312 writer.WriteElementString(Rss20Constants.DescriptionTag, Rss20Constants.Rss20Namespace, summary.Text);
1315 if (item.SourceFeed != null)
1317 writer.WriteStartElement(Rss20Constants.SourceTag, Rss20Constants.Rss20Namespace);
1318 WriteAttributeExtensions(writer, item.SourceFeed, this.Version);
1319 SyndicationLink selfLink = null;
1320 for (int i = 0; i < item.SourceFeed.Links.Count; ++i)
1322 if (item.SourceFeed.Links[i].RelationshipType == Atom10Constants.SelfTag)
1324 selfLink = item.SourceFeed.Links[i];
1328 if (selfLink != null && !item.SourceFeed.AttributeExtensions.ContainsKey(Rss20Url))
1330 writer.WriteAttributeString(Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace, FeedUtils.GetUriString(selfLink.Uri));
1332 string title = (item.SourceFeed.Title != null) ? item.SourceFeed.Title.Text : string.Empty;
1333 writer.WriteString(title);
1334 writer.WriteEndElement();
1337 if (item.PublishDate > DateTimeOffset.MinValue)
1339 writer.WriteElementString(Rss20Constants.PubDateTag, Rss20Constants.Rss20Namespace, AsString(item.PublishDate));
1342 // serialize the enclosures
1343 SyndicationLink firstEnclosureLink = null;
1344 bool passedFirstAlternateLink = false;
1345 bool isLinkIgnored = false;
1346 for (int i = 0; i < item.Links.Count; ++i)
1348 if (item.Links[i].RelationshipType == Rss20Constants.EnclosureTag)
1350 if (firstEnclosureLink == null)
1352 firstEnclosureLink = item.Links[i];
1353 WriteMediaEnclosure(writer, item.Links[i], item.BaseUri);
1357 else if (item.Links[i].RelationshipType == Atom10Constants.AlternateTag)
1359 if (!passedFirstAlternateLink)
1361 passedFirstAlternateLink = true;
1365 if (this.serializeExtensionsAsAtom)
1367 this.atomSerializer.WriteLink(writer, item.Links[i], item.BaseUri);
1371 isLinkIgnored = true;
1376 TraceExtensionsIgnoredOnWrite(SR.ItemLinksIgnoredOnWrite);
1379 if (item.LastUpdatedTime > DateTimeOffset.MinValue)
1381 if (this.serializeExtensionsAsAtom)
1383 this.atomSerializer.WriteItemLastUpdatedTimeTo(writer, item.LastUpdatedTime);
1387 TraceExtensionsIgnoredOnWrite(SR.ItemLastUpdatedTimeIgnoredOnWrite);
1391 if (serializeExtensionsAsAtom)
1393 this.atomSerializer.WriteContentTo(writer, Atom10Constants.RightsTag, item.Copyright);
1397 TraceExtensionsIgnoredOnWrite(SR.ItemCopyrightIgnoredOnWrite);
1400 if (!serializedContentAsDescription)
1402 if (serializeExtensionsAsAtom)
1404 this.atomSerializer.WriteContentTo(writer, Atom10Constants.ContentTag, item.Content);
1408 TraceExtensionsIgnoredOnWrite(SR.ItemContentIgnoredOnWrite);
1412 #pragma warning disable 56506 // Microsoft, item.COntributors is never null
1413 if (item.Contributors.Count > 0)
1414 #pragma warning restore 56506
1416 if (serializeExtensionsAsAtom)
1418 this.atomSerializer.WriteItemContributorsTo(writer, item.Contributors);
1422 TraceExtensionsIgnoredOnWrite(SR.ItemContributorsIgnoredOnWrite);
1426 WriteElementExtensions(writer, item, this.Version);
1429 void WriteMediaEnclosure(XmlWriter writer, SyndicationLink link, Uri baseUri)
1431 writer.WriteStartElement(Rss20Constants.EnclosureTag, Rss20Constants.Rss20Namespace);
1432 Uri baseUriToWrite = FeedUtils.GetBaseUriToWrite(baseUri, link.BaseUri);
1433 if (baseUriToWrite != null)
1435 writer.WriteAttributeString("xml", "base", Atom10FeedFormatter.XmlNs, FeedUtils.GetUriString(baseUriToWrite));
1437 link.WriteAttributeExtensions(writer, SyndicationVersions.Rss20);
1438 if (!link.AttributeExtensions.ContainsKey(Rss20Url))
1440 writer.WriteAttributeString(Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace, FeedUtils.GetUriString(link.Uri));
1442 if (link.MediaType != null && !link.AttributeExtensions.ContainsKey(Rss20Type))
1444 writer.WriteAttributeString(Rss20Constants.TypeTag, Rss20Constants.Rss20Namespace, link.MediaType);
1446 if (link.Length != 0 && !link.AttributeExtensions.ContainsKey(Rss20Length))
1448 writer.WriteAttributeString(Rss20Constants.LengthTag, Rss20Constants.Rss20Namespace, Convert.ToString(link.Length, CultureInfo.InvariantCulture));
1450 writer.WriteEndElement();
1453 void WritePerson(XmlWriter writer, string elementTag, SyndicationPerson person)
1455 writer.WriteStartElement(elementTag, Rss20Constants.Rss20Namespace);
1456 WriteAttributeExtensions(writer, person, this.Version);
1457 writer.WriteString(person.Email);
1458 writer.WriteEndElement();
1462 [TypeForwardedFrom("System.ServiceModel.Web, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
1463 [XmlRoot(ElementName = Rss20Constants.RssTag, Namespace = Rss20Constants.Rss20Namespace)]
1464 public class Rss20FeedFormatter<TSyndicationFeed> : Rss20FeedFormatter
1465 where TSyndicationFeed : SyndicationFeed, new ()
1468 public Rss20FeedFormatter()
1469 : base(typeof(TSyndicationFeed))
1472 public Rss20FeedFormatter(TSyndicationFeed feedToWrite)
1476 public Rss20FeedFormatter(TSyndicationFeed feedToWrite, bool serializeExtensionsAsAtom)
1477 : base(feedToWrite, serializeExtensionsAsAtom)
1481 protected override SyndicationFeed CreateFeedInstance()
1483 return new TSyndicationFeed();