Merge pull request #1573 from akoeplinger/msbuild-import-empty
[mono.git] / mcs / class / System.ServiceModel.Web / System.ServiceModel.Syndication / Atom10FeedFormatter.cs
1 //
2 // Atom10FeedFormatter.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
29 // WARNING: this class is not for ensuring valid ATOM 1.0 document output
30 // (as well as Atom10ItemFormatter).
31
32 using System;
33 using System.Collections.Generic;
34 using System.Collections.ObjectModel;
35 using System.Globalization;
36 using System.IO;
37 using System.Runtime.Serialization;
38 using System.Text;
39 using System.Xml;
40 using System.Xml.Schema;
41 using System.Xml.Serialization;
42
43 namespace System.ServiceModel.Syndication
44 {
45         [XmlRoot ("feed", Namespace = "http://www.w3.org/2005/Atom")]
46         public class Atom10FeedFormatter : SyndicationFeedFormatter, IXmlSerializable
47         {
48                 const string AtomNamespace ="http://www.w3.org/2005/Atom";
49
50                 bool preserve_att_ext = true, preserve_elem_ext = true;
51                 Type feed_type;
52
53                 public Atom10FeedFormatter ()
54                 {
55                 }
56
57                 public Atom10FeedFormatter (SyndicationFeed feedToWrite)
58                         : base (feedToWrite)
59                 {
60                 }
61
62                 public Atom10FeedFormatter (Type feedTypeToCreate)
63                 {
64                         if (feedTypeToCreate == null)
65                                 throw new ArgumentNullException ("feedTypeToCreate");
66                         feed_type = feedTypeToCreate;
67                 }
68
69                 protected Type FeedType {
70                         get { return feed_type; }
71                 }
72
73                 public bool PreserveAttributeExtensions {
74                         get { return preserve_att_ext; }
75                         set { preserve_att_ext = value; }
76                 }
77
78                 public bool PreserveElementExtensions {
79                         get { return preserve_elem_ext; }
80                         set { preserve_elem_ext = value; }
81                 }
82
83                 public override string Version {
84                         get { return "Atom10"; }
85                 }
86
87                 protected override SyndicationFeed CreateFeedInstance ()
88                 {
89                         return new SyndicationFeed ();
90                 }
91
92                 public override bool CanRead (XmlReader reader)
93                 {
94                         if (reader == null)
95                                 throw new ArgumentNullException ("reader");
96                         reader.MoveToContent ();
97                         return reader.IsStartElement ("feed", AtomNamespace);
98                 }
99
100                 public override void ReadFrom (XmlReader reader)
101                 {
102                         if (!CanRead (reader))
103                                 throw new XmlException (String.Format ("Element '{0}' in namespace '{1}' is not accepted by this syndication formatter", reader.LocalName, reader.NamespaceURI));
104                         ReadXml (reader, true);
105                 }
106
107                 protected virtual SyndicationItem ReadItem (XmlReader reader, SyndicationFeed feed)
108                 {
109                         Atom10ItemFormatter formatter = new Atom10ItemFormatter ();
110                         formatter.ReadFrom (reader);
111                         return formatter.Item;
112                 }
113
114                 protected virtual IEnumerable<SyndicationItem> ReadItems (XmlReader reader, SyndicationFeed feed, out bool areAllItemsRead)
115                 {
116                         Collection<SyndicationItem> c = new Collection<SyndicationItem> ();
117                         for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ())
118                                 if (reader.LocalName == "entry" && reader.NamespaceURI == AtomNamespace)
119                                         c.Add (ReadItem (reader, feed));
120                         areAllItemsRead = (reader.NodeType == XmlNodeType.EndElement);
121                         return c;
122                 }
123
124                 [MonoTODO ("Find out how feedBaseUri is used")]
125                 protected virtual void WriteItem (XmlWriter writer, SyndicationItem item, Uri feedBaseUri)
126                 {
127                         item.SaveAsAtom10 (writer);
128                 }
129
130                 protected virtual void WriteItems (XmlWriter writer, IEnumerable<SyndicationItem> items, Uri feedBaseUri)
131                 {
132                         if (items == null)
133                                 throw new ArgumentNullException ("items");
134                         foreach (SyndicationItem item in items)
135                                 WriteItem (writer, item, feedBaseUri);
136                 }
137
138                 public override void WriteTo (XmlWriter writer)
139                 {
140                         WriteXml (writer, true);
141                 }
142
143                 void IXmlSerializable.ReadXml (XmlReader reader)
144                 {
145                         ReadXml (reader, false);
146                 }
147
148                 void IXmlSerializable.WriteXml (XmlWriter writer)
149                 {
150                         WriteXml (writer, false);
151                 }
152
153                 XmlSchema IXmlSerializable.GetSchema ()
154                 {
155                         return null;
156                 }
157
158                 // read
159
160                 void ReadXml (XmlReader reader, bool fromSerializable)
161                 {
162                         if (reader == null)
163                                 throw new ArgumentNullException ("reader");
164                         SetFeed (CreateFeedInstance ());
165
166                         reader.MoveToContent ();
167
168                         if (reader.MoveToFirstAttribute ()) {
169                                 do {
170                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
171                                                 continue;
172                                         if (reader.LocalName == "lang" && reader.NamespaceURI == "http://www.w3.org/XML/1998/namespace") {
173                                                 Feed.Language = reader.Value;
174                                                 continue;
175                                         }
176                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, Feed, Version) && PreserveAttributeExtensions)
177                                                 Feed.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
178                                 } while (reader.MoveToNextAttribute ());
179                         }
180
181                         reader.ReadStartElement ();
182
183                         Collection<SyndicationItem> items = null;
184
185                         for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
186                                 if (reader.NodeType != XmlNodeType.Element)
187                                         throw new XmlException ("Only element node is expected under 'feed' element");
188                                 if (reader.NamespaceURI == AtomNamespace)
189                                         switch (reader.LocalName) {
190                                         case "author":
191                                                 SyndicationPerson p = Feed.CreatePerson ();
192                                                 ReadPerson (reader, p);
193                                                 Feed.Authors.Add (p);
194                                                 continue;
195                                         case "category":
196                                                 SyndicationCategory c = Feed.CreateCategory ();
197                                                 ReadCategory (reader, c);
198                                                 Feed.Categories.Add (c);
199                                                 continue;
200                                         case "contributor":
201                                                 p = Feed.CreatePerson ();
202                                                 ReadPerson (reader, p);
203                                                 Feed.Contributors.Add (p);
204                                                 continue;
205                                         case "generator":
206                                                 Feed.Generator = reader.ReadElementContentAsString ();
207                                                 continue;
208                                         // "icon" is an extension
209                                         case "id":
210                                                 Feed.Generator = reader.ReadElementContentAsString ();
211                                                 continue;
212                                         case "link":
213                                                 SyndicationLink l = Feed.CreateLink ();
214                                                 ReadLink (reader, l);
215                                                 Feed.Links.Add (l);
216                                                 continue;
217                                         case "logo":
218                                                 Feed.ImageUrl = CreateUri (reader.ReadElementContentAsString ());
219                                                 continue;
220                                         case "rights":
221                                                 Feed.Copyright = ReadTextSyndicationContent (reader);
222                                                 continue;
223                                         case "subtitle":
224                                                 Feed.Description = ReadTextSyndicationContent (reader);
225                                                 continue;
226                                         case "title":
227                                                 Feed.Title = ReadTextSyndicationContent (reader);
228                                                 continue;
229                                         case "updated":
230                                                 // FIXME: somehow DateTimeOffset causes the runtime crash.
231                                                 reader.ReadElementContentAsString ();
232                                                 // Feed.LastUpdatedTime = XmlConvert.ToDateTimeOffset (reader.ReadElementContentAsString ());
233                                                 continue;
234                                         case "entry":
235                                                 if (items == null) {
236                                                         items = new Collection<SyndicationItem> ();
237                                                         Feed.Items = items;
238                                                 }
239                                                 items.Add (ReadItem (reader, Feed));
240                                                 continue;
241                                         }
242                                 if (!TryParseElement (reader, Feed, Version)) {
243                                         if (PreserveElementExtensions)
244                                                 // FIXME: what to specify for maxExtensionSize
245                                                 LoadElementExtensions (reader, Feed, int.MaxValue);
246                                         else
247                                                 reader.Skip ();
248                                 }
249                         }
250
251                         reader.ReadEndElement ();
252                 }
253
254                 internal static TextSyndicationContent ReadTextSyndicationContent (XmlReader reader)
255                 {
256                         TextSyndicationContentKind kind = TextSyndicationContentKind.Plaintext;
257                         switch (reader.GetAttribute ("type")) {
258                         case "html":
259                                 kind = TextSyndicationContentKind.Html;
260                                 break;
261                         case "xhtml":
262                                 kind = TextSyndicationContentKind.XHtml;
263                                 break;
264                         }
265                         string text = reader.ReadElementContentAsString ();
266                         TextSyndicationContent t = new TextSyndicationContent (text, kind);
267                         return t;
268                 }
269
270                 internal void ReadCategory (XmlReader reader, SyndicationCategory category)
271                 {
272                         if (reader.MoveToFirstAttribute ()) {
273                                 do {
274                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
275                                                 continue;
276                                         if (reader.NamespaceURI == String.Empty) {
277                                                 switch (reader.LocalName) {
278                                                 case "term":
279                                                         category.Name = reader.Value;
280                                                         continue;
281                                                 case "scheme":
282                                                         category.Scheme = reader.Value;
283                                                         continue;
284                                                 case "label":
285                                                         category.Label = reader.Value;
286                                                         continue;
287                                                 }
288                                         }
289                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, category, Version) && PreserveAttributeExtensions)
290                                                 category.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
291                                 } while (reader.MoveToNextAttribute ());
292                                 reader.MoveToElement ();
293                         }
294
295                         if (!reader.IsEmptyElement) {
296                                 reader.Read ();
297                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
298                                         if (!TryParseElement (reader, category, Version)) {
299                                                 if (PreserveElementExtensions)
300                                                         // FIXME: what should be used for maxExtenswionSize
301                                                         LoadElementExtensions (reader, category, int.MaxValue);
302                                                 else
303                                                         reader.Skip ();
304                                         }
305                                 }
306                         }
307                         reader.Read (); // </category> or <category ... />
308                 }
309
310                 void ReadLink (XmlReader reader, SyndicationLink link)
311                 {
312                         if (reader.MoveToFirstAttribute ()) {
313                                 do {
314                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
315                                                 continue;
316                                         if (reader.NamespaceURI == String.Empty) {
317                                                 switch (reader.LocalName) {
318                                                 case "href":
319                                                         link.Uri = CreateUri (reader.Value);
320                                                         continue;
321                                                 case "rel":
322                                                         link.RelationshipType = reader.Value;
323                                                         continue;
324                                                 case "type":
325                                                         link.MediaType = reader.Value;
326                                                         continue;
327                                                 case "length":
328                                                         link.Length = XmlConvert.ToInt64 (reader.Value);
329                                                         continue;
330                                                 case "title":
331                                                         link.Title = reader.Value;
332                                                         continue;
333                                                 }
334                                         }
335                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, link, Version) && PreserveAttributeExtensions)
336                                                 link.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
337                                 } while (reader.MoveToNextAttribute ());
338                                 reader.MoveToElement ();
339                         }
340
341                         if (!reader.IsEmptyElement) {
342                                 reader.Read ();
343                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
344                                         if (!TryParseElement (reader, link, Version)) {
345                                                 if (PreserveElementExtensions)
346                                                         // FIXME: what should be used for maxExtenswionSize
347                                                         LoadElementExtensions (reader, link, int.MaxValue);
348                                                 else
349                                                         reader.Skip ();
350                                         }
351                                 }
352                         }
353                         reader.Read (); // </link> or <link ... />
354                 }
355
356                 void ReadPerson (XmlReader reader, SyndicationPerson person)
357                 {
358                         if (reader.MoveToFirstAttribute ()) {
359                                 do {
360                                         if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
361                                                 continue;
362                                         if (!TryParseAttribute (reader.LocalName, reader.NamespaceURI, reader.Value, person, Version) && PreserveAttributeExtensions)
363                                                 person.AttributeExtensions.Add (new XmlQualifiedName (reader.LocalName, reader.NamespaceURI), reader.Value);
364                                 } while (reader.MoveToNextAttribute ());
365                                 reader.MoveToElement ();
366                         }
367
368                         if (!reader.IsEmptyElement) {
369                                 reader.Read ();
370                                 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
371                                         if (reader.NodeType == XmlNodeType.Element && reader.NamespaceURI == AtomNamespace) {
372                                                 switch (reader.LocalName) {
373                                                 case "name":
374                                                         person.Name = reader.ReadElementContentAsString ();
375                                                         continue;
376                                                 case "uri":
377                                                         person.Uri = reader.ReadElementContentAsString ();
378                                                         continue;
379                                                 case "email":
380                                                         person.Email = reader.ReadElementContentAsString ();
381                                                         continue;
382                                                 }
383                                         }
384                                         if (!TryParseElement (reader, person, Version)) {
385                                                 if (PreserveElementExtensions)
386                                                         // FIXME: what should be used for maxExtenswionSize
387                                                         LoadElementExtensions (reader, person, int.MaxValue);
388                                                 else
389                                                         reader.Skip ();
390                                         }
391                                 }
392                         }
393                         reader.Read (); // end element or empty element
394                 }
395
396                 Uri CreateUri (string uri)
397                 {
398                         return new Uri (uri, UriKind.RelativeOrAbsolute);
399                 }
400
401                 // write
402
403                 void WriteXml (XmlWriter writer, bool writeRoot)
404                 {
405                         if (writer == null)
406                                 throw new ArgumentNullException ("writer");
407                         if (Feed == null)
408                                 throw new InvalidOperationException ("Syndication item must be set before writing");
409
410                         if (writeRoot)
411                                 writer.WriteStartElement ("feed", AtomNamespace);
412
413                         if (Feed.BaseUri != null)
414                                 writer.WriteAttributeString ("xml", "base", null, Feed.BaseUri.ToString ());
415
416                         if (Feed.Language != null)
417                                 writer.WriteAttributeString ("xml", "lang", null, Feed.Language);
418
419                         // atom:feed elements MUST contain exactly one atom:title element.
420                         (Feed.Title ?? new TextSyndicationContent (String.Empty)).WriteTo (writer, "title", AtomNamespace);
421
422                         // atom:feed elements MUST contain exactly one atom:id element.
423                         writer.WriteElementString ("id", AtomNamespace, Feed.Id ?? new UniqueId ().ToString ());
424
425                         if (Feed.Copyright != null)
426                                 Feed.Copyright.WriteTo (writer, "rights", AtomNamespace);
427
428                         // atom:feed elements MUST contain exactly one atom:updated element.
429                         writer.WriteStartElement ("updated", AtomNamespace);
430                         // FIXME: use DateTimeOffset itself once it is implemented.
431                         writer.WriteString (XmlConvert.ToString (Feed.LastUpdatedTime.UtcDateTime, XmlDateTimeSerializationMode.RoundtripKind));
432                         writer.WriteEndElement ();
433
434                         foreach (SyndicationCategory category in Feed.Categories)
435                                 if (category != null)
436                                         WriteCategory (category, writer);
437
438                         foreach (SyndicationPerson author in Feed.Authors)
439                                 if (author != null) {
440                                         writer.WriteStartElement ("author", AtomNamespace);
441                                         WriteAttributeExtensions (writer, author, Version);
442                                         writer.WriteElementString ("name", AtomNamespace, author.Name);
443                                         writer.WriteElementString ("uri", AtomNamespace, author.Uri);
444                                         writer.WriteElementString ("email", AtomNamespace, author.Email);
445                                         WriteElementExtensions (writer, author, Version);
446                                         writer.WriteEndElement ();
447                                 }
448
449                         foreach (SyndicationPerson contributor in Feed.Contributors) {
450                                 if (contributor != null) {
451                                         writer.WriteStartElement ("contributor", AtomNamespace);
452                                         WriteAttributeExtensions (writer, contributor, Version);
453                                         writer.WriteElementString ("name", AtomNamespace, contributor.Name);
454                                         writer.WriteElementString ("uri", AtomNamespace, contributor.Uri);
455                                         writer.WriteElementString ("email", AtomNamespace, contributor.Email);
456                                         WriteElementExtensions (writer, contributor, Version);
457                                         writer.WriteEndElement ();
458                                 }
459                         }
460
461                         foreach (SyndicationLink link in Feed.Links)
462                                 if (link != null) {
463                                         writer.WriteStartElement ("link");
464                                         if (link.RelationshipType != null)
465                                                 writer.WriteAttributeString ("rel", link.RelationshipType);
466                                         if (link.MediaType != null)
467                                                 writer.WriteAttributeString ("type", link.MediaType);
468                                         if (link.Title != null)
469                                                 writer.WriteAttributeString ("title", link.Title);
470                                         if (link.Length != 0)
471                                                 writer.WriteAttributeString ("length", link.Length.ToString (CultureInfo.InvariantCulture));
472                                         writer.WriteAttributeString ("href", link.Uri != null ? link.Uri.ToString () : String.Empty);
473                                         WriteAttributeExtensions (writer, link, Version);
474                                         WriteElementExtensions (writer, link, Version);
475                                         writer.WriteEndElement ();
476                                 }
477
478                         if (Feed.Description != null)
479                                 Feed.Description.WriteTo (writer, "subtitle", AtomNamespace);
480
481                         if (Feed.ImageUrl != null)
482                                 writer.WriteElementString ("logo", AtomNamespace, Feed.ImageUrl.ToString ());
483
484                         if (Feed.Generator != null)
485                                 writer.WriteElementString ("generator", AtomNamespace, Feed.Generator);
486
487                         WriteItems (writer, Feed.Items, Feed.BaseUri);
488
489                         WriteElementExtensions (writer, Feed, Version);
490                         if (writeRoot)
491                                 writer.WriteEndElement ();
492                 }
493
494                 internal void WriteCategory (SyndicationCategory category, XmlWriter writer)
495                 {
496                         writer.WriteStartElement ("category", AtomNamespace);
497                         if (category.Name != null)
498                                 writer.WriteAttributeString ("term", category.Name);
499                         if (category.Label != null)
500                                 writer.WriteAttributeString ("label", category.Label);
501                         if (category.Scheme != null)
502                                 writer.WriteAttributeString ("scheme", category.Scheme);
503                         WriteAttributeExtensions (writer, category, Version);
504                         WriteElementExtensions (writer, category, Version);
505                         writer.WriteEndElement ();
506                 }
507         }
508 }