File: System\Xml\Linq\XObject.cs
Web Access
Project: src\src\libraries\System.Private.Xml.Linq\src\System.Private.Xml.Linq.csproj (System.Private.Xml.Linq)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
 
using Debug = System.Diagnostics.Debug;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
 
namespace System.Xml.Linq
{
    /// <summary>
    /// Represents a node or an attribute in an XML tree.
    /// </summary>
    public abstract class XObject : IXmlLineInfo
    {
        internal XContainer? parent;
        internal object? annotations;
 
        internal XObject() { }
 
        /// <summary>
        /// Get the BaseUri for this <see cref="XObject"/>.
        /// </summary>
        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<BaseUriAnnotation>();
                    if (a != null) return a.baseUri;
                    o = o.parent;
                }
                return string.Empty;
            }
        }
 
        /// <summary>
        /// Gets the XDocument object for this <see cref="XObject"/>.
        /// </summary>
        public XDocument? Document
        {
            get
            {
                XObject n = this;
                while (n.parent != null) n = n.parent;
 
                XDocument? doc = n as XDocument;
 
                return doc;
            }
        }
 
        /// <summary>
        /// Gets the node type for this <see cref="XObject"/>.
        /// </summary>
        public abstract XmlNodeType NodeType { get; }
 
        /// <summary>
        /// Gets the parent <see cref="XElement"/> of this <see cref="XObject"/>.
        /// </summary>
        /// <remarks>
        /// If this <see cref="XObject"/> has no parent <see cref="XElement"/>, this property returns null.
        /// </remarks>
        public XElement? Parent
        {
            get { return parent as XElement; }
        }
 
        /// <summary>
        /// Adds an object to the annotation list of this <see cref="XObject"/>.
        /// </summary>
        /// <param name="annotation">The annotation to add.</param>
        public void AddAnnotation(object annotation)
        {
            ArgumentNullException.ThrowIfNull(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;
                }
            }
        }
 
        /// <summary>
        /// Returns the first annotation object of the specified type from the list of annotations
        /// of this <see cref="XObject"/>.
        /// </summary>
        /// <param name="type">The type of the annotation to retrieve.</param>
        /// <returns>
        /// The first matching annotation object, or null
        /// if no annotation is the specified type.
        /// </returns>
        public object? Annotation(Type type)
        {
            ArgumentNullException.ThrowIfNull(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;
        }
 
        /// <summary>
        /// Returns the first annotation object of the specified type from the list of annotations
        /// of this <see cref="XObject"/>.
        /// </summary>
        /// <typeparam name="T">The type of the annotation to retrieve.</typeparam>
        /// <returns>
        /// The first matching annotation object, or null if no annotation
        /// is the specified type.
        /// </returns>
        public T? Annotation<T>() 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;
        }
 
        /// <summary>
        /// Returns an enumerable collection of annotations of the specified type
        /// for this <see cref="XObject"/>.
        /// </summary>
        /// <param name="type">The type of the annotations to retrieve.</param>
        /// <returns>An enumerable collection of annotations for this XObject.</returns>
        public IEnumerable<object> Annotations(Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            return AnnotationsIterator(type);
        }
 
        private IEnumerable<object> 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;
                    }
                }
            }
        }
 
        /// <summary>
        /// Returns an enumerable collection of annotations of the specified type
        /// for this <see cref="XObject"/>.
        /// </summary>
        /// <typeparam name="T">The type of the annotations to retrieve.</typeparam>
        /// <returns>An enumerable collection of annotations for this XObject.</returns>
        public IEnumerable<T> Annotations<T>() 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;
                    }
                }
            }
        }
 
        /// <summary>
        /// Removes the annotations of the specified type from this <see cref="XObject"/>.
        /// </summary>
        /// <param name="type">The type of annotations to remove.</param>
        public void RemoveAnnotations(Type type)
        {
            ArgumentNullException.ThrowIfNull(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;
                    }
                }
            }
        }
 
        /// <summary>
        /// Removes the annotations of the specified type from this <see cref="XObject"/>.
        /// </summary>
        /// <typeparam name="T">The type of annotations to remove.</typeparam>
        public void RemoveAnnotations<T>() 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;
                    }
                }
            }
        }
 
        /// <summary>
        /// Occurs when this <see cref="XObject"/> or any of its descendants have changed.
        /// </summary>
        public event EventHandler<XObjectChangeEventArgs> Changed
        {
            add
            {
                if (value == null) return;
                XObjectChangeAnnotation? a = Annotation<XObjectChangeAnnotation>();
                if (a == null)
                {
                    a = new XObjectChangeAnnotation();
                    AddAnnotation(a);
                }
                a.changed += value;
            }
            remove
            {
                if (value == null) return;
                XObjectChangeAnnotation? a = Annotation<XObjectChangeAnnotation>();
                if (a == null) return;
                a.changed -= value;
                if (a.changing == null && a.changed == null)
                {
                    RemoveAnnotations<XObjectChangeAnnotation>();
                }
            }
        }
 
        /// <summary>
        /// Occurs when this <see cref="XObject"/> or any of its descendants are about to change.
        /// </summary>
        public event EventHandler<XObjectChangeEventArgs> Changing
        {
            add
            {
                if (value == null) return;
                XObjectChangeAnnotation? a = Annotation<XObjectChangeAnnotation>();
                if (a == null)
                {
                    a = new XObjectChangeAnnotation();
                    AddAnnotation(a);
                }
                a.changing += value;
            }
            remove
            {
                if (value == null) return;
                XObjectChangeAnnotation? a = Annotation<XObjectChangeAnnotation>();
                if (a == null) return;
                a.changing -= value;
                if (a.changing == null && a.changed == null)
                {
                    RemoveAnnotations<XObjectChangeAnnotation>();
                }
            }
        }
 
        bool IXmlLineInfo.HasLineInfo()
        {
            return Annotation<LineInfoAnnotation>() != null;
        }
 
        int IXmlLineInfo.LineNumber
        {
            get
            {
                LineInfoAnnotation? a = Annotation<LineInfoAnnotation>();
                if (a != null) return a.lineNumber;
                return 0;
            }
        }
 
        int IXmlLineInfo.LinePosition
        {
            get
            {
                LineInfoAnnotation? a = Annotation<LineInfoAnnotation>();
                if (a != null) return a.linePosition;
                return 0;
            }
        }
 
        internal bool HasBaseUri
        {
            get
            {
                return Annotation<BaseUriAnnotation>() != 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<XObjectChangeAnnotation>();
                if (a != null)
                {
                    notify = true;
                    a.changed?.Invoke(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<XObjectChangeAnnotation>();
                if (a != null)
                {
                    notify = true;
                    a.changing?.Invoke(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.Annotation<XObjectChangeAnnotation>() != null) return false;
                o = o.parent;
            }
        }
 
        /// <summary>
        /// Walks the tree starting with "this" node and returns first annotation of type <see cref="SaveOptions"/>
        ///   found in the ancestors.
        /// </summary>
        /// <returns>The effective <see cref="SaveOptions"/> for this <see cref="XObject"/></returns>
        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;
            }
        }
    }
}