| File: System\Xml\Linq\XLinq.cs | Web Access |
| Project: src\runtime\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 System.Threading; using System.Threading.Tasks; using IEnumerable = System.Collections.IEnumerable; using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; namespace System.Xml.Linq { internal struct Inserter { private readonly XContainer _parent; private XNode? _previous; private string? _text; public Inserter(XContainer parent, XNode? anchor) { _parent = parent; _previous = anchor; _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) { XText? prevXText = _previous as XText; if (prevXText != null && !(_previous is XCData)) { prevXText.Value += _text; } else { _parent.ConvertTextToNode(); InsertNode(new XText(_text)); } } } } private 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)); } private 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) { XText? prevXText = _previous as XText; if (prevXText != null && !(_previous is XCData)) { prevXText.Value += _text; } else { InsertNode(new XText(_text)); } } _text = null; } InsertNode(n); } private void AddString(string s) { _parent.ValidateString(s); _text += s; } // Prepends if previous == null, otherwise inserts after previous private 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 { private XNamespace _ns; private string _namespaceName; public XNamespace Get(string namespaceName) { if ((object)namespaceName == (object)_namespaceName) return _ns; _namespaceName = namespaceName; _ns = XNamespace.Get(namespaceName); return _ns; } } internal struct ElementWriter { private readonly XmlWriter _writer; private NamespaceResolver _resolver; public ElementWriter(XmlWriter writer) { _writer = writer; _resolver = default; } public void WriteElement(XElement e) { PushAncestors(e); XElement root = e; XNode n = e; while (true) { XElement? current = n as XElement; if (current != null) { WriteStartElement(current); if (current.content == null) { WriteEndElement(); } else { string? s = current.content as string; if (s != null) { _writer.WriteString(s); WriteFullEndElement(); } else { n = ((XNode)current.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!; } } public async Task WriteElementAsync(XElement e, CancellationToken cancellationToken) { PushAncestors(e); XElement root = e; XNode n = e; while (true) { XElement? current = n as XElement; if (current != null) { await WriteStartElementAsync(current, cancellationToken).ConfigureAwait(false); if (current.content == null) { await WriteEndElementAsync(cancellationToken).ConfigureAwait(false); } else { string? s = current.content as string; if (s != null) { cancellationToken.ThrowIfCancellationRequested(); await _writer.WriteStringAsync(s).ConfigureAwait(false); await WriteFullEndElementAsync(cancellationToken).ConfigureAwait(false); } else { n = ((XNode)current.content).next!; continue; } } } else { await n.WriteToAsync(_writer, cancellationToken).ConfigureAwait(false); } while (n != root && n == n.parent!.content) { n = n.parent; await WriteFullEndElementAsync(cancellationToken).ConfigureAwait(false); } if (n == root) break; n = n.next!; } } private 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; } private 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); } } } private 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); } } private void WriteEndElement() { _writer.WriteEndElement(); _resolver.PopScope(); } private async Task WriteEndElementAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); await _writer.WriteEndElementAsync().ConfigureAwait(false); _resolver.PopScope(); } private void WriteFullEndElement() { _writer.WriteFullEndElement(); _resolver.PopScope(); } private async Task WriteFullEndElementAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); await _writer.WriteFullEndElementAsync().ConfigureAwait(false); _resolver.PopScope(); } private 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); } } private async Task WriteStartElementAsync(XElement e, CancellationToken cancellationToken) { PushElement(e); XNamespace ns = e.Name.Namespace; await _writer.WriteStartElementAsync(GetPrefixOfNamespace(ns, true), e.Name.LocalName, ns.NamespaceName).ConfigureAwait(false); XAttribute? a = e.lastAttr; if (a != null) { do { a = a.next!; ns = a.Name.Namespace; string localName = a.Name.LocalName; string namespaceName = ns.NamespaceName; cancellationToken.ThrowIfCancellationRequested(); await _writer.WriteAttributeStringAsync(GetPrefixOfNamespace(ns, false), localName, namespaceName.Length == 0 && localName == "xmlns" ? XNamespace.xmlnsPrefixNamespace : namespaceName, a.Value).ConfigureAwait(false); } while (a != e.lastAttr); } } } internal struct NamespaceResolver { private sealed class NamespaceDeclaration { public string prefix = null!; public XNamespace ns = null!; public int scope; public NamespaceDeclaration prev = null!; } private int _scope; private NamespaceDeclaration? _declaration; private 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; } } internal struct StreamingElementWriter { private readonly XmlWriter _writer; private XStreamingElement? _element; private readonly List<XAttribute> _attributes; private NamespaceResolver _resolver; public StreamingElementWriter(XmlWriter w) { _writer = w; _element = null; _attributes = new List<XAttribute>(); _resolver = default; } private 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(); } } private 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; } private 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)); } } } private 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)); } private void WriteAttribute(XAttribute a) { if (_element == null) throw new InvalidOperationException(SR.InvalidOperation_WriteAttribute); _attributes.Add(a); } private void WriteNode(XNode n) { FlushElement(); n.WriteTo(_writer); } internal void WriteStreamingElement(XStreamingElement e) { FlushElement(); _element = e; Write(e.content); FlushElement(); _writer.WriteEndElement(); _resolver.PopScope(); } private void WriteString(string s) { FlushElement(); _writer.WriteString(s); } } /// <summary> /// Specifies the event type when an event is raised for an <see cref="XObject"/>. /// </summary> public enum XObjectChange { /// <summary> /// An <see cref="XObject"/> has been or will be added to an <see cref="XContainer"/>. /// </summary> Add, /// <summary> /// An <see cref="XObject"/> has been or will be removed from an <see cref="XContainer"/>. /// </summary> Remove, /// <summary> /// An <see cref="XObject"/> has been or will be renamed. /// </summary> Name, /// <summary> /// The value of an <see cref="XObject"/> 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. /// </summary> Value, } /// <summary> /// Specifies a set of options for Load(). /// </summary> [Flags] public enum LoadOptions { /// <summary>Default options.</summary> None = 0x00000000, /// <summary>Preserve whitespace.</summary> PreserveWhitespace = 0x00000001, /// <summary>Set the BaseUri property.</summary> SetBaseUri = 0x00000002, /// <summary>Set the IXmlLineInfo.</summary> SetLineInfo = 0x00000004, } /// <summary> /// Specifies a set of options for Save(). /// </summary> [Flags] public enum SaveOptions { /// <summary>Default options.</summary> None = 0x00000000, /// <summary>Disable formatting.</summary> DisableFormatting = 0x00000001, /// <summary>Remove duplicate namespace declarations.</summary> OmitDuplicateNamespaces = 0x00000002, } /// <summary> /// Specifies a set of options for CreateReader(). /// </summary> [Flags] public enum ReaderOptions { /// <summary>Default options.</summary> None = 0x00000000, /// <summary>Remove duplicate namespace declarations.</summary> OmitDuplicateNamespaces = 0x00000001, } }