File: System\Xml\Dom\DocumentXmlWriter.cs
Project: ndp\fx\src\Xml\System.Xml.csproj (System.Xml)
//------------------------------------------------------------------------------
// <copyright file="DocumentXmlWriter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml.Schema;
 
namespace System.Xml {
    enum DocumentXmlWriterType {
        InsertSiblingAfter,
        InsertSiblingBefore,
        PrependChild,
        AppendChild,
        AppendAttribute,
        ReplaceToFollowingSibling,
    }
 
    // Implements a XmlWriter that augments a XmlDocument.
    sealed class DocumentXmlWriter : XmlRawWriter, IXmlNamespaceResolver {
        enum State {
            Error,
            Attribute,
            Prolog,
            Fragment,
            Content,
 
            Last, // always last
        }
 
        enum Method {
            WriteXmlDeclaration,
            WriteStartDocument,
            WriteEndDocument,
            WriteDocType,
            WriteStartElement,
            WriteEndElement,
            WriteFullEndElement,
            WriteStartAttribute,
            WriteEndAttribute,
            WriteStartNamespaceDeclaration,
            WriteEndNamespaceDeclaration,
            WriteCData,
            WriteComment,
            WriteProcessingInstruction,
            WriteEntityRef,
            WriteWhitespace,
            WriteString,
        }
 
        DocumentXmlWriterType type; // writer type
        XmlNode start; // context node
        XmlDocument document; // context document 
        XmlNamespaceManager namespaceManager; // context namespace manager
        State state; // current state
        XmlNode write; // current node 
        List<XmlNode> fragment; // top level node cache
        XmlWriterSettings settings; // wrapping writer settings
        DocumentXPathNavigator navigator; // context for replace 
        XmlNode end; // context for replace 
 
        public DocumentXmlWriter(DocumentXmlWriterType type, XmlNode start, XmlDocument document) {
            this.type = type;
            this.start = start;
            this.document = document;
 
            state = StartState(); 
            fragment = new List<XmlNode>();
            settings = new XmlWriterSettings();
            settings.ReadOnly = false;
            settings.CheckCharacters = false;
            settings.CloseOutput = false;
            settings.ConformanceLevel = (state == State.Prolog ? ConformanceLevel.Document : ConformanceLevel.Fragment);
            settings.ReadOnly = true;
        }
 
        public XmlNamespaceManager NamespaceManager {
            set { 
                namespaceManager = value; 
            }
        }
 
        public override XmlWriterSettings Settings {
            get { 
                return settings; 
            }
        }
 
        internal void SetSettings(XmlWriterSettings value) {
            settings = value;
        }
 
        public DocumentXPathNavigator Navigator {
            set {
                navigator = value;
            }
        }
 
        public XmlNode EndNode {
            set {
                end = value;
            }
        }
 
        internal override void WriteXmlDeclaration(XmlStandalone standalone) {
            VerifyState(Method.WriteXmlDeclaration);
            if (standalone != XmlStandalone.Omit) {
                XmlNode node = document.CreateXmlDeclaration("1.0", string.Empty, standalone == XmlStandalone.Yes ? "yes" : "no");  
                AddChild(node, write);
            }
        }
 
        internal override void WriteXmlDeclaration(string xmldecl) {
            VerifyState(Method.WriteXmlDeclaration);
            string version, encoding, standalone;
            XmlLoader.ParseXmlDeclarationValue(xmldecl, out version, out encoding, out standalone);
            XmlNode node = document.CreateXmlDeclaration(version, encoding, standalone);
            AddChild(node, write);
        }
 
        public override void WriteStartDocument() {
            VerifyState(Method.WriteStartDocument);
        }
 
        public override void WriteStartDocument(bool standalone) {
            VerifyState(Method.WriteStartDocument);
        }
 
        public override void WriteEndDocument() {
            VerifyState(Method.WriteEndDocument);
        }
 
        public override void WriteDocType(string name, string pubid, string sysid, string subset) {
            VerifyState(Method.WriteDocType);
            XmlNode node = document.CreateDocumentType(name, pubid, sysid, subset);
            AddChild(node, write); 
        }
 
        public override void WriteStartElement(string prefix, string localName, string ns) {
            VerifyState(Method.WriteStartElement);
            XmlNode node = document.CreateElement(prefix, localName, ns);
            AddChild(node, write);
            write = node; 
        }
 
