File: System\Xml\Core\XmlWellFormedWriterHelpers.cs
Project: ndp\fx\src\Xml\System.Xml.csproj (System.Xml)

//------------------------------------------------------------------------------
// <copyright file="XmlWellFormedWriterHelpers.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
 
using System;
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;
 
namespace System.Xml {
 
    internal partial class XmlWellFormedWriter : XmlWriter {
 
        //
        // Private types
        //
        class NamespaceResolverProxy : IXmlNamespaceResolver {
            XmlWellFormedWriter wfWriter;
 
            internal NamespaceResolverProxy(XmlWellFormedWriter wfWriter) {
                this.wfWriter = wfWriter;
            }
 
            IDictionary<string, string> IXmlNamespaceResolver.GetNamespacesInScope(XmlNamespaceScope scope) {
                throw new NotImplementedException();
            }
            string IXmlNamespaceResolver.LookupNamespace(string prefix) {
                return wfWriter.LookupNamespace(prefix);
            }
 
            string IXmlNamespaceResolver.LookupPrefix(string namespaceName) {
                return wfWriter.LookupPrefix(namespaceName);
            }
        }
 
        partial struct ElementScope {
 
            internal int prevNSTop;
            internal string prefix;
            internal string localName;
            internal string namespaceUri;
            internal XmlSpace xmlSpace;
            internal string xmlLang;
 
            internal void Set(string prefix, string localName, string namespaceUri, int prevNSTop) {
                this.prevNSTop = prevNSTop;
                this.prefix = prefix;
                this.namespaceUri = namespaceUri;
                this.localName = localName;
                this.xmlSpace = (System.Xml.XmlSpace)(int)-1;
                this.xmlLang = null;
            }
 
            internal void WriteEndElement(XmlRawWriter rawWriter) {
                rawWriter.WriteEndElement(prefix, localName, namespaceUri);
            }
 
            internal void WriteFullEndElement(XmlRawWriter rawWriter) {
                rawWriter.WriteFullEndElement(prefix, localName, namespaceUri);
            }
        }
 
        enum NamespaceKind {
            Written,
            NeedToWrite,
            Implied,
            Special,
        }
 
        partial struct Namespace {
 
            internal string prefix;
            internal string namespaceUri;
            internal NamespaceKind kind;
            internal int prevNsIndex;
 
            internal void Set(string prefix, string namespaceUri, NamespaceKind kind) {
                this.prefix = prefix;
                this.namespaceUri = namespaceUri;
                this.kind = kind;
                this.prevNsIndex = -1;
            }
 
            internal void WriteDecl(XmlWriter writer, XmlRawWriter rawWriter) {
                Debug.Assert(kind == NamespaceKind.NeedToWrite);
                if (null != rawWriter) {
                    rawWriter.WriteNamespaceDeclaration(prefix, namespaceUri);
                }
                else {
                    if (prefix.Length == 0) {
                        writer.WriteStartAttribute(string.Empty, "xmlns", XmlReservedNs.NsXmlNs);
                    }
                    else {
                        writer.WriteStartAttribute("xmlns", prefix, XmlReservedNs.NsXmlNs);
                    }
                    writer.WriteString(namespaceUri);
                    writer.WriteEndAttribute();
                }
            }
        }
 
        struct AttrName {
            internal string prefix;
            internal string namespaceUri;
            internal string localName;
            internal int prev;
 
            internal void Set(string prefix, string localName, string namespaceUri) {
                this.prefix = prefix;
                this.namespaceUri = namespaceUri;
                this.localName = localName;
                this.prev = 0;
            }
 
            internal bool IsDuplicate(string prefix, string localName, string namespaceUri) {
                return ((this.localName == localName)
                    && ((this.prefix == prefix) || (this.namespaceUri == namespaceUri)));
            }
        }
 
        enum SpecialAttribute {
            No = 0,
            DefaultXmlns,
            PrefixedXmlns,
            XmlSpace,
            XmlLang
        }
 
        partial class AttributeValueCache {
 
            enum ItemType {
                EntityRef,
                CharEntity,
                SurrogateCharEntity,
                Whitespace,
                String,
                StringChars,
                Raw,
                RawChars,
                ValueString,
            }
 
            class Item {
                internal ItemType type;
                internal object data;
 
                internal Item() { }
 
                internal void Set(ItemType type, object data) {
                    this.type = type;
                    this.data = data;
                }
            }
 
            class BufferChunk {
                internal char[] buffer;
                internal int index;
                internal int count;
 
                internal BufferChunk(char[] buffer, int index, int count) {
                    this.buffer = buffer;
                    this.index = index;
                    this.count = count;
                }
            }
 
            StringBuilder stringValue = new StringBuilder();
            string singleStringValue; // special-case for a single WriteString call
            Item[] items;
            int firstItem;
            int lastItem = -1;
 
            internal string StringValue {
                get {
                    if (singleStringValue != null) {
                        return singleStringValue;
                    }
                    else {
                        return stringValue.ToString();
                    }
                }
            }
 
            internal void WriteEntityRef(string name) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
 
                switch (name) {
                    case "lt":
                        stringValue.Append('<');
                        break;
                    case "gt":
                        stringValue.Append('>');
                        break;
                    case "quot":
                        stringValue.Append('"');
                        break;
                    case "apos":
                        stringValue.Append('\'');
                        break;
                    case "amp":
                        stringValue.Append('&');
                        break;
                    default:
                        stringValue.Append('&');
                        stringValue.Append(name);
                        stringValue.Append(';');
                        break;
                }
 
                AddItem(ItemType.EntityRef, name);
            }
 
            internal void WriteCharEntity(char ch) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
                stringValue.Append(ch);
                AddItem(ItemType.CharEntity, ch);
            }
 
            internal void WriteSurrogateCharEntity(char lowChar, char highChar) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
                stringValue.Append(highChar);
                stringValue.Append(lowChar);
                AddItem(ItemType.SurrogateCharEntity, new char[] { lowChar, highChar });
            }
 
            internal void WriteWhitespace(string ws) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
                stringValue.Append(ws);
                AddItem(ItemType.Whitespace, ws);
            }
 
            internal void WriteString(string text) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
                else {
                    // special-case for a single WriteString
                    if (lastItem == -1) {
                        singleStringValue = text;
                        return;
                    }
                }
                
                stringValue.Append(text);
                AddItem(ItemType.String, text);
            }
 
            internal void WriteChars(char[] buffer, int index, int count) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
                stringValue.Append(buffer, index, count);
                AddItem(ItemType.StringChars, new BufferChunk(buffer, index, count));
            }
 
            internal void WriteRaw(char[] buffer, int index, int count) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
                stringValue.Append(buffer, index, count);
                AddItem(ItemType.RawChars, new BufferChunk(buffer, index, count));
            }
 
            internal void WriteRaw(string data) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
                stringValue.Append(data);
                AddItem(ItemType.Raw, data);
            }
 
            internal void WriteValue(string value) {
                if (singleStringValue != null) {
                    StartComplexValue();
                }
                stringValue.Append(value);
                AddItem(ItemType.ValueString, value);
            }
 
            internal void Replay(XmlWriter writer) {
                if (singleStringValue != null) {
                    writer.WriteString(singleStringValue);
                    return;
                }
 
                BufferChunk bufChunk;
                for (int i = firstItem; i <= lastItem; i++) {
                    Item item = items[i];
                    switch (item.type) {
                        case ItemType.EntityRef:
                            writer.WriteEntityRef((string)item.data);
                            break;
                        case ItemType.CharEntity:
                            writer.WriteCharEntity((char)item.data);
                            break;
                        case ItemType.SurrogateCharEntity:
                            char[] chars = (char[])item.data;
                            writer.WriteSurrogateCharEntity(chars[0], chars[1]);
                            break;
                        case ItemType.Whitespace:
                            writer.WriteWhitespace((string)item.data);
                            break;
                        case ItemType.String:
                            writer.WriteString((string)item.data);
                            break;
                        case ItemType.StringChars:
                            bufChunk = (BufferChunk)item.data;
                            writer.WriteChars(bufChunk.buffer, bufChunk.index, bufChunk.count);
                            break;
                        case ItemType.Raw:
                            writer.WriteRaw((string)item.data);
                            break;
                        case ItemType.RawChars:
                            bufChunk = (BufferChunk)item.data;
                            writer.WriteChars(bufChunk.buffer, bufChunk.index, bufChunk.count);
                            break;
                        case ItemType.ValueString:
                            writer.WriteValue((string)item.data);
                            break;
                        default:
                            Debug.Assert(false, "Unexpected ItemType value.");
                            break;
                    }
                }
            }
 
            // This method trims whitespaces from the beginnig and the end of the string and cached writer events
            internal void Trim() {
                // if only one string value -> trim the write spaces directly
                if (singleStringValue != null) {
                    singleStringValue = XmlConvert.TrimString(singleStringValue);
                    return;
                }
 
                // trim the string in StringBuilder
                string valBefore = stringValue.ToString();
                string valAfter = XmlConvert.TrimString(valBefore);
                if (valBefore != valAfter) {
                    stringValue = new StringBuilder(valAfter);
                }
 
                // trim the beginning of the recorded writer events
                XmlCharType xmlCharType = XmlCharType.Instance;
 
                int i = firstItem;
                while (i == firstItem && i <= lastItem) {
                    Item item = items[i];
                    switch (item.type) {
                        case ItemType.Whitespace:
                            firstItem++;
                            break;
                        case ItemType.String:
                        case ItemType.Raw:
                        case ItemType.ValueString:
                            item.data = XmlConvert.TrimStringStart((string)item.data);
                            if (((string)item.data).Length == 0) {
                                // no characters left -> move the firstItem index to exclude it from the Replay
                                firstItem++;
                            }
                            break;
                        case ItemType.StringChars:
                        case ItemType.RawChars:
                            BufferChunk bufChunk = (BufferChunk)item.data;
                            int endIndex = bufChunk.index + bufChunk.count;
                            while (bufChunk.index < endIndex && xmlCharType.IsWhiteSpace(bufChunk.buffer[bufChunk.index])) {
                                bufChunk.index++;
                                bufChunk.count--;
                            }
                            if (bufChunk.index == endIndex) {
                                // no characters left -> move the firstItem index to exclude it from the Replay
                                firstItem++;
                            }
                            break;
                    }
                    i++;
                }
 
                // trim the end of the recorded writer events
                i = lastItem;
                while (i == lastItem && i >= firstItem) {
                    Item item = items[i];
                    switch (item.type) {
                        case ItemType.Whitespace:
                            lastItem--;
                            break;
                        case ItemType.String:
                        case ItemType.Raw:
                        case ItemType.ValueString:
                            item.data = XmlConvert.TrimStringEnd((string)item.data);
                            if (((string)item.data).Length == 0) {
                                // no characters left -> move the lastItem index to exclude it from the Replay
                                lastItem--;
                            }
                            break;
                        case ItemType.StringChars:
                        case ItemType.RawChars:
                            BufferChunk bufChunk = (BufferChunk)item.data;  
                            while (bufChunk.count > 0 && xmlCharType.IsWhiteSpace(bufChunk.buffer[bufChunk.index + bufChunk.count - 1])) {
                                bufChunk.count--;
                            }
                            if (bufChunk.count == 0) {
                                // no characters left -> move the lastItem index to exclude it from the Replay
                                lastItem--;
                            }
                            break;
                    }
                    i--;
                }
            }
 
            internal void Clear() {
                singleStringValue = null;
                lastItem = -1;
                firstItem = 0;
                stringValue.Length = 0;
            }
 
            private void StartComplexValue() {
                Debug.Assert(singleStringValue != null);
                Debug.Assert(lastItem == -1);
 
                stringValue.Append( singleStringValue );
                AddItem(ItemType.String, singleStringValue);
                
                singleStringValue = null;
            }
 
            void AddItem(ItemType type, object data) {
                int newItemIndex = lastItem + 1;
                if (items == null) {
                    items = new Item[4];
                }
                else if (items.Length == newItemIndex) {
                    Item[] newItems = new Item[newItemIndex * 2];
                    Array.Copy(items, newItems, newItemIndex);
                    items = newItems;
                }
                if (items[newItemIndex] == null) {
                    items[newItemIndex] = new Item();
                }
                items[newItemIndex].Set(type, data);
                lastItem = newItemIndex;
            }
 
        }
    }
}