a7bfdc2a4404d397ed6285ebb1001ecdeda6db4c
[mono.git] / mcs / class / referencesource / System.ServiceModel / System / ServiceModel / Syndication / Rss20FeedFormatter.cs
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 #pragma warning disable 1634, 1691
5 namespace System.ServiceModel.Syndication
6 {
7     using System;
8     using System.Collections.Generic;
9     using System.Diagnostics;
10     using System.Diagnostics.CodeAnalysis;
11     using System.Globalization;
12     using System.Runtime;
13     using System.ServiceModel.Diagnostics;
14     using System.Text;
15     using System.Xml;
16     using System.Xml.Schema;
17     using System.Xml.Serialization;
18     using DiagnosticUtility = System.ServiceModel.DiagnosticUtility;
19     using System.Runtime.CompilerServices;
20
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
24     {
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";
31
32         Atom10FeedFormatter atomSerializer;
33         Type feedType;
34         int maxExtensionSize;
35         bool preserveAttributeExtensions;
36         bool preserveElementExtensions;
37         bool serializeExtensionsAsAtom;
38
39         public Rss20FeedFormatter()
40             : this(typeof(SyndicationFeed))
41         {
42         }
43
44         public Rss20FeedFormatter(Type feedTypeToCreate)
45             : base()
46         {
47             if (feedTypeToCreate == null)
48             {
49                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("feedTypeToCreate");
50             }
51             if (!typeof(SyndicationFeed).IsAssignableFrom(feedTypeToCreate))
52             {
53                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("feedTypeToCreate",
54                     SR.GetString(SR.InvalidObjectTypePassed, "feedTypeToCreate", "SyndicationFeed"));
55             }
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;
62         }
63
64         public Rss20FeedFormatter(SyndicationFeed feedToWrite)
65             : this(feedToWrite, true)
66         {
67         }
68
69         public Rss20FeedFormatter(SyndicationFeed feedToWrite, bool serializeExtensionsAsAtom)
70             : base(feedToWrite)
71         {
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();
79         }
80
81         public bool PreserveAttributeExtensions
82         {
83             get { return this.preserveAttributeExtensions; }
84             set { this.preserveAttributeExtensions = value; }
85         }
86
87         public bool PreserveElementExtensions
88         {
89             get { return this.preserveElementExtensions; }
90             set { this.preserveElementExtensions = value; }
91         }
92
93         public bool SerializeExtensionsAsAtom
94         {
95             get { return this.serializeExtensionsAsAtom; }
96             set { this.serializeExtensionsAsAtom = value; }
97         }
98
99         public override string Version
100         {
101             get { return SyndicationVersions.Rss20; }
102         }
103
104         protected Type FeedType
105         {
106             get
107             {
108                 return this.feedType;
109             }
110         }
111
112         public override bool CanRead(XmlReader reader)
113         {
114             if (reader == null)
115             {
116                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
117             }
118             return reader.IsStartElement(Rss20Constants.RssTag, Rss20Constants.Rss20Namespace);
119         }
120
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()
123         {
124             return null;
125         }
126
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)
129         {
130             if (reader == null)
131             {
132                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
133             }
134             TraceFeedReadBegin();
135             ReadFeed(reader);
136             TraceFeedReadEnd();
137         }
138
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)
141         {
142             if (writer == null)
143             {
144                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
145             }
146             TraceFeedWriteBegin();
147             WriteFeed(writer);
148             TraceFeedWriteEnd();
149         }
150
151         public override void ReadFrom(XmlReader reader)
152         {
153             TraceFeedReadBegin();
154             if (!CanRead(reader))
155             {
156                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.UnknownFeedXml, reader.LocalName, reader.NamespaceURI)));
157             }
158             ReadFeed(reader);
159             TraceFeedReadEnd();
160         }
161
162         public override void WriteTo(XmlWriter writer)
163         {
164             if (writer == null)
165             {
166                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
167             }
168             TraceFeedWriteBegin();
169             writer.WriteStartElement(Rss20Constants.RssTag, Rss20Constants.Rss20Namespace);
170             WriteFeed(writer);
171             writer.WriteEndElement();
172             TraceFeedWriteEnd();
173         }
174
175         protected internal override void SetFeed(SyndicationFeed feed)
176         {
177             base.SetFeed(feed);
178             this.atomSerializer.SetFeed(this.Feed);
179         }
180
181         internal static void TraceExtensionsIgnoredOnWrite(string message)
182         {
183             if (DiagnosticUtility.ShouldTraceInformation)
184             {
185                 TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.SyndicationProtocolElementIgnoredOnWrite, SR.GetString(message));
186             }
187         }
188
189         internal void ReadItemFrom(XmlReader reader, SyndicationItem result)
190         {
191             ReadItemFrom(reader, result, null);
192         }
193
194         internal void WriteItemContents(XmlWriter writer, SyndicationItem item)
195         {
196             WriteItemContents(writer, item, null);
197         }
198
199         protected override SyndicationFeed CreateFeedInstance()
200         {
201             return SyndicationFeedFormatter.CreateFeedInstance(this.feedType);
202         }
203
204         protected virtual SyndicationItem ReadItem(XmlReader reader, SyndicationFeed feed)
205         {
206             if (feed == null)
207             {
208                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("feed");
209             }
210             if (reader == null)
211             {
212                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
213             }
214             SyndicationItem item = CreateItem(feed);
215             TraceItemReadBegin();
216             ReadItemFrom(reader, item, feed.BaseUri);
217             TraceItemReadEnd();
218             return item;
219         }
220
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)
223         {
224             if (feed == null)
225             {
226                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("feed");
227             }
228             if (reader == null)
229             {
230                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
231             }
232             NullNotAllowedCollection<SyndicationItem> items = new NullNotAllowedCollection<SyndicationItem>();
233             while (reader.IsStartElement(Rss20Constants.ItemTag, Rss20Constants.Rss20Namespace))
234             {
235                 items.Add(ReadItem(reader, feed));
236             }
237             areAllItemsRead = true;
238             return items;
239         }
240
241         protected virtual void WriteItem(XmlWriter writer, SyndicationItem item, Uri feedBaseUri)
242         {
243             TraceItemWriteBegin();
244             writer.WriteStartElement(Rss20Constants.ItemTag, Rss20Constants.Rss20Namespace);
245             WriteItemContents(writer, item, feedBaseUri);
246             writer.WriteEndElement();
247             TraceItemWriteEnd();
248         }
249
250         protected virtual void WriteItems(XmlWriter writer, IEnumerable<SyndicationItem> items, Uri feedBaseUri)
251         {
252             if (items == null)
253             {
254                 return;
255             }
256             foreach (SyndicationItem item in items)
257             {
258                 this.WriteItem(writer, item, feedBaseUri);
259             }
260         }
261
262         static DateTimeOffset DateFromString(string dateTimeString, XmlReader reader)
263         {
264             StringBuilder dateTimeStringBuilder = new StringBuilder(dateTimeString.Trim());
265             if (dateTimeStringBuilder.Length < 18)
266             {
267                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
268                     new XmlException(FeedUtils.AddLineInfo(reader,
269                     SR.ErrorParsingDateTime)));
270             }
271             if (dateTimeStringBuilder[3] == ',')
272             {
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);
277             }
278             ReplaceMultipleWhiteSpaceWithSingleWhiteSpace(dateTimeStringBuilder);
279             if (char.IsDigit(dateTimeStringBuilder[1]))
280             {
281                 // two-digit day, we are good
282             }
283             else
284             {
285                 dateTimeStringBuilder.Insert(0, '0');
286             }
287             if (dateTimeStringBuilder.Length < 19)
288             {
289                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
290                     new XmlException(FeedUtils.AddLineInfo(reader,
291                     SR.ErrorParsingDateTime)));
292             }
293             bool thereAreSeconds = (dateTimeStringBuilder[17] == ':');
294             int timeZoneStartIndex;
295             if (thereAreSeconds)
296             {
297                 timeZoneStartIndex = 21;
298             }
299             else
300             {
301                 timeZoneStartIndex = 18;
302             }
303             string timeZoneSuffix = dateTimeStringBuilder.ToString().Substring(timeZoneStartIndex);
304             dateTimeStringBuilder.Remove(timeZoneStartIndex, dateTimeStringBuilder.Length - timeZoneStartIndex);
305             bool isUtc;
306             dateTimeStringBuilder.Append(NormalizeTimeZone(timeZoneSuffix, out isUtc));
307             string wellFormattedString = dateTimeStringBuilder.ToString();
308
309             DateTimeOffset theTime;
310             string parseFormat;
311             if (thereAreSeconds)
312             {
313                 parseFormat = "dd MMM yyyy HH:mm:ss zzz";
314             }
315             else
316             {
317                 parseFormat = "dd MMM yyyy HH:mm zzz";
318             }
319             if (DateTimeOffset.TryParseExact(wellFormattedString, parseFormat,
320                 CultureInfo.InvariantCulture.DateTimeFormat,
321                 (isUtc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), out theTime))
322             {
323                 return theTime;
324             }
325             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
326                 new XmlException(FeedUtils.AddLineInfo(reader,
327                 SR.ErrorParsingDateTime)));
328         }
329
330         static string NormalizeTimeZone(string rfc822TimeZone, out bool isUtc)
331         {
332             isUtc = false;
333             // return a string in "-08:00" format
334             if (rfc822TimeZone[0] == '+' || rfc822TimeZone[0] == '-')
335             {
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)
339                 {
340                     // the timezone is +/-HMM. Convert to +/-HHMM
341                     result.Insert(1, '0');
342                 }
343                 result.Insert(3, ':');
344                 return result.ToString();
345             }
346             switch (rfc822TimeZone)
347             {
348                 case "UT":
349                 case "Z":
350                     isUtc = true;
351                     return "-00:00";
352                 case "GMT":
353                     return "-00:00";
354                 case "A":
355                     return "-01:00";
356                 case "B":
357                     return "-02:00";
358                 case "C":
359                     return "-03:00";
360                 case "D":
361                 case "EDT":
362                     return "-04:00";
363                 case "E":
364                 case "EST":
365                 case "CDT":
366                     return "-05:00";
367                 case "F":
368                 case "CST":
369                 case "MDT":
370                     return "-06:00";
371                 case "G":
372                 case "MST":
373                 case "PDT":
374                     return "-07:00";
375                 case "H":
376                 case "PST":
377                     return "-08:00";
378                 case "I":
379                     return "-09:00";
380                 case "K":
381                     return "-10:00";
382                 case "L":
383                     return "-11:00";
384                 case "M":
385                     return "-12:00";
386                 case "N":
387                     return "+01:00";
388                 case "O":
389                     return "+02:00";
390                 case "P":
391                     return "+03:00";
392                 case "Q":
393                     return "+04:00";
394                 case "R":
395                     return "+05:00";
396                 case "S":
397                     return "+06:00";
398                 case "T":
399                     return "+07:00";
400                 case "U":
401                     return "+08:00";
402                 case "V":
403                     return "+09:00";
404                 case "W":
405                     return "+10:00";
406                 case "X":
407                     return "+11:00";
408                 case "Y":
409                     return "+12:00";
410                 default:
411                     return "";
412             }
413         }
414
415         static void RemoveExtraWhiteSpaceAtStart(StringBuilder stringBuilder)
416         {
417             int i = 0;
418             while (i < stringBuilder.Length)
419             {
420                 if (!char.IsWhiteSpace(stringBuilder[i]))
421                 {
422                     break;
423                 }
424                 ++i;
425             }
426             if (i > 0)
427             {
428                 stringBuilder.Remove(0, i);
429             }
430         }
431
432         static void ReplaceMultipleWhiteSpaceWithSingleWhiteSpace(StringBuilder builder)
433         {
434             int index = 0;
435             int whiteSpaceStart = -1;
436             while (index < builder.Length)
437             {
438                 if (char.IsWhiteSpace(builder[index]))
439                 {
440                     if (whiteSpaceStart < 0)
441                     {
442                         whiteSpaceStart = index;
443                         // normalize all white spaces to be ' ' so that the date time parsing works
444                         builder[index] = ' ';
445                     }
446                 }
447                 else if (whiteSpaceStart >= 0)
448                 {
449                     if (index > whiteSpaceStart + 1)
450                     {
451                         // there are at least 2 spaces... replace by 1
452                         builder.Remove(whiteSpaceStart, index - whiteSpaceStart - 1);
453                         index = whiteSpaceStart + 1;
454                     }
455                     whiteSpaceStart = -1;
456                 }
457                 ++index;
458             }
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");
461         }
462
463         string AsString(DateTimeOffset dateTime)
464         {
465             if (dateTime.Offset == Atom10FeedFormatter.zeroOffset)
466             {
467                 return dateTime.ToUniversalTime().ToString(Rfc822OutputUtcDateTimeFormat, CultureInfo.InvariantCulture);
468             }
469             else
470             {
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();
475
476             }
477         }
478
479         SyndicationLink ReadAlternateLink(XmlReader reader, Uri baseUri)
480         {
481             SyndicationLink link = new SyndicationLink();
482             link.BaseUri = baseUri;
483             link.RelationshipType = Atom10Constants.AlternateTag;
484             if (reader.HasAttributes)
485             {
486                 while (reader.MoveToNextAttribute())
487                 {
488                     if (reader.LocalName == "base" && reader.NamespaceURI == Atom10FeedFormatter.XmlNs)
489                     {
490                         link.BaseUri = FeedUtils.CombineXmlBase(link.BaseUri, reader.Value);
491                     }
492                     else if (!FeedUtils.IsXmlns(reader.LocalName, reader.NamespaceURI))
493                     {
494                         if (this.PreserveAttributeExtensions)
495                         {
496                             link.AttributeExtensions.Add(new XmlQualifiedName(reader.LocalName, reader.NamespaceURI), reader.Value);
497                         }
498                         else
499                         {
500                             TraceSyndicationElementIgnoredOnRead(reader);
501                         }
502                     }
503                 }
504             }
505             string uri = reader.ReadElementString();
506             link.Uri = new Uri(uri, UriKind.RelativeOrAbsolute); 
507             return link;
508         }
509
510         SyndicationCategory ReadCategory(XmlReader reader, SyndicationFeed feed)
511         {
512             SyndicationCategory result = CreateCategory(feed);
513             ReadCategory(reader, result);
514             return result;
515         }
516
517         SyndicationCategory ReadCategory(XmlReader reader, SyndicationItem item)
518         {
519             SyndicationCategory result = CreateCategory(item);
520             ReadCategory(reader, result);
521             return result;
522         }
523
524         void ReadCategory(XmlReader reader, SyndicationCategory category)
525         {
526             bool isEmpty = reader.IsEmptyElement;
527             if (reader.HasAttributes)
528             {
529                 while (reader.MoveToNextAttribute())
530                 {
531                     string ns = reader.NamespaceURI;
532                     string name = reader.LocalName;
533                     if (FeedUtils.IsXmlns(name, ns))
534                     {
535                         continue;
536                     }
537                     string val = reader.Value;
538                     if (name == Rss20Constants.DomainTag && ns == Rss20Constants.Rss20Namespace)
539                     {
540                         category.Scheme = val;
541                     }
542                     else if (!TryParseAttribute(name, ns, val, category, this.Version))
543                     {
544                         if (this.preserveAttributeExtensions)
545                         {
546                             category.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
547                         }
548                         else
549                         {
550                             TraceSyndicationElementIgnoredOnRead(reader);
551                         }
552                     }
553                 }
554             }
555             reader.ReadStartElement(Rss20Constants.CategoryTag, Rss20Constants.Rss20Namespace);
556             if (!isEmpty)
557             {
558                 category.Name = reader.ReadString();
559                 reader.ReadEndElement();
560             }
561         }
562
563         void ReadFeed(XmlReader reader)
564         {
565             SetFeed(CreateFeedInstance());
566             ReadXml(reader, this.Feed);
567         }
568
569         void ReadItemFrom(XmlReader reader, SyndicationItem result, Uri feedBaseUri)
570         {
571             try
572             {
573                 result.BaseUri = feedBaseUri;
574                 reader.MoveToContent();
575                 bool isEmpty = reader.IsEmptyElement;
576                 if (reader.HasAttributes)
577                 {
578                     while (reader.MoveToNextAttribute())
579                     {
580                         string ns = reader.NamespaceURI;
581                         string name = reader.LocalName;
582                         if (name == "base" && ns == Atom10FeedFormatter.XmlNs)
583                         {
584                             result.BaseUri = FeedUtils.CombineXmlBase(result.BaseUri, reader.Value);
585                             continue;
586                         }
587                         if (FeedUtils.IsXmlns(name, ns) || FeedUtils.IsXmlSchemaType(name, ns))
588                         {
589                             continue;
590                         }
591                         string val = reader.Value;
592                         if (!TryParseAttribute(name, ns, val, result, this.Version))
593                         {
594                             if (this.preserveAttributeExtensions)
595                             {
596                                 result.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
597                             }
598                             else
599                             {
600                                 TraceSyndicationElementIgnoredOnRead(reader);
601                             }
602                         }
603                     }
604                 }
605                 reader.ReadStartElement();
606                 if (!isEmpty)
607                 {
608                     string fallbackAlternateLink = null;
609                     XmlDictionaryWriter extWriter = null;
610                     bool readAlternateLink = false;
611                     try
612                     {
613                         XmlBuffer buffer = null;
614                         while (reader.IsStartElement())
615                         {
616                             if (reader.IsStartElement(Rss20Constants.TitleTag, Rss20Constants.Rss20Namespace))
617                             {
618                                 result.Title = new TextSyndicationContent(reader.ReadElementString());
619                             }
620                             else if (reader.IsStartElement(Rss20Constants.LinkTag, Rss20Constants.Rss20Namespace))
621                             {
622                                 result.Links.Add(ReadAlternateLink(reader, result.BaseUri));
623                                 readAlternateLink = true;
624                             }
625                             else if (reader.IsStartElement(Rss20Constants.DescriptionTag, Rss20Constants.Rss20Namespace))
626                             {
627                                 result.Summary = new TextSyndicationContent(reader.ReadElementString());
628                             }
629                             else if (reader.IsStartElement(Rss20Constants.AuthorTag, Rss20Constants.Rss20Namespace))
630                             {
631                                 result.Authors.Add(ReadPerson(reader, result));
632                             }
633                             else if (reader.IsStartElement(Rss20Constants.CategoryTag, Rss20Constants.Rss20Namespace))
634                             {
635                                 result.Categories.Add(ReadCategory(reader, result));
636                             }
637                             else if (reader.IsStartElement(Rss20Constants.EnclosureTag, Rss20Constants.Rss20Namespace))
638                             {
639                                 result.Links.Add(ReadMediaEnclosure(reader, result.BaseUri));
640                             }
641                             else if (reader.IsStartElement(Rss20Constants.GuidTag, Rss20Constants.Rss20Namespace))
642                             {
643                                 bool isPermalink = true;
644                                 string permalinkString = reader.GetAttribute(Rss20Constants.IsPermaLinkTag, Rss20Constants.Rss20Namespace);
645                                 if ((permalinkString != null) && (permalinkString.ToUpperInvariant() == "FALSE"))
646                                 {
647                                     isPermalink = false;
648                                 }
649                                 result.Id = reader.ReadElementString();
650                                 if (isPermalink)
651                                 {
652                                     fallbackAlternateLink = result.Id;
653                                 }
654                             }
655                             else if (reader.IsStartElement(Rss20Constants.PubDateTag, Rss20Constants.Rss20Namespace))
656                             {
657                                 bool canReadContent = !reader.IsEmptyElement;
658                                 reader.ReadStartElement();
659                                 if (canReadContent)
660                                 {
661                                     string str = reader.ReadString();
662                                     if (!string.IsNullOrEmpty(str))
663                                     {
664                                         result.PublishDate = DateFromString(str, reader);
665                                     }
666                                     reader.ReadEndElement();
667                                 }
668                             }
669                             else if (reader.IsStartElement(Rss20Constants.SourceTag, Rss20Constants.Rss20Namespace))
670                             {
671                                 SyndicationFeed feed = new SyndicationFeed();
672                                 if (reader.HasAttributes)
673                                 {
674                                     while (reader.MoveToNextAttribute())
675                                     {
676                                         string ns = reader.NamespaceURI;
677                                         string name = reader.LocalName;
678                                         if (FeedUtils.IsXmlns(name, ns))
679                                         {
680                                             continue;
681                                         }
682                                         string val = reader.Value;
683                                         if (name == Rss20Constants.UrlTag && ns == Rss20Constants.Rss20Namespace)
684                                         {
685                                             feed.Links.Add(SyndicationLink.CreateSelfLink(new Uri(val, UriKind.RelativeOrAbsolute)));
686                                         }
687                                         else if (!FeedUtils.IsXmlns(name, ns))
688                                         {
689                                             if (this.preserveAttributeExtensions)
690                                             {
691                                                 feed.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
692                                             }
693                                             else
694                                             {
695                                                 TraceSyndicationElementIgnoredOnRead(reader);
696                                             }
697                                         }
698                                     }
699                                 }
700                                 string feedTitle = reader.ReadElementString();
701                                 feed.Title = new TextSyndicationContent(feedTitle);
702                                 result.SourceFeed = feed;
703                             }
704                             else
705                             {
706                                 bool parsedExtension = this.serializeExtensionsAsAtom && this.atomSerializer.TryParseItemElementFrom(reader, result);
707                                 if (!parsedExtension)
708                                 {
709                                     parsedExtension = TryParseElement(reader, result, this.Version);
710                                 }
711                                 if (!parsedExtension)
712                                 {
713                                     if (this.preserveElementExtensions)
714                                     {
715                                         CreateBufferIfRequiredAndWriteNode(ref buffer, ref extWriter, reader, this.maxExtensionSize);
716                                     }
717                                     else
718                                     {
719                                         TraceSyndicationElementIgnoredOnRead(reader);
720                                         reader.Skip();
721                                     }
722                                 }
723                             }
724                         }
725                         LoadElementExtensions(buffer, extWriter, result);
726                     }
727                     finally
728                     {
729                         if (extWriter != null)
730                         {
731                             ((IDisposable) extWriter).Dispose();
732                         }
733                     }
734                     reader.ReadEndElement(); // item
735                     if (!readAlternateLink && fallbackAlternateLink != null)
736                     {
737                         result.Links.Add(SyndicationLink.CreateAlternateLink(new Uri(fallbackAlternateLink, UriKind.RelativeOrAbsolute)));
738                         readAlternateLink = true;
739                     }
740
741                     // if there's no content and no alternate link set the summary as the item content
742                     if (result.Content == null && !readAlternateLink)
743                     {
744                         result.Content = result.Summary;
745                         result.Summary = null;
746                     }
747                 }
748             }
749             catch (FormatException e)
750             {
751                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingItem), e));
752             }
753             catch (ArgumentException e)
754             {
755                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingItem), e));
756             }
757         }
758
759         SyndicationLink ReadMediaEnclosure(XmlReader reader, Uri baseUri)
760         {
761             SyndicationLink link = new SyndicationLink();
762             link.BaseUri = baseUri;
763             link.RelationshipType = Rss20Constants.EnclosureTag;
764             bool isEmptyElement = reader.IsEmptyElement;
765             if (reader.HasAttributes)
766             {
767                 while (reader.MoveToNextAttribute())
768                 {
769                     string ns = reader.NamespaceURI;
770                     string name = reader.LocalName;
771                     if (name == "base" && ns == Atom10FeedFormatter.XmlNs)
772                     {
773                         link.BaseUri = FeedUtils.CombineXmlBase(link.BaseUri, reader.Value);
774                         continue;
775                     }
776                     if (FeedUtils.IsXmlns(name, ns))
777                     {
778                         continue;
779                     }
780                     string val = reader.Value;
781                     if (name == Rss20Constants.UrlTag && ns == Rss20Constants.Rss20Namespace)
782                     {
783                         link.Uri = new Uri(val, UriKind.RelativeOrAbsolute);
784                     }
785                     else if (name == Rss20Constants.TypeTag && ns == Rss20Constants.Rss20Namespace)
786                     {
787                         link.MediaType = val;
788                     }
789                     else if (name == Rss20Constants.LengthTag && ns == Rss20Constants.Rss20Namespace)
790                     {
791                         link.Length = !string.IsNullOrEmpty(val) ? Convert.ToInt64(val, CultureInfo.InvariantCulture.NumberFormat) : 0;
792                     }
793                     else if (!FeedUtils.IsXmlns(name, ns))
794                     {
795                         if (this.preserveAttributeExtensions)
796                         {
797                             link.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
798                         }
799                         else
800                         {
801                             TraceSyndicationElementIgnoredOnRead(reader);
802                         }
803                     }
804                 }
805             }
806             reader.ReadStartElement(Rss20Constants.EnclosureTag, Rss20Constants.Rss20Namespace);
807             if (!isEmptyElement)
808             {
809                 reader.ReadEndElement();
810             }
811             return link;
812         }
813
814         SyndicationPerson ReadPerson(XmlReader reader, SyndicationFeed feed)
815         {
816             SyndicationPerson result = CreatePerson(feed);
817             ReadPerson(reader, result);
818             return result;
819         }
820
821         SyndicationPerson ReadPerson(XmlReader reader, SyndicationItem item)
822         {
823             SyndicationPerson result = CreatePerson(item);
824             ReadPerson(reader, result);
825             return result;
826         }
827
828         void ReadPerson(XmlReader reader, SyndicationPerson person)
829         {
830             bool isEmpty = reader.IsEmptyElement;
831             if (reader.HasAttributes)
832             {
833                 while (reader.MoveToNextAttribute())
834                 {
835                     string ns = reader.NamespaceURI;
836                     string name = reader.LocalName;
837                     if (FeedUtils.IsXmlns(name, ns))
838                     {
839                         continue;
840                     }
841                     string val = reader.Value;
842                     if (!TryParseAttribute(name, ns, val, person, this.Version))
843                     {
844                         if (this.preserveAttributeExtensions)
845                         {
846                             person.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
847                         }
848                         else
849                         {
850                             TraceSyndicationElementIgnoredOnRead(reader);
851                         }
852                     }
853                 }
854             }
855             reader.ReadStartElement();
856             if (!isEmpty)
857             {
858                 string email = reader.ReadString();
859                 reader.ReadEndElement();
860                 person.Email = email;
861             }
862         }
863
864         void ReadXml(XmlReader reader, SyndicationFeed result)
865         {
866             try
867             {
868                 string baseUri = null;
869                 reader.MoveToContent();
870                 string version = reader.GetAttribute(Rss20Constants.VersionTag, Rss20Constants.Rss20Namespace);
871                 if (version != Rss20Constants.Version)
872                 {
873                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(FeedUtils.AddLineInfo(reader, (SR.GetString(SR.UnsupportedRssVersion, version)))));
874                 }
875                 if (reader.AttributeCount > 1)
876                 {
877                     string tmp = reader.GetAttribute("base", Atom10FeedFormatter.XmlNs);
878                     if (!string.IsNullOrEmpty(tmp))
879                     {
880                         baseUri = tmp;
881                     }
882                 }
883                 reader.ReadStartElement();
884                 reader.MoveToContent();
885                 if (reader.HasAttributes)
886                 {
887                     while (reader.MoveToNextAttribute())
888                     {
889                         string ns = reader.NamespaceURI;
890                         string name = reader.LocalName;
891                         if (name == "base" && ns == Atom10FeedFormatter.XmlNs)
892                         {
893                             baseUri = reader.Value;
894                             continue;
895                         }
896                         if (FeedUtils.IsXmlns(name, ns) || FeedUtils.IsXmlSchemaType(name, ns))
897                         {
898                             continue;
899                         }
900                         string val = reader.Value;
901                         if (!TryParseAttribute(name, ns, val, result, this.Version))
902                         {
903                             if (this.preserveAttributeExtensions)
904                             {
905                                 result.AttributeExtensions.Add(new XmlQualifiedName(name, ns), val);
906                             }
907                             else
908                             {
909                                 TraceSyndicationElementIgnoredOnRead(reader);
910                             }
911                         }
912                     }
913                 }
914                 if (!string.IsNullOrEmpty(baseUri))
915                 {
916                     result.BaseUri = new Uri(baseUri, UriKind.RelativeOrAbsolute);
917                 }
918                 bool areAllItemsRead = true;
919                 bool readItemsAtLeastOnce = false;
920                 reader.ReadStartElement(Rss20Constants.ChannelTag, Rss20Constants.Rss20Namespace);
921
922                 XmlBuffer buffer = null;
923                 XmlDictionaryWriter extWriter = null;
924                 try
925                 {
926                     while (reader.IsStartElement())
927                     {
928                         if (reader.IsStartElement(Rss20Constants.TitleTag, Rss20Constants.Rss20Namespace))
929                         {
930                             result.Title = new TextSyndicationContent(reader.ReadElementString());
931                         }
932                         else if (reader.IsStartElement(Rss20Constants.LinkTag, Rss20Constants.Rss20Namespace))
933                         {
934                             result.Links.Add(ReadAlternateLink(reader, result.BaseUri));
935                         }
936                         else if (reader.IsStartElement(Rss20Constants.DescriptionTag, Rss20Constants.Rss20Namespace))
937                         {
938                             result.Description = new TextSyndicationContent(reader.ReadElementString());
939                         }
940                         else if (reader.IsStartElement(Rss20Constants.LanguageTag, Rss20Constants.Rss20Namespace))
941                         {
942                             result.Language = reader.ReadElementString();
943                         }
944                         else if (reader.IsStartElement(Rss20Constants.CopyrightTag, Rss20Constants.Rss20Namespace))
945                         {
946                             result.Copyright = new TextSyndicationContent(reader.ReadElementString());
947                         }
948                         else if (reader.IsStartElement(Rss20Constants.ManagingEditorTag, Rss20Constants.Rss20Namespace))
949                         {
950                             result.Authors.Add(ReadPerson(reader, result));
951                         }
952                         else if (reader.IsStartElement(Rss20Constants.LastBuildDateTag, Rss20Constants.Rss20Namespace))
953                         {
954                             bool canReadContent = !reader.IsEmptyElement;
955                             reader.ReadStartElement();
956                             if (canReadContent)
957                             {
958                                 string str = reader.ReadString();
959                                 if (!string.IsNullOrEmpty(str))
960                                 {
961                                     result.LastUpdatedTime = DateFromString(str, reader);
962                                 }
963                                 reader.ReadEndElement();
964                             }
965                         }
966                         else if (reader.IsStartElement(Rss20Constants.CategoryTag, Rss20Constants.Rss20Namespace))
967                         {
968                             result.Categories.Add(ReadCategory(reader, result));
969                         }
970                         else if (reader.IsStartElement(Rss20Constants.GeneratorTag, Rss20Constants.Rss20Namespace))
971                         {
972                             result.Generator = reader.ReadElementString();
973                         }
974                         else if (reader.IsStartElement(Rss20Constants.ImageTag, Rss20Constants.Rss20Namespace))
975                         {
976                             reader.ReadStartElement();
977                             while (reader.IsStartElement())
978                             {
979                                 if (reader.IsStartElement(Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace))
980                                 {
981                                     result.ImageUrl = new Uri(reader.ReadElementString(), UriKind.RelativeOrAbsolute);
982                                 }
983                                 else
984                                 {
985                                     // ignore other content
986                                     TraceSyndicationElementIgnoredOnRead(reader);
987                                     reader.Skip();
988                                 }
989                             }
990                             reader.ReadEndElement(); // image
991                         }
992                         else if (reader.IsStartElement(Rss20Constants.ItemTag, Rss20Constants.Rss20Namespace))
993                         {
994                             if (readItemsAtLeastOnce)
995                             {
996                                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new InvalidOperationException(SR.GetString(SR.FeedHasNonContiguousItems, this.GetType().ToString())));
997                             }
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)
1002                             {
1003                                 break;
1004                             }
1005                         }
1006                         else
1007                         {
1008                             bool parsedExtension = this.serializeExtensionsAsAtom && this.atomSerializer.TryParseFeedElementFrom(reader, result);
1009                             if (!parsedExtension)
1010                             {
1011                                 parsedExtension = TryParseElement(reader, result, this.Version);
1012                             }
1013                             if (!parsedExtension)
1014                             {
1015                                 if (preserveElementExtensions)
1016                                 {
1017                                     CreateBufferIfRequiredAndWriteNode(ref buffer, ref extWriter, reader, this.maxExtensionSize);
1018                                 }
1019                                 else
1020                                 {
1021                                     TraceSyndicationElementIgnoredOnRead(reader);
1022                                     reader.Skip();
1023                                 }
1024                             }
1025                         }
1026                     }
1027                     LoadElementExtensions(buffer, extWriter, result);
1028                 }
1029                 finally
1030                 {
1031                     if (extWriter != null)
1032                     {
1033                         ((IDisposable) extWriter).Dispose();
1034                     }
1035                 }
1036                 if (areAllItemsRead)
1037                 {
1038                     reader.ReadEndElement(); // channel   
1039                     reader.ReadEndElement(); // rss
1040                 }
1041             }
1042             catch (FormatException e)
1043             {
1044                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingFeed), e));
1045             }
1046             catch (ArgumentException e)
1047             {
1048                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(FeedUtils.AddLineInfo(reader, SR.ErrorParsingFeed), e));
1049             }
1050         }
1051
1052         void WriteAlternateLink(XmlWriter writer, SyndicationLink link, Uri baseUri)
1053         {
1054             writer.WriteStartElement(Rss20Constants.LinkTag, Rss20Constants.Rss20Namespace);
1055             Uri baseUriToWrite = FeedUtils.GetBaseUriToWrite(baseUri, link.BaseUri);
1056             if (baseUriToWrite != null)
1057             {
1058                 writer.WriteAttributeString("xml", "base", Atom10FeedFormatter.XmlNs, FeedUtils.GetUriString(baseUriToWrite));
1059             }
1060             link.WriteAttributeExtensions(writer, SyndicationVersions.Rss20);
1061             writer.WriteString(FeedUtils.GetUriString(link.Uri));
1062             writer.WriteEndElement();
1063         }
1064
1065         void WriteCategory(XmlWriter writer, SyndicationCategory category)
1066         {
1067             if (category == null)
1068             {
1069                 return;
1070             }
1071             writer.WriteStartElement(Rss20Constants.CategoryTag, Rss20Constants.Rss20Namespace);
1072             WriteAttributeExtensions(writer, category, this.Version);
1073             if (!string.IsNullOrEmpty(category.Scheme) && !category.AttributeExtensions.ContainsKey(Rss20Domain))
1074             {
1075                 writer.WriteAttributeString(Rss20Constants.DomainTag, Rss20Constants.Rss20Namespace, category.Scheme);
1076             }
1077             writer.WriteString(category.Name);
1078             writer.WriteEndElement();
1079         }
1080
1081         void WriteFeed(XmlWriter writer)
1082         {
1083             if (this.Feed == null)
1084             {
1085                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.FeedFormatterDoesNotHaveFeed)));
1086             }
1087             if (this.serializeExtensionsAsAtom)
1088             {
1089                 writer.WriteAttributeString("xmlns", Atom10Constants.Atom10Prefix, null, Atom10Constants.Atom10Namespace);
1090             }
1091             writer.WriteAttributeString(Rss20Constants.VersionTag, Rss20Constants.Version);
1092             writer.WriteStartElement(Rss20Constants.ChannelTag, Rss20Constants.Rss20Namespace);
1093             if (this.Feed.BaseUri != null)
1094             {
1095                 writer.WriteAttributeString("xml", "base", Atom10FeedFormatter.XmlNs, FeedUtils.GetUriString(this.Feed.BaseUri));
1096             }
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);
1100
1101             SyndicationLink alternateLink = null;
1102             for (int i = 0; i < this.Feed.Links.Count; ++i)
1103             {
1104                 if (this.Feed.Links[i].RelationshipType == Atom10Constants.AlternateTag)
1105                 {
1106                     alternateLink = this.Feed.Links[i];
1107                     WriteAlternateLink(writer, alternateLink, this.Feed.BaseUri);
1108                     break;
1109                 }
1110             }
1111
1112             string description = this.Feed.Description != null ? this.Feed.Description.Text : string.Empty;
1113             writer.WriteElementString(Rss20Constants.DescriptionTag, Rss20Constants.Rss20Namespace, description);
1114
1115             if (this.Feed.Language != null)
1116             {
1117                 writer.WriteElementString(Rss20Constants.LanguageTag, this.Feed.Language);
1118             }
1119
1120             if (this.Feed.Copyright != null)
1121             {
1122                 writer.WriteElementString(Rss20Constants.CopyrightTag, Rss20Constants.Rss20Namespace, this.Feed.Copyright.Text);
1123             }
1124
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
1130             {
1131                 WritePerson(writer, Rss20Constants.ManagingEditorTag, this.Feed.Authors[0]);
1132             }
1133             else
1134             {
1135                 if (serializeExtensionsAsAtom)
1136                 {
1137                     this.atomSerializer.WriteFeedAuthorsTo(writer, this.Feed.Authors);
1138                 }
1139                 else
1140                 {
1141                     TraceExtensionsIgnoredOnWrite(SR.FeedAuthorsIgnoredOnWrite);
1142                 }
1143             }
1144
1145             if (this.Feed.LastUpdatedTime > DateTimeOffset.MinValue)
1146             {
1147                 writer.WriteStartElement(Rss20Constants.LastBuildDateTag);
1148                 writer.WriteString(AsString(this.Feed.LastUpdatedTime));
1149                 writer.WriteEndElement();
1150             }
1151
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
1155             {
1156                 WriteCategory(writer, this.Feed.Categories[i]);
1157             }
1158
1159             if (!string.IsNullOrEmpty(this.Feed.Generator))
1160             {
1161                 writer.WriteElementString(Rss20Constants.GeneratorTag, this.Feed.Generator);
1162             }
1163
1164 #pragma warning disable 56506 // Microsoft: this.Feed.Contributors is never null
1165             if (this.Feed.Contributors.Count > 0)
1166 #pragma warning restore 56506
1167             {
1168                 if (serializeExtensionsAsAtom)
1169                 {
1170                     this.atomSerializer.WriteFeedContributorsTo(writer, this.Feed.Contributors);
1171                 }
1172                 else
1173                 {
1174                     TraceExtensionsIgnoredOnWrite(SR.FeedContributorsIgnoredOnWrite);
1175                 }
1176             }
1177
1178             if (this.Feed.ImageUrl != null)
1179             {
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
1186             }
1187
1188             if (serializeExtensionsAsAtom)
1189             {
1190                 this.atomSerializer.WriteElement(writer, Atom10Constants.IdTag, this.Feed.Id);
1191
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)
1195                 {
1196                     if (this.Feed.Links[i].RelationshipType == Atom10Constants.AlternateTag && isFirstAlternateLink)
1197                     {
1198                         isFirstAlternateLink = false;
1199                         continue;
1200                     }
1201                     this.atomSerializer.WriteLink(writer, this.Feed.Links[i], this.Feed.BaseUri);
1202                 }
1203             }
1204             else
1205             {
1206                 if (this.Feed.Id != null)
1207                 {
1208                     TraceExtensionsIgnoredOnWrite(SR.FeedIdIgnoredOnWrite);
1209                 }
1210                 if (this.Feed.Links.Count > 1)
1211                 {
1212                     TraceExtensionsIgnoredOnWrite(SR.FeedLinksIgnoredOnWrite);
1213                 }
1214             }
1215
1216             WriteElementExtensions(writer, this.Feed, this.Version);
1217             WriteItems(writer, this.Feed.Items, this.Feed.BaseUri);
1218             writer.WriteEndElement(); // channel
1219         }
1220
1221         void WriteItemContents(XmlWriter writer, SyndicationItem item, Uri feedBaseUri)
1222         {
1223             Uri baseUriToWrite = FeedUtils.GetBaseUriToWrite(feedBaseUri, item.BaseUri);
1224             if (baseUriToWrite != null)
1225             {
1226                 writer.WriteAttributeString("xml", "base", Atom10FeedFormatter.XmlNs, FeedUtils.GetUriString(baseUriToWrite));
1227             }
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)
1233             {
1234                 if (item.Links[i].RelationshipType == Atom10Constants.AlternateTag)
1235                 {
1236                     if (firstAlternateLink == null)
1237                     {
1238                         firstAlternateLink = item.Links[i];
1239                     }
1240                     if (guid == FeedUtils.GetUriString(item.Links[i].Uri))
1241                     {
1242                         isPermalink = true;
1243                         break;
1244                     }
1245                 }
1246             }
1247             if (!string.IsNullOrEmpty(guid))
1248             {
1249                 writer.WriteStartElement(Rss20Constants.GuidTag);
1250                 if (isPermalink)
1251                 {
1252                     writer.WriteAttributeString(Rss20Constants.IsPermaLinkTag, "true");
1253                 }
1254                 else
1255                 {
1256                     writer.WriteAttributeString(Rss20Constants.IsPermaLinkTag, "false");
1257                 }
1258                 writer.WriteString(guid);
1259                 writer.WriteEndElement();
1260             }
1261             if (firstAlternateLink != null)
1262             {
1263                 WriteAlternateLink(writer, firstAlternateLink, (item.BaseUri != null ? item.BaseUri : feedBaseUri));
1264             }
1265
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
1269             {
1270                 WritePerson(writer, Rss20Constants.AuthorTag, item.Authors[0]);
1271             }
1272             else
1273             {
1274                 if (serializeExtensionsAsAtom)
1275                 {
1276                     this.atomSerializer.WriteItemAuthorsTo(writer, item.Authors);
1277                 }
1278                 else
1279                 {
1280                     TraceExtensionsIgnoredOnWrite(SR.ItemAuthorsIgnoredOnWrite);
1281                 }
1282             }
1283
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
1287             {
1288                 WriteCategory(writer, item.Categories[i]);
1289             }
1290
1291             bool serializedTitle = false;
1292             if (item.Title != null)
1293             {
1294                 writer.WriteElementString(Rss20Constants.TitleTag, item.Title.Text);
1295                 serializedTitle = true;
1296             }
1297
1298             bool serializedContentAsDescription = false;
1299             TextSyndicationContent summary = item.Summary;
1300             if (summary == null)
1301             {
1302                 summary = (item.Content as TextSyndicationContent);
1303                 serializedContentAsDescription = (summary != null);
1304             }
1305             // the spec requires the wire to have a title or a description
1306             if (!serializedTitle && summary == null)
1307             {
1308                 summary = new TextSyndicationContent(string.Empty);
1309             }
1310             if (summary != null)
1311             {
1312                 writer.WriteElementString(Rss20Constants.DescriptionTag, Rss20Constants.Rss20Namespace, summary.Text);
1313             }
1314
1315             if (item.SourceFeed != null)
1316             {
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)
1321                 {
1322                     if (item.SourceFeed.Links[i].RelationshipType == Atom10Constants.SelfTag)
1323                     {
1324                         selfLink = item.SourceFeed.Links[i];
1325                         break;
1326                     }
1327                 }
1328                 if (selfLink != null && !item.SourceFeed.AttributeExtensions.ContainsKey(Rss20Url))
1329                 {
1330                     writer.WriteAttributeString(Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace, FeedUtils.GetUriString(selfLink.Uri));
1331                 }
1332                 string title = (item.SourceFeed.Title != null) ? item.SourceFeed.Title.Text : string.Empty;
1333                 writer.WriteString(title);
1334                 writer.WriteEndElement();
1335             }
1336
1337             if (item.PublishDate > DateTimeOffset.MinValue)
1338             {
1339                 writer.WriteElementString(Rss20Constants.PubDateTag, Rss20Constants.Rss20Namespace, AsString(item.PublishDate));
1340             }
1341
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)
1347             {
1348                 if (item.Links[i].RelationshipType == Rss20Constants.EnclosureTag)
1349                 {
1350                     if (firstEnclosureLink == null)
1351                     {
1352                         firstEnclosureLink = item.Links[i];
1353                         WriteMediaEnclosure(writer, item.Links[i], item.BaseUri);
1354                         continue;
1355                     }
1356                 }
1357                 else if (item.Links[i].RelationshipType == Atom10Constants.AlternateTag)
1358                 {
1359                     if (!passedFirstAlternateLink)
1360                     {
1361                         passedFirstAlternateLink = true;
1362                         continue;
1363                     }
1364                 }
1365                 if (this.serializeExtensionsAsAtom)
1366                 {
1367                     this.atomSerializer.WriteLink(writer, item.Links[i], item.BaseUri);
1368                 }
1369                 else
1370                 {
1371                     isLinkIgnored = true;
1372                 }
1373             }
1374             if (isLinkIgnored)
1375             {
1376                 TraceExtensionsIgnoredOnWrite(SR.ItemLinksIgnoredOnWrite);
1377             }
1378
1379             if (item.LastUpdatedTime > DateTimeOffset.MinValue)
1380             {
1381                 if (this.serializeExtensionsAsAtom)
1382                 {
1383                     this.atomSerializer.WriteItemLastUpdatedTimeTo(writer, item.LastUpdatedTime);
1384                 }
1385                 else
1386                 {
1387                     TraceExtensionsIgnoredOnWrite(SR.ItemLastUpdatedTimeIgnoredOnWrite);
1388                 }
1389             }
1390
1391             if (serializeExtensionsAsAtom)
1392             {
1393                 this.atomSerializer.WriteContentTo(writer, Atom10Constants.RightsTag, item.Copyright);
1394             }
1395             else
1396             {
1397                 TraceExtensionsIgnoredOnWrite(SR.ItemCopyrightIgnoredOnWrite);
1398             }
1399
1400             if (!serializedContentAsDescription)
1401             {
1402                 if (serializeExtensionsAsAtom)
1403                 {
1404                     this.atomSerializer.WriteContentTo(writer, Atom10Constants.ContentTag, item.Content);
1405                 }
1406                 else
1407                 {
1408                     TraceExtensionsIgnoredOnWrite(SR.ItemContentIgnoredOnWrite);
1409                 }
1410             }
1411
1412 #pragma warning disable 56506 // Microsoft, item.COntributors is never null
1413             if (item.Contributors.Count > 0)
1414 #pragma warning restore 56506
1415             {
1416                 if (serializeExtensionsAsAtom)
1417                 {
1418                     this.atomSerializer.WriteItemContributorsTo(writer, item.Contributors);
1419                 }
1420                 else
1421                 {
1422                     TraceExtensionsIgnoredOnWrite(SR.ItemContributorsIgnoredOnWrite);
1423                 }
1424             }
1425
1426             WriteElementExtensions(writer, item, this.Version);
1427         }
1428
1429         void WriteMediaEnclosure(XmlWriter writer, SyndicationLink link, Uri baseUri)
1430         {
1431             writer.WriteStartElement(Rss20Constants.EnclosureTag, Rss20Constants.Rss20Namespace);
1432             Uri baseUriToWrite = FeedUtils.GetBaseUriToWrite(baseUri, link.BaseUri);
1433             if (baseUriToWrite != null)
1434             {
1435                 writer.WriteAttributeString("xml", "base", Atom10FeedFormatter.XmlNs, FeedUtils.GetUriString(baseUriToWrite));
1436             }
1437             link.WriteAttributeExtensions(writer, SyndicationVersions.Rss20);
1438             if (!link.AttributeExtensions.ContainsKey(Rss20Url))
1439             {
1440                 writer.WriteAttributeString(Rss20Constants.UrlTag, Rss20Constants.Rss20Namespace, FeedUtils.GetUriString(link.Uri));
1441             }
1442             if (link.MediaType != null && !link.AttributeExtensions.ContainsKey(Rss20Type))
1443             {
1444                 writer.WriteAttributeString(Rss20Constants.TypeTag, Rss20Constants.Rss20Namespace, link.MediaType);
1445             }
1446             if (link.Length != 0 && !link.AttributeExtensions.ContainsKey(Rss20Length))
1447             {
1448                 writer.WriteAttributeString(Rss20Constants.LengthTag, Rss20Constants.Rss20Namespace, Convert.ToString(link.Length, CultureInfo.InvariantCulture));
1449             }
1450             writer.WriteEndElement();
1451         }
1452
1453         void WritePerson(XmlWriter writer, string elementTag, SyndicationPerson person)
1454         {
1455             writer.WriteStartElement(elementTag, Rss20Constants.Rss20Namespace);
1456             WriteAttributeExtensions(writer, person, this.Version);
1457             writer.WriteString(person.Email);
1458             writer.WriteEndElement();
1459         }
1460     }
1461
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 ()
1466     {
1467         // constructors
1468         public Rss20FeedFormatter()
1469             : base(typeof(TSyndicationFeed))
1470         {
1471         }
1472         public Rss20FeedFormatter(TSyndicationFeed feedToWrite)
1473             : base(feedToWrite)
1474         {
1475         }
1476         public Rss20FeedFormatter(TSyndicationFeed feedToWrite, bool serializeExtensionsAsAtom)
1477             : base(feedToWrite, serializeExtensionsAsAtom)
1478         {
1479         }
1480
1481         protected override SyndicationFeed CreateFeedInstance()
1482         {
1483             return new TSyndicationFeed();
1484         }
1485     }
1486 }