        public override void WriteEndElement() {
            VerifyState(Method.WriteEndElement);
            if (write == null) {
                throw new InvalidOperationException();
            }
            write = write.ParentNode;
        }
 
        internal override void WriteEndElement(string prefix, string localName, string ns) {
            WriteEndElement();
        }
 
        public override void WriteFullEndElement() {
            VerifyState(Method.WriteFullEndElement);
            XmlElement elem = write as XmlElement;
            if (elem == null) {
                throw new InvalidOperationException();
            }
            elem.IsEmpty = false;
            write = elem.ParentNode;
        }
 
        internal override void WriteFullEndElement(string prefix, string localName, string ns) {
            WriteFullEndElement();
        }
 
        internal override void StartElementContent() {
            // nop
        }
 
        public override void WriteStartAttribute(string prefix, string localName, string ns) {
            VerifyState(Method.WriteStartAttribute);
            XmlAttribute attr = document.CreateAttribute(prefix, localName, ns);
            AddAttribute(attr, write);
            write = attr;
        }
 
        public override void WriteEndAttribute() {
            VerifyState(Method.WriteEndAttribute);
            XmlAttribute attr = write as XmlAttribute;
            if (attr == null) {
                throw new InvalidOperationException();
            }
            if (!attr.HasChildNodes) {
                XmlNode node = document.CreateTextNode(string.Empty); 
                AddChild(node, attr);
            }
            write = attr.OwnerElement;
        }
 
        internal override void WriteNamespaceDeclaration(string prefix, string ns) {
            this.WriteStartNamespaceDeclaration(prefix);
            this.WriteString(ns);
            this.WriteEndNamespaceDeclaration();
        }
 
        internal override bool SupportsNamespaceDeclarationInChunks {
            get {
                return true;
            }
        }
 
        internal override void WriteStartNamespaceDeclaration(string prefix) {
            VerifyState(Method.WriteStartNamespaceDeclaration);
            XmlAttribute attr;
            if (prefix.Length == 0) {
                attr = document.CreateAttribute(prefix, document.strXmlns, document.strReservedXmlns);
            }
            else {
                attr = document.CreateAttribute(document.strXmlns, prefix, document.strReservedXmlns);
            }
            AddAttribute(attr, write);
            write = attr;
        }
 
        internal override void WriteEndNamespaceDeclaration() {
            VerifyState(Method.WriteEndNamespaceDeclaration);
            XmlAttribute attr = write as XmlAttribute;
            if (attr == null) {
                throw new InvalidOperationException();
            }
            if (!attr.HasChildNodes) {
                XmlNode node = document.CreateTextNode(string.Empty);
                AddChild(node, attr);
            }
            write = attr.OwnerElement;
        }
 
        public override void WriteCData(string text) {
            VerifyState(Method.WriteCData);
            XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
            XmlNode node = document.CreateCDataSection(text);
            AddChild(node, write);
        }
 
        public override void WriteComment(string text) {
            VerifyState(Method.WriteComment);
            XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
            XmlNode node = document.CreateComment(text);
            AddChild(node, write);
        }
 
        public override void WriteProcessingInstruction(string name, string text) {
            VerifyState(Method.WriteProcessingInstruction);
            XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
            XmlNode node = document.CreateProcessingInstruction(name, text);
            AddChild(node, write);
        }
 
        public override void WriteEntityRef(string name) {
            VerifyState(Method.WriteEntityRef);
            XmlNode node = document.CreateEntityReference(name);
            AddChild(node, write);
            // 
        }
 
        public override void WriteCharEntity(char ch) {
            WriteString(new string(ch, 1));
        }
 
        public override void WriteWhitespace(string text) {
            VerifyState(Method.WriteWhitespace);
            XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
            if (document.PreserveWhitespace) {
                XmlNode node = document.CreateWhitespace(text);
                AddChild(node, write);
            }
        }
 
        public override void WriteString(string text) {
            VerifyState(Method.WriteString);
            XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
            XmlNode node = document.CreateTextNode(text);
            AddChild(node, write);
        }
 
        public override void WriteSurrogateCharEntity(char lowCh, char highCh) {
            WriteString(new string(new char[] {highCh, lowCh}));
        }
 
        public override void WriteChars(char[] buffer, int index, int count) {
            WriteString(new string(buffer, index, count));
        }
 
        public override void WriteRaw(char[] buffer, int index, int count) {
            WriteString(new string(buffer, index, count));
        }
 
        public override void WriteRaw(string data) {
            WriteString(data);
        }
 
        public override void Close() {
            // nop
        }
 
        internal override void Close(WriteState currentState) {
            if (currentState == WriteState.Error) {
                return;
            }
            try {
                switch (type) {
                    case DocumentXmlWriterType.InsertSiblingAfter:
                        XmlNode parent = start.ParentNode;
                        if (parent == null) {
                            throw new InvalidOperationException(Res.GetString(Res.Xpn_MissingParent));
                        }
                        for (int i = fragment.Count - 1; i >= 0; i--) {
                            parent.InsertAfter(fragment[i], start);
                        }
                        break;
                    case DocumentXmlWriterType.InsertSiblingBefore:
                        parent = start.ParentNode;
                        if (parent == null) {
                            throw new InvalidOperationException(Res.GetString(Res.Xpn_MissingParent));
                        }
                        for (int i = 0; i < fragment.Count; i++) {
                            parent.InsertBefore(fragment[i], start);
                        }
                        break;
                    case DocumentXmlWriterType.PrependChild:
                        for (int i = fragment.Count - 1; i >= 0; i--) {
                            start.PrependChild(fragment[i]);
                        }
                        break;
                    case DocumentXmlWriterType.AppendChild:
                        for (int i = 0; i < fragment.Count; i++) {
                            start.AppendChild(fragment[i]);
                        }
                        break;
                    case DocumentXmlWriterType.AppendAttribute:
                        CloseWithAppendAttribute();
                        break;
                    case DocumentXmlWriterType.ReplaceToFollowingSibling:
                        if (fragment.Count == 0) {
                            throw new InvalidOperationException(Res.GetString(Res.Xpn_NoContent));
                        }
                        CloseWithReplaceToFollowingSibling();
                        break;
                }
            }
            finally {
                fragment.Clear();
            }
        }
 
        private void CloseWithAppendAttribute() {
            XmlElement elem = start as XmlElement;
            Debug.Assert(elem != null);
            XmlAttributeCollection attrs = elem.Attributes;
            for (int i = 0; i < fragment.Count; i++) {
                XmlAttribute attr = fragment[i] as XmlAttribute; 
                Debug.Assert(attr != null);
                int offset = attrs.FindNodeOffsetNS(attr);
                if (offset != -1
                    && ((XmlAttribute)attrs.nodes[offset]).Specified) {
                    throw new XmlException(Res.Xml_DupAttributeName, attr.Prefix.Length == 0 ? attr.LocalName : string.Concat(attr.Prefix, ":", attr.LocalName));
                }
            }
            for (int i = 0; i < fragment.Count; i++) {
                XmlAttribute attr = fragment[i] as XmlAttribute; 
                Debug.Assert(attr != null);
                attrs.Append(attr);
            }
        }
 
        private void CloseWithReplaceToFollowingSibling() {
            XmlNode parent = start.ParentNode;
            if (parent == null) {
                throw new InvalidOperationException(Res.GetString(Res.Xpn_MissingParent));
            }
            if (start != end) {
                if (!DocumentXPathNavigator.IsFollowingSibling(start, end)) {
                    throw new InvalidOperationException(Res.GetString(Res.Xpn_BadPosition));
                }
                if (start.IsReadOnly) {
                    throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Modify_ReadOnly));
                }
                DocumentXPathNavigator.DeleteToFollowingSibling(start.NextSibling, end);
            }
            XmlNode fragment0 = fragment[0];
            parent.ReplaceChild(fragment0, start);
            for (int i = fragment.Count - 1; i >= 1; i--) {
                parent.InsertAfter(fragment[i], fragment0);
            }
            navigator.ResetPosition(fragment0);
        }
 
        public override void Flush() {
            // nop
        }
 
        IDictionary<string,string> IXmlNamespaceResolver.GetNamespacesInScope(XmlNamespaceScope scope) {
            return namespaceManager.GetNamespacesInScope(scope);
        }
 
        string IXmlNamespaceResolver.LookupNamespace(string prefix) {
            return namespaceManager.LookupNamespace(prefix);
        }
 
        string IXmlNamespaceResolver.LookupPrefix(string namespaceName) {
            return namespaceManager.LookupPrefix(namespaceName);
        }
 
        void AddAttribute(XmlAttribute attr, XmlNode parent) {
            if (parent == null) {
                fragment.Add(attr);
            }
            else {
                XmlElement elem = parent as XmlElement; 
                if (elem == null) {
                    throw new InvalidOperationException();
                }
                elem.Attributes.Append(attr);
            }
        }
 
        void AddChild(XmlNode node, XmlNode parent) {
            if (parent == null) {
                fragment.Add(node);
            }
            else {
                parent.AppendChild(node);
            }
        }
 
        State StartState() {
            XmlNodeType nodeType = XmlNodeType.None;
 
            switch (type) {
                case DocumentXmlWriterType.InsertSiblingAfter:
                case DocumentXmlWriterType.InsertSiblingBefore:
                    XmlNode parent = start.ParentNode;
                    if (parent != null) { 
                        nodeType = parent.NodeType; 
                    }
                    if (nodeType == XmlNodeType.Document) {
                        return State.Prolog;
                    }
                    else if (nodeType == XmlNodeType.DocumentFragment)  {
                        return State.Fragment;
                    }
                    break;
                case DocumentXmlWriterType.PrependChild:
                case DocumentXmlWriterType.AppendChild:
                    nodeType = start.NodeType; 
                    if (nodeType == XmlNodeType.Document) {
                        return State.Prolog;
                    }
                    else if (nodeType == XmlNodeType.DocumentFragment)  {
                        return State.Fragment;
                    }
                    break;
                case DocumentXmlWriterType.AppendAttribute:
                    return State.Attribute;
                case DocumentXmlWriterType.ReplaceToFollowingSibling:
                    break;
            }
            return State.Content;
        }
 
        static State[] changeState = {
//          State.Error,    State.Attribute,State.Prolog,   State.Fragment, State.Content,  
 
// Method.XmlDeclaration:
            State.Error,    State.Error,    State.Prolog,   State.Content,  State.Error,    
// Method.StartDocument:
            State.Error,    State.Error,    State.Error,    State.Error,    State.Error,    
// Method.EndDocument:
            State.Error,    State.Error,    State.Error,    State.Error,    State.Error,    
// Method.DocType:
            State.Error,    State.Error,    State.Prolog,   State.Error,    State.Error,    
// Method.StartElement:
            State.Error,    State.Error,    State.Content,  State.Content,  State.Content,  
// Method.EndElement:
            State.Error,    State.Error,    State.Error,    State.Error,    State.Content,  
// Method.FullEndElement:
            State.Error,    State.Error,    State.Error,    State.Error,    State.Content,  
// Method.StartAttribute:
            State.Error,    State.Content,  State.Error,    State.Error,    State.Content,  
// Method.EndAttribute:
            State.Error,    State.Error,    State.Error,    State.Error,    State.Content,  
// Method.StartNamespaceDeclaration:
            State.Error,    State.Content,  State.Error,    State.Error,    State.Content,  
// Method.EndNamespaceDeclaration:
            State.Error,    State.Error,    State.Error,    State.Error,    State.Content,  
// Method.CData:
            State.Error,    State.Error,    State.Error,    State.Content,  State.Content,  
// Method.Comment:
            State.Error,    State.Error,    State.Prolog,   State.Content,  State.Content,  
// Method.ProcessingInstruction:
            State.Error,    State.Error,    State.Prolog,   State.Content,  State.Content,  
// Method.EntityRef:
            State.Error,    State.Error,    State.Error,    State.Content,  State.Content,  
// Method.Whitespace:
            State.Error,    State.Error,    State.Prolog,   State.Content,  State.Content,  
// Method.String:
            State.Error,    State.Error,    State.Error,    State.Content,  State.Content,  
        };
 
        void VerifyState(Method method) {
            state = changeState[(int)method * (int)State.Last + (int)state]; 
            if (state == State.Error) {
                throw new InvalidOperationException(Res.GetString(Res.Xml_ClosedOrError));
            }
        }
    }
}