Merge pull request #1326 from BrzVlad/master
[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.Id = reader.ReadElementContentAsString ();
207                                                 if (reader.GetAttribute ("isPermaLink") == "true")
208                                                         Item.AddPermalink (CreateUri (Item.Id));
209                                                 continue;
210                                         case "pubDate":
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                                                 Item.LastUpdatedTime = XmlConvert.ToDateTimeOffset (reader.ReadElementContentAsString ());
227                                                 continue;
228                                         case "rights":
229                                                 Item.Copyright = ReadTextSyndicationContent (reader);
230                                                 continue;
231                                         case "content":
232                                                 if (reader.GetAttribute ("src") != null) {
233                                                         Item.Content = new UrlSyndicationContent (CreateUri (reader.GetAttribute ("src")), reader.GetAttribute ("type"));
234                                                         reader.Skip ();
235                                                         continue;
236                                                 }
237                                                 switch (reader.GetAttribute ("type")) {
238                                                 case "text":
239                                                 case "html":
240                                                 case "xhtml":
241                                                         Item.Content = ReadTextSyndicationContent (reader);
242                                                         continue;
243                                                 default:
244                                                         SyndicationContent content;
245                                                         if (!TryParseContent (reader, Item, reader.GetAttribute ("type"), Version, out content))
246                                                                 Item.Content = new XmlSyndicationContent (reader);
247                                                         continue;
248                                                 }
249                                         }
250                                 }
251                                 if (!TryParseElement (reader, Item, Version)) {
252                                         if (PreserveElementExtensions)
253                                                 // FIXME: what to specify for maxExtensionSize
254                                                 LoadElementExtensions (reader, Item, int.MaxValue);
255                                         else
256                                                 reader.Skip ();
257                                 }
258                         }
259
260                         reader.ReadEndElement (); // </item>
261                 }
262
263                 TextSyndicationContent ReadTextSyndicationContent (XmlReader reader)
264                 {
265                         TextSyndicationContentKind kind = TextSyndicationContentKind.Plaintext;
266                         switch (reader.GetAttribute ("type")) {
267                         case "html":
268                                 kind = TextSyndicationContentKind.Html;
269                                 break;
270                         case "xhtml":
271                                 kind = TextSyndicationContentKind.XHtml;
272                                 break;
273                         }
274                         string text = reader.ReadElementContentAsString ();
275                         TextSyndicationContent t = new TextSyndicationContent (text, kind);
276                         return t;
277                 }
278
279                 void ReadCategory (XmlReader reader, SyndicationCategory category)
280                 {
281                         if (reader.MoveToFirstAttribute ()) {
282                                 do {
283                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
284                                                 continue;
285                                         if (reader.NamespaceURI == String.Empty) {
286                                                 switch (reader.LocalName) {
287                                                 case "domain":
288                                                         category.Scheme = reader.Value;
289                                                         continue;
290                                                 }
291                                         }
292                                         if (PreserveAttributeExtensions)
293                                                 if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, category, Version))
294                                                         category.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
295                                 } while (reader.MoveToNextAttribute ());
296                                 reader.MoveToElement ();
297                         }
298
299                         if (!reader.IsEmptyElement) {
300                                 reader.Read ();
301                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
302                                         if (reader.IsTextNode ())
303                                                 category.Name += reader.Value;
304                                         else if (!TryParseElement (reader, category, Version)) {
305                                                 if (PreserveElementExtensions)
306                                                         // FIXME: what should be used for maxExtenswionSize
307                                                         LoadElementExtensions (reader, category, int.MaxValue);
308                                                 else
309                                                         reader.Skip ();
310                                         }
311                                         reader.Read ();
312                                 }
313                         }
314                         reader.Read (); // </category> or <category ... />
315                 }
316
317                 // SyndicationLink.CreateMediaEnclosureLink() is almost
318                 // useless here since it cannot handle extension attributes
319                 // in straightforward way (it I use it, I have to iterate
320                 // attributes twice just to read extensions).
321                 void ReadEnclosure (XmlReader reader, SyndicationLink link)
322                 {
323                         link.RelationshipType = "enclosure";
324
325                         if (PreserveAttributeExtensions && reader.MoveToFirstAttribute ()) {
326                                 do {
327                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
328                                                 continue;
329                                         if (reader.NamespaceURI == String.Empty) {
330                                                 switch (reader.LocalName) {
331                                                 case "url":
332                                                         link.Uri = CreateUri (reader.Value);
333                                                         continue;
334                                                 case "type":
335                                                         link.MediaType = reader.Value;
336                                                         continue;
337                                                 case "length":
338                                                         link.Length = XmlConvert.ToInt64 (reader.Value);
339                                                         continue;
340                                                 }
341                                         }
342                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, link, Version))
343                                                 link.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
344                                 } while (reader.MoveToNextAttribute ());
345                                 reader.MoveToElement ();
346                         }
347
348                         // Actually .NET fails to read extension here.
349                         if (!reader.IsEmptyElement) {
350                                 reader.Read ();
351                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
352                                         if (!TryParseElement (reader, link, Version)) {
353                                                 if (PreserveElementExtensions)
354                                                         // FIXME: what should be used for maxExtenswionSize
355                                                         LoadElementExtensions (reader, link, int.MaxValue);
356                                                 else
357                                                         reader.Skip ();
358                                         }
359                                 }
360                         }
361                         reader.Read (); // </enclosure> or <enclosure ... />
362                 }
363
364                 void ReadLink (XmlReader reader, SyndicationLink link)
365                 {
366                         if (PreserveAttributeExtensions && reader.MoveToFirstAttribute ()) {
367                                 do {
368                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
369                                                 continue;
370                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, link, Version))
371                                                 link.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
372                                 } while (reader.MoveToNextAttribute ());
373                                 reader.MoveToElement ();
374                         }
375
376                         if (!reader.IsEmptyElement) {
377                                 string url = null;
378                                 reader.Read ();
379                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
380                                         if (reader.IsTextNode ())
381                                                 url += reader.Value;
382                                         else if (!TryParseElement (reader, link, Version)) {
383                                                 if (PreserveElementExtensions)
384                                                         // FIXME: what should be used for maxExtenswionSize
385                                                         LoadElementExtensions (reader, link, int.MaxValue);
386                                                 else
387                                                         reader.Skip ();
388                                         }
389                                         reader.Read ();
390                                 }
391                                 link.Uri = CreateUri (url);
392                         }
393                         reader.Read (); // </link> or <link ... />
394                 }
395
396                 void ReadPerson (XmlReader reader, SyndicationPerson person)
397                 {
398                         if (PreserveAttributeExtensions && reader.MoveToFirstAttribute ()) {
399                                 do {
400                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
401                                                 continue;
402                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, person, Version))
403                                                 person.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
404                                 } while (reader.MoveToNextAttribute ());
405                                 reader.MoveToElement ();
406                         }
407
408                         if (!reader.IsEmptyElement) {
409                                 reader.Read ();
410                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
411                                         if (reader.IsTextNode ())
412                                                 person.Email += reader.Value;
413                                         else if (!TryParseElement (reader, person, Version)) {
414                                                 if (PreserveElementExtensions)
415                                                         // FIXME: what should be used for maxExtenswionSize
416                                                         LoadElementExtensions (reader, person, int.MaxValue);
417                                                 else
418                                                         reader.Skip ();
419                                         }
420                                         reader.Read ();
421                                 }
422                         }
423                         reader.Read (); // end element or empty element
424                 }
425
426                 // copied from Atom10ItemFormatter
427                 void ReadPersonAtom10 (XmlReader reader, SyndicationPerson person)
428                 {
429                         if (reader.MoveToFirstAttribute ()) {
430                                 do {
431                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
432                                                 continue;
433                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, person, Version) && PreserveAttributeExtensions)
434                                                 person.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
435                                 } while (reader.MoveToNextAttribute ());
436                                 reader.MoveToElement ();
437                         }
438
439                         if (!reader.IsEmptyElement) {
440                                 reader.Read ();
441                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
442                                         if (reader.NodeType == XmlNodeType.Element && reader.NamespaceURI == AtomNamespace) {
443                                                 switch (reader.LocalName) {
444                                                 case "name":
445                                                         person.Name = reader.ReadElementContentAsString ();
446                                                         continue;
447                                                 case "uri":
448                                                         person.Uri = reader.ReadElementContentAsString ();
449                                                         continue;
450                                                 case "email":
451                                                         person.Email = reader.ReadElementContentAsString ();
452                                                         continue;
453                                                 }
454                                         }
455                                         if (!TryParseElement (reader, person, Version)) {
456                                                 if (PreserveElementExtensions)
457                                                         // FIXME: what should be used for maxExtenswionSize
458                                                         LoadElementExtensions (reader, person, int.MaxValue);
459                                                 else
460                                                         reader.Skip ();
461                                         }
462                                 }
463                         }
464                         reader.Read (); // end element or empty element
465                 }
466
467                 void ReadSourceFeed (XmlReader reader, SyndicationFeed feed)
468                 {
469                         if (reader.MoveToFirstAttribute ()) {
470                                 do {
471                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
472                                                 continue;
473                                         if (reader.NamespaceURI == String.Empty) {
474                                                 switch (reader.LocalName) {
475                                                 case "url":
476                                                         feed.Links.Add (new SyndicationLink (CreateUri (reader.Value)));
477                                                         continue;
478                                                 }
479                                         }
480                                 } while (reader.MoveToNextAttribute ());
481                                 reader.MoveToElement ();
482                         }
483
484                         if (!reader.IsEmptyElement) {
485                                 reader.Read ();
486                                 string title = null;
487                                 while (reader.NodeType != XmlNodeType.EndElement) {
488                                         if (reader.IsTextNode ())
489                                                 title += reader.Value;
490                                         reader.Skip ();
491                                         reader.MoveToContent ();
492                                 }
493                                 feed.Title = new TextSyndicationContent (title);
494                         }
495                         reader.Read (); // </source> or <source ... />
496                 }
497
498                 Uri CreateUri (string uri)
499                 {
500                         return new Uri (uri, UriKind.RelativeOrAbsolute);
501                 }
502
503                 // write
504
505                 void WriteXml (XmlWriter writer, bool writeRoot)
506                 {
507                         if (writer == null)
508                                 throw new ArgumentNullException ("writer");
509                         if (Item == null)
510                                 throw new InvalidOperationException ("Syndication item must be set before writing");
511
512                         if (writeRoot)
513                                 writer.WriteStartElement ("item");
514
515                         if (Item.BaseUri != null)
516                                 writer.WriteAttributeString ("xml:base", Item.BaseUri.ToString ());
517
518                         WriteAttributeExtensions (writer, Item, Version);
519
520                         if (Item.Id != null) {
521                                 writer.WriteStartElement ("guid");
522                                 writer.WriteAttributeString ("isPermaLink", "false");
523                                 writer.WriteString (Item.Id);
524                                 writer.WriteEndElement ();
525                         }
526
527                         if (Item.Title != null) {
528                                 writer.WriteStartElement ("title");
529                                 writer.WriteString (Item.Title.Text);
530                                 writer.WriteEndElement ();
531                         }
532
533                         foreach (SyndicationPerson author in Item.Authors)
534                                 if (author != null) {
535                                         writer.WriteStartElement ("author");
536                                         WriteAttributeExtensions (writer, author, Version);
537                                         writer.WriteString (author.Email);
538                                         WriteElementExtensions (writer, author, Version);
539                                         writer.WriteEndElement ();
540                                 }
541                         foreach (SyndicationCategory category in Item.Categories)
542                                 if (category != null) {
543                                         writer.WriteStartElement ("category");
544                                         if (category.Scheme != null)
545                                                 writer.WriteAttributeString ("domain", category.Scheme);
546                                         WriteAttributeExtensions (writer, category, Version);
547                                         writer.WriteString (category.Name);
548                                         WriteElementExtensions (writer, category, Version);
549                                         writer.WriteEndElement ();
550                                 }
551
552                         if (Item.Content != null) {
553                                 Item.Content.WriteTo (writer, "description", String.Empty);
554                         } else if (Item.Summary != null)
555                                 Item.Summary.WriteTo (writer, "description", String.Empty);
556                         else if (Item.Title == null) { // according to the RSS 2.0 spec, either of title or description must exist.
557                                 writer.WriteStartElement ("description");
558                                 writer.WriteEndElement ();
559                         }
560
561                         foreach (SyndicationLink link in Item.Links)
562                                 switch (link.RelationshipType) {
563                                 case "enclosure":
564                                         writer.WriteStartElement ("enclosure");
565                                         if (link.Uri != null)
566                                                 writer.WriteAttributeString ("uri", link.Uri.ToString ());
567                                         if (link.Length != 0)
568                                                 writer.WriteAttributeString ("length", XmlConvert.ToString (link.Length));
569                                         if (link.MediaType != null)
570                                                 writer.WriteAttributeString ("type", link.MediaType);
571                                         WriteAttributeExtensions (writer, link, Version);
572                                         WriteElementExtensions (writer, link, Version);
573                                         writer.WriteEndElement ();
574                                         break;
575                                 default:
576                                         writer.WriteStartElement ("link");
577                                         WriteAttributeExtensions (writer, link, Version);
578                                         writer.WriteString (link.Uri != null ? link.Uri.ToString () : String.Empty);
579                                         WriteElementExtensions (writer, link, Version);
580                                         writer.WriteEndElement ();
581                                         break;
582                                 }
583
584                         if (Item.SourceFeed != null) {
585                                 writer.WriteStartElement ("source");
586                                 if (Item.SourceFeed.Links.Count > 0) {
587                                         Uri u = Item.SourceFeed.Links [0].Uri;
588                                         writer.WriteAttributeString ("url", u != null ? u.ToString () : String.Empty);
589                                 }
590                                 writer.WriteString (Item.SourceFeed.Title != null ? Item.SourceFeed.Title.Text : String.Empty);
591                                 writer.WriteEndElement ();
592                         }
593
594                         if (!Item.PublishDate.Equals (default (DateTimeOffset))) {
595                                 writer.WriteStartElement ("pubDate");
596                                 writer.WriteString (ToRFC822DateString (Item.PublishDate));
597                                 writer.WriteEndElement ();
598                         }
599
600                         if (SerializeExtensionsAsAtom) {
601                                 foreach (SyndicationPerson contributor in Item.Contributors) {
602                                         if (contributor != null) {
603                                                 writer.WriteStartElement ("contributor", AtomNamespace);
604                                                 WriteAttributeExtensions (writer, contributor, Version);
605                                                 writer.WriteElementString ("name", AtomNamespace, contributor.Name);
606                                                 writer.WriteElementString ("uri", AtomNamespace, contributor.Uri);
607                                                 writer.WriteElementString ("email", AtomNamespace, contributor.Email);
608                                                 WriteElementExtensions (writer, contributor, Version);
609                                                 writer.WriteEndElement ();
610                                         }
611                                 }
612
613                                 if (!Item.LastUpdatedTime.Equals (default (DateTimeOffset))) {
614                                         writer.WriteStartElement ("updated", AtomNamespace);
615                                         // FIXME: how to handle offset part?
616                                         writer.WriteString (XmlConvert.ToString (Item.LastUpdatedTime.DateTime, XmlDateTimeSerializationMode.Local));
617                                         writer.WriteEndElement ();
618                                 }
619
620                                 if (Item.Copyright != null)
621                                         Item.Copyright.WriteTo (writer, "rights", AtomNamespace);
622 #if false
623                                 if (Item.Content != null)
624                                         Item.Content.WriteTo (writer, "content", AtomNamespace);
625 #endif
626                         }
627
628                         WriteElementExtensions (writer, Item, Version);
629
630                         if (writeRoot)
631                                 writer.WriteEndElement ();
632                 }
633
634                 // FIXME: DateTimeOffset.ToString() needs another overload.
635                 // When it is implemented, just remove ".DateTime" parts below.
636                 string ToRFC822DateString (DateTimeOffset date)
637                 {
638                         switch (date.DateTime.Kind) {
639                         case DateTimeKind.Utc:
640                                 return date.DateTime.ToString ("ddd, dd MMM yyyy HH:mm:ss 'Z'", DateTimeFormatInfo.InvariantInfo);
641                         case DateTimeKind.Local:
642                                 StringBuilder sb = new StringBuilder (date.DateTime.ToString ("ddd, dd MMM yyyy HH:mm:ss zzz", DateTimeFormatInfo.InvariantInfo));
643                                 sb.Remove (sb.Length - 3, 1);
644                                 return sb.ToString (); // remove ':' from +hh:mm
645                         default:
646                                 return date.DateTime.ToString ("ddd, dd MMM yyyy HH:mm:ss", DateTimeFormatInfo.InvariantInfo);
647                         }
648                 }
649
650                 string [] rfc822formats = new string [] {
651                         "ddd, dd MMM yyyy HH:mm:ss 'Z'",
652                         "ddd, dd MMM yyyy HH:mm:ss zzz",
653                         "ddd, dd MMM yyyy HH:mm:ss"};
654
655                 // FIXME: DateTimeOffset is still incomplete. When it is done,
656                 // simplify the code.
657                 DateTimeOffset FromRFC822DateString (string s)
658                 {
659                         return XmlConvert.ToDateTimeOffset (s, rfc822formats);
660                 }
661         }
662 }