592 lines
24 KiB
C#
592 lines
24 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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;
|
|
}
|
|
|
|
}
|
|
}
|
|
|