2009-10-16 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.ServiceModel.Web / System.ServiceModel.Syndication / Rss20ItemFormatter.cs
1 //
2 // Rss20ItemFormatter.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections.Generic;
30 using System.Collections.ObjectModel;
31 using System.Globalization;
32 using System.IO;
33 using System.Linq;
34 using System.Runtime.Serialization;
35 using System.Text;
36 using System.Xml;
37 using System.Xml.Schema;
38 using System.Xml.Serialization;
39
40 namespace System.ServiceModel.Syndication
41 {
42         static class XmlReaderExtensions
43         {
44                 public static bool IsTextNode (this XmlReader r)
45                 {
46                         switch (r.NodeType) {
47                         case XmlNodeType.Text:
48                         case XmlNodeType.CDATA:
49                         case XmlNodeType.Whitespace:
50                         case XmlNodeType.SignificantWhitespace:
51                                 return true;
52                         }
53                         return false;
54                 }
55         }
56
57         [XmlRoot ("item", Namespace = "")]
58         public class Rss20ItemFormatter : SyndicationItemFormatter, IXmlSerializable
59         {
60                 const string AtomNamespace ="http://www.w3.org/2005/Atom";
61
62                 bool ext_atom_serialization, preserve_att_ext = true, preserve_elem_ext = true;
63                 Type item_type;
64
65                 public Rss20ItemFormatter ()
66                 {
67                         ext_atom_serialization = true;
68                 }
69
70                 public Rss20ItemFormatter (SyndicationItem itemToWrite)
71                         : this (itemToWrite, true)
72                 {
73                 }
74
75                 public Rss20ItemFormatter (SyndicationItem itemToWrite, bool serializeExtensionsAsAtom)
76                         : base (itemToWrite)
77                 {
78                         ext_atom_serialization = serializeExtensionsAsAtom;
79                 }
80
81                 public Rss20ItemFormatter (Type itemTypeToCreate)
82                 {
83                         if (itemTypeToCreate == null)
84                                 throw new ArgumentNullException ("itemTypeToCreate");
85                         item_type = itemTypeToCreate;
86                 }
87
88                 public bool SerializeExtensionsAsAtom {
89                         get { return ext_atom_serialization; }
90                         set { ext_atom_serialization = value; }
91                 }
92
93                 protected Type ItemType {
94                         get { return item_type; }
95                 }
96
97                 public bool PreserveAttributeExtensions {
98                         get { return preserve_att_ext; }
99                         set { preserve_att_ext = value; }
100                 }
101
102                 public bool PreserveElementExtensions {
103                         get { return preserve_elem_ext; }
104                         set { preserve_elem_ext = value; }
105                 }
106
107                 public override string Version {
108                         get { return "Rss20"; }
109                 }
110
111                 protected override SyndicationItem CreateItemInstance ()
112                 {
113                         return new SyndicationItem ();
114                 }
115
116                 public override bool CanRead (XmlReader reader)
117                 {
118                         if (reader == null)
119                                 throw new ArgumentNullException ("reader");
120                         reader.MoveToContent ();
121                         return reader.IsStartElement ("item", String.Empty);
122                 }
123
124                 public override void ReadFrom (XmlReader reader)
125                 {
126                         if (!CanRead (reader))
127                                 throw new XmlException (String.Format ("Element '{0}' in namespace '{1}' is not accepted by this syndication formatter", reader.LocalName, reader.NamespaceURI));
128                         ReadXml (reader, true);
129                 }
130
131                 public override void WriteTo (XmlWriter writer)
132                 {
133                         WriteXml (writer, true);
134                 }
135
136                 void IXmlSerializable.ReadXml (XmlReader reader)
137                 {
138                         ReadXml (reader, false);
139                 }
140
141                 void IXmlSerializable.WriteXml (XmlWriter writer)
142                 {
143                         WriteXml (writer, false);
144                 }
145
146                 XmlSchema IXmlSerializable.GetSchema ()
147                 {
148                         return null;
149                 }
150
151                 // read
152
153                 void ReadXml (XmlReader reader, bool fromSerializable)
154                 {
155                         if (reader == null)
156                                 throw new ArgumentNullException ("reader");
157                         
158                         SetItem (CreateItemInstance ());
159
160                         reader.MoveToContent ();
161
162                         if (PreserveAttributeExtensions && reader.MoveToFirstAttribute ()) {
163                                 do {
164                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
165                                                 continue;
166                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, Item, Version))
167                                                 Item.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
168                                 } while (reader.MoveToNextAttribute ());
169                         }
170
171                         reader.ReadStartElement ();
172
173                         for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
174                                 if (reader.NodeType != XmlNodeType.Element)
175                                         throw new XmlException ("Only element node is expected under 'item' element");
176                                 if (reader.NamespaceURI == String.Empty)
177                                         switch (reader.LocalName) {
178                                         case "title":
179                                                 Item.Title = ReadTextSyndicationContent (reader);
180                                                 continue;
181                                         case "link":
182                                                 SyndicationLink l = Item.CreateLink ();
183                                                 ReadLink (reader, l);
184                                                 Item.Links.Add (l);
185                                                 continue;
186                                         case "description":
187                                                 Item.Summary = ReadTextSyndicationContent (reader);
188                                                 continue;
189                                         case "author":
190                                                 SyndicationPerson p = Item.CreatePerson ();
191                                                 ReadPerson (reader, p);
192                                                 Item.Authors.Add (p);
193                                                 continue;
194                                         case "category":
195                                                 SyndicationCategory c = Item.CreateCategory ();
196                                                 ReadCategory (reader, c);
197                                                 Item.Categories.Add (c);
198                                                 continue;
199                                         // case "comments": // treated as extension ...
200                                         case "enclosure":
201                                                 l = Item.CreateLink ();
202                                                 ReadEnclosure (reader, l);
203                                                 Item.Links.Add (l);
204                                                 continue;
205                                         case "guid":
206                                                 Item.AddPermalink (CreateUri (reader.ReadElementContentAsString ()));
207                                                 continue;
208                                         case "pubDate":
209                                                 // FIXME: somehow DateTimeOffset causes the runtime crash.
210                                                 reader.ReadElementContentAsString ();
211                                                 // Item.PublishDate = FromRFC822DateString (reader.ReadElementContentAsString ());
212                                                 continue;
213                                         case "source":
214                                                 Item.SourceFeed = new SyndicationFeed ();
215                                                 ReadSourceFeed (reader, Item.SourceFeed);
216                                                 continue;
217                                         }
218                                 else if (SerializeExtensionsAsAtom && reader.NamespaceURI == AtomNamespace) {
219                                         switch (reader.LocalName) {
220                                         case "contributor":
221                                                 SyndicationPerson p = Item.CreatePerson ();
222                                                 ReadPersonAtom10 (reader, p);
223                                                 Item.Contributors.Add (p);
224                                                 continue;
225                                         case "updated":
226                                                 // FIXME: somehow DateTimeOffset causes the runtime crash.
227                                                 reader.ReadElementContentAsString ();
228                                                 // Item.LastUpdatedTime = XmlConvert.ToDateTimeOffset (reader.ReadElementContentAsString ());
229                                                 continue;
230                                         case "rights":
231                                                 Item.Copyright = ReadTextSyndicationContent (reader);
232                                                 continue;
233                                         case "content":
234                                                 if (reader.GetAttribute ("src") != null) {
235                                                         Item.Content = new UrlSyndicationContent (CreateUri (reader.GetAttribute ("src")), reader.GetAttribute ("type"));
236                                                         reader.Skip ();
237                                                         continue;
238                                                 }
239                                                 switch (reader.GetAttribute ("type")) {
240                                                 case "text":
241                                                 case "html":
242                                                 case "xhtml":
243                                                         Item.Content = ReadTextSyndicationContent (reader);
244                                                         continue;
245                                                 default:
246                                                         SyndicationContent content;
247                                                         if (!TryParseContent (reader, Item, reader.GetAttribute ("type"), Version, out content))
248                                                                 Item.Content = new XmlSyndicationContent (reader);
249                                                         continue;
250                                                 }
251                                         }
252                                 }
253                                 if (!TryParseElement (reader, Item, Version)) {
254                                         if (PreserveElementExtensions)
255                                                 // FIXME: what to specify for maxExtensionSize
256                                                 LoadElementExtensions (reader, Item, int.MaxValue);
257                                         else
258                                                 reader.Skip ();
259                                 }
260                         }
261
262                         reader.ReadEndElement (); // </item>
263                 }
264
265                 TextSyndicationContent ReadTextSyndicationContent (XmlReader reader)
266                 {
267                         TextSyndicationContentKind kind = TextSyndicationContentKind.Plaintext;
268                         switch (reader.GetAttribute ("type")) {
269                         case "html":
270                                 kind = TextSyndicationContentKind.Html;
271                                 break;
272                         case "xhtml":
273                                 kind = TextSyndicationContentKind.XHtml;
274                                 break;
275                         }
276                         string text = reader.ReadElementContentAsString ();
277                         TextSyndicationContent t = new TextSyndicationContent (text, kind);
278                         return t;
279                 }
280
281                 void ReadCategory (XmlReader reader, SyndicationCategory category)
282                 {
283                         if (reader.MoveToFirstAttribute ()) {
284                                 do {
285                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
286                                                 continue;
287                                         if (reader.NamespaceURI == String.Empty) {
288                                                 switch (reader.LocalName) {
289                                                 case "domain":
290                                                         category.Scheme = reader.Value;
291                                                         continue;
292                                                 }
293                                         }
294                                         if (PreserveAttributeExtensions)
295                                                 if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, category, Version))
296                                                         category.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
297                                 } while (reader.MoveToNextAttribute ());
298                                 reader.MoveToElement ();
299                         }
300
301                         if (!reader.IsEmptyElement) {
302                                 reader.Read ();
303                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
304                                         if (reader.IsTextNode ())
305                                                 category.Name += reader.Value;
306                                         else if (!TryParseElement (reader, category, Version)) {
307                                                 if (PreserveElementExtensions)
308                                                         // FIXME: what should be used for maxExtenswionSize
309                                                         LoadElementExtensions (reader, category, int.MaxValue);
310                                                 else
311                                                         reader.Skip ();
312                                         }
313                                         reader.Read ();
314                                 }
315                         }
316                         reader.Read (); // </category> or <category ... />
317                 }
318
319                 // SyndicationLink.CreateMediaEnclosureLink() is almost
320                 // useless here since it cannot handle extension attributes
321                 // in straightforward way (it I use it, I have to iterate
322                 // attributes twice just to read extensions).
323                 void ReadEnclosure (XmlReader reader, SyndicationLink link)
324                 {
325                         link.RelationshipType = "enclosure";
326
327                         if (PreserveAttributeExtensions && reader.MoveToFirstAttribute ()) {
328                                 do {
329                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
330                                                 continue;
331                                         if (reader.NamespaceURI == String.Empty) {
332                                                 switch (reader.LocalName) {
333                                                 case "url":
334                                                         link.Uri = CreateUri (reader.Value);
335                                                         continue;
336                                                 case "type":
337                                                         link.MediaType = reader.Value;
338                                                         continue;
339                                                 case "length":
340                                                         link.Length = XmlConvert.ToInt64 (reader.Value);
341                                                         continue;
342                                                 }
343                                         }
344                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, link, Version))
345                                                 link.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
346                                 } while (reader.MoveToNextAttribute ());
347                                 reader.MoveToElement ();
348                         }
349
350                         // Actually .NET fails to read extension here.
351                         if (!reader.IsEmptyElement) {
352                                 reader.Read ();
353                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
354                                         if (!TryParseElement (reader, link, Version)) {
355                                                 if (PreserveElementExtensions)
356                                                         // FIXME: what should be used for maxExtenswionSize
357                                                         LoadElementExtensions (reader, link, int.MaxValue);
358                                                 else
359                                                         reader.Skip ();
360                                         }
361                                 }
362                         }
363                         reader.Read (); // </enclosure> or <enclosure ... />
364                 }
365
366                 void ReadLink (XmlReader reader, SyndicationLink link)
367                 {
368                         if (PreserveAttributeExtensions && reader.MoveToFirstAttribute ()) {
369                                 do {
370                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
371                                                 continue;
372                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, link, Version))
373                                                 link.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
374                                 } while (reader.MoveToNextAttribute ());
375                                 reader.MoveToElement ();
376                         }
377
378                         if (!reader.IsEmptyElement) {
379                                 string url = null;
380                                 reader.Read ();
381                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
382                                         if (reader.IsTextNode ())
383                                                 url += reader.Value;
384                                         else if (!TryParseElement (reader, link, Version)) {
385                                                 if (PreserveElementExtensions)
386                                                         // FIXME: what should be used for maxExtenswionSize
387                                                         LoadElementExtensions (reader, link, int.MaxValue);
388                                                 else
389                                                         reader.Skip ();
390                                         }
391                                         reader.Read ();
392                                 }
393                                 link.Uri = CreateUri (url);
394                         }
395                         reader.Read (); // </link> or <link ... />
396                 }
397
398                 void ReadPerson (XmlReader reader, SyndicationPerson person)
399                 {
400                         if (PreserveAttributeExtensions && reader.MoveToFirstAttribute ()) {
401                                 do {
402                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
403                                                 continue;
404                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, person, Version))
405                                                 person.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
406                                 } while (reader.MoveToNextAttribute ());
407                                 reader.MoveToElement ();
408                         }
409
410                         if (!reader.IsEmptyElement) {
411                                 reader.Read ();
412                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
413                                         if (reader.IsTextNode ())
414                                                 person.Email += reader.Value;
415                                         else if (!TryParseElement (reader, person, Version)) {
416                                                 if (PreserveElementExtensions)
417                                                         // FIXME: what should be used for maxExtenswionSize
418                                                         LoadElementExtensions (reader, person, int.MaxValue);
419                                                 else
420                                                         reader.Skip ();
421                                         }
422                                         reader.Read ();
423                                 }
424                         }
425                         reader.Read (); // end element or empty element
426                 }
427
428                 // copied from Atom10ItemFormatter
429                 void ReadPersonAtom10 (XmlReader reader, SyndicationPerson person)
430                 {
431                         if (reader.MoveToFirstAttribute ()) {
432                                 do {
433                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
434                                                 continue;
435                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, person, Version) && PreserveAttributeExtensions)
436                                                 person.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
437                                 } while (reader.MoveToNextAttribute ());
438                                 reader.MoveToElement ();
439                         }
440
441                         if (!reader.IsEmptyElement) {
442                                 reader.Read ();
443                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
444                                         if (reader.NodeType == XmlNodeType.Element && reader.NamespaceURI == AtomNamespace) {
445                                                 switch (reader.LocalName) {
446                                                 case "name":
447                                                         person.Name = reader.ReadElementContentAsString ();
448                                                         continue;
449                                                 case "uri":
450                                                         person.Uri = reader.ReadElementContentAsString ();
451                                                         continue;
452                                                 case "email":
453                                                         person.Email = reader.ReadElementContentAsString ();
454                                                         continue;
455                                                 }
456                                         }
457                                         if (!TryParseElement (reader, person, Version)) {
458                                                 if (PreserveElementExtensions)
459                                                         // FIXME: what should be used for maxExtenswionSize
460                                                         LoadElementExtensions (reader, person, int.MaxValue);
461                                                 else
462                                                         reader.Skip ();
463                                         }
464                                 }
465                         }
466                         reader.Read (); // end element or empty element
467                 }
468
469                 void ReadSourceFeed (XmlReader reader, SyndicationFeed feed)
470                 {
471                         if (reader.MoveToFirstAttribute ()) {
472                                 do {
473                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
474                                                 continue;
475                                         if (reader.NamespaceURI == String.Empty) {
476                                                 switch (reader.LocalName) {
477                                                 case "url":
478                                                         feed.Links.Add (new SyndicationLink (CreateUri (reader.Value)));
479                                                         continue;
480                                                 }
481                                         }
482                                 } while (reader.MoveToNextAttribute ());
483                                 reader.MoveToElement ();
484                         }
485
486                         if (!reader.IsEmptyElement) {
487                                 reader.Read ();
488                                 string title = null;
489                                 while (reader.NodeType != XmlNodeType.EndElement) {
490                                         if (reader.IsTextNode ())
491                                                 title += reader.Value;
492                                         reader.Skip ();
493                                         reader.MoveToContent ();
494                                 }
495                                 feed.Title = new TextSyndicationContent (title);
496                         }
497                         reader.Read (); // </source> or <source ... />
498                 }
499
500                 Uri CreateUri (string uri)
501                 {
502                         return new Uri (uri, UriKind.RelativeOrAbsolute);
503                 }
504
505                 // write
506
507                 void WriteXml (XmlWriter writer, bool writeRoot)
508                 {
509                         if (writer == null)
510                                 throw new ArgumentNullException ("writer");
511                         if (Item == null)
512                                 throw new InvalidOperationException ("Syndication item must be set before writing");
513
514                         if (writeRoot)
515                                 writer.WriteStartElement ("item");
516
517                         if (Item.BaseUri != null)
518                                 writer.WriteAttributeString ("xml:base", Item.BaseUri.ToString ());
519
520                         WriteAttributeExtensions (writer, Item, Version);
521
522                         if (Item.Id != null) {
523                                 writer.WriteStartElement ("guid");
524                                 writer.WriteAttributeString ("isPermaLink", "false");
525                                 writer.WriteString (Item.Id);
526                                 writer.WriteEndElement ();
527                         }
528
529                         if (Item.Title != null) {
530                                 writer.WriteStartElement ("title");
531                                 writer.WriteString (Item.Title.Text);
532                                 writer.WriteEndElement ();
533                         }
534
535                         foreach (SyndicationPerson author in Item.Authors)
536                                 if (author != null) {
537                                         writer.WriteStartElement ("author");
538                                         WriteAttributeExtensions (writer, author, Version);
539                                         writer.WriteString (author.Email);
540                                         WriteElementExtensions (writer, author, Version);
541                                         writer.WriteEndElement ();
542                                 }
543                         foreach (SyndicationCategory category in Item.Categories)
544                                 if (category != null) {
545                                         writer.WriteStartElement ("category");
546                                         if (category.Scheme != null)
547                                                 writer.WriteAttributeString ("domain", category.Scheme);
548                                         WriteAttributeExtensions (writer, category, Version);
549                                         writer.WriteString (category.Name);
550                                         WriteElementExtensions (writer, category, Version);
551                                         writer.WriteEndElement ();
552                                 }
553
554                         if (Item.Content != null) {
555                                 Item.Content.WriteTo (writer, "description", String.Empty);
556                         } else if (Item.Summary != null)
557                                 Item.Summary.WriteTo (writer, "description", String.Empty);
558                         else if (Item.Title == null) { // according to the RSS 2.0 spec, either of title or description must exist.
559                                 writer.WriteStartElement ("description");
560                                 writer.WriteEndElement ();
561                         }
562
563                         foreach (SyndicationLink link in Item.Links)
564                                 switch (link.RelationshipType) {
565                                 case "enclosure":
566                                         writer.WriteStartElement ("enclosure");
567                                         if (link.Uri != null)
568                                                 writer.WriteAttributeString ("uri", link.Uri.ToString ());
569                                         if (link.Length != 0)
570                                                 writer.WriteAttributeString ("length", XmlConvert.ToString (link.Length));
571                                         if (link.MediaType != null)
572                                                 writer.WriteAttributeString ("type", link.MediaType);
573                                         WriteAttributeExtensions (writer, link, Version);
574                                         WriteElementExtensions (writer, link, Version);
575                                         writer.WriteEndElement ();
576                                         break;
577                                 default:
578                                         writer.WriteStartElement ("link");
579                                         WriteAttributeExtensions (writer, link, Version);
580                                         writer.WriteString (link.Uri != null ? link.Uri.ToString () : String.Empty);
581                                         WriteElementExtensions (writer, link, Version);
582                                         writer.WriteEndElement ();
583                                         break;
584                                 }
585
586                         if (Item.SourceFeed != null) {
587                                 writer.WriteStartElement ("source");
588                                 if (Item.SourceFeed.Links.Count > 0) {
589                                         Uri u = Item.SourceFeed.Links [0].Uri;
590                                         writer.WriteAttributeString ("url", u != null ? u.ToString () : String.Empty);
591                                 }
592                                 writer.WriteString (Item.SourceFeed.Title != null ? Item.SourceFeed.Title.Text : String.Empty);
593                                 writer.WriteEndElement ();
594                         }
595
596                         if (!Item.PublishDate.Equals (default (DateTimeOffset))) {
597                                 writer.WriteStartElement ("pubDate");
598                                 writer.WriteString (ToRFC822DateString (Item.PublishDate));
599                                 writer.WriteEndElement ();
600                         }
601
602                         if (SerializeExtensionsAsAtom) {
603                                 foreach (SyndicationPerson contributor in Item.Contributors) {
604                                         if (contributor != null) {
605                                                 writer.WriteStartElement ("contributor", AtomNamespace);
606                                                 WriteAttributeExtensions (writer, contributor, Version);
607                                                 writer.WriteElementString ("name", AtomNamespace, contributor.Name);
608                                                 writer.WriteElementString ("uri", AtomNamespace, contributor.Uri);
609                                                 writer.WriteElementString ("email", AtomNamespace, contributor.Email);
610                                                 WriteElementExtensions (writer, contributor, Version);
611                                                 writer.WriteEndElement ();
612                                         }
613                                 }
614
615                                 if (!Item.LastUpdatedTime.Equals (default (DateTimeOffset))) {
616                                         writer.WriteStartElement ("updated", AtomNamespace);
617                                         // FIXME: how to handle offset part?
618                                         writer.WriteString (XmlConvert.ToString (Item.LastUpdatedTime.DateTime, XmlDateTimeSerializationMode.Local));
619                                         writer.WriteEndElement ();
620                                 }
621
622                                 if (Item.Copyright != null)
623                                         Item.Copyright.WriteTo (writer, "rights", AtomNamespace);
624 #if false
625                                 if (Item.Content != null)
626                                         Item.Content.WriteTo (writer, "content", AtomNamespace);
627 #endif
628                         }
629
630                         WriteElementExtensions (writer, Item, Version);
631
632                         if (writeRoot)
633                                 writer.WriteEndElement ();
634                 }
635
636                 // FIXME: DateTimeOffset.ToString() needs another overload.
637                 // When it is implemented, just remove ".DateTime" parts below.
638                 string ToRFC822DateString (DateTimeOffset date)
639                 {
640                         switch (date.DateTime.Kind) {
641                         case DateTimeKind.Utc:
642                                 return date.DateTime.ToString ("ddd, dd MMM yyyy HH:mm:ss 'Z'", DateTimeFormatInfo.InvariantInfo);
643                         case DateTimeKind.Local:
644                                 StringBuilder sb = new StringBuilder (date.DateTime.ToString ("ddd, dd MMM yyyy HH:mm:ss zzz", DateTimeFormatInfo.InvariantInfo));
645                                 sb.Remove (sb.Length - 3, 1);
646                                 return sb.ToString (); // remove ':' from +hh:mm
647                         default:
648                                 return date.DateTime.ToString ("ddd, dd MMM yyyy HH:mm:ss", DateTimeFormatInfo.InvariantInfo);
649                         }
650                 }
651
652                 string [] rfc822formats = new string [] {
653                         "ddd, dd MMM yyyy HH:mm:ss 'Z'",
654                         "ddd, dd MMM yyyy HH:mm:ss zzz",
655                         "ddd, dd MMM yyyy HH:mm:ss"};
656
657                 // FIXME: DateTimeOffset is still incomplete. When it is done,
658                 // simplify the code.
659                 DateTimeOffset FromRFC822DateString (string s)
660                 {
661                         return XmlConvert.ToDateTimeOffset (s, rfc822formats);
662                 }
663         }
664 }