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 
Atom10FeedFormatter()53 		public Atom10FeedFormatter ()
54 		{
55 		}
56 
Atom10FeedFormatter(SyndicationFeed feedToWrite)57 		public Atom10FeedFormatter (SyndicationFeed feedToWrite)
58 			: base (feedToWrite)
59 		{
60 		}
61 
Atom10FeedFormatter(Type feedTypeToCreate)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 
CreateFeedInstance()87 		protected override SyndicationFeed CreateFeedInstance ()
88 		{
89 			return new SyndicationFeed ();
90 		}
91 
CanRead(XmlReader reader)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 
ReadFrom(XmlReader reader)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 
ReadItem(XmlReader reader, SyndicationFeed feed)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 
ReadItems(XmlReader reader, SyndicationFeed feed, out bool areAllItemsRead)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")]
WriteItem(XmlWriter writer, SyndicationItem item, Uri feedBaseUri)125 		protected virtual void WriteItem (XmlWriter writer, SyndicationItem item, Uri feedBaseUri)
126 		{
127 			item.SaveAsAtom10 (writer);
128 		}
129 
WriteItems(XmlWriter writer, IEnumerable<SyndicationItem> items, Uri feedBaseUri)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 
WriteTo(XmlWriter writer)138 		public override void WriteTo (XmlWriter writer)
139 		{
140 			WriteXml (writer, true);
141 		}
142 
IXmlSerializable.ReadXml(XmlReader reader)143 		void IXmlSerializable.ReadXml (XmlReader reader)
144 		{
145 			ReadXml (reader, false);
146 		}
147 
IXmlSerializable.WriteXml(XmlWriter writer)148 		void IXmlSerializable.WriteXml (XmlWriter writer)
149 		{
150 			WriteXml (writer, false);
151 		}
152 
IXmlSerializable.GetSchema()153 		XmlSchema IXmlSerializable.GetSchema ()
154 		{
155 			return null;
156 		}
157 
158 		// read
159 
ReadXml(XmlReader reader, bool fromSerializable)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 
ReadTextSyndicationContent(XmlReader reader)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 
ReadCategory(XmlReader reader, SyndicationCategory category)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 
ReadLink(XmlReader reader, SyndicationLink link)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 
ReadPerson(XmlReader reader, SyndicationPerson person)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 
CreateUri(string uri)396 		Uri CreateUri (string uri)
397 		{
398 			return new Uri (uri, UriKind.RelativeOrAbsolute);
399 		}
400 
401 		// write
402 
WriteXml(XmlWriter writer, bool writeRoot)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 
WriteCategory(SyndicationCategory category, XmlWriter writer)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 }
509