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