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