// // Authors: // Atsushi Enomoto // // Copyright 2007 Novell (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; namespace System.Xml.Linq { [XmlSchemaProvider (null, IsAny = true)] public class XElement : XContainer, IXmlSerializable { static IEnumerable emptySequence = new List (); public static IEnumerable EmptySequence { get { return emptySequence; } } XName name; XAttribute attr_first, attr_last; bool explicit_is_empty = true; public XElement (XName name, object content) { this.name = name; Add (content); } public XElement (XElement other) { name = other.name; Add (other.Attributes ()); Add (other.Nodes ()); } public XElement (XName name) { this.name = name; } public XElement (XName name, params object [] content) { this.name = name; Add (content); } public XElement (XStreamingElement other) { this.name = other.Name; Add (other.Contents); } [CLSCompliant (false)] public static explicit operator bool (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XUtil.ConvertToBoolean (element.Value); } [CLSCompliant (false)] public static explicit operator bool? (XElement element) { if (element == null) return null; return element.Value == null ? (bool?) null : XUtil.ConvertToBoolean (element.Value); } [CLSCompliant (false)] public static explicit operator DateTime (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XUtil.ToDateTime (element.Value); } [CLSCompliant (false)] public static explicit operator DateTime? (XElement element) { if (element == null) return null; return element.Value == null ? (DateTime?) null : XUtil.ToDateTime (element.Value); } #if !TARGET_JVM // Same as for System.Xml.XmlConvert.ToDateTimeOffset [CLSCompliant (false)] public static explicit operator DateTimeOffset (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToDateTimeOffset (element.Value); } [CLSCompliant (false)] public static explicit operator DateTimeOffset? (XElement element) { if (element == null) return null; return element.Value == null ? (DateTimeOffset?) null : XmlConvert.ToDateTimeOffset (element.Value); } #endif [CLSCompliant (false)] public static explicit operator decimal (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToDecimal (element.Value); } [CLSCompliant (false)] public static explicit operator decimal? (XElement element) { if (element == null) return null; return element.Value == null ? (decimal?) null : XmlConvert.ToDecimal (element.Value); } [CLSCompliant (false)] public static explicit operator double (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToDouble (element.Value); } [CLSCompliant (false)] public static explicit operator double? (XElement element) { if (element == null) return null; return element.Value == null ? (double?) null : XmlConvert.ToDouble (element.Value); } [CLSCompliant (false)] public static explicit operator float (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToSingle (element.Value); } [CLSCompliant (false)] public static explicit operator float? (XElement element) { if (element == null) return null; return element.Value == null ? (float?) null : XmlConvert.ToSingle (element.Value); } [CLSCompliant (false)] public static explicit operator Guid (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToGuid (element.Value); } [CLSCompliant (false)] public static explicit operator Guid? (XElement element) { if (element == null) return null; return element.Value == null ? (Guid?) null : XmlConvert.ToGuid (element.Value); } [CLSCompliant (false)] public static explicit operator int (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToInt32 (element.Value); } [CLSCompliant (false)] public static explicit operator int? (XElement element) { if (element == null) return null; return element.Value == null ? (int?) null : XmlConvert.ToInt32 (element.Value); } [CLSCompliant (false)] public static explicit operator long (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToInt64 (element.Value); } [CLSCompliant (false)] public static explicit operator long? (XElement element) { if (element == null) return null; return element.Value == null ? (long?) null : XmlConvert.ToInt64 (element.Value); } [CLSCompliant (false)] public static explicit operator uint (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToUInt32 (element.Value); } [CLSCompliant (false)] public static explicit operator uint? (XElement element) { if (element == null) return null; return element.Value == null ? (uint?) null : XmlConvert.ToUInt32 (element.Value); } [CLSCompliant (false)] public static explicit operator ulong (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToUInt64 (element.Value); } [CLSCompliant (false)] public static explicit operator ulong? (XElement element) { if (element == null) return null; return element.Value == null ? (ulong?) null : XmlConvert.ToUInt64 (element.Value); } [CLSCompliant (false)] public static explicit operator TimeSpan (XElement element) { if (element == null) throw new ArgumentNullException ("element"); return XmlConvert.ToTimeSpan (element.Value); } [CLSCompliant (false)] public static explicit operator TimeSpan? (XElement element) { if (element == null) return null; return element.Value == null ? (TimeSpan?) null : XmlConvert.ToTimeSpan (element.Value); } [CLSCompliant (false)] public static explicit operator string (XElement element) { if (element == null) return null; return element.Value; } public XAttribute FirstAttribute { get { return attr_first; } internal set { attr_first = value; } } public XAttribute LastAttribute { get { return attr_last; } internal set { attr_last = value; } } public bool HasAttributes { get { return attr_first != null; } } public bool HasElements { get { foreach (object o in Nodes ()) if (o is XElement) return true; return false; } } public bool IsEmpty { get { return !Nodes ().GetEnumerator ().MoveNext () && explicit_is_empty; } internal set { explicit_is_empty = value; } } public XName Name { get { return name; } set { if (value == null) throw new ArgumentNullException ("Name"); name = value; } } public override XmlNodeType NodeType { get { return XmlNodeType.Element; } } public string Value { get { StringBuilder sb = null; foreach (XNode n in Nodes ()) { if (sb == null) sb = new StringBuilder (); if (n is XText) sb.Append (((XText) n).Value); else if (n is XElement) sb.Append (((XElement) n).Value); } return sb == null ? String.Empty : sb.ToString (); } set { RemoveNodes (); Add (value); } } IEnumerable GetAncestorList (XName name, bool getMeIn) { List list = new List (); if (getMeIn) list.Add (this); for (XElement el = Parent as XElement; el != null; el = el.Parent as XElement) if (name == null || el.Name == name) list.Add (el); return list; } public XAttribute Attribute (XName name) { foreach (XAttribute a in Attributes ()) if (a.Name == name) return a; return null; } public IEnumerable Attributes () { XAttribute next; for (XAttribute a = attr_first; a != null; a = next) { next = a.NextAttribute; yield return a; } } // huh? public IEnumerable Attributes (XName name) { foreach (XAttribute a in Attributes ()) if (a.Name == name) yield return a; } static void DefineDefaultSettings (XmlReaderSettings settings, LoadOptions options) { #if MOONLIGHT // 2.1 has a DtdProcessing property which defaults to DtdProcessing.Prohibit settings.DtdProcessing = DtdProcessing.Parse; #else settings.ProhibitDtd = false; #endif settings.IgnoreWhitespace = (options & LoadOptions.PreserveWhitespace) == 0; } static XmlReaderSettings CreateDefaultSettings (LoadOptions options) { var settings = new XmlReaderSettings (); DefineDefaultSettings (settings, options); return settings; } public static XElement Load (string uri) { return Load (uri, LoadOptions.None); } public static XElement Load (string uri, LoadOptions options) { XmlReaderSettings s = CreateDefaultSettings (options); using (XmlReader r = XmlReader.Create (uri, s)) { return LoadCore (r, options); } } public static XElement Load (TextReader textReader) { return Load (textReader, LoadOptions.None); } public static XElement Load (TextReader textReader, LoadOptions options) { XmlReaderSettings s = CreateDefaultSettings (options); using (XmlReader r = XmlReader.Create (textReader, s)) { return LoadCore (r, options); } } public static XElement Load (XmlReader reader) { return Load (reader, LoadOptions.None); } public static XElement Load (XmlReader reader, LoadOptions options) { XmlReaderSettings s = reader.Settings != null ? reader.Settings.Clone () : new XmlReaderSettings (); DefineDefaultSettings (s, options); using (XmlReader r = XmlReader.Create (reader, s)) { return LoadCore (r, options); } } #if MOONLIGHT || MOBILE || NET_4_0 public static XElement Load (Stream stream) { return Load (stream, LoadOptions.None); } public static XElement Load (Stream stream, LoadOptions options) { XmlReaderSettings s = new XmlReaderSettings (); DefineDefaultSettings (s, options); using (XmlReader r = XmlReader.Create (stream, s)) { return LoadCore (r, options); } } #endif internal static XElement LoadCore (XmlReader r, LoadOptions options) { r.MoveToContent (); if (r.NodeType != XmlNodeType.Element) throw new InvalidOperationException ("The XmlReader must be positioned at an element"); XName name = XName.Get (r.LocalName, r.NamespaceURI); XElement e = new XElement (name); e.FillLineInfoAndBaseUri (r, options); if (r.MoveToFirstAttribute ()) { do { // not sure how current Orcas behavior makes sense here though ... if (r.LocalName == "xmlns" && r.NamespaceURI == XNamespace.Xmlns.NamespaceName) e.SetAttributeValue (XNamespace.None.GetName ("xmlns"), r.Value); else e.SetAttributeValue (XName.Get (r.LocalName, r.NamespaceURI), r.Value); e.LastAttribute.FillLineInfoAndBaseUri (r, options); } while (r.MoveToNextAttribute ()); r.MoveToElement (); } if (!r.IsEmptyElement) { r.Read (); e.ReadContentFrom (r, options); r.ReadEndElement (); e.explicit_is_empty = false; } else { e.explicit_is_empty = true; r.Read (); } return e; } public static XElement Parse (string text) { return Parse (text, LoadOptions.None); } public static XElement Parse (string text, LoadOptions options) { return Load (new StringReader (text), options); } public void RemoveAll () { RemoveAttributes (); RemoveNodes (); } public void RemoveAttributes () { while (attr_first != null) attr_last.Remove (); } public void Save (string fileName) { Save (fileName, SaveOptions.None); } public void Save (string fileName, SaveOptions options) { XmlWriterSettings s = new XmlWriterSettings (); if ((options & SaveOptions.DisableFormatting) == SaveOptions.None) s.Indent = true; #if NET_4_0 || MOONLIGHT || MOBILE if ((options & SaveOptions.OmitDuplicateNamespaces) == SaveOptions.OmitDuplicateNamespaces) s.NamespaceHandling |= NamespaceHandling.OmitDuplicates; #endif using (XmlWriter w = XmlWriter.Create (fileName, s)) { Save (w); } } public void Save (TextWriter textWriter) { Save (textWriter, SaveOptions.None); } public void Save (TextWriter textWriter, SaveOptions options) { XmlWriterSettings s = new XmlWriterSettings (); if ((options & SaveOptions.DisableFormatting) == SaveOptions.None) s.Indent = true; #if NET_4_0 || MOONLIGHT || MOBILE if ((options & SaveOptions.OmitDuplicateNamespaces) == SaveOptions.OmitDuplicateNamespaces) s.NamespaceHandling |= NamespaceHandling.OmitDuplicates; #endif using (XmlWriter w = XmlWriter.Create (textWriter, s)) { Save (w); } } public void Save (XmlWriter writer) { WriteTo (writer); } #if NET_4_0 || MOONLIGHT || MOBILE public void Save (Stream stream) { Save (stream, SaveOptions.None); } public void Save (Stream stream, SaveOptions options) { XmlWriterSettings s = new XmlWriterSettings (); if ((options & SaveOptions.DisableFormatting) == SaveOptions.None) s.Indent = true; if ((options & SaveOptions.OmitDuplicateNamespaces) == SaveOptions.OmitDuplicateNamespaces) s.NamespaceHandling |= NamespaceHandling.OmitDuplicates; using (var writer = XmlWriter.Create (stream, s)){ Save (writer); } } #endif public IEnumerable AncestorsAndSelf () { return GetAncestorList (null, true); } public IEnumerable AncestorsAndSelf (XName name) { return GetAncestorList (name, true); } public IEnumerable DescendantsAndSelf () { List list = new List (); list.Add (this); list.AddRange (Descendants ()); return list; } public IEnumerable DescendantsAndSelf (XName name) { List list = new List (); if (name == this.name) list.Add (this); list.AddRange (Descendants (name)); return list; } public IEnumerable DescendantNodesAndSelf () { yield return this; foreach (XNode node in DescendantNodes ()) yield return node; } public void SetAttributeValue (XName name, object value) { XAttribute a = Attribute (name); if (value == null) { if (a != null) a.Remove (); } else { if (a == null) { SetAttributeObject (new XAttribute (name, value)); } else a.Value = XUtil.ToString (value); } } void SetAttributeObject (XAttribute a) { a = (XAttribute) XUtil.GetDetachedObject (a); a.SetOwner (this); if (attr_first == null) { attr_first = a; attr_last = a; } else { attr_last.NextAttribute = a; a.PreviousAttribute = attr_last; attr_last = a; } } string LookupPrefix (string ns, XmlWriter w) { string prefix = ns.Length > 0 ? GetPrefixOfNamespace (ns) ?? w.LookupPrefix (ns) : String.Empty; foreach (XAttribute a in Attributes ()) { if (a.IsNamespaceDeclaration && a.Value == ns) { if (a.Name.Namespace == XNamespace.Xmlns) prefix = a.Name.LocalName; // otherwise xmlns="..." break; } } return prefix; } static string CreateDummyNamespace (ref int createdNS, IEnumerable atts, bool isAttr) { if (!isAttr && atts.All (a => a.Name.LocalName != "xmlns" || a.Name.NamespaceName == XNamespace.Xmlns.NamespaceName)) return String.Empty; string p = null; do { p = "p" + (++createdNS); // check conflict if (atts.All (a => a.Name.LocalName != p || a.Name.NamespaceName == XNamespace.Xmlns.NamespaceName)) break; } while (true); return p; } public override void WriteTo (XmlWriter writer) { // some people expect the same prefix output as in input, // in the loss of performance... see bug #466423. string prefix = LookupPrefix (name.NamespaceName, writer); int createdNS = 0; if (prefix == null) prefix = CreateDummyNamespace (ref createdNS, Attributes (), false); writer.WriteStartElement (prefix, name.LocalName, name.Namespace.NamespaceName); foreach (XAttribute a in Attributes ()) { if (a.IsNamespaceDeclaration) { if (a.Name.Namespace == XNamespace.Xmlns) writer.WriteAttributeString ("xmlns", a.Name.LocalName, XNamespace.Xmlns.NamespaceName, a.Value); else writer.WriteAttributeString ("xmlns", a.Value); } else { string apfix = LookupPrefix (a.Name.NamespaceName, writer); if (apfix == null) apfix = CreateDummyNamespace (ref createdNS, Attributes (), true); writer.WriteAttributeString (apfix, a.Name.LocalName, a.Name.Namespace.NamespaceName, a.Value); } } foreach (XNode node in Nodes ()) node.WriteTo (writer); if (explicit_is_empty) writer.WriteEndElement (); else writer.WriteFullEndElement (); } public XNamespace GetDefaultNamespace () { for (XElement el = this; el != null; el = el.Parent) foreach (XAttribute a in el.Attributes ()) if (a.IsNamespaceDeclaration && a.Name.Namespace == XNamespace.None) return XNamespace.Get (a.Value); return XNamespace.None; // nothing is declared. } public XNamespace GetNamespaceOfPrefix (string prefix) { for (XElement el = this; el != null; el = el.Parent) foreach (XAttribute a in el.Attributes ()) if (a.IsNamespaceDeclaration && (prefix.Length == 0 && a.Name.LocalName == "xmlns" || a.Name.LocalName == prefix)) return XNamespace.Get (a.Value); return XNamespace.None; // nothing is declared. } public string GetPrefixOfNamespace (XNamespace ns) { foreach (string prefix in GetPrefixOfNamespaceCore (ns)) if (GetNamespaceOfPrefix (prefix) == ns) return prefix; return null; // nothing is declared } IEnumerable GetPrefixOfNamespaceCore (XNamespace ns) { for (XElement el = this; el != null; el = el.Parent) foreach (XAttribute a in el.Attributes ()) if (a.IsNamespaceDeclaration && a.Value == ns.NamespaceName) yield return a.Name.Namespace == XNamespace.None ? String.Empty : a.Name.LocalName; } public void ReplaceAll (object content) { RemoveNodes (); Add (content); } public void ReplaceAll (params object [] content) { RemoveNodes (); Add (content); } public void ReplaceAttributes (object content) { RemoveAttributes (); Add (content); } public void ReplaceAttributes (params object [] content) { RemoveAttributes (); Add (content); } public void SetElementValue (XName name, object value) { var element = Element (name); if (element == null && value != null) { Add (new XElement (name, value)); } else if (element != null && value == null) { element.Remove (); } else element.SetValue (value); } public void SetValue (object value) { if (value == null) throw new ArgumentNullException ("value"); if (value is XAttribute || value is XDocument || value is XDeclaration || value is XDocumentType) throw new ArgumentException (String.Format ("Node type {0} is not allowed as element value", value.GetType ())); RemoveNodes (); foreach (object o in XUtil.ExpandArray (value)) Add (o); } internal override bool OnAddingObject (object o, bool rejectAttribute, XNode refNode, bool addFirst) { if (o is XDocument || o is XDocumentType || o is XDeclaration || (rejectAttribute && o is XAttribute)) throw new ArgumentException (String.Format ("A node of type {0} cannot be added as a content", o.GetType ())); XAttribute a = o as XAttribute; if (a != null) { foreach (XAttribute ia in Attributes ()) if (a.Name == ia.Name) throw new InvalidOperationException (String.Format ("Duplicate attribute: {0}", a.Name)); SetAttributeObject (a); return true; } else if (o is string && refNode is XText) { ((XText) refNode).Value += o as string; return true; } else return false; } void IXmlSerializable.WriteXml (XmlWriter writer) { Save (writer); } void IXmlSerializable.ReadXml (XmlReader reader) { ReadContentFrom (reader, LoadOptions.None); } XmlSchema IXmlSerializable.GetSchema () { return null; } } }