diff --git a/src/System.Xml.XDocument/System.Xml.XDocument.csproj b/src/System.Xml.XDocument/System.Xml.XDocument.csproj index 873582d64b15..ce715ee12cdc 100644 --- a/src/System.Xml.XDocument/System.Xml.XDocument.csproj +++ b/src/System.Xml.XDocument/System.Xml.XDocument.csproj @@ -71,8 +71,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/System.Xml.XDocument/System/Xml/Linq/BaseUriAnnotation.cs b/src/System.Xml.XDocument/System/Xml/Linq/BaseUriAnnotation.cs new file mode 100644 index 000000000000..dcdbbbbb65a5 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/BaseUriAnnotation.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + class BaseUriAnnotation + { + internal string baseUri; + + public BaseUriAnnotation(string baseUri) + { + this.baseUri = baseUri; + } + } +} \ No newline at end of file diff --git a/src/System.Xml.XDocument/System/Xml/Linq/Extensions.cs b/src/System.Xml.XDocument/System/Xml/Linq/Extensions.cs new file mode 100644 index 000000000000..d127d8347a5c --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/Extensions.cs @@ -0,0 +1,426 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Defines the LINQ to XML extension methods. + /// + public static class Extensions + { + /// + /// Returns all of the s for each of + /// this of . + /// + /// + /// An of containing the XML + /// Attributes for every in the target + /// of . + /// + public static IEnumerable Attributes(this IEnumerable source) + { + if (source == null) throw new ArgumentNullException("source"); + return GetAttributes(source, null); + } + + /// + /// Returns the s that have a matching . Each + /// 's s in the target + /// of are scanned for a matching . + /// + /// + /// An of containing the XML + /// Attributes with a matching for every in + /// the target of . + /// + public static IEnumerable Attributes(this IEnumerable source, XName name) + { + if (source == null) throw new ArgumentNullException("source"); + return name != null ? GetAttributes(source, name) : XAttribute.EmptySequence; + } + + /// + /// Returns an of containing the ancestors (parent + /// and it's parent up to the root) of each of the s in this + /// of . + /// + /// + /// An of containing the ancestors (parent + /// and it's parent up to the root) of each of the s in this + /// of . + /// + public static IEnumerable Ancestors(this IEnumerable source) where T : XNode + { + if (source == null) throw new ArgumentNullException("source"); + return GetAncestors(source, null, false); + } + + /// + /// Returns an of containing the ancestors (parent + /// and it's parent up to the root) that have a matching . This is done for each + /// in this of . + /// + /// + /// An of containing the ancestors (parent + /// and it's parent up to the root) that have a matching . This is done for each + /// in this of . + /// + public static IEnumerable Ancestors(this IEnumerable source, XName name) where T : XNode + { + if (source == null) throw new ArgumentNullException("source"); + return name != null ? GetAncestors(source, name, false) : XElement.EmptySequence; + } + + /// + /// Returns an of containing the + /// and it's ancestors (parent and it's parent up to the root). + /// This is done for each in this of + /// . + /// + /// + /// An of containing the + /// and it's ancestors (parent and it's parent up to the root). + /// This is done for each in this of + /// . + /// + public static IEnumerable AncestorsAndSelf(this IEnumerable source) + { + if (source == null) throw new ArgumentNullException("source"); + return GetAncestors(source, null, true); + } + + /// + /// Returns an of containing the + /// and it's ancestors (parent and it's parent up to the root) + /// that match the passed in . This is done for each + /// in this of . + /// + /// + /// An of containing the + /// and it's ancestors (parent and it's parent up to the root) + /// that match the passed in . This is done for each + /// in this of . + /// + public static IEnumerable AncestorsAndSelf(this IEnumerable source, XName name) + { + if (source == null) throw new ArgumentNullException("source"); + return name != null ? GetAncestors(source, name, true) : XElement.EmptySequence; + } + + /// + /// Returns an of over the content of a set of nodes + /// + public static IEnumerable Nodes(this IEnumerable source) where T : XContainer + { + if (source == null) throw new ArgumentNullException("source"); + foreach (XContainer root in source) + { + if (root != null) + { + XNode n = root.LastNode; + if (n != null) + { + do + { + n = n.next; + yield return n; + } while (n.parent == root && n != root.content); + } + } + } + } + + /// + /// Returns an of over the descendants of a set of nodes + /// + public static IEnumerable DescendantNodes(this IEnumerable source) where T : XContainer + { + if (source == null) throw new ArgumentNullException("source"); + return GetDescendantNodes(source, false); + } + + /// + /// Returns an of containing the descendants (children + /// and their children down to the leaf level). This is done for each in + /// this of . + /// + /// + /// An of containing the descendants (children + /// and their children down to the leaf level). This is done for each in + /// this of . + /// + public static IEnumerable Descendants(this IEnumerable source) where T : XContainer + { + if (source == null) throw new ArgumentNullException("source"); + return GetDescendants(source, null, false); + } + + /// + /// Returns an of containing the descendants (children + /// and their children down to the leaf level) that have a matching . This is done + /// for each in the target of . + /// + /// + /// An of containing the descendants (children + /// and their children down to the leaf level) that have a matching . This is done + /// for each in this of . + /// + public static IEnumerable Descendants(this IEnumerable source, XName name) where T : XContainer + { + if (source == null) throw new ArgumentNullException("source"); + return name != null ? GetDescendants(source, name, false) : XElement.EmptySequence; + } + + /// + /// Returns an of containing the + /// and it's descendants + /// that match the passed in . This is done for each + /// in this of . + /// + /// + /// An of containing the + /// and descendants. + /// This is done for each + /// in this of . + /// + public static IEnumerable DescendantNodesAndSelf(this IEnumerable source) + { + if (source == null) throw new ArgumentNullException("source"); + return GetDescendantNodes(source, true); + } + + /// + /// Returns an of containing the + /// and it's descendants (children and children's children down + /// to the leaf nodes). This is done for each in this + /// of . + /// + /// + /// An of containing the + /// and it's descendants (children and children's children down + /// to the leaf nodes). This is done for each in this + /// of . + /// + public static IEnumerable DescendantsAndSelf(this IEnumerable source) + { + if (source == null) throw new ArgumentNullException("source"); + return GetDescendants(source, null, true); + } + + /// + /// Returns an of containing the + /// and it's descendants (children and children's children down + /// to the leaf nodes) that match the passed in . This is done for + /// each in this of . + /// + /// + /// An of containing the + /// and it's descendants (children and children's children down + /// to the leaf nodes) that match the passed in . This is done for + /// each in this of . + /// + public static IEnumerable DescendantsAndSelf(this IEnumerable source, XName name) + { + if (source == null) throw new ArgumentNullException("source"); + return name != null ? GetDescendants(source, name, true) : XElement.EmptySequence; + } + + /// + /// Returns an of containing the child elements + /// for each in this of . + /// + /// + /// An of containing the child elements + /// for each in this of . + /// + public static IEnumerable Elements(this IEnumerable source) where T : XContainer + { + if (source == null) throw new ArgumentNullException("source"); + return GetElements(source, null); + } + + /// + /// Returns an of containing the child elements + /// with a matching for each in this of . + /// + /// + /// An of containing the child elements + /// for each in this of . + /// + public static IEnumerable Elements(this IEnumerable source, XName name) where T : XContainer + { + if (source == null) throw new ArgumentNullException("source"); + return name != null ? GetElements(source, name) : XElement.EmptySequence; + } + + /// + /// Returns an of containing the child elements + /// with a matching for each in this of . + /// + /// + /// An of containing the child elements + /// for each in this of . + /// in document order + /// + public static IEnumerable InDocumentOrder(this IEnumerable source) where T : XNode + { + return Enumerable.OrderBy(source, n => (XNode)n, XNode.DocumentOrderComparer); + } + + /// + /// Removes each represented in this of + /// . Note that this method uses snapshot semantics (copies the + /// attributes to a before deleting each). + /// + public static void Remove(this IEnumerable source) + { + if (source == null) throw new ArgumentNullException("source"); + foreach (XAttribute a in new List(source)) + if (a != null) a.Remove(); + } + + /// + /// Removes each represented in this + /// T which must be a derived from . Note that this method uses snapshot semantics + /// (copies the s to a List before deleting each). + /// + public static void Remove(this IEnumerable source) where T : XNode + { + if (source == null) throw new ArgumentNullException("source"); + foreach (T node in new List(source)) + if (node != null) node.Remove(); + } + + static IEnumerable GetAttributes(IEnumerable source, XName name) + { + foreach (XElement e in source) + { + if (e != null) + { + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (name == null || a.name == name) yield return a; + } while (a.parent == e && a != e.lastAttr); + } + } + } + } + + static IEnumerable GetAncestors(IEnumerable source, XName name, bool self) where T : XNode + { + foreach (XNode node in source) + { + if (node != null) + { + XElement e = (self ? node : node.parent) as XElement; + while (e != null) + { + if (name == null || e.name == name) yield return e; + e = e.parent as XElement; + } + } + } + } + + static IEnumerable GetDescendantNodes(IEnumerable source, bool self) where T : XContainer + { + foreach (XContainer root in source) + { + if (root != null) + { + if (self) yield return root; + XNode n = root; + while (true) + { + XContainer c = n as XContainer; + XNode first; + if (c != null && (first = c.FirstNode) != null) + { + n = first; + } + else + { + while (n != null && n != root && n == n.parent.content) n = n.parent; + if (n == null || n == root) break; + n = n.next; + } + yield return n; + } + } + } + } + + static IEnumerable GetDescendants(IEnumerable source, XName name, bool self) where T : XContainer + { + foreach (XContainer root in source) + { + if (root != null) + { + if (self) + { + XElement e = (XElement)root; + if (name == null || e.name == name) yield return e; + } + XNode n = root; + XContainer c = root; + while (true) + { + if (c != null && c.content is XNode) + { + n = ((XNode)c.content).next; + } + else + { + while (n != null && n != root && n == n.parent.content) n = n.parent; + if (n == null || n == root) break; + n = n.next; + } + XElement e = n as XElement; + if (e != null && (name == null || e.name == name)) yield return e; + c = e; + } + } + } + } + + static IEnumerable GetElements(IEnumerable source, XName name) where T : XContainer + { + foreach (XContainer root in source) + { + if (root != null) + { + XNode n = root.content as XNode; + if (n != null) + { + do + { + n = n.next; + XElement e = n as XElement; + if (e != null && (name == null || e.name == name)) yield return e; + } while (n.parent == root && n != root.content); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/System.Xml.XDocument/System/Xml/Linq/LineInfoAnnotation.cs b/src/System.Xml.XDocument/System/Xml/Linq/LineInfoAnnotation.cs new file mode 100644 index 000000000000..68daed0cb446 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/LineInfoAnnotation.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Instance of this class is used as an annotation on any node + /// for which we want to store its line information. + /// Note: on XElement nodes this annotation stores the line info + /// for the element start tag. The matching end tag line info + /// if present is stored using the LineInfoEndElementAnnotation + /// instance annotation. + /// + class LineInfoAnnotation + { + internal int lineNumber; + internal int linePosition; + + public LineInfoAnnotation(int lineNumber, int linePosition) + { + this.lineNumber = lineNumber; + this.linePosition = linePosition; + } + } +} \ No newline at end of file diff --git a/src/System.Xml.XDocument/System/Xml/Linq/LineInfoEndElementAnnotation.cs b/src/System.Xml.XDocument/System/Xml/Linq/LineInfoEndElementAnnotation.cs new file mode 100644 index 000000000000..08831d2266d4 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/LineInfoEndElementAnnotation.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Instance of this class is used as an annotation on XElement nodes + /// if that element is not empty element and we want to store the line info + /// for its end element tag. + /// + class LineInfoEndElementAnnotation : LineInfoAnnotation + { + public LineInfoEndElementAnnotation(int lineNumber, int linePosition) + : base(lineNumber, linePosition) + { } + } +} \ No newline at end of file diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XAttribute.cs b/src/System.Xml.XDocument/System/Xml/Linq/XAttribute.cs new file mode 100644 index 000000000000..9b289dcf1916 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XAttribute.cs @@ -0,0 +1,740 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents an XML attribute. + /// + /// + /// An XML attribute is a name/value pair associated with an XML element. + /// + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "Reviewed.")] + public class XAttribute : XObject + { + static IEnumerable emptySequence; + + /// + /// Gets an empty collection of attributes. + /// + public static IEnumerable EmptySequence + { + get + { + if (emptySequence == null) emptySequence = new XAttribute[0]; + return emptySequence; + } + } + + internal XAttribute next; + internal XName name; + internal string value; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Initializes a new instance of the class from + /// the specified name and value. + /// + /// + /// The name of the attribute. + /// + /// + /// The value of the attribute. + /// + /// + /// Thrown if the passed in name or value are null. + /// + public XAttribute(XName name, object value) + { + if (name == null) throw new ArgumentNullException("name"); + if (value == null) throw new ArgumentNullException("value"); + string s = XContainer.GetStringValue(value); + ValidateAttribute(name, s); + this.name = name; + this.value = s; + } + + /// + /// Initializes an instance of the XAttribute class + /// from another XAttribute object. + /// + /// object to copy from. + /// + /// Thrown if the specified is null. + /// + public XAttribute(XAttribute other) + { + if (other == null) throw new ArgumentNullException("other"); + name = other.name; + value = other.value; + } + + /// + /// Gets a value indicating if this attribute is a namespace declaration. + /// + public bool IsNamespaceDeclaration + { + get + { + string namespaceName = name.NamespaceName; + if (namespaceName.Length == 0) + { + return name.LocalName == "xmlns"; + } + return (object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace; + } + } + + /// + /// Gets the name of this attribute. + /// + public XName Name + { + get { return name; } + } + + /// + /// Gets the next attribute of the parent element. + /// + /// + /// If this attribute does not have a parent, or if there is no next attribute, + /// then this property returns null. + /// + public XAttribute NextAttribute + { + get { return parent != null && ((XElement)parent).lastAttr != this ? next : null; } + } + + /// + /// Gets the node type for this node. + /// + /// + /// This property will always return XmlNodeType.Attribute. + /// + public override XmlNodeType NodeType + { + get + { + return XmlNodeType.Attribute; + } + } + + /// + /// Gets the previous attribute of the parent element. + /// + /// + /// If this attribute does not have a parent, or if there is no previous attribute, + /// then this property returns null. + /// + public XAttribute PreviousAttribute + { + get + { + if (parent == null) return null; + XAttribute a = ((XElement)parent).lastAttr; + while (a.next != this) + { + a = a.next; + } + return a != ((XElement)parent).lastAttr ? a : null; + } + } + + /// + /// Gets or sets the value of this attribute. + /// + /// + /// Thrown if the value set is null. + /// + public string Value + { + get + { + return value; + } + set + { + if (value == null) throw new ArgumentNullException("value"); + ValidateAttribute(name, value); + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); + this.value = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); + } + } + + /// + /// Deletes this XAttribute. + /// + /// + /// Thrown if the parent element is null. + /// + public void Remove() + { + if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); + ((XElement)parent).RemoveAttribute(this); + } + + /// + /// Sets the value of this . + /// + /// + /// + /// + /// + /// The value to assign to this attribute. The value is converted to its string + /// representation and assigned to the property. + /// + /// + /// Thrown if the specified value is null. + /// + public void SetValue(object value) + { + if (value == null) throw new ArgumentNullException("value"); + Value = XContainer.GetStringValue(value); + } + + /// + /// Override for on + /// + /// XML text representation of an attribute and its value + public override string ToString() + { + using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture)) + { + XmlWriterSettings ws = new XmlWriterSettings(); + ws.ConformanceLevel = ConformanceLevel.Fragment; + using (XmlWriter w = XmlWriter.Create(sw, ws)) + { + w.WriteAttributeString(GetPrefixOfNamespace(name.Namespace), name.LocalName, name.NamespaceName, value); + } + return sw.ToString().Trim(); + } + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + [CLSCompliant(false)] + public static explicit operator string(XAttribute attribute) + { + if (attribute == null) return null; + return attribute.value; + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator bool(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToBoolean(XHelper.ToLower_InvariantCulture(attribute.value)); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator bool?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToBoolean(XHelper.ToLower_InvariantCulture(attribute.value)); + } + + /// + /// Cast the value of this to an . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as an . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator int(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToInt32(attribute.value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as an ?. + /// + [CLSCompliant(false)] + public static explicit operator int?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToInt32(attribute.value); + } + + /// + /// Cast the value of this to an . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as an . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator uint(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToUInt32(attribute.value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as an ?. + /// + [CLSCompliant(false)] + public static explicit operator uint?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToUInt32(attribute.value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator long(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToInt64(attribute.value); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator long?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToInt64(attribute.value); + } + + /// + /// Cast the value of this to an . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as an . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator ulong(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToUInt64(attribute.value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as an ?. + /// + [CLSCompliant(false)] + public static explicit operator ulong?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToUInt64(attribute.value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator float(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToSingle(attribute.value); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator float?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToSingle(attribute.value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator double(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToDouble(attribute.value); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator double?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToDouble(attribute.value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator decimal(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToDecimal(attribute.value); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator decimal?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToDecimal(attribute.value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator DateTime(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return DateTime.Parse(attribute.value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator DateTime?(XAttribute attribute) + { + if (attribute == null) return null; + return DateTime.Parse(attribute.value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator DateTimeOffset(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToDateTimeOffset(attribute.value); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator DateTimeOffset?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToDateTimeOffset(attribute.value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator TimeSpan(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToTimeSpan(attribute.value); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator TimeSpan?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToTimeSpan(attribute.value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified attribute is null. + /// + [CLSCompliant(false)] + public static explicit operator Guid(XAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException("attribute"); + return XmlConvert.ToGuid(attribute.value); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. Can be null. + /// + /// + /// The content of this as a ?. + /// + [CLSCompliant(false)] + public static explicit operator Guid?(XAttribute attribute) + { + if (attribute == null) return null; + return XmlConvert.ToGuid(attribute.value); + } + + internal int GetDeepHashCode() + { + return name.GetHashCode() ^ value.GetHashCode(); + } + + internal string GetPrefixOfNamespace(XNamespace ns) + { + string namespaceName = ns.NamespaceName; + if (namespaceName.Length == 0) return string.Empty; + if (parent != null) return ((XElement)parent).GetPrefixOfNamespace(ns); + if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) return "xml"; + if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) return "xmlns"; + return null; + } + + static void ValidateAttribute(XName name, string value) + { + // The following constraints apply for namespace declarations: + string namespaceName = name.NamespaceName; + if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) + { + if (value.Length == 0) + { + // The empty namespace name can only be declared by + // the default namespace declaration + throw new ArgumentException(SR.Format(SR.Argument_NamespaceDeclarationPrefixed, name.LocalName)); + } + else if (value == XNamespace.xmlPrefixNamespace) + { + // 'http://www.w3.org/XML/1998/namespace' can only + // be declared by the 'xml' prefix namespace declaration. + if (name.LocalName != "xml") throw new ArgumentException(SR.Argument_NamespaceDeclarationXml); + } + else if (value == XNamespace.xmlnsPrefixNamespace) + { + // 'http://www.w3.org/2000/xmlns/' must not be declared + // by any namespace declaration. + throw new ArgumentException(SR.Argument_NamespaceDeclarationXmlns); + } + else + { + string localName = name.LocalName; + if (localName == "xml") + { + // No other namespace name can be declared by the 'xml' + // prefix namespace declaration. + throw new ArgumentException(SR.Argument_NamespaceDeclarationXml); + } + else if (localName == "xmlns") + { + // The 'xmlns' prefix must not be declared. + throw new ArgumentException(SR.Argument_NamespaceDeclarationXmlns); + } + } + } + else if (namespaceName.Length == 0 && name.LocalName == "xmlns") + { + if (value == XNamespace.xmlPrefixNamespace) + { + // 'http://www.w3.org/XML/1998/namespace' can only + // be declared by the 'xml' prefix namespace declaration. + throw new ArgumentException(SR.Argument_NamespaceDeclarationXml); + } + else if (value == XNamespace.xmlnsPrefixNamespace) + { + // 'http://www.w3.org/2000/xmlns/' must not be declared + // by any namespace declaration. + throw new ArgumentException(SR.Argument_NamespaceDeclarationXmlns); + } + } + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XCData.cs b/src/System.Xml.XDocument/System/Xml/Linq/XCData.cs new file mode 100644 index 000000000000..6c7b86eaf03a --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XCData.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents a text node that contains CDATA. + /// + public class XCData : XText + { + /// + /// Initializes a new instance of the XCData class. + /// + /// The string that contains the value of the XCData node. + public XCData(string value) : base(value) { } + + /// + /// Initializes a new instance of the XCData class from another XCData object. + /// + /// Text node to copy from + public XCData(XCData other) : base(other) { } + + internal XCData(XmlReader r) : base(r) { } + + /// + /// Gets the node type for this node. + /// + /// + /// This property will always return XmlNodeType.CDATA. + /// + public override XmlNodeType NodeType + { + get + { + return XmlNodeType.CDATA; + } + } + + /// + /// Write this to the given . + /// + /// + /// The to write this to. + /// + public override void WriteTo(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + writer.WriteCData(text); + } + + internal override XNode CloneNode() + { + return new XCData(this); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XComment.cs b/src/System.Xml.XDocument/System/Xml/Linq/XComment.cs new file mode 100644 index 000000000000..0290f3e5e8ef --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XComment.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents an XML comment. + /// + public class XComment : XNode + { + internal string value; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Initializes a new instance of the class with the + /// specified string content. + /// + /// + /// The contents of the new XComment object. + /// + /// + /// Thrown if the specified value is null. + /// + public XComment(string value) + { + if (value == null) throw new ArgumentNullException("value"); + this.value = value; + } + + /// + /// Initializes a new comment node from an existing comment node. + /// + /// Comment node to copy from. + public XComment(XComment other) + { + if (other == null) throw new ArgumentNullException("other"); + this.value = other.value; + } + + internal XComment(XmlReader r) + { + value = r.Value; + r.Read(); + } + + /// + /// Gets the node type for this node. + /// + /// + /// This property will always return XmlNodeType.Comment. + /// + public override XmlNodeType NodeType + { + get + { + return XmlNodeType.Comment; + } + } + + /// + /// Gets or sets the string value of this comment. + /// + /// + /// Thrown if the specified value is null. + /// + public string Value + { + get + { + return value; + } + set + { + if (value == null) throw new ArgumentNullException("value"); + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); + this.value = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); + } + } + + /// + /// Write this to the passed in . + /// + /// + /// The to write this to. + /// + public override void WriteTo(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + writer.WriteComment(value); + } + + internal override XNode CloneNode() + { + return new XComment(this); + } + + internal override bool DeepEquals(XNode node) + { + XComment other = node as XComment; + return other != null && value == other.value; + } + + internal override int GetDeepHashCode() + { + return value.GetHashCode(); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XContainer.cs b/src/System.Xml.XDocument/System/Xml/Linq/XContainer.cs new file mode 100644 index 000000000000..9552fc47a4f5 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XContainer.cs @@ -0,0 +1,1146 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents a node that can contain other nodes. + /// + /// + /// The two classes that derive from are + /// and . + /// + public abstract class XContainer : XNode + { + internal object content; + + internal XContainer() { } + + internal XContainer(XContainer other) + { + if (other == null) throw new ArgumentNullException("other"); + if (other.content is string) + { + this.content = other.content; + } + else + { + XNode n = (XNode)other.content; + if (n != null) + { + do + { + n = n.next; + AppendNodeSkipNotify(n.CloneNode()); + } while (n != other.content); + } + } + } + + /// + /// Get the first child node of this node. + /// + public XNode FirstNode + { + get + { + XNode last = LastNode; + return last != null ? last.next : null; + } + } + + /// + /// Get the last child node of this node. + /// + public XNode LastNode + { + get + { + if (content == null) return null; + XNode n = content as XNode; + if (n != null) return n; + string s = content as string; + if (s != null) + { + if (s.Length == 0) return null; + XText t = new XText(s); + t.parent = this; + t.next = t; + Interlocked.CompareExchange(ref content, t, s); + } + return (XNode)content; + } + } + + /// + /// Adds the specified content as a child (or as children) to this XContainer. The + /// content can be simple content, a collection of content objects, a parameter list + /// of content objects, or null. + /// + /// + /// Adds the specified content as a child (or children) of this XContainer. + /// + /// + /// A content object containing simple content or a collection of content objects + /// to be added. + /// + /// + /// When adding simple content, a number of types may be passed to this method. + /// Valid types include: + /// + /// string + /// double + /// float + /// decimal + /// bool + /// DateTime + /// DateTimeOffset + /// TimeSpan + /// Any type implementing ToString() + /// Any type implementing IEnumerable + /// + /// + /// When adding complex content, a number of types may be passed to this method. + /// + /// XObject + /// XNode + /// XAttribute + /// Any type implementing IEnumerable + /// + /// + /// If an object implements IEnumerable, then the collection in the object is enumerated, + /// and all items in the collection are added. If the collection contains simple content, + /// then the simple content in the collection is concatenated and added as a single + /// string of simple content. If the collection contains complex content, then each item + /// in the collection is added separately. + /// + /// If content is null, nothing is added. This allows the results of a query to be passed + /// as content. If the query returns null, no contents are added, and this method does not + /// throw a NullReferenceException. + /// + /// Attributes and simple content can't be added to a document. + /// + /// An added attribute must have a unique name within the element to + /// which it is being added. + /// + public void Add(object content) + { + if (SkipNotify()) + { + AddContentSkipNotify(content); + return; + } + if (content == null) return; + XNode n = content as XNode; + if (n != null) + { + AddNode(n); + return; + } + string s = content as string; + if (s != null) + { + AddString(s); + return; + } + XAttribute a = content as XAttribute; + if (a != null) + { + AddAttribute(a); + return; + } + XStreamingElement x = content as XStreamingElement; + if (x != null) + { + AddNode(new XElement(x)); + return; + } + object[] o = content as object[]; + if (o != null) + { + foreach (object obj in o) Add(obj); + return; + } + IEnumerable e = content as IEnumerable; + if (e != null) + { + foreach (object obj in e) Add(obj); + return; + } + AddString(GetStringValue(content)); + } + + /// + /// Adds the specified content as a child (or children) of this XContainer. + /// + /// + /// A parameter list of content objects. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void Add(params object[] content) + { + Add((object)content); + } + + /// + /// Adds the specified content as the first child (or children) of this document or element. The + /// content can be simple content, a collection of content objects, a parameter + /// list of content objects, or null. + /// + /// + /// Adds the specified content as the first child (or children) of this document or element. + /// + /// + /// A content object containing simple content or a collection of content objects + /// to be added. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void AddFirst(object content) + { + new Inserter(this, null).Add(content); + } + + /// + /// Adds the specified content as the first children of this document or element. + /// + /// + /// A parameter list of content objects. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + /// + /// Thrown if the parent is null. + /// + public void AddFirst(params object[] content) + { + AddFirst((object)content); + } + + /// + /// Creates an used to add either nodes + /// or attributes to the . The later option + /// applies only for . + /// + /// An + public XmlWriter CreateWriter() + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.ConformanceLevel = this is XDocument ? ConformanceLevel.Document : ConformanceLevel.Fragment; + return XmlWriter.Create(new XNodeBuilder(this), settings); + } + + /// + /// Get descendant elements plus leaf nodes contained in an + /// + /// IEnumerable over all descendants + public IEnumerable DescendantNodes() + { + return GetDescendantNodes(false); + } + + /// + /// Returns the descendant s of this . Note this method will + /// not return itself in the resulting IEnumerable. See if you + /// need to include the current in the results. + /// + /// + /// + /// An IEnumerable of with all of the descendants below this in the XML tree. + /// + public IEnumerable Descendants() + { + return GetDescendants(null, false); + } + + /// + /// Returns the Descendant s with the passed in as an IEnumerable + /// of XElement. + /// + /// The to match against descendant s. + /// An of + public IEnumerable Descendants(XName name) + { + return name != null ? GetDescendants(name, false) : XElement.EmptySequence; + } + + /// + /// Returns the child element with this or null if there is no child element + /// with a matching . + /// + /// + /// + /// The to match against this s child elements. + /// + /// + /// An child that matches the passed in, or null. + /// + public XElement Element(XName name) + { + XNode n = content as XNode; + if (n != null) + { + do + { + n = n.next; + XElement e = n as XElement; + if (e != null && e.name == name) return e; + } while (n != content); + } + return null; + } + + /// + /// Returns the child s of this . + /// + /// + /// Returns all of the child elements of this . + /// + /// + /// An over all of this 's child s. + /// + public IEnumerable Elements() + { + return GetElements(null); + } + + /// + /// Returns the child elements of this that match the passed in. + /// + /// + /// The to match against the children of this . + /// + /// + /// An of children of this that have + /// a matching . + /// + public IEnumerable Elements(XName name) + { + return name != null ? GetElements(name) : XElement.EmptySequence; + } + + /// + /// Returns the content of this . Note that the content does not + /// include s. + /// + /// + /// + /// Returns the content of this as an of . Note + /// that the content does not include s. + /// + /// + /// The contents of this + public IEnumerable Nodes() + { + XNode n = LastNode; + if (n != null) + { + do + { + n = n.next; + yield return n; + } while (n.parent == this && n != content); + } + } + + /// + /// Removes the nodes from this . Note this + /// methods does not remove attributes. See . + /// + /// + public void RemoveNodes() + { + if (SkipNotify()) + { + RemoveNodesSkipNotify(); + return; + } + while (content != null) + { + string s = content as string; + if (s != null) + { + if (s.Length > 0) + { + ConvertTextToNode(); + } + else + { + if (this is XElement) + { + // Change in the serialization of an empty element: + // from start/end tag pair to empty tag + NotifyChanging(this, XObjectChangeEventArgs.Value); + if ((object)s != (object)content) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + content = null; + NotifyChanged(this, XObjectChangeEventArgs.Value); + } + else + { + content = null; + } + } + } + XNode last = content as XNode; + if (last != null) + { + XNode n = last.next; + NotifyChanging(n, XObjectChangeEventArgs.Remove); + if (last != content || n != last.next) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + if (n != last) + { + last.next = n.next; + } + else + { + content = null; + } + n.parent = null; + n.next = null; + NotifyChanged(n, XObjectChangeEventArgs.Remove); + } + } + } + + /// + /// Replaces the children nodes of this document or element with the specified content. The + /// content can be simple content, a collection of content objects, a parameter + /// list of content objects, or null. + /// + /// + /// Replaces the children nodes of this document or element with the specified content. + /// + /// + /// A content object containing simple content or a collection of content objects + /// that replace the children nodes. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void ReplaceNodes(object content) + { + content = GetContentSnapshot(content); + RemoveNodes(); + Add(content); + } + + /// + /// Replaces the children nodes of this document or element with the specified content. + /// + /// + /// A parameter list of content objects. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void ReplaceNodes(params object[] content) + { + ReplaceNodes((object)content); + } + + internal virtual void AddAttribute(XAttribute a) + { + } + + internal virtual void AddAttributeSkipNotify(XAttribute a) + { + } + + internal void AddContentSkipNotify(object content) + { + if (content == null) return; + XNode n = content as XNode; + if (n != null) + { + AddNodeSkipNotify(n); + return; + } + string s = content as string; + if (s != null) + { + AddStringSkipNotify(s); + return; + } + XAttribute a = content as XAttribute; + if (a != null) + { + AddAttributeSkipNotify(a); + return; + } + XStreamingElement x = content as XStreamingElement; + if (x != null) + { + AddNodeSkipNotify(new XElement(x)); + return; + } + object[] o = content as object[]; + if (o != null) + { + foreach (object obj in o) AddContentSkipNotify(obj); + return; + } + IEnumerable e = content as IEnumerable; + if (e != null) + { + foreach (object obj in e) AddContentSkipNotify(obj); + return; + } + AddStringSkipNotify(GetStringValue(content)); + } + + internal void AddNode(XNode n) + { + ValidateNode(n, this); + if (n.parent != null) + { + n = n.CloneNode(); + } + else + { + XNode p = this; + while (p.parent != null) p = p.parent; + if (n == p) n = n.CloneNode(); + } + ConvertTextToNode(); + AppendNode(n); + } + + internal void AddNodeSkipNotify(XNode n) + { + ValidateNode(n, this); + if (n.parent != null) + { + n = n.CloneNode(); + } + else + { + XNode p = this; + while (p.parent != null) p = p.parent; + if (n == p) n = n.CloneNode(); + } + ConvertTextToNode(); + AppendNodeSkipNotify(n); + } + + internal void AddString(string s) + { + ValidateString(s); + if (content == null) + { + if (s.Length > 0) + { + AppendNode(new XText(s)); + } + else + { + if (this is XElement) + { + // Change in the serialization of an empty element: + // from empty tag to start/end tag pair + NotifyChanging(this, XObjectChangeEventArgs.Value); + if (content != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + content = s; + NotifyChanged(this, XObjectChangeEventArgs.Value); + } + else + { + content = s; + } + } + } + else if (s.Length > 0) + { + ConvertTextToNode(); + XText tn = content as XText; + if (tn != null && !(tn is XCData)) + { + tn.Value += s; + } + else + { + AppendNode(new XText(s)); + } + } + } + + internal void AddStringSkipNotify(string s) + { + ValidateString(s); + if (content == null) + { + content = s; + } + else if (s.Length > 0) + { + if (content is string) + { + content = (string)content + s; + } + else + { + XText tn = content as XText; + if (tn != null && !(tn is XCData)) + { + tn.text += s; + } + else + { + AppendNodeSkipNotify(new XText(s)); + } + } + } + } + + internal void AppendNode(XNode n) + { + bool notify = NotifyChanging(n, XObjectChangeEventArgs.Add); + if (n.parent != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + AppendNodeSkipNotify(n); + if (notify) NotifyChanged(n, XObjectChangeEventArgs.Add); + } + + internal void AppendNodeSkipNotify(XNode n) + { + n.parent = this; + if (content == null || content is string) + { + n.next = n; + } + else + { + XNode x = (XNode)content; + n.next = x.next; + x.next = n; + } + content = n; + } + + internal override void AppendText(StringBuilder sb) + { + string s = content as string; + if (s != null) + { + sb.Append(s); + } + else + { + XNode n = (XNode)content; + if (n != null) + { + do + { + n = n.next; + n.AppendText(sb); + } while (n != content); + } + } + } + + string GetTextOnly() + { + if (content == null) return null; + string s = content as string; + if (s == null) + { + XNode n = (XNode)content; + do + { + n = n.next; + if (n.NodeType != XmlNodeType.Text) return null; + s += ((XText)n).Value; + } while (n != content); + } + return s; + } + + string CollectText(ref XNode n) + { + string s = ""; + while (n != null && n.NodeType == XmlNodeType.Text) + { + s += ((XText)n).Value; + n = n != content ? n.next : null; + } + return s; + } + + internal bool ContentsEqual(XContainer e) + { + if (content == e.content) return true; + string s = GetTextOnly(); + if (s != null) return s == e.GetTextOnly(); + XNode n1 = content as XNode; + XNode n2 = e.content as XNode; + if (n1 != null && n2 != null) + { + n1 = n1.next; + n2 = n2.next; + while (true) + { + if (CollectText(ref n1) != e.CollectText(ref n2)) break; + if (n1 == null && n2 == null) return true; + if (n1 == null || n2 == null || !n1.DeepEquals(n2)) break; + n1 = n1 != content ? n1.next : null; + n2 = n2 != e.content ? n2.next : null; + } + } + return false; + } + + internal int ContentsHashCode() + { + string s = GetTextOnly(); + if (s != null) return s.GetHashCode(); + int h = 0; + XNode n = content as XNode; + if (n != null) + { + do + { + n = n.next; + string text = CollectText(ref n); + if (text.Length > 0) + { + h ^= text.GetHashCode(); + } + if (n == null) break; + h ^= n.GetDeepHashCode(); + } while (n != content); + } + return h; + } + + internal void ConvertTextToNode() + { + string s = content as string; + if (s != null && s.Length > 0) + { + XText t = new XText(s); + t.parent = this; + t.next = t; + content = t; + } + } + + internal IEnumerable GetDescendantNodes(bool self) + { + if (self) yield return this; + XNode n = this; + while (true) + { + XContainer c = n as XContainer; + XNode first; + if (c != null && (first = c.FirstNode) != null) + { + n = first; + } + else + { + while (n != null && n != this && n == n.parent.content) n = n.parent; + if (n == null || n == this) break; + n = n.next; + } + yield return n; + } + } + + internal IEnumerable GetDescendants(XName name, bool self) + { + if (self) + { + XElement e = (XElement)this; + if (name == null || e.name == name) yield return e; + } + XNode n = this; + XContainer c = this; + while (true) + { + if (c != null && c.content is XNode) + { + n = ((XNode)c.content).next; + } + else + { + while (n != this && n == n.parent.content) n = n.parent; + if (n == this) break; + n = n.next; + } + XElement e = n as XElement; + if (e != null && (name == null || e.name == name)) yield return e; + c = e; + } + } + + IEnumerable GetElements(XName name) + { + XNode n = content as XNode; + if (n != null) + { + do + { + n = n.next; + XElement e = n as XElement; + if (e != null && (name == null || e.name == name)) yield return e; + } while (n.parent == this && n != content); + } + } + + internal static string GetStringValue(object value) + { + string s; + if (value is string) + { + s = (string)value; + } + else if (value is double) + { + s = XmlConvert.ToString((double)value); + } + else if (value is float) + { + s = XmlConvert.ToString((float)value); + } + else if (value is decimal) + { + s = XmlConvert.ToString((decimal)value); + } + else if (value is bool) + { + s = XmlConvert.ToString((bool)value); + } + else if (value is DateTime) + { + s = ((DateTime)value).ToString("o"); // Round-trip date/time pattern. + } + else if (value is DateTimeOffset) + { + s = XmlConvert.ToString((DateTimeOffset)value); + } + else if (value is TimeSpan) + { + s = XmlConvert.ToString((TimeSpan)value); + } + else if (value is XObject) + { + throw new ArgumentException(SR.Argument_XObjectValue); + } + else + { + s = value.ToString(); + } + if (s == null) throw new ArgumentException(SR.Argument_ConvertToString); + return s; + } + + internal void ReadContentFrom(XmlReader r) + { + if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive); + XContainer c = this; + NamespaceCache eCache = new NamespaceCache(); + NamespaceCache aCache = new NamespaceCache(); + do + { + switch (r.NodeType) + { + case XmlNodeType.Element: + XElement e = new XElement(eCache.Get(r.NamespaceURI).GetName(r.LocalName)); + if (r.MoveToFirstAttribute()) + { + do + { + e.AppendAttributeSkipNotify(new XAttribute(aCache.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName), r.Value)); + } while (r.MoveToNextAttribute()); + r.MoveToElement(); + } + c.AddNodeSkipNotify(e); + if (!r.IsEmptyElement) + { + c = e; + } + break; + case XmlNodeType.EndElement: + if (c.content == null) + { + c.content = string.Empty; + } + if (c == this) return; + c = c.parent; + break; + case XmlNodeType.Text: + case XmlNodeType.SignificantWhitespace: + case XmlNodeType.Whitespace: + c.AddStringSkipNotify(r.Value); + break; + case XmlNodeType.CDATA: + c.AddNodeSkipNotify(new XCData(r.Value)); + break; + case XmlNodeType.Comment: + c.AddNodeSkipNotify(new XComment(r.Value)); + break; + case XmlNodeType.ProcessingInstruction: + c.AddNodeSkipNotify(new XProcessingInstruction(r.Name, r.Value)); + break; + case XmlNodeType.DocumentType: + c.AddNodeSkipNotify(new XDocumentType(r.LocalName, r.GetAttribute("PUBLIC"), r.GetAttribute("SYSTEM"), r.Value)); + break; + case XmlNodeType.EntityReference: + if (!r.CanResolveEntity) throw new InvalidOperationException(SR.InvalidOperation_UnresolvedEntityReference); + r.ResolveEntity(); + break; + case XmlNodeType.EndEntity: + break; + default: + throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, r.NodeType)); + } + } while (r.Read()); + } + + internal void ReadContentFrom(XmlReader r, LoadOptions o) + { + if ((o & (LoadOptions.SetBaseUri | LoadOptions.SetLineInfo)) == 0) + { + ReadContentFrom(r); + return; + } + if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive); + XContainer c = this; + XNode n = null; + NamespaceCache eCache = new NamespaceCache(); + NamespaceCache aCache = new NamespaceCache(); + string baseUri = (o & LoadOptions.SetBaseUri) != 0 ? r.BaseURI : null; + IXmlLineInfo li = (o & LoadOptions.SetLineInfo) != 0 ? r as IXmlLineInfo : null; + do + { + string uri = r.BaseURI; + switch (r.NodeType) + { + case XmlNodeType.Element: + { + XElement e = new XElement(eCache.Get(r.NamespaceURI).GetName(r.LocalName)); + if (baseUri != null && baseUri != uri) + { + e.SetBaseUri(uri); + } + if (li != null && li.HasLineInfo()) + { + e.SetLineInfo(li.LineNumber, li.LinePosition); + } + if (r.MoveToFirstAttribute()) + { + do + { + XAttribute a = new XAttribute(aCache.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName), r.Value); + if (li != null && li.HasLineInfo()) + { + a.SetLineInfo(li.LineNumber, li.LinePosition); + } + e.AppendAttributeSkipNotify(a); + } while (r.MoveToNextAttribute()); + r.MoveToElement(); + } + c.AddNodeSkipNotify(e); + if (!r.IsEmptyElement) + { + c = e; + if (baseUri != null) + { + baseUri = uri; + } + } + break; + } + case XmlNodeType.EndElement: + { + if (c.content == null) + { + c.content = string.Empty; + } + // Store the line info of the end element tag. + // Note that since we've got EndElement the current container must be an XElement + XElement e = c as XElement; + Debug.Assert(e != null, "EndElement recieved but the current container is not an element."); + if (e != null && li != null && li.HasLineInfo()) + { + e.SetEndElementLineInfo(li.LineNumber, li.LinePosition); + } + if (c == this) return; + if (baseUri != null && c.HasBaseUri) + { + baseUri = c.parent.BaseUri; + } + c = c.parent; + break; + } + case XmlNodeType.Text: + case XmlNodeType.SignificantWhitespace: + case XmlNodeType.Whitespace: + if ((baseUri != null && baseUri != uri) || + (li != null && li.HasLineInfo())) + { + n = new XText(r.Value); + } + else + { + c.AddStringSkipNotify(r.Value); + } + break; + case XmlNodeType.CDATA: + n = new XCData(r.Value); + break; + case XmlNodeType.Comment: + n = new XComment(r.Value); + break; + case XmlNodeType.ProcessingInstruction: + n = new XProcessingInstruction(r.Name, r.Value); + break; + case XmlNodeType.DocumentType: + n = new XDocumentType(r.LocalName, r.GetAttribute("PUBLIC"), r.GetAttribute("SYSTEM"), r.Value); + break; + case XmlNodeType.EntityReference: + if (!r.CanResolveEntity) throw new InvalidOperationException(SR.InvalidOperation_UnresolvedEntityReference); + r.ResolveEntity(); + break; + case XmlNodeType.EndEntity: + break; + default: + throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, r.NodeType)); + } + if (n != null) + { + if (baseUri != null && baseUri != uri) + { + n.SetBaseUri(uri); + } + if (li != null && li.HasLineInfo()) + { + n.SetLineInfo(li.LineNumber, li.LinePosition); + } + c.AddNodeSkipNotify(n); + n = null; + } + } while (r.Read()); + } + + internal void RemoveNode(XNode n) + { + bool notify = NotifyChanging(n, XObjectChangeEventArgs.Remove); + if (n.parent != this) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + XNode p = (XNode)content; + while (p.next != n) p = p.next; + if (p == n) + { + content = null; + } + else + { + if (content == n) content = p; + p.next = n.next; + } + n.parent = null; + n.next = null; + if (notify) NotifyChanged(n, XObjectChangeEventArgs.Remove); + } + + void RemoveNodesSkipNotify() + { + XNode n = content as XNode; + if (n != null) + { + do + { + XNode next = n.next; + n.parent = null; + n.next = null; + n = next; + } while (n != content); + } + content = null; + } + + // Validate insertion of the given node. previous is the node after which insertion + // will occur. previous == null means at beginning, previous == this means at end. + internal virtual void ValidateNode(XNode node, XNode previous) + { + } + + internal virtual void ValidateString(string s) + { + } + + internal void WriteContentTo(XmlWriter writer) + { + if (content != null) + { + if (content is string) + { + if (this is XDocument) + { + writer.WriteWhitespace((string)content); + } + else + { + writer.WriteString((string)content); + } + } + else + { + XNode n = (XNode)content; + do + { + n = n.next; + n.WriteTo(writer); + } while (n != content); + } + } + } + + static void AddContentToList(List list, object content) + { + IEnumerable e = content is string ? null : content as IEnumerable; + if (e == null) + { + list.Add(content); + } + else + { + foreach (object obj in e) + { + if (obj != null) AddContentToList(list, obj); + } + } + } + + static internal object GetContentSnapshot(object content) + { + if (content is string || !(content is IEnumerable)) return content; + List list = new List(); + AddContentToList(list, content); + return list; + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XDeclaration.cs b/src/System.Xml.XDocument/System/Xml/Linq/XDeclaration.cs new file mode 100644 index 000000000000..5b9eb0982945 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XDeclaration.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents an XML declaration. + /// + /// + /// An XML declaration is used to declare the XML version, + /// the encoding, and whether or not the XML document is standalone. + /// + public class XDeclaration + { + string version; + string encoding; + string standalone; + + /// + /// Initilizes a new instance of the class from the + /// specified version, encoding, and standalone properties. + /// + /// + /// The version of the XML, usually "1.0". + /// + /// + /// The encoding for the XML document. + /// + /// + /// Specifies whether the XML is standalone or requires external entities + /// to be resolved. + /// + public XDeclaration(string version, string encoding, string standalone) + { + this.version = version; + this.encoding = encoding; + this.standalone = standalone; + } + + /// + /// Initializes an instance of the class + /// from another object. + /// + /// + /// The used to initialize this object. + /// + public XDeclaration(XDeclaration other) + { + if (other == null) throw new ArgumentNullException("other"); + version = other.version; + encoding = other.encoding; + standalone = other.standalone; + } + + internal XDeclaration(XmlReader r) + { + version = r.GetAttribute("version"); + encoding = r.GetAttribute("encoding"); + standalone = r.GetAttribute("standalone"); + r.Read(); + } + + /// + /// Gets or sets the encoding for this document. + /// + public string Encoding + { + get { return encoding; } + set { encoding = value; } + } + + /// + /// Gets or sets the standalone property for this document. + /// + /// + /// The valid values for standalone are "yes" or "no". + /// + public string Standalone + { + get { return standalone; } + set { standalone = value; } + } + + /// + /// Gets or sets the version property for this document. + /// + /// + /// The value is usually "1.0". + /// + public string Version + { + get { return version; } + set { version = value; } + } + + /// + /// Provides a formatted string. + /// + /// A formatted XML string. + public override string ToString() + { + StringBuilder sb = new StringBuilder(""); + return sb.ToString(); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XDocument.cs b/src/System.Xml.XDocument/System/Xml/Linq/XDocument.cs new file mode 100644 index 000000000000..a1dbbeb655bb --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XDocument.cs @@ -0,0 +1,665 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents an XML document. + /// + /// + /// An can contain: + /// + /// + /// A Document Type Declaration (DTD), see + /// + /// One root element. + /// Zero or more objects. + /// Zero or more objects. + /// + /// + public class XDocument : XContainer + { + XDeclaration declaration; + + /// + /// Initializes a new instance of the class. + /// Overloaded constructors are provided for creating a new empty + /// , creating an with + /// a parameter list of initial content, and as a copy of another + /// object. + /// + /// + /// Initializes a new instance of the class. + /// + public XDocument() + { + } + + /// + /// Initializes a new instance of the class with the specified content. + /// + /// + /// A parameter list of content objects to add to this document. + /// + /// + /// Valid content includes: + /// + /// Zero or one objects + /// Zero or one elements + /// Zero or more comments + /// Zero or more processing instructions + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public XDocument(params object[] content) + : this() + { + AddContentSkipNotify(content); + } + + /// + /// Initializes a new instance of the class + /// with the specifed and content. + /// + /// + /// The XML declaration for the document. + /// + /// + /// The contents of the document. + /// + /// + /// Valid content includes: + /// + /// Zero or one objects + /// Zero or one elements + /// Zero or more comments + /// Zero or more processing instructions + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public XDocument(XDeclaration declaration, params object[] content) + : this(content) + { + this.declaration = declaration; + } + + /// + /// Initializes a new instance of the class from an + /// existing XDocument object. + /// + /// + /// The object that will be copied. + /// + public XDocument(XDocument other) + : base(other) + { + if (other.declaration != null) + { + declaration = new XDeclaration(other.declaration); + } + } + + /// + /// Gets the XML declaration for this document. + /// + public XDeclaration Declaration + { + get { return declaration; } + set { declaration = value; } + } + + /// + /// Gets the Document Type Definition (DTD) for this document. + /// + public XDocumentType DocumentType + { + get + { + return GetFirstNode(); + } + } + + /// + /// Gets the node type for this node. + /// + /// + /// This property will always return XmlNodeType.Document. + /// + public override XmlNodeType NodeType + { + get + { + return XmlNodeType.Document; + } + } + + /// + /// Gets the root element of the XML Tree for this document. + /// + public XElement Root + { + get + { + return GetFirstNode(); + } + } + + /// + /// The Load method provides multiple strategies for creating a new + /// and initializing it from a data source containing + /// raw XML. Load from a file (passing in a URI to the file), a + /// , a , or an + /// . Note: Use + /// to create an from a string containing XML. + /// + /// + /// + /// Create a new based on the contents of the file + /// referenced by the URI parameter passed in. Note: Use + /// to create an from + /// a string containing XML. + /// + /// + /// + /// + /// This method uses the method to create + /// an to read the raw XML into the underlying + /// XML tree. + /// + /// + /// A URI string referencing the file to load into a new . + /// + /// + /// An initialized with the contents of the file referenced + /// in the passed in uri parameter. + /// + [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Back-compat with System.Xml.")] + public static XDocument Load(string uri) + { + return Load(uri, LoadOptions.None); + } + + /// + /// Create a new based on the contents of the file + /// referenced by the URI parameter passed in. Optionally, whitespace can be preserved. + /// + /// + /// + /// This method uses the method to create + /// an to read the raw XML into an underlying + /// XML tree. If LoadOptions.PreserveWhitespace is enabled then + /// the property + /// is set to false. + /// + /// + /// A string representing the URI of the file to be loaded into a new . + /// + /// + /// A set of . + /// + /// + /// An initialized with the contents of the file referenced + /// in the passed uri parameter. If LoadOptions.PreserveWhitespace is enabled then + /// all whitespace will be preserved. + /// + [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Back-compat with System.Xml.")] + public static XDocument Load(string uri, LoadOptions options) + { + XmlReaderSettings rs = GetXmlReaderSettings(options); + using (XmlReader r = XmlReader.Create(uri, rs)) + { + return Load(r, options); + } + } + + /// + /// Create a new and initialize its underlying XML tree using + /// the passed parameter. + /// + /// + /// A containing the raw XML to read into the newly + /// created . + /// + /// + /// A new containing the contents of the passed in + /// . + /// + public static XDocument Load(Stream stream) + { + return Load(stream, LoadOptions.None); + } + + /// + /// Create a new and initialize its underlying XML tree using + /// the passed parameter. Optionally whitespace handling + /// can be preserved. + /// + /// + /// If LoadOptions.PreserveWhitespace is enabled then + /// the underlying property + /// is set to false. + /// + /// + /// A containing the raw XML to read into the newly + /// created . + /// + /// + /// A set of . + /// + /// + /// A new containing the contents of the passed in + /// . + /// + public static XDocument Load(Stream stream, LoadOptions options) + { + XmlReaderSettings rs = GetXmlReaderSettings(options); + using (XmlReader r = XmlReader.Create(stream, rs)) + { + return Load(r, options); + } + } + + /// + /// Create a new and initialize its underlying XML tree using + /// the passed parameter. + /// + /// + /// A containing the raw XML to read into the newly + /// created . + /// + /// + /// A new containing the contents of the passed in + /// . + /// + public static XDocument Load(TextReader textReader) + { + return Load(textReader, LoadOptions.None); + } + + /// + /// Create a new and initialize its underlying XML tree using + /// the passed parameter. Optionally whitespace handling + /// can be preserved. + /// + /// + /// If LoadOptions.PreserveWhitespace is enabled then + /// the property + /// is set to false. + /// + /// + /// A containing the raw XML to read into the newly + /// created . + /// + /// + /// A set of . + /// + /// + /// A new containing the contents of the passed in + /// . + /// + public static XDocument Load(TextReader textReader, LoadOptions options) + { + XmlReaderSettings rs = GetXmlReaderSettings(options); + using (XmlReader r = XmlReader.Create(textReader, rs)) + { + return Load(r, options); + } + } + + /// + /// Create a new containing the contents of the + /// passed in . + /// + /// + /// An containing the XML to be read into the new + /// . + /// + /// + /// A new containing the contents of the passed + /// in . + /// + public static XDocument Load(XmlReader reader) + { + return Load(reader, LoadOptions.None); + } + + /// + /// Create a new containing the contents of the + /// passed in . + /// + /// + /// An containing the XML to be read into the new + /// . + /// + /// + /// A set of . + /// + /// + /// A new containing the contents of the passed + /// in . + /// + public static XDocument Load(XmlReader reader, LoadOptions options) + { + if (reader == null) throw new ArgumentNullException("reader"); + if (reader.ReadState == ReadState.Initial) reader.Read(); + XDocument d = new XDocument(); + if ((options & LoadOptions.SetBaseUri) != 0) + { + string baseUri = reader.BaseURI; + if (baseUri != null && baseUri.Length != 0) + { + d.SetBaseUri(baseUri); + } + } + if ((options & LoadOptions.SetLineInfo) != 0) + { + IXmlLineInfo li = reader as IXmlLineInfo; + if (li != null && li.HasLineInfo()) + { + d.SetLineInfo(li.LineNumber, li.LinePosition); + } + } + if (reader.NodeType == XmlNodeType.XmlDeclaration) + { + d.Declaration = new XDeclaration(reader); + } + d.ReadContentFrom(reader, options); + if (!reader.EOF) throw new InvalidOperationException(SR.InvalidOperation_ExpectedEndOfFile); + if (d.Root == null) throw new InvalidOperationException(SR.InvalidOperation_MissingRoot); + return d; + } + + /// + /// Create a new from a string containing + /// XML. Optionally whitespace can be preserved. + /// + /// + /// Create a new from a string containing + /// XML. + /// + /// + /// A string containing XML. + /// + /// + /// An containing an XML tree initialized from the + /// passed in XML string. + /// + public static XDocument Parse(string text) + { + return Parse(text, LoadOptions.None); + } + + /// + /// Create a new from a string containing + /// XML. Optionally whitespace can be preserved. + /// + /// + /// This method uses method passing it a StringReader + /// constructed from the passed in XML String. If LoadOptions.PreserveWhitespace + /// is enabled then is + /// set to false. See + /// for more information on whitespace handling. + /// + /// + /// A string containing XML. + /// + /// + /// A set of . + /// + /// + /// An containing an XML tree initialized from the + /// passed in XML string. + /// + public static XDocument Parse(string text, LoadOptions options) + { + using (StringReader sr = new StringReader(text)) + { + XmlReaderSettings rs = GetXmlReaderSettings(options); + using (XmlReader r = XmlReader.Create(sr, rs)) + { + return Load(r, options); + } + } + } + + /// + /// Output this to the passed in . + /// + /// + /// The format will be indented by default. If you want + /// no indenting then use the SaveOptions version of Save (see + /// ) enabling + /// SaveOptions.DisableFormatting + /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations. + /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. + /// + /// + /// The to output this to. + /// + public void Save(Stream stream) + { + Save(stream, GetSaveOptionsFromAnnotations()); + } + + /// + /// Output this to a . + /// + /// + /// The to output the XML to. + /// + /// + /// If SaveOptions.DisableFormatting is enabled the output is not indented. + /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. + /// + public void Save(Stream stream, SaveOptions options) + { + XmlWriterSettings ws = GetXmlWriterSettings(options); + if (declaration != null && !string.IsNullOrEmpty(declaration.Encoding)) + { + try + { + ws.Encoding = Encoding.GetEncoding(declaration.Encoding); + } + catch (ArgumentException) + { + } + } + using (XmlWriter w = XmlWriter.Create(stream, ws)) + { + Save(w); + } + } + + /// + /// Output this to the passed in . + /// + /// + /// The format will be indented by default. If you want + /// no indenting then use the SaveOptions version of Save (see + /// ) enabling + /// SaveOptions.DisableFormatting + /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations. + /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. + /// + /// + /// The to output this to. + /// + public void Save(TextWriter textWriter) + { + Save(textWriter, GetSaveOptionsFromAnnotations()); + } + + /// + /// Output this to a . + /// + /// + /// The to output the XML to. + /// + /// + /// If SaveOptions.DisableFormatting is enabled the output is not indented. + /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. + /// + public void Save(TextWriter textWriter, SaveOptions options) + { + XmlWriterSettings ws = GetXmlWriterSettings(options); + using (XmlWriter w = XmlWriter.Create(textWriter, ws)) + { + Save(w); + } + } + + /// + /// Output this to an . + /// + /// + /// The to output the XML to. + /// + public void Save(XmlWriter writer) + { + WriteTo(writer); + } + + + /// + /// Output this 's underlying XML tree to the + /// passed in . + /// + /// + /// + /// The to output the content of this + /// . + /// + public override void WriteTo(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + if (declaration != null && declaration.Standalone == "yes") + { + writer.WriteStartDocument(true); + } + else if (declaration != null && declaration.Standalone == "no") + { + writer.WriteStartDocument(false); + } + else + { + writer.WriteStartDocument(); + } + WriteContentTo(writer); + writer.WriteEndDocument(); + } + + internal override void AddAttribute(XAttribute a) + { + throw new ArgumentException(SR.Argument_AddAttribute); + } + + internal override void AddAttributeSkipNotify(XAttribute a) + { + throw new ArgumentException(SR.Argument_AddAttribute); + } + + internal override XNode CloneNode() + { + return new XDocument(this); + } + + internal override bool DeepEquals(XNode node) + { + XDocument other = node as XDocument; + return other != null && ContentsEqual(other); + } + + internal override int GetDeepHashCode() + { + return ContentsHashCode(); + } + + T GetFirstNode() where T : XNode + { + XNode n = content as XNode; + if (n != null) + { + do + { + n = n.next; + T e = n as T; + if (e != null) return e; + } while (n != content); + } + return null; + } + + internal static bool IsWhitespace(string s) + { + foreach (char ch in s) + { + if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') return false; + } + return true; + } + + internal override void ValidateNode(XNode node, XNode previous) + { + switch (node.NodeType) + { + case XmlNodeType.Text: + ValidateString(((XText)node).Value); + break; + case XmlNodeType.Element: + ValidateDocument(previous, XmlNodeType.DocumentType, XmlNodeType.None); + break; + case XmlNodeType.DocumentType: + ValidateDocument(previous, XmlNodeType.None, XmlNodeType.Element); + break; + case XmlNodeType.CDATA: + throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.CDATA)); + case XmlNodeType.Document: + throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.Document)); + } + } + + void ValidateDocument(XNode previous, XmlNodeType allowBefore, XmlNodeType allowAfter) + { + XNode n = content as XNode; + if (n != null) + { + if (previous == null) allowBefore = allowAfter; + do + { + n = n.next; + XmlNodeType nt = n.NodeType; + if (nt == XmlNodeType.Element || nt == XmlNodeType.DocumentType) + { + if (nt != allowBefore) throw new InvalidOperationException(SR.InvalidOperation_DocumentStructure); + allowBefore = XmlNodeType.None; + } + if (n == previous) allowBefore = allowAfter; + } while (n != content); + } + } + + internal override void ValidateString(string s) + { + if (!IsWhitespace(s)) throw new ArgumentException(SR.Argument_AddNonWhitespace); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XDocumentType.cs b/src/System.Xml.XDocument/System/Xml/Linq/XDocumentType.cs new file mode 100644 index 000000000000..a41df48298c4 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XDocumentType.cs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents an XML Document Type Definition (DTD). + /// + public class XDocumentType : XNode + { + string name; + string publicId; + string systemId; + string internalSubset; + + /// + /// Initializes an empty instance of the class. + /// + public XDocumentType(string name, string publicId, string systemId, string internalSubset) + { + this.name = XmlConvert.VerifyName(name); + this.publicId = publicId; + this.systemId = systemId; + this.internalSubset = internalSubset; + } + + /// + /// Initializes an instance of the XDocumentType class + /// from another XDocumentType object. + /// + /// object to copy from. + public XDocumentType(XDocumentType other) + { + if (other == null) throw new ArgumentNullException("other"); + this.name = other.name; + this.publicId = other.publicId; + this.systemId = other.systemId; + this.internalSubset = other.internalSubset; + } + + internal XDocumentType(XmlReader r) + { + name = r.Name; + publicId = r.GetAttribute("PUBLIC"); + systemId = r.GetAttribute("SYSTEM"); + internalSubset = r.Value; + r.Read(); + } + + /// + /// Gets or sets the internal subset for this Document Type Definition (DTD). + /// + public string InternalSubset + { + get + { + return internalSubset; + } + set + { + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); + internalSubset = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); + } + } + + /// + /// Gets or sets the name for this Document Type Definition (DTD). + /// + public string Name + { + get + { + return name; + } + set + { + value = XmlConvert.VerifyName(value); + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Name); + name = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Name); + } + } + + /// + /// Gets the node type for this node. + /// + /// + /// This property will always return XmlNodeType.DocumentType. + /// + public override XmlNodeType NodeType + { + get + { + return XmlNodeType.DocumentType; + } + } + + /// + /// Gets or sets the public identifier for this Document Type Definition (DTD). + /// + public string PublicId + { + get + { + return publicId; + } + set + { + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); + publicId = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); + } + } + + /// + /// Gets or sets the system identifier for this Document Type Definition (DTD). + /// + public string SystemId + { + get + { + return systemId; + } + set + { + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); + systemId = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); + } + } + + /// + /// Write this to the passed in . + /// + /// + /// The to write this to. + /// + public override void WriteTo(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + writer.WriteDocType(name, publicId, systemId, internalSubset); + } + + internal override XNode CloneNode() + { + return new XDocumentType(this); + } + + internal override bool DeepEquals(XNode node) + { + XDocumentType other = node as XDocumentType; + return other != null && name == other.name && publicId == other.publicId && + systemId == other.SystemId && internalSubset == other.internalSubset; + } + + internal override int GetDeepHashCode() + { + return name.GetHashCode() ^ + (publicId != null ? publicId.GetHashCode() : 0) ^ + (systemId != null ? systemId.GetHashCode() : 0) ^ + (internalSubset != null ? internalSubset.GetHashCode() : 0); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XElement.cs b/src/System.Xml.XDocument/System/Xml/Linq/XElement.cs new file mode 100644 index 000000000000..fd31e826dcfb --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XElement.cs @@ -0,0 +1,1803 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents an XML element. + /// + /// + /// An element has an , optionally one or more attributes, + /// and can optionally contain content (see . + /// An can contain the following types of content: + /// + /// string (Text content) + /// + /// + /// + /// + /// + public class XElement : XContainer + { + static IEnumerable emptySequence; + + /// + /// Gets an empty collection of elements. + /// + public static IEnumerable EmptySequence + { + get + { + if (emptySequence == null) emptySequence = new XElement[0]; + return emptySequence; + } + } + + internal XName name; + internal XAttribute lastAttr; + + /// + /// Initializes a new instance of the XElement class with the specified name. + /// + /// + /// The name of the element. + /// + public XElement(XName name) + { + if (name == null) throw new ArgumentNullException("name"); + this.name = name; + } + + /// + /// Initializes a new instance of the XElement class with the specified name and content. + /// + /// + /// The element name. + /// + /// The initial contents of the element. + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public XElement(XName name, object content) + : this(name) + { + AddContentSkipNotify(content); + } + + /// + /// Initializes a new instance of the XElement class with the specified name and content. + /// + /// + /// The element name. + /// + /// + /// The initial content of the element. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public XElement(XName name, params object[] content) : this(name, (object)content) { } + + /// + /// Initializes a new instance of the XElement class from another XElement object. + /// + /// + /// Another element that will be copied to this element. + /// + /// + /// This constructor makes a deep copy from one element to another. + /// + public XElement(XElement other) + : base(other) + { + this.name = other.name; + XAttribute a = other.lastAttr; + if (a != null) + { + do + { + a = a.next; + AppendAttributeSkipNotify(new XAttribute(a)); + } while (a != other.lastAttr); + } + } + + /// + /// Initializes an XElement object from an object. + /// + /// + /// The object whose value will be used + /// to initialise the new element. + /// + public XElement(XStreamingElement other) + { + if (other == null) throw new ArgumentNullException("other"); + name = other.name; + AddContentSkipNotify(other.content); + } + + internal XElement() + : this("default") + { + } + + internal XElement(XmlReader r) + : this(r, LoadOptions.None) + { + } + + internal XElement(XmlReader r, LoadOptions o) + { + ReadElementFrom(r, o); + } + + /// + /// Gets the first attribute of an element. + /// + public XAttribute FirstAttribute + { + get { return lastAttr != null ? lastAttr.next : null; } + } + + /// + /// Gets a value indicating whether the element has at least one attribute. + /// + public bool HasAttributes + { + get { return lastAttr != null; } + } + + /// + /// Gets a value indicating whether the element has at least one child element. + /// + public bool HasElements + { + get + { + XNode n = content as XNode; + if (n != null) + { + do + { + if (n is XElement) return true; + n = n.next; + } while (n != content); + } + return false; + } + } + + /// + /// Gets a value indicating whether the element contains no content. + /// + public bool IsEmpty + { + get { return content == null; } + } + + /// + /// Gets the last attribute of an element. + /// + public XAttribute LastAttribute + { + get { return lastAttr; } + } + + /// + /// Gets the name of this element. + /// + public XName Name + { + get + { + return name; + } + set + { + if (value == null) throw new ArgumentNullException("value"); + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Name); + name = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Name); + } + } + + /// + /// Gets the node type for this node. + /// + /// + /// This property will always return XmlNodeType.Text. + /// + public override XmlNodeType NodeType + { + get + { + return XmlNodeType.Element; + } + } + + /// + /// Gets the text contents of this element. + /// + /// + /// If there is text content interspersed with nodes (mixed content) then the text content + /// will be concatenated and returned. + /// + public string Value + { + get + { + if (content == null) return string.Empty; + string s = content as string; + if (s != null) return s; + StringBuilder sb = new StringBuilder(); + AppendText(sb); + return sb.ToString(); + } + set + { + if (value == null) throw new ArgumentNullException("value"); + RemoveNodes(); + Add(value); + } + } + + /// + /// Returns this and all of it's ancestors up + /// to the root node. Optionally an can be passed + /// in to target a specific ancestor(s). + /// + /// + /// + /// Returns this and all of it's ancestors up to + /// the root node. + /// + /// + /// + /// An of containing all of + /// this 's ancestors up to the root node (including + /// this . + /// + public IEnumerable AncestorsAndSelf() + { + return GetAncestors(null, true); + } + + /// + /// Returns the ancestor(s) of this with the matching + /// . If this 's + /// matches the passed in then it will be invluded in the + /// resulting or . + /// + /// + /// + /// The of the target ancestor. + /// + /// + /// An of containing the + /// ancestors of this with a matching . + /// + public IEnumerable AncestorsAndSelf(XName name) + { + return name != null ? GetAncestors(name, true) : XElement.EmptySequence; + } + + /// + /// Returns the associated with this that has this + /// . + /// + /// + /// The of the to get. + /// + /// + /// The with the passed in. If there is no + /// with this then null is returned. + /// + public XAttribute Attribute(XName name) + { + XAttribute a = lastAttr; + if (a != null) + { + do + { + a = a.next; + if (a.name == name) return a; + } while (a != lastAttr); + } + return null; + } + + /// + /// Returns the associated with this . Optionally + /// an can be given to target a specific (s). + /// + /// + /// Returns all of the s associated with this . + /// + /// + /// + /// An of containing all of the s + /// associated with this . + /// + public IEnumerable Attributes() + { + return GetAttributes(null); + } + + /// + /// Returns the (s) associated with this that has the passed + /// in . + /// + /// + /// + /// The of the targeted . + /// + /// + /// The (s) with the matching + /// + public IEnumerable Attributes(XName name) + { + return name != null ? GetAttributes(name) : XAttribute.EmptySequence; + } + + /// + /// Get the self and descendant nodes for an + /// + /// + public IEnumerable DescendantNodesAndSelf() + { + return GetDescendantNodes(true); + } + + /// + /// Returns this and all of it's descendants. Overloads allow + /// specification of a type of descendant to return, or a specific + /// of a descendant to match. + /// + /// + /// Returns this and all of it's descendant s + /// as an of . + /// + /// + /// + /// An of containing this + /// and all of it's descendants. + /// + public IEnumerable DescendantsAndSelf() + { + return GetDescendants(null, true); + } + + /// + /// Returns the descendants of this that have a matching + /// to the one passed in, including, potentially, this . + /// + /// + /// + /// The of the descendant that is being targeted. + /// + /// + /// An of containing all of the descendant + /// s that have this . + /// + public IEnumerable DescendantsAndSelf(XName name) + { + return name != null ? GetDescendants(name, true) : XElement.EmptySequence; + } + + /// + /// Returns the default of an + /// + public XNamespace GetDefaultNamespace() + { + string namespaceName = GetNamespaceOfPrefixInScope("xmlns", null); + return namespaceName != null ? XNamespace.Get(namespaceName) : XNamespace.None; + } + + /// + /// Get the namespace associated with a particular prefix for this + /// in its document context. + /// + /// The namespace prefix to look up + /// An for the namespace bound to the prefix + public XNamespace GetNamespaceOfPrefix(string prefix) + { + if (prefix == null) throw new ArgumentNullException("prefix"); + if (prefix.Length == 0) throw new ArgumentException(SR.Format(SR.Argument_InvalidPrefix, prefix)); + if (prefix == "xmlns") return XNamespace.Xmlns; + string namespaceName = GetNamespaceOfPrefixInScope(prefix, null); + if (namespaceName != null) return XNamespace.Get(namespaceName); + if (prefix == "xml") return XNamespace.Xml; + return null; + } + + /// + /// Get the prefix associated with a namespace for an element in its context. + /// + /// The for which to get a prefix + /// The namespace prefix string + public string GetPrefixOfNamespace(XNamespace ns) + { + if (ns == null) throw new ArgumentNullException("ns"); + string namespaceName = ns.NamespaceName; + bool hasInScopeNamespace = false; + XElement e = this; + do + { + XAttribute a = e.lastAttr; + if (a != null) + { + bool hasLocalNamespace = false; + do + { + a = a.next; + if (a.IsNamespaceDeclaration) + { + if (a.Value == namespaceName) + { + if (a.Name.NamespaceName.Length != 0 && + (!hasInScopeNamespace || + GetNamespaceOfPrefixInScope(a.Name.LocalName, e) == null)) + { + return a.Name.LocalName; + } + } + hasLocalNamespace = true; + } + } + while (a != e.lastAttr); + hasInScopeNamespace |= hasLocalNamespace; + } + e = e.parent as XElement; + } + while (e != null); + if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) + { + if (!hasInScopeNamespace || GetNamespaceOfPrefixInScope("xml", null) == null) return "xml"; + } + else if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) + { + return "xmlns"; + } + return null; + } + + /// + /// The Load method provides multiple strategies for creating a new + /// and initializing it from a data source containing + /// raw XML. Load from a file (passing in a URI to the file), an + /// , a , or an + /// . Note: Use + /// to create an from a string containing XML. + /// + /// + /// + /// + /// Create a new based on the contents of the file + /// referenced by the URI parameter passed in. Note: Use + /// to create an from + /// a string containing XML. + /// + /// + /// + /// + /// + /// This method uses the method to create + /// an to read the raw XML into the underlying + /// XML tree. + /// + /// + /// A URI string referencing the file to load into a new . + /// + /// + /// An initialized with the contents of the file referenced + /// in the passed in uri parameter. + /// + [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "Back-compat with System.Xml.")] + public static XElement Load(string uri) + { + return Load(uri, LoadOptions.None); + } + + /// + /// Create a new based on the contents of the file + /// referenced by the URI parameter passed in. Optionally, whitespace can be preserved. + /// + /// + /// + /// + /// This method uses the method to create + /// an to read the raw XML into an underlying + /// XML tree. If LoadOptions.PreserveWhitespace is enabled then + /// the property + /// is set to false. + /// + /// + /// A string representing the URI of the file to be loaded into a new . + /// + /// + /// A set of . + /// + /// + /// An initialized with the contents of the file referenced + /// in the passed uri parameter. If LoadOptions.PreserveWhitespace is enabled then + /// significant whitespace will be preserved. + /// + [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "Back-compat with System.Xml.")] + public static XElement Load(string uri, LoadOptions options) + { + XmlReaderSettings rs = GetXmlReaderSettings(options); + using (XmlReader r = XmlReader.Create(uri, rs)) + { + return Load(r, options); + } + } + + /// + /// Create a new and initialize its underlying XML tree using + /// the passed parameter. + /// + /// + /// A containing the raw XML to read into the newly + /// created . + /// + /// + /// A new containing the contents of the passed in + /// . + /// + public static XElement Load(Stream stream) + { + return Load(stream, LoadOptions.None); + } + + /// + /// Create a new and initialize its underlying XML tree using + /// the passed parameter. Optionally whitespace handling + /// can be preserved. + /// + /// + /// If LoadOptions.PreserveWhitespace is enabled then + /// the property + /// is set to false. + /// + /// + /// A containing the raw XML to read into the newly + /// created . + /// + /// + /// A set of . + /// + /// + /// A new containing the contents of the passed in + /// . + /// + public static XElement Load(Stream stream, LoadOptions options) + { + XmlReaderSettings rs = GetXmlReaderSettings(options); + using (XmlReader r = XmlReader.Create(stream, rs)) + { + return Load(r, options); + } + } + /// + /// Create a new and initialize its underlying XML tree using + /// the passed parameter. + /// + /// + /// A containing the raw XML to read into the newly + /// created . + /// + /// + /// A new containing the contents of the passed in + /// . + /// + public static XElement Load(TextReader textReader) + { + return Load(textReader, LoadOptions.None); + } + + /// + /// Create a new and initialize its underlying XML tree using + /// the passed parameter. Optionally whitespace handling + /// can be preserved. + /// + /// + /// If LoadOptions.PreserveWhitespace is enabled then + /// the property + /// is set to false. + /// + /// + /// A containing the raw XML to read into the newly + /// created . + /// + /// + /// A set of . + /// + /// + /// A new containing the contents of the passed in + /// . + /// + public static XElement Load(TextReader textReader, LoadOptions options) + { + XmlReaderSettings rs = GetXmlReaderSettings(options); + using (XmlReader r = XmlReader.Create(textReader, rs)) + { + return Load(r, options); + } + } + + /// + /// Create a new containing the contents of the + /// passed in . + /// + /// + /// An containing the XML to be read into the new + /// . + /// + /// + /// A new containing the contents of the passed + /// in . + /// + public static XElement Load(XmlReader reader) + { + return Load(reader, LoadOptions.None); + } + + /// + /// Create a new containing the contents of the + /// passed in . + /// + /// + /// An containing the XML to be read into the new + /// . + /// + /// + /// A set of . + /// + /// + /// A new containing the contents of the passed + /// in . + /// + public static XElement Load(XmlReader reader, LoadOptions options) + { + if (reader == null) throw new ArgumentNullException("reader"); + if (reader.MoveToContent() != XmlNodeType.Element) throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ExpectedNodeType, XmlNodeType.Element, reader.NodeType)); + XElement e = new XElement(reader, options); + reader.MoveToContent(); + if (!reader.EOF) throw new InvalidOperationException(SR.InvalidOperation_ExpectedEndOfFile); + return e; + } + + /// + /// Parses a string containing XML into an . Optionally + /// whitespace can be preserved. + /// + /// + /// Parses a string containing XML into an . + /// + /// + /// The XML must contain only one root node. + /// + /// + /// A string containing the XML to parse into an . + /// + /// + /// An created from the XML string passed in. + /// + public static XElement Parse(string text) + { + return Parse(text, LoadOptions.None); + } + + /// + /// Parses a string containing XML into an and + /// optionally preserves the Whitespace. See . + /// + /// + /// + /// The XML must contain only one root node. + /// + /// If LoadOptions.PreserveWhitespace is enabled the underlying + /// ' + /// property will be set to false. + /// + /// + /// + /// + /// A string containing the XML to parse into an . + /// + /// + /// A set of . + /// + /// + /// An created from the XML string passed in. + /// + public static XElement Parse(string text, LoadOptions options) + { + using (StringReader sr = new StringReader(text)) + { + XmlReaderSettings rs = GetXmlReaderSettings(options); + using (XmlReader r = XmlReader.Create(sr, rs)) + { + return Load(r, options); + } + } + } + + /// + /// Removes content and attributes from this . + /// + /// + /// + public void RemoveAll() + { + RemoveAttributes(); + RemoveNodes(); + } + + /// + /// Removes that attributes of this . + /// + /// + /// + public void RemoveAttributes() + { + if (SkipNotify()) + { + RemoveAttributesSkipNotify(); + return; + } + while (lastAttr != null) + { + XAttribute a = lastAttr.next; + NotifyChanging(a, XObjectChangeEventArgs.Remove); + if (lastAttr == null || a != lastAttr.next) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + if (a != lastAttr) + { + lastAttr.next = a.next; + } + else + { + lastAttr = null; + } + a.parent = null; + a.next = null; + NotifyChanged(a, XObjectChangeEventArgs.Remove); + } + } + + /// + /// Replaces the child nodes and the attributes of this element with the + /// specified content. The content can be simple content, a collection of + /// content objects, a parameter list of content objects, or null. + /// + /// + /// Replaces the children nodes and the attributes of this element with the specified content. + /// + /// + /// The content that will replace the child nodes and attributes of this element. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void ReplaceAll(object content) + { + content = GetContentSnapshot(content); + RemoveAll(); + Add(content); + } + + /// + /// Replaces the children nodes and the attributes of this element with the specified content. + /// + /// + /// A parameter list of content objects. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void ReplaceAll(params object[] content) + { + ReplaceAll((object)content); + } + + /// + /// Replaces the attributes of this element with the specified content. + /// The content can be simple content, a collection of + /// content objects, a parameter list of content objects, or null. + /// + /// + /// Replaces the attributes of this element with the specified content. + /// + /// + /// The content that will replace the attributes of this element. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void ReplaceAttributes(object content) + { + content = GetContentSnapshot(content); + RemoveAttributes(); + Add(content); + } + + /// + /// Replaces the attributes of this element with the specified content. + /// + /// + /// A parameter list of content objects. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void ReplaceAttributes(params object[] content) + { + ReplaceAttributes((object)content); + } + + + /// + /// Output this to the passed in . + /// + /// + /// The format will be indented by default. If you want + /// no indenting then use the SaveOptions version of Save (see + /// ) enabling + /// SaveOptions.DisableFormatting. + /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations. + /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. + /// + /// + /// The to output this to. + /// + public void Save(Stream stream) + { + Save(stream, GetSaveOptionsFromAnnotations()); + } + + /// + /// Output this to a . + /// + /// + /// The to output the XML to. + /// + /// + /// If SaveOptions.DisableFormatting is enabled the output is not indented. + /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. + /// + public void Save(Stream stream, SaveOptions options) + { + XmlWriterSettings ws = GetXmlWriterSettings(options); + using (XmlWriter w = XmlWriter.Create(stream, ws)) + { + Save(w); + } + } + + /// + /// Output this to the passed in . + /// + /// + /// The format will be indented by default. If you want + /// no indenting then use the SaveOptions version of Save (see + /// ) enabling + /// SaveOptions.DisableFormatting. + /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations. + /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. + /// + /// + /// The to output this to. + /// + public void Save(TextWriter textWriter) + { + Save(textWriter, GetSaveOptionsFromAnnotations()); + } + + /// + /// Output this to a . + /// + /// + /// The to output the XML to. + /// + /// + /// If SaveOptions.DisableFormatting is enabled the output is not indented. + /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. + /// + public void Save(TextWriter textWriter, SaveOptions options) + { + XmlWriterSettings ws = GetXmlWriterSettings(options); + using (XmlWriter w = XmlWriter.Create(textWriter, ws)) + { + Save(w); + } + } + + /// + /// Output this to an . + /// + /// + /// The to output the XML to. + /// + public void Save(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + writer.WriteStartDocument(); + WriteTo(writer); + writer.WriteEndDocument(); + } + + /// + /// Sets the value of an attribute. The value is assigned to the attribute with the given + /// name. If no attribute with the given name exists, a new attribute is added. If the + /// value is null, the attribute with the given name, if any, is deleted. + /// + /// + /// + /// + /// + /// The name of the attribute whose value to change. + /// + /// + /// The value to assign to the attribute. The attribute is deleted if the value is null. + /// Otherwise, the value is converted to its string representation and assigned to the + /// property of the attribute. + /// + /// + /// Thrown if the value is an instance of . + /// + public void SetAttributeValue(XName name, object value) + { + XAttribute a = Attribute(name); + if (value == null) + { + if (a != null) RemoveAttribute(a); + } + else + { + if (a != null) + { + a.Value = GetStringValue(value); + } + else + { + AppendAttribute(new XAttribute(name, value)); + } + } + } + + /// + /// Sets the value of a child element. The value is assigned to the first child element + /// with the given name. If no child element with the given name exists, a new child + /// element is added. If the value is null, the first child element with the given name, + /// if any, is deleted. + /// + /// + /// + /// + /// + /// The name of the child element whose value to change. + /// + /// + /// The value to assign to the child element. The child element is deleted if the value + /// is null. Otherwise, the value is converted to its string representation and assigned + /// to the property of the child element. + /// + /// + /// Thrown if the value is an instance of . + /// + public void SetElementValue(XName name, object value) + { + XElement e = Element(name); + if (value == null) + { + if (e != null) RemoveNode(e); + } + else + { + if (e != null) + { + e.Value = GetStringValue(value); + } + else + { + AddNode(new XElement(name, GetStringValue(value))); + } + } + } + + /// + /// Sets the value of this element. + /// + /// + /// + /// + /// + /// The value to assign to this element. The value is converted to its string representation + /// and assigned to the property. + /// + /// + /// Thrown if the specified value is null. + /// + public void SetValue(object value) + { + if (value == null) throw new ArgumentNullException("value"); + Value = GetStringValue(value); + } + + /// + /// Write this to the passed in . + /// + /// + /// The to write this to. + /// + public override void WriteTo(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + new ElementWriter(writer).WriteElement(this); + } + + /// + /// Cast the value of this to a . + /// + /// + /// If the is a subtre (an + /// that has children. The concatenated string + /// value of all of the 's text and descendants + /// text is returned. + /// + /// + /// The to cast to a string. + /// + /// + /// The content of this as a . + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator string(XElement element) + { + if (element == null) return null; + return element.Value; + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the element does not contain a valid boolean value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator bool(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToBoolean(XHelper.ToLower_InvariantCulture(element.Value)); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the element does not contain a valid boolean value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator bool?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToBoolean(XHelper.ToLower_InvariantCulture(element.Value)); + } + + /// + /// Cast the value of this to an . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the element does not contain a valid integer value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator int(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToInt32(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid integer value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator int?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToInt32(element.Value); + } + + /// + /// Cast the value of this to an . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid unsigned integer value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator uint(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToUInt32(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid unsigned integer value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator uint?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToUInt32(element.Value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the element does not contain a valid long integer value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator long(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToInt64(element.Value); + } + + /// + /// Cast the value of this to a ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid long integer value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator long?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToInt64(element.Value); + } + + /// + /// Cast the value of this to an . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid unsigned long integer value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator ulong(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToUInt64(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid unsigned long integer value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator ulong?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToUInt64(element.Value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid float value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator float(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToSingle(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid float value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator float?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToSingle(element.Value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid double value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator double(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToDouble(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid double value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator double?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToDouble(element.Value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid decimal value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator decimal(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToDecimal(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid decimal value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator decimal?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToDecimal(element.Value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator DateTime(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return DateTime.Parse(element.Value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator DateTime?(XElement element) + { + if (element == null) return null; + return DateTime.Parse(element.Value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator DateTimeOffset(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToDateTimeOffset(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator DateTimeOffset?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToDateTimeOffset(element.Value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid value. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator TimeSpan(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToTimeSpan(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid value. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator TimeSpan?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToTimeSpan(element.Value); + } + + /// + /// Cast the value of this to a . + /// + /// + /// The to cast to . + /// + /// + /// The content of this as a . + /// + /// + /// Thrown if the specified element does not contain a valid guid. + /// + /// + /// Thrown if the specified element is null. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator Guid(XElement element) + { + if (element == null) throw new ArgumentNullException("element"); + return XmlConvert.ToGuid(element.Value); + } + + /// + /// Cast the value of this to an ?. + /// + /// + /// The to cast to ?. + /// + /// + /// The content of this as a ?. + /// + /// + /// Thrown if the specified element does not contain a valid guid. + /// + [CLSCompliant(false)] + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] + public static explicit operator Guid?(XElement element) + { + if (element == null) return null; + return XmlConvert.ToGuid(element.Value); + } + + internal override void AddAttribute(XAttribute a) + { + if (Attribute(a.Name) != null) throw new InvalidOperationException(SR.InvalidOperation_DuplicateAttribute); + if (a.parent != null) a = new XAttribute(a); + AppendAttribute(a); + } + + internal override void AddAttributeSkipNotify(XAttribute a) + { + if (Attribute(a.Name) != null) throw new InvalidOperationException(SR.InvalidOperation_DuplicateAttribute); + if (a.parent != null) a = new XAttribute(a); + AppendAttributeSkipNotify(a); + } + + internal void AppendAttribute(XAttribute a) + { + bool notify = NotifyChanging(a, XObjectChangeEventArgs.Add); + if (a.parent != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + AppendAttributeSkipNotify(a); + if (notify) NotifyChanged(a, XObjectChangeEventArgs.Add); + } + + internal void AppendAttributeSkipNotify(XAttribute a) + { + a.parent = this; + if (lastAttr == null) + { + a.next = a; + } + else + { + a.next = lastAttr.next; + lastAttr.next = a; + } + lastAttr = a; + } + + bool AttributesEqual(XElement e) + { + XAttribute a1 = lastAttr; + XAttribute a2 = e.lastAttr; + if (a1 != null && a2 != null) + { + do + { + a1 = a1.next; + a2 = a2.next; + if (a1.name != a2.name || a1.value != a2.value) return false; + } while (a1 != lastAttr); + return a2 == e.lastAttr; + } + return a1 == null && a2 == null; + } + + internal override XNode CloneNode() + { + return new XElement(this); + } + + internal override bool DeepEquals(XNode node) + { + XElement e = node as XElement; + return e != null && name == e.name && ContentsEqual(e) && AttributesEqual(e); + } + + IEnumerable GetAttributes(XName name) + { + XAttribute a = lastAttr; + if (a != null) + { + do + { + a = a.next; + if (name == null || a.name == name) yield return a; + } while (a.parent == this && a != lastAttr); + } + } + + string GetNamespaceOfPrefixInScope(string prefix, XElement outOfScope) + { + XElement e = this; + while (e != outOfScope) + { + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (a.IsNamespaceDeclaration && a.Name.LocalName == prefix) return a.Value; + } + while (a != e.lastAttr); + } + e = e.parent as XElement; + } + return null; + } + + internal override int GetDeepHashCode() + { + int h = name.GetHashCode(); + h ^= ContentsHashCode(); + XAttribute a = lastAttr; + if (a != null) + { + do + { + a = a.next; + h ^= a.GetDeepHashCode(); + } while (a != lastAttr); + } + return h; + } + + void ReadElementFrom(XmlReader r, LoadOptions o) + { + if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive); + name = XNamespace.Get(r.NamespaceURI).GetName(r.LocalName); + if ((o & LoadOptions.SetBaseUri) != 0) + { + string baseUri = r.BaseURI; + if (baseUri != null && baseUri.Length != 0) + { + SetBaseUri(baseUri); + } + } + IXmlLineInfo li = null; + if ((o & LoadOptions.SetLineInfo) != 0) + { + li = r as IXmlLineInfo; + if (li != null && li.HasLineInfo()) + { + SetLineInfo(li.LineNumber, li.LinePosition); + } + } + if (r.MoveToFirstAttribute()) + { + do + { + XAttribute a = new XAttribute(XNamespace.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName), r.Value); + if (li != null && li.HasLineInfo()) + { + a.SetLineInfo(li.LineNumber, li.LinePosition); + } + AppendAttributeSkipNotify(a); + } while (r.MoveToNextAttribute()); + r.MoveToElement(); + } + if (!r.IsEmptyElement) + { + r.Read(); + ReadContentFrom(r, o); + } + r.Read(); + } + + internal void RemoveAttribute(XAttribute a) + { + bool notify = NotifyChanging(a, XObjectChangeEventArgs.Remove); + if (a.parent != this) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + XAttribute p = lastAttr, n; + while ((n = p.next) != a) p = n; + if (p == a) + { + lastAttr = null; + } + else + { + if (lastAttr == a) lastAttr = p; + p.next = a.next; + } + a.parent = null; + a.next = null; + if (notify) NotifyChanged(a, XObjectChangeEventArgs.Remove); + } + + void RemoveAttributesSkipNotify() + { + if (lastAttr != null) + { + XAttribute a = lastAttr; + do + { + XAttribute next = a.next; + a.parent = null; + a.next = null; + a = next; + } while (a != lastAttr); + lastAttr = null; + } + } + + internal void SetEndElementLineInfo(int lineNumber, int linePosition) + { + AddAnnotation(new LineInfoEndElementAnnotation(lineNumber, linePosition)); + } + + internal override void ValidateNode(XNode node, XNode previous) + { + if (node is XDocument) throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.Document)); + if (node is XDocumentType) throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.DocumentType)); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XHashtable.cs b/src/System.Xml.XDocument/System/Xml/Linq/XHashtable.cs new file mode 100644 index 000000000000..70c27b5a390d --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XHashtable.cs @@ -0,0 +1,458 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// This is a thread-safe hash table which maps string keys to values of type TValue. It is assumed that the string key is embedded in the hashed value + /// and can be extracted via a call to ExtractKeyDelegate (in order to save space and allow cleanup of key if value is released due to a WeakReference + /// TValue releasing its target). + /// + /// + /// All methods on this class are thread-safe. + /// + /// When the hash table fills up, it is necessary to resize it and rehash all contents. Because this can be expensive, + /// a lock is taken, and one thread is responsible for the resize. Other threads which need to add values must wait + /// for the resize to be complete. + /// + /// Thread-Safety Notes + /// =================== + /// + /// 1. Because performance and scalability are such a concern with the global name table, I have avoided the use of + /// BIFALOs (Big Fat Locks). Instead, I use CompareExchange, Interlocked.Increment, memory barriers, atomic state objects, + /// etc. to avoid locks. Any changes to code which accesses these variables should be carefully reviewed and tested, + /// as it can be *very* tricky. In particular, if you don't understand the CLR memory model or if you don't know + /// what a memory barrier is, DON'T attempt to modify this code. A good discussion of these topics can be found at + /// . + /// + /// 2. Because I am not sure if the CLR spec has changed since versions 1.0/1.1, I am assuming the weak memory model that + /// is described in the ECMA spec, in which normal writes can be reordered. This means I must introduce more memory + /// barriers than otherwise would be necessary. + /// + /// 3. There are several thread-safety concepts and patterns I utilize in this code: + /// a. Publishing -- There are a small number of places where state is exposed, or published, to multiple threads. + /// These places are marked with the comment "PUBLISH", and are key locations to consider when + /// reviewing the code for thread-safety. + /// + /// b. Immutable objects -- Immutable objects initialize their fields once in their constructor and then never modify + /// them again. As long as care is taken to ensure that initial field values are visible to + /// other threads before publishing the immutable object itself, immutable objects are + /// completely thread-safe. + /// + /// c. Atomic state objects -- Locks typically are taken when several pieces of state must be updated atomically. In + /// other words, there is a window in which state is inconsistent, and that window must + /// be protected from view by locking. However, if a new object is created each time state + /// changes (or state changes substantially), then during creation the new object is only + /// visible to a single thread. Once construction is complete, an assignment (guaranteed + /// atomic) can replace the old state object with the new state object, thus publishing a + /// consistent view to all threads. + /// + /// d. Retry -- When several threads contend over shared state which only one is allowed to possess, it is possible + /// to avoid locking by repeatedly attempting to acquire the shared state. The CompareExchange method + /// is useful for atomically ensuring that only one thread succeeds, and other threads are notified that + /// they must retry. + /// + /// 4. All variables which can be written by multiple threads are marked "SHARED STATE". + /// + internal sealed class XHashtable + { + private XHashtableState state; // SHARED STATE: Contains all XHashtable state, so it can be atomically swapped when resizes occur + + private const int StartingHash = (5381 << 16) + 5381; // Starting hash code value for string keys to be hashed + + /// + /// Prototype of function which is called to extract a string key value from a hashed value. + /// Returns null if the hashed value is invalid (e.g. value has been released due to a WeakReference TValue being cleaned up). + /// + public delegate string ExtractKeyDelegate(TValue value); + + /// + /// Construct a new XHashtable with the specified starting capacity. + /// + public XHashtable(ExtractKeyDelegate extractKey, int capacity) + { + state = new XHashtableState(extractKey, capacity); + } + + /// + /// Get an existing value from the hash table. Return false if no such value exists. + /// + public bool TryGetValue(string key, int index, int count, out TValue value) + { + return state.TryGetValue(key, index, count, out value); + } + + /// + /// Add a value to the hash table, hashed based on a string key embedded in it. Return the added value (may be a different object than "value"). + /// + public TValue Add(TValue value) + { + TValue newValue; + + // Loop until value is in hash table + while (true) + { + // Add new value + // XHashtableState.TryAdd returns false if hash table is not big enough + if (state.TryAdd(value, out newValue)) + return newValue; + + // PUBLISH (state) + // Hash table was not big enough, so resize it. + // We only want one thread to perform a resize, as it is an expensive operation + // First thread will perform resize; waiting threads will call Resize(), but should immediately + // return since there will almost always be space in the hash table resized by the first thread. + lock (this) + { + XHashtableState newState = state.Resize(); + + // Use memory barrier to ensure that the resized XHashtableState object is fully constructed before it is assigned +#if !SILVERLIGHT + Thread.MemoryBarrier(); +#else // SILVERLIGHT + // According to this document "http://my/sites/juddhall/ThreadingFeatureCrew/Shared Documents/System.Threading - FX Audit Proposal.docx" + // The MemoryBarrier method usage is busted (mostly - don't know about ours) and should be removed. + + // Replacing with Interlocked.CompareExchange for now (with no effect) + // which will do a very similar thing to MemoryBarrier (it's just slower) + System.Threading.Interlocked.CompareExchange(ref state, null, null); +#endif // SILVERLIGHT + state = newState; + } + } + } + + /// + /// This class contains all the hash table state. Rather than creating a bucket object, buckets are structs + /// packed into an array. Buckets with the same truncated hash code are linked into lists, so that collisions + /// can be disambiguated. + /// + /// + /// Note that the "buckets" and "entries" arrays are never themselves written by multiple threads. Instead, the + /// *contents* of the array are written by multiple threads. Resizing the hash table does not modify these variables, + /// or even modify the contents of these variables. Instead, resizing makes an entirely new XHashtableState object + /// in which all entries are rehashed. This strategy allows reader threads to continue finding values in the "old" + /// XHashtableState, while writer threads (those that need to add a new value to the table) are blocked waiting for + /// the resize to complete. + /// + private sealed class XHashtableState + { + private int[] buckets; // Buckets contain indexes into entries array (bucket values are SHARED STATE) + private Entry[] entries; // Entries contain linked lists of buckets (next pointers are SHARED STATE) + private int numEntries; // SHARED STATE: Current number of entries (including orphaned entries) + private ExtractKeyDelegate extractKey; // Delegate called in order to extract string key embedded in hashed TValue + + private const int EndOfList = 0; // End of linked list marker + private const int FullList = -1; // Indicates entries should not be added to end of linked list + + /// + /// Construct a new XHashtableState object with the specified capacity. + /// + public XHashtableState(ExtractKeyDelegate extractKey, int capacity) + { + Debug.Assert((capacity & (capacity - 1)) == 0, "capacity must be a power of 2"); + Debug.Assert(extractKey != null, "extractKey may not be null"); + + // Initialize hash table data structures, with specified maximum capacity + buckets = new int[capacity]; + entries = new Entry[capacity]; + + // Save delegate + this.extractKey = extractKey; + } + + /// + /// If this table is not full, then just return "this". Otherwise, create and return a new table with + /// additional capacity, and rehash all values in the table. + /// + public XHashtableState Resize() + { + // No need to resize if there are open entries + if (numEntries < buckets.Length) + return this; + + int newSize = 0; + + // Determine capacity of resized hash table by first counting number of valid, non-orphaned entries + // As this count proceeds, close all linked lists so that no additional entries can be added to them + for (int bucketIdx = 0; bucketIdx < buckets.Length; bucketIdx++) + { + int entryIdx = buckets[bucketIdx]; + + if (entryIdx == EndOfList) + { + // Replace EndOfList with FullList, so that any threads still attempting to add will be forced to resize + entryIdx = Interlocked.CompareExchange(ref buckets[bucketIdx], FullList, EndOfList); + } + + // Loop until we've guaranteed that the list has been counted and closed to further adds + while (entryIdx > EndOfList) + { + // Count each valid entry + if (extractKey(entries[entryIdx].Value) != null) + newSize++; + + if (entries[entryIdx].Next == EndOfList) + { + // Replace EndOfList with FullList, so that any threads still attempting to add will be forced to resize + entryIdx = Interlocked.CompareExchange(ref entries[entryIdx].Next, FullList, EndOfList); + } + else + { + // Move to next entry in the list + entryIdx = entries[entryIdx].Next; + } + } + Debug.Assert(entryIdx == EndOfList, "Resize() should only be called by one thread"); + } + + // Double number of valid entries; if result is less than current capacity, then use current capacity + if (newSize < buckets.Length / 2) + { + newSize = buckets.Length; + } + else + { + newSize = buckets.Length * 2; + + if (newSize < 0) + throw new OverflowException(); + } + + // Create new hash table with additional capacity + XHashtableState newHashtable = new XHashtableState(extractKey, newSize); + + // Rehash names (TryAdd will always succeed, since we won't fill the new table) + // Do not simply walk over entries and add them to table, as that would add orphaned + // entries. Instead, walk the linked lists and add each name. + for (int bucketIdx = 0; bucketIdx < buckets.Length; bucketIdx++) + { + int entryIdx = buckets[bucketIdx]; + TValue newValue; + + while (entryIdx > EndOfList) + { + newHashtable.TryAdd(entries[entryIdx].Value, out newValue); + + entryIdx = entries[entryIdx].Next; + } + Debug.Assert(entryIdx == FullList, "Linked list should have been closed when it was counted"); + } + + return newHashtable; + } + + /// + /// Attempt to find "key" in the table. If the key exists, return the associated value in "value" and + /// return true. Otherwise return false. + /// + public bool TryGetValue(string key, int index, int count, out TValue value) + { + int hashCode = ComputeHashCode(key, index, count); + int entryIndex = 0; + + // If a matching entry is found, return its value + if (FindEntry(hashCode, key, index, count, ref entryIndex)) + { + value = entries[entryIndex].Value; + return true; + } + + // No matching entry found, so return false + value = default(TValue); + return false; + } + + /// + /// Attempt to add "value" to the table, hashed by an embedded string key. If a value having the same key already exists, + /// then return the existing value in "newValue". Otherwise, return the newly added value in "newValue". + /// + /// If the hash table is full, return false. Otherwise, return true. + /// + public bool TryAdd(TValue value, out TValue newValue) + { + int newEntry, entryIndex; + string key; + int hashCode; + + // Assume "value" will be added and returned as "newValue" + newValue = value; + + // Extract the key from the value. If it's null, then value is invalid and does not need to be added to table. + key = extractKey(value); + if (key == null) + return true; + + // Compute hash code over entire length of key + hashCode = ComputeHashCode(key, 0, key.Length); + + // Assume value is not yet in the hash table, and prepare to add it (if table is full, return false). + // Use the entry index returned from Increment, which will never be zero, as zero conflicts with EndOfList. + // Although this means that the first entry will never be used, it avoids the need to initialize all + // starting buckets to the EndOfList value. + newEntry = Interlocked.Increment(ref numEntries); + if (newEntry < 0 || newEntry >= buckets.Length) + return false; + + entries[newEntry].Value = value; + entries[newEntry].HashCode = hashCode; + + // Ensure that all writes to the entry can't be reordered past this barrier (or other threads might see new entry + // in list before entry has been initialized!). +#if !SILVERLIGHT + Thread.MemoryBarrier(); +#else // SILVERLIGHT + // According to this document "http://my/sites/juddhall/ThreadingFeatureCrew/Shared Documents/System.Threading - FX Audit Proposal.docx" + // The MemoryBarrier method usage is busted (mostly - don't know about ours) and should be removed. + + // Replacing with Interlocked.CompareExchange for now (with no effect) + // which will do a very similar thing to MemoryBarrier (it's just slower) + System.Threading.Interlocked.CompareExchange(ref entries, null, null); +#endif // SILVERLIGHT + + // Loop until a matching entry is found, a new entry is added, or linked list is found to be full + entryIndex = 0; + while (!FindEntry(hashCode, key, 0, key.Length, ref entryIndex)) + { + // PUBLISH (buckets slot) + // No matching entry found, so add the new entry to the end of the list ("entryIndex" is index of last entry) + if (entryIndex == 0) + entryIndex = Interlocked.CompareExchange(ref buckets[hashCode & (buckets.Length - 1)], newEntry, EndOfList); + else + entryIndex = Interlocked.CompareExchange(ref entries[entryIndex].Next, newEntry, EndOfList); + + // Return true only if the CompareExchange succeeded (happens when replaced value is EndOfList). + // Return false if the linked list turned out to be full because another thread is currently resizing + // the hash table. In this case, entries[newEntry] is orphaned (not part of any linked list) and the + // Add needs to be performed on the new hash table. Otherwise, keep looping, looking for new end of list. + if (entryIndex <= EndOfList) + return entryIndex == EndOfList; + } + + // Another thread already added the value while this thread was trying to add, so return that instance instead. + // Note that entries[newEntry] will be orphaned (not part of any linked list) in this case + newValue = entries[entryIndex].Value; + + return true; + } + + /// + /// Searches a linked list of entries, beginning at "entryIndex". If "entryIndex" is 0, then search starts at a hash bucket instead. + /// Each entry in the list is matched against the (hashCode, key, index, count) key. If a matching entry is found, then its + /// entry index is returned in "entryIndex" and true is returned. If no matching entry is found, then the index of the last entry + /// in the list (or 0 if list is empty) is returned in "entryIndex" and false is returned. + /// + /// + /// This method has the side effect of removing invalid entries from the list as it is traversed. + /// + private bool FindEntry(int hashCode, string key, int index, int count, ref int entryIndex) + { + int previousIndex = entryIndex; + int currentIndex; + + // Set initial value of currentIndex to index of the next entry following entryIndex + if (previousIndex == 0) + currentIndex = buckets[hashCode & (buckets.Length - 1)]; + else + currentIndex = previousIndex; + + // Loop while not at end of list + while (currentIndex > EndOfList) + { + // Check for matching hash code, then matching key + if (entries[currentIndex].HashCode == hashCode) + { + string keyCompare = extractKey(entries[currentIndex].Value); + + // If the key is invalid, then attempt to remove the current entry from the linked list. + // This is thread-safe in the case where the Next field points to another entry, since once a Next field points + // to another entry, it will never be modified to be EndOfList or FullList. + if (keyCompare == null) + { + if (entries[currentIndex].Next > EndOfList) + { + // PUBLISH (buckets slot or entries slot) + // Entry is invalid, so modify previous entry to point to its next entry + entries[currentIndex].Value = default(TValue); + currentIndex = entries[currentIndex].Next; + + if (previousIndex == 0) + buckets[hashCode & (buckets.Length - 1)] = currentIndex; + else + entries[previousIndex].Next = currentIndex; + + continue; + } + } + else + { + // Valid key, so compare keys + if (count == keyCompare.Length && string.CompareOrdinal(key, index, keyCompare, 0, count) == 0) + { + // Found match, so return true and matching entry in list + entryIndex = currentIndex; + return true; + } + } + } + + // Move to next entry + previousIndex = currentIndex; + currentIndex = entries[currentIndex].Next; + } + + // Return false and last entry in list + entryIndex = previousIndex; + return false; + } + + /// + /// Compute hash code for a string key (index, count substring of "key"). The algorithm used is the same on used in NameTable.cs in System.Xml. + /// + private static int ComputeHashCode(string key, int index, int count) + { + int hashCode = StartingHash; + int end = index + count; + Debug.Assert(key != null, "key should have been checked previously for null"); + + // Hash the key + for (int i = index; i < end; i++) + hashCode += (hashCode << 7) ^ key[i]; + + // Mix up hash code a bit more and clear the sign bit. This code was taken from NameTable.cs in System.Xml. + hashCode -= hashCode >> 17; + hashCode -= hashCode >> 11; + hashCode -= hashCode >> 5; + return hashCode & 0x7FFFFFFF; + } + + /// + /// Hash table entry. The "Value" and "HashCode" fields are filled during initialization, and are never changed. The "Next" + /// field is updated when a new entry is chained to this one, and therefore care must be taken to ensure that updates to + /// this field are thread-safe. + /// + private struct Entry + { + public TValue Value; // Hashed value + public int HashCode; // Hash code of string key (equal to extractKey(Value).GetHashCode()) + public int Next; // SHARED STATE: Points to next entry in linked list + } + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XHelper.cs b/src/System.Xml.XDocument/System/Xml/Linq/XHelper.cs new file mode 100644 index 000000000000..0bc3f34c3ff9 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XHelper.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + static class XHelper + { + internal static string ToLower_InvariantCulture(string str) + { + return CultureInfo.InvariantCulture.TextInfo.ToLower(str); + } + + internal static bool IsInstanceOfType(object o, Type type) + { + Debug.Assert(type != null); + + if (o == null) + return false; + + return type.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XLinq.cs b/src/System.Xml.XDocument/System/Xml/Linq/XLinq.cs index d3294d7513a9..1f0ad33e23aa 100644 --- a/src/System.Xml.XDocument/System/Xml/Linq/XLinq.cs +++ b/src/System.Xml.XDocument/System/Xml/Linq/XLinq.cs @@ -19,10183 +19,649 @@ namespace System.Xml.Linq { - /// - /// Represents a name of an XML element or attribute. This class cannot be inherited. - /// - [SuppressMessage("Microsoft.Usage", "CA2229:ImplementSerializationConstructors", Justification = "Deserialization handled by NameSerializer.")] - public sealed class XName : IEquatable - { - XNamespace ns; - string localName; - int hashCode; - - /// - /// Constructor, internal so that external users must go through the Get() method to create an XName. - /// - internal XName(XNamespace ns, string localName) - { - this.ns = ns; - this.localName = XmlConvert.VerifyNCName(localName); - this.hashCode = ns.GetHashCode() ^ localName.GetHashCode(); - } - - /// - /// Gets the local (unqualified) part of the name. - /// - /// - public string LocalName - { - get { return localName; } - } - - /// - /// Gets the namespace of the name. - /// - public XNamespace Namespace - { - get { return ns; } - } - - /// - /// Gets the namespace name part of the name. - /// - public string NamespaceName - { - get { return ns.NamespaceName; } - } - - /// - /// Returns the expanded XML name in the format: {namespaceName}localName. - /// - public override string ToString() - { - if (ns.NamespaceName.Length == 0) return localName; - return "{" + ns.NamespaceName + "}" + localName; - } - - /// - /// Returns an object created from the specified expanded name. - /// - /// - /// A string containing an expanded XML name in the format: {namespace}localname. - /// - /// - /// An object constructed from the specified expanded name. - /// - public static XName Get(string expandedName) - { - if (expandedName == null) throw new ArgumentNullException("expandedName"); - if (expandedName.Length == 0) throw new ArgumentException(SR.Format(SR.Argument_InvalidExpandedName, expandedName)); - if (expandedName[0] == '{') - { - int i = expandedName.LastIndexOf('}'); - if (i <= 1 || i == expandedName.Length - 1) throw new ArgumentException(SR.Format(SR.Argument_InvalidExpandedName, expandedName)); - return XNamespace.Get(expandedName, 1, i - 1).GetName(expandedName, i + 1, expandedName.Length - i - 1); - } - else - { - return XNamespace.None.GetName(expandedName); - } - } - - /// - /// Returns an object from a local name and a namespace. - /// - /// A local (unqualified) name. - /// An XML namespace. - /// An XName object created from the specified local name and namespace. - public static XName Get(string localName, string namespaceName) - { - return XNamespace.Get(namespaceName).GetName(localName); - } - - /// - /// Converts a string formatted as an expanded XML name ({namespace}localname) to an XName object. - /// - /// A string containing an expanded XML name in the format: {namespace}localname. - /// An XName object constructed from the expanded name. - [CLSCompliant(false)] - public static implicit operator XName(string expandedName) - { - return expandedName != null ? Get(expandedName) : null; - } - - /// - /// Determines whether the specified is equal to the current . - /// - /// The XName to compare to the current XName. - /// - /// true if the specified is equal to the current XName; otherwise false. - /// - /// - /// For two objects to be equal, they must have the same expanded name. - /// - public override bool Equals(object obj) - { - return (object)this == obj; - } - - /// - /// Serves as a hash function for . GetHashCode is suitable - /// for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() - { - return hashCode; - } - - // The overloads of == and != are included to enable comparisons between - // XName and string (e.g. element.Name == "foo"). C#'s predefined reference - // equality operators require one operand to be convertible to the type of - // the other through reference conversions only and do not consider the - // implicit conversion from string to XName. - - /// - /// Returns a value indicating whether two instances of are equal. - /// - /// The first XName to compare. - /// The second XName to compare. - /// true if left and right are equal; otherwise false. - /// - /// This overload is included to enable the comparison between - /// an instance of XName and string. - /// - public static bool operator ==(XName left, XName right) - { - return (object)left == (object)right; - } - - /// - /// Returns a value indicating whether two instances of are not equal. - /// - /// The first XName to compare. - /// The second XName to compare. - /// true if left and right are not equal; otherwise false. - /// - /// This overload is included to enable the comparison between - /// an instance of XName and string. - /// - public static bool operator !=(XName left, XName right) - { - return (object)left != (object)right; - } - - /// - /// Indicates whether the current is equal to - /// the specified - /// - /// The to compare with the - /// current - /// - /// Returns true if the current is equal to - /// the specified . Returns false otherwise. - /// - bool IEquatable.Equals(XName other) - { - return (object)this == (object)other; - } - } - - /// - /// Represents an XML namespace. This class cannot be inherited. - /// - public sealed class XNamespace - { - internal const string xmlPrefixNamespace = "http://www.w3.org/XML/1998/namespace"; - internal const string xmlnsPrefixNamespace = "http://www.w3.org/2000/xmlns/"; - - static XHashtable namespaces; - static WeakReference refNone; - static WeakReference refXml; - static WeakReference refXmlns; - - string namespaceName; - int hashCode; - XHashtable names; - - const int NamesCapacity = 8; // Starting capacity of XName table, which must be power of 2 - const int NamespacesCapacity = 32; // Starting capacity of XNamespace table, which must be power of 2 - - /// - /// Constructor, internal so that external users must go through the Get() method to create an XNamespace. - /// - internal XNamespace(string namespaceName) - { - this.namespaceName = namespaceName; - this.hashCode = namespaceName.GetHashCode(); - names = new XHashtable(ExtractLocalName, NamesCapacity); - } - - /// - /// Gets the namespace name of the namespace. - /// - public string NamespaceName - { - get { return namespaceName; } - } - - /// - /// Returns an XName object created from the current instance and the specified local name. - /// - /// - /// The returned XName object is guaranteed to be atomic (i.e. the only one in the system for this - /// particular expanded name). - /// - public XName GetName(string localName) - { - if (localName == null) throw new ArgumentNullException("localName"); - return GetName(localName, 0, localName.Length); - } - - /// - /// Returns the namespace name of this . - /// - /// A string value containing the namespace name. - public override string ToString() - { - return namespaceName; - } - - /// - /// Gets the XNamespace object that corresponds to no namespace. - /// - /// - /// If an element or attribute is in no namespace, its namespace - /// will be set to the namespace returned by this property. - /// - public static XNamespace None - { - get - { - return EnsureNamespace(ref refNone, string.Empty); - } - } - - /// - /// Gets the XNamespace object that corresponds to the xml uri (http://www.w3.org/XML/1998/namespace). - /// - public static XNamespace Xml - { - get - { - return EnsureNamespace(ref refXml, xmlPrefixNamespace); - } - } - - /// - /// Gets the XNamespace object that corresponds to the xmlns uri (http://www.w3.org/2000/xmlns/). - /// - public static XNamespace Xmlns - { - get - { - return EnsureNamespace(ref refXmlns, xmlnsPrefixNamespace); - } - } - - /// - /// Gets an XNamespace created from the specified namespace name. - /// - /// - /// The returned XNamespace object is guaranteed to be atomic - /// (i.e. the only one in the system for that particular namespace name). - /// - public static XNamespace Get(string namespaceName) - { - if (namespaceName == null) throw new ArgumentNullException("namespaceName"); - return Get(namespaceName, 0, namespaceName.Length); - } - - /// - /// Converts a string containing a namespace name to an XNamespace. - /// - /// A string containing the namespace name. - /// An XNamespace constructed from the namespace name string. - [CLSCompliant(false)] - public static implicit operator XNamespace(string namespaceName) - { - return namespaceName != null ? Get(namespaceName) : null; - } - - /// - /// Combines an object with a local name to create an . - /// - /// The namespace for the expanded name. - /// The local name for the expanded name. - /// The new XName constructed from the namespace and local name. - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Functionality available via XNamespace.Get().")] - public static XName operator +(XNamespace ns, string localName) - { - if (ns == null) throw new ArgumentNullException("ns"); - return ns.GetName(localName); - } - - /// - /// Determines whether the specified is equal to the current . - /// - /// The XNamespace to compare to the current XNamespace. - /// - /// true if the specified is equal to the current XNamespace; otherwise false. - /// - /// - /// For two objects to be equal they must have the same - /// namespace name. - /// - public override bool Equals(object obj) - { - return (object)this == obj; - } - - /// - /// Serves as a hash function for . GetHashCode is suitable - /// for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() - { - return hashCode; - } - - - // The overloads of == and != are included to enable comparisons between - // XNamespace and string (e.g. element.Name.Namespace == "foo"). C#'s - // predefined reference equality operators require one operand to be - // convertible to the type of the other through reference conversions only - // and do not consider the implicit conversion from string to XNamespace. - - /// - /// Returns a value indicating whether two instances of are equal. - /// - /// The first XNamespace to compare. - /// The second XNamespace to compare. - /// true if left and right are equal; otherwise false. - /// - /// This overload is included to enable the comparison between - /// an instance of XNamespace and string. - /// - public static bool operator ==(XNamespace left, XNamespace right) - { - return (object)left == (object)right; - } - - /// - /// Returns a value indicating whether two instances of are not equal. - /// - /// The first XNamespace to compare. - /// The second XNamespace to compare. - /// true if left and right are not equal; otherwise false. - /// - /// This overload is included to enable the comparison between - /// an instance of XNamespace and string. - /// - public static bool operator !=(XNamespace left, XNamespace right) - { - return (object)left != (object)right; - } - - /// - /// Returns an created from this XNamespace and a portion of the passed in - /// local name parameter. The returned XName object is guaranteed to be atomic (i.e. the only one in the system for - /// this particular expanded name). - /// - internal XName GetName(string localName, int index, int count) - { - Debug.Assert(index >= 0 && index <= localName.Length, "Caller should have checked that index was in bounds"); - Debug.Assert(count >= 0 && index + count <= localName.Length, "Caller should have checked that count was in bounds"); - - // Attempt to get the local name from the hash table - XName name; - if (names.TryGetValue(localName, index, count, out name)) - return name; - - // No local name has yet been added, so add it now - return names.Add(new XName(this, localName.Substring(index, count))); - } - - /// - /// Returns an created from a portion of the passed in namespace name parameter. The returned XNamespace - /// object is guaranteed to be atomic (i.e. the only one in the system for this particular namespace name). - /// - internal static XNamespace Get(string namespaceName, int index, int count) - { - Debug.Assert(index >= 0 && index <= namespaceName.Length, "Caller should have checked that index was in bounds"); - Debug.Assert(count >= 0 && index + count <= namespaceName.Length, "Caller should have checked that count was in bounds"); - - if (count == 0) return None; - - // Use CompareExchange to ensure that exactly one XHashtable is used to store namespaces - if (namespaces == null) - Interlocked.CompareExchange(ref namespaces, new XHashtable(ExtractNamespace, NamespacesCapacity), null); - - WeakReference refNamespace; - XNamespace ns; - - // Keep looping until a non-null namespace has been retrieved - do - { - // Attempt to get the WeakReference for the namespace from the hash table - if (!namespaces.TryGetValue(namespaceName, index, count, out refNamespace)) - { - // If it is not there, first determine whether it's a special namespace - if (count == xmlPrefixNamespace.Length && string.CompareOrdinal(namespaceName, index, xmlPrefixNamespace, 0, count) == 0) return Xml; - if (count == xmlnsPrefixNamespace.Length && string.CompareOrdinal(namespaceName, index, xmlnsPrefixNamespace, 0, count) == 0) return Xmlns; - - // Go ahead and create the namespace and add it to the table - refNamespace = namespaces.Add(new WeakReference(new XNamespace(namespaceName.Substring(index, count)))); - } - - ns = (refNamespace != null) ? (XNamespace)refNamespace.Target : null; - } - while (ns == null); - - return ns; - } - - /// - /// This function is used by the ]]> to extract the local name part from an XName. The hash table - /// uses the local name as the hash key. - /// - private static string ExtractLocalName(XName n) - { - Debug.Assert(n != null, "Null name should never exist here"); - return n.LocalName; - } - - /// - /// This function is used by the ]]> to extract the XNamespace that the WeakReference is - /// referencing. In cases where the XNamespace has been cleaned up, this function returns null. - /// - private static string ExtractNamespace(WeakReference r) - { - XNamespace ns; - - if (r == null || (ns = (XNamespace)r.Target) == null) - return null; - - return ns.NamespaceName; - } - - /// - /// Ensure that an XNamespace object for 'namespaceName' has been atomically created. In other words, all outstanding - /// references to this particular namespace, on any thread, must all be to the same object. Care must be taken, - /// since other threads can be concurrently calling this method, and the target of a WeakReference can be cleaned up - /// at any time by the GC. - /// - private static XNamespace EnsureNamespace(ref WeakReference refNmsp, string namespaceName) - { - WeakReference refOld; - - // Keep looping until a non-null namespace has been retrieved - while (true) - { - // Save refNmsp in local variable, so we can work on a value that will not be changed by another thread - refOld = refNmsp; - - if (refOld != null) - { - // If the target of the WeakReference is non-null, then we're done--just return the value - XNamespace ns = (XNamespace)refOld.Target; - if (ns != null) return ns; - } - - // Either refNmsp is null, or its target is null, so update it - // Make sure to do this atomically, so that we can guarantee atomicity of XNamespace objects - Interlocked.CompareExchange(ref refNmsp, new WeakReference(new XNamespace(namespaceName)), refOld); - } - } - } - - /// - /// This is a thread-safe hash table which maps string keys to values of type TValue. It is assumed that the string key is embedded in the hashed value - /// and can be extracted via a call to ExtractKeyDelegate (in order to save space and allow cleanup of key if value is released due to a WeakReference - /// TValue releasing its target). - /// - /// - /// All methods on this class are thread-safe. - /// - /// When the hash table fills up, it is necessary to resize it and rehash all contents. Because this can be expensive, - /// a lock is taken, and one thread is responsible for the resize. Other threads which need to add values must wait - /// for the resize to be complete. - /// - /// Thread-Safety Notes - /// =================== - /// - /// 1. Because performance and scalability are such a concern with the global name table, I have avoided the use of - /// BIFALOs (Big Fat Locks). Instead, I use CompareExchange, Interlocked.Increment, memory barriers, atomic state objects, - /// etc. to avoid locks. Any changes to code which accesses these variables should be carefully reviewed and tested, - /// as it can be *very* tricky. In particular, if you don't understand the CLR memory model or if you don't know - /// what a memory barrier is, DON'T attempt to modify this code. A good discussion of these topics can be found at - /// . - /// - /// 2. Because I am not sure if the CLR spec has changed since versions 1.0/1.1, I am assuming the weak memory model that - /// is described in the ECMA spec, in which normal writes can be reordered. This means I must introduce more memory - /// barriers than otherwise would be necessary. - /// - /// 3. There are several thread-safety concepts and patterns I utilize in this code: - /// a. Publishing -- There are a small number of places where state is exposed, or published, to multiple threads. - /// These places are marked with the comment "PUBLISH", and are key locations to consider when - /// reviewing the code for thread-safety. - /// - /// b. Immutable objects -- Immutable objects initialize their fields once in their constructor and then never modify - /// them again. As long as care is taken to ensure that initial field values are visible to - /// other threads before publishing the immutable object itself, immutable objects are - /// completely thread-safe. - /// - /// c. Atomic state objects -- Locks typically are taken when several pieces of state must be updated atomically. In - /// other words, there is a window in which state is inconsistent, and that window must - /// be protected from view by locking. However, if a new object is created each time state - /// changes (or state changes substantially), then during creation the new object is only - /// visible to a single thread. Once construction is complete, an assignment (guaranteed - /// atomic) can replace the old state object with the new state object, thus publishing a - /// consistent view to all threads. - /// - /// d. Retry -- When several threads contend over shared state which only one is allowed to possess, it is possible - /// to avoid locking by repeatedly attempting to acquire the shared state. The CompareExchange method - /// is useful for atomically ensuring that only one thread succeeds, and other threads are notified that - /// they must retry. - /// - /// 4. All variables which can be written by multiple threads are marked "SHARED STATE". - /// - internal sealed class XHashtable - { - private XHashtableState state; // SHARED STATE: Contains all XHashtable state, so it can be atomically swapped when resizes occur - - private const int StartingHash = (5381 << 16) + 5381; // Starting hash code value for string keys to be hashed - - /// - /// Prototype of function which is called to extract a string key value from a hashed value. - /// Returns null if the hashed value is invalid (e.g. value has been released due to a WeakReference TValue being cleaned up). - /// - public delegate string ExtractKeyDelegate(TValue value); - - /// - /// Construct a new XHashtable with the specified starting capacity. - /// - public XHashtable(ExtractKeyDelegate extractKey, int capacity) - { - state = new XHashtableState(extractKey, capacity); - } - - /// - /// Get an existing value from the hash table. Return false if no such value exists. - /// - public bool TryGetValue(string key, int index, int count, out TValue value) - { - return state.TryGetValue(key, index, count, out value); - } - - /// - /// Add a value to the hash table, hashed based on a string key embedded in it. Return the added value (may be a different object than "value"). - /// - public TValue Add(TValue value) - { - TValue newValue; - - // Loop until value is in hash table - while (true) - { - // Add new value - // XHashtableState.TryAdd returns false if hash table is not big enough - if (state.TryAdd(value, out newValue)) - return newValue; - - // PUBLISH (state) - // Hash table was not big enough, so resize it. - // We only want one thread to perform a resize, as it is an expensive operation - // First thread will perform resize; waiting threads will call Resize(), but should immediately - // return since there will almost always be space in the hash table resized by the first thread. - lock (this) - { - XHashtableState newState = state.Resize(); - - // Use memory barrier to ensure that the resized XHashtableState object is fully constructed before it is assigned -#if !SILVERLIGHT - Thread.MemoryBarrier(); -#else // SILVERLIGHT - // According to this document "http://my/sites/juddhall/ThreadingFeatureCrew/Shared Documents/System.Threading - FX Audit Proposal.docx" - // The MemoryBarrier method usage is busted (mostly - don't know about ours) and should be removed. - - // Replacing with Interlocked.CompareExchange for now (with no effect) - // which will do a very similar thing to MemoryBarrier (it's just slower) - System.Threading.Interlocked.CompareExchange(ref state, null, null); -#endif // SILVERLIGHT - state = newState; - } - } - } - - /// - /// This class contains all the hash table state. Rather than creating a bucket object, buckets are structs - /// packed into an array. Buckets with the same truncated hash code are linked into lists, so that collisions - /// can be disambiguated. - /// - /// - /// Note that the "buckets" and "entries" arrays are never themselves written by multiple threads. Instead, the - /// *contents* of the array are written by multiple threads. Resizing the hash table does not modify these variables, - /// or even modify the contents of these variables. Instead, resizing makes an entirely new XHashtableState object - /// in which all entries are rehashed. This strategy allows reader threads to continue finding values in the "old" - /// XHashtableState, while writer threads (those that need to add a new value to the table) are blocked waiting for - /// the resize to complete. - /// - private sealed class XHashtableState - { - private int[] buckets; // Buckets contain indexes into entries array (bucket values are SHARED STATE) - private Entry[] entries; // Entries contain linked lists of buckets (next pointers are SHARED STATE) - private int numEntries; // SHARED STATE: Current number of entries (including orphaned entries) - private ExtractKeyDelegate extractKey; // Delegate called in order to extract string key embedded in hashed TValue - - private const int EndOfList = 0; // End of linked list marker - private const int FullList = -1; // Indicates entries should not be added to end of linked list - - /// - /// Construct a new XHashtableState object with the specified capacity. - /// - public XHashtableState(ExtractKeyDelegate extractKey, int capacity) - { - Debug.Assert((capacity & (capacity - 1)) == 0, "capacity must be a power of 2"); - Debug.Assert(extractKey != null, "extractKey may not be null"); - - // Initialize hash table data structures, with specified maximum capacity - buckets = new int[capacity]; - entries = new Entry[capacity]; - - // Save delegate - this.extractKey = extractKey; - } - - /// - /// If this table is not full, then just return "this". Otherwise, create and return a new table with - /// additional capacity, and rehash all values in the table. - /// - public XHashtableState Resize() - { - // No need to resize if there are open entries - if (numEntries < buckets.Length) - return this; - - int newSize = 0; - - // Determine capacity of resized hash table by first counting number of valid, non-orphaned entries - // As this count proceeds, close all linked lists so that no additional entries can be added to them - for (int bucketIdx = 0; bucketIdx < buckets.Length; bucketIdx++) - { - int entryIdx = buckets[bucketIdx]; - - if (entryIdx == EndOfList) - { - // Replace EndOfList with FullList, so that any threads still attempting to add will be forced to resize - entryIdx = Interlocked.CompareExchange(ref buckets[bucketIdx], FullList, EndOfList); - } - - // Loop until we've guaranteed that the list has been counted and closed to further adds - while (entryIdx > EndOfList) - { - // Count each valid entry - if (extractKey(entries[entryIdx].Value) != null) - newSize++; - - if (entries[entryIdx].Next == EndOfList) - { - // Replace EndOfList with FullList, so that any threads still attempting to add will be forced to resize - entryIdx = Interlocked.CompareExchange(ref entries[entryIdx].Next, FullList, EndOfList); - } - else - { - // Move to next entry in the list - entryIdx = entries[entryIdx].Next; - } - } - Debug.Assert(entryIdx == EndOfList, "Resize() should only be called by one thread"); - } - - // Double number of valid entries; if result is less than current capacity, then use current capacity - if (newSize < buckets.Length / 2) - { - newSize = buckets.Length; - } - else - { - newSize = buckets.Length * 2; - - if (newSize < 0) - throw new OverflowException(); - } - - // Create new hash table with additional capacity - XHashtableState newHashtable = new XHashtableState(extractKey, newSize); - - // Rehash names (TryAdd will always succeed, since we won't fill the new table) - // Do not simply walk over entries and add them to table, as that would add orphaned - // entries. Instead, walk the linked lists and add each name. - for (int bucketIdx = 0; bucketIdx < buckets.Length; bucketIdx++) - { - int entryIdx = buckets[bucketIdx]; - TValue newValue; - - while (entryIdx > EndOfList) - { - newHashtable.TryAdd(entries[entryIdx].Value, out newValue); - - entryIdx = entries[entryIdx].Next; - } - Debug.Assert(entryIdx == FullList, "Linked list should have been closed when it was counted"); - } - - return newHashtable; - } - - /// - /// Attempt to find "key" in the table. If the key exists, return the associated value in "value" and - /// return true. Otherwise return false. - /// - public bool TryGetValue(string key, int index, int count, out TValue value) - { - int hashCode = ComputeHashCode(key, index, count); - int entryIndex = 0; - - // If a matching entry is found, return its value - if (FindEntry(hashCode, key, index, count, ref entryIndex)) - { - value = entries[entryIndex].Value; - return true; - } - - // No matching entry found, so return false - value = default(TValue); - return false; - } - - /// - /// Attempt to add "value" to the table, hashed by an embedded string key. If a value having the same key already exists, - /// then return the existing value in "newValue". Otherwise, return the newly added value in "newValue". - /// - /// If the hash table is full, return false. Otherwise, return true. - /// - public bool TryAdd(TValue value, out TValue newValue) - { - int newEntry, entryIndex; - string key; - int hashCode; - - // Assume "value" will be added and returned as "newValue" - newValue = value; - - // Extract the key from the value. If it's null, then value is invalid and does not need to be added to table. - key = extractKey(value); - if (key == null) - return true; - - // Compute hash code over entire length of key - hashCode = ComputeHashCode(key, 0, key.Length); - - // Assume value is not yet in the hash table, and prepare to add it (if table is full, return false). - // Use the entry index returned from Increment, which will never be zero, as zero conflicts with EndOfList. - // Although this means that the first entry will never be used, it avoids the need to initialize all - // starting buckets to the EndOfList value. - newEntry = Interlocked.Increment(ref numEntries); - if (newEntry < 0 || newEntry >= buckets.Length) - return false; - - entries[newEntry].Value = value; - entries[newEntry].HashCode = hashCode; - - // Ensure that all writes to the entry can't be reordered past this barrier (or other threads might see new entry - // in list before entry has been initialized!). -#if !SILVERLIGHT - Thread.MemoryBarrier(); -#else // SILVERLIGHT - // According to this document "http://my/sites/juddhall/ThreadingFeatureCrew/Shared Documents/System.Threading - FX Audit Proposal.docx" - // The MemoryBarrier method usage is busted (mostly - don't know about ours) and should be removed. - - // Replacing with Interlocked.CompareExchange for now (with no effect) - // which will do a very similar thing to MemoryBarrier (it's just slower) - System.Threading.Interlocked.CompareExchange(ref entries, null, null); -#endif // SILVERLIGHT - - // Loop until a matching entry is found, a new entry is added, or linked list is found to be full - entryIndex = 0; - while (!FindEntry(hashCode, key, 0, key.Length, ref entryIndex)) - { - // PUBLISH (buckets slot) - // No matching entry found, so add the new entry to the end of the list ("entryIndex" is index of last entry) - if (entryIndex == 0) - entryIndex = Interlocked.CompareExchange(ref buckets[hashCode & (buckets.Length - 1)], newEntry, EndOfList); - else - entryIndex = Interlocked.CompareExchange(ref entries[entryIndex].Next, newEntry, EndOfList); - - // Return true only if the CompareExchange succeeded (happens when replaced value is EndOfList). - // Return false if the linked list turned out to be full because another thread is currently resizing - // the hash table. In this case, entries[newEntry] is orphaned (not part of any linked list) and the - // Add needs to be performed on the new hash table. Otherwise, keep looping, looking for new end of list. - if (entryIndex <= EndOfList) - return entryIndex == EndOfList; - } - - // Another thread already added the value while this thread was trying to add, so return that instance instead. - // Note that entries[newEntry] will be orphaned (not part of any linked list) in this case - newValue = entries[entryIndex].Value; - - return true; - } - - /// - /// Searches a linked list of entries, beginning at "entryIndex". If "entryIndex" is 0, then search starts at a hash bucket instead. - /// Each entry in the list is matched against the (hashCode, key, index, count) key. If a matching entry is found, then its - /// entry index is returned in "entryIndex" and true is returned. If no matching entry is found, then the index of the last entry - /// in the list (or 0 if list is empty) is returned in "entryIndex" and false is returned. - /// - /// - /// This method has the side effect of removing invalid entries from the list as it is traversed. - /// - private bool FindEntry(int hashCode, string key, int index, int count, ref int entryIndex) - { - int previousIndex = entryIndex; - int currentIndex; - - // Set initial value of currentIndex to index of the next entry following entryIndex - if (previousIndex == 0) - currentIndex = buckets[hashCode & (buckets.Length - 1)]; - else - currentIndex = previousIndex; - - // Loop while not at end of list - while (currentIndex > EndOfList) - { - // Check for matching hash code, then matching key - if (entries[currentIndex].HashCode == hashCode) - { - string keyCompare = extractKey(entries[currentIndex].Value); - - // If the key is invalid, then attempt to remove the current entry from the linked list. - // This is thread-safe in the case where the Next field points to another entry, since once a Next field points - // to another entry, it will never be modified to be EndOfList or FullList. - if (keyCompare == null) - { - if (entries[currentIndex].Next > EndOfList) - { - // PUBLISH (buckets slot or entries slot) - // Entry is invalid, so modify previous entry to point to its next entry - entries[currentIndex].Value = default(TValue); - currentIndex = entries[currentIndex].Next; - - if (previousIndex == 0) - buckets[hashCode & (buckets.Length - 1)] = currentIndex; - else - entries[previousIndex].Next = currentIndex; - - continue; - } - } - else - { - // Valid key, so compare keys - if (count == keyCompare.Length && string.CompareOrdinal(key, index, keyCompare, 0, count) == 0) - { - // Found match, so return true and matching entry in list - entryIndex = currentIndex; - return true; - } - } - } - - // Move to next entry - previousIndex = currentIndex; - currentIndex = entries[currentIndex].Next; - } - - // Return false and last entry in list - entryIndex = previousIndex; - return false; - } - - /// - /// Compute hash code for a string key (index, count substring of "key"). The algorithm used is the same on used in NameTable.cs in System.Xml. - /// - private static int ComputeHashCode(string key, int index, int count) - { - int hashCode = StartingHash; - int end = index + count; - Debug.Assert(key != null, "key should have been checked previously for null"); - - // Hash the key - for (int i = index; i < end; i++) - hashCode += (hashCode << 7) ^ key[i]; - - // Mix up hash code a bit more and clear the sign bit. This code was taken from NameTable.cs in System.Xml. - hashCode -= hashCode >> 17; - hashCode -= hashCode >> 11; - hashCode -= hashCode >> 5; - return hashCode & 0x7FFFFFFF; - } - - /// - /// Hash table entry. The "Value" and "HashCode" fields are filled during initialization, and are never changed. The "Next" - /// field is updated when a new entry is chained to this one, and therefore care must be taken to ensure that updates to - /// this field are thread-safe. - /// - private struct Entry - { - public TValue Value; // Hashed value - public int HashCode; // Hash code of string key (equal to extractKey(Value).GetHashCode()) - public int Next; // SHARED STATE: Points to next entry in linked list - } - } - } - - /// - /// Represents a node or an attribute in an XML tree. - /// - public abstract class XObject : IXmlLineInfo - { - internal XContainer parent; - internal object annotations; - - internal XObject() { } - - /// - /// Get the BaseUri for this . - /// - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Back-compat with System.Xml.")] - public string BaseUri - { - get - { - XObject o = this; - while (true) - { - while (o != null && o.annotations == null) - { - o = o.parent; - } - if (o == null) break; - BaseUriAnnotation a = o.Annotation(); - if (a != null) return a.baseUri; - o = o.parent; - } - return string.Empty; - } - } - - /// - /// Gets the XDocument object for this . - /// - public XDocument Document - { - get - { - XObject n = this; - while (n.parent != null) n = n.parent; - return n as XDocument; - } - } - - /// - /// Gets the node type for this . - /// - public abstract XmlNodeType NodeType { get; } - - /// - /// Gets the parent of this . - /// - /// - /// If this has no parent , this property returns null. - /// - public XElement Parent - { - get { return parent as XElement; } - } - - /// - /// Adds an object to the annotation list of this . - /// - /// The annotation to add. - public void AddAnnotation(object annotation) - { - if (annotation == null) throw new ArgumentNullException("annotation"); - if (annotations == null) - { - annotations = annotation is object[] ? new object[] { annotation } : annotation; - } - else - { - object[] a = annotations as object[]; - if (a == null) - { - annotations = new object[] { annotations, annotation }; - } - else - { - int i = 0; - while (i < a.Length && a[i] != null) i++; - if (i == a.Length) - { - Array.Resize(ref a, i * 2); - annotations = a; - } - a[i] = annotation; - } - } - } - - /// - /// Returns the first annotation object of the specified type from the list of annotations - /// of this . - /// - /// The type of the annotation to retrieve. - /// - /// The first matching annotation object, or null - /// if no annotation is the specified type. - /// - public object Annotation(Type type) - { - if (type == null) throw new ArgumentNullException("type"); - if (annotations != null) - { - object[] a = annotations as object[]; - if (a == null) - { - if (XHelper.IsInstanceOfType(annotations, type)) return annotations; - } - else - { - for (int i = 0; i < a.Length; i++) - { - object obj = a[i]; - if (obj == null) break; - if (XHelper.IsInstanceOfType(obj, type)) return obj; - } - } - } - return null; - } - - private object AnnotationForSealedType(Type type) - { - Debug.Assert(type != null); - - if (annotations != null) - { - object[] a = annotations as object[]; - if (a == null) - { - if (annotations.GetType() == type) return annotations; - } - else - { - for (int i = 0; i < a.Length; i++) - { - object obj = a[i]; - if (obj == null) break; - if (obj.GetType() == type) return obj; - } - } - } - return null; - } - - /// - /// Returns the first annotation object of the specified type from the list of annotations - /// of this . - /// - /// The type of the annotation to retrieve. - /// - /// The first matching annotation object, or null if no annotation - /// is the specified type. - /// - public T Annotation() where T : class - { - if (annotations != null) - { - object[] a = annotations as object[]; - if (a == null) return annotations as T; - for (int i = 0; i < a.Length; i++) - { - object obj = a[i]; - if (obj == null) break; - T result = obj as T; - if (result != null) return result; - } - } - return null; - } - - /// - /// Returns an enumerable collection of annotations of the specified type - /// for this . - /// - /// The type of the annotations to retrieve. - /// An enumerable collection of annotations for this XObject. - public IEnumerable Annotations(Type type) - { - if (type == null) throw new ArgumentNullException("type"); - return AnnotationsIterator(type); - } - - IEnumerable AnnotationsIterator(Type type) - { - if (annotations != null) - { - object[] a = annotations as object[]; - if (a == null) - { - if (XHelper.IsInstanceOfType(annotations, type)) yield return annotations; - } - else - { - for (int i = 0; i < a.Length; i++) - { - object obj = a[i]; - if (obj == null) break; - if (XHelper.IsInstanceOfType(obj, type)) yield return obj; - } - } - } - } - - /// - /// Returns an enumerable collection of annotations of the specified type - /// for this . - /// - /// The type of the annotations to retrieve. - /// An enumerable collection of annotations for this XObject. - public IEnumerable Annotations() where T : class - { - if (annotations != null) - { - object[] a = annotations as object[]; - if (a == null) - { - T result = annotations as T; - if (result != null) yield return result; - } - else - { - for (int i = 0; i < a.Length; i++) - { - object obj = a[i]; - if (obj == null) break; - T result = obj as T; - if (result != null) yield return result; - } - } - } - } - - /// - /// Removes the annotations of the specified type from this . - /// - /// The type of annotations to remove. - public void RemoveAnnotations(Type type) - { - if (type == null) throw new ArgumentNullException("type"); - if (annotations != null) - { - object[] a = annotations as object[]; - if (a == null) - { - if (XHelper.IsInstanceOfType(annotations, type)) annotations = null; - } - else - { - int i = 0, j = 0; - while (i < a.Length) - { - object obj = a[i]; - if (obj == null) break; - if (!XHelper.IsInstanceOfType(obj, type)) a[j++] = obj; - i++; - } - if (j == 0) - { - annotations = null; - } - else - { - while (j < i) a[j++] = null; - } - } - } - } - - /// - /// Removes the annotations of the specified type from this . - /// - /// The type of annotations to remove. - public void RemoveAnnotations() where T : class - { - if (annotations != null) - { - object[] a = annotations as object[]; - if (a == null) - { - if (annotations is T) annotations = null; - } - else - { - int i = 0, j = 0; - while (i < a.Length) - { - object obj = a[i]; - if (obj == null) break; - if (!(obj is T)) a[j++] = obj; - i++; - } - if (j == 0) - { - annotations = null; - } - else - { - while (j < i) a[j++] = null; - } - } - } - } - - /// - /// Occurs when this or any of its descendants have changed. - /// - public event EventHandler Changed - { - add - { - if (value == null) return; - XObjectChangeAnnotation a = Annotation(); - if (a == null) - { - a = new XObjectChangeAnnotation(); - AddAnnotation(a); - } - a.changed += value; - } - remove - { - if (value == null) return; - XObjectChangeAnnotation a = Annotation(); - if (a == null) return; - a.changed -= value; - if (a.changing == null && a.changed == null) - { - RemoveAnnotations(); - } - } - } - - /// - /// Occurs when this or any of its descendants are about to change. - /// - public event EventHandler Changing - { - add - { - if (value == null) return; - XObjectChangeAnnotation a = Annotation(); - if (a == null) - { - a = new XObjectChangeAnnotation(); - AddAnnotation(a); - } - a.changing += value; - } - remove - { - if (value == null) return; - XObjectChangeAnnotation a = Annotation(); - if (a == null) return; - a.changing -= value; - if (a.changing == null && a.changed == null) - { - RemoveAnnotations(); - } - } - } - - bool IXmlLineInfo.HasLineInfo() - { - return Annotation() != null; - } - - int IXmlLineInfo.LineNumber - { - get - { - LineInfoAnnotation a = Annotation(); - if (a != null) return a.lineNumber; - return 0; - } - } - - int IXmlLineInfo.LinePosition - { - get - { - LineInfoAnnotation a = Annotation(); - if (a != null) return a.linePosition; - return 0; - } - } - - internal bool HasBaseUri - { - get - { - return Annotation() != null; - } - } - - internal bool NotifyChanged(object sender, XObjectChangeEventArgs e) - { - bool notify = false; - XObject o = this; - while (true) - { - while (o != null && o.annotations == null) - { - o = o.parent; - } - if (o == null) break; - XObjectChangeAnnotation a = o.Annotation(); - if (a != null) - { - notify = true; - if (a.changed != null) - { - a.changed(sender, e); - } - } - o = o.parent; - } - return notify; - } - - internal bool NotifyChanging(object sender, XObjectChangeEventArgs e) - { - bool notify = false; - XObject o = this; - while (true) - { - while (o != null && o.annotations == null) - { - o = o.parent; - } - if (o == null) break; - XObjectChangeAnnotation a = o.Annotation(); - if (a != null) - { - notify = true; - if (a.changing != null) - { - a.changing(sender, e); - } - } - o = o.parent; - } - return notify; - } - - internal void SetBaseUri(string baseUri) - { - AddAnnotation(new BaseUriAnnotation(baseUri)); - } - - internal void SetLineInfo(int lineNumber, int linePosition) - { - AddAnnotation(new LineInfoAnnotation(lineNumber, linePosition)); - } - - internal bool SkipNotify() - { - XObject o = this; - while (true) - { - while (o != null && o.annotations == null) - { - o = o.parent; - } - if (o == null) return true; - if (o.Annotations() != null) return false; - o = o.parent; - } - } - - /// - /// Walks the tree starting with "this" node and returns first annotation of type - /// found in the ancestors. - /// - /// The effective for this - internal SaveOptions GetSaveOptionsFromAnnotations() - { - XObject o = this; - while (true) - { - while (o != null && o.annotations == null) - { - o = o.parent; - } - if (o == null) - { - return SaveOptions.None; - } - object saveOptions = o.AnnotationForSealedType(typeof(SaveOptions)); - if (saveOptions != null) - { - return (SaveOptions)saveOptions; - } - o = o.parent; - } - } - } - - class BaseUriAnnotation - { - internal string baseUri; - - public BaseUriAnnotation(string baseUri) - { - this.baseUri = baseUri; - } - } - - /// - /// Instance of this class is used as an annotation on any node - /// for which we want to store its line information. - /// Note: on XElement nodes this annotation stores the line info - /// for the element start tag. The matching end tag line info - /// if present is stored using the LineInfoEndElementAnnotation - /// instance annotation. - /// - class LineInfoAnnotation - { - internal int lineNumber; - internal int linePosition; - - public LineInfoAnnotation(int lineNumber, int linePosition) - { - this.lineNumber = lineNumber; - this.linePosition = linePosition; - } - } - - /// - /// Instance of this class is used as an annotation on XElement nodes - /// if that element is not empty element and we want to store the line info - /// for its end element tag. - /// - class LineInfoEndElementAnnotation : LineInfoAnnotation - { - public LineInfoEndElementAnnotation(int lineNumber, int linePosition) - : base(lineNumber, linePosition) - { } - } - - class XObjectChangeAnnotation - { - internal EventHandler changing; - internal EventHandler changed; - - public XObjectChangeAnnotation() - { - } - } - - /// - /// Specifies the event type when an event is raised for an . - /// - public enum XObjectChange - { - /// - /// An has been or will be added to an . - /// - Add, - - /// - /// An has been or will be removed from an . - /// - Remove, - - /// - /// An has been or will be renamed. - /// - Name, - - /// - /// The value of an has been or will be changed. - /// There is a special case for elements. Change in the serialization - /// of an empty element (either from an empty tag to start/end tag - /// pair or vice versa) raises this event. - /// - Value, - } - - /// - /// Provides data for the and events. - /// - public class XObjectChangeEventArgs : EventArgs - { - XObjectChange objectChange; - - /// - /// Event argument for a change event. - /// - [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "XObjectChangeEventArgs is in fact immutable.")] - public static readonly XObjectChangeEventArgs Add = new XObjectChangeEventArgs(XObjectChange.Add); - - /// - /// Event argument for a change event. - /// - [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "XObjectChangeEventArgs is in fact immutable.")] - public static readonly XObjectChangeEventArgs Remove = new XObjectChangeEventArgs(XObjectChange.Remove); - - /// - /// Event argument for a change event. - /// - [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "XObjectChangeEventArgs is in fact immutable.")] - public static readonly XObjectChangeEventArgs Name = new XObjectChangeEventArgs(XObjectChange.Name); - - /// - /// Event argument for a change event. - /// - [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "XObjectChangeEventArgs is in fact immutable.")] - public static readonly XObjectChangeEventArgs Value = new XObjectChangeEventArgs(XObjectChange.Value); - - /// - /// Initializes a new instance of the class. - /// - public XObjectChangeEventArgs(XObjectChange objectChange) - { - this.objectChange = objectChange; - } - - /// - /// Gets the type () of change. - /// - public XObjectChange ObjectChange - { - get { return objectChange; } - } - } - - /// - /// Represents nodes (elements, comments, document type, processing instruction, - /// and text nodes) in the XML tree. - /// - /// - /// Nodes in the XML tree consist of objects of the following classes: - /// , - /// , - /// , - /// , - /// , - /// - /// Note that an is not an . - /// - public abstract class XNode : XObject - { - static XNodeDocumentOrderComparer documentOrderComparer; - static XNodeEqualityComparer equalityComparer; - - internal XNode next; - - internal XNode() { } - - /// - /// Gets the next sibling node of this node. - /// - /// - /// If this property does not have a parent, or if there is no next node, - /// then this property returns null. - /// - public XNode NextNode - { - get - { - return parent == null || this == parent.content ? null : next; - } - } - - /// - /// Gets the previous sibling node of this node. - /// - /// - /// If this property does not have a parent, or if there is no previous node, - /// then this property returns null. - /// - public XNode PreviousNode - { - get - { - if (parent == null) return null; - XNode n = ((XNode)parent.content).next; - XNode p = null; - while (n != this) - { - p = n; - n = n.next; - } - return p; - } - } - - /// - /// Gets a comparer that can compare the relative position of two nodes. - /// - public static XNodeDocumentOrderComparer DocumentOrderComparer - { - get - { - if (documentOrderComparer == null) documentOrderComparer = new XNodeDocumentOrderComparer(); - return documentOrderComparer; - } - } - - /// - /// Gets a comparer that can compare two nodes for value equality. - /// - public static XNodeEqualityComparer EqualityComparer - { - get - { - if (equalityComparer == null) equalityComparer = new XNodeEqualityComparer(); - return equalityComparer; - } - } - - /// - /// Adds the specified content immediately after this node. The - /// content can be simple content, a collection of - /// content objects, a parameter list of content objects, - /// or null. - /// - /// - /// Adds the specified content immediately after this node. - /// - /// - /// A content object containing simple content or a collection of content objects - /// to be added after this node. - /// - /// - /// Thrown if the parent is null. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void AddAfterSelf(object content) - { - if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); - new Inserter(parent, this).Add(content); - } - - /// - /// Adds the specified content immediately after this node. - /// - /// - /// A parameter list of content objects. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - /// - /// Thrown if the parent is null. - /// - public void AddAfterSelf(params object[] content) - { - AddAfterSelf((object)content); - } - - /// - /// Adds the specified content immediately before this node. The - /// content can be simple content, a collection of - /// content objects, a parameter list of content objects, - /// or null. - /// - /// - /// Adds the specified content immediately before this node. - /// - /// - /// A content object containing simple content or a collection of content objects - /// to be added after this node. - /// - /// - /// Thrown if the parent is null. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void AddBeforeSelf(object content) - { - if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); - XNode p = (XNode)parent.content; - while (p.next != this) p = p.next; - if (p == parent.content) p = null; - new Inserter(parent, p).Add(content); - } - - /// - /// Adds the specified content immediately before this node. - /// - /// - /// A parameter list of content objects. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - /// - /// Thrown if the parent is null. - /// - public void AddBeforeSelf(params object[] content) - { - AddBeforeSelf((object)content); - } - - /// - /// Returns an collection of the ancestor elements for this node. - /// Optionally an node name can be specified to filter for a specific ancestor element. - /// - /// - /// Returns a collection of the ancestor elements of this node. - /// - /// - /// The ancestor elements of this node. - /// - /// - /// This method will not return itself in the results. - /// - public IEnumerable Ancestors() - { - return GetAncestors(null, false); - } - - /// - /// Returns a collection of the ancestor elements of this node with the specified name. - /// - /// - /// The name of the ancestor elements to find. - /// - /// - /// A collection of the ancestor elements of this node with the specified name. - /// - /// - /// This method will not return itself in the results. - /// - public IEnumerable Ancestors(XName name) - { - return name != null ? GetAncestors(name, false) : XElement.EmptySequence; - } - - /// - /// Compares two nodes to determine their relative XML document order. - /// - /// First node to compare. - /// Second node to compare. - /// - /// 0 if the nodes are equal; -1 if n1 is before n2; 1 if n1 is after n2. - /// - /// - /// Thrown if the two nodes do not share a common ancestor. - /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Reviewed.")] - public static int CompareDocumentOrder(XNode n1, XNode n2) - { - if (n1 == n2) return 0; - if (n1 == null) return -1; - if (n2 == null) return 1; - if (n1.parent != n2.parent) - { - int height = 0; - XNode p1 = n1; - while (p1.parent != null) - { - p1 = p1.parent; - height++; - } - XNode p2 = n2; - while (p2.parent != null) - { - p2 = p2.parent; - height--; - } - if (p1 != p2) throw new InvalidOperationException(SR.InvalidOperation_MissingAncestor); - if (height < 0) - { - do - { - n2 = n2.parent; - height++; - } while (height != 0); - if (n1 == n2) return -1; - } - else if (height > 0) - { - do - { - n1 = n1.parent; - height--; - } while (height != 0); - if (n1 == n2) return 1; - } - while (n1.parent != n2.parent) - { - n1 = n1.parent; - n2 = n2.parent; - } - } - else if (n1.parent == null) - { - throw new InvalidOperationException(SR.InvalidOperation_MissingAncestor); - } - XNode n = (XNode)n1.parent.content; - while (true) - { - n = n.next; - if (n == n1) return -1; - if (n == n2) return 1; - } - } - - /// - /// Creates an for the node. - /// - /// An that can be used to read the node and its descendants. - public XmlReader CreateReader() - { - return new XNodeReader(this, null); - } - - /// - /// Creates an for the node. - /// - /// - /// Options to be used for the returned reader. These override the default usage of annotations from the tree. - /// - /// An that can be used to read the node and its descendants. - public XmlReader CreateReader(ReaderOptions readerOptions) - { - return new XNodeReader(this, null, readerOptions); - } - - /// - /// Returns a collection of the sibling nodes after this node, in document order. - /// - /// - /// This method only includes sibling nodes in the returned collection. - /// - /// The nodes after this node. - public IEnumerable NodesAfterSelf() - { - XNode n = this; - while (n.parent != null && n != n.parent.content) - { - n = n.next; - yield return n; - } - } - - /// - /// Returns a collection of the sibling nodes before this node, in document order. - /// - /// - /// This method only includes sibling nodes in the returned collection. - /// - /// The nodes after this node. - public IEnumerable NodesBeforeSelf() - { - if (parent != null) - { - XNode n = (XNode)parent.content; - do - { - n = n.next; - if (n == this) break; - yield return n; - } while (parent != null && parent == n.parent); - } - } - - /// - /// Returns a collection of the sibling element nodes after this node, in document order. - /// - /// - /// This method only includes sibling element nodes in the returned collection. - /// - /// The element nodes after this node. - public IEnumerable ElementsAfterSelf() - { - return GetElementsAfterSelf(null); - } - - /// - /// Returns a collection of the sibling element nodes with the specified name - /// after this node, in document order. - /// - /// - /// This method only includes sibling element nodes in the returned collection. - /// - /// The element nodes after this node with the specified name. - /// The name of elements to enumerate. - public IEnumerable ElementsAfterSelf(XName name) - { - return name != null ? GetElementsAfterSelf(name) : XElement.EmptySequence; - } - - /// - /// Returns a collection of the sibling element nodes before this node, in document order. - /// - /// - /// This method only includes sibling element nodes in the returned collection. - /// - /// The element nodes before this node. - public IEnumerable ElementsBeforeSelf() - { - return GetElementsBeforeSelf(null); - } - - /// - /// Returns a collection of the sibling element nodes with the specified name - /// before this node, in document order. - /// - /// - /// This method only includes sibling element nodes in the returned collection. - /// - /// The element nodes before this node with the specified name. - /// The name of elements to enumerate. - public IEnumerable ElementsBeforeSelf(XName name) - { - return name != null ? GetElementsBeforeSelf(name) : XElement.EmptySequence; - } - - /// - /// Determines if the current node appears after a specified node - /// in terms of document order. - /// - /// The node to compare for document order. - /// True if this node appears after the specified node; false if not. - public bool IsAfter(XNode node) - { - return CompareDocumentOrder(this, node) > 0; - } - - /// - /// Determines if the current node appears before a specified node - /// in terms of document order. - /// - /// The node to compare for document order. - /// True if this node appears before the specified node; false if not. - public bool IsBefore(XNode node) - { - return CompareDocumentOrder(this, node) < 0; - } - - /// - /// Creates an from an . - /// The runtime type of the node is determined by the node type - /// () of the first node encountered - /// in the reader. - /// - /// An positioned at the node to read into this . - /// An that contains the nodes read from the reader. - /// - /// Thrown if the is not positioned on a recognized node type. - /// - public static XNode ReadFrom(XmlReader reader) - { - if (reader == null) throw new ArgumentNullException("reader"); - if (reader.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive); - switch (reader.NodeType) - { - case XmlNodeType.Text: - case XmlNodeType.SignificantWhitespace: - case XmlNodeType.Whitespace: - return new XText(reader); - case XmlNodeType.CDATA: - return new XCData(reader); - case XmlNodeType.Comment: - return new XComment(reader); - case XmlNodeType.DocumentType: - return new XDocumentType(reader); - case XmlNodeType.Element: - return new XElement(reader); - case XmlNodeType.ProcessingInstruction: - return new XProcessingInstruction(reader); - default: - throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, reader.NodeType)); - } - } - - /// - /// Removes this XNode from the underlying XML tree. - /// - /// - /// Thrown if the parent is null. - /// - public void Remove() - { - if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); - parent.RemoveNode(this); - } - - /// - /// Replaces this node with the specified content. The - /// content can be simple content, a collection of - /// content objects, a parameter list of content objects, - /// or null. - /// - /// - /// Replaces the content of this . - /// - /// Content that replaces this node. - public void ReplaceWith(object content) - { - if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); - XContainer c = parent; - XNode p = (XNode)parent.content; - while (p.next != this) p = p.next; - if (p == parent.content) p = null; - parent.RemoveNode(this); - if (p != null && p.parent != c) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - new Inserter(c, p).Add(content); - } - - /// - /// Replaces this node with the specified content. - /// - /// Content that replaces this node. - public void ReplaceWith(params object[] content) - { - ReplaceWith((object)content); - } - - /// - /// Provides the formatted XML text representation. - /// You can use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. - /// - /// A formatted XML string. - public override string ToString() - { - return GetXmlString(GetSaveOptionsFromAnnotations()); - } - - /// - /// Provides the XML text representation. - /// - /// - /// If SaveOptions.DisableFormatting is enabled the output is not indented. - /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. - /// - /// An XML string. - public string ToString(SaveOptions options) - { - return GetXmlString(options); - } - - /// - /// Compares the values of two nodes, including the values of all descendant nodes. - /// - /// The first node to compare. - /// The second node to compare. - /// true if the nodes are equal, false otherwise. - /// - /// A null node is equal to another null node but unequal to a non-null - /// node. Two objects of different types are never equal. Two - /// nodes are equal if they contain the same text. Two - /// nodes are equal if they have the same tag name, the same - /// set of attributes with the same values, and, ignoring comments and processing - /// instructions, contain two equal length sequences of equal content nodes. - /// Two s are equal if their root nodes are equal. Two - /// nodes are equal if they contain the same comment text. - /// Two nodes are equal if they have the same - /// target and data. Two nodes are equal if the have the - /// same name, public id, system id, and internal subset. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Reviewed.")] - public static bool DeepEquals(XNode n1, XNode n2) - { - if (n1 == n2) return true; - if (n1 == null || n2 == null) return false; - return n1.DeepEquals(n2); - } - - /// - /// Write the current node to an . - /// - /// The to write the current node into. - public abstract void WriteTo(XmlWriter writer); - - internal virtual void AppendText(StringBuilder sb) - { - } - - internal abstract XNode CloneNode(); - - internal abstract bool DeepEquals(XNode node); - - internal IEnumerable GetAncestors(XName name, bool self) - { - XElement e = (self ? this : parent) as XElement; - while (e != null) - { - if (name == null || e.name == name) yield return e; - e = e.parent as XElement; - } - } - - IEnumerable GetElementsAfterSelf(XName name) - { - XNode n = this; - while (n.parent != null && n != n.parent.content) - { - n = n.next; - XElement e = n as XElement; - if (e != null && (name == null || e.name == name)) yield return e; - } - } - - IEnumerable GetElementsBeforeSelf(XName name) - { - if (parent != null) - { - XNode n = (XNode)parent.content; - do - { - n = n.next; - if (n == this) break; - XElement e = n as XElement; - if (e != null && (name == null || e.name == name)) yield return e; - } while (parent != null && parent == n.parent); - } - } - - internal abstract int GetDeepHashCode(); - - // The settings simulate a non-validating processor with the external - // entity resolution disabled. The processing of the internal subset is - // enabled by default. In order to prevent DoS attacks, the expanded - // size of the internal subset is limited to 10 million characters. - internal static XmlReaderSettings GetXmlReaderSettings(LoadOptions o) - { - XmlReaderSettings rs = new XmlReaderSettings(); - if ((o & LoadOptions.PreserveWhitespace) == 0) rs.IgnoreWhitespace = true; - - // DtdProcessing.Parse; Parse is not defined in the public contract - rs.DtdProcessing = (DtdProcessing)2; - rs.MaxCharactersFromEntities = (long)1e7; - // rs.XmlResolver = null; - return rs; - } - - internal static XmlWriterSettings GetXmlWriterSettings(SaveOptions o) - { - XmlWriterSettings ws = new XmlWriterSettings(); - if ((o & SaveOptions.DisableFormatting) == 0) ws.Indent = true; - if ((o & SaveOptions.OmitDuplicateNamespaces) != 0) ws.NamespaceHandling |= NamespaceHandling.OmitDuplicates; - return ws; - } - - string GetXmlString(SaveOptions o) - { - using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture)) - { - XmlWriterSettings ws = new XmlWriterSettings(); - ws.OmitXmlDeclaration = true; - if ((o & SaveOptions.DisableFormatting) == 0) ws.Indent = true; - if ((o & SaveOptions.OmitDuplicateNamespaces) != 0) ws.NamespaceHandling |= NamespaceHandling.OmitDuplicates; - if (this is XText) ws.ConformanceLevel = ConformanceLevel.Fragment; - using (XmlWriter w = XmlWriter.Create(sw, ws)) - { - XDocument n = this as XDocument; - if (n != null) - { - n.WriteContentTo(w); - } - else - { - WriteTo(w); - } - } - return sw.ToString(); - } - } - } - - /// - /// Contains functionality to compare nodes for their document order. - /// This class cannot be inherited. - /// - public sealed class XNodeDocumentOrderComparer : - IComparer, - IComparer - { - /// - /// Compares two nodes to determine their relative XML document order. - /// - /// The first node to compare. - /// The second node to compare. - /// - /// 0 if the nodes are equal; - /// -1 if x is before y; - /// 1 if x is after y. - /// - /// - /// Thrown if the two nodes do not share a common ancestor. - /// - public int Compare(XNode x, XNode y) - { - return XNode.CompareDocumentOrder(x, y); - } - - /// - /// Compares two nodes to determine their relative XML document order. - /// - /// The first node to compare. - /// The second node to compare. - /// - /// 0 if the nodes are equal; - /// -1 if x is before y; - /// 1 if x is after y. - /// - /// - /// Thrown if the two nodes do not share a common ancestor. - /// - /// - /// Thrown if either of the two nodes are not derived from XNode. - /// - int IComparer.Compare(object x, object y) - { - XNode n1 = x as XNode; - if (n1 == null && x != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "x"); - XNode n2 = y as XNode; - if (n2 == null && y != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "y"); - return Compare(n1, n2); - } - } - - /// - /// Contains functionality to compare nodes for value equality. - /// This class cannot be inherited. - /// - public sealed class XNodeEqualityComparer : - IEqualityComparer, - IEqualityComparer - { - /// - /// Compares the values of two nodes. - /// - /// The first node to compare. - /// The second node to compare. - /// true if the nodes are equal, false otherwise. - /// - /// A null node is equal to another null node but unequal to a non-null - /// node. Two s of different types are never equal. Two - /// nodes are equal if they contain the same text. Two - /// nodes are equal if they have the same tag name, the same - /// set of attributes with the same values, and, ignoring comments and processing - /// instructions, contain two equal length sequences of pairwise equal content nodes. - /// Two s are equal if their root nodes are equal. Two - /// nodes are equal if they contain the same comment text. - /// Two nodes are equal if they have the same - /// target and data. Two nodes are equal if the have the - /// same name, public id, system id, and internal subset. - /// - public bool Equals(XNode x, XNode y) - { - return XNode.DeepEquals(x, y); - } - - /// - /// Returns a hash code based on an objects value. - /// - /// The node to hash. - /// A value-based hash code for the node. - /// - /// The class's implementation of - /// is based on the referential identity of the node. This method computes a - /// hash code based on the value of the node. - /// - public int GetHashCode(XNode obj) - { - return obj != null ? obj.GetDeepHashCode() : 0; - } - - /// - /// Compares the values of two nodes. - /// - /// The first node to compare. - /// The second node to compare. - /// true if the nodes are equal, false otherwise. - /// - /// A null node is equal to another null node but unequal to a non-null - /// node. Two s of different types are never equal. Two - /// nodes are equal if they contain the same text. Two - /// nodes are equal if they have the same tag name, the same - /// set of attributes with the same values, and, ignoring comments and processing - /// instructions, contain two equal length sequences of pairwise equal content nodes. - /// Two s are equal if their root nodes are equal. Two - /// nodes are equal if they contain the same comment text. - /// Two nodes are equal if they have the same - /// target and data. Two nodes are equal if the have the - /// same name, public id, system id, and internal subset. - /// - bool IEqualityComparer.Equals(object x, object y) - { - XNode n1 = x as XNode; - if (n1 == null && x != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "x"); - XNode n2 = y as XNode; - if (n2 == null && y != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "y"); - return Equals(n1, n2); - } - - /// - /// Returns a hash code based on a node's value. - /// - /// The node to hash. - /// A value-based hash code for the node. - /// - /// The class's implementation of - /// is based on the referential identity of the node. This method computes a - /// hash code based on the value of the node. - /// - int IEqualityComparer.GetHashCode(object obj) - { - XNode n = obj as XNode; - if (n == null && obj != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "obj"); - return GetHashCode(n); - } - } - - /// - /// Represents a text node. - /// - public class XText : XNode - { - internal string text; - - /// - /// Initializes a new instance of the XText class. - /// - /// The string that contains the value of the text node. - public XText(string value) - { - if (value == null) throw new ArgumentNullException("value"); - text = value; - } - - /// - /// Initializes a new instance of the XText class from another XText object. - /// - /// The text node to copy from. - public XText(XText other) - { - if (other == null) throw new ArgumentNullException("other"); - text = other.text; - } - - internal XText(XmlReader r) - { - text = r.Value; - r.Read(); - } - - /// - /// Gets the node type for this node. - /// - /// - /// This property will always return XmlNodeType.Text. - /// - public override XmlNodeType NodeType - { - get - { - return XmlNodeType.Text; - } - } - - /// - /// Gets or sets the value of this node. - /// - public string Value - { - get - { - return text; - } - set - { - if (value == null) throw new ArgumentNullException("value"); - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); - text = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); - } - } - - /// - /// Write this to the given . - /// - /// - /// The to write this to. - /// - public override void WriteTo(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - if (parent is XDocument) - { - writer.WriteWhitespace(text); - } - else - { - writer.WriteString(text); - } - } - - internal override void AppendText(StringBuilder sb) - { - sb.Append(text); - } - - internal override XNode CloneNode() - { - return new XText(this); - } - - internal override bool DeepEquals(XNode node) - { - return node != null && NodeType == node.NodeType && text == ((XText)node).text; - } - - internal override int GetDeepHashCode() - { - return text.GetHashCode(); - } - } - - /// - /// Represents a text node that contains CDATA. - /// - public class XCData : XText - { - /// - /// Initializes a new instance of the XCData class. - /// - /// The string that contains the value of the XCData node. - public XCData(string value) : base(value) { } - - /// - /// Initializes a new instance of the XCData class from another XCData object. - /// - /// Text node to copy from - public XCData(XCData other) : base(other) { } - - internal XCData(XmlReader r) : base(r) { } - - /// - /// Gets the node type for this node. - /// - /// - /// This property will always return XmlNodeType.CDATA. - /// - public override XmlNodeType NodeType - { - get - { - return XmlNodeType.CDATA; - } - } - - /// - /// Write this to the given . - /// - /// - /// The to write this to. - /// - public override void WriteTo(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - writer.WriteCData(text); - } - - internal override XNode CloneNode() - { - return new XCData(this); - } - } - - /// - /// Represents a node that can contain other nodes. - /// - /// - /// The two classes that derive from are - /// and . - /// - public abstract class XContainer : XNode - { - internal object content; - - internal XContainer() { } - - internal XContainer(XContainer other) - { - if (other == null) throw new ArgumentNullException("other"); - if (other.content is string) - { - this.content = other.content; - } - else - { - XNode n = (XNode)other.content; - if (n != null) - { - do - { - n = n.next; - AppendNodeSkipNotify(n.CloneNode()); - } while (n != other.content); - } - } - } - - /// - /// Get the first child node of this node. - /// - public XNode FirstNode - { - get - { - XNode last = LastNode; - return last != null ? last.next : null; - } - } - - /// - /// Get the last child node of this node. - /// - public XNode LastNode - { - get - { - if (content == null) return null; - XNode n = content as XNode; - if (n != null) return n; - string s = content as string; - if (s != null) - { - if (s.Length == 0) return null; - XText t = new XText(s); - t.parent = this; - t.next = t; - Interlocked.CompareExchange(ref content, t, s); - } - return (XNode)content; - } - } - - /// - /// Adds the specified content as a child (or as children) to this XContainer. The - /// content can be simple content, a collection of content objects, a parameter list - /// of content objects, or null. - /// - /// - /// Adds the specified content as a child (or children) of this XContainer. - /// - /// - /// A content object containing simple content or a collection of content objects - /// to be added. - /// - /// - /// When adding simple content, a number of types may be passed to this method. - /// Valid types include: - /// - /// string - /// double - /// float - /// decimal - /// bool - /// DateTime - /// DateTimeOffset - /// TimeSpan - /// Any type implementing ToString() - /// Any type implementing IEnumerable - /// - /// - /// When adding complex content, a number of types may be passed to this method. - /// - /// XObject - /// XNode - /// XAttribute - /// Any type implementing IEnumerable - /// - /// - /// If an object implements IEnumerable, then the collection in the object is enumerated, - /// and all items in the collection are added. If the collection contains simple content, - /// then the simple content in the collection is concatenated and added as a single - /// string of simple content. If the collection contains complex content, then each item - /// in the collection is added separately. - /// - /// If content is null, nothing is added. This allows the results of a query to be passed - /// as content. If the query returns null, no contents are added, and this method does not - /// throw a NullReferenceException. - /// - /// Attributes and simple content can't be added to a document. - /// - /// An added attribute must have a unique name within the element to - /// which it is being added. - /// - public void Add(object content) - { - if (SkipNotify()) - { - AddContentSkipNotify(content); - return; - } - if (content == null) return; - XNode n = content as XNode; - if (n != null) - { - AddNode(n); - return; - } - string s = content as string; - if (s != null) - { - AddString(s); - return; - } - XAttribute a = content as XAttribute; - if (a != null) - { - AddAttribute(a); - return; - } - XStreamingElement x = content as XStreamingElement; - if (x != null) - { - AddNode(new XElement(x)); - return; - } - object[] o = content as object[]; - if (o != null) - { - foreach (object obj in o) Add(obj); - return; - } - IEnumerable e = content as IEnumerable; - if (e != null) - { - foreach (object obj in e) Add(obj); - return; - } - AddString(GetStringValue(content)); - } - - /// - /// Adds the specified content as a child (or children) of this XContainer. - /// - /// - /// A parameter list of content objects. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void Add(params object[] content) - { - Add((object)content); - } - - /// - /// Adds the specified content as the first child (or children) of this document or element. The - /// content can be simple content, a collection of content objects, a parameter - /// list of content objects, or null. - /// - /// - /// Adds the specified content as the first child (or children) of this document or element. - /// - /// - /// A content object containing simple content or a collection of content objects - /// to be added. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void AddFirst(object content) - { - new Inserter(this, null).Add(content); - } - - /// - /// Adds the specified content as the first children of this document or element. - /// - /// - /// A parameter list of content objects. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - /// - /// Thrown if the parent is null. - /// - public void AddFirst(params object[] content) - { - AddFirst((object)content); - } - - /// - /// Creates an used to add either nodes - /// or attributes to the . The later option - /// applies only for . - /// - /// An - public XmlWriter CreateWriter() - { - XmlWriterSettings settings = new XmlWriterSettings(); - settings.ConformanceLevel = this is XDocument ? ConformanceLevel.Document : ConformanceLevel.Fragment; - return XmlWriter.Create(new XNodeBuilder(this), settings); - } - - /// - /// Get descendant elements plus leaf nodes contained in an - /// - /// IEnumerable over all descendants - public IEnumerable DescendantNodes() - { - return GetDescendantNodes(false); - } - - /// - /// Returns the descendant s of this . Note this method will - /// not return itself in the resulting IEnumerable. See if you - /// need to include the current in the results. - /// - /// - /// - /// An IEnumerable of with all of the descendants below this in the XML tree. - /// - public IEnumerable Descendants() - { - return GetDescendants(null, false); - } - - /// - /// Returns the Descendant s with the passed in as an IEnumerable - /// of XElement. - /// - /// The to match against descendant s. - /// An of - public IEnumerable Descendants(XName name) - { - return name != null ? GetDescendants(name, false) : XElement.EmptySequence; - } - - /// - /// Returns the child element with this or null if there is no child element - /// with a matching . - /// - /// - /// - /// The to match against this s child elements. - /// - /// - /// An child that matches the passed in, or null. - /// - public XElement Element(XName name) - { - XNode n = content as XNode; - if (n != null) - { - do - { - n = n.next; - XElement e = n as XElement; - if (e != null && e.name == name) return e; - } while (n != content); - } - return null; - } - - /// - /// Returns the child s of this . - /// - /// - /// Returns all of the child elements of this . - /// - /// - /// An over all of this 's child s. - /// - public IEnumerable Elements() - { - return GetElements(null); - } - - /// - /// Returns the child elements of this that match the passed in. - /// - /// - /// The to match against the children of this . - /// - /// - /// An of children of this that have - /// a matching . - /// - public IEnumerable Elements(XName name) - { - return name != null ? GetElements(name) : XElement.EmptySequence; - } - - /// - /// Returns the content of this . Note that the content does not - /// include s. - /// - /// - /// - /// Returns the content of this as an of . Note - /// that the content does not include s. - /// - /// - /// The contents of this - public IEnumerable Nodes() - { - XNode n = LastNode; - if (n != null) - { - do - { - n = n.next; - yield return n; - } while (n.parent == this && n != content); - } - } - - /// - /// Removes the nodes from this . Note this - /// methods does not remove attributes. See . - /// - /// - public void RemoveNodes() - { - if (SkipNotify()) - { - RemoveNodesSkipNotify(); - return; - } - while (content != null) - { - string s = content as string; - if (s != null) - { - if (s.Length > 0) - { - ConvertTextToNode(); - } - else - { - if (this is XElement) - { - // Change in the serialization of an empty element: - // from start/end tag pair to empty tag - NotifyChanging(this, XObjectChangeEventArgs.Value); - if ((object)s != (object)content) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - content = null; - NotifyChanged(this, XObjectChangeEventArgs.Value); - } - else - { - content = null; - } - } - } - XNode last = content as XNode; - if (last != null) - { - XNode n = last.next; - NotifyChanging(n, XObjectChangeEventArgs.Remove); - if (last != content || n != last.next) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - if (n != last) - { - last.next = n.next; - } - else - { - content = null; - } - n.parent = null; - n.next = null; - NotifyChanged(n, XObjectChangeEventArgs.Remove); - } - } - } - - /// - /// Replaces the children nodes of this document or element with the specified content. The - /// content can be simple content, a collection of content objects, a parameter - /// list of content objects, or null. - /// - /// - /// Replaces the children nodes of this document or element with the specified content. - /// - /// - /// A content object containing simple content or a collection of content objects - /// that replace the children nodes. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void ReplaceNodes(object content) - { - content = GetContentSnapshot(content); - RemoveNodes(); - Add(content); - } - - /// - /// Replaces the children nodes of this document or element with the specified content. - /// - /// - /// A parameter list of content objects. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void ReplaceNodes(params object[] content) - { - ReplaceNodes((object)content); - } - - internal virtual void AddAttribute(XAttribute a) - { - } - - internal virtual void AddAttributeSkipNotify(XAttribute a) - { - } - - internal void AddContentSkipNotify(object content) - { - if (content == null) return; - XNode n = content as XNode; - if (n != null) - { - AddNodeSkipNotify(n); - return; - } - string s = content as string; - if (s != null) - { - AddStringSkipNotify(s); - return; - } - XAttribute a = content as XAttribute; - if (a != null) - { - AddAttributeSkipNotify(a); - return; - } - XStreamingElement x = content as XStreamingElement; - if (x != null) - { - AddNodeSkipNotify(new XElement(x)); - return; - } - object[] o = content as object[]; - if (o != null) - { - foreach (object obj in o) AddContentSkipNotify(obj); - return; - } - IEnumerable e = content as IEnumerable; - if (e != null) - { - foreach (object obj in e) AddContentSkipNotify(obj); - return; - } - AddStringSkipNotify(GetStringValue(content)); - } - - internal void AddNode(XNode n) - { - ValidateNode(n, this); - if (n.parent != null) - { - n = n.CloneNode(); - } - else - { - XNode p = this; - while (p.parent != null) p = p.parent; - if (n == p) n = n.CloneNode(); - } - ConvertTextToNode(); - AppendNode(n); - } - - internal void AddNodeSkipNotify(XNode n) - { - ValidateNode(n, this); - if (n.parent != null) - { - n = n.CloneNode(); - } - else - { - XNode p = this; - while (p.parent != null) p = p.parent; - if (n == p) n = n.CloneNode(); - } - ConvertTextToNode(); - AppendNodeSkipNotify(n); - } - - internal void AddString(string s) - { - ValidateString(s); - if (content == null) - { - if (s.Length > 0) - { - AppendNode(new XText(s)); - } - else - { - if (this is XElement) - { - // Change in the serialization of an empty element: - // from empty tag to start/end tag pair - NotifyChanging(this, XObjectChangeEventArgs.Value); - if (content != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - content = s; - NotifyChanged(this, XObjectChangeEventArgs.Value); - } - else - { - content = s; - } - } - } - else if (s.Length > 0) - { - ConvertTextToNode(); - XText tn = content as XText; - if (tn != null && !(tn is XCData)) - { - tn.Value += s; - } - else - { - AppendNode(new XText(s)); - } - } - } - - internal void AddStringSkipNotify(string s) - { - ValidateString(s); - if (content == null) - { - content = s; - } - else if (s.Length > 0) - { - if (content is string) - { - content = (string)content + s; - } - else - { - XText tn = content as XText; - if (tn != null && !(tn is XCData)) - { - tn.text += s; - } - else - { - AppendNodeSkipNotify(new XText(s)); - } - } - } - } - - internal void AppendNode(XNode n) - { - bool notify = NotifyChanging(n, XObjectChangeEventArgs.Add); - if (n.parent != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - AppendNodeSkipNotify(n); - if (notify) NotifyChanged(n, XObjectChangeEventArgs.Add); - } - - internal void AppendNodeSkipNotify(XNode n) - { - n.parent = this; - if (content == null || content is string) - { - n.next = n; - } - else - { - XNode x = (XNode)content; - n.next = x.next; - x.next = n; - } - content = n; - } - - internal override void AppendText(StringBuilder sb) - { - string s = content as string; - if (s != null) - { - sb.Append(s); - } - else - { - XNode n = (XNode)content; - if (n != null) - { - do - { - n = n.next; - n.AppendText(sb); - } while (n != content); - } - } - } - - string GetTextOnly() - { - if (content == null) return null; - string s = content as string; - if (s == null) - { - XNode n = (XNode)content; - do - { - n = n.next; - if (n.NodeType != XmlNodeType.Text) return null; - s += ((XText)n).Value; - } while (n != content); - } - return s; - } - - string CollectText(ref XNode n) - { - string s = ""; - while (n != null && n.NodeType == XmlNodeType.Text) - { - s += ((XText)n).Value; - n = n != content ? n.next : null; - } - return s; - } - - internal bool ContentsEqual(XContainer e) - { - if (content == e.content) return true; - string s = GetTextOnly(); - if (s != null) return s == e.GetTextOnly(); - XNode n1 = content as XNode; - XNode n2 = e.content as XNode; - if (n1 != null && n2 != null) - { - n1 = n1.next; - n2 = n2.next; - while (true) - { - if (CollectText(ref n1) != e.CollectText(ref n2)) break; - if (n1 == null && n2 == null) return true; - if (n1 == null || n2 == null || !n1.DeepEquals(n2)) break; - n1 = n1 != content ? n1.next : null; - n2 = n2 != e.content ? n2.next : null; - } - } - return false; - } - - internal int ContentsHashCode() - { - string s = GetTextOnly(); - if (s != null) return s.GetHashCode(); - int h = 0; - XNode n = content as XNode; - if (n != null) - { - do - { - n = n.next; - string text = CollectText(ref n); - if (text.Length > 0) - { - h ^= text.GetHashCode(); - } - if (n == null) break; - h ^= n.GetDeepHashCode(); - } while (n != content); - } - return h; - } - - internal void ConvertTextToNode() - { - string s = content as string; - if (s != null && s.Length > 0) - { - XText t = new XText(s); - t.parent = this; - t.next = t; - content = t; - } - } - - internal IEnumerable GetDescendantNodes(bool self) - { - if (self) yield return this; - XNode n = this; - while (true) - { - XContainer c = n as XContainer; - XNode first; - if (c != null && (first = c.FirstNode) != null) - { - n = first; - } - else - { - while (n != null && n != this && n == n.parent.content) n = n.parent; - if (n == null || n == this) break; - n = n.next; - } - yield return n; - } - } - - internal IEnumerable GetDescendants(XName name, bool self) - { - if (self) - { - XElement e = (XElement)this; - if (name == null || e.name == name) yield return e; - } - XNode n = this; - XContainer c = this; - while (true) - { - if (c != null && c.content is XNode) - { - n = ((XNode)c.content).next; - } - else - { - while (n != this && n == n.parent.content) n = n.parent; - if (n == this) break; - n = n.next; - } - XElement e = n as XElement; - if (e != null && (name == null || e.name == name)) yield return e; - c = e; - } - } - - IEnumerable GetElements(XName name) - { - XNode n = content as XNode; - if (n != null) - { - do - { - n = n.next; - XElement e = n as XElement; - if (e != null && (name == null || e.name == name)) yield return e; - } while (n.parent == this && n != content); - } - } - - internal static string GetStringValue(object value) - { - string s; - if (value is string) - { - s = (string)value; - } - else if (value is double) - { - s = XmlConvert.ToString((double)value); - } - else if (value is float) - { - s = XmlConvert.ToString((float)value); - } - else if (value is decimal) - { - s = XmlConvert.ToString((decimal)value); - } - else if (value is bool) - { - s = XmlConvert.ToString((bool)value); - } - else if (value is DateTime) - { - s = ((DateTime)value).ToString("o"); // Round-trip date/time pattern. - } - else if (value is DateTimeOffset) - { - s = XmlConvert.ToString((DateTimeOffset)value); - } - else if (value is TimeSpan) - { - s = XmlConvert.ToString((TimeSpan)value); - } - else if (value is XObject) - { - throw new ArgumentException(SR.Argument_XObjectValue); - } - else - { - s = value.ToString(); - } - if (s == null) throw new ArgumentException(SR.Argument_ConvertToString); - return s; - } - - internal void ReadContentFrom(XmlReader r) - { - if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive); - XContainer c = this; - NamespaceCache eCache = new NamespaceCache(); - NamespaceCache aCache = new NamespaceCache(); - do - { - switch (r.NodeType) - { - case XmlNodeType.Element: - XElement e = new XElement(eCache.Get(r.NamespaceURI).GetName(r.LocalName)); - if (r.MoveToFirstAttribute()) - { - do - { - e.AppendAttributeSkipNotify(new XAttribute(aCache.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName), r.Value)); - } while (r.MoveToNextAttribute()); - r.MoveToElement(); - } - c.AddNodeSkipNotify(e); - if (!r.IsEmptyElement) - { - c = e; - } - break; - case XmlNodeType.EndElement: - if (c.content == null) - { - c.content = string.Empty; - } - if (c == this) return; - c = c.parent; - break; - case XmlNodeType.Text: - case XmlNodeType.SignificantWhitespace: - case XmlNodeType.Whitespace: - c.AddStringSkipNotify(r.Value); - break; - case XmlNodeType.CDATA: - c.AddNodeSkipNotify(new XCData(r.Value)); - break; - case XmlNodeType.Comment: - c.AddNodeSkipNotify(new XComment(r.Value)); - break; - case XmlNodeType.ProcessingInstruction: - c.AddNodeSkipNotify(new XProcessingInstruction(r.Name, r.Value)); - break; - case XmlNodeType.DocumentType: - c.AddNodeSkipNotify(new XDocumentType(r.LocalName, r.GetAttribute("PUBLIC"), r.GetAttribute("SYSTEM"), r.Value)); - break; - case XmlNodeType.EntityReference: - if (!r.CanResolveEntity) throw new InvalidOperationException(SR.InvalidOperation_UnresolvedEntityReference); - r.ResolveEntity(); - break; - case XmlNodeType.EndEntity: - break; - default: - throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, r.NodeType)); - } - } while (r.Read()); - } - - internal void ReadContentFrom(XmlReader r, LoadOptions o) - { - if ((o & (LoadOptions.SetBaseUri | LoadOptions.SetLineInfo)) == 0) - { - ReadContentFrom(r); - return; - } - if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive); - XContainer c = this; - XNode n = null; - NamespaceCache eCache = new NamespaceCache(); - NamespaceCache aCache = new NamespaceCache(); - string baseUri = (o & LoadOptions.SetBaseUri) != 0 ? r.BaseURI : null; - IXmlLineInfo li = (o & LoadOptions.SetLineInfo) != 0 ? r as IXmlLineInfo : null; - do - { - string uri = r.BaseURI; - switch (r.NodeType) - { - case XmlNodeType.Element: - { - XElement e = new XElement(eCache.Get(r.NamespaceURI).GetName(r.LocalName)); - if (baseUri != null && baseUri != uri) - { - e.SetBaseUri(uri); - } - if (li != null && li.HasLineInfo()) - { - e.SetLineInfo(li.LineNumber, li.LinePosition); - } - if (r.MoveToFirstAttribute()) - { - do - { - XAttribute a = new XAttribute(aCache.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName), r.Value); - if (li != null && li.HasLineInfo()) - { - a.SetLineInfo(li.LineNumber, li.LinePosition); - } - e.AppendAttributeSkipNotify(a); - } while (r.MoveToNextAttribute()); - r.MoveToElement(); - } - c.AddNodeSkipNotify(e); - if (!r.IsEmptyElement) - { - c = e; - if (baseUri != null) - { - baseUri = uri; - } - } - break; - } - case XmlNodeType.EndElement: - { - if (c.content == null) - { - c.content = string.Empty; - } - // Store the line info of the end element tag. - // Note that since we've got EndElement the current container must be an XElement - XElement e = c as XElement; - Debug.Assert(e != null, "EndElement recieved but the current container is not an element."); - if (e != null && li != null && li.HasLineInfo()) - { - e.SetEndElementLineInfo(li.LineNumber, li.LinePosition); - } - if (c == this) return; - if (baseUri != null && c.HasBaseUri) - { - baseUri = c.parent.BaseUri; - } - c = c.parent; - break; - } - case XmlNodeType.Text: - case XmlNodeType.SignificantWhitespace: - case XmlNodeType.Whitespace: - if ((baseUri != null && baseUri != uri) || - (li != null && li.HasLineInfo())) - { - n = new XText(r.Value); - } - else - { - c.AddStringSkipNotify(r.Value); - } - break; - case XmlNodeType.CDATA: - n = new XCData(r.Value); - break; - case XmlNodeType.Comment: - n = new XComment(r.Value); - break; - case XmlNodeType.ProcessingInstruction: - n = new XProcessingInstruction(r.Name, r.Value); - break; - case XmlNodeType.DocumentType: - n = new XDocumentType(r.LocalName, r.GetAttribute("PUBLIC"), r.GetAttribute("SYSTEM"), r.Value); - break; - case XmlNodeType.EntityReference: - if (!r.CanResolveEntity) throw new InvalidOperationException(SR.InvalidOperation_UnresolvedEntityReference); - r.ResolveEntity(); - break; - case XmlNodeType.EndEntity: - break; - default: - throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, r.NodeType)); - } - if (n != null) - { - if (baseUri != null && baseUri != uri) - { - n.SetBaseUri(uri); - } - if (li != null && li.HasLineInfo()) - { - n.SetLineInfo(li.LineNumber, li.LinePosition); - } - c.AddNodeSkipNotify(n); - n = null; - } - } while (r.Read()); - } - - internal void RemoveNode(XNode n) - { - bool notify = NotifyChanging(n, XObjectChangeEventArgs.Remove); - if (n.parent != this) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - XNode p = (XNode)content; - while (p.next != n) p = p.next; - if (p == n) - { - content = null; - } - else - { - if (content == n) content = p; - p.next = n.next; - } - n.parent = null; - n.next = null; - if (notify) NotifyChanged(n, XObjectChangeEventArgs.Remove); - } - - void RemoveNodesSkipNotify() - { - XNode n = content as XNode; - if (n != null) - { - do - { - XNode next = n.next; - n.parent = null; - n.next = null; - n = next; - } while (n != content); - } - content = null; - } - - // Validate insertion of the given node. previous is the node after which insertion - // will occur. previous == null means at beginning, previous == this means at end. - internal virtual void ValidateNode(XNode node, XNode previous) - { - } - - internal virtual void ValidateString(string s) - { - } - - internal void WriteContentTo(XmlWriter writer) - { - if (content != null) - { - if (content is string) - { - if (this is XDocument) - { - writer.WriteWhitespace((string)content); - } - else - { - writer.WriteString((string)content); - } - } - else - { - XNode n = (XNode)content; - do - { - n = n.next; - n.WriteTo(writer); - } while (n != content); - } - } - } - - static void AddContentToList(List list, object content) - { - IEnumerable e = content is string ? null : content as IEnumerable; - if (e == null) - { - list.Add(content); - } - else - { - foreach (object obj in e) - { - if (obj != null) AddContentToList(list, obj); - } - } - } - - static internal object GetContentSnapshot(object content) - { - if (content is string || !(content is IEnumerable)) return content; - List list = new List(); - AddContentToList(list, content); - return list; - } - } - - internal struct Inserter - { - XContainer parent; - XNode previous; - string text; - - public Inserter(XContainer parent, XNode anchor) - { - this.parent = parent; - this.previous = anchor; - this.text = null; - } - - public void Add(object content) - { - AddContent(content); - if (text != null) - { - if (parent.content == null) - { - if (parent.SkipNotify()) - { - parent.content = text; - } - else - { - if (text.Length > 0) - { - InsertNode(new XText(text)); - } - else - { - if (parent is XElement) - { - // Change in the serialization of an empty element: - // from empty tag to start/end tag pair - parent.NotifyChanging(parent, XObjectChangeEventArgs.Value); - if (parent.content != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - parent.content = text; - parent.NotifyChanged(parent, XObjectChangeEventArgs.Value); - } - else - { - parent.content = text; - } - } - } - } - else if (text.Length > 0) - { - if (previous is XText && !(previous is XCData)) - { - ((XText)previous).Value += text; - } - else - { - parent.ConvertTextToNode(); - InsertNode(new XText(text)); - } - } - } - } - - void AddContent(object content) - { - if (content == null) return; - XNode n = content as XNode; - if (n != null) - { - AddNode(n); - return; - } - string s = content as string; - if (s != null) - { - AddString(s); - return; - } - XStreamingElement x = content as XStreamingElement; - if (x != null) - { - AddNode(new XElement(x)); - return; - } - object[] o = content as object[]; - if (o != null) - { - foreach (object obj in o) AddContent(obj); - return; - } - IEnumerable e = content as IEnumerable; - if (e != null) - { - foreach (object obj in e) AddContent(obj); - return; - } - if (content is XAttribute) throw new ArgumentException(SR.Argument_AddAttribute); - AddString(XContainer.GetStringValue(content)); - } - - void AddNode(XNode n) - { - parent.ValidateNode(n, previous); - if (n.parent != null) - { - n = n.CloneNode(); - } - else - { - XNode p = parent; - while (p.parent != null) p = p.parent; - if (n == p) n = n.CloneNode(); - } - parent.ConvertTextToNode(); - if (text != null) - { - if (text.Length > 0) - { - if (previous is XText && !(previous is XCData)) - { - ((XText)previous).Value += text; - } - else - { - InsertNode(new XText(text)); - } - } - text = null; - } - InsertNode(n); - } - - void AddString(string s) - { - parent.ValidateString(s); - text += s; - } - - // Prepends if previous == null, otherwise inserts after previous - void InsertNode(XNode n) - { - bool notify = parent.NotifyChanging(n, XObjectChangeEventArgs.Add); - if (n.parent != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - n.parent = parent; - if (parent.content == null || parent.content is string) - { - n.next = n; - parent.content = n; - } - else if (previous == null) - { - XNode last = (XNode)parent.content; - n.next = last.next; - last.next = n; - } - else - { - n.next = previous.next; - previous.next = n; - if (parent.content == previous) parent.content = n; - } - previous = n; - if (notify) parent.NotifyChanged(n, XObjectChangeEventArgs.Add); - } - } - - internal struct NamespaceCache - { - XNamespace ns; - string namespaceName; - - public XNamespace Get(string namespaceName) - { - if ((object)namespaceName == (object)this.namespaceName) return this.ns; - this.namespaceName = namespaceName; - this.ns = XNamespace.Get(namespaceName); - return this.ns; - } - } - - /// - /// Represents an XML element. - /// - /// - /// An element has an , optionally one or more attributes, - /// and can optionally contain content (see . - /// An can contain the following types of content: - /// - /// string (Text content) - /// - /// - /// - /// - /// - public class XElement : XContainer - { - static IEnumerable emptySequence; - - /// - /// Gets an empty collection of elements. - /// - public static IEnumerable EmptySequence - { - get - { - if (emptySequence == null) emptySequence = new XElement[0]; - return emptySequence; - } - } - - internal XName name; - internal XAttribute lastAttr; - - /// - /// Initializes a new instance of the XElement class with the specified name. - /// - /// - /// The name of the element. - /// - public XElement(XName name) - { - if (name == null) throw new ArgumentNullException("name"); - this.name = name; - } - - /// - /// Initializes a new instance of the XElement class with the specified name and content. - /// - /// - /// The element name. - /// - /// The initial contents of the element. - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public XElement(XName name, object content) : this(name) - { - AddContentSkipNotify(content); - } - - /// - /// Initializes a new instance of the XElement class with the specified name and content. - /// - /// - /// The element name. - /// - /// - /// The initial content of the element. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public XElement(XName name, params object[] content) : this(name, (object)content) { } - - /// - /// Initializes a new instance of the XElement class from another XElement object. - /// - /// - /// Another element that will be copied to this element. - /// - /// - /// This constructor makes a deep copy from one element to another. - /// - public XElement(XElement other) : base(other) - { - this.name = other.name; - XAttribute a = other.lastAttr; - if (a != null) - { - do - { - a = a.next; - AppendAttributeSkipNotify(new XAttribute(a)); - } while (a != other.lastAttr); - } - } - - /// - /// Initializes an XElement object from an object. - /// - /// - /// The object whose value will be used - /// to initialise the new element. - /// - public XElement(XStreamingElement other) - { - if (other == null) throw new ArgumentNullException("other"); - name = other.name; - AddContentSkipNotify(other.content); - } - - internal XElement() : this("default") - { - } - - internal XElement(XmlReader r) : this(r, LoadOptions.None) - { - } - - internal XElement(XmlReader r, LoadOptions o) - { - ReadElementFrom(r, o); - } - - /// - /// Gets the first attribute of an element. - /// - public XAttribute FirstAttribute - { - get { return lastAttr != null ? lastAttr.next : null; } - } - - /// - /// Gets a value indicating whether the element has at least one attribute. - /// - public bool HasAttributes - { - get { return lastAttr != null; } - } - - /// - /// Gets a value indicating whether the element has at least one child element. - /// - public bool HasElements - { - get - { - XNode n = content as XNode; - if (n != null) - { - do - { - if (n is XElement) return true; - n = n.next; - } while (n != content); - } - return false; - } - } - - /// - /// Gets a value indicating whether the element contains no content. - /// - public bool IsEmpty - { - get { return content == null; } - } - - /// - /// Gets the last attribute of an element. - /// - public XAttribute LastAttribute - { - get { return lastAttr; } - } - - /// - /// Gets the name of this element. - /// - public XName Name - { - get - { - return name; - } - set - { - if (value == null) throw new ArgumentNullException("value"); - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Name); - name = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Name); - } - } - - /// - /// Gets the node type for this node. - /// - /// - /// This property will always return XmlNodeType.Text. - /// - public override XmlNodeType NodeType - { - get - { - return XmlNodeType.Element; - } - } - - /// - /// Gets the text contents of this element. - /// - /// - /// If there is text content interspersed with nodes (mixed content) then the text content - /// will be concatenated and returned. - /// - public string Value - { - get - { - if (content == null) return string.Empty; - string s = content as string; - if (s != null) return s; - StringBuilder sb = new StringBuilder(); - AppendText(sb); - return sb.ToString(); - } - set - { - if (value == null) throw new ArgumentNullException("value"); - RemoveNodes(); - Add(value); - } - } - - /// - /// Returns this and all of it's ancestors up - /// to the root node. Optionally an can be passed - /// in to target a specific ancestor(s). - /// - /// - /// - /// Returns this and all of it's ancestors up to - /// the root node. - /// - /// - /// - /// An of containing all of - /// this 's ancestors up to the root node (including - /// this . - /// - public IEnumerable AncestorsAndSelf() - { - return GetAncestors(null, true); - } - - /// - /// Returns the ancestor(s) of this with the matching - /// . If this 's - /// matches the passed in then it will be invluded in the - /// resulting or . - /// - /// - /// - /// The of the target ancestor. - /// - /// - /// An of containing the - /// ancestors of this with a matching . - /// - public IEnumerable AncestorsAndSelf(XName name) - { - return name != null ? GetAncestors(name, true) : XElement.EmptySequence; - } - - /// - /// Returns the associated with this that has this - /// . - /// - /// - /// The of the to get. - /// - /// - /// The with the passed in. If there is no - /// with this then null is returned. - /// - public XAttribute Attribute(XName name) - { - XAttribute a = lastAttr; - if (a != null) - { - do - { - a = a.next; - if (a.name == name) return a; - } while (a != lastAttr); - } - return null; - } - - /// - /// Returns the associated with this . Optionally - /// an can be given to target a specific (s). - /// - /// - /// Returns all of the s associated with this . - /// - /// - /// - /// An of containing all of the s - /// associated with this . - /// - public IEnumerable Attributes() - { - return GetAttributes(null); - } - - /// - /// Returns the (s) associated with this that has the passed - /// in . - /// - /// - /// - /// The of the targeted . - /// - /// - /// The (s) with the matching - /// - public IEnumerable Attributes(XName name) - { - return name != null ? GetAttributes(name) : XAttribute.EmptySequence; - } - - /// - /// Get the self and descendant nodes for an - /// - /// - public IEnumerable DescendantNodesAndSelf() - { - return GetDescendantNodes(true); - } - - /// - /// Returns this and all of it's descendants. Overloads allow - /// specification of a type of descendant to return, or a specific - /// of a descendant to match. - /// - /// - /// Returns this and all of it's descendant s - /// as an of . - /// - /// - /// - /// An of containing this - /// and all of it's descendants. - /// - public IEnumerable DescendantsAndSelf() - { - return GetDescendants(null, true); - } - - /// - /// Returns the descendants of this that have a matching - /// to the one passed in, including, potentially, this . - /// - /// - /// - /// The of the descendant that is being targeted. - /// - /// - /// An of containing all of the descendant - /// s that have this . - /// - public IEnumerable DescendantsAndSelf(XName name) - { - return name != null ? GetDescendants(name, true) : XElement.EmptySequence; - } - - /// - /// Returns the default of an - /// - public XNamespace GetDefaultNamespace() - { - string namespaceName = GetNamespaceOfPrefixInScope("xmlns", null); - return namespaceName != null ? XNamespace.Get(namespaceName) : XNamespace.None; - } - - /// - /// Get the namespace associated with a particular prefix for this - /// in its document context. - /// - /// The namespace prefix to look up - /// An for the namespace bound to the prefix - public XNamespace GetNamespaceOfPrefix(string prefix) - { - if (prefix == null) throw new ArgumentNullException("prefix"); - if (prefix.Length == 0) throw new ArgumentException(SR.Format(SR.Argument_InvalidPrefix, prefix)); - if (prefix == "xmlns") return XNamespace.Xmlns; - string namespaceName = GetNamespaceOfPrefixInScope(prefix, null); - if (namespaceName != null) return XNamespace.Get(namespaceName); - if (prefix == "xml") return XNamespace.Xml; - return null; - } - - /// - /// Get the prefix associated with a namespace for an element in its context. - /// - /// The for which to get a prefix - /// The namespace prefix string - public string GetPrefixOfNamespace(XNamespace ns) - { - if (ns == null) throw new ArgumentNullException("ns"); - string namespaceName = ns.NamespaceName; - bool hasInScopeNamespace = false; - XElement e = this; - do - { - XAttribute a = e.lastAttr; - if (a != null) - { - bool hasLocalNamespace = false; - do - { - a = a.next; - if (a.IsNamespaceDeclaration) - { - if (a.Value == namespaceName) - { - if (a.Name.NamespaceName.Length != 0 && - (!hasInScopeNamespace || - GetNamespaceOfPrefixInScope(a.Name.LocalName, e) == null)) - { - return a.Name.LocalName; - } - } - hasLocalNamespace = true; - } - } - while (a != e.lastAttr); - hasInScopeNamespace |= hasLocalNamespace; - } - e = e.parent as XElement; - } - while (e != null); - if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) - { - if (!hasInScopeNamespace || GetNamespaceOfPrefixInScope("xml", null) == null) return "xml"; - } - else if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) - { - return "xmlns"; - } - return null; - } - - /// - /// The Load method provides multiple strategies for creating a new - /// and initializing it from a data source containing - /// raw XML. Load from a file (passing in a URI to the file), an - /// , a , or an - /// . Note: Use - /// to create an from a string containing XML. - /// - /// - /// - /// - /// Create a new based on the contents of the file - /// referenced by the URI parameter passed in. Note: Use - /// to create an from - /// a string containing XML. - /// - /// - /// - /// - /// - /// This method uses the method to create - /// an to read the raw XML into the underlying - /// XML tree. - /// - /// - /// A URI string referencing the file to load into a new . - /// - /// - /// An initialized with the contents of the file referenced - /// in the passed in uri parameter. - /// - [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "Back-compat with System.Xml.")] - public static XElement Load(string uri) - { - return Load(uri, LoadOptions.None); - } - - /// - /// Create a new based on the contents of the file - /// referenced by the URI parameter passed in. Optionally, whitespace can be preserved. - /// - /// - /// - /// - /// This method uses the method to create - /// an to read the raw XML into an underlying - /// XML tree. If LoadOptions.PreserveWhitespace is enabled then - /// the property - /// is set to false. - /// - /// - /// A string representing the URI of the file to be loaded into a new . - /// - /// - /// A set of . - /// - /// - /// An initialized with the contents of the file referenced - /// in the passed uri parameter. If LoadOptions.PreserveWhitespace is enabled then - /// significant whitespace will be preserved. - /// - [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "Back-compat with System.Xml.")] - public static XElement Load(string uri, LoadOptions options) - { - XmlReaderSettings rs = GetXmlReaderSettings(options); - using (XmlReader r = XmlReader.Create(uri, rs)) - { - return Load(r, options); - } - } - - /// - /// Create a new and initialize its underlying XML tree using - /// the passed parameter. - /// - /// - /// A containing the raw XML to read into the newly - /// created . - /// - /// - /// A new containing the contents of the passed in - /// . - /// - public static XElement Load(Stream stream) - { - return Load(stream, LoadOptions.None); - } - - /// - /// Create a new and initialize its underlying XML tree using - /// the passed parameter. Optionally whitespace handling - /// can be preserved. - /// - /// - /// If LoadOptions.PreserveWhitespace is enabled then - /// the property - /// is set to false. - /// - /// - /// A containing the raw XML to read into the newly - /// created . - /// - /// - /// A set of . - /// - /// - /// A new containing the contents of the passed in - /// . - /// - public static XElement Load(Stream stream, LoadOptions options) - { - XmlReaderSettings rs = GetXmlReaderSettings(options); - using (XmlReader r = XmlReader.Create(stream, rs)) - { - return Load(r, options); - } - } - /// - /// Create a new and initialize its underlying XML tree using - /// the passed parameter. - /// - /// - /// A containing the raw XML to read into the newly - /// created . - /// - /// - /// A new containing the contents of the passed in - /// . - /// - public static XElement Load(TextReader textReader) - { - return Load(textReader, LoadOptions.None); - } - - /// - /// Create a new and initialize its underlying XML tree using - /// the passed parameter. Optionally whitespace handling - /// can be preserved. - /// - /// - /// If LoadOptions.PreserveWhitespace is enabled then - /// the property - /// is set to false. - /// - /// - /// A containing the raw XML to read into the newly - /// created . - /// - /// - /// A set of . - /// - /// - /// A new containing the contents of the passed in - /// . - /// - public static XElement Load(TextReader textReader, LoadOptions options) - { - XmlReaderSettings rs = GetXmlReaderSettings(options); - using (XmlReader r = XmlReader.Create(textReader, rs)) - { - return Load(r, options); - } - } - - /// - /// Create a new containing the contents of the - /// passed in . - /// - /// - /// An containing the XML to be read into the new - /// . - /// - /// - /// A new containing the contents of the passed - /// in . - /// - public static XElement Load(XmlReader reader) - { - return Load(reader, LoadOptions.None); - } - - /// - /// Create a new containing the contents of the - /// passed in . - /// - /// - /// An containing the XML to be read into the new - /// . - /// - /// - /// A set of . - /// - /// - /// A new containing the contents of the passed - /// in . - /// - public static XElement Load(XmlReader reader, LoadOptions options) - { - if (reader == null) throw new ArgumentNullException("reader"); - if (reader.MoveToContent() != XmlNodeType.Element) throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ExpectedNodeType, XmlNodeType.Element, reader.NodeType)); - XElement e = new XElement(reader, options); - reader.MoveToContent(); - if (!reader.EOF) throw new InvalidOperationException(SR.InvalidOperation_ExpectedEndOfFile); - return e; - } - - /// - /// Parses a string containing XML into an . Optionally - /// whitespace can be preserved. - /// - /// - /// Parses a string containing XML into an . - /// - /// - /// The XML must contain only one root node. - /// - /// - /// A string containing the XML to parse into an . - /// - /// - /// An created from the XML string passed in. - /// - public static XElement Parse(string text) - { - return Parse(text, LoadOptions.None); - } - - /// - /// Parses a string containing XML into an and - /// optionally preserves the Whitespace. See . - /// - /// - /// - /// The XML must contain only one root node. - /// - /// If LoadOptions.PreserveWhitespace is enabled the underlying - /// ' - /// property will be set to false. - /// - /// - /// - /// - /// A string containing the XML to parse into an . - /// - /// - /// A set of . - /// - /// - /// An created from the XML string passed in. - /// - public static XElement Parse(string text, LoadOptions options) - { - using (StringReader sr = new StringReader(text)) - { - XmlReaderSettings rs = GetXmlReaderSettings(options); - using (XmlReader r = XmlReader.Create(sr, rs)) - { - return Load(r, options); - } - } - } - - /// - /// Removes content and attributes from this . - /// - /// - /// - public void RemoveAll() - { - RemoveAttributes(); - RemoveNodes(); - } - - /// - /// Removes that attributes of this . - /// - /// - /// - public void RemoveAttributes() - { - if (SkipNotify()) - { - RemoveAttributesSkipNotify(); - return; - } - while (lastAttr != null) - { - XAttribute a = lastAttr.next; - NotifyChanging(a, XObjectChangeEventArgs.Remove); - if (lastAttr == null || a != lastAttr.next) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - if (a != lastAttr) - { - lastAttr.next = a.next; - } - else - { - lastAttr = null; - } - a.parent = null; - a.next = null; - NotifyChanged(a, XObjectChangeEventArgs.Remove); - } - } - - /// - /// Replaces the child nodes and the attributes of this element with the - /// specified content. The content can be simple content, a collection of - /// content objects, a parameter list of content objects, or null. - /// - /// - /// Replaces the children nodes and the attributes of this element with the specified content. - /// - /// - /// The content that will replace the child nodes and attributes of this element. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void ReplaceAll(object content) - { - content = GetContentSnapshot(content); - RemoveAll(); - Add(content); - } - - /// - /// Replaces the children nodes and the attributes of this element with the specified content. - /// - /// - /// A parameter list of content objects. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void ReplaceAll(params object[] content) - { - ReplaceAll((object)content); - } - - /// - /// Replaces the attributes of this element with the specified content. - /// The content can be simple content, a collection of - /// content objects, a parameter list of content objects, or null. - /// - /// - /// Replaces the attributes of this element with the specified content. - /// - /// - /// The content that will replace the attributes of this element. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void ReplaceAttributes(object content) - { - content = GetContentSnapshot(content); - RemoveAttributes(); - Add(content); - } - - /// - /// Replaces the attributes of this element with the specified content. - /// - /// - /// A parameter list of content objects. - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public void ReplaceAttributes(params object[] content) - { - ReplaceAttributes((object)content); - } - - - /// - /// Output this to the passed in . - /// - /// - /// The format will be indented by default. If you want - /// no indenting then use the SaveOptions version of Save (see - /// ) enabling - /// SaveOptions.DisableFormatting. - /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations. - /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. - /// - /// - /// The to output this to. - /// - public void Save(Stream stream) - { - Save(stream, GetSaveOptionsFromAnnotations()); - } - - /// - /// Output this to a . - /// - /// - /// The to output the XML to. - /// - /// - /// If SaveOptions.DisableFormatting is enabled the output is not indented. - /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. - /// - public void Save(Stream stream, SaveOptions options) - { - XmlWriterSettings ws = GetXmlWriterSettings(options); - using (XmlWriter w = XmlWriter.Create(stream, ws)) - { - Save(w); - } - } - - /// - /// Output this to the passed in . - /// - /// - /// The format will be indented by default. If you want - /// no indenting then use the SaveOptions version of Save (see - /// ) enabling - /// SaveOptions.DisableFormatting. - /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations. - /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. - /// - /// - /// The to output this to. - /// - public void Save(TextWriter textWriter) - { - Save(textWriter, GetSaveOptionsFromAnnotations()); - } - - /// - /// Output this to a . - /// - /// - /// The to output the XML to. - /// - /// - /// If SaveOptions.DisableFormatting is enabled the output is not indented. - /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. - /// - public void Save(TextWriter textWriter, SaveOptions options) - { - XmlWriterSettings ws = GetXmlWriterSettings(options); - using (XmlWriter w = XmlWriter.Create(textWriter, ws)) - { - Save(w); - } - } - - /// - /// Output this to an . - /// - /// - /// The to output the XML to. - /// - public void Save(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - writer.WriteStartDocument(); - WriteTo(writer); - writer.WriteEndDocument(); - } - - /// - /// Sets the value of an attribute. The value is assigned to the attribute with the given - /// name. If no attribute with the given name exists, a new attribute is added. If the - /// value is null, the attribute with the given name, if any, is deleted. - /// - /// - /// - /// - /// - /// The name of the attribute whose value to change. - /// - /// - /// The value to assign to the attribute. The attribute is deleted if the value is null. - /// Otherwise, the value is converted to its string representation and assigned to the - /// property of the attribute. - /// - /// - /// Thrown if the value is an instance of . - /// - public void SetAttributeValue(XName name, object value) - { - XAttribute a = Attribute(name); - if (value == null) - { - if (a != null) RemoveAttribute(a); - } - else - { - if (a != null) - { - a.Value = GetStringValue(value); - } - else - { - AppendAttribute(new XAttribute(name, value)); - } - } - } - - /// - /// Sets the value of a child element. The value is assigned to the first child element - /// with the given name. If no child element with the given name exists, a new child - /// element is added. If the value is null, the first child element with the given name, - /// if any, is deleted. - /// - /// - /// - /// - /// - /// The name of the child element whose value to change. - /// - /// - /// The value to assign to the child element. The child element is deleted if the value - /// is null. Otherwise, the value is converted to its string representation and assigned - /// to the property of the child element. - /// - /// - /// Thrown if the value is an instance of . - /// - public void SetElementValue(XName name, object value) - { - XElement e = Element(name); - if (value == null) - { - if (e != null) RemoveNode(e); - } - else - { - if (e != null) - { - e.Value = GetStringValue(value); - } - else - { - AddNode(new XElement(name, GetStringValue(value))); - } - } - } - - /// - /// Sets the value of this element. - /// - /// - /// - /// - /// - /// The value to assign to this element. The value is converted to its string representation - /// and assigned to the property. - /// - /// - /// Thrown if the specified value is null. - /// - public void SetValue(object value) - { - if (value == null) throw new ArgumentNullException("value"); - Value = GetStringValue(value); - } - - /// - /// Write this to the passed in . - /// - /// - /// The to write this to. - /// - public override void WriteTo(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - new ElementWriter(writer).WriteElement(this); - } - - /// - /// Cast the value of this to a . - /// - /// - /// If the is a subtre (an - /// that has children. The concatenated string - /// value of all of the 's text and descendants - /// text is returned. - /// - /// - /// The to cast to a string. - /// - /// - /// The content of this as a . - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator string (XElement element) - { - if (element == null) return null; - return element.Value; - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the element does not contain a valid boolean value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator bool (XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToBoolean(XHelper.ToLower_InvariantCulture(element.Value)); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the element does not contain a valid boolean value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator bool? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToBoolean(XHelper.ToLower_InvariantCulture(element.Value)); - } - - /// - /// Cast the value of this to an . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the element does not contain a valid integer value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator int (XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToInt32(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid integer value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator int? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToInt32(element.Value); - } - - /// - /// Cast the value of this to an . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid unsigned integer value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator uint (XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToUInt32(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid unsigned integer value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator uint? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToUInt32(element.Value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the element does not contain a valid long integer value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator long (XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToInt64(element.Value); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid long integer value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator long? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToInt64(element.Value); - } - - /// - /// Cast the value of this to an . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid unsigned long integer value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator ulong (XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToUInt64(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid unsigned long integer value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator ulong? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToUInt64(element.Value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid float value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator float (XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToSingle(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid float value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator float? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToSingle(element.Value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid double value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator double (XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToDouble(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid double value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator double? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToDouble(element.Value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid decimal value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator decimal (XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToDecimal(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid decimal value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator decimal? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToDecimal(element.Value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator DateTime(XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return DateTime.Parse(element.Value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator DateTime? (XElement element) - { - if (element == null) return null; - return DateTime.Parse(element.Value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator DateTimeOffset(XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToDateTimeOffset(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator DateTimeOffset? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToDateTimeOffset(element.Value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid value. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator TimeSpan(XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToTimeSpan(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid value. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator TimeSpan? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToTimeSpan(element.Value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified element does not contain a valid guid. - /// - /// - /// Thrown if the specified element is null. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator Guid(XElement element) - { - if (element == null) throw new ArgumentNullException("element"); - return XmlConvert.ToGuid(element.Value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. - /// - /// - /// The content of this as a ?. - /// - /// - /// Thrown if the specified element does not contain a valid guid. - /// - [CLSCompliant(false)] - [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator marked with CLSCompliant(false).")] - public static explicit operator Guid? (XElement element) - { - if (element == null) return null; - return XmlConvert.ToGuid(element.Value); - } - - internal override void AddAttribute(XAttribute a) - { - if (Attribute(a.Name) != null) throw new InvalidOperationException(SR.InvalidOperation_DuplicateAttribute); - if (a.parent != null) a = new XAttribute(a); - AppendAttribute(a); - } - - internal override void AddAttributeSkipNotify(XAttribute a) - { - if (Attribute(a.Name) != null) throw new InvalidOperationException(SR.InvalidOperation_DuplicateAttribute); - if (a.parent != null) a = new XAttribute(a); - AppendAttributeSkipNotify(a); - } - - internal void AppendAttribute(XAttribute a) - { - bool notify = NotifyChanging(a, XObjectChangeEventArgs.Add); - if (a.parent != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - AppendAttributeSkipNotify(a); - if (notify) NotifyChanged(a, XObjectChangeEventArgs.Add); - } - - internal void AppendAttributeSkipNotify(XAttribute a) - { - a.parent = this; - if (lastAttr == null) - { - a.next = a; - } - else - { - a.next = lastAttr.next; - lastAttr.next = a; - } - lastAttr = a; - } - - bool AttributesEqual(XElement e) - { - XAttribute a1 = lastAttr; - XAttribute a2 = e.lastAttr; - if (a1 != null && a2 != null) - { - do - { - a1 = a1.next; - a2 = a2.next; - if (a1.name != a2.name || a1.value != a2.value) return false; - } while (a1 != lastAttr); - return a2 == e.lastAttr; - } - return a1 == null && a2 == null; - } - - internal override XNode CloneNode() - { - return new XElement(this); - } - - internal override bool DeepEquals(XNode node) - { - XElement e = node as XElement; - return e != null && name == e.name && ContentsEqual(e) && AttributesEqual(e); - } - - IEnumerable GetAttributes(XName name) - { - XAttribute a = lastAttr; - if (a != null) - { - do - { - a = a.next; - if (name == null || a.name == name) yield return a; - } while (a.parent == this && a != lastAttr); - } - } - - string GetNamespaceOfPrefixInScope(string prefix, XElement outOfScope) - { - XElement e = this; - while (e != outOfScope) - { - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - if (a.IsNamespaceDeclaration && a.Name.LocalName == prefix) return a.Value; - } - while (a != e.lastAttr); - } - e = e.parent as XElement; - } - return null; - } - - internal override int GetDeepHashCode() - { - int h = name.GetHashCode(); - h ^= ContentsHashCode(); - XAttribute a = lastAttr; - if (a != null) - { - do - { - a = a.next; - h ^= a.GetDeepHashCode(); - } while (a != lastAttr); - } - return h; - } - - void ReadElementFrom(XmlReader r, LoadOptions o) - { - if (r.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive); - name = XNamespace.Get(r.NamespaceURI).GetName(r.LocalName); - if ((o & LoadOptions.SetBaseUri) != 0) - { - string baseUri = r.BaseURI; - if (baseUri != null && baseUri.Length != 0) - { - SetBaseUri(baseUri); - } - } - IXmlLineInfo li = null; - if ((o & LoadOptions.SetLineInfo) != 0) - { - li = r as IXmlLineInfo; - if (li != null && li.HasLineInfo()) - { - SetLineInfo(li.LineNumber, li.LinePosition); - } - } - if (r.MoveToFirstAttribute()) - { - do - { - XAttribute a = new XAttribute(XNamespace.Get(r.Prefix.Length == 0 ? string.Empty : r.NamespaceURI).GetName(r.LocalName), r.Value); - if (li != null && li.HasLineInfo()) - { - a.SetLineInfo(li.LineNumber, li.LinePosition); - } - AppendAttributeSkipNotify(a); - } while (r.MoveToNextAttribute()); - r.MoveToElement(); - } - if (!r.IsEmptyElement) - { - r.Read(); - ReadContentFrom(r, o); - } - r.Read(); - } - - internal void RemoveAttribute(XAttribute a) - { - bool notify = NotifyChanging(a, XObjectChangeEventArgs.Remove); - if (a.parent != this) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); - XAttribute p = lastAttr, n; - while ((n = p.next) != a) p = n; - if (p == a) - { - lastAttr = null; - } - else - { - if (lastAttr == a) lastAttr = p; - p.next = a.next; - } - a.parent = null; - a.next = null; - if (notify) NotifyChanged(a, XObjectChangeEventArgs.Remove); - } - - void RemoveAttributesSkipNotify() - { - if (lastAttr != null) - { - XAttribute a = lastAttr; - do - { - XAttribute next = a.next; - a.parent = null; - a.next = null; - a = next; - } while (a != lastAttr); - lastAttr = null; - } - } - - internal void SetEndElementLineInfo(int lineNumber, int linePosition) - { - AddAnnotation(new LineInfoEndElementAnnotation(lineNumber, linePosition)); - } - - internal override void ValidateNode(XNode node, XNode previous) - { - if (node is XDocument) throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.Document)); - if (node is XDocumentType) throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.DocumentType)); - } - } - - internal struct ElementWriter - { - XmlWriter writer; - NamespaceResolver resolver; - - public ElementWriter(XmlWriter writer) - { - this.writer = writer; - this.resolver = new NamespaceResolver(); - } - - public void WriteElement(XElement e) - { - PushAncestors(e); - XElement root = e; - XNode n = e; - while (true) - { - e = n as XElement; - if (e != null) - { - WriteStartElement(e); - if (e.content == null) - { - WriteEndElement(); - } - else - { - string s = e.content as string; - if (s != null) - { - writer.WriteString(s); - WriteFullEndElement(); - } - else - { - n = ((XNode)e.content).next; - continue; - } - } - } - else - { - n.WriteTo(writer); - } - while (n != root && n == n.parent.content) - { - n = n.parent; - WriteFullEndElement(); - } - if (n == root) break; - n = n.next; - } - } - - string GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace) - { - string namespaceName = ns.NamespaceName; - if (namespaceName.Length == 0) return string.Empty; - string prefix = resolver.GetPrefixOfNamespace(ns, allowDefaultNamespace); - if (prefix != null) return prefix; - if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) return "xml"; - if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) return "xmlns"; - return null; - } - - void PushAncestors(XElement e) - { - while (true) - { - e = e.parent as XElement; - if (e == null) break; - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - if (a.IsNamespaceDeclaration) - { - resolver.AddFirst(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value)); - } - } while (a != e.lastAttr); - } - } - } - - void PushElement(XElement e) - { - resolver.PushScope(); - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - if (a.IsNamespaceDeclaration) - { - resolver.Add(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value)); - } - } while (a != e.lastAttr); - } - } - - void WriteEndElement() - { - writer.WriteEndElement(); - resolver.PopScope(); - } - - void WriteFullEndElement() - { - writer.WriteFullEndElement(); - resolver.PopScope(); - } - - void WriteStartElement(XElement e) - { - PushElement(e); - XNamespace ns = e.Name.Namespace; - writer.WriteStartElement(GetPrefixOfNamespace(ns, true), e.Name.LocalName, ns.NamespaceName); - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - ns = a.Name.Namespace; - string localName = a.Name.LocalName; - string namespaceName = ns.NamespaceName; - writer.WriteAttributeString(GetPrefixOfNamespace(ns, false), localName, namespaceName.Length == 0 && localName == "xmlns" ? XNamespace.xmlnsPrefixNamespace : namespaceName, a.Value); - } while (a != e.lastAttr); - } - } - } - - internal struct NamespaceResolver - { - class NamespaceDeclaration - { - public string prefix; - public XNamespace ns; - public int scope; - public NamespaceDeclaration prev; - } - - int scope; - NamespaceDeclaration declaration; - NamespaceDeclaration rover; - - public void PushScope() - { - scope++; - } - - public void PopScope() - { - NamespaceDeclaration d = declaration; - if (d != null) - { - do - { - d = d.prev; - if (d.scope != scope) break; - if (d == declaration) - { - declaration = null; - } - else - { - declaration.prev = d.prev; - } - rover = null; - } while (d != declaration && declaration != null); - } - scope--; - } - - public void Add(string prefix, XNamespace ns) - { - NamespaceDeclaration d = new NamespaceDeclaration(); - d.prefix = prefix; - d.ns = ns; - d.scope = scope; - if (declaration == null) - { - declaration = d; - } - else - { - d.prev = declaration.prev; - } - declaration.prev = d; - rover = null; - } - - public void AddFirst(string prefix, XNamespace ns) - { - NamespaceDeclaration d = new NamespaceDeclaration(); - d.prefix = prefix; - d.ns = ns; - d.scope = scope; - if (declaration == null) - { - d.prev = d; - } - else - { - d.prev = declaration.prev; - declaration.prev = d; - } - declaration = d; - rover = null; - } - - // Only elements allow default namespace declarations. The rover - // caches the last namespace declaration used by an element. - public string GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace) - { - if (rover != null && rover.ns == ns && (allowDefaultNamespace || rover.prefix.Length > 0)) return rover.prefix; - NamespaceDeclaration d = declaration; - if (d != null) - { - do - { - d = d.prev; - if (d.ns == ns) - { - NamespaceDeclaration x = declaration.prev; - while (x != d && x.prefix != d.prefix) - { - x = x.prev; - } - if (x == d) - { - if (allowDefaultNamespace) - { - rover = d; - return d.prefix; - } - else if (d.prefix.Length > 0) - { - return d.prefix; - } - } - } - } while (d != declaration); - } - return null; - } - } - - /// - /// Specifies a set of options for Load(). - /// - [Flags()] - public enum LoadOptions - { - /// Default options. - None = 0x00000000, - - /// Preserve whitespace. - [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Justification = "Back-compat with System.Xml.")] - PreserveWhitespace = 0x00000001, - - /// Set the BaseUri property. - SetBaseUri = 0x00000002, - - /// Set the IXmlLineInfo. - SetLineInfo = 0x00000004, - } - - /// - /// Specifies a set of options for Save(). - /// - [Flags()] - public enum SaveOptions - { - /// Default options. - None = 0x00000000, - - /// Disable formatting. - DisableFormatting = 0x00000001, - - /// Remove duplicate namespace declarations. - OmitDuplicateNamespaces = 0x00000002, - } - - /// - /// Specifies a set of options for CreateReader(). - /// - [Flags()] - public enum ReaderOptions - { - /// Default options. - None = 0x00000000, - - /// Remove duplicate namespace declarations. - OmitDuplicateNamespaces = 0x00000001, - } - - /// - /// Represents an XML document. - /// - /// - /// An can contain: - /// - /// - /// A Document Type Declaration (DTD), see - /// - /// One root element. - /// Zero or more objects. - /// Zero or more objects. - /// - /// - public class XDocument : XContainer - { - XDeclaration declaration; - - /// - /// Initializes a new instance of the class. - /// Overloaded constructors are provided for creating a new empty - /// , creating an with - /// a parameter list of initial content, and as a copy of another - /// object. - /// - /// - /// Initializes a new instance of the class. - /// - public XDocument() - { - } - - /// - /// Initializes a new instance of the class with the specified content. - /// - /// - /// A parameter list of content objects to add to this document. - /// - /// - /// Valid content includes: - /// - /// Zero or one objects - /// Zero or one elements - /// Zero or more comments - /// Zero or more processing instructions - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public XDocument(params object[] content) : this() - { - AddContentSkipNotify(content); - } - - /// - /// Initializes a new instance of the class - /// with the specifed and content. - /// - /// - /// The XML declaration for the document. - /// - /// - /// The contents of the document. - /// - /// - /// Valid content includes: - /// - /// Zero or one objects - /// Zero or one elements - /// Zero or more comments - /// Zero or more processing instructions - /// - /// - /// See XContainer.Add(object content) for details about the content that can be added - /// using this method. - /// - public XDocument(XDeclaration declaration, params object[] content) : this(content) - { - this.declaration = declaration; - } - - /// - /// Initializes a new instance of the class from an - /// existing XDocument object. - /// - /// - /// The object that will be copied. - /// - public XDocument(XDocument other) : base(other) - { - if (other.declaration != null) - { - declaration = new XDeclaration(other.declaration); - } - } - - /// - /// Gets the XML declaration for this document. - /// - public XDeclaration Declaration - { - get { return declaration; } - set { declaration = value; } - } - - /// - /// Gets the Document Type Definition (DTD) for this document. - /// - public XDocumentType DocumentType - { - get - { - return GetFirstNode(); - } - } - - /// - /// Gets the node type for this node. - /// - /// - /// This property will always return XmlNodeType.Document. - /// - public override XmlNodeType NodeType - { - get - { - return XmlNodeType.Document; - } - } - - /// - /// Gets the root element of the XML Tree for this document. - /// - public XElement Root - { - get - { - return GetFirstNode(); - } - } - - /// - /// The Load method provides multiple strategies for creating a new - /// and initializing it from a data source containing - /// raw XML. Load from a file (passing in a URI to the file), a - /// , a , or an - /// . Note: Use - /// to create an from a string containing XML. - /// - /// - /// - /// Create a new based on the contents of the file - /// referenced by the URI parameter passed in. Note: Use - /// to create an from - /// a string containing XML. - /// - /// - /// - /// - /// This method uses the method to create - /// an to read the raw XML into the underlying - /// XML tree. - /// - /// - /// A URI string referencing the file to load into a new . - /// - /// - /// An initialized with the contents of the file referenced - /// in the passed in uri parameter. - /// - [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Back-compat with System.Xml.")] - public static XDocument Load(string uri) - { - return Load(uri, LoadOptions.None); - } - - /// - /// Create a new based on the contents of the file - /// referenced by the URI parameter passed in. Optionally, whitespace can be preserved. - /// - /// - /// - /// This method uses the method to create - /// an to read the raw XML into an underlying - /// XML tree. If LoadOptions.PreserveWhitespace is enabled then - /// the property - /// is set to false. - /// - /// - /// A string representing the URI of the file to be loaded into a new . - /// - /// - /// A set of . - /// - /// - /// An initialized with the contents of the file referenced - /// in the passed uri parameter. If LoadOptions.PreserveWhitespace is enabled then - /// all whitespace will be preserved. - /// - [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Back-compat with System.Xml.")] - public static XDocument Load(string uri, LoadOptions options) - { - XmlReaderSettings rs = GetXmlReaderSettings(options); - using (XmlReader r = XmlReader.Create(uri, rs)) - { - return Load(r, options); - } - } - - /// - /// Create a new and initialize its underlying XML tree using - /// the passed parameter. - /// - /// - /// A containing the raw XML to read into the newly - /// created . - /// - /// - /// A new containing the contents of the passed in - /// . - /// - public static XDocument Load(Stream stream) - { - return Load(stream, LoadOptions.None); - } - - /// - /// Create a new and initialize its underlying XML tree using - /// the passed parameter. Optionally whitespace handling - /// can be preserved. - /// - /// - /// If LoadOptions.PreserveWhitespace is enabled then - /// the underlying property - /// is set to false. - /// - /// - /// A containing the raw XML to read into the newly - /// created . - /// - /// - /// A set of . - /// - /// - /// A new containing the contents of the passed in - /// . - /// - public static XDocument Load(Stream stream, LoadOptions options) - { - XmlReaderSettings rs = GetXmlReaderSettings(options); - using (XmlReader r = XmlReader.Create(stream, rs)) - { - return Load(r, options); - } - } - - /// - /// Create a new and initialize its underlying XML tree using - /// the passed parameter. - /// - /// - /// A containing the raw XML to read into the newly - /// created . - /// - /// - /// A new containing the contents of the passed in - /// . - /// - public static XDocument Load(TextReader textReader) - { - return Load(textReader, LoadOptions.None); - } - - /// - /// Create a new and initialize its underlying XML tree using - /// the passed parameter. Optionally whitespace handling - /// can be preserved. - /// - /// - /// If LoadOptions.PreserveWhitespace is enabled then - /// the property - /// is set to false. - /// - /// - /// A containing the raw XML to read into the newly - /// created . - /// - /// - /// A set of . - /// - /// - /// A new containing the contents of the passed in - /// . - /// - public static XDocument Load(TextReader textReader, LoadOptions options) - { - XmlReaderSettings rs = GetXmlReaderSettings(options); - using (XmlReader r = XmlReader.Create(textReader, rs)) - { - return Load(r, options); - } - } - - /// - /// Create a new containing the contents of the - /// passed in . - /// - /// - /// An containing the XML to be read into the new - /// . - /// - /// - /// A new containing the contents of the passed - /// in . - /// - public static XDocument Load(XmlReader reader) - { - return Load(reader, LoadOptions.None); - } - - /// - /// Create a new containing the contents of the - /// passed in . - /// - /// - /// An containing the XML to be read into the new - /// . - /// - /// - /// A set of . - /// - /// - /// A new containing the contents of the passed - /// in . - /// - public static XDocument Load(XmlReader reader, LoadOptions options) - { - if (reader == null) throw new ArgumentNullException("reader"); - if (reader.ReadState == ReadState.Initial) reader.Read(); - XDocument d = new XDocument(); - if ((options & LoadOptions.SetBaseUri) != 0) - { - string baseUri = reader.BaseURI; - if (baseUri != null && baseUri.Length != 0) - { - d.SetBaseUri(baseUri); - } - } - if ((options & LoadOptions.SetLineInfo) != 0) - { - IXmlLineInfo li = reader as IXmlLineInfo; - if (li != null && li.HasLineInfo()) - { - d.SetLineInfo(li.LineNumber, li.LinePosition); - } - } - if (reader.NodeType == XmlNodeType.XmlDeclaration) - { - d.Declaration = new XDeclaration(reader); - } - d.ReadContentFrom(reader, options); - if (!reader.EOF) throw new InvalidOperationException(SR.InvalidOperation_ExpectedEndOfFile); - if (d.Root == null) throw new InvalidOperationException(SR.InvalidOperation_MissingRoot); - return d; - } - - /// - /// Create a new from a string containing - /// XML. Optionally whitespace can be preserved. - /// - /// - /// Create a new from a string containing - /// XML. - /// - /// - /// A string containing XML. - /// - /// - /// An containing an XML tree initialized from the - /// passed in XML string. - /// - public static XDocument Parse(string text) - { - return Parse(text, LoadOptions.None); - } - - /// - /// Create a new from a string containing - /// XML. Optionally whitespace can be preserved. - /// - /// - /// This method uses method passing it a StringReader - /// constructed from the passed in XML String. If LoadOptions.PreserveWhitespace - /// is enabled then is - /// set to false. See - /// for more information on whitespace handling. - /// - /// - /// A string containing XML. - /// - /// - /// A set of . - /// - /// - /// An containing an XML tree initialized from the - /// passed in XML string. - /// - public static XDocument Parse(string text, LoadOptions options) - { - using (StringReader sr = new StringReader(text)) - { - XmlReaderSettings rs = GetXmlReaderSettings(options); - using (XmlReader r = XmlReader.Create(sr, rs)) - { - return Load(r, options); - } - } - } - - /// - /// Output this to the passed in . - /// - /// - /// The format will be indented by default. If you want - /// no indenting then use the SaveOptions version of Save (see - /// ) enabling - /// SaveOptions.DisableFormatting - /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations. - /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. - /// - /// - /// The to output this to. - /// - public void Save(Stream stream) - { - Save(stream, GetSaveOptionsFromAnnotations()); - } - - /// - /// Output this to a . - /// - /// - /// The to output the XML to. - /// - /// - /// If SaveOptions.DisableFormatting is enabled the output is not indented. - /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. - /// - public void Save(Stream stream, SaveOptions options) - { - XmlWriterSettings ws = GetXmlWriterSettings(options); - if (declaration != null && !string.IsNullOrEmpty(declaration.Encoding)) - { - try - { - ws.Encoding = Encoding.GetEncoding(declaration.Encoding); - } - catch (ArgumentException) - { - } - } - using (XmlWriter w = XmlWriter.Create(stream, ws)) - { - Save(w); - } - } - - /// - /// Output this to the passed in . - /// - /// - /// The format will be indented by default. If you want - /// no indenting then use the SaveOptions version of Save (see - /// ) enabling - /// SaveOptions.DisableFormatting - /// There is also an option SaveOptions.OmitDuplicateNamespaces for removing duplicate namespace declarations. - /// Or instead use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. - /// - /// - /// The to output this to. - /// - public void Save(TextWriter textWriter) - { - Save(textWriter, GetSaveOptionsFromAnnotations()); - } - - /// - /// Output this to a . - /// - /// - /// The to output the XML to. - /// - /// - /// If SaveOptions.DisableFormatting is enabled the output is not indented. - /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. - /// - public void Save(TextWriter textWriter, SaveOptions options) - { - XmlWriterSettings ws = GetXmlWriterSettings(options); - using (XmlWriter w = XmlWriter.Create(textWriter, ws)) - { - Save(w); - } - } - - /// - /// Output this to an . - /// - /// - /// The to output the XML to. - /// - public void Save(XmlWriter writer) - { - WriteTo(writer); - } - - - /// - /// Output this 's underlying XML tree to the - /// passed in . - /// - /// - /// - /// The to output the content of this - /// . - /// - public override void WriteTo(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - if (declaration != null && declaration.Standalone == "yes") - { - writer.WriteStartDocument(true); - } - else if (declaration != null && declaration.Standalone == "no") - { - writer.WriteStartDocument(false); - } - else - { - writer.WriteStartDocument(); - } - WriteContentTo(writer); - writer.WriteEndDocument(); - } - - internal override void AddAttribute(XAttribute a) - { - throw new ArgumentException(SR.Argument_AddAttribute); - } - - internal override void AddAttributeSkipNotify(XAttribute a) - { - throw new ArgumentException(SR.Argument_AddAttribute); - } - - internal override XNode CloneNode() - { - return new XDocument(this); - } - - internal override bool DeepEquals(XNode node) - { - XDocument other = node as XDocument; - return other != null && ContentsEqual(other); - } - - internal override int GetDeepHashCode() - { - return ContentsHashCode(); - } - - T GetFirstNode() where T : XNode - { - XNode n = content as XNode; - if (n != null) - { - do - { - n = n.next; - T e = n as T; - if (e != null) return e; - } while (n != content); - } - return null; - } - - internal static bool IsWhitespace(string s) - { - foreach (char ch in s) - { - if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') return false; - } - return true; - } - - internal override void ValidateNode(XNode node, XNode previous) - { - switch (node.NodeType) - { - case XmlNodeType.Text: - ValidateString(((XText)node).Value); - break; - case XmlNodeType.Element: - ValidateDocument(previous, XmlNodeType.DocumentType, XmlNodeType.None); - break; - case XmlNodeType.DocumentType: - ValidateDocument(previous, XmlNodeType.None, XmlNodeType.Element); - break; - case XmlNodeType.CDATA: - throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.CDATA)); - case XmlNodeType.Document: - throw new ArgumentException(SR.Format(SR.Argument_AddNode, XmlNodeType.Document)); - } - } - - void ValidateDocument(XNode previous, XmlNodeType allowBefore, XmlNodeType allowAfter) - { - XNode n = content as XNode; - if (n != null) - { - if (previous == null) allowBefore = allowAfter; - do - { - n = n.next; - XmlNodeType nt = n.NodeType; - if (nt == XmlNodeType.Element || nt == XmlNodeType.DocumentType) - { - if (nt != allowBefore) throw new InvalidOperationException(SR.InvalidOperation_DocumentStructure); - allowBefore = XmlNodeType.None; - } - if (n == previous) allowBefore = allowAfter; - } while (n != content); - } - } - - internal override void ValidateString(string s) - { - if (!IsWhitespace(s)) throw new ArgumentException(SR.Argument_AddNonWhitespace); - } - } - - /// - /// Represents an XML comment. - /// - public class XComment : XNode - { - internal string value; - - /// - /// Initializes a new instance of the class. - /// - /// - /// Initializes a new instance of the class with the - /// specified string content. - /// - /// - /// The contents of the new XComment object. - /// - /// - /// Thrown if the specified value is null. - /// - public XComment(string value) - { - if (value == null) throw new ArgumentNullException("value"); - this.value = value; - } - - /// - /// Initializes a new comment node from an existing comment node. - /// - /// Comment node to copy from. - public XComment(XComment other) - { - if (other == null) throw new ArgumentNullException("other"); - this.value = other.value; - } - - internal XComment(XmlReader r) - { - value = r.Value; - r.Read(); - } - - /// - /// Gets the node type for this node. - /// - /// - /// This property will always return XmlNodeType.Comment. - /// - public override XmlNodeType NodeType - { - get - { - return XmlNodeType.Comment; - } - } - - /// - /// Gets or sets the string value of this comment. - /// - /// - /// Thrown if the specified value is null. - /// - public string Value - { - get - { - return value; - } - set - { - if (value == null) throw new ArgumentNullException("value"); - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); - this.value = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); - } - } - - /// - /// Write this to the passed in . - /// - /// - /// The to write this to. - /// - public override void WriteTo(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - writer.WriteComment(value); - } - - internal override XNode CloneNode() - { - return new XComment(this); - } - - internal override bool DeepEquals(XNode node) - { - XComment other = node as XComment; - return other != null && value == other.value; - } - - internal override int GetDeepHashCode() - { - return value.GetHashCode(); - } - } - - /// - /// Represents an XML processing instruction. - /// - public class XProcessingInstruction : XNode - { - internal string target; - internal string data; - - /// - /// Initializes a new XML Processing Instruction from the specified target and string data. - /// - /// - /// The target application for this . - /// - /// - /// The string data that comprises the . - /// - /// - /// Thrown if either the target or data parameter are null. - /// - public XProcessingInstruction(string target, string data) - { - if (data == null) throw new ArgumentNullException("data"); - ValidateName(target); - this.target = target; - this.data = data; - } - - /// - /// Initializes a new XML processing instruction by copying its target and data - /// from another XML processing instruction. - /// - /// XML processing instruction to copy from. - public XProcessingInstruction(XProcessingInstruction other) - { - if (other == null) throw new ArgumentNullException("other"); - this.target = other.target; - this.data = other.data; - } - - internal XProcessingInstruction(XmlReader r) - { - target = r.Name; - data = r.Value; - r.Read(); - } - - /// - /// Gets or sets the string value of this processing instruction. - /// - /// - /// Thrown if the value set is null. - /// - public string Data - { - get - { - return data; - } - set - { - if (value == null) throw new ArgumentNullException("value"); - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); - data = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); - } - } - - /// - /// Gets the node type for this node. - /// - /// - /// This property will always return XmlNodeType.ProcessingInstruction. - /// - public override XmlNodeType NodeType - { - get - { - return XmlNodeType.ProcessingInstruction; - } - } - - /// - /// Gets or sets a string representing the target application for this processing instruction. - /// - /// - /// Thrown if the value set is null. - /// - public string Target - { - get - { - return target; - } - set - { - ValidateName(value); - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Name); - target = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Name); - } - } - - /// - /// Writes this to the passed in . - /// - /// - /// The to write this to. - /// - public override void WriteTo(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - writer.WriteProcessingInstruction(target, data); - } - - internal override XNode CloneNode() - { - return new XProcessingInstruction(this); - } - - internal override bool DeepEquals(XNode node) - { - XProcessingInstruction other = node as XProcessingInstruction; - return other != null && target == other.target && data == other.data; - } - - internal override int GetDeepHashCode() - { - return target.GetHashCode() ^ data.GetHashCode(); - } - - static void ValidateName(string name) - { - XmlConvert.VerifyNCName(name); - if (string.Compare(name, "xml", StringComparison.OrdinalIgnoreCase) == 0) throw new ArgumentException(SR.Format(SR.Argument_InvalidPIName, name)); - } - } - - /// - /// Represents an XML declaration. - /// - /// - /// An XML declaration is used to declare the XML version, - /// the encoding, and whether or not the XML document is standalone. - /// - public class XDeclaration - { - string version; - string encoding; - string standalone; - - /// - /// Initilizes a new instance of the class from the - /// specified version, encoding, and standalone properties. - /// - /// - /// The version of the XML, usually "1.0". - /// - /// - /// The encoding for the XML document. - /// - /// - /// Specifies whether the XML is standalone or requires external entities - /// to be resolved. - /// - public XDeclaration(string version, string encoding, string standalone) - { - this.version = version; - this.encoding = encoding; - this.standalone = standalone; - } - - /// - /// Initializes an instance of the class - /// from another object. - /// - /// - /// The used to initialize this object. - /// - public XDeclaration(XDeclaration other) - { - if (other == null) throw new ArgumentNullException("other"); - version = other.version; - encoding = other.encoding; - standalone = other.standalone; - } - - internal XDeclaration(XmlReader r) - { - version = r.GetAttribute("version"); - encoding = r.GetAttribute("encoding"); - standalone = r.GetAttribute("standalone"); - r.Read(); - } - - /// - /// Gets or sets the encoding for this document. - /// - public string Encoding - { - get { return encoding; } - set { encoding = value; } - } - - /// - /// Gets or sets the standalone property for this document. - /// - /// - /// The valid values for standalone are "yes" or "no". - /// - public string Standalone - { - get { return standalone; } - set { standalone = value; } - } - - /// - /// Gets or sets the version property for this document. - /// - /// - /// The value is usually "1.0". - /// - public string Version - { - get { return version; } - set { version = value; } - } - - /// - /// Provides a formatted string. - /// - /// A formatted XML string. - public override string ToString() - { - StringBuilder sb = new StringBuilder(""); - return sb.ToString(); - } - } - - /// - /// Represents an XML Document Type Definition (DTD). - /// - public class XDocumentType : XNode - { - string name; - string publicId; - string systemId; - string internalSubset; - - /// - /// Initializes an empty instance of the class. - /// - public XDocumentType(string name, string publicId, string systemId, string internalSubset) - { - this.name = XmlConvert.VerifyName(name); - this.publicId = publicId; - this.systemId = systemId; - this.internalSubset = internalSubset; - } - - /// - /// Initializes an instance of the XDocumentType class - /// from another XDocumentType object. - /// - /// object to copy from. - public XDocumentType(XDocumentType other) - { - if (other == null) throw new ArgumentNullException("other"); - this.name = other.name; - this.publicId = other.publicId; - this.systemId = other.systemId; - this.internalSubset = other.internalSubset; - } - - internal XDocumentType(XmlReader r) - { - name = r.Name; - publicId = r.GetAttribute("PUBLIC"); - systemId = r.GetAttribute("SYSTEM"); - internalSubset = r.Value; - r.Read(); - } - - /// - /// Gets or sets the internal subset for this Document Type Definition (DTD). - /// - public string InternalSubset - { - get - { - return internalSubset; - } - set - { - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); - internalSubset = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); - } - } - - /// - /// Gets or sets the name for this Document Type Definition (DTD). - /// - public string Name - { - get - { - return name; - } - set - { - value = XmlConvert.VerifyName(value); - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Name); - name = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Name); - } - } - - /// - /// Gets the node type for this node. - /// - /// - /// This property will always return XmlNodeType.DocumentType. - /// - public override XmlNodeType NodeType - { - get - { - return XmlNodeType.DocumentType; - } - } - - /// - /// Gets or sets the public identifier for this Document Type Definition (DTD). - /// - public string PublicId - { - get - { - return publicId; - } - set - { - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); - publicId = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); - } - } - - /// - /// Gets or sets the system identifier for this Document Type Definition (DTD). - /// - public string SystemId - { - get - { - return systemId; - } - set - { - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); - systemId = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); - } - } - - /// - /// Write this to the passed in . - /// - /// - /// The to write this to. - /// - public override void WriteTo(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - writer.WriteDocType(name, publicId, systemId, internalSubset); - } - - internal override XNode CloneNode() - { - return new XDocumentType(this); - } - - internal override bool DeepEquals(XNode node) - { - XDocumentType other = node as XDocumentType; - return other != null && name == other.name && publicId == other.publicId && - systemId == other.SystemId && internalSubset == other.internalSubset; - } - - internal override int GetDeepHashCode() - { - return name.GetHashCode() ^ - (publicId != null ? publicId.GetHashCode() : 0) ^ - (systemId != null ? systemId.GetHashCode() : 0) ^ - (internalSubset != null ? internalSubset.GetHashCode() : 0); - } - } - - /// - /// Represents an XML attribute. - /// - /// - /// An XML attribute is a name/value pair associated with an XML element. - /// - [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "Reviewed.")] - public class XAttribute : XObject - { - static IEnumerable emptySequence; - - /// - /// Gets an empty collection of attributes. - /// - public static IEnumerable EmptySequence - { - get - { - if (emptySequence == null) emptySequence = new XAttribute[0]; - return emptySequence; - } - } - - internal XAttribute next; - internal XName name; - internal string value; - - /// - /// Initializes a new instance of the class. - /// - /// - /// Initializes a new instance of the class from - /// the specified name and value. - /// - /// - /// The name of the attribute. - /// - /// - /// The value of the attribute. - /// - /// - /// Thrown if the passed in name or value are null. - /// - public XAttribute(XName name, object value) - { - if (name == null) throw new ArgumentNullException("name"); - if (value == null) throw new ArgumentNullException("value"); - string s = XContainer.GetStringValue(value); - ValidateAttribute(name, s); - this.name = name; - this.value = s; - } - - /// - /// Initializes an instance of the XAttribute class - /// from another XAttribute object. - /// - /// object to copy from. - /// - /// Thrown if the specified is null. - /// - public XAttribute(XAttribute other) - { - if (other == null) throw new ArgumentNullException("other"); - name = other.name; - value = other.value; - } - - /// - /// Gets a value indicating if this attribute is a namespace declaration. - /// - public bool IsNamespaceDeclaration - { - get - { - string namespaceName = name.NamespaceName; - if (namespaceName.Length == 0) - { - return name.LocalName == "xmlns"; - } - return (object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace; - } - } - - /// - /// Gets the name of this attribute. - /// - public XName Name - { - get { return name; } - } - - /// - /// Gets the next attribute of the parent element. - /// - /// - /// If this attribute does not have a parent, or if there is no next attribute, - /// then this property returns null. - /// - public XAttribute NextAttribute - { - get { return parent != null && ((XElement)parent).lastAttr != this ? next : null; } - } - - /// - /// Gets the node type for this node. - /// - /// - /// This property will always return XmlNodeType.Attribute. - /// - public override XmlNodeType NodeType - { - get - { - return XmlNodeType.Attribute; - } - } - - /// - /// Gets the previous attribute of the parent element. - /// - /// - /// If this attribute does not have a parent, or if there is no previous attribute, - /// then this property returns null. - /// - public XAttribute PreviousAttribute - { - get - { - if (parent == null) return null; - XAttribute a = ((XElement)parent).lastAttr; - while (a.next != this) - { - a = a.next; - } - return a != ((XElement)parent).lastAttr ? a : null; - } - } - - /// - /// Gets or sets the value of this attribute. - /// - /// - /// Thrown if the value set is null. - /// - public string Value - { - get - { - return value; - } - set - { - if (value == null) throw new ArgumentNullException("value"); - ValidateAttribute(name, value); - bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); - this.value = value; - if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); - } - } - - /// - /// Deletes this XAttribute. - /// - /// - /// Thrown if the parent element is null. - /// - public void Remove() - { - if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); - ((XElement)parent).RemoveAttribute(this); - } - - /// - /// Sets the value of this . - /// - /// - /// - /// - /// - /// The value to assign to this attribute. The value is converted to its string - /// representation and assigned to the property. - /// - /// - /// Thrown if the specified value is null. - /// - public void SetValue(object value) - { - if (value == null) throw new ArgumentNullException("value"); - Value = XContainer.GetStringValue(value); - } - - /// - /// Override for on - /// - /// XML text representation of an attribute and its value - public override string ToString() - { - using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture)) - { - XmlWriterSettings ws = new XmlWriterSettings(); - ws.ConformanceLevel = ConformanceLevel.Fragment; - using (XmlWriter w = XmlWriter.Create(sw, ws)) - { - w.WriteAttributeString(GetPrefixOfNamespace(name.Namespace), name.LocalName, name.NamespaceName, value); - } - return sw.ToString().Trim(); - } - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - [CLSCompliant(false)] - public static explicit operator string (XAttribute attribute) - { - if (attribute == null) return null; - return attribute.value; - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator bool (XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToBoolean(XHelper.ToLower_InvariantCulture(attribute.value)); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator bool? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToBoolean(XHelper.ToLower_InvariantCulture(attribute.value)); - } - - /// - /// Cast the value of this to an . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as an . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator int (XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToInt32(attribute.value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as an ?. - /// - [CLSCompliant(false)] - public static explicit operator int? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToInt32(attribute.value); - } - - /// - /// Cast the value of this to an . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as an . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator uint (XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToUInt32(attribute.value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as an ?. - /// - [CLSCompliant(false)] - public static explicit operator uint? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToUInt32(attribute.value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator long (XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToInt64(attribute.value); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator long? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToInt64(attribute.value); - } - - /// - /// Cast the value of this to an . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as an . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator ulong (XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToUInt64(attribute.value); - } - - /// - /// Cast the value of this to an ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as an ?. - /// - [CLSCompliant(false)] - public static explicit operator ulong? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToUInt64(attribute.value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator float (XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToSingle(attribute.value); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator float? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToSingle(attribute.value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator double (XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToDouble(attribute.value); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator double? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToDouble(attribute.value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator decimal (XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToDecimal(attribute.value); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator decimal? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToDecimal(attribute.value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator DateTime(XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return DateTime.Parse(attribute.value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator DateTime? (XAttribute attribute) - { - if (attribute == null) return null; - return DateTime.Parse(attribute.value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator DateTimeOffset(XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToDateTimeOffset(attribute.value); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator DateTimeOffset? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToDateTimeOffset(attribute.value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator TimeSpan(XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToTimeSpan(attribute.value); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator TimeSpan? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToTimeSpan(attribute.value); - } - - /// - /// Cast the value of this to a . - /// - /// - /// The to cast to . - /// - /// - /// The content of this as a . - /// - /// - /// Thrown if the specified attribute is null. - /// - [CLSCompliant(false)] - public static explicit operator Guid(XAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException("attribute"); - return XmlConvert.ToGuid(attribute.value); - } - - /// - /// Cast the value of this to a ?. - /// - /// - /// The to cast to ?. Can be null. - /// - /// - /// The content of this as a ?. - /// - [CLSCompliant(false)] - public static explicit operator Guid? (XAttribute attribute) - { - if (attribute == null) return null; - return XmlConvert.ToGuid(attribute.value); - } - - internal int GetDeepHashCode() - { - return name.GetHashCode() ^ value.GetHashCode(); - } - - internal string GetPrefixOfNamespace(XNamespace ns) - { - string namespaceName = ns.NamespaceName; - if (namespaceName.Length == 0) return string.Empty; - if (parent != null) return ((XElement)parent).GetPrefixOfNamespace(ns); - if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) return "xml"; - if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) return "xmlns"; - return null; - } - - static void ValidateAttribute(XName name, string value) - { - // The following constraints apply for namespace declarations: - string namespaceName = name.NamespaceName; - if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) - { - if (value.Length == 0) - { - // The empty namespace name can only be declared by - // the default namespace declaration - throw new ArgumentException(SR.Format(SR.Argument_NamespaceDeclarationPrefixed, name.LocalName)); - } - else if (value == XNamespace.xmlPrefixNamespace) - { - // 'http://www.w3.org/XML/1998/namespace' can only - // be declared by the 'xml' prefix namespace declaration. - if (name.LocalName != "xml") throw new ArgumentException(SR.Argument_NamespaceDeclarationXml); - } - else if (value == XNamespace.xmlnsPrefixNamespace) - { - // 'http://www.w3.org/2000/xmlns/' must not be declared - // by any namespace declaration. - throw new ArgumentException(SR.Argument_NamespaceDeclarationXmlns); - } - else - { - string localName = name.LocalName; - if (localName == "xml") - { - // No other namespace name can be declared by the 'xml' - // prefix namespace declaration. - throw new ArgumentException(SR.Argument_NamespaceDeclarationXml); - } - else if (localName == "xmlns") - { - // The 'xmlns' prefix must not be declared. - throw new ArgumentException(SR.Argument_NamespaceDeclarationXmlns); - } - } - } - else if (namespaceName.Length == 0 && name.LocalName == "xmlns") - { - if (value == XNamespace.xmlPrefixNamespace) - { - // 'http://www.w3.org/XML/1998/namespace' can only - // be declared by the 'xml' prefix namespace declaration. - throw new ArgumentException(SR.Argument_NamespaceDeclarationXml); - } - else if (value == XNamespace.xmlnsPrefixNamespace) - { - // 'http://www.w3.org/2000/xmlns/' must not be declared - // by any namespace declaration. - throw new ArgumentException(SR.Argument_NamespaceDeclarationXmlns); - } - } - } - } - - /// - /// Represents a class that allows elements to be streamed - /// on input and output. - /// - public class XStreamingElement - { - internal XName name; - internal object content; - - /// - /// Creates a node with a given name - /// - /// The name to assign to the new node - public XStreamingElement(XName name) - { - if (name == null) throw new ArgumentNullException("name"); - this.name = name; - } - - /// - /// Creates a node with a given name and content - /// - /// The name to assign to the new node - /// The content to assign to the new node - public XStreamingElement(XName name, object content) - : this(name) - { - this.content = content is List ? new object[] { content } : content; - } - - /// - /// Creates a node with a given name and content - /// - /// The name to assign to the new node - /// An array containing content to assign to the new node - public XStreamingElement(XName name, params object[] content) - : this(name) - { - this.content = content; - } - - /// - /// Gets or sets the name of this streaming element. - /// - public XName Name - { - get - { - return name; - } - set - { - if (value == null) throw new ArgumentNullException("value"); - name = value; - } - } - - /// - /// Add content to an - /// - /// Object containg content to add - public void Add(object content) - { - if (content != null) - { - List list = this.content as List; - if (list == null) - { - list = new List(); - if (this.content != null) list.Add(this.content); - this.content = list; - } - list.Add(content); - } - } - - /// - /// Add content to an - /// - /// array of objects containg content to add - public void Add(params object[] content) - { - Add((object)content); - } - - /// - /// Save the contents of an to a - /// with formatting. - /// - /// to write to - public void Save(Stream stream) - { - Save(stream, SaveOptions.None); - } - - /// - /// Save the contents of an to a , - /// optionally formatting. - /// - /// to write to - /// - /// If SaveOptions.DisableFormatting is enabled the output is not indented. - /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. - /// - public void Save(Stream stream, SaveOptions options) - { - XmlWriterSettings ws = XNode.GetXmlWriterSettings(options); - using (XmlWriter w = XmlWriter.Create(stream, ws)) - { - Save(w); - } - } - - /// - /// Save the contents of an to a text writer - /// with formatting. - /// - /// to write to - public void Save(TextWriter textWriter) - { - Save(textWriter, SaveOptions.None); - } - - /// - /// Save the contents of an to a text writer - /// optionally formatting. - /// - /// to write to - /// - /// If SaveOptions.DisableFormatting is enabled the output is not indented. - /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. - /// - public void Save(TextWriter textWriter, SaveOptions options) - { - XmlWriterSettings ws = XNode.GetXmlWriterSettings(options); - using (XmlWriter w = XmlWriter.Create(textWriter, ws)) - { - Save(w); - } - } - - /// - /// Save the contents of an to an XML writer, not preserving whitepace - /// - /// to write to - public void Save(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - writer.WriteStartDocument(); - WriteTo(writer); - writer.WriteEndDocument(); - } - - /// - /// Get the XML content of an as a - /// formatted string. - /// - /// The XML text as a formatted string - public override string ToString() - { - return GetXmlString(SaveOptions.None); - } - - /// - /// Gets the XML content of this streaming element as a string. - /// - /// - /// If SaveOptions.DisableFormatting is enabled the content is not indented. - /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. - /// - /// An XML string - public string ToString(SaveOptions options) - { - return GetXmlString(options); - } - - /// - /// Write this to an - /// - /// - public void WriteTo(XmlWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - new StreamingElementWriter(writer).WriteStreamingElement(this); - } - - string GetXmlString(SaveOptions o) - { - using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture)) - { - XmlWriterSettings ws = new XmlWriterSettings(); - ws.OmitXmlDeclaration = true; - if ((o & SaveOptions.DisableFormatting) == 0) ws.Indent = true; - if ((o & SaveOptions.OmitDuplicateNamespaces) != 0) ws.NamespaceHandling |= NamespaceHandling.OmitDuplicates; - using (XmlWriter w = XmlWriter.Create(sw, ws)) - { - WriteTo(w); - } - return sw.ToString(); - } - } - } - - internal struct StreamingElementWriter - { - XmlWriter writer; - XStreamingElement element; - List attributes; - NamespaceResolver resolver; - - public StreamingElementWriter(XmlWriter w) - { - writer = w; - element = null; - attributes = new List(); - resolver = new NamespaceResolver(); - } - - void FlushElement() - { - if (element != null) - { - PushElement(); - XNamespace ns = element.Name.Namespace; - writer.WriteStartElement(GetPrefixOfNamespace(ns, true), element.Name.LocalName, ns.NamespaceName); - foreach (XAttribute a in attributes) - { - ns = a.Name.Namespace; - string localName = a.Name.LocalName; - string namespaceName = ns.NamespaceName; - writer.WriteAttributeString(GetPrefixOfNamespace(ns, false), localName, namespaceName.Length == 0 && localName == "xmlns" ? XNamespace.xmlnsPrefixNamespace : namespaceName, a.Value); - } - element = null; - attributes.Clear(); - } - } - - string GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace) - { - string namespaceName = ns.NamespaceName; - if (namespaceName.Length == 0) return string.Empty; - string prefix = resolver.GetPrefixOfNamespace(ns, allowDefaultNamespace); - if (prefix != null) return prefix; - if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) return "xml"; - if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) return "xmlns"; - return null; - } - - void PushElement() - { - resolver.PushScope(); - foreach (XAttribute a in attributes) - { - if (a.IsNamespaceDeclaration) - { - resolver.Add(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value)); - } - } - } - - void Write(object content) - { - if (content == null) return; - XNode n = content as XNode; - if (n != null) - { - WriteNode(n); - return; - } - string s = content as string; - if (s != null) - { - WriteString(s); - return; - } - XAttribute a = content as XAttribute; - if (a != null) - { - WriteAttribute(a); - return; - } - XStreamingElement x = content as XStreamingElement; - if (x != null) - { - WriteStreamingElement(x); - return; - } - object[] o = content as object[]; - if (o != null) - { - foreach (object obj in o) Write(obj); - return; - } - IEnumerable e = content as IEnumerable; - if (e != null) - { - foreach (object obj in e) Write(obj); - return; - } - WriteString(XContainer.GetStringValue(content)); - } - - void WriteAttribute(XAttribute a) - { - if (element == null) throw new InvalidOperationException(SR.InvalidOperation_WriteAttribute); - attributes.Add(a); - } - - void WriteNode(XNode n) - { - FlushElement(); - n.WriteTo(writer); - } - - internal void WriteStreamingElement(XStreamingElement e) - { - FlushElement(); - element = e; - Write(e.content); - bool contentWritten = element == null; - FlushElement(); - if (contentWritten) - { - writer.WriteFullEndElement(); - } - else - { - writer.WriteEndElement(); - } - resolver.PopScope(); - } - - void WriteString(string s) - { - FlushElement(); - writer.WriteString(s); - } - } - - /// - /// Defines the LINQ to XML extension methods. - /// - public static class Extensions - { - /// - /// Returns all of the s for each of - /// this of . - /// - /// - /// An of containing the XML - /// Attributes for every in the target - /// of . - /// - public static IEnumerable Attributes(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException("source"); - return GetAttributes(source, null); - } - - /// - /// Returns the s that have a matching . Each - /// 's s in the target - /// of are scanned for a matching . - /// - /// - /// An of containing the XML - /// Attributes with a matching for every in - /// the target of . - /// - public static IEnumerable Attributes(this IEnumerable source, XName name) - { - if (source == null) throw new ArgumentNullException("source"); - return name != null ? GetAttributes(source, name) : XAttribute.EmptySequence; - } - - /// - /// Returns an of containing the ancestors (parent - /// and it's parent up to the root) of each of the s in this - /// of . - /// - /// - /// An of containing the ancestors (parent - /// and it's parent up to the root) of each of the s in this - /// of . - /// - public static IEnumerable Ancestors(this IEnumerable source) where T : XNode - { - if (source == null) throw new ArgumentNullException("source"); - return GetAncestors(source, null, false); - } - - /// - /// Returns an of containing the ancestors (parent - /// and it's parent up to the root) that have a matching . This is done for each - /// in this of . - /// - /// - /// An of containing the ancestors (parent - /// and it's parent up to the root) that have a matching . This is done for each - /// in this of . - /// - public static IEnumerable Ancestors(this IEnumerable source, XName name) where T : XNode - { - if (source == null) throw new ArgumentNullException("source"); - return name != null ? GetAncestors(source, name, false) : XElement.EmptySequence; - } - - /// - /// Returns an of containing the - /// and it's ancestors (parent and it's parent up to the root). - /// This is done for each in this of - /// . - /// - /// - /// An of containing the - /// and it's ancestors (parent and it's parent up to the root). - /// This is done for each in this of - /// . - /// - public static IEnumerable AncestorsAndSelf(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException("source"); - return GetAncestors(source, null, true); - } - - /// - /// Returns an of containing the - /// and it's ancestors (parent and it's parent up to the root) - /// that match the passed in . This is done for each - /// in this of . - /// - /// - /// An of containing the - /// and it's ancestors (parent and it's parent up to the root) - /// that match the passed in . This is done for each - /// in this of . - /// - public static IEnumerable AncestorsAndSelf(this IEnumerable source, XName name) - { - if (source == null) throw new ArgumentNullException("source"); - return name != null ? GetAncestors(source, name, true) : XElement.EmptySequence; - } - - /// - /// Returns an of over the content of a set of nodes - /// - public static IEnumerable Nodes(this IEnumerable source) where T : XContainer - { - if (source == null) throw new ArgumentNullException("source"); - foreach (XContainer root in source) - { - if (root != null) - { - XNode n = root.LastNode; - if (n != null) - { - do - { - n = n.next; - yield return n; - } while (n.parent == root && n != root.content); - } - } - } - } - - /// - /// Returns an of over the descendants of a set of nodes - /// - public static IEnumerable DescendantNodes(this IEnumerable source) where T : XContainer - { - if (source == null) throw new ArgumentNullException("source"); - return GetDescendantNodes(source, false); - } - - /// - /// Returns an of containing the descendants (children - /// and their children down to the leaf level). This is done for each in - /// this of . - /// - /// - /// An of containing the descendants (children - /// and their children down to the leaf level). This is done for each in - /// this of . - /// - public static IEnumerable Descendants(this IEnumerable source) where T : XContainer - { - if (source == null) throw new ArgumentNullException("source"); - return GetDescendants(source, null, false); - } - - /// - /// Returns an of containing the descendants (children - /// and their children down to the leaf level) that have a matching . This is done - /// for each in the target of . - /// - /// - /// An of containing the descendants (children - /// and their children down to the leaf level) that have a matching . This is done - /// for each in this of . - /// - public static IEnumerable Descendants(this IEnumerable source, XName name) where T : XContainer - { - if (source == null) throw new ArgumentNullException("source"); - return name != null ? GetDescendants(source, name, false) : XElement.EmptySequence; - } - - /// - /// Returns an of containing the - /// and it's descendants - /// that match the passed in . This is done for each - /// in this of . - /// - /// - /// An of containing the - /// and descendants. - /// This is done for each - /// in this of . - /// - public static IEnumerable DescendantNodesAndSelf(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException("source"); - return GetDescendantNodes(source, true); - } - - /// - /// Returns an of containing the - /// and it's descendants (children and children's children down - /// to the leaf nodes). This is done for each in this - /// of . - /// - /// - /// An of containing the - /// and it's descendants (children and children's children down - /// to the leaf nodes). This is done for each in this - /// of . - /// - public static IEnumerable DescendantsAndSelf(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException("source"); - return GetDescendants(source, null, true); - } - - /// - /// Returns an of containing the - /// and it's descendants (children and children's children down - /// to the leaf nodes) that match the passed in . This is done for - /// each in this of . - /// - /// - /// An of containing the - /// and it's descendants (children and children's children down - /// to the leaf nodes) that match the passed in . This is done for - /// each in this of . - /// - public static IEnumerable DescendantsAndSelf(this IEnumerable source, XName name) - { - if (source == null) throw new ArgumentNullException("source"); - return name != null ? GetDescendants(source, name, true) : XElement.EmptySequence; - } - - /// - /// Returns an of containing the child elements - /// for each in this of . - /// - /// - /// An of containing the child elements - /// for each in this of . - /// - public static IEnumerable Elements(this IEnumerable source) where T : XContainer - { - if (source == null) throw new ArgumentNullException("source"); - return GetElements(source, null); - } - - /// - /// Returns an of containing the child elements - /// with a matching for each in this of . - /// - /// - /// An of containing the child elements - /// for each in this of . - /// - public static IEnumerable Elements(this IEnumerable source, XName name) where T : XContainer - { - if (source == null) throw new ArgumentNullException("source"); - return name != null ? GetElements(source, name) : XElement.EmptySequence; - } - - /// - /// Returns an of containing the child elements - /// with a matching for each in this of . - /// - /// - /// An of containing the child elements - /// for each in this of . - /// in document order - /// - public static IEnumerable InDocumentOrder(this IEnumerable source) where T : XNode - { - return Enumerable.OrderBy(source, n => (XNode)n, XNode.DocumentOrderComparer); - } - - /// - /// Removes each represented in this of - /// . Note that this method uses snapshot semantics (copies the - /// attributes to a before deleting each). - /// - public static void Remove(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException("source"); - foreach (XAttribute a in new List(source)) - if (a != null) a.Remove(); - } - - /// - /// Removes each represented in this - /// T which must be a derived from . Note that this method uses snapshot semantics - /// (copies the s to a List before deleting each). - /// - public static void Remove(this IEnumerable source) where T : XNode - { - if (source == null) throw new ArgumentNullException("source"); - foreach (T node in new List(source)) - if (node != null) node.Remove(); - } - - static IEnumerable GetAttributes(IEnumerable source, XName name) - { - foreach (XElement e in source) - { - if (e != null) - { - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - if (name == null || a.name == name) yield return a; - } while (a.parent == e && a != e.lastAttr); - } - } - } - } - - static IEnumerable GetAncestors(IEnumerable source, XName name, bool self) where T : XNode - { - foreach (XNode node in source) - { - if (node != null) - { - XElement e = (self ? node : node.parent) as XElement; - while (e != null) - { - if (name == null || e.name == name) yield return e; - e = e.parent as XElement; - } - } - } - } - - static IEnumerable GetDescendantNodes(IEnumerable source, bool self) where T : XContainer - { - foreach (XContainer root in source) - { - if (root != null) - { - if (self) yield return root; - XNode n = root; - while (true) - { - XContainer c = n as XContainer; - XNode first; - if (c != null && (first = c.FirstNode) != null) - { - n = first; - } - else - { - while (n != null && n != root && n == n.parent.content) n = n.parent; - if (n == null || n == root) break; - n = n.next; - } - yield return n; - } - } - } - } - - static IEnumerable GetDescendants(IEnumerable source, XName name, bool self) where T : XContainer - { - foreach (XContainer root in source) - { - if (root != null) - { - if (self) - { - XElement e = (XElement)root; - if (name == null || e.name == name) yield return e; - } - XNode n = root; - XContainer c = root; - while (true) - { - if (c != null && c.content is XNode) - { - n = ((XNode)c.content).next; - } - else - { - while (n != null && n != root && n == n.parent.content) n = n.parent; - if (n == null || n == root) break; - n = n.next; - } - XElement e = n as XElement; - if (e != null && (name == null || e.name == name)) yield return e; - c = e; - } - } - } - } - - static IEnumerable GetElements(IEnumerable source, XName name) where T : XContainer - { - foreach (XContainer root in source) - { - if (root != null) - { - XNode n = root.content as XNode; - if (n != null) - { - do - { - n = n.next; - XElement e = n as XElement; - if (e != null && (name == null || e.name == name)) yield return e; - } while (n.parent == root && n != root.content); - } - } - } - } - } - - internal class XNodeBuilder : XmlWriter - { - List content; - XContainer parent; - XName attrName; - string attrValue; - XContainer root; - - public XNodeBuilder(XContainer container) - { - root = container; - } - - public override XmlWriterSettings Settings - { - get - { - XmlWriterSettings settings = new XmlWriterSettings(); - settings.ConformanceLevel = ConformanceLevel.Auto; - return settings; - } - } - - public override WriteState WriteState - { - get { throw new NotSupportedException(); } // nop - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - Close(); - } - } - - private void Close() - { - root.Add(content); - } - - public override void Flush() - { - } - - public override string LookupPrefix(string namespaceName) - { - throw new NotSupportedException(); // nop - } - - public override void WriteBase64(byte[] buffer, int index, int count) - { - throw new NotSupportedException(SR.NotSupported_WriteBase64); - } - - public override void WriteCData(string text) - { - AddNode(new XCData(text)); - } - - public override void WriteCharEntity(char ch) - { - AddString(new string(ch, 1)); - } - - public override void WriteChars(char[] buffer, int index, int count) - { - AddString(new string(buffer, index, count)); - } - - public override void WriteComment(string text) - { - AddNode(new XComment(text)); - } - - public override void WriteDocType(string name, string pubid, string sysid, string subset) - { - AddNode(new XDocumentType(name, pubid, sysid, subset)); - } - - public override void WriteEndAttribute() - { - XAttribute a = new XAttribute(attrName, attrValue); - attrName = null; - attrValue = null; - if (parent != null) - { - parent.Add(a); - } - else - { - Add(a); - } - } - - public override void WriteEndDocument() - { - } - - public override void WriteEndElement() - { - parent = ((XElement)parent).parent; - } - - public override void WriteEntityRef(string name) - { - switch (name) - { - case "amp": - AddString("&"); - break; - case "apos": - AddString("'"); - break; - case "gt": - AddString(">"); - break; - case "lt": - AddString("<"); - break; - case "quot": - AddString("\""); - break; - default: - throw new NotSupportedException(SR.NotSupported_WriteEntityRef); - } - } - - public override void WriteFullEndElement() - { - XElement e = (XElement)parent; - if (e.IsEmpty) - { - e.Add(string.Empty); - } - parent = e.parent; - } - - public override void WriteProcessingInstruction(string name, string text) - { - if (name == "xml") - { - return; - } - AddNode(new XProcessingInstruction(name, text)); - } - - public override void WriteRaw(char[] buffer, int index, int count) - { - AddString(new string(buffer, index, count)); - } - - public override void WriteRaw(string data) - { - AddString(data); - } - - public override void WriteStartAttribute(string prefix, string localName, string namespaceName) - { - if (prefix == null) throw new ArgumentNullException("prefix"); - attrName = XNamespace.Get(prefix.Length == 0 ? string.Empty : namespaceName).GetName(localName); - attrValue = string.Empty; - } - - public override void WriteStartDocument() - { - } - - public override void WriteStartDocument(bool standalone) - { - } - - public override void WriteStartElement(string prefix, string localName, string namespaceName) - { - AddNode(new XElement(XNamespace.Get(namespaceName).GetName(localName))); - } - - public override void WriteString(string text) - { - AddString(text); - } - - public override void WriteSurrogateCharEntity(char lowCh, char highCh) - { - AddString(new string(new char[] { highCh, lowCh })); - } - - public override void WriteValue(DateTimeOffset value) - { - // For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime. - // Our internal writers should use the DateTimeOffset-String conversion from XmlConvert. - WriteString(XmlConvert.ToString(value)); - } - - public override void WriteWhitespace(string ws) - { - AddString(ws); - } - - void Add(object o) - { - if (content == null) - { - content = new List(); - } - content.Add(o); - } - - void AddNode(XNode n) - { - if (parent != null) - { - parent.Add(n); - } - else - { - Add(n); - } - XContainer c = n as XContainer; - if (c != null) - { - parent = c; - } - } - - void AddString(string s) - { - if (s == null) - { - return; - } - if (attrValue != null) - { - attrValue += s; - } - else if (parent != null) - { - parent.Add(s); - } - else - { - Add(s); - } - } - } - - internal class XNodeReader : XmlReader, IXmlLineInfo - { - // The reader position is encoded by the tuple (source, parent). - // Lazy text uses (instance, parent element). Attribute value - // uses (instance, parent attribute). End element uses (instance, - // instance). Common XObject uses (instance, null). - object source; - object parent; - ReadState state; - XNode root; - XmlNameTable nameTable; - bool omitDuplicateNamespaces; - - internal XNodeReader(XNode node, XmlNameTable nameTable, ReaderOptions options) - { - this.source = node; - this.root = node; - this.nameTable = nameTable != null ? nameTable : CreateNameTable(); - this.omitDuplicateNamespaces = (options & ReaderOptions.OmitDuplicateNamespaces) != 0 ? true : false; - } - - internal XNodeReader(XNode node, XmlNameTable nameTable) - : this(node, nameTable, - (node.GetSaveOptionsFromAnnotations() & SaveOptions.OmitDuplicateNamespaces) != 0 ? - ReaderOptions.OmitDuplicateNamespaces : ReaderOptions.None) - { - } - - public override int AttributeCount - { - get - { - if (!IsInteractive) - { - return 0; - } - int count = 0; - XElement e = GetElementInAttributeScope(); - if (e != null) - { - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - if (!omitDuplicateNamespaces || !IsDuplicateNamespaceAttribute(a)) - { - count++; - } - } while (a != e.lastAttr); - } - } - return count; - } - } - - public override string BaseURI - { - get - { - XObject o = source as XObject; - if (o != null) - { - return o.BaseUri; - } - o = parent as XObject; - if (o != null) - { - return o.BaseUri; - } - return string.Empty; - } - } - - public override int Depth - { - get - { - if (!IsInteractive) - { - return 0; - } - XObject o = source as XObject; - if (o != null) - { - return GetDepth(o); - } - o = parent as XObject; - if (o != null) - { - return GetDepth(o) + 1; - } - return 0; - } - } - - static int GetDepth(XObject o) - { - int depth = 0; - while (o.parent != null) - { - depth++; - o = o.parent; - } - if (o is XDocument) - { - depth--; - } - return depth; - } - - public override bool EOF - { - get { return state == ReadState.EndOfFile; } - } - - public override bool HasAttributes - { - get - { - if (!IsInteractive) - { - return false; - } - XElement e = GetElementInAttributeScope(); - if (e != null && e.lastAttr != null) - { - if (omitDuplicateNamespaces) - { - return GetFirstNonDuplicateNamespaceAttribute(e.lastAttr.next) != null; - } - else - { - return true; - } - } - else - { - return false; - } - } - } - - public override bool HasValue - { - get - { - if (!IsInteractive) - { - return false; - } - XObject o = source as XObject; - if (o != null) - { - switch (o.NodeType) - { - case XmlNodeType.Attribute: - case XmlNodeType.Text: - case XmlNodeType.CDATA: - case XmlNodeType.Comment: - case XmlNodeType.ProcessingInstruction: - case XmlNodeType.DocumentType: - return true; - default: - return false; - } - } - return true; - } - } - - public override bool IsEmptyElement - { - get - { - if (!IsInteractive) - { - return false; - } - XElement e = source as XElement; - return e != null && e.IsEmpty; - } - } - - public override string LocalName - { - get { return nameTable.Add(GetLocalName()); } - } - - string GetLocalName() - { - if (!IsInteractive) - { - return string.Empty; - } - XElement e = source as XElement; - if (e != null) - { - return e.Name.LocalName; - } - XAttribute a = source as XAttribute; - if (a != null) - { - return a.Name.LocalName; - } - XProcessingInstruction p = source as XProcessingInstruction; - if (p != null) - { - return p.Target; - } - XDocumentType n = source as XDocumentType; - if (n != null) - { - return n.Name; - } - return string.Empty; - } - - public override string Name - { - get - { - string prefix = GetPrefix(); - if (prefix.Length == 0) - { - return nameTable.Add(GetLocalName()); - } - return nameTable.Add(string.Concat(prefix, ":", GetLocalName())); - } - } - - public override string NamespaceURI - { - get { return nameTable.Add(GetNamespaceURI()); } - } - - string GetNamespaceURI() - { - if (!IsInteractive) - { - return string.Empty; - } - XElement e = source as XElement; - if (e != null) - { - return e.Name.NamespaceName; - } - XAttribute a = source as XAttribute; - if (a != null) - { - string namespaceName = a.Name.NamespaceName; - if (namespaceName.Length == 0 && a.Name.LocalName == "xmlns") - { - return XNamespace.xmlnsPrefixNamespace; - } - return namespaceName; - } - return string.Empty; - } - - public override XmlNameTable NameTable - { - get { return nameTable; } - } - - public override XmlNodeType NodeType - { - get - { - if (!IsInteractive) - { - return XmlNodeType.None; - } - XObject o = source as XObject; - if (o != null) - { - if (IsEndElement) - { - return XmlNodeType.EndElement; - } - XmlNodeType nt = o.NodeType; - if (nt != XmlNodeType.Text) - { - return nt; - } - if (o.parent != null && o.parent.parent == null && o.parent is XDocument) - { - return XmlNodeType.Whitespace; - } - return XmlNodeType.Text; - } - if (parent is XDocument) - { - return XmlNodeType.Whitespace; - } - return XmlNodeType.Text; - } - } - - public override string Prefix - { - get { return nameTable.Add(GetPrefix()); } - } - - string GetPrefix() - { - if (!IsInteractive) - { - return string.Empty; - } - XElement e = source as XElement; - if (e != null) - { - string prefix = e.GetPrefixOfNamespace(e.Name.Namespace); - if (prefix != null) - { - return prefix; - } - return string.Empty; - } - XAttribute a = source as XAttribute; - if (a != null) - { - string prefix = a.GetPrefixOfNamespace(a.Name.Namespace); - if (prefix != null) - { - return prefix; - } - } - return string.Empty; - } - - public override ReadState ReadState - { - get { return state; } - } - - public override XmlReaderSettings Settings - { - get - { - XmlReaderSettings settings = new XmlReaderSettings(); - settings.CheckCharacters = false; - return settings; - } - } - - public override string Value - { - get - { - if (!IsInteractive) - { - return string.Empty; - } - XObject o = source as XObject; - if (o != null) - { - switch (o.NodeType) - { - case XmlNodeType.Attribute: - return ((XAttribute)o).Value; - case XmlNodeType.Text: - case XmlNodeType.CDATA: - return ((XText)o).Value; - case XmlNodeType.Comment: - return ((XComment)o).Value; - case XmlNodeType.ProcessingInstruction: - return ((XProcessingInstruction)o).Data; - case XmlNodeType.DocumentType: - return ((XDocumentType)o).InternalSubset; - default: - return string.Empty; - } - } - return (string)source; - } - } - - public override string XmlLang - { - get - { - if (!IsInteractive) - { - return string.Empty; - } - XElement e = GetElementInScope(); - if (e != null) - { - XName name = XNamespace.Xml.GetName("lang"); - do - { - XAttribute a = e.Attribute(name); - if (a != null) - { - return a.Value; - } - e = e.parent as XElement; - } while (e != null); - } - return string.Empty; - } - } - - public override XmlSpace XmlSpace - { - get - { - if (!IsInteractive) - { - return XmlSpace.None; - } - XElement e = GetElementInScope(); - if (e != null) - { - XName name = XNamespace.Xml.GetName("space"); - do - { - XAttribute a = e.Attribute(name); - if (a != null) - { - switch (a.Value.Trim(new char[] { ' ', '\t', '\n', '\r' })) - { - case "preserve": - return XmlSpace.Preserve; - case "default": - return XmlSpace.Default; - default: - break; - } - } - e = e.parent as XElement; - } while (e != null); - } - return XmlSpace.None; - } - } - - protected override void Dispose(bool disposing) - { - if (disposing && ReadState != ReadState.Closed) - { - Close(); - } - } + internal struct Inserter + { + XContainer parent; + XNode previous; + string text; - private void Close() + public Inserter(XContainer parent, XNode anchor) { - source = null; - parent = null; - root = null; - state = ReadState.Closed; + this.parent = parent; + this.previous = anchor; + this.text = null; } - public override string GetAttribute(string name) + public void Add(object content) { - if (!IsInteractive) - { - return null; - } - XElement e = GetElementInAttributeScope(); - if (e != null) + AddContent(content); + if (text != null) { - string localName, namespaceName; - GetNameInAttributeScope(name, e, out localName, out namespaceName); - XAttribute a = e.lastAttr; - if (a != null) + if (parent.content == null) { - do + if (parent.SkipNotify()) { - a = a.next; - if (a.Name.LocalName == localName && a.Name.NamespaceName == namespaceName) + parent.content = text; + } + else + { + if (text.Length > 0) + { + InsertNode(new XText(text)); + } + else { - if (omitDuplicateNamespaces && IsDuplicateNamespaceAttribute(a)) + if (parent is XElement) { - return null; + // Change in the serialization of an empty element: + // from empty tag to start/end tag pair + parent.NotifyChanging(parent, XObjectChangeEventArgs.Value); + if (parent.content != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + parent.content = text; + parent.NotifyChanged(parent, XObjectChangeEventArgs.Value); } else { - return a.Value; + parent.content = text; } } - } while (a != e.lastAttr); - } - return null; - } - XDocumentType n = source as XDocumentType; - if (n != null) - { - switch (name) - { - case "PUBLIC": - return n.PublicId; - case "SYSTEM": - return n.SystemId; + } } - } - return null; - } - - public override string GetAttribute(string localName, string namespaceName) - { - if (!IsInteractive) - { - return null; - } - XElement e = GetElementInAttributeScope(); - if (e != null) - { - if (localName == "xmlns") + else if (text.Length > 0) { - if (namespaceName != null && namespaceName.Length == 0) + if (previous is XText && !(previous is XCData)) { - return null; + ((XText)previous).Value += text; } - if (namespaceName == XNamespace.xmlnsPrefixNamespace) + else { - namespaceName = string.Empty; + parent.ConvertTextToNode(); + InsertNode(new XText(text)); } } - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - if (a.Name.LocalName == localName && a.Name.NamespaceName == namespaceName) - { - if (omitDuplicateNamespaces && IsDuplicateNamespaceAttribute(a)) - { - return null; - } - else - { - return a.Value; - } - } - } while (a != e.lastAttr); - } } - return null; } - public override string GetAttribute(int index) + void AddContent(object content) { - if (!IsInteractive) + if (content == null) return; + XNode n = content as XNode; + if (n != null) + { + AddNode(n); + return; + } + string s = content as string; + if (s != null) + { + AddString(s); + return; + } + XStreamingElement x = content as XStreamingElement; + if (x != null) { - return null; + AddNode(new XElement(x)); + return; } - if (index < 0) + object[] o = content as object[]; + if (o != null) { - return null; + foreach (object obj in o) AddContent(obj); + return; } - XElement e = GetElementInAttributeScope(); + IEnumerable e = content as IEnumerable; if (e != null) { - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - if (!omitDuplicateNamespaces || !IsDuplicateNamespaceAttribute(a)) - { - if (index-- == 0) - { - return a.Value; - } - } - } while (a != e.lastAttr); - } + foreach (object obj in e) AddContent(obj); + return; } - return null; + if (content is XAttribute) throw new ArgumentException(SR.Argument_AddAttribute); + AddString(XContainer.GetStringValue(content)); } - public override string LookupNamespace(string prefix) + void AddNode(XNode n) { - if (!IsInteractive) + parent.ValidateNode(n, previous); + if (n.parent != null) { - return null; + n = n.CloneNode(); } - if (prefix == null) + else { - return null; + XNode p = parent; + while (p.parent != null) p = p.parent; + if (n == p) n = n.CloneNode(); } - XElement e = GetElementInScope(); - if (e != null) + parent.ConvertTextToNode(); + if (text != null) { - XNamespace ns = prefix.Length == 0 ? e.GetDefaultNamespace() : e.GetNamespaceOfPrefix(prefix); - if (ns != null) + if (text.Length > 0) { - return nameTable.Add(ns.NamespaceName); + if (previous is XText && !(previous is XCData)) + { + ((XText)previous).Value += text; + } + else + { + InsertNode(new XText(text)); + } } + text = null; } - return null; + InsertNode(n); } - public override bool MoveToAttribute(string name) + void AddString(string s) + { + parent.ValidateString(s); + text += s; + } + + // Prepends if previous == null, otherwise inserts after previous + void InsertNode(XNode n) { - if (!IsInteractive) + bool notify = parent.NotifyChanging(n, XObjectChangeEventArgs.Add); + if (n.parent != null) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + n.parent = parent; + if (parent.content == null || parent.content is string) { - return false; + n.next = n; + parent.content = n; } - XElement e = GetElementInAttributeScope(); - if (e != null) + else if (previous == null) { - string localName, namespaceName; - GetNameInAttributeScope(name, e, out localName, out namespaceName); - XAttribute a = e.lastAttr; - if (a != null) - { - do - { - a = a.next; - if (a.Name.LocalName == localName && - a.Name.NamespaceName == namespaceName) - { - if (omitDuplicateNamespaces && IsDuplicateNamespaceAttribute(a)) - { - // If it's a duplicate namespace attribute just act as if it doesn't exist - return false; - } - else - { - source = a; - parent = null; - return true; - } - } - } while (a != e.lastAttr); - } + XNode last = (XNode)parent.content; + n.next = last.next; + last.next = n; + } + else + { + n.next = previous.next; + previous.next = n; + if (parent.content == previous) parent.content = n; } - return false; + previous = n; + if (notify) parent.NotifyChanged(n, XObjectChangeEventArgs.Add); } + } + + internal struct NamespaceCache + { + XNamespace ns; + string namespaceName; - public override bool MoveToAttribute(string localName, string namespaceName) + public XNamespace Get(string namespaceName) { - if (!IsInteractive) - { - return false; - } - XElement e = GetElementInAttributeScope(); - if (e != null) + if ((object)namespaceName == (object)this.namespaceName) return this.ns; + this.namespaceName = namespaceName; + this.ns = XNamespace.Get(namespaceName); + return this.ns; + } + } + + internal struct ElementWriter + { + XmlWriter writer; + NamespaceResolver resolver; + + public ElementWriter(XmlWriter writer) + { + this.writer = writer; + this.resolver = new NamespaceResolver(); + } + + public void WriteElement(XElement e) + { + PushAncestors(e); + XElement root = e; + XNode n = e; + while (true) { - if (localName == "xmlns") + e = n as XElement; + if (e != null) { - if (namespaceName != null && namespaceName.Length == 0) - { - return false; - } - if (namespaceName == XNamespace.xmlnsPrefixNamespace) + WriteStartElement(e); + if (e.content == null) { - namespaceName = string.Empty; + WriteEndElement(); } - } - XAttribute a = e.lastAttr; - if (a != null) - { - do + else { - a = a.next; - if (a.Name.LocalName == localName && - a.Name.NamespaceName == namespaceName) + string s = e.content as string; + if (s != null) { - if (omitDuplicateNamespaces && IsDuplicateNamespaceAttribute(a)) - { - // If it's a duplicate namespace attribute just act as if it doesn't exist - return false; - } - else - { - source = a; - parent = null; - return true; - } + writer.WriteString(s); + WriteFullEndElement(); + } + else + { + n = ((XNode)e.content).next; + continue; } - } while (a != e.lastAttr); + } + } + else + { + n.WriteTo(writer); + } + while (n != root && n == n.parent.content) + { + n = n.parent; + WriteFullEndElement(); } + if (n == root) break; + n = n.next; } - return false; } - public override void MoveToAttribute(int index) + string GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace) { - if (!IsInteractive) - { - return; - } - if (index < 0) throw new ArgumentOutOfRangeException("index"); - XElement e = GetElementInAttributeScope(); - if (e != null) + string namespaceName = ns.NamespaceName; + if (namespaceName.Length == 0) return string.Empty; + string prefix = resolver.GetPrefixOfNamespace(ns, allowDefaultNamespace); + if (prefix != null) return prefix; + if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) return "xml"; + if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) return "xmlns"; + return null; + } + + void PushAncestors(XElement e) + { + while (true) { + e = e.parent as XElement; + if (e == null) break; XAttribute a = e.lastAttr; if (a != null) { do { a = a.next; - if (!omitDuplicateNamespaces || !IsDuplicateNamespaceAttribute(a)) + if (a.IsNamespaceDeclaration) { - // Only count those which are non-duplicates if we're asked to - if (index-- == 0) - { - source = a; - parent = null; - return; - } + resolver.AddFirst(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value)); } } while (a != e.lastAttr); } } - throw new ArgumentOutOfRangeException("index"); } - public override bool MoveToElement() + void PushElement(XElement e) { - if (!IsInteractive) - { - return false; - } - XAttribute a = source as XAttribute; - if (a == null) - { - a = parent as XAttribute; - } + resolver.PushScope(); + XAttribute a = e.lastAttr; if (a != null) { - if (a.parent != null) - { - source = a.parent; - parent = null; - return true; - } - } - return false; - } - - public override bool MoveToFirstAttribute() - { - if (!IsInteractive) - { - return false; - } - XElement e = GetElementInAttributeScope(); - if (e != null) - { - if (e.lastAttr != null) + do { - if (omitDuplicateNamespaces) - { - object na = GetFirstNonDuplicateNamespaceAttribute(e.lastAttr.next); - if (na == null) - { - return false; - } - source = na; - } - else + a = a.next; + if (a.IsNamespaceDeclaration) { - source = e.lastAttr.next; + resolver.Add(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value)); } - return true; - } + } while (a != e.lastAttr); } - return false; } - public override bool MoveToNextAttribute() + void WriteEndElement() { - if (!IsInteractive) - { - return false; - } - XElement e = source as XElement; - if (e != null) - { - if (IsEndElement) - { - return false; - } - if (e.lastAttr != null) - { - if (omitDuplicateNamespaces) - { - // Skip duplicate namespace attributes - // We must NOT modify the this.source until we find the one we're looking for - // because if we don't find anything, we need to stay positioned where we're now - object na = GetFirstNonDuplicateNamespaceAttribute(e.lastAttr.next); - if (na == null) - { - return false; - } - source = na; - } - else - { - source = e.lastAttr.next; - } - return true; - } - return false; - } - XAttribute a = source as XAttribute; - if (a == null) - { - a = parent as XAttribute; - } - if (a != null) - { - if (a.parent != null && ((XElement)a.parent).lastAttr != a) - { - if (omitDuplicateNamespaces) - { - // Skip duplicate namespace attributes - // We must NOT modify the this.source until we find the one we're looking for - // because if we don't find anything, we need to stay positioned where we're now - object na = GetFirstNonDuplicateNamespaceAttribute(a.next); - if (na == null) - { - return false; - } - source = na; - } - else - { - source = a.next; - } - parent = null; - return true; - } - } - return false; + writer.WriteEndElement(); + resolver.PopScope(); } - public override bool Read() + void WriteFullEndElement() { - switch (state) - { - case ReadState.Initial: - state = ReadState.Interactive; - XDocument d = source as XDocument; - if (d != null) - { - return ReadIntoDocument(d); - } - return true; - case ReadState.Interactive: - return Read(false); - default: - return false; - } + writer.WriteFullEndElement(); + resolver.PopScope(); } - public override bool ReadAttributeValue() + void WriteStartElement(XElement e) { - if (!IsInteractive) - { - return false; - } - XAttribute a = source as XAttribute; + PushElement(e); + XNamespace ns = e.Name.Namespace; + writer.WriteStartElement(GetPrefixOfNamespace(ns, true), e.Name.LocalName, ns.NamespaceName); + XAttribute a = e.lastAttr; if (a != null) { - return ReadIntoAttribute(a); + do + { + a = a.next; + ns = a.Name.Namespace; + string localName = a.Name.LocalName; + string namespaceName = ns.NamespaceName; + writer.WriteAttributeString(GetPrefixOfNamespace(ns, false), localName, namespaceName.Length == 0 && localName == "xmlns" ? XNamespace.xmlnsPrefixNamespace : namespaceName, a.Value); + } while (a != e.lastAttr); } - return false; } + } - public override bool ReadToDescendant(string localName, string namespaceName) + internal struct NamespaceResolver + { + class NamespaceDeclaration { - if (!IsInteractive) - { - return false; - } - MoveToElement(); - XElement c = source as XElement; - if (c != null && !c.IsEmpty) - { - if (IsEndElement) - { - return false; - } - foreach (XElement e in c.Descendants()) - { - if (e.Name.LocalName == localName && - e.Name.NamespaceName == namespaceName) - { - source = e; - return true; - } - } - IsEndElement = true; - } - return false; + public string prefix; + public XNamespace ns; + public int scope; + public NamespaceDeclaration prev; } - public override bool ReadToFollowing(string localName, string namespaceName) + int scope; + NamespaceDeclaration declaration; + NamespaceDeclaration rover; + + public void PushScope() { - while (Read()) - { - XElement e = source as XElement; - if (e != null) - { - if (IsEndElement) continue; - if (e.Name.LocalName == localName && e.Name.NamespaceName == namespaceName) - { - return true; - } - } - } - return false; + scope++; } - public override bool ReadToNextSibling(string localName, string namespaceName) + public void PopScope() { - if (!IsInteractive) - { - return false; - } - MoveToElement(); - if (source != root) + NamespaceDeclaration d = declaration; + if (d != null) { - XNode n = source as XNode; - if (n != null) + do { - foreach (XElement e in n.ElementsAfterSelf()) - { - if (e.Name.LocalName == localName && - e.Name.NamespaceName == namespaceName) - { - source = e; - IsEndElement = false; - return true; - } - } - if (n.parent is XElement) + d = d.prev; + if (d.scope != scope) break; + if (d == declaration) { - source = n.parent; - IsEndElement = true; - return false; + declaration = null; } - } - else - { - if (parent is XElement) + else { - source = parent; - parent = null; - IsEndElement = true; - return false; + declaration.prev = d.prev; } - } + rover = null; + } while (d != declaration && declaration != null); } - return ReadToEnd(); - } - - public override void ResolveEntity() - { + scope--; } - public override void Skip() + public void Add(string prefix, XNamespace ns) { - if (!IsInteractive) + NamespaceDeclaration d = new NamespaceDeclaration(); + d.prefix = prefix; + d.ns = ns; + d.scope = scope; + if (declaration == null) { - return; + declaration = d; } - Read(true); + else + { + d.prev = declaration.prev; + } + declaration.prev = d; + rover = null; } - bool IXmlLineInfo.HasLineInfo() + public void AddFirst(string prefix, XNamespace ns) { - if (IsEndElement) + NamespaceDeclaration d = new NamespaceDeclaration(); + d.prefix = prefix; + d.ns = ns; + d.scope = scope; + if (declaration == null) { - // Special case for EndElement - we store the line info differently in this case - // we also know that the current node (source) is XElement - XElement e = source as XElement; - if (e != null) - { - return e.Annotation() != null; - } + d.prev = d; } else { - IXmlLineInfo li = source as IXmlLineInfo; - if (li != null) - { - return li.HasLineInfo(); - } + d.prev = declaration.prev; + declaration.prev = d; } - return false; + declaration = d; + rover = null; } - int IXmlLineInfo.LineNumber + // Only elements allow default namespace declarations. The rover + // caches the last namespace declaration used by an element. + public string GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace) { - get + if (rover != null && rover.ns == ns && (allowDefaultNamespace || rover.prefix.Length > 0)) return rover.prefix; + NamespaceDeclaration d = declaration; + if (d != null) { - if (IsEndElement) + do { - // Special case for EndElement - we store the line info differently in this case - // we also know that the current node (source) is XElement - XElement e = source as XElement; - if (e != null) + d = d.prev; + if (d.ns == ns) { - LineInfoEndElementAnnotation a = e.Annotation(); - if (a != null) + NamespaceDeclaration x = declaration.prev; + while (x != d && x.prefix != d.prefix) { - return a.lineNumber; + x = x.prev; } - } - } - else - { - IXmlLineInfo li = source as IXmlLineInfo; - if (li != null) - { - return li.LineNumber; - } - } - return 0; - } - } - - int IXmlLineInfo.LinePosition - { - get - { - if (IsEndElement) - { - // Special case for EndElement - we store the line info differently in this case - // we also know that the current node (source) is XElement - XElement e = source as XElement; - if (e != null) - { - LineInfoEndElementAnnotation a = e.Annotation(); - if (a != null) + if (x == d) { - return a.linePosition; + if (allowDefaultNamespace) + { + rover = d; + return d.prefix; + } + else if (d.prefix.Length > 0) + { + return d.prefix; + } } } - } - else - { - IXmlLineInfo li = source as IXmlLineInfo; - if (li != null) - { - return li.LinePosition; - } - } - return 0; + } while (d != declaration); } + return null; } + } - bool IsEndElement - { - get { return parent == source; } - set { parent = value ? source : null; } - } - - bool IsInteractive - { - get { return state == ReadState.Interactive; } - } + internal struct StreamingElementWriter + { + XmlWriter writer; + XStreamingElement element; + List attributes; + NamespaceResolver resolver; - static XmlNameTable CreateNameTable() + public StreamingElementWriter(XmlWriter w) { - XmlNameTable nameTable = new NameTable(); - nameTable.Add(string.Empty); - nameTable.Add(XNamespace.xmlnsPrefixNamespace); - nameTable.Add(XNamespace.xmlPrefixNamespace); - return nameTable; + writer = w; + element = null; + attributes = new List(); + resolver = new NamespaceResolver(); } - XElement GetElementInAttributeScope() + void FlushElement() { - XElement e = source as XElement; - if (e != null) + if (element != null) { - if (IsEndElement) + PushElement(); + XNamespace ns = element.Name.Namespace; + writer.WriteStartElement(GetPrefixOfNamespace(ns, true), element.Name.LocalName, ns.NamespaceName); + foreach (XAttribute a in attributes) { - return null; + ns = a.Name.Namespace; + string localName = a.Name.LocalName; + string namespaceName = ns.NamespaceName; + writer.WriteAttributeString(GetPrefixOfNamespace(ns, false), localName, namespaceName.Length == 0 && localName == "xmlns" ? XNamespace.xmlnsPrefixNamespace : namespaceName, a.Value); } - return e; - } - XAttribute a = source as XAttribute; - if (a != null) - { - return (XElement)a.parent; - } - a = parent as XAttribute; - if (a != null) - { - return (XElement)a.parent; + element = null; + attributes.Clear(); } - return null; } - XElement GetElementInScope() + string GetPrefixOfNamespace(XNamespace ns, bool allowDefaultNamespace) { - XElement e = source as XElement; - if (e != null) - { - return e; - } - XNode n = source as XNode; - if (n != null) - { - return n.parent as XElement; - } - XAttribute a = source as XAttribute; - if (a != null) - { - return (XElement)a.parent; - } - e = parent as XElement; - if (e != null) - { - return e; - } - a = parent as XAttribute; - if (a != null) - { - return (XElement)a.parent; - } + string namespaceName = ns.NamespaceName; + if (namespaceName.Length == 0) return string.Empty; + string prefix = resolver.GetPrefixOfNamespace(ns, allowDefaultNamespace); + if (prefix != null) return prefix; + if ((object)namespaceName == (object)XNamespace.xmlPrefixNamespace) return "xml"; + if ((object)namespaceName == (object)XNamespace.xmlnsPrefixNamespace) return "xmlns"; return null; } - static void GetNameInAttributeScope(string qualifiedName, XElement e, out string localName, out string namespaceName) + void PushElement() { - if (qualifiedName != null && qualifiedName.Length != 0) + resolver.PushScope(); + foreach (XAttribute a in attributes) { - int i = qualifiedName.IndexOf(':'); - if (i != 0 && i != qualifiedName.Length - 1) + if (a.IsNamespaceDeclaration) { - if (i == -1) - { - localName = qualifiedName; - namespaceName = string.Empty; - return; - } - XNamespace ns = e.GetNamespaceOfPrefix(qualifiedName.Substring(0, i)); - if (ns != null) - { - localName = qualifiedName.Substring(i + 1, qualifiedName.Length - i - 1); - namespaceName = ns.NamespaceName; - return; - } + resolver.Add(a.Name.NamespaceName.Length == 0 ? string.Empty : a.Name.LocalName, XNamespace.Get(a.Value)); } } - localName = null; - namespaceName = null; } - bool Read(bool skipContent) + void Write(object content) { - XElement e = source as XElement; - if (e != null) - { - if (e.IsEmpty || IsEndElement || skipContent) - { - return ReadOverNode(e); - } - return ReadIntoElement(e); - } - XNode n = source as XNode; + if (content == null) return; + XNode n = content as XNode; if (n != null) { - return ReadOverNode(n); + WriteNode(n); + return; } - XAttribute a = source as XAttribute; - if (a != null) + string s = content as string; + if (s != null) { - return ReadOverAttribute(a, skipContent); + WriteString(s); + return; } - return ReadOverText(skipContent); - } - - bool ReadIntoDocument(XDocument d) - { - XNode n = d.content as XNode; - if (n != null) + XAttribute a = content as XAttribute; + if (a != null) { - source = n.next; - return true; + WriteAttribute(a); + return; } - string s = d.content as string; - if (s != null) + XStreamingElement x = content as XStreamingElement; + if (x != null) { - if (s.Length > 0) - { - source = s; - parent = d; - return true; - } + WriteStreamingElement(x); + return; } - return ReadToEnd(); - } - - bool ReadIntoElement(XElement e) - { - XNode n = e.content as XNode; - if (n != null) + object[] o = content as object[]; + if (o != null) { - source = n.next; - return true; + foreach (object obj in o) Write(obj); + return; } - string s = e.content as string; - if (s != null) + IEnumerable e = content as IEnumerable; + if (e != null) { - if (s.Length > 0) - { - source = s; - parent = e; - } - else - { - source = e; - IsEndElement = true; - } - return true; + foreach (object obj in e) Write(obj); + return; } - return ReadToEnd(); + WriteString(XContainer.GetStringValue(content)); } - bool ReadIntoAttribute(XAttribute a) + void WriteAttribute(XAttribute a) { - source = a.value; - parent = a; - return true; + if (element == null) throw new InvalidOperationException(SR.InvalidOperation_WriteAttribute); + attributes.Add(a); } - bool ReadOverAttribute(XAttribute a, bool skipContent) + void WriteNode(XNode n) { - XElement e = (XElement)a.parent; - if (e != null) - { - if (e.IsEmpty || skipContent) - { - return ReadOverNode(e); - } - return ReadIntoElement(e); - } - return ReadToEnd(); + FlushElement(); + n.WriteTo(writer); } - bool ReadOverNode(XNode n) + internal void WriteStreamingElement(XStreamingElement e) { - if (n == root) - { - return ReadToEnd(); - } - XNode next = n.next; - if (null == next || next == n || n == n.parent.content) + FlushElement(); + element = e; + Write(e.content); + bool contentWritten = element == null; + FlushElement(); + if (contentWritten) { - if (n.parent == null || (n.parent.parent == null && n.parent is XDocument)) - { - return ReadToEnd(); - } - source = n.parent; - IsEndElement = true; + writer.WriteFullEndElement(); } else { - source = next; - IsEndElement = false; + writer.WriteEndElement(); } - return true; + resolver.PopScope(); } - bool ReadOverText(bool skipContent) + void WriteString(string s) { - if (parent is XElement) - { - source = parent; - parent = null; - IsEndElement = true; - return true; - } - if (parent is XAttribute) - { - XAttribute a = (XAttribute)parent; - parent = null; - return ReadOverAttribute(a, skipContent); - } - return ReadToEnd(); + FlushElement(); + writer.WriteString(s); } + } - bool ReadToEnd() - { - state = ReadState.EndOfFile; - return false; - } + /// + /// Specifies the event type when an event is raised for an . + /// + public enum XObjectChange + { + /// + /// An has been or will be added to an . + /// + Add, /// - /// Determines if the specified attribute would be a duplicate namespace declaration - /// - one which we already reported on some ancestor, so it's not necessary to report it here + /// An has been or will be removed from an . /// - /// The attribute to test - /// true if the attribute is a duplicate namespace declaration attribute - bool IsDuplicateNamespaceAttribute(XAttribute candidateAttribute) - { - if (!candidateAttribute.IsNamespaceDeclaration) - { - return false; - } - else - { - // Split the method in two to enable inlining of this piece (Which will work for 95% of cases) - return IsDuplicateNamespaceAttributeInner(candidateAttribute); - } - } + Remove, - bool IsDuplicateNamespaceAttributeInner(XAttribute candidateAttribute) - { - // First of all - if this is an xmlns:xml declaration then it's a duplicate - // since xml prefix can't be redeclared and it's declared by default always. - if (candidateAttribute.Name.LocalName == "xml") - { - return true; - } - // The algorithm we use is: - // Go up the tree (but don't go higher than the root of this reader) - // and find the closest namespace declaration attribute which declares the same prefix - // If it declares that prefix to the exact same URI as ours does then ours is a duplicate - // Note that if we find a namespace declaration for the same prefix but with a different URI, then we don't have a dupe! - XElement element = candidateAttribute.parent as XElement; - if (element == root || element == null) - { - // If there's only the parent element of our attribute, there can be no duplicates - return false; - } - element = element.parent as XElement; - while (element != null) - { - // Search all attributes of this element for the same prefix declaration - // Trick - a declaration for the same prefix will have the exact same XName - so we can do a quick ref comparison of names - // (The default ns decl is represented by an XName "xmlns{}", even if you try to create - // an attribute with XName "xmlns{http://www.w3.org/2000/xmlns/}" it will fail, - // because it's treated as a declaration of prefix "xmlns" which is invalid) - XAttribute a = element.lastAttr; - if (a != null) - { - do - { - if (a.name == candidateAttribute.name) - { - // Found the same prefix decl - if (a.Value == candidateAttribute.Value) - { - // And it's for the same namespace URI as well - so ours is a duplicate - return true; - } - else - { - // It's not for the same namespace URI - which means we have to keep ours - // (no need to continue the search as this one overrides anything above it) - return false; - } - } - a = a.next; - } while (a != element.lastAttr); - } - if (element == root) - { - return false; - } - element = element.parent as XElement; - } - return false; - } + /// + /// An has been or will be renamed. + /// + Name, /// - /// Finds a first attribute (starting with the parameter) which is not a duplicate namespace attribute + /// The value of an has been or will be changed. + /// There is a special case for elements. Change in the serialization + /// of an empty element (either from an empty tag to start/end tag + /// pair or vice versa) raises this event. /// - /// The attribute to start with - /// The first attribute which is not a namespace attribute or null if the end of attributes has bean reached - XAttribute GetFirstNonDuplicateNamespaceAttribute(XAttribute candidate) - { - Debug.Assert(omitDuplicateNamespaces, "This method should only be caled if we're omitting duplicate namespace attribute." + - "For perf reason it's better to test this flag in the caller method."); - if (!IsDuplicateNamespaceAttribute(candidate)) - { - return candidate; - } + Value, + } - XElement e = candidate.parent as XElement; - if (e != null && candidate != e.lastAttr) - { - do - { - candidate = candidate.next; - if (!IsDuplicateNamespaceAttribute(candidate)) - { - return candidate; - } - } while (candidate != e.lastAttr); - } - return null; - } + /// + /// Specifies a set of options for Load(). + /// + [Flags()] + public enum LoadOptions + { + /// Default options. + None = 0x00000000, + + /// Preserve whitespace. + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Justification = "Back-compat with System.Xml.")] + PreserveWhitespace = 0x00000001, + + /// Set the BaseUri property. + SetBaseUri = 0x00000002, + + /// Set the IXmlLineInfo. + SetLineInfo = 0x00000004, } -} -namespace System.Xml.Linq -{ - static class XHelper + /// + /// Specifies a set of options for Save(). + /// + [Flags()] + public enum SaveOptions { - internal static string ToLower_InvariantCulture(string str) - { - return CultureInfo.InvariantCulture.TextInfo.ToLower(str); - } + /// Default options. + None = 0x00000000, - internal static bool IsInstanceOfType(object o, Type type) - { - Debug.Assert(type != null); + /// Disable formatting. + DisableFormatting = 0x00000001, - if (o == null) - return false; + /// Remove duplicate namespace declarations. + OmitDuplicateNamespaces = 0x00000002, + } - return type.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); - } + /// + /// Specifies a set of options for CreateReader(). + /// + [Flags()] + public enum ReaderOptions + { + /// Default options. + None = 0x00000000, + + /// Remove duplicate namespace declarations. + OmitDuplicateNamespaces = 0x00000001, } } diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XName.cs b/src/System.Xml.XDocument/System/Xml/Linq/XName.cs new file mode 100644 index 000000000000..38be61360e55 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XName.cs @@ -0,0 +1,198 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents a name of an XML element or attribute. This class cannot be inherited. + /// + [SuppressMessage("Microsoft.Usage", "CA2229:ImplementSerializationConstructors", Justification = "Deserialization handled by NameSerializer.")] + public sealed class XName : IEquatable + { + XNamespace ns; + string localName; + int hashCode; + + /// + /// Constructor, internal so that external users must go through the Get() method to create an XName. + /// + internal XName(XNamespace ns, string localName) + { + this.ns = ns; + this.localName = XmlConvert.VerifyNCName(localName); + this.hashCode = ns.GetHashCode() ^ localName.GetHashCode(); + } + + /// + /// Gets the local (unqualified) part of the name. + /// + /// + public string LocalName + { + get { return localName; } + } + + /// + /// Gets the namespace of the name. + /// + public XNamespace Namespace + { + get { return ns; } + } + + /// + /// Gets the namespace name part of the name. + /// + public string NamespaceName + { + get { return ns.NamespaceName; } + } + + /// + /// Returns the expanded XML name in the format: {namespaceName}localName. + /// + public override string ToString() + { + if (ns.NamespaceName.Length == 0) return localName; + return "{" + ns.NamespaceName + "}" + localName; + } + + /// + /// Returns an object created from the specified expanded name. + /// + /// + /// A string containing an expanded XML name in the format: {namespace}localname. + /// + /// + /// An object constructed from the specified expanded name. + /// + public static XName Get(string expandedName) + { + if (expandedName == null) throw new ArgumentNullException("expandedName"); + if (expandedName.Length == 0) throw new ArgumentException(SR.Format(SR.Argument_InvalidExpandedName, expandedName)); + if (expandedName[0] == '{') + { + int i = expandedName.LastIndexOf('}'); + if (i <= 1 || i == expandedName.Length - 1) throw new ArgumentException(SR.Format(SR.Argument_InvalidExpandedName, expandedName)); + return XNamespace.Get(expandedName, 1, i - 1).GetName(expandedName, i + 1, expandedName.Length - i - 1); + } + else + { + return XNamespace.None.GetName(expandedName); + } + } + + /// + /// Returns an object from a local name and a namespace. + /// + /// A local (unqualified) name. + /// An XML namespace. + /// An XName object created from the specified local name and namespace. + public static XName Get(string localName, string namespaceName) + { + return XNamespace.Get(namespaceName).GetName(localName); + } + + /// + /// Converts a string formatted as an expanded XML name ({namespace}localname) to an XName object. + /// + /// A string containing an expanded XML name in the format: {namespace}localname. + /// An XName object constructed from the expanded name. + [CLSCompliant(false)] + public static implicit operator XName(string expandedName) + { + return expandedName != null ? Get(expandedName) : null; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The XName to compare to the current XName. + /// + /// true if the specified is equal to the current XName; otherwise false. + /// + /// + /// For two objects to be equal, they must have the same expanded name. + /// + public override bool Equals(object obj) + { + return (object)this == obj; + } + + /// + /// Serves as a hash function for . GetHashCode is suitable + /// for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + return hashCode; + } + + // The overloads of == and != are included to enable comparisons between + // XName and string (e.g. element.Name == "foo"). C#'s predefined reference + // equality operators require one operand to be convertible to the type of + // the other through reference conversions only and do not consider the + // implicit conversion from string to XName. + + /// + /// Returns a value indicating whether two instances of are equal. + /// + /// The first XName to compare. + /// The second XName to compare. + /// true if left and right are equal; otherwise false. + /// + /// This overload is included to enable the comparison between + /// an instance of XName and string. + /// + public static bool operator ==(XName left, XName right) + { + return (object)left == (object)right; + } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first XName to compare. + /// The second XName to compare. + /// true if left and right are not equal; otherwise false. + /// + /// This overload is included to enable the comparison between + /// an instance of XName and string. + /// + public static bool operator !=(XName left, XName right) + { + return (object)left != (object)right; + } + + /// + /// Indicates whether the current is equal to + /// the specified + /// + /// The to compare with the + /// current + /// + /// Returns true if the current is equal to + /// the specified . Returns false otherwise. + /// + bool IEquatable.Equals(XName other) + { + return (object)this == (object)other; + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XNamespace.cs b/src/System.Xml.XDocument/System/Xml/Linq/XNamespace.cs new file mode 100644 index 000000000000..4e3ab68c41c0 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XNamespace.cs @@ -0,0 +1,329 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents an XML namespace. This class cannot be inherited. + /// + public sealed class XNamespace + { + internal const string xmlPrefixNamespace = "http://www.w3.org/XML/1998/namespace"; + internal const string xmlnsPrefixNamespace = "http://www.w3.org/2000/xmlns/"; + + static XHashtable namespaces; + static WeakReference refNone; + static WeakReference refXml; + static WeakReference refXmlns; + + string namespaceName; + int hashCode; + XHashtable names; + + const int NamesCapacity = 8; // Starting capacity of XName table, which must be power of 2 + const int NamespacesCapacity = 32; // Starting capacity of XNamespace table, which must be power of 2 + + /// + /// Constructor, internal so that external users must go through the Get() method to create an XNamespace. + /// + internal XNamespace(string namespaceName) + { + this.namespaceName = namespaceName; + this.hashCode = namespaceName.GetHashCode(); + names = new XHashtable(ExtractLocalName, NamesCapacity); + } + + /// + /// Gets the namespace name of the namespace. + /// + public string NamespaceName + { + get { return namespaceName; } + } + + /// + /// Returns an XName object created from the current instance and the specified local name. + /// + /// + /// The returned XName object is guaranteed to be atomic (i.e. the only one in the system for this + /// particular expanded name). + /// + public XName GetName(string localName) + { + if (localName == null) throw new ArgumentNullException("localName"); + return GetName(localName, 0, localName.Length); + } + + /// + /// Returns the namespace name of this . + /// + /// A string value containing the namespace name. + public override string ToString() + { + return namespaceName; + } + + /// + /// Gets the XNamespace object that corresponds to no namespace. + /// + /// + /// If an element or attribute is in no namespace, its namespace + /// will be set to the namespace returned by this property. + /// + public static XNamespace None + { + get + { + return EnsureNamespace(ref refNone, string.Empty); + } + } + + /// + /// Gets the XNamespace object that corresponds to the xml uri (http://www.w3.org/XML/1998/namespace). + /// + public static XNamespace Xml + { + get + { + return EnsureNamespace(ref refXml, xmlPrefixNamespace); + } + } + + /// + /// Gets the XNamespace object that corresponds to the xmlns uri (http://www.w3.org/2000/xmlns/). + /// + public static XNamespace Xmlns + { + get + { + return EnsureNamespace(ref refXmlns, xmlnsPrefixNamespace); + } + } + + /// + /// Gets an XNamespace created from the specified namespace name. + /// + /// + /// The returned XNamespace object is guaranteed to be atomic + /// (i.e. the only one in the system for that particular namespace name). + /// + public static XNamespace Get(string namespaceName) + { + if (namespaceName == null) throw new ArgumentNullException("namespaceName"); + return Get(namespaceName, 0, namespaceName.Length); + } + + /// + /// Converts a string containing a namespace name to an XNamespace. + /// + /// A string containing the namespace name. + /// An XNamespace constructed from the namespace name string. + [CLSCompliant(false)] + public static implicit operator XNamespace(string namespaceName) + { + return namespaceName != null ? Get(namespaceName) : null; + } + + /// + /// Combines an object with a local name to create an . + /// + /// The namespace for the expanded name. + /// The local name for the expanded name. + /// The new XName constructed from the namespace and local name. + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Functionality available via XNamespace.Get().")] + public static XName operator +(XNamespace ns, string localName) + { + if (ns == null) throw new ArgumentNullException("ns"); + return ns.GetName(localName); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The XNamespace to compare to the current XNamespace. + /// + /// true if the specified is equal to the current XNamespace; otherwise false. + /// + /// + /// For two objects to be equal they must have the same + /// namespace name. + /// + public override bool Equals(object obj) + { + return (object)this == obj; + } + + /// + /// Serves as a hash function for . GetHashCode is suitable + /// for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + return hashCode; + } + + + // The overloads of == and != are included to enable comparisons between + // XNamespace and string (e.g. element.Name.Namespace == "foo"). C#'s + // predefined reference equality operators require one operand to be + // convertible to the type of the other through reference conversions only + // and do not consider the implicit conversion from string to XNamespace. + + /// + /// Returns a value indicating whether two instances of are equal. + /// + /// The first XNamespace to compare. + /// The second XNamespace to compare. + /// true if left and right are equal; otherwise false. + /// + /// This overload is included to enable the comparison between + /// an instance of XNamespace and string. + /// + public static bool operator ==(XNamespace left, XNamespace right) + { + return (object)left == (object)right; + } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first XNamespace to compare. + /// The second XNamespace to compare. + /// true if left and right are not equal; otherwise false. + /// + /// This overload is included to enable the comparison between + /// an instance of XNamespace and string. + /// + public static bool operator !=(XNamespace left, XNamespace right) + { + return (object)left != (object)right; + } + + /// + /// Returns an created from this XNamespace and a portion of the passed in + /// local name parameter. The returned XName object is guaranteed to be atomic (i.e. the only one in the system for + /// this particular expanded name). + /// + internal XName GetName(string localName, int index, int count) + { + Debug.Assert(index >= 0 && index <= localName.Length, "Caller should have checked that index was in bounds"); + Debug.Assert(count >= 0 && index + count <= localName.Length, "Caller should have checked that count was in bounds"); + + // Attempt to get the local name from the hash table + XName name; + if (names.TryGetValue(localName, index, count, out name)) + return name; + + // No local name has yet been added, so add it now + return names.Add(new XName(this, localName.Substring(index, count))); + } + + /// + /// Returns an created from a portion of the passed in namespace name parameter. The returned XNamespace + /// object is guaranteed to be atomic (i.e. the only one in the system for this particular namespace name). + /// + internal static XNamespace Get(string namespaceName, int index, int count) + { + Debug.Assert(index >= 0 && index <= namespaceName.Length, "Caller should have checked that index was in bounds"); + Debug.Assert(count >= 0 && index + count <= namespaceName.Length, "Caller should have checked that count was in bounds"); + + if (count == 0) return None; + + // Use CompareExchange to ensure that exactly one XHashtable is used to store namespaces + if (namespaces == null) + Interlocked.CompareExchange(ref namespaces, new XHashtable(ExtractNamespace, NamespacesCapacity), null); + + WeakReference refNamespace; + XNamespace ns; + + // Keep looping until a non-null namespace has been retrieved + do + { + // Attempt to get the WeakReference for the namespace from the hash table + if (!namespaces.TryGetValue(namespaceName, index, count, out refNamespace)) + { + // If it is not there, first determine whether it's a special namespace + if (count == xmlPrefixNamespace.Length && string.CompareOrdinal(namespaceName, index, xmlPrefixNamespace, 0, count) == 0) return Xml; + if (count == xmlnsPrefixNamespace.Length && string.CompareOrdinal(namespaceName, index, xmlnsPrefixNamespace, 0, count) == 0) return Xmlns; + + // Go ahead and create the namespace and add it to the table + refNamespace = namespaces.Add(new WeakReference(new XNamespace(namespaceName.Substring(index, count)))); + } + + ns = (refNamespace != null) ? (XNamespace)refNamespace.Target : null; + } + while (ns == null); + + return ns; + } + + /// + /// This function is used by the ]]> to extract the local name part from an XName. The hash table + /// uses the local name as the hash key. + /// + private static string ExtractLocalName(XName n) + { + Debug.Assert(n != null, "Null name should never exist here"); + return n.LocalName; + } + + /// + /// This function is used by the ]]> to extract the XNamespace that the WeakReference is + /// referencing. In cases where the XNamespace has been cleaned up, this function returns null. + /// + private static string ExtractNamespace(WeakReference r) + { + XNamespace ns; + + if (r == null || (ns = (XNamespace)r.Target) == null) + return null; + + return ns.NamespaceName; + } + + /// + /// Ensure that an XNamespace object for 'namespaceName' has been atomically created. In other words, all outstanding + /// references to this particular namespace, on any thread, must all be to the same object. Care must be taken, + /// since other threads can be concurrently calling this method, and the target of a WeakReference can be cleaned up + /// at any time by the GC. + /// + private static XNamespace EnsureNamespace(ref WeakReference refNmsp, string namespaceName) + { + WeakReference refOld; + + // Keep looping until a non-null namespace has been retrieved + while (true) + { + // Save refNmsp in local variable, so we can work on a value that will not be changed by another thread + refOld = refNmsp; + + if (refOld != null) + { + // If the target of the WeakReference is non-null, then we're done--just return the value + XNamespace ns = (XNamespace)refOld.Target; + if (ns != null) return ns; + } + + // Either refNmsp is null, or its target is null, so update it + // Make sure to do this atomically, so that we can guarantee atomicity of XNamespace objects + Interlocked.CompareExchange(ref refNmsp, new WeakReference(new XNamespace(namespaceName)), refOld); + } + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XNode.cs b/src/System.Xml.XDocument/System/Xml/Linq/XNode.cs new file mode 100644 index 000000000000..25db7375b504 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XNode.cs @@ -0,0 +1,665 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents nodes (elements, comments, document type, processing instruction, + /// and text nodes) in the XML tree. + /// + /// + /// Nodes in the XML tree consist of objects of the following classes: + /// , + /// , + /// , + /// , + /// , + /// + /// Note that an is not an . + /// + public abstract class XNode : XObject + { + static XNodeDocumentOrderComparer documentOrderComparer; + static XNodeEqualityComparer equalityComparer; + + internal XNode next; + + internal XNode() { } + + /// + /// Gets the next sibling node of this node. + /// + /// + /// If this property does not have a parent, or if there is no next node, + /// then this property returns null. + /// + public XNode NextNode + { + get + { + return parent == null || this == parent.content ? null : next; + } + } + + /// + /// Gets the previous sibling node of this node. + /// + /// + /// If this property does not have a parent, or if there is no previous node, + /// then this property returns null. + /// + public XNode PreviousNode + { + get + { + if (parent == null) return null; + XNode n = ((XNode)parent.content).next; + XNode p = null; + while (n != this) + { + p = n; + n = n.next; + } + return p; + } + } + + /// + /// Gets a comparer that can compare the relative position of two nodes. + /// + public static XNodeDocumentOrderComparer DocumentOrderComparer + { + get + { + if (documentOrderComparer == null) documentOrderComparer = new XNodeDocumentOrderComparer(); + return documentOrderComparer; + } + } + + /// + /// Gets a comparer that can compare two nodes for value equality. + /// + public static XNodeEqualityComparer EqualityComparer + { + get + { + if (equalityComparer == null) equalityComparer = new XNodeEqualityComparer(); + return equalityComparer; + } + } + + /// + /// Adds the specified content immediately after this node. The + /// content can be simple content, a collection of + /// content objects, a parameter list of content objects, + /// or null. + /// + /// + /// Adds the specified content immediately after this node. + /// + /// + /// A content object containing simple content or a collection of content objects + /// to be added after this node. + /// + /// + /// Thrown if the parent is null. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void AddAfterSelf(object content) + { + if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); + new Inserter(parent, this).Add(content); + } + + /// + /// Adds the specified content immediately after this node. + /// + /// + /// A parameter list of content objects. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + /// + /// Thrown if the parent is null. + /// + public void AddAfterSelf(params object[] content) + { + AddAfterSelf((object)content); + } + + /// + /// Adds the specified content immediately before this node. The + /// content can be simple content, a collection of + /// content objects, a parameter list of content objects, + /// or null. + /// + /// + /// Adds the specified content immediately before this node. + /// + /// + /// A content object containing simple content or a collection of content objects + /// to be added after this node. + /// + /// + /// Thrown if the parent is null. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + public void AddBeforeSelf(object content) + { + if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); + XNode p = (XNode)parent.content; + while (p.next != this) p = p.next; + if (p == parent.content) p = null; + new Inserter(parent, p).Add(content); + } + + /// + /// Adds the specified content immediately before this node. + /// + /// + /// A parameter list of content objects. + /// + /// + /// See XContainer.Add(object content) for details about the content that can be added + /// using this method. + /// + /// + /// Thrown if the parent is null. + /// + public void AddBeforeSelf(params object[] content) + { + AddBeforeSelf((object)content); + } + + /// + /// Returns an collection of the ancestor elements for this node. + /// Optionally an node name can be specified to filter for a specific ancestor element. + /// + /// + /// Returns a collection of the ancestor elements of this node. + /// + /// + /// The ancestor elements of this node. + /// + /// + /// This method will not return itself in the results. + /// + public IEnumerable Ancestors() + { + return GetAncestors(null, false); + } + + /// + /// Returns a collection of the ancestor elements of this node with the specified name. + /// + /// + /// The name of the ancestor elements to find. + /// + /// + /// A collection of the ancestor elements of this node with the specified name. + /// + /// + /// This method will not return itself in the results. + /// + public IEnumerable Ancestors(XName name) + { + return name != null ? GetAncestors(name, false) : XElement.EmptySequence; + } + + /// + /// Compares two nodes to determine their relative XML document order. + /// + /// First node to compare. + /// Second node to compare. + /// + /// 0 if the nodes are equal; -1 if n1 is before n2; 1 if n1 is after n2. + /// + /// + /// Thrown if the two nodes do not share a common ancestor. + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Reviewed.")] + public static int CompareDocumentOrder(XNode n1, XNode n2) + { + if (n1 == n2) return 0; + if (n1 == null) return -1; + if (n2 == null) return 1; + if (n1.parent != n2.parent) + { + int height = 0; + XNode p1 = n1; + while (p1.parent != null) + { + p1 = p1.parent; + height++; + } + XNode p2 = n2; + while (p2.parent != null) + { + p2 = p2.parent; + height--; + } + if (p1 != p2) throw new InvalidOperationException(SR.InvalidOperation_MissingAncestor); + if (height < 0) + { + do + { + n2 = n2.parent; + height++; + } while (height != 0); + if (n1 == n2) return -1; + } + else if (height > 0) + { + do + { + n1 = n1.parent; + height--; + } while (height != 0); + if (n1 == n2) return 1; + } + while (n1.parent != n2.parent) + { + n1 = n1.parent; + n2 = n2.parent; + } + } + else if (n1.parent == null) + { + throw new InvalidOperationException(SR.InvalidOperation_MissingAncestor); + } + XNode n = (XNode)n1.parent.content; + while (true) + { + n = n.next; + if (n == n1) return -1; + if (n == n2) return 1; + } + } + + /// + /// Creates an for the node. + /// + /// An that can be used to read the node and its descendants. + public XmlReader CreateReader() + { + return new XNodeReader(this, null); + } + + /// + /// Creates an for the node. + /// + /// + /// Options to be used for the returned reader. These override the default usage of annotations from the tree. + /// + /// An that can be used to read the node and its descendants. + public XmlReader CreateReader(ReaderOptions readerOptions) + { + return new XNodeReader(this, null, readerOptions); + } + + /// + /// Returns a collection of the sibling nodes after this node, in document order. + /// + /// + /// This method only includes sibling nodes in the returned collection. + /// + /// The nodes after this node. + public IEnumerable NodesAfterSelf() + { + XNode n = this; + while (n.parent != null && n != n.parent.content) + { + n = n.next; + yield return n; + } + } + + /// + /// Returns a collection of the sibling nodes before this node, in document order. + /// + /// + /// This method only includes sibling nodes in the returned collection. + /// + /// The nodes after this node. + public IEnumerable NodesBeforeSelf() + { + if (parent != null) + { + XNode n = (XNode)parent.content; + do + { + n = n.next; + if (n == this) break; + yield return n; + } while (parent != null && parent == n.parent); + } + } + + /// + /// Returns a collection of the sibling element nodes after this node, in document order. + /// + /// + /// This method only includes sibling element nodes in the returned collection. + /// + /// The element nodes after this node. + public IEnumerable ElementsAfterSelf() + { + return GetElementsAfterSelf(null); + } + + /// + /// Returns a collection of the sibling element nodes with the specified name + /// after this node, in document order. + /// + /// + /// This method only includes sibling element nodes in the returned collection. + /// + /// The element nodes after this node with the specified name. + /// The name of elements to enumerate. + public IEnumerable ElementsAfterSelf(XName name) + { + return name != null ? GetElementsAfterSelf(name) : XElement.EmptySequence; + } + + /// + /// Returns a collection of the sibling element nodes before this node, in document order. + /// + /// + /// This method only includes sibling element nodes in the returned collection. + /// + /// The element nodes before this node. + public IEnumerable ElementsBeforeSelf() + { + return GetElementsBeforeSelf(null); + } + + /// + /// Returns a collection of the sibling element nodes with the specified name + /// before this node, in document order. + /// + /// + /// This method only includes sibling element nodes in the returned collection. + /// + /// The element nodes before this node with the specified name. + /// The name of elements to enumerate. + public IEnumerable ElementsBeforeSelf(XName name) + { + return name != null ? GetElementsBeforeSelf(name) : XElement.EmptySequence; + } + + /// + /// Determines if the current node appears after a specified node + /// in terms of document order. + /// + /// The node to compare for document order. + /// True if this node appears after the specified node; false if not. + public bool IsAfter(XNode node) + { + return CompareDocumentOrder(this, node) > 0; + } + + /// + /// Determines if the current node appears before a specified node + /// in terms of document order. + /// + /// The node to compare for document order. + /// True if this node appears before the specified node; false if not. + public bool IsBefore(XNode node) + { + return CompareDocumentOrder(this, node) < 0; + } + + /// + /// Creates an from an . + /// The runtime type of the node is determined by the node type + /// () of the first node encountered + /// in the reader. + /// + /// An positioned at the node to read into this . + /// An that contains the nodes read from the reader. + /// + /// Thrown if the is not positioned on a recognized node type. + /// + public static XNode ReadFrom(XmlReader reader) + { + if (reader == null) throw new ArgumentNullException("reader"); + if (reader.ReadState != ReadState.Interactive) throw new InvalidOperationException(SR.InvalidOperation_ExpectedInteractive); + switch (reader.NodeType) + { + case XmlNodeType.Text: + case XmlNodeType.SignificantWhitespace: + case XmlNodeType.Whitespace: + return new XText(reader); + case XmlNodeType.CDATA: + return new XCData(reader); + case XmlNodeType.Comment: + return new XComment(reader); + case XmlNodeType.DocumentType: + return new XDocumentType(reader); + case XmlNodeType.Element: + return new XElement(reader); + case XmlNodeType.ProcessingInstruction: + return new XProcessingInstruction(reader); + default: + throw new InvalidOperationException(SR.Format(SR.InvalidOperation_UnexpectedNodeType, reader.NodeType)); + } + } + + /// + /// Removes this XNode from the underlying XML tree. + /// + /// + /// Thrown if the parent is null. + /// + public void Remove() + { + if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); + parent.RemoveNode(this); + } + + /// + /// Replaces this node with the specified content. The + /// content can be simple content, a collection of + /// content objects, a parameter list of content objects, + /// or null. + /// + /// + /// Replaces the content of this . + /// + /// Content that replaces this node. + public void ReplaceWith(object content) + { + if (parent == null) throw new InvalidOperationException(SR.InvalidOperation_MissingParent); + XContainer c = parent; + XNode p = (XNode)parent.content; + while (p.next != this) p = p.next; + if (p == parent.content) p = null; + parent.RemoveNode(this); + if (p != null && p.parent != c) throw new InvalidOperationException(SR.InvalidOperation_ExternalCode); + new Inserter(c, p).Add(content); + } + + /// + /// Replaces this node with the specified content. + /// + /// Content that replaces this node. + public void ReplaceWith(params object[] content) + { + ReplaceWith((object)content); + } + + /// + /// Provides the formatted XML text representation. + /// You can use the SaveOptions as an annotation on this node or its ancestors, then this method will use those options. + /// + /// A formatted XML string. + public override string ToString() + { + return GetXmlString(GetSaveOptionsFromAnnotations()); + } + + /// + /// Provides the XML text representation. + /// + /// + /// If SaveOptions.DisableFormatting is enabled the output is not indented. + /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. + /// + /// An XML string. + public string ToString(SaveOptions options) + { + return GetXmlString(options); + } + + /// + /// Compares the values of two nodes, including the values of all descendant nodes. + /// + /// The first node to compare. + /// The second node to compare. + /// true if the nodes are equal, false otherwise. + /// + /// A null node is equal to another null node but unequal to a non-null + /// node. Two objects of different types are never equal. Two + /// nodes are equal if they contain the same text. Two + /// nodes are equal if they have the same tag name, the same + /// set of attributes with the same values, and, ignoring comments and processing + /// instructions, contain two equal length sequences of equal content nodes. + /// Two s are equal if their root nodes are equal. Two + /// nodes are equal if they contain the same comment text. + /// Two nodes are equal if they have the same + /// target and data. Two nodes are equal if the have the + /// same name, public id, system id, and internal subset. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Reviewed.")] + public static bool DeepEquals(XNode n1, XNode n2) + { + if (n1 == n2) return true; + if (n1 == null || n2 == null) return false; + return n1.DeepEquals(n2); + } + + /// + /// Write the current node to an . + /// + /// The to write the current node into. + public abstract void WriteTo(XmlWriter writer); + + internal virtual void AppendText(StringBuilder sb) + { + } + + internal abstract XNode CloneNode(); + + internal abstract bool DeepEquals(XNode node); + + internal IEnumerable GetAncestors(XName name, bool self) + { + XElement e = (self ? this : parent) as XElement; + while (e != null) + { + if (name == null || e.name == name) yield return e; + e = e.parent as XElement; + } + } + + IEnumerable GetElementsAfterSelf(XName name) + { + XNode n = this; + while (n.parent != null && n != n.parent.content) + { + n = n.next; + XElement e = n as XElement; + if (e != null && (name == null || e.name == name)) yield return e; + } + } + + IEnumerable GetElementsBeforeSelf(XName name) + { + if (parent != null) + { + XNode n = (XNode)parent.content; + do + { + n = n.next; + if (n == this) break; + XElement e = n as XElement; + if (e != null && (name == null || e.name == name)) yield return e; + } while (parent != null && parent == n.parent); + } + } + + internal abstract int GetDeepHashCode(); + + // The settings simulate a non-validating processor with the external + // entity resolution disabled. The processing of the internal subset is + // enabled by default. In order to prevent DoS attacks, the expanded + // size of the internal subset is limited to 10 million characters. + internal static XmlReaderSettings GetXmlReaderSettings(LoadOptions o) + { + XmlReaderSettings rs = new XmlReaderSettings(); + if ((o & LoadOptions.PreserveWhitespace) == 0) rs.IgnoreWhitespace = true; + + // DtdProcessing.Parse; Parse is not defined in the public contract + rs.DtdProcessing = (DtdProcessing)2; + rs.MaxCharactersFromEntities = (long)1e7; + // rs.XmlResolver = null; + return rs; + } + + internal static XmlWriterSettings GetXmlWriterSettings(SaveOptions o) + { + XmlWriterSettings ws = new XmlWriterSettings(); + if ((o & SaveOptions.DisableFormatting) == 0) ws.Indent = true; + if ((o & SaveOptions.OmitDuplicateNamespaces) != 0) ws.NamespaceHandling |= NamespaceHandling.OmitDuplicates; + return ws; + } + + string GetXmlString(SaveOptions o) + { + using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture)) + { + XmlWriterSettings ws = new XmlWriterSettings(); + ws.OmitXmlDeclaration = true; + if ((o & SaveOptions.DisableFormatting) == 0) ws.Indent = true; + if ((o & SaveOptions.OmitDuplicateNamespaces) != 0) ws.NamespaceHandling |= NamespaceHandling.OmitDuplicates; + if (this is XText) ws.ConformanceLevel = ConformanceLevel.Fragment; + using (XmlWriter w = XmlWriter.Create(sw, ws)) + { + XDocument n = this as XDocument; + if (n != null) + { + n.WriteContentTo(w); + } + else + { + WriteTo(w); + } + } + return sw.ToString(); + } + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XNodeBuilder.cs b/src/System.Xml.XDocument/System/Xml/Linq/XNodeBuilder.cs new file mode 100644 index 000000000000..35310bfd82c2 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XNodeBuilder.cs @@ -0,0 +1,267 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + internal class XNodeBuilder : XmlWriter + { + List content; + XContainer parent; + XName attrName; + string attrValue; + XContainer root; + + public XNodeBuilder(XContainer container) + { + root = container; + } + + public override XmlWriterSettings Settings + { + get + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.ConformanceLevel = ConformanceLevel.Auto; + return settings; + } + } + + public override WriteState WriteState + { + get { throw new NotSupportedException(); } // nop + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Close(); + } + } + + private void Close() + { + root.Add(content); + } + + public override void Flush() + { + } + + public override string LookupPrefix(string namespaceName) + { + throw new NotSupportedException(); // nop + } + + public override void WriteBase64(byte[] buffer, int index, int count) + { + throw new NotSupportedException(SR.NotSupported_WriteBase64); + } + + public override void WriteCData(string text) + { + AddNode(new XCData(text)); + } + + public override void WriteCharEntity(char ch) + { + AddString(new string(ch, 1)); + } + + public override void WriteChars(char[] buffer, int index, int count) + { + AddString(new string(buffer, index, count)); + } + + public override void WriteComment(string text) + { + AddNode(new XComment(text)); + } + + public override void WriteDocType(string name, string pubid, string sysid, string subset) + { + AddNode(new XDocumentType(name, pubid, sysid, subset)); + } + + public override void WriteEndAttribute() + { + XAttribute a = new XAttribute(attrName, attrValue); + attrName = null; + attrValue = null; + if (parent != null) + { + parent.Add(a); + } + else + { + Add(a); + } + } + + public override void WriteEndDocument() + { + } + + public override void WriteEndElement() + { + parent = ((XElement)parent).parent; + } + + public override void WriteEntityRef(string name) + { + switch (name) + { + case "amp": + AddString("&"); + break; + case "apos": + AddString("'"); + break; + case "gt": + AddString(">"); + break; + case "lt": + AddString("<"); + break; + case "quot": + AddString("\""); + break; + default: + throw new NotSupportedException(SR.NotSupported_WriteEntityRef); + } + } + + public override void WriteFullEndElement() + { + XElement e = (XElement)parent; + if (e.IsEmpty) + { + e.Add(string.Empty); + } + parent = e.parent; + } + + public override void WriteProcessingInstruction(string name, string text) + { + if (name == "xml") + { + return; + } + AddNode(new XProcessingInstruction(name, text)); + } + + public override void WriteRaw(char[] buffer, int index, int count) + { + AddString(new string(buffer, index, count)); + } + + public override void WriteRaw(string data) + { + AddString(data); + } + + public override void WriteStartAttribute(string prefix, string localName, string namespaceName) + { + if (prefix == null) throw new ArgumentNullException("prefix"); + attrName = XNamespace.Get(prefix.Length == 0 ? string.Empty : namespaceName).GetName(localName); + attrValue = string.Empty; + } + + public override void WriteStartDocument() + { + } + + public override void WriteStartDocument(bool standalone) + { + } + + public override void WriteStartElement(string prefix, string localName, string namespaceName) + { + AddNode(new XElement(XNamespace.Get(namespaceName).GetName(localName))); + } + + public override void WriteString(string text) + { + AddString(text); + } + + public override void WriteSurrogateCharEntity(char lowCh, char highCh) + { + AddString(new string(new char[] { highCh, lowCh })); + } + + public override void WriteValue(DateTimeOffset value) + { + // For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime. + // Our internal writers should use the DateTimeOffset-String conversion from XmlConvert. + WriteString(XmlConvert.ToString(value)); + } + + public override void WriteWhitespace(string ws) + { + AddString(ws); + } + + void Add(object o) + { + if (content == null) + { + content = new List(); + } + content.Add(o); + } + + void AddNode(XNode n) + { + if (parent != null) + { + parent.Add(n); + } + else + { + Add(n); + } + XContainer c = n as XContainer; + if (c != null) + { + parent = c; + } + } + + void AddString(string s) + { + if (s == null) + { + return; + } + if (attrValue != null) + { + attrValue += s; + } + else if (parent != null) + { + parent.Add(s); + } + else + { + Add(s); + } + } + } +} \ No newline at end of file diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XNodeDocumentOrderComparer.cs b/src/System.Xml.XDocument/System/Xml/Linq/XNodeDocumentOrderComparer.cs new file mode 100644 index 000000000000..d705fba0d520 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XNodeDocumentOrderComparer.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Contains functionality to compare nodes for their document order. + /// This class cannot be inherited. + /// + public sealed class XNodeDocumentOrderComparer : + IComparer, + IComparer + { + /// + /// Compares two nodes to determine their relative XML document order. + /// + /// The first node to compare. + /// The second node to compare. + /// + /// 0 if the nodes are equal; + /// -1 if x is before y; + /// 1 if x is after y. + /// + /// + /// Thrown if the two nodes do not share a common ancestor. + /// + public int Compare(XNode x, XNode y) + { + return XNode.CompareDocumentOrder(x, y); + } + + /// + /// Compares two nodes to determine their relative XML document order. + /// + /// The first node to compare. + /// The second node to compare. + /// + /// 0 if the nodes are equal; + /// -1 if x is before y; + /// 1 if x is after y. + /// + /// + /// Thrown if the two nodes do not share a common ancestor. + /// + /// + /// Thrown if either of the two nodes are not derived from XNode. + /// + int IComparer.Compare(object x, object y) + { + XNode n1 = x as XNode; + if (n1 == null && x != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "x"); + XNode n2 = y as XNode; + if (n2 == null && y != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "y"); + return Compare(n1, n2); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XNodeEqualityComparer.cs b/src/System.Xml.XDocument/System/Xml/Linq/XNodeEqualityComparer.cs new file mode 100644 index 000000000000..d53d6567b2bb --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XNodeEqualityComparer.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + + /// + /// Contains functionality to compare nodes for value equality. + /// This class cannot be inherited. + /// + public sealed class XNodeEqualityComparer : + IEqualityComparer, + IEqualityComparer + { + /// + /// Compares the values of two nodes. + /// + /// The first node to compare. + /// The second node to compare. + /// true if the nodes are equal, false otherwise. + /// + /// A null node is equal to another null node but unequal to a non-null + /// node. Two s of different types are never equal. Two + /// nodes are equal if they contain the same text. Two + /// nodes are equal if they have the same tag name, the same + /// set of attributes with the same values, and, ignoring comments and processing + /// instructions, contain two equal length sequences of pairwise equal content nodes. + /// Two s are equal if their root nodes are equal. Two + /// nodes are equal if they contain the same comment text. + /// Two nodes are equal if they have the same + /// target and data. Two nodes are equal if the have the + /// same name, public id, system id, and internal subset. + /// + public bool Equals(XNode x, XNode y) + { + return XNode.DeepEquals(x, y); + } + + /// + /// Returns a hash code based on an objects value. + /// + /// The node to hash. + /// A value-based hash code for the node. + /// + /// The class's implementation of + /// is based on the referential identity of the node. This method computes a + /// hash code based on the value of the node. + /// + public int GetHashCode(XNode obj) + { + return obj != null ? obj.GetDeepHashCode() : 0; + } + + /// + /// Compares the values of two nodes. + /// + /// The first node to compare. + /// The second node to compare. + /// true if the nodes are equal, false otherwise. + /// + /// A null node is equal to another null node but unequal to a non-null + /// node. Two s of different types are never equal. Two + /// nodes are equal if they contain the same text. Two + /// nodes are equal if they have the same tag name, the same + /// set of attributes with the same values, and, ignoring comments and processing + /// instructions, contain two equal length sequences of pairwise equal content nodes. + /// Two s are equal if their root nodes are equal. Two + /// nodes are equal if they contain the same comment text. + /// Two nodes are equal if they have the same + /// target and data. Two nodes are equal if the have the + /// same name, public id, system id, and internal subset. + /// + bool IEqualityComparer.Equals(object x, object y) + { + XNode n1 = x as XNode; + if (n1 == null && x != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "x"); + XNode n2 = y as XNode; + if (n2 == null && y != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "y"); + return Equals(n1, n2); + } + + /// + /// Returns a hash code based on a node's value. + /// + /// The node to hash. + /// A value-based hash code for the node. + /// + /// The class's implementation of + /// is based on the referential identity of the node. This method computes a + /// hash code based on the value of the node. + /// + int IEqualityComparer.GetHashCode(object obj) + { + XNode n = obj as XNode; + if (n == null && obj != null) throw new ArgumentException(SR.Format(SR.Argument_MustBeDerivedFrom, typeof(XNode)), "obj"); + return GetHashCode(n); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XNodeReader.cs b/src/System.Xml.XDocument/System/Xml/Linq/XNodeReader.cs new file mode 100644 index 000000000000..4609cf74687c --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XNodeReader.cs @@ -0,0 +1,1422 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + internal class XNodeReader : XmlReader, IXmlLineInfo + { + // The reader position is encoded by the tuple (source, parent). + // Lazy text uses (instance, parent element). Attribute value + // uses (instance, parent attribute). End element uses (instance, + // instance). Common XObject uses (instance, null). + object source; + object parent; + ReadState state; + XNode root; + XmlNameTable nameTable; + bool omitDuplicateNamespaces; + + internal XNodeReader(XNode node, XmlNameTable nameTable, ReaderOptions options) + { + this.source = node; + this.root = node; + this.nameTable = nameTable != null ? nameTable : CreateNameTable(); + this.omitDuplicateNamespaces = (options & ReaderOptions.OmitDuplicateNamespaces) != 0 ? true : false; + } + + internal XNodeReader(XNode node, XmlNameTable nameTable) + : this(node, nameTable, + (node.GetSaveOptionsFromAnnotations() & SaveOptions.OmitDuplicateNamespaces) != 0 ? + ReaderOptions.OmitDuplicateNamespaces : ReaderOptions.None) + { + } + + public override int AttributeCount + { + get + { + if (!IsInteractive) + { + return 0; + } + int count = 0; + XElement e = GetElementInAttributeScope(); + if (e != null) + { + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (!omitDuplicateNamespaces || !IsDuplicateNamespaceAttribute(a)) + { + count++; + } + } while (a != e.lastAttr); + } + } + return count; + } + } + + public override string BaseURI + { + get + { + XObject o = source as XObject; + if (o != null) + { + return o.BaseUri; + } + o = parent as XObject; + if (o != null) + { + return o.BaseUri; + } + return string.Empty; + } + } + + public override int Depth + { + get + { + if (!IsInteractive) + { + return 0; + } + XObject o = source as XObject; + if (o != null) + { + return GetDepth(o); + } + o = parent as XObject; + if (o != null) + { + return GetDepth(o) + 1; + } + return 0; + } + } + + static int GetDepth(XObject o) + { + int depth = 0; + while (o.parent != null) + { + depth++; + o = o.parent; + } + if (o is XDocument) + { + depth--; + } + return depth; + } + + public override bool EOF + { + get { return state == ReadState.EndOfFile; } + } + + public override bool HasAttributes + { + get + { + if (!IsInteractive) + { + return false; + } + XElement e = GetElementInAttributeScope(); + if (e != null && e.lastAttr != null) + { + if (omitDuplicateNamespaces) + { + return GetFirstNonDuplicateNamespaceAttribute(e.lastAttr.next) != null; + } + else + { + return true; + } + } + else + { + return false; + } + } + } + + public override bool HasValue + { + get + { + if (!IsInteractive) + { + return false; + } + XObject o = source as XObject; + if (o != null) + { + switch (o.NodeType) + { + case XmlNodeType.Attribute: + case XmlNodeType.Text: + case XmlNodeType.CDATA: + case XmlNodeType.Comment: + case XmlNodeType.ProcessingInstruction: + case XmlNodeType.DocumentType: + return true; + default: + return false; + } + } + return true; + } + } + + public override bool IsEmptyElement + { + get + { + if (!IsInteractive) + { + return false; + } + XElement e = source as XElement; + return e != null && e.IsEmpty; + } + } + + public override string LocalName + { + get { return nameTable.Add(GetLocalName()); } + } + + string GetLocalName() + { + if (!IsInteractive) + { + return string.Empty; + } + XElement e = source as XElement; + if (e != null) + { + return e.Name.LocalName; + } + XAttribute a = source as XAttribute; + if (a != null) + { + return a.Name.LocalName; + } + XProcessingInstruction p = source as XProcessingInstruction; + if (p != null) + { + return p.Target; + } + XDocumentType n = source as XDocumentType; + if (n != null) + { + return n.Name; + } + return string.Empty; + } + + public override string Name + { + get + { + string prefix = GetPrefix(); + if (prefix.Length == 0) + { + return nameTable.Add(GetLocalName()); + } + return nameTable.Add(string.Concat(prefix, ":", GetLocalName())); + } + } + + public override string NamespaceURI + { + get { return nameTable.Add(GetNamespaceURI()); } + } + + string GetNamespaceURI() + { + if (!IsInteractive) + { + return string.Empty; + } + XElement e = source as XElement; + if (e != null) + { + return e.Name.NamespaceName; + } + XAttribute a = source as XAttribute; + if (a != null) + { + string namespaceName = a.Name.NamespaceName; + if (namespaceName.Length == 0 && a.Name.LocalName == "xmlns") + { + return XNamespace.xmlnsPrefixNamespace; + } + return namespaceName; + } + return string.Empty; + } + + public override XmlNameTable NameTable + { + get { return nameTable; } + } + + public override XmlNodeType NodeType + { + get + { + if (!IsInteractive) + { + return XmlNodeType.None; + } + XObject o = source as XObject; + if (o != null) + { + if (IsEndElement) + { + return XmlNodeType.EndElement; + } + XmlNodeType nt = o.NodeType; + if (nt != XmlNodeType.Text) + { + return nt; + } + if (o.parent != null && o.parent.parent == null && o.parent is XDocument) + { + return XmlNodeType.Whitespace; + } + return XmlNodeType.Text; + } + if (parent is XDocument) + { + return XmlNodeType.Whitespace; + } + return XmlNodeType.Text; + } + } + + public override string Prefix + { + get { return nameTable.Add(GetPrefix()); } + } + + string GetPrefix() + { + if (!IsInteractive) + { + return string.Empty; + } + XElement e = source as XElement; + if (e != null) + { + string prefix = e.GetPrefixOfNamespace(e.Name.Namespace); + if (prefix != null) + { + return prefix; + } + return string.Empty; + } + XAttribute a = source as XAttribute; + if (a != null) + { + string prefix = a.GetPrefixOfNamespace(a.Name.Namespace); + if (prefix != null) + { + return prefix; + } + } + return string.Empty; + } + + public override ReadState ReadState + { + get { return state; } + } + + public override XmlReaderSettings Settings + { + get + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.CheckCharacters = false; + return settings; + } + } + + public override string Value + { + get + { + if (!IsInteractive) + { + return string.Empty; + } + XObject o = source as XObject; + if (o != null) + { + switch (o.NodeType) + { + case XmlNodeType.Attribute: + return ((XAttribute)o).Value; + case XmlNodeType.Text: + case XmlNodeType.CDATA: + return ((XText)o).Value; + case XmlNodeType.Comment: + return ((XComment)o).Value; + case XmlNodeType.ProcessingInstruction: + return ((XProcessingInstruction)o).Data; + case XmlNodeType.DocumentType: + return ((XDocumentType)o).InternalSubset; + default: + return string.Empty; + } + } + return (string)source; + } + } + + public override string XmlLang + { + get + { + if (!IsInteractive) + { + return string.Empty; + } + XElement e = GetElementInScope(); + if (e != null) + { + XName name = XNamespace.Xml.GetName("lang"); + do + { + XAttribute a = e.Attribute(name); + if (a != null) + { + return a.Value; + } + e = e.parent as XElement; + } while (e != null); + } + return string.Empty; + } + } + + public override XmlSpace XmlSpace + { + get + { + if (!IsInteractive) + { + return XmlSpace.None; + } + XElement e = GetElementInScope(); + if (e != null) + { + XName name = XNamespace.Xml.GetName("space"); + do + { + XAttribute a = e.Attribute(name); + if (a != null) + { + switch (a.Value.Trim(new char[] { ' ', '\t', '\n', '\r' })) + { + case "preserve": + return XmlSpace.Preserve; + case "default": + return XmlSpace.Default; + default: + break; + } + } + e = e.parent as XElement; + } while (e != null); + } + return XmlSpace.None; + } + } + + protected override void Dispose(bool disposing) + { + if (disposing && ReadState != ReadState.Closed) + { + Close(); + } + } + + private void Close() + { + source = null; + parent = null; + root = null; + state = ReadState.Closed; + } + + public override string GetAttribute(string name) + { + if (!IsInteractive) + { + return null; + } + XElement e = GetElementInAttributeScope(); + if (e != null) + { + string localName, namespaceName; + GetNameInAttributeScope(name, e, out localName, out namespaceName); + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (a.Name.LocalName == localName && a.Name.NamespaceName == namespaceName) + { + if (omitDuplicateNamespaces && IsDuplicateNamespaceAttribute(a)) + { + return null; + } + else + { + return a.Value; + } + } + } while (a != e.lastAttr); + } + return null; + } + XDocumentType n = source as XDocumentType; + if (n != null) + { + switch (name) + { + case "PUBLIC": + return n.PublicId; + case "SYSTEM": + return n.SystemId; + } + } + return null; + } + + public override string GetAttribute(string localName, string namespaceName) + { + if (!IsInteractive) + { + return null; + } + XElement e = GetElementInAttributeScope(); + if (e != null) + { + if (localName == "xmlns") + { + if (namespaceName != null && namespaceName.Length == 0) + { + return null; + } + if (namespaceName == XNamespace.xmlnsPrefixNamespace) + { + namespaceName = string.Empty; + } + } + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (a.Name.LocalName == localName && a.Name.NamespaceName == namespaceName) + { + if (omitDuplicateNamespaces && IsDuplicateNamespaceAttribute(a)) + { + return null; + } + else + { + return a.Value; + } + } + } while (a != e.lastAttr); + } + } + return null; + } + + public override string GetAttribute(int index) + { + if (!IsInteractive) + { + return null; + } + if (index < 0) + { + return null; + } + XElement e = GetElementInAttributeScope(); + if (e != null) + { + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (!omitDuplicateNamespaces || !IsDuplicateNamespaceAttribute(a)) + { + if (index-- == 0) + { + return a.Value; + } + } + } while (a != e.lastAttr); + } + } + return null; + } + + public override string LookupNamespace(string prefix) + { + if (!IsInteractive) + { + return null; + } + if (prefix == null) + { + return null; + } + XElement e = GetElementInScope(); + if (e != null) + { + XNamespace ns = prefix.Length == 0 ? e.GetDefaultNamespace() : e.GetNamespaceOfPrefix(prefix); + if (ns != null) + { + return nameTable.Add(ns.NamespaceName); + } + } + return null; + } + + public override bool MoveToAttribute(string name) + { + if (!IsInteractive) + { + return false; + } + XElement e = GetElementInAttributeScope(); + if (e != null) + { + string localName, namespaceName; + GetNameInAttributeScope(name, e, out localName, out namespaceName); + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (a.Name.LocalName == localName && + a.Name.NamespaceName == namespaceName) + { + if (omitDuplicateNamespaces && IsDuplicateNamespaceAttribute(a)) + { + // If it's a duplicate namespace attribute just act as if it doesn't exist + return false; + } + else + { + source = a; + parent = null; + return true; + } + } + } while (a != e.lastAttr); + } + } + return false; + } + + public override bool MoveToAttribute(string localName, string namespaceName) + { + if (!IsInteractive) + { + return false; + } + XElement e = GetElementInAttributeScope(); + if (e != null) + { + if (localName == "xmlns") + { + if (namespaceName != null && namespaceName.Length == 0) + { + return false; + } + if (namespaceName == XNamespace.xmlnsPrefixNamespace) + { + namespaceName = string.Empty; + } + } + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (a.Name.LocalName == localName && + a.Name.NamespaceName == namespaceName) + { + if (omitDuplicateNamespaces && IsDuplicateNamespaceAttribute(a)) + { + // If it's a duplicate namespace attribute just act as if it doesn't exist + return false; + } + else + { + source = a; + parent = null; + return true; + } + } + } while (a != e.lastAttr); + } + } + return false; + } + + public override void MoveToAttribute(int index) + { + if (!IsInteractive) + { + return; + } + if (index < 0) throw new ArgumentOutOfRangeException("index"); + XElement e = GetElementInAttributeScope(); + if (e != null) + { + XAttribute a = e.lastAttr; + if (a != null) + { + do + { + a = a.next; + if (!omitDuplicateNamespaces || !IsDuplicateNamespaceAttribute(a)) + { + // Only count those which are non-duplicates if we're asked to + if (index-- == 0) + { + source = a; + parent = null; + return; + } + } + } while (a != e.lastAttr); + } + } + throw new ArgumentOutOfRangeException("index"); + } + + public override bool MoveToElement() + { + if (!IsInteractive) + { + return false; + } + XAttribute a = source as XAttribute; + if (a == null) + { + a = parent as XAttribute; + } + if (a != null) + { + if (a.parent != null) + { + source = a.parent; + parent = null; + return true; + } + } + return false; + } + + public override bool MoveToFirstAttribute() + { + if (!IsInteractive) + { + return false; + } + XElement e = GetElementInAttributeScope(); + if (e != null) + { + if (e.lastAttr != null) + { + if (omitDuplicateNamespaces) + { + object na = GetFirstNonDuplicateNamespaceAttribute(e.lastAttr.next); + if (na == null) + { + return false; + } + source = na; + } + else + { + source = e.lastAttr.next; + } + return true; + } + } + return false; + } + + public override bool MoveToNextAttribute() + { + if (!IsInteractive) + { + return false; + } + XElement e = source as XElement; + if (e != null) + { + if (IsEndElement) + { + return false; + } + if (e.lastAttr != null) + { + if (omitDuplicateNamespaces) + { + // Skip duplicate namespace attributes + // We must NOT modify the this.source until we find the one we're looking for + // because if we don't find anything, we need to stay positioned where we're now + object na = GetFirstNonDuplicateNamespaceAttribute(e.lastAttr.next); + if (na == null) + { + return false; + } + source = na; + } + else + { + source = e.lastAttr.next; + } + return true; + } + return false; + } + XAttribute a = source as XAttribute; + if (a == null) + { + a = parent as XAttribute; + } + if (a != null) + { + if (a.parent != null && ((XElement)a.parent).lastAttr != a) + { + if (omitDuplicateNamespaces) + { + // Skip duplicate namespace attributes + // We must NOT modify the this.source until we find the one we're looking for + // because if we don't find anything, we need to stay positioned where we're now + object na = GetFirstNonDuplicateNamespaceAttribute(a.next); + if (na == null) + { + return false; + } + source = na; + } + else + { + source = a.next; + } + parent = null; + return true; + } + } + return false; + } + + public override bool Read() + { + switch (state) + { + case ReadState.Initial: + state = ReadState.Interactive; + XDocument d = source as XDocument; + if (d != null) + { + return ReadIntoDocument(d); + } + return true; + case ReadState.Interactive: + return Read(false); + default: + return false; + } + } + + public override bool ReadAttributeValue() + { + if (!IsInteractive) + { + return false; + } + XAttribute a = source as XAttribute; + if (a != null) + { + return ReadIntoAttribute(a); + } + return false; + } + + public override bool ReadToDescendant(string localName, string namespaceName) + { + if (!IsInteractive) + { + return false; + } + MoveToElement(); + XElement c = source as XElement; + if (c != null && !c.IsEmpty) + { + if (IsEndElement) + { + return false; + } + foreach (XElement e in c.Descendants()) + { + if (e.Name.LocalName == localName && + e.Name.NamespaceName == namespaceName) + { + source = e; + return true; + } + } + IsEndElement = true; + } + return false; + } + + public override bool ReadToFollowing(string localName, string namespaceName) + { + while (Read()) + { + XElement e = source as XElement; + if (e != null) + { + if (IsEndElement) continue; + if (e.Name.LocalName == localName && e.Name.NamespaceName == namespaceName) + { + return true; + } + } + } + return false; + } + + public override bool ReadToNextSibling(string localName, string namespaceName) + { + if (!IsInteractive) + { + return false; + } + MoveToElement(); + if (source != root) + { + XNode n = source as XNode; + if (n != null) + { + foreach (XElement e in n.ElementsAfterSelf()) + { + if (e.Name.LocalName == localName && + e.Name.NamespaceName == namespaceName) + { + source = e; + IsEndElement = false; + return true; + } + } + if (n.parent is XElement) + { + source = n.parent; + IsEndElement = true; + return false; + } + } + else + { + if (parent is XElement) + { + source = parent; + parent = null; + IsEndElement = true; + return false; + } + } + } + return ReadToEnd(); + } + + public override void ResolveEntity() + { + } + + public override void Skip() + { + if (!IsInteractive) + { + return; + } + Read(true); + } + + bool IXmlLineInfo.HasLineInfo() + { + if (IsEndElement) + { + // Special case for EndElement - we store the line info differently in this case + // we also know that the current node (source) is XElement + XElement e = source as XElement; + if (e != null) + { + return e.Annotation() != null; + } + } + else + { + IXmlLineInfo li = source as IXmlLineInfo; + if (li != null) + { + return li.HasLineInfo(); + } + } + return false; + } + + int IXmlLineInfo.LineNumber + { + get + { + if (IsEndElement) + { + // Special case for EndElement - we store the line info differently in this case + // we also know that the current node (source) is XElement + XElement e = source as XElement; + if (e != null) + { + LineInfoEndElementAnnotation a = e.Annotation(); + if (a != null) + { + return a.lineNumber; + } + } + } + else + { + IXmlLineInfo li = source as IXmlLineInfo; + if (li != null) + { + return li.LineNumber; + } + } + return 0; + } + } + + int IXmlLineInfo.LinePosition + { + get + { + if (IsEndElement) + { + // Special case for EndElement - we store the line info differently in this case + // we also know that the current node (source) is XElement + XElement e = source as XElement; + if (e != null) + { + LineInfoEndElementAnnotation a = e.Annotation(); + if (a != null) + { + return a.linePosition; + } + } + } + else + { + IXmlLineInfo li = source as IXmlLineInfo; + if (li != null) + { + return li.LinePosition; + } + } + return 0; + } + } + + bool IsEndElement + { + get { return parent == source; } + set { parent = value ? source : null; } + } + + bool IsInteractive + { + get { return state == ReadState.Interactive; } + } + + static XmlNameTable CreateNameTable() + { + XmlNameTable nameTable = new NameTable(); + nameTable.Add(string.Empty); + nameTable.Add(XNamespace.xmlnsPrefixNamespace); + nameTable.Add(XNamespace.xmlPrefixNamespace); + return nameTable; + } + + XElement GetElementInAttributeScope() + { + XElement e = source as XElement; + if (e != null) + { + if (IsEndElement) + { + return null; + } + return e; + } + XAttribute a = source as XAttribute; + if (a != null) + { + return (XElement)a.parent; + } + a = parent as XAttribute; + if (a != null) + { + return (XElement)a.parent; + } + return null; + } + + XElement GetElementInScope() + { + XElement e = source as XElement; + if (e != null) + { + return e; + } + XNode n = source as XNode; + if (n != null) + { + return n.parent as XElement; + } + XAttribute a = source as XAttribute; + if (a != null) + { + return (XElement)a.parent; + } + e = parent as XElement; + if (e != null) + { + return e; + } + a = parent as XAttribute; + if (a != null) + { + return (XElement)a.parent; + } + return null; + } + + static void GetNameInAttributeScope(string qualifiedName, XElement e, out string localName, out string namespaceName) + { + if (qualifiedName != null && qualifiedName.Length != 0) + { + int i = qualifiedName.IndexOf(':'); + if (i != 0 && i != qualifiedName.Length - 1) + { + if (i == -1) + { + localName = qualifiedName; + namespaceName = string.Empty; + return; + } + XNamespace ns = e.GetNamespaceOfPrefix(qualifiedName.Substring(0, i)); + if (ns != null) + { + localName = qualifiedName.Substring(i + 1, qualifiedName.Length - i - 1); + namespaceName = ns.NamespaceName; + return; + } + } + } + localName = null; + namespaceName = null; + } + + bool Read(bool skipContent) + { + XElement e = source as XElement; + if (e != null) + { + if (e.IsEmpty || IsEndElement || skipContent) + { + return ReadOverNode(e); + } + return ReadIntoElement(e); + } + XNode n = source as XNode; + if (n != null) + { + return ReadOverNode(n); + } + XAttribute a = source as XAttribute; + if (a != null) + { + return ReadOverAttribute(a, skipContent); + } + return ReadOverText(skipContent); + } + + bool ReadIntoDocument(XDocument d) + { + XNode n = d.content as XNode; + if (n != null) + { + source = n.next; + return true; + } + string s = d.content as string; + if (s != null) + { + if (s.Length > 0) + { + source = s; + parent = d; + return true; + } + } + return ReadToEnd(); + } + + bool ReadIntoElement(XElement e) + { + XNode n = e.content as XNode; + if (n != null) + { + source = n.next; + return true; + } + string s = e.content as string; + if (s != null) + { + if (s.Length > 0) + { + source = s; + parent = e; + } + else + { + source = e; + IsEndElement = true; + } + return true; + } + return ReadToEnd(); + } + + bool ReadIntoAttribute(XAttribute a) + { + source = a.value; + parent = a; + return true; + } + + bool ReadOverAttribute(XAttribute a, bool skipContent) + { + XElement e = (XElement)a.parent; + if (e != null) + { + if (e.IsEmpty || skipContent) + { + return ReadOverNode(e); + } + return ReadIntoElement(e); + } + return ReadToEnd(); + } + + bool ReadOverNode(XNode n) + { + if (n == root) + { + return ReadToEnd(); + } + XNode next = n.next; + if (null == next || next == n || n == n.parent.content) + { + if (n.parent == null || (n.parent.parent == null && n.parent is XDocument)) + { + return ReadToEnd(); + } + source = n.parent; + IsEndElement = true; + } + else + { + source = next; + IsEndElement = false; + } + return true; + } + + bool ReadOverText(bool skipContent) + { + if (parent is XElement) + { + source = parent; + parent = null; + IsEndElement = true; + return true; + } + if (parent is XAttribute) + { + XAttribute a = (XAttribute)parent; + parent = null; + return ReadOverAttribute(a, skipContent); + } + return ReadToEnd(); + } + + bool ReadToEnd() + { + state = ReadState.EndOfFile; + return false; + } + + /// + /// Determines if the specified attribute would be a duplicate namespace declaration + /// - one which we already reported on some ancestor, so it's not necessary to report it here + /// + /// The attribute to test + /// true if the attribute is a duplicate namespace declaration attribute + bool IsDuplicateNamespaceAttribute(XAttribute candidateAttribute) + { + if (!candidateAttribute.IsNamespaceDeclaration) + { + return false; + } + else + { + // Split the method in two to enable inlining of this piece (Which will work for 95% of cases) + return IsDuplicateNamespaceAttributeInner(candidateAttribute); + } + } + + bool IsDuplicateNamespaceAttributeInner(XAttribute candidateAttribute) + { + // First of all - if this is an xmlns:xml declaration then it's a duplicate + // since xml prefix can't be redeclared and it's declared by default always. + if (candidateAttribute.Name.LocalName == "xml") + { + return true; + } + // The algorithm we use is: + // Go up the tree (but don't go higher than the root of this reader) + // and find the closest namespace declaration attribute which declares the same prefix + // If it declares that prefix to the exact same URI as ours does then ours is a duplicate + // Note that if we find a namespace declaration for the same prefix but with a different URI, then we don't have a dupe! + XElement element = candidateAttribute.parent as XElement; + if (element == root || element == null) + { + // If there's only the parent element of our attribute, there can be no duplicates + return false; + } + element = element.parent as XElement; + while (element != null) + { + // Search all attributes of this element for the same prefix declaration + // Trick - a declaration for the same prefix will have the exact same XName - so we can do a quick ref comparison of names + // (The default ns decl is represented by an XName "xmlns{}", even if you try to create + // an attribute with XName "xmlns{http://www.w3.org/2000/xmlns/}" it will fail, + // because it's treated as a declaration of prefix "xmlns" which is invalid) + XAttribute a = element.lastAttr; + if (a != null) + { + do + { + if (a.name == candidateAttribute.name) + { + // Found the same prefix decl + if (a.Value == candidateAttribute.Value) + { + // And it's for the same namespace URI as well - so ours is a duplicate + return true; + } + else + { + // It's not for the same namespace URI - which means we have to keep ours + // (no need to continue the search as this one overrides anything above it) + return false; + } + } + a = a.next; + } while (a != element.lastAttr); + } + if (element == root) + { + return false; + } + element = element.parent as XElement; + } + return false; + } + + /// + /// Finds a first attribute (starting with the parameter) which is not a duplicate namespace attribute + /// + /// The attribute to start with + /// The first attribute which is not a namespace attribute or null if the end of attributes has bean reached + XAttribute GetFirstNonDuplicateNamespaceAttribute(XAttribute candidate) + { + Debug.Assert(omitDuplicateNamespaces, "This method should only be caled if we're omitting duplicate namespace attribute." + + "For perf reason it's better to test this flag in the caller method."); + if (!IsDuplicateNamespaceAttribute(candidate)) + { + return candidate; + } + + XElement e = candidate.parent as XElement; + if (e != null && candidate != e.lastAttr) + { + do + { + candidate = candidate.next; + if (!IsDuplicateNamespaceAttribute(candidate)) + { + return candidate; + } + } while (candidate != e.lastAttr); + } + return null; + } + } +} \ No newline at end of file diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XObject.cs b/src/System.Xml.XDocument/System/Xml/Linq/XObject.cs new file mode 100644 index 000000000000..b577ed75bbdc --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XObject.cs @@ -0,0 +1,525 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents a node or an attribute in an XML tree. + /// + public abstract class XObject : IXmlLineInfo + { + internal XContainer parent; + internal object annotations; + + internal XObject() { } + + /// + /// Get the BaseUri for this . + /// + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Back-compat with System.Xml.")] + public string BaseUri + { + get + { + XObject o = this; + while (true) + { + while (o != null && o.annotations == null) + { + o = o.parent; + } + if (o == null) break; + BaseUriAnnotation a = o.Annotation(); + if (a != null) return a.baseUri; + o = o.parent; + } + return string.Empty; + } + } + + /// + /// Gets the XDocument object for this . + /// + public XDocument Document + { + get + { + XObject n = this; + while (n.parent != null) n = n.parent; + return n as XDocument; + } + } + + /// + /// Gets the node type for this . + /// + public abstract XmlNodeType NodeType { get; } + + /// + /// Gets the parent of this . + /// + /// + /// If this has no parent , this property returns null. + /// + public XElement Parent + { + get { return parent as XElement; } + } + + /// + /// Adds an object to the annotation list of this . + /// + /// The annotation to add. + public void AddAnnotation(object annotation) + { + if (annotation == null) throw new ArgumentNullException("annotation"); + if (annotations == null) + { + annotations = annotation is object[] ? new object[] { annotation } : annotation; + } + else + { + object[] a = annotations as object[]; + if (a == null) + { + annotations = new object[] { annotations, annotation }; + } + else + { + int i = 0; + while (i < a.Length && a[i] != null) i++; + if (i == a.Length) + { + Array.Resize(ref a, i * 2); + annotations = a; + } + a[i] = annotation; + } + } + } + + /// + /// Returns the first annotation object of the specified type from the list of annotations + /// of this . + /// + /// The type of the annotation to retrieve. + /// + /// The first matching annotation object, or null + /// if no annotation is the specified type. + /// + public object Annotation(Type type) + { + if (type == null) throw new ArgumentNullException("type"); + if (annotations != null) + { + object[] a = annotations as object[]; + if (a == null) + { + if (XHelper.IsInstanceOfType(annotations, type)) return annotations; + } + else + { + for (int i = 0; i < a.Length; i++) + { + object obj = a[i]; + if (obj == null) break; + if (XHelper.IsInstanceOfType(obj, type)) return obj; + } + } + } + return null; + } + + private object AnnotationForSealedType(Type type) + { + Debug.Assert(type != null); + + if (annotations != null) + { + object[] a = annotations as object[]; + if (a == null) + { + if (annotations.GetType() == type) return annotations; + } + else + { + for (int i = 0; i < a.Length; i++) + { + object obj = a[i]; + if (obj == null) break; + if (obj.GetType() == type) return obj; + } + } + } + return null; + } + + /// + /// Returns the first annotation object of the specified type from the list of annotations + /// of this . + /// + /// The type of the annotation to retrieve. + /// + /// The first matching annotation object, or null if no annotation + /// is the specified type. + /// + public T Annotation() where T : class + { + if (annotations != null) + { + object[] a = annotations as object[]; + if (a == null) return annotations as T; + for (int i = 0; i < a.Length; i++) + { + object obj = a[i]; + if (obj == null) break; + T result = obj as T; + if (result != null) return result; + } + } + return null; + } + + /// + /// Returns an enumerable collection of annotations of the specified type + /// for this . + /// + /// The type of the annotations to retrieve. + /// An enumerable collection of annotations for this XObject. + public IEnumerable Annotations(Type type) + { + if (type == null) throw new ArgumentNullException("type"); + return AnnotationsIterator(type); + } + + IEnumerable AnnotationsIterator(Type type) + { + if (annotations != null) + { + object[] a = annotations as object[]; + if (a == null) + { + if (XHelper.IsInstanceOfType(annotations, type)) yield return annotations; + } + else + { + for (int i = 0; i < a.Length; i++) + { + object obj = a[i]; + if (obj == null) break; + if (XHelper.IsInstanceOfType(obj, type)) yield return obj; + } + } + } + } + + /// + /// Returns an enumerable collection of annotations of the specified type + /// for this . + /// + /// The type of the annotations to retrieve. + /// An enumerable collection of annotations for this XObject. + public IEnumerable Annotations() where T : class + { + if (annotations != null) + { + object[] a = annotations as object[]; + if (a == null) + { + T result = annotations as T; + if (result != null) yield return result; + } + else + { + for (int i = 0; i < a.Length; i++) + { + object obj = a[i]; + if (obj == null) break; + T result = obj as T; + if (result != null) yield return result; + } + } + } + } + + /// + /// Removes the annotations of the specified type from this . + /// + /// The type of annotations to remove. + public void RemoveAnnotations(Type type) + { + if (type == null) throw new ArgumentNullException("type"); + if (annotations != null) + { + object[] a = annotations as object[]; + if (a == null) + { + if (XHelper.IsInstanceOfType(annotations, type)) annotations = null; + } + else + { + int i = 0, j = 0; + while (i < a.Length) + { + object obj = a[i]; + if (obj == null) break; + if (!XHelper.IsInstanceOfType(obj, type)) a[j++] = obj; + i++; + } + if (j == 0) + { + annotations = null; + } + else + { + while (j < i) a[j++] = null; + } + } + } + } + + /// + /// Removes the annotations of the specified type from this . + /// + /// The type of annotations to remove. + public void RemoveAnnotations() where T : class + { + if (annotations != null) + { + object[] a = annotations as object[]; + if (a == null) + { + if (annotations is T) annotations = null; + } + else + { + int i = 0, j = 0; + while (i < a.Length) + { + object obj = a[i]; + if (obj == null) break; + if (!(obj is T)) a[j++] = obj; + i++; + } + if (j == 0) + { + annotations = null; + } + else + { + while (j < i) a[j++] = null; + } + } + } + } + + /// + /// Occurs when this or any of its descendants have changed. + /// + public event EventHandler Changed + { + add + { + if (value == null) return; + XObjectChangeAnnotation a = Annotation(); + if (a == null) + { + a = new XObjectChangeAnnotation(); + AddAnnotation(a); + } + a.changed += value; + } + remove + { + if (value == null) return; + XObjectChangeAnnotation a = Annotation(); + if (a == null) return; + a.changed -= value; + if (a.changing == null && a.changed == null) + { + RemoveAnnotations(); + } + } + } + + /// + /// Occurs when this or any of its descendants are about to change. + /// + public event EventHandler Changing + { + add + { + if (value == null) return; + XObjectChangeAnnotation a = Annotation(); + if (a == null) + { + a = new XObjectChangeAnnotation(); + AddAnnotation(a); + } + a.changing += value; + } + remove + { + if (value == null) return; + XObjectChangeAnnotation a = Annotation(); + if (a == null) return; + a.changing -= value; + if (a.changing == null && a.changed == null) + { + RemoveAnnotations(); + } + } + } + + bool IXmlLineInfo.HasLineInfo() + { + return Annotation() != null; + } + + int IXmlLineInfo.LineNumber + { + get + { + LineInfoAnnotation a = Annotation(); + if (a != null) return a.lineNumber; + return 0; + } + } + + int IXmlLineInfo.LinePosition + { + get + { + LineInfoAnnotation a = Annotation(); + if (a != null) return a.linePosition; + return 0; + } + } + + internal bool HasBaseUri + { + get + { + return Annotation() != null; + } + } + + internal bool NotifyChanged(object sender, XObjectChangeEventArgs e) + { + bool notify = false; + XObject o = this; + while (true) + { + while (o != null && o.annotations == null) + { + o = o.parent; + } + if (o == null) break; + XObjectChangeAnnotation a = o.Annotation(); + if (a != null) + { + notify = true; + if (a.changed != null) + { + a.changed(sender, e); + } + } + o = o.parent; + } + return notify; + } + + internal bool NotifyChanging(object sender, XObjectChangeEventArgs e) + { + bool notify = false; + XObject o = this; + while (true) + { + while (o != null && o.annotations == null) + { + o = o.parent; + } + if (o == null) break; + XObjectChangeAnnotation a = o.Annotation(); + if (a != null) + { + notify = true; + if (a.changing != null) + { + a.changing(sender, e); + } + } + o = o.parent; + } + return notify; + } + + internal void SetBaseUri(string baseUri) + { + AddAnnotation(new BaseUriAnnotation(baseUri)); + } + + internal void SetLineInfo(int lineNumber, int linePosition) + { + AddAnnotation(new LineInfoAnnotation(lineNumber, linePosition)); + } + + internal bool SkipNotify() + { + XObject o = this; + while (true) + { + while (o != null && o.annotations == null) + { + o = o.parent; + } + if (o == null) return true; + if (o.Annotations() != null) return false; + o = o.parent; + } + } + + /// + /// Walks the tree starting with "this" node and returns first annotation of type + /// found in the ancestors. + /// + /// The effective for this + internal SaveOptions GetSaveOptionsFromAnnotations() + { + XObject o = this; + while (true) + { + while (o != null && o.annotations == null) + { + o = o.parent; + } + if (o == null) + { + return SaveOptions.None; + } + object saveOptions = o.AnnotationForSealedType(typeof(SaveOptions)); + if (saveOptions != null) + { + return (SaveOptions)saveOptions; + } + o = o.parent; + } + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XObjectChangeAnnotation.cs b/src/System.Xml.XDocument/System/Xml/Linq/XObjectChangeAnnotation.cs new file mode 100644 index 000000000000..09d6fb96b099 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XObjectChangeAnnotation.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + class XObjectChangeAnnotation + { + internal EventHandler changing; + internal EventHandler changed; + + public XObjectChangeAnnotation() + { + } + } +} \ No newline at end of file diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XObjectChangeEventArgs.cs b/src/System.Xml.XDocument/System/Xml/Linq/XObjectChangeEventArgs.cs new file mode 100644 index 000000000000..70e355503bab --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XObjectChangeEventArgs.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Provides data for the and events. + /// + public class XObjectChangeEventArgs : EventArgs + { + XObjectChange objectChange; + + /// + /// Event argument for a change event. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "XObjectChangeEventArgs is in fact immutable.")] + public static readonly XObjectChangeEventArgs Add = new XObjectChangeEventArgs(XObjectChange.Add); + + /// + /// Event argument for a change event. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "XObjectChangeEventArgs is in fact immutable.")] + public static readonly XObjectChangeEventArgs Remove = new XObjectChangeEventArgs(XObjectChange.Remove); + + /// + /// Event argument for a change event. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "XObjectChangeEventArgs is in fact immutable.")] + public static readonly XObjectChangeEventArgs Name = new XObjectChangeEventArgs(XObjectChange.Name); + + /// + /// Event argument for a change event. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "XObjectChangeEventArgs is in fact immutable.")] + public static readonly XObjectChangeEventArgs Value = new XObjectChangeEventArgs(XObjectChange.Value); + + /// + /// Initializes a new instance of the class. + /// + public XObjectChangeEventArgs(XObjectChange objectChange) + { + this.objectChange = objectChange; + } + + /// + /// Gets the type () of change. + /// + public XObjectChange ObjectChange + { + get { return objectChange; } + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XProcessingInstruction.cs b/src/System.Xml.XDocument/System/Xml/Linq/XProcessingInstruction.cs new file mode 100644 index 000000000000..d59ba1a0b30f --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XProcessingInstruction.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents an XML processing instruction. + /// + public class XProcessingInstruction : XNode + { + internal string target; + internal string data; + + /// + /// Initializes a new XML Processing Instruction from the specified target and string data. + /// + /// + /// The target application for this . + /// + /// + /// The string data that comprises the . + /// + /// + /// Thrown if either the target or data parameter are null. + /// + public XProcessingInstruction(string target, string data) + { + if (data == null) throw new ArgumentNullException("data"); + ValidateName(target); + this.target = target; + this.data = data; + } + + /// + /// Initializes a new XML processing instruction by copying its target and data + /// from another XML processing instruction. + /// + /// XML processing instruction to copy from. + public XProcessingInstruction(XProcessingInstruction other) + { + if (other == null) throw new ArgumentNullException("other"); + this.target = other.target; + this.data = other.data; + } + + internal XProcessingInstruction(XmlReader r) + { + target = r.Name; + data = r.Value; + r.Read(); + } + + /// + /// Gets or sets the string value of this processing instruction. + /// + /// + /// Thrown if the value set is null. + /// + public string Data + { + get + { + return data; + } + set + { + if (value == null) throw new ArgumentNullException("value"); + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); + data = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); + } + } + + /// + /// Gets the node type for this node. + /// + /// + /// This property will always return XmlNodeType.ProcessingInstruction. + /// + public override XmlNodeType NodeType + { + get + { + return XmlNodeType.ProcessingInstruction; + } + } + + /// + /// Gets or sets a string representing the target application for this processing instruction. + /// + /// + /// Thrown if the value set is null. + /// + public string Target + { + get + { + return target; + } + set + { + ValidateName(value); + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Name); + target = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Name); + } + } + + /// + /// Writes this to the passed in . + /// + /// + /// The to write this to. + /// + public override void WriteTo(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + writer.WriteProcessingInstruction(target, data); + } + + internal override XNode CloneNode() + { + return new XProcessingInstruction(this); + } + + internal override bool DeepEquals(XNode node) + { + XProcessingInstruction other = node as XProcessingInstruction; + return other != null && target == other.target && data == other.data; + } + + internal override int GetDeepHashCode() + { + return target.GetHashCode() ^ data.GetHashCode(); + } + + static void ValidateName(string name) + { + XmlConvert.VerifyNCName(name); + if (string.Compare(name, "xml", StringComparison.OrdinalIgnoreCase) == 0) throw new ArgumentException(SR.Format(SR.Argument_InvalidPIName, name)); + } + } +} diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XStreamingElement.cs b/src/System.Xml.XDocument/System/Xml/Linq/XStreamingElement.cs new file mode 100644 index 000000000000..14e7ca0d7ad1 --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XStreamingElement.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents a class that allows elements to be streamed + /// on input and output. + /// + public class XStreamingElement + { + internal XName name; + internal object content; + + /// + /// Creates a node with a given name + /// + /// The name to assign to the new node + public XStreamingElement(XName name) + { + if (name == null) throw new ArgumentNullException("name"); + this.name = name; + } + + /// + /// Creates a node with a given name and content + /// + /// The name to assign to the new node + /// The content to assign to the new node + public XStreamingElement(XName name, object content) + : this(name) + { + this.content = content is List ? new object[] { content } : content; + } + + /// + /// Creates a node with a given name and content + /// + /// The name to assign to the new node + /// An array containing content to assign to the new node + public XStreamingElement(XName name, params object[] content) + : this(name) + { + this.content = content; + } + + /// + /// Gets or sets the name of this streaming element. + /// + public XName Name + { + get + { + return name; + } + set + { + if (value == null) throw new ArgumentNullException("value"); + name = value; + } + } + + /// + /// Add content to an + /// + /// Object containg content to add + public void Add(object content) + { + if (content != null) + { + List list = this.content as List; + if (list == null) + { + list = new List(); + if (this.content != null) list.Add(this.content); + this.content = list; + } + list.Add(content); + } + } + + /// + /// Add content to an + /// + /// array of objects containg content to add + public void Add(params object[] content) + { + Add((object)content); + } + + /// + /// Save the contents of an to a + /// with formatting. + /// + /// to write to + public void Save(Stream stream) + { + Save(stream, SaveOptions.None); + } + + /// + /// Save the contents of an to a , + /// optionally formatting. + /// + /// to write to + /// + /// If SaveOptions.DisableFormatting is enabled the output is not indented. + /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. + /// + public void Save(Stream stream, SaveOptions options) + { + XmlWriterSettings ws = XNode.GetXmlWriterSettings(options); + using (XmlWriter w = XmlWriter.Create(stream, ws)) + { + Save(w); + } + } + + /// + /// Save the contents of an to a text writer + /// with formatting. + /// + /// to write to + public void Save(TextWriter textWriter) + { + Save(textWriter, SaveOptions.None); + } + + /// + /// Save the contents of an to a text writer + /// optionally formatting. + /// + /// to write to + /// + /// If SaveOptions.DisableFormatting is enabled the output is not indented. + /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. + /// + public void Save(TextWriter textWriter, SaveOptions options) + { + XmlWriterSettings ws = XNode.GetXmlWriterSettings(options); + using (XmlWriter w = XmlWriter.Create(textWriter, ws)) + { + Save(w); + } + } + + /// + /// Save the contents of an to an XML writer, not preserving whitepace + /// + /// to write to + public void Save(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + writer.WriteStartDocument(); + WriteTo(writer); + writer.WriteEndDocument(); + } + + /// + /// Get the XML content of an as a + /// formatted string. + /// + /// The XML text as a formatted string + public override string ToString() + { + return GetXmlString(SaveOptions.None); + } + + /// + /// Gets the XML content of this streaming element as a string. + /// + /// + /// If SaveOptions.DisableFormatting is enabled the content is not indented. + /// If SaveOptions.OmitDuplicateNamespaces is enabled duplicate namespace declarations will be removed. + /// + /// An XML string + public string ToString(SaveOptions options) + { + return GetXmlString(options); + } + + /// + /// Write this to an + /// + /// + public void WriteTo(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + new StreamingElementWriter(writer).WriteStreamingElement(this); + } + + string GetXmlString(SaveOptions o) + { + using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture)) + { + XmlWriterSettings ws = new XmlWriterSettings(); + ws.OmitXmlDeclaration = true; + if ((o & SaveOptions.DisableFormatting) == 0) ws.Indent = true; + if ((o & SaveOptions.OmitDuplicateNamespaces) != 0) ws.NamespaceHandling |= NamespaceHandling.OmitDuplicates; + using (XmlWriter w = XmlWriter.Create(sw, ws)) + { + WriteTo(w); + } + return sw.ToString(); + } + } + } +} \ No newline at end of file diff --git a/src/System.Xml.XDocument/System/Xml/Linq/XText.cs b/src/System.Xml.XDocument/System/Xml/Linq/XText.cs new file mode 100644 index 000000000000..4c806d0447bc --- /dev/null +++ b/src/System.Xml.XDocument/System/Xml/Linq/XText.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using CultureInfo = System.Globalization.CultureInfo; +using Debug = System.Diagnostics.Debug; +using IEnumerable = System.Collections.IEnumerable; +using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; +using Enumerable = System.Linq.Enumerable; +using IComparer = System.Collections.IComparer; +using IEqualityComparer = System.Collections.IEqualityComparer; +using StringBuilder = System.Text.StringBuilder; +using Encoding = System.Text.Encoding; +using Interlocked = System.Threading.Interlocked; +using System.Reflection; + +namespace System.Xml.Linq +{ + /// + /// Represents a text node. + /// + public class XText : XNode + { + internal string text; + + /// + /// Initializes a new instance of the XText class. + /// + /// The string that contains the value of the text node. + public XText(string value) + { + if (value == null) throw new ArgumentNullException("value"); + text = value; + } + + /// + /// Initializes a new instance of the XText class from another XText object. + /// + /// The text node to copy from. + public XText(XText other) + { + if (other == null) throw new ArgumentNullException("other"); + text = other.text; + } + + internal XText(XmlReader r) + { + text = r.Value; + r.Read(); + } + + /// + /// Gets the node type for this node. + /// + /// + /// This property will always return XmlNodeType.Text. + /// + public override XmlNodeType NodeType + { + get + { + return XmlNodeType.Text; + } + } + + /// + /// Gets or sets the value of this node. + /// + public string Value + { + get + { + return text; + } + set + { + if (value == null) throw new ArgumentNullException("value"); + bool notify = NotifyChanging(this, XObjectChangeEventArgs.Value); + text = value; + if (notify) NotifyChanged(this, XObjectChangeEventArgs.Value); + } + } + + /// + /// Write this to the given . + /// + /// + /// The to write this to. + /// + public override void WriteTo(XmlWriter writer) + { + if (writer == null) throw new ArgumentNullException("writer"); + if (parent is XDocument) + { + writer.WriteWhitespace(text); + } + else + { + writer.WriteString(text); + } + } + + internal override void AppendText(StringBuilder sb) + { + sb.Append(text); + } + + internal override XNode CloneNode() + { + return new XText(this); + } + + internal override bool DeepEquals(XNode node) + { + return node != null && NodeType == node.NodeType && text == ((XText)node).text; + } + + internal override int GetDeepHashCode() + { + return text.GetHashCode(); + } + } +}