2 // Atom10FeedFormatter.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
29 // WARNING: this class is not for ensuring valid ATOM 1.0 document output
30 // (as well as Atom10ItemFormatter).
33 using System.Collections.Generic;
34 using System.Collections.ObjectModel;
35 using System.Globalization;
37 using System.Runtime.Serialization;
40 using System.Xml.Schema;
41 using System.Xml.Serialization;
43 namespace System.ServiceModel.Syndication
45 [XmlRoot ("feed", Namespace = "http://www.w3.org/2005/Atom")]
46 public class Atom10FeedFormatter : SyndicationFeedFormatter, IXmlSerializable
48 const string AtomNamespace ="http://www.w3.org/2005/Atom";
50 bool preserve_att_ext = true, preserve_elem_ext = true;
53 public Atom10FeedFormatter ()
57 public Atom10FeedFormatter (SyndicationFeed feedToWrite)
62 public Atom10FeedFormatter (Type feedTypeToCreate)
64 if (feedTypeToCreate == null)
65 throw new ArgumentNullException ("feedTypeToCreate");
66 feed_type = feedTypeToCreate;
69 protected Type FeedType {
70 get { return feed_type; }
73 public bool PreserveAttributeExtensions {
74 get { return preserve_att_ext; }
75 set { preserve_att_ext = value; }
78 public bool PreserveElementExtensions {
79 get { return preserve_elem_ext; }
80 set { preserve_elem_ext = value; }
83 public override string Version {
84 get { return "Atom10"; }
87 protected override SyndicationFeed CreateFeedInstance ()
89 return new SyndicationFeed ();
92 public override bool CanRead (XmlReader reader)
95 throw new ArgumentNullException ("reader");
96 reader.MoveToContent ();
97 return reader.IsStartElement ("feed", AtomNamespace);
100 public override void ReadFrom (XmlReader reader)
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);
107 protected virtual SyndicationItem ReadItem (XmlReader reader, SyndicationFeed feed)
109 Atom10ItemFormatter formatter = new Atom10ItemFormatter ();
110 formatter.ReadFrom (reader);
111 return formatter.Item;
114 protected virtual IEnumerable<SyndicationItem> ReadItems (XmlReader reader, SyndicationFeed feed, out bool areAllItemsRead)
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);
124 [MonoTODO ("Find out how feedBaseUri is used")]
125 protected virtual void WriteItem (XmlWriter writer, SyndicationItem item, Uri feedBaseUri)
127 item.SaveAsAtom10 (writer);
130 protected virtual void WriteItems (XmlWriter writer, IEnumerable<SyndicationItem> items, Uri feedBaseUri)
133 throw new ArgumentNullException ("items");
134 foreach (SyndicationItem item in items)
135 WriteItem (writer, item, feedBaseUri);
138 public override void WriteTo (XmlWriter writer)
140 WriteXml (writer, true);
143 void IXmlSerializable.ReadXml (XmlReader reader)
145 ReadXml (reader, false);
148 void IXmlSerializable.WriteXml (XmlWriter writer)
150 WriteXml (writer, false);
153 XmlSchema IXmlSerializable.GetSchema ()
160 void ReadXml (XmlReader reader, bool fromSerializable)
163 throw new ArgumentNullException ("reader");
164 SetFeed (CreateFeedInstance ());
166 reader.MoveToContent ();
168 if (reader.MoveToFirstAttribute ()) {
170 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
172 if (reader.LocalName == "lang" && reader.NamespaceURI == "http://www.w3.org/XML/1998/namespace") {
173 Feed.Language = reader.Value;
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 ());
181 reader.ReadStartElement ();
183 Collection<SyndicationItem> items = null;
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) {
191 SyndicationPerson p = Feed.CreatePerson ();
192 ReadPerson (reader, p);
193 Feed.Authors.Add (p);
196 SyndicationCategory c = Feed.CreateCategory ();
197 ReadCategory (reader, c);
198 Feed.Categories.Add (c);
201 p = Feed.CreatePerson ();
202 ReadPerson (reader, p);
203 Feed.Contributors.Add (p);
206 Feed.Generator = reader.ReadElementContentAsString ();
208 // "icon" is an extension
210 Feed.Generator = reader.ReadElementContentAsString ();
213 SyndicationLink l = Feed.CreateLink ();
214 ReadLink (reader, l);
218 Feed.ImageUrl = CreateUri (reader.ReadElementContentAsString ());
221 Feed.Copyright = ReadTextSyndicationContent (reader);
224 Feed.Description = ReadTextSyndicationContent (reader);
227 Feed.Title = ReadTextSyndicationContent (reader);
230 // FIXME: somehow DateTimeOffset causes the runtime crash.
231 reader.ReadElementContentAsString ();
232 // Feed.LastUpdatedTime = XmlConvert.ToDateTimeOffset (reader.ReadElementContentAsString ());
236 items = new Collection<SyndicationItem> ();
239 items.Add (ReadItem (reader, Feed));
242 if (!TryParseElement (reader, Feed, Version)) {
243 if (PreserveElementExtensions)
244 // FIXME: what to specify for maxExtensionSize
245 LoadElementExtensions (reader, Feed, int.MaxValue);
251 reader.ReadEndElement ();
254 internal static TextSyndicationContent ReadTextSyndicationContent (XmlReader reader)
256 TextSyndicationContentKind kind = TextSyndicationContentKind.Plaintext;
257 switch (reader.GetAttribute ("type")) {
259 kind = TextSyndicationContentKind.Html;
262 kind = TextSyndicationContentKind.XHtml;
265 string text = reader.ReadElementContentAsString ();
266 TextSyndicationContent t = new TextSyndicationContent (text, kind);
270 internal void ReadCategory (XmlReader reader, SyndicationCategory category)
272 if (reader.MoveToFirstAttribute ()) {
274 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
276 if (reader.NamespaceURI == String.Empty) {
277 switch (reader.LocalName) {
279 category.Name = reader.Value;
282 category.Scheme = reader.Value;
285 category.Label = reader.Value;
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 ();
295 if (!reader.IsEmptyElement) {
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);
307 reader.Read (); // </category> or <category ... />
310 void ReadLink (XmlReader reader, SyndicationLink link)
312 if (reader.MoveToFirstAttribute ()) {
314 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
316 if (reader.NamespaceURI == String.Empty) {
317 switch (reader.LocalName) {
319 link.Uri = CreateUri (reader.Value);
322 link.RelationshipType = reader.Value;
325 link.MediaType = reader.Value;
328 link.Length = XmlConvert.ToInt64 (reader.Value);
331 link.Title = reader.Value;
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 ();
341 if (!reader.IsEmptyElement) {
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);
353 reader.Read (); // </link> or <link ... />
356 void ReadPerson (XmlReader reader, SyndicationPerson person)
358 if (reader.MoveToFirstAttribute ()) {
360 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
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 ();
368 if (!reader.IsEmptyElement) {
370 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
371 if (reader.NodeType == XmlNodeType.Element && reader.NamespaceURI == AtomNamespace) {
372 switch (reader.LocalName) {
374 person.Name = reader.ReadElementContentAsString ();
377 person.Uri = reader.ReadElementContentAsString ();
380 person.Email = reader.ReadElementContentAsString ();
384 if (!TryParseElement (reader, person, Version)) {
385 if (PreserveElementExtensions)
386 // FIXME: what should be used for maxExtenswionSize
387 LoadElementExtensions (reader, person, int.MaxValue);
393 reader.Read (); // end element or empty element
396 Uri CreateUri (string uri)
398 return new Uri (uri, UriKind.RelativeOrAbsolute);
403 void WriteXml (XmlWriter writer, bool writeRoot)
406 throw new ArgumentNullException ("writer");
408 throw new InvalidOperationException ("Syndication item must be set before writing");
411 writer.WriteStartElement ("feed", AtomNamespace);
413 if (Feed.BaseUri != null)
414 writer.WriteAttributeString ("xml", "base", null, Feed.BaseUri.ToString ());
416 if (Feed.Language != null)
417 writer.WriteAttributeString ("xml", "lang", null, Feed.Language);
419 // atom:feed elements MUST contain exactly one atom:title element.
420 (Feed.Title ?? new TextSyndicationContent (String.Empty)).WriteTo (writer, "title", AtomNamespace);
422 // atom:feed elements MUST contain exactly one atom:id element.
423 writer.WriteElementString ("id", AtomNamespace, Feed.Id ?? new UniqueId ().ToString ());
425 if (Feed.Copyright != null)
426 Feed.Copyright.WriteTo (writer, "rights", AtomNamespace);
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 ();
434 foreach (SyndicationCategory category in Feed.Categories)
435 if (category != null)
436 WriteCategory (category, writer);
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 ();
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 ();
461 foreach (SyndicationLink link in Feed.Links)
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 ();
478 if (Feed.Description != null)
479 Feed.Description.WriteTo (writer, "subtitle", AtomNamespace);
481 if (Feed.ImageUrl != null)
482 writer.WriteElementString ("logo", AtomNamespace, Feed.ImageUrl.ToString ());
484 if (Feed.Generator != null)
485 writer.WriteElementString ("generator", AtomNamespace, Feed.Generator);
487 WriteItems (writer, Feed.Items, Feed.BaseUri);
489 WriteElementExtensions (writer, Feed, Version);
491 writer.WriteEndElement ();
494 internal void WriteCategory (SyndicationCategory category, XmlWriter writer)
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 ();