| File: System\Configuration\XmlUtilWriter.cs | Web Access |
| Project: src\runtime\src\libraries\System.Configuration.ConfigurationManager\src\System.Configuration.ConfigurationManager.csproj (System.Configuration.ConfigurationManager) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using System.IO; using System.Xml; namespace System.Configuration { // 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 sealed class XmlUtilWriter { private const char Space = ' '; private const string NewLine = "\r\n"; private static readonly string s_spaces8 = new string(Space, 8); private static readonly string s_spaces4 = new string(Space, 4); private static readonly string s_spaces2 = new string(Space, 2); private readonly Stream _baseStream; // stream under TextWriter when tracking position private object _lineStartCheckpoint; // checkpoint taken at the start of each line 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; } internal bool TrackPosition { get; } internal int LineNumber { get; private set; } internal int LinePosition { get; private set; } internal bool IsLastLineBlank { get; private set; } // 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); 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); 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; } } 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"); 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; } 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; } // Write spaces up to the line position, taking into account the // current line position of the writer. internal int AppendSpacesToLinePosition(int linePosition) { if (linePosition <= 0) return 0; int delta = linePosition - LinePosition; if ((delta < 0) && IsLastLineBlank) SeekToLineStart(); return AppendSpaces(linePosition - LinePosition); } internal int AppendNewLine() { return Write(NewLine); } // 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(s_spaces8); c -= 8; } else { if (c >= 4) { Write(s_spaces4); c -= 4; } else { if (c >= 2) { Write(s_spaces2); c -= 2; } else { Write(Space); break; } } } return count > 0 ? count : 0; } internal int AppendSpace() { return Write(Space); } // Reset the stream to the beginning of the current blank line. internal void SeekToLineStart() { 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 sealed class StreamWriterCheckpoint { internal readonly bool _isLastLineBlank; internal readonly int _lineNumber; internal readonly int _linePosition; internal readonly long _streamLength; internal readonly long _streamPosition; 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; } } } }