2009-08-02 Jb Evain <jbevain@novell.com>
[mono.git] / mcs / class / System.Xml.Linq / System.Xml.Linq / XElement.cs
1 //
2 // Authors:
3 //   Atsushi Enomoto
4 //
5 // Copyright 2007 Novell (http://www.novell.com)
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining
8 // a copy of this software and associated documentation files (the
9 // "Software"), to deal in the Software without restriction, including
10 // without limitation the rights to use, copy, modify, merge, publish,
11 // distribute, sublicense, and/or sell copies of the Software, and to
12 // permit persons to whom the Software is furnished to do so, subject to
13 // the following conditions:
14 // 
15 // The above copyright notice and this permission notice shall be
16 // included in all copies or substantial portions of the Software.
17 // 
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 //
26
27 using System;
28 using System.Collections;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.Text;
32 using System.Xml;
33 using System.Xml.Schema;
34 using System.Xml.Serialization;
35
36 namespace System.Xml.Linq
37 {
38         [XmlSchemaProvider (null, IsAny = true)]
39         public class XElement : XContainer, IXmlSerializable
40         {
41                 static IEnumerable <XElement> emptySequence =
42                         new List <XElement> ();
43
44                 public static IEnumerable <XElement> EmptySequence {
45                         get { return emptySequence; }
46                 }
47
48                 XName name;
49                 XAttribute attr_first, attr_last;
50                 bool explicit_is_empty = true;
51
52                 public XElement (XName name, object value)
53                 {
54                         this.name = name;
55                         Add (value);
56                 }
57
58                 public XElement (XElement source)
59                 {
60                         name = source.name;
61                         Add (source.Attributes ());
62                         Add (source.Nodes ());
63                 }
64
65                 public XElement (XName name)
66                 {
67                         this.name = name;
68                 }
69
70                 public XElement (XName name, params object [] contents)
71                 {
72                         this.name = name;
73                         Add (contents);
74                 }
75
76                 public XElement (XStreamingElement source)
77                 {
78                         this.name = source.Name;
79                         Add (source.Contents);
80                 }
81
82                 public static explicit operator bool (XElement element)
83                 {
84                         if (element == null)
85                                 throw new ArgumentNullException ("element");
86                         return XmlConvert.ToBoolean (element.Value);
87                 }
88
89                 public static explicit operator bool? (XElement element)
90                 {
91                         if (element == null)
92                                 return null;
93                         
94                         return element.Value == null ? (bool?) null : XmlConvert.ToBoolean (element.Value);
95                 }
96
97                 public static explicit operator DateTime (XElement element)
98                 {
99                         if (element == null)
100                                 throw new ArgumentNullException ("element");
101                         return XmlConvert.ToDateTime (element.Value, XmlDateTimeSerializationMode.RoundtripKind);
102                 }
103
104                 public static explicit operator DateTime? (XElement element)
105                 {
106                         if (element == null)
107                                 return null;
108                         
109                         return element.Value == null ? (DateTime?) null : XmlConvert.ToDateTime (element.Value, XmlDateTimeSerializationMode.RoundtripKind);
110                 }
111
112                 public static explicit operator decimal (XElement element)
113                 {
114                         if (element == null)
115                                 throw new ArgumentNullException ("element");
116                         return XmlConvert.ToDecimal (element.Value);
117                 }
118
119                 public static explicit operator decimal? (XElement element)
120                 {
121                         if (element == null)
122                                 return null;
123                         
124                         return element.Value == null ? (decimal?) null : XmlConvert.ToDecimal (element.Value);
125                 }
126
127                 public static explicit operator double (XElement element)
128                 {
129                         if (element == null)
130                                 throw new ArgumentNullException ("element");
131                         return XmlConvert.ToDouble (element.Value);
132                 }
133
134                 public static explicit operator double? (XElement element)
135                 {
136                         if (element == null)
137                                 return null;
138                         
139                         return element.Value == null ? (double?) null : XmlConvert.ToDouble (element.Value);
140                 }
141
142                 public static explicit operator float (XElement element)
143                 {
144                         if (element == null)
145                                 throw new ArgumentNullException ("element");
146                         return XmlConvert.ToSingle (element.Value);
147                 }
148
149                 public static explicit operator float? (XElement element)
150                 {
151                         if (element == null)
152                                 return null;
153                         
154                         return element.Value == null ? (float?) null : XmlConvert.ToSingle (element.Value);
155                 }
156
157                 public static explicit operator Guid (XElement element)
158                 {
159                         if (element == null)
160                                 throw new ArgumentNullException ("element");
161                         return XmlConvert.ToGuid (element.Value);
162                 }
163
164                 public static explicit operator Guid? (XElement element)
165                 {
166                         if (element == null)
167                                 return null;
168                         
169                         return element.Value == null ? (Guid?) null : XmlConvert.ToGuid (element.Value);
170                 }
171
172                 public static explicit operator int (XElement element)
173                 {
174                         if (element == null)
175                                 throw new ArgumentNullException ("element");
176                         return XmlConvert.ToInt32 (element.Value);
177                 }
178
179                 public static explicit operator int? (XElement element)
180                 {
181                         if (element == null)
182                                 return null;
183                         
184                         return element.Value == null ? (int?) null : XmlConvert.ToInt32 (element.Value);
185                 }
186
187                 public static explicit operator long (XElement element)
188                 {
189                         if (element == null)
190                                 throw new ArgumentNullException ("element");
191                         return XmlConvert.ToInt64 (element.Value);
192                 }
193
194                 public static explicit operator long? (XElement element)
195                 {
196                         if (element == null)
197                                 return null;
198                         
199                         return element.Value == null ? (long?) null : XmlConvert.ToInt64 (element.Value);
200                 }
201
202                 [CLSCompliant (false)]
203                 public static explicit operator uint (XElement element)
204                 {
205                         if (element == null)
206                                 throw new ArgumentNullException ("element");
207                         return XmlConvert.ToUInt32 (element.Value);
208                 }
209
210                 [CLSCompliant (false)]
211                 public static explicit operator uint? (XElement element)
212                 {
213                         if (element == null)
214                                 return null;
215                         
216                         return element.Value == null ? (uint?) null : XmlConvert.ToUInt32 (element.Value);
217                 }
218
219                 [CLSCompliant (false)]
220                 public static explicit operator ulong (XElement element)
221                 {
222                         if (element == null)
223                                 throw new ArgumentNullException ("element");
224                         return XmlConvert.ToUInt64 (element.Value);
225                 }
226
227                 [CLSCompliant (false)]
228                 public static explicit operator ulong? (XElement element)
229                 {
230                         if (element == null)
231                                 return null;
232                         
233                         return element.Value == null ? (ulong?) null : XmlConvert.ToUInt64 (element.Value);
234                 }
235
236                 public static explicit operator TimeSpan (XElement element)
237                 {
238                         if (element == null)
239                                 throw new ArgumentNullException ("element");
240                         return XmlConvert.ToTimeSpan (element.Value);
241                 }
242
243                 public static explicit operator TimeSpan? (XElement element)
244                 {
245                         if (element == null)
246                                 return null;
247                         
248                         return element.Value == null ? (TimeSpan?) null : XmlConvert.ToTimeSpan (element.Value);
249                 }
250
251                 public static explicit operator string (XElement element)
252                 {
253                         if (element == null)
254                                 return null;
255                         
256                         return element.Value;
257                 }
258
259                 public XAttribute FirstAttribute {
260                         get { return attr_first; }
261                         internal set { attr_first = value; }
262                 }
263
264                 public XAttribute LastAttribute {
265                         get { return attr_last; }
266                         internal set { attr_last = value; }
267                 }
268
269                 public bool HasAttributes {
270                         get { return attr_first != null; }
271                 }
272
273                 public bool HasElements {
274                         get {
275                                 foreach (object o in Nodes ())
276                                         if (o is XElement)
277                                                 return true;
278                                 return false;
279                         }
280                 }
281
282                 public bool IsEmpty {
283                         get { return !Nodes ().GetEnumerator ().MoveNext () && explicit_is_empty; }
284                         internal set { explicit_is_empty = value; }
285                 }
286
287                 public XName Name {
288                         get { return name; }
289                         set {
290                                 if (name == null)
291                                         throw new ArgumentNullException ("value");
292                                 name = value;
293                         }
294                 }
295
296                 public override XmlNodeType NodeType {
297                         get { return XmlNodeType.Element; }
298                 }
299
300                 public string Value {
301                         get {
302                                 StringBuilder sb = null;
303                                 foreach (XNode n in Nodes ()) {
304                                         if (sb == null)
305                                                 sb = new StringBuilder ();
306                                         if (n is XText)
307                                                 sb.Append (((XText) n).Value);
308                                         else if (n is XElement)
309                                                 sb.Append (((XElement) n).Value);
310                                 }
311                                 return sb == null ? String.Empty : sb.ToString ();
312                         }
313                         set {
314                                 RemoveNodes ();
315                                 Add (value);
316                         }
317                 }
318
319                 IEnumerable <XElement> GetAncestorList (XName name, bool getMeIn)
320                 {
321                         List <XElement> list = new List <XElement> ();
322                         if (getMeIn)
323                                 list.Add (this);
324                         for (XElement el = Parent as XElement; el != null; el = el.Parent as XElement)
325                                 if (name == null || el.Name == name)
326                                         list.Add (el);
327                         return list;
328                 }
329
330                 public XAttribute Attribute (XName name)
331                 {
332                         foreach (XAttribute a in Attributes ())
333                                 if (a.Name == name)
334                                         return a;
335                         return null;
336                 }
337
338                 public IEnumerable <XAttribute> Attributes ()
339                 {
340                         XAttribute next;
341                         for (XAttribute a = attr_first; a != null; a = next) {
342                                 next = a.NextAttribute;
343                                 yield return a;
344                         }
345                 }
346
347                 // huh?
348                 public IEnumerable <XAttribute> Attributes (XName name)
349                 {
350                         foreach (XAttribute a in Attributes ())
351                                 if (a.Name == name)
352                                         yield return a;
353                 }
354
355                 static void DefineDefaultSettings (XmlReaderSettings settings, LoadOptions options)
356                 {
357 #if NET_2_1 && !MONOTOUCH
358                         // 2.1 has a DtdProcessing property which defaults to DtdProcessing.Prohibit
359                         settings.DtdProcessing = DtdProcessing.Parse;
360 #else
361                         settings.ProhibitDtd = false;
362 #endif
363
364                         settings.IgnoreWhitespace = (options & LoadOptions.PreserveWhitespace) == 0;
365                 }
366
367                 static XmlReaderSettings CreateDefaultSettings (LoadOptions options)
368                 {
369                         var settings = new XmlReaderSettings ();
370                         DefineDefaultSettings (settings, options);
371                         return settings;
372                 }
373
374                 public static XElement Load (string uri)
375                 {
376                         return Load (uri, LoadOptions.None);
377                 }
378
379                 public static XElement Load (string uri, LoadOptions options)
380                 {
381                         XmlReaderSettings s = CreateDefaultSettings (options);
382
383                         using (XmlReader r = XmlReader.Create (uri, s)) {
384                                 return LoadCore (r, options);
385                         }
386                 }
387
388                 public static XElement Load (TextReader tr)
389                 {
390                         return Load (tr, LoadOptions.None);
391                 }
392
393                 public static XElement Load (TextReader tr, LoadOptions options)
394                 {
395                         XmlReaderSettings s = CreateDefaultSettings (options);
396
397                         using (XmlReader r = XmlReader.Create (tr, s)) {
398                                 return LoadCore (r, options);
399                         }
400                 }
401
402                 public static XElement Load (XmlReader reader)
403                 {
404                         return Load (reader, LoadOptions.None);
405                 }
406
407                 public static XElement Load (XmlReader reader, LoadOptions options)
408                 {
409                         XmlReaderSettings s = reader.Settings != null ? reader.Settings.Clone () : new XmlReaderSettings ();
410                         DefineDefaultSettings (s, options);
411
412                         using (XmlReader r = XmlReader.Create (reader, s)) {
413                                 return LoadCore (r, options);
414                         }
415                 }
416
417                 internal static XElement LoadCore (XmlReader r, LoadOptions options)
418                 {
419                         r.MoveToContent ();
420                         if (r.NodeType != XmlNodeType.Element)
421                                 throw new InvalidOperationException ("The XmlReader must be positioned at an element");
422                         XName name = XName.Get (r.LocalName, r.NamespaceURI);
423                         XElement e = new XElement (name);
424                         e.FillLineInfoAndBaseUri (r, options);
425
426                         if (r.MoveToFirstAttribute ()) {
427                                 do {
428                                         // not sure how current Orcas behavior makes sense here though ...
429                                         if (r.LocalName == "xmlns" && r.NamespaceURI == XNamespace.Xmlns.NamespaceName)
430                                                 e.SetAttributeValue (XNamespace.None.GetName ("xmlns"), r.Value);
431                                         else
432                                                 e.SetAttributeValue (XName.Get (r.LocalName, r.NamespaceURI), r.Value);
433                                         e.LastAttribute.FillLineInfoAndBaseUri (r, options);
434                                 } while (r.MoveToNextAttribute ());
435                                 r.MoveToElement ();
436                         }
437                         if (!r.IsEmptyElement) {
438                                 r.Read ();
439                                 e.ReadContentFrom (r, options);
440                                 r.ReadEndElement ();
441                                 e.explicit_is_empty = false;
442                         } else {
443                                 e.explicit_is_empty = true;
444                                 r.Read ();
445                         }
446                         return e;
447                 }
448
449                 public static XElement Parse (string s)
450                 {
451                         return Parse (s, LoadOptions.None);
452                 }
453
454                 public static XElement Parse (string s, LoadOptions options)
455                 {
456                         return Load (new StringReader (s), options);
457                 }
458
459                 public void RemoveAll ()
460                 {
461                         RemoveAttributes ();
462                         RemoveNodes ();
463                 }
464
465                 public void RemoveAttributes ()
466                 {
467                         while (attr_first != null)
468                                 attr_last.Remove ();
469                 }
470
471                 public void Save (string filename)
472                 {
473                         Save (filename, SaveOptions.None);
474                 }
475
476                 public void Save (string filename, SaveOptions options)
477                 {
478                         XmlWriterSettings s = new XmlWriterSettings ();
479                         s.Indent = options != SaveOptions.DisableFormatting;
480                         using (XmlWriter w = XmlWriter.Create (filename, s)) {
481                                 Save (w);
482                         }
483                 }
484
485                 public void Save (TextWriter tw)
486                 {
487                         Save (tw, SaveOptions.None);
488                 }
489
490                 public void Save (TextWriter tw, SaveOptions options)
491                 {
492                         XmlWriterSettings s = new XmlWriterSettings ();
493                         s.Indent = options != SaveOptions.DisableFormatting;
494                         using (XmlWriter w = XmlWriter.Create (tw, s)) {
495                                 Save (w);
496                         }
497                 }
498
499                 public void Save (XmlWriter w)
500                 {
501                         WriteTo (w);
502                 }
503
504                 public IEnumerable <XElement> AncestorsAndSelf ()
505                 {
506                         return GetAncestorList (null, true);
507                 }
508
509                 public IEnumerable <XElement> AncestorsAndSelf (XName name)
510                 {
511                         return GetAncestorList (name, true);
512                 }
513
514                 public IEnumerable <XElement> DescendantsAndSelf ()
515                 {
516                         List <XElement> list = new List <XElement> ();
517                         list.Add (this);
518                         list.AddRange (Descendants ());
519                         return list;
520                 }
521
522                 public IEnumerable <XElement> DescendantsAndSelf (XName name)
523                 {
524                         List <XElement> list = new List <XElement> ();
525                         if (name == this.name)
526                                 list.Add (this);
527                         list.AddRange (Descendants (name));
528                         return list;
529                 }
530
531                 public IEnumerable <XNode> DescendantNodesAndSelf ()
532                 {
533                         yield return this;
534                         foreach (XNode node in DescendantNodes ())
535                                 yield return node;
536                 }
537
538                 public void SetAttributeValue (XName name, object value)
539                 {
540                         XAttribute a = Attribute (name);
541                         if (value == null) {
542                                 if (a != null)
543                                         a.Remove ();
544                         } else {
545                                 if (a == null) {
546                                         SetAttributeObject (new XAttribute (name, value));
547                                 }
548                                 else
549                                         a.Value = XUtil.ToString (value);
550                         }
551                 }
552
553                 void SetAttributeObject (XAttribute a)
554                 {
555                         a = (XAttribute) XUtil.GetDetachedObject (a);
556                         a.SetOwner (this);
557                         if (attr_first == null) {
558                                 attr_first = a;
559                                 attr_last = a;
560                         } else {
561                                 attr_last.NextAttribute = a;
562                                 a.PreviousAttribute = attr_last;
563                                 attr_last = a;
564                         }
565                 }
566
567                 public override void WriteTo (XmlWriter w)
568                 {
569                         // some people expect the same prefix output as in input,
570                         // in the loss of performance... see bug #466423.
571                         string prefix = name.NamespaceName.Length > 0 ? w.LookupPrefix (name.Namespace.NamespaceName) : String.Empty;
572                         foreach (XAttribute a in Attributes ()) {
573                                 if (a.IsNamespaceDeclaration && a.Value == name.Namespace.NamespaceName) {
574                                         if (a.Name.Namespace == XNamespace.Xmlns)
575                                                 prefix = a.Name.LocalName;
576                                         // otherwise xmlns="..."
577                                         break;
578                                 }
579                         }
580
581                         w.WriteStartElement (prefix, name.LocalName, name.Namespace.NamespaceName);
582
583                         foreach (XAttribute a in Attributes ()) {
584                                 if (a.IsNamespaceDeclaration) {
585                                         if (a.Name.Namespace == XNamespace.Xmlns)
586                                                 w.WriteAttributeString ("xmlns", a.Name.LocalName, XNamespace.Xmlns.NamespaceName, a.Value);
587                                         else
588                                                 w.WriteAttributeString ("xmlns", a.Value);
589                                 }
590                                 else
591                                         w.WriteAttributeString (a.Name.LocalName, a.Name.Namespace.NamespaceName, a.Value);
592                         }
593
594                         foreach (XNode node in Nodes ())
595                                 node.WriteTo (w);
596
597                         if (explicit_is_empty)
598                                 w.WriteEndElement ();
599                         else
600                                 w.WriteFullEndElement ();
601                 }
602
603                 public XNamespace GetDefaultNamespace ()
604                 {
605                         for (XElement el = this; el != null; el = el.Parent)
606                                 foreach (XAttribute a in el.Attributes ())
607                                         if (a.IsNamespaceDeclaration && a.Name.Namespace == XNamespace.None)
608                                                 return XNamespace.Get (a.Value);
609                         return XNamespace.None; // nothing is declared.
610                 }
611
612                 public XNamespace GetNamespaceOfPrefix (string prefix)
613                 {
614                         for (XElement el = this; el != null; el = el.Parent)
615                                 foreach (XAttribute a in el.Attributes ())
616                                         if (a.IsNamespaceDeclaration && a.Name.LocalName == prefix)
617                                                 return XNamespace.Get (a.Value);
618                         return XNamespace.None; // nothing is declared.
619                 }
620
621                 public string GetPrefixOfNamespace (XNamespace ns)
622                 {
623                         foreach (string prefix in GetPrefixOfNamespaceCore (ns))
624                                 if (GetNamespaceOfPrefix (prefix) == ns)
625                                         return prefix;
626                         return null; // nothing is declared
627                 }
628                 
629                 IEnumerable<string> GetPrefixOfNamespaceCore (XNamespace ns)
630                 {
631                         for (XElement el = this; el != null; el = el.Parent)
632                                 foreach (XAttribute a in el.Attributes ())
633                                         if (a.IsNamespaceDeclaration && a.Value == ns.NamespaceName)
634                                                 yield return a.Name.Namespace == XNamespace.None ? String.Empty : a.Name.LocalName;
635                 }
636
637                 public void ReplaceAll (object item)
638                 {
639                         RemoveNodes ();
640                         Add (item);
641                 }
642
643                 public void ReplaceAll (params object [] items)
644                 {
645                         RemoveNodes ();
646                         Add (items);
647                 }
648
649                 public void ReplaceAttributes (object item)
650                 {
651                         RemoveAttributes ();
652                         Add (item);
653                 }
654
655                 public void ReplaceAttributes (params object [] items)
656                 {
657                         RemoveAttributes ();
658                         Add (items);
659                 }
660
661                 public void SetElementValue (XName name, object value)
662                 {
663                         XElement el = new XElement (name, value);
664                         RemoveNodes ();
665                         Add (el);
666                 }
667
668                 public void SetValue (object value)
669                 {
670                         if (value == null)
671                                 throw new ArgumentNullException ("value");
672                         if (value is XAttribute || value is XDocument || value is XDeclaration || value is XDocumentType)
673                                 throw new ArgumentException (String.Format ("Node type {0} is not allowed as element value", value.GetType ()));
674                         RemoveNodes ();
675                         foreach (object o in XUtil.ExpandArray (value))
676                                 Add (o);
677                 }
678
679                 internal override bool OnAddingObject (object o, bool rejectAttribute, XNode refNode, bool addFirst)
680                 {
681                         if (o is XDocument || o is XDocumentType || o is XDeclaration || (rejectAttribute && o is XAttribute))
682                                 throw new ArgumentException (String.Format ("A node of type {0} cannot be added as a content", o.GetType ()));
683
684                         XAttribute a = o as XAttribute;
685                         if (a != null) {
686                                 foreach (XAttribute ia in Attributes ())
687                                         if (a.Name == ia.Name)
688                                                 throw new InvalidOperationException (String.Format ("Duplicate attribute: {0}", a.Name));
689                                 SetAttributeObject (a);
690                                 return true;
691                         }
692                         else if (o is string && refNode is XText) {
693                                 ((XText) refNode).Value += o as string;
694                                 return true;
695                         }
696                         else
697                                 return false;
698                 }
699
700                 void IXmlSerializable.WriteXml (XmlWriter writer)
701                 {
702                         Save (writer);
703                 }
704
705                 void IXmlSerializable.ReadXml (XmlReader reader)
706                 {
707                         ReadContentFrom (reader, LoadOptions.None);
708                 }
709
710                 XmlSchema IXmlSerializable.GetSchema ()
711                 {
712                         return null;
713                 }
714         }
715 }