|
//------------------------------------------------------------------------------
// <copyright file="XmlUtilWriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Configuration {
using System.Configuration.Internal;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Xml;
using System.Net;
//
// A utility class for writing XML to a TextWriter.
//
// When this class is used to copy an XML document that may include a "<!DOCTYPE" directive,
// we must track what is written until the "<!DOCTYPE" or first document element is found.
// This is needed because the XML reader does not give us accurate spacing information
// for the beginning of the "<!DOCTYPE" element.
//
// Note that tracking this information is expensive, as it requires a scan of everything that is written
// until "<!DOCTYPE" or the first element is found.
//
// Note also that this class is used at runtime to copy sections, so performance of all
// writing functions directly affects application startup time.
//
internal class XmlUtilWriter {
private const char SPACE = ' ';
private const string NL = "\r\n";
private static string SPACES_8;
private static string SPACES_4;
private static string SPACES_2;
private TextWriter _writer; // the wrapped text writer
private Stream _baseStream; // stream under TextWriter when tracking position
private bool _trackPosition; // should write position be tracked?
private int _lineNumber; // line number
private int _linePosition; // line position
private bool _isLastLineBlank; // is the last line blank?
private object _lineStartCheckpoint; // checkpoint taken at the start of each line
static XmlUtilWriter() {
SPACES_8 = new String(SPACE, 8);
SPACES_4 = new String(SPACE, 4);
SPACES_2 = new String(SPACE, 2);
}
internal XmlUtilWriter(TextWriter writer, bool trackPosition) {
_writer = writer;
_trackPosition = trackPosition;
_lineNumber = 1;
_linePosition = 1;
_isLastLineBlank = true;
if (_trackPosition) {
_baseStream = ((StreamWriter)_writer).BaseStream;
_lineStartCheckpoint = CreateStreamCheckpoint();
}
}
internal TextWriter Writer {
get {
return _writer;
}
}
internal bool TrackPosition {
get {
return _trackPosition;
}
}
internal int LineNumber {
get {
return _lineNumber;
}
}
internal int LinePosition {
get {
return _linePosition;
}
}
internal bool IsLastLineBlank {
get {
return _isLastLineBlank;
}
}
//
// Update the position after the character is written to the stream.
//
private void UpdatePosition(char ch) {
switch (ch) {
case '\r':
_lineNumber++;
_linePosition = 1;
_isLastLineBlank = true;
break;
case '\n':
_lineStartCheckpoint = CreateStreamCheckpoint();
break;
case SPACE:
case '\t':
_linePosition++;
break;
default:
_linePosition++;
_isLastLineBlank = false;
break;
}
}
//
// Write a string to _writer.
// If we are tracking position, determine the line number and position
//
internal int Write(string s) {
if (_trackPosition) {
for (int i = 0; i < s.Length; i++) {
char ch = s[i];
_writer.Write(ch);
UpdatePosition(ch);
}
}
else {
_writer.Write(s);
}
#if DEBUG_WRITE
Flush();
#endif
return s.Length;
}
//
// Write a character to _writer.
// If we are tracking position, determine the line number and position
//
internal int Write(char ch) {
_writer.Write(ch);
if (_trackPosition) {
UpdatePosition(ch);
}
#if DEBUG_WRITE
Flush();
#endif
return 1;
}
internal void Flush() {
_writer.Flush();
}
// Escape a text string
internal int AppendEscapeTextString(string s) {
return AppendEscapeXmlString(s, false, 'A');
}
// Escape a XML string to preserve XML markup.
internal int AppendEscapeXmlString(string s, bool inAttribute, char quoteChar) {
int charactersWritten = 0;
for (int i = 0; i < s.Length; i++) {
char ch = s[i];
bool appendCharEntity = false;
string entityRef = null;
if ((ch < 32 && ch != '\t' && ch != '\r' && ch != '\n') || (ch > 0xFFFD)) {
appendCharEntity = true;
}
else {
switch (ch)
{
case '<':
entityRef = "lt";
break;
case '>':
entityRef = "gt";
break;
case '&':
entityRef = "amp";
break;
case '\'':
if (inAttribute && quoteChar == ch) {
entityRef = "apos";
}
break;
case '"':
if (inAttribute && quoteChar == ch) {
entityRef = "quot";
}
break;
case '\n':
case '\r':
appendCharEntity = inAttribute;
break;
default:
break;
}
}
if (appendCharEntity) {
charactersWritten += AppendCharEntity(ch);
}
else if (entityRef != null) {
charactersWritten += AppendEntityRef(entityRef);
}
else {
charactersWritten += Write(ch);
}
}
return charactersWritten;
}
internal int AppendEntityRef(string entityRef) {
Write('&');
Write(entityRef);
Write(';');
return entityRef.Length + 2;
}
internal int AppendCharEntity(char ch) {
string numberToWrite = ((int)ch).ToString("X", CultureInfo.InvariantCulture);
Write('&');
Write('#');
Write('x');
Write(numberToWrite);
Write(';');
return numberToWrite.Length + 4;
}
internal int AppendCData(string cdata) {
Write("<![CDATA[");
Write(cdata);
Write("]]>");
return cdata.Length + 12;
}
internal int AppendProcessingInstruction(string name, string value) {
Write("<?");
Write(name);
AppendSpace();
Write(value);
Write("?>");
return name.Length + value.Length + 5;
}
internal int AppendComment(string comment) {
Write("<!--");
Write(comment);
Write("-->");
return comment.Length + 7;
}
internal int AppendAttributeValue(XmlTextReader reader) {
int charactersWritten = 0;
char quote = reader.QuoteChar;
//
// In !DOCTYPE, quote is '\0' for second public attribute.
// Protect ourselves from writing invalid XML by always
// supplying a valid quote char.
//
if (quote != '"' && quote != '\'') {
quote = '"';
}
charactersWritten += Write(quote);
while (reader.ReadAttributeValue()) {
if (reader.NodeType == XmlNodeType.Text) {
charactersWritten += AppendEscapeXmlString(reader.Value, true, quote);
}
else {
charactersWritten += AppendEntityRef(reader.Name);
}
}
charactersWritten += Write(quote);
return charactersWritten;
}
// Append whitespace, ensuring there is at least one space.
internal int AppendRequiredWhiteSpace(int fromLineNumber, int fromLinePosition, int toLineNumber, int toLinePosition) {
int charactersWritten = AppendWhiteSpace(fromLineNumber, fromLinePosition, toLineNumber, toLinePosition);
if (charactersWritten == 0) {
charactersWritten += AppendSpace();
}
return charactersWritten;
}
// Append whitespce
internal int AppendWhiteSpace(int fromLineNumber, int fromLinePosition, int toLineNumber, int toLinePosition) {
int charactersWritten = 0;
while (fromLineNumber++ < toLineNumber) {
charactersWritten += AppendNewLine();
fromLinePosition = 1;
}
charactersWritten += AppendSpaces(toLinePosition - fromLinePosition);
return charactersWritten;
}
//
// Append indent
// linePosition - starting line position
// indent - number of spaces to indent each unit of depth
// depth - depth to indent
// newLine - insert new line before indent?
//
internal int AppendIndent(int linePosition, int indent, int depth, bool newLine) {
int charactersWritten = 0;
if (newLine) {
charactersWritten += AppendNewLine();
}
int c = (linePosition - 1) + (indent * depth);
charactersWritten += AppendSpaces(c);
return charactersWritten;
}
//
// AppendSpacesToLinePosition
//
// Write spaces up to the line position, taking into account the
// current line position of the writer.
//
internal int AppendSpacesToLinePosition(int linePosition) {
Debug.Assert(_trackPosition, "_trackPosition");
if (linePosition <= 0) {
return 0;
}
int delta = linePosition - _linePosition;
if (delta < 0 && IsLastLineBlank) {
SeekToLineStart();
}
return AppendSpaces(linePosition - _linePosition);
}
//
// Append a new line
//
internal int AppendNewLine() {
return Write(NL);
}
//
// AppendSpaces
//
// Write spaces to the writer provided. Since we do not want waste
// memory by allocating do not use "new String(' ', count)".
//
internal int AppendSpaces(int count) {
int c = count;
while (c > 0) {
if (c >= 8) {
Write(SPACES_8);
c -= 8;
}
else if (c >= 4) {
Write(SPACES_4);
c -= 4;
}
else if (c >= 2) {
Write(SPACES_2);
c -= 2;
}
else {
Write(SPACE);
break;
}
}
return (count > 0) ? count : 0;
}
//
// Append a single space character
//
internal int AppendSpace() {
return Write(SPACE);
}
//
// Reset the stream to the beginning of the current blank line.
//
internal void SeekToLineStart() {
Debug.Assert(_isLastLineBlank, "_isLastLineBlank");
RestoreStreamCheckpoint(_lineStartCheckpoint);
}
// Create a checkpoint that can be restored with RestoreStreamCheckpoint().
internal object CreateStreamCheckpoint() {
return new StreamWriterCheckpoint(this);
}
// Restore the writer state that was recorded with CreateStreamCheckpoint().
internal void RestoreStreamCheckpoint(object o) {
StreamWriterCheckpoint checkpoint = (StreamWriterCheckpoint) o;
Flush();
_lineNumber = checkpoint._lineNumber;
_linePosition = checkpoint._linePosition;
_isLastLineBlank = checkpoint._isLastLineBlank;
_baseStream.Seek(checkpoint._streamPosition, SeekOrigin.Begin);
_baseStream.SetLength(checkpoint._streamLength);
_baseStream.Flush();
}
// Class that contains the state of the writer and its underlying stream.
private class StreamWriterCheckpoint {
internal int _lineNumber; // line number
internal int _linePosition; // line position
internal bool _isLastLineBlank; // is the last line blank?
internal long _streamLength; // length of the stream
internal long _streamPosition; // position of the stream pointer
internal StreamWriterCheckpoint(XmlUtilWriter writer) {
writer.Flush();
_lineNumber = writer._lineNumber;
_linePosition = writer._linePosition;
_isLastLineBlank = writer._isLastLineBlank;
writer._baseStream.Flush();
_streamPosition = writer._baseStream.Position;
_streamLength = writer._baseStream.Length;
}
}
}
}
|