e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
460 lines
15 KiB
C#
460 lines
15 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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;
|
|
}
|
|
}
|
|
}
|
|
}
|