//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace Microsoft.Singularity.Xml { using System; using System.Collections; using System.Text; using System.IO; // /// /// /// Specifies formatting options for the XmlWriter stream. /// /// public enum Formatting { // /// /// /// No special formatting is done (this is the default). /// /// None, // /// /// This option causes child elements to be indented using /// the Indentation and IndentChar properties. It only indents Element Content /// (http://www.w3.org/TR/1998/REC-xml-19980210#sec-element-content) /// and not Mixed Content (http://www.w3.org/TR/1998/REC-xml-19980210#sec-mixed-content) /// according to the XML 1.0 definitions of these terms. /// Indented, }; // /// /// /// Specifies the state of the XmlWriter stream. /// /// public enum WriteState { // /// /// /// Nothing has been written yet. /// /// Start, // /// /// /// Writing the prolog. /// /// Prolog, // /// /// /// Writing a the start tag for an element. /// /// Element, // /// /// /// Writing an attribute value. /// /// Attribute, // /// /// /// Writing element content. /// /// Content, // /// /// /// Close has been called. /// /// Closed, }; // Abstract base class. // /// /// Represents a writer that provides fast non-cached forward-only way of generating XML streams containing XML documents that conform to the W3C Extensible Markup Language (XML) 1.0 specification and the Namespaces in XML specification. /// This class is . /// public class XmlWriter { Encoding encoding; TextWriter textWriter; char quoteChar; TagInfo[] stack; int top; int indentation; char indentChar; bool flush; bool indented; // perf - faster to check a boolean. // State machine is working through autocomplete private enum State { Start, Prolog, PostDTD, Element, Attribute, Content, AttrOnly, Epilog, Error, Closed } private enum Token { PI, Doctype, Comment, CData, StartElement, EndElement, LongEndElement, StartAttribute, EndAttribute, Content, RawData, Whitespace, Empty } static string[] stateName = { "Start", "Prolog", "PostDTD", "Element", "Attribute", "Content", "AttrOnly", "Epilog", "Error", "Closed", }; static string[] tokenName = { "PI", "Doctype", "Comment", "CData", "StartElement", "EndElement", "LongEndElement", "StartAttribute", "EndAttribute", "Content", "RawData", "Whitespace", "Empty" }; static readonly State[,] stateTableDefault = { // State.Start State.Prolog State.PostDTD State.Element State.Attribute State.Content State.AttrOnly State.Epilog // /* Token.PI */{ State.Prolog, State.Prolog, State.PostDTD, State.Content, State.Content, State.Content, State.Error, State.Epilog}, /* Token.Doctype */{ State.PostDTD, State.PostDTD, State.Error, State.Error, State.Error, State.Error, State.Error, State.Error}, /* Token.Comment */{ State.Prolog, State.Prolog, State.PostDTD, State.Content, State.Content, State.Content, State.Error, State.Epilog}, /* Token.CData */{ State.Content, State.Content, State.Error, State.Content, State.Content, State.Content, State.Error, State.Epilog}, /* Token.StartElement */{ State.Element, State.Element, State.Element, State.Element, State.Element, State.Element, State.Error, State.Element}, /* Token.EndElement */{ State.Error, State.Error, State.Error, State.Content, State.Content, State.Content, State.Error, State.Error}, /* Token.LongEndElement */{ State.Error, State.Error, State.Error, State.Content, State.Content, State.Content, State.Error, State.Error}, /* Token.StartAttribute */{ State.AttrOnly, State.Error, State.Error, State.Attribute, State.Attribute, State.Error, State.Error, State.Error}, /* Token.EndAttribute */{ State.Error, State.Error, State.Error, State.Error, State.Element, State.Error, State.Epilog, State.Error}, /* Token.Content */{ State.Content, State.Content, State.Error, State.Content, State.Attribute, State.Content, State.Attribute, State.Epilog}, /* Token.RawData */{ State.Prolog, State.Prolog, State.PostDTD, State.Content, State.Attribute, State.Content, State.Attribute, State.Epilog}, /* Token.Whitespace */{ State.Prolog, State.Prolog, State.PostDTD, State.Content, State.Attribute, State.Content, State.Attribute, State.Epilog}, }; static readonly State[,] stateTableDocument = { // State.Start State.Prolog State.PostDTD State.Element State.Attribute State.Content State.AttrOnly State.Epilog // /* Token.PI */{ State.Error, State.Prolog, State.PostDTD, State.Content, State.Content, State.Content, State.Error, State.Epilog}, /* Token.Doctype */{ State.Error, State.PostDTD, State.Error, State.Error, State.Error, State.Error, State.Error, State.Error}, /* Token.Comment */{ State.Error, State.Prolog, State.PostDTD, State.Content, State.Content, State.Content, State.Error, State.Epilog}, /* Token.CData */{ State.Error, State.Error, State.Error, State.Content, State.Content, State.Content, State.Error, State.Error}, /* Token.StartElement */{ State.Error, State.Element, State.Element, State.Element, State.Element, State.Element, State.Error, State.Error}, /* Token.EndElement */{ State.Error, State.Error, State.Error, State.Content, State.Content, State.Content, State.Error, State.Error}, /* Token.LongEndElement */{ State.Error, State.Error, State.Error, State.Content, State.Content, State.Content, State.Error, State.Error}, /* Token.StartAttribute */{ State.Error, State.Error, State.Error, State.Attribute, State.Attribute, State.Error, State.Error, State.Error}, /* Token.EndAttribute */{ State.Error, State.Error, State.Error, State.Error, State.Element, State.Error, State.Error, State.Error}, /* Token.Content */{ State.Error, State.Error, State.Error, State.Content, State.Attribute, State.Content, State.Error, State.Error}, /* Token.RawData */{ State.Error, State.Prolog, State.PostDTD, State.Content, State.Attribute, State.Content, State.Error, State.Epilog}, /* Token.Whitespace */{ State.Error, State.Prolog, State.PostDTD, State.Content, State.Attribute, State.Content, State.Error, State.Epilog}, }; State[,] stateTable; State currentState; Token lastToken; struct TagInfo { internal string name; internal string prefix; internal string defaultNs; internal int prefixCount; internal bool mixed; // whether to pretty print the contents of this element. internal void Init() { name = null; prefix = null; defaultNs = String.Empty; prefixCount = 0; mixed = false; } } public XmlWriter(Stream w, Encoding encoding) { textWriter = new StreamWriter(w, encoding); this.encoding = encoding; indentation = 2; indentChar = ' '; stack = new TagInfo[10]; top = 0;// 0 is an empty sentanial element stack[top].Init(); quoteChar = '"'; flush = false; indented = true; this.stateTable = stateTableDefault; } // /// /// Writes out the XML declaration with the version "1.0". /// public virtual void WriteStartDocument() { } // /// /// Writes out the XML declaration with the version "1.0" and the /// standalone attribute. /// public virtual void WriteStartDocument(bool standalone) { StartDocument(-1); } // /// /// Closes any open elements or attributes and /// puts the writer back in the Start state. /// public virtual void WriteEndDocument() { } // /// /// Writes out the specified start tag and associates it with the /// given namespace. /// public void WriteStartElement(string localName, string ns) { WriteStartElement(null, localName, ns); } // /// /// Writes out the specified start tag and /// associates it with the given namespace and prefix. /// public virtual void WriteStartElement(string prefix, string localName, string ns) { AutoComplete(Token.StartElement); PushStack(); textWriter.Write('<'); stack[top].name = localName; textWriter.Write(localName); } // /// /// Writes out a start tag with the specified name. /// public void WriteStartElement(string localName) { WriteStartElement(null, localName, null); } public void WriteAttributeString(string localName, string value) { WriteStartAttribute(null, localName, null); WriteString(value); WriteEndAttribute(); } public virtual void WriteString(string text) { if (null != text && String.Empty != text) { AutoComplete(Token.Content); textWriter.Write(text); } } public virtual void WriteEndAttribute() { AutoComplete(Token.EndAttribute); } // /// /// Writes the start of an attribute. /// public virtual void WriteStartAttribute(string prefix, string localName, string ns) { AutoComplete(Token.StartAttribute); textWriter.Write(localName); textWriter.Write('='); textWriter.Write(this.quoteChar); } // /// /// Closes one element and pops the corresponding namespace scope. /// public virtual void WriteEndElement() { InternalWriteEndElement(false); } // /// /// Closes one element and pops the /// corresponding namespace scope. /// public virtual void WriteFullEndElement() { InternalWriteEndElement(true); } // /// /// Close this stream and the underlying stream. /// public virtual void Close() { try { AutoCompleteAll(); } catch (Exception) { } // never fail Flush(); textWriter.Close(); this.currentState = State.Closed; } // /// /// Flush whatever is in the buffer to the underlying streams and flush the /// underlying stream. /// public virtual void Flush() { textWriter.Flush(); } internal void FlushEncoders() { textWriter.Flush(); } void Indent(bool beforeEndElement) { // pretty printing. if (top == 0) { textWriter.WriteLine(); } else if (!stack[top].mixed) { textWriter.WriteLine(); int i = beforeEndElement ? top - 1 : top; for (i *= this.indentation; i > 0; i--) { textWriter.Write(this.indentChar); } } } void StartDocument(int standalone) { if (this.currentState != State.Start) { throw new InvalidOperationException("Res.Xml_NotTheFirst"); } this.stateTable = stateTableDocument; this.currentState = State.Prolog; StringBuilder bufBld = new StringBuilder(128); bufBld.Append("version=" + quoteChar + "1.0" + quoteChar); if (this.encoding != null) { bufBld.Append(" encoding=" + quoteChar); //bufBld.Append(this.encoding.WebName); bufBld.Append("utf-8"); bufBld.Append(quoteChar); } if (standalone >= 0) { bufBld.Append(" standalone=" + quoteChar); bufBld.Append(standalone == 0 ? "no" : "yes"); bufBld.Append(quoteChar); } InternalWriteProcessingInstruction("xml", bufBld.ToString()); } void InternalWriteProcessingInstruction(string name, string text) { textWriter.Write(""); } void AutoCompleteAll() { while (top > 0) { if (this.flush) { FlushEncoders(); } WriteEndElement(); } } void InternalWriteEndElement(bool longFormat) { if (top <= 0) { throw new InvalidOperationException("Res.Xml_NoStartTag"); } // if we are in the element, we need to close it. AutoComplete(longFormat ? Token.LongEndElement : Token.EndElement); if (this.lastToken == Token.LongEndElement) { if (this.indented) { Indent(true); } textWriter.Write('<'); textWriter.Write('/'); textWriter.Write(stack[top].name); textWriter.Write('>'); } top--; } void PushStack() { if (top == stack.Length - 1) { TagInfo[] na = new TagInfo[stack.Length + 10]; if (top > 0) Array.Copy(stack,na,top + 1); stack = na; } top ++; // Move up stack stack[top].Init(); } void WriteEndStartTag(bool empty) { //xmlEncoder.StartAttribute(false); //xmlEncoder.EndAttribute(); if (empty) { textWriter.Write(" /"); } textWriter.Write('>'); } void WriteEndAttributeQuote() { textWriter.Write(this.quoteChar); } void AutoComplete(Token token) { if (this.currentState == State.Closed) { throw new InvalidOperationException("Res.Xml_Closed"); } State curr = this.currentState; State newState = this.stateTable[(int)token, (int)this.currentState]; //Console.WriteLine("complete: <{1},{0}> ->{2}", // tokenName[(int) token], stateName[(int) currentState], stateName[(int) newState]); if (newState == State.Error) { throw new InvalidOperationException("Res.Xml_WrongToken, tokenName[(int)token], stateName[(int)this.currentState]"); } switch (token) { case Token.Doctype: if (this.indented && this.currentState != State.Start) { Indent(false); } break; case Token.StartElement: case Token.Comment: case Token.PI: case Token.CData: if (this.currentState == State.Attribute) { WriteEndAttributeQuote(); WriteEndStartTag(false); } else if (this.currentState == State.Element) { WriteEndStartTag(false); } Flush(); if (token == Token.CData) { stack[top].mixed = true; } else if (this.indented && this.currentState != State.Start) { Indent(false); } break; case Token.EndElement: case Token.LongEndElement: if (this.flush) { FlushEncoders(); } if (this.currentState == State.Attribute) { WriteEndAttributeQuote(); } if (this.currentState == State.Content) { token = Token.LongEndElement; } else { WriteEndStartTag(token == Token.EndElement); } if (stateTableDocument == this.stateTable && top == 1) { newState = State.Epilog; } break; case Token.StartAttribute: if (this.flush) { FlushEncoders(); } if (this.currentState == State.Attribute) { WriteEndAttributeQuote(); textWriter.Write(' '); } else if (this.currentState == State.Element) { textWriter.Write(' '); } break; case Token.EndAttribute: if (this.flush) { FlushEncoders(); } WriteEndAttributeQuote(); break; case Token.Whitespace: case Token.Content: case Token.RawData: if (this.currentState == State.Element && this.lastToken != Token.Content) { WriteEndStartTag(false); } //else if (this.currentState == State.Attribute && this.specialAttr == SpecialAttr.None && this.lastToken == Token.Content) else if (this.currentState == State.Attribute && this.lastToken == Token.Content) { // Beta 2 // textWriter.Write(' '); } if (newState == State.Content) { stack[top].mixed = true; } break; default: throw new InvalidOperationException("Res.Xml_InvalidOperation"); } this.currentState = newState; this.lastToken = token; } } }