singrdk/base/Libraries/Xml/XmlWriter.cs

592 lines
24 KiB
C#
Raw Permalink Normal View History

2008-11-17 18:29:00 -05:00
//------------------------------------------------------------------------------
// <copyright file="XmlWriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.Singularity.Xml
{
using System;
using System.Collections;
using System.Text;
using System.IO;
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="Formatting"]/*' />
/// <devdoc>
/// <para>
/// Specifies formatting options for the XmlWriter stream.
/// </para>
/// </devdoc>
public enum Formatting {
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="Formatting.None"]/*' />
/// <devdoc>
/// <para>
/// No special formatting is done (this is the default).
/// </para>
/// </devdoc>
None,
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="Formatting.Indented"]/*' />
/// <devdoc>
/// 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.
/// </devdoc>
Indented,
};
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="WriteState"]/*' />
/// <devdoc>
/// <para>
/// Specifies the state of the XmlWriter stream.
/// </para>
/// </devdoc>
public enum WriteState {
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="WriteState.Start"]/*' />
/// <devdoc>
/// <para>
/// Nothing has been written yet.
/// </para>
/// </devdoc>
Start,
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="WriteState.Prolog"]/*' />
/// <devdoc>
/// <para>
/// Writing the prolog.
/// </para>
/// </devdoc>
Prolog,
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="WriteState.Element"]/*' />
/// <devdoc>
/// <para>
/// Writing a the start tag for an element.
/// </para>
/// </devdoc>
Element,
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="WriteState.Attribute"]/*' />
/// <devdoc>
/// <para>
/// Writing an attribute value.
/// </para>
/// </devdoc>
Attribute,
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="WriteState.Content"]/*' />
/// <devdoc>
/// <para>
/// Writing element content.
/// </para>
/// </devdoc>
Content,
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="WriteState.Closed"]/*' />
/// <devdoc>
/// <para>
/// Close has been called.
/// </para>
/// </devdoc>
Closed,
};
// Abstract base class.
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter"]/*' />
/// <devdoc>
/// <para>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.</para>
/// <para>This class is <see langword='virtual'/> .</para>
/// </devdoc>
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;
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.WriteStartDocument"]/*' />
/// <devdoc>
/// <para>Writes out the XML declaration with the version "1.0".</para>
/// </devdoc>
public virtual void WriteStartDocument()
{
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.WriteStartDocument1"]/*' />
/// <devdoc>
/// <para>Writes out the XML declaration with the version "1.0" and the
/// standalone attribute.</para>
/// </devdoc>
public virtual void WriteStartDocument(bool standalone)
{
StartDocument(-1);
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.WriteEndDocument"]/*' />
/// <devdoc>
/// Closes any open elements or attributes and
/// puts the writer back in the Start state.
/// </devdoc>
public virtual void WriteEndDocument()
{
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.WriteStartElement"]/*' />
/// <devdoc>
/// <para>Writes out the specified start tag and associates it with the
/// given namespace.</para>
/// </devdoc>
public void WriteStartElement(string localName, string ns) {
WriteStartElement(null, localName, ns);
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.WriteStartElement1"]/*' />
/// <devdoc>
/// <para>Writes out the specified start tag and
/// associates it with the given namespace and prefix.</para>
/// </devdoc>
public virtual void WriteStartElement(string prefix, string localName, string ns)
{
AutoComplete(Token.StartElement);
PushStack();
textWriter.Write('<');
stack[top].name = localName;
textWriter.Write(localName);
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.WriteStartElement2"]/*' />
/// <devdoc>
/// <para>Writes out a start tag with the specified name.</para>
/// </devdoc>
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);
}
// <include file='doc\XmlTextWriter.uex' path='docs/doc[@for="XmlTextWriter.WriteStartAttribute"]/*' />
/// <devdoc>
/// <para>Writes the start of an attribute.</para>
/// </devdoc>
public virtual void WriteStartAttribute(string prefix, string localName, string ns)
{
AutoComplete(Token.StartAttribute);
textWriter.Write(localName);
textWriter.Write('=');
textWriter.Write(this.quoteChar);
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.WriteEndElement"]/*' />
/// <devdoc>
/// <para>Closes one element and pops the corresponding namespace scope.</para>
/// </devdoc>
public virtual void WriteEndElement()
{
InternalWriteEndElement(false);
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.WriteFullEndElement"]/*' />
/// <devdoc>
/// <para>Closes one element and pops the
/// corresponding namespace scope.</para>
/// </devdoc>
public virtual void WriteFullEndElement()
{
InternalWriteEndElement(true);
}
// <include file='doc\XmlTextWriter.uex' path='docs/doc[@for="XmlTextWriter.Close"]/*' />
/// <devdoc>
/// <para>Close this stream and the underlying stream.</para>
/// </devdoc>
public virtual void Close()
{
try {
AutoCompleteAll();
}
catch (Exception) { } // never fail
Flush();
textWriter.Close();
this.currentState = State.Closed;
}
// <include file='doc\XmlWriter.uex' path='docs/doc[@for="XmlWriter.Flush"]/*' />
/// <devdoc>
/// <para>Flush whatever is in the buffer to the underlying streams and flush the
/// underlying stream.</para>
/// </devdoc>
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("<?");
textWriter.Write(name);
textWriter.Write(' ');
if (null != text) {
textWriter.Write(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;
}
}
}