|
//------------------------------------------------------------------------------
// <copyright file="XmlUtil.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.Diagnostics.CodeAnalysis;
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;
//
// XmlTextReader Helper class.
//
// Provides the following services:
//
// * Reader methods that verify restrictions on the XML that can be contained in a config file.
// * Methods to copy the reader stream to a writer stream.
// * Method to copy a configuration section to a string.
// * Methods to format a string of XML.
//
// Errors found during read are accumlated in a ConfigurationSchemaErrors object.
//
internal sealed class XmlUtil : IDisposable, IConfigErrorInfo
{
private const int MAX_LINE_WIDTH=60;
// Offset from where the reader reports the LinePosition of an Xml Node to
// the start of that representation in text.
static readonly int[] s_positionOffset = {
0, // None,
1, // Element, <elem
-1, // Attribute, N/A
0, // Text,
9, // CDATA, <![CDATA[
1, // EntityReference, <
-1, // Entity, N/A
2, // ProcessingInstruction, <?pi
4, // Comment, <!--
-1, // Document, N/A
10, // DocumentType, <!DOCTYPE
-1, // DocumentFragment, N/A
-1, // Notation, N/A
0, // Whitespace,
0, // SignificantWhitespace,
2, // EndElement, />
-1, // EndEntity, N/A
2, // XmlDeclaration <?xml
};
private static int GetPositionOffset(XmlNodeType nodeType) {
return s_positionOffset[(int)nodeType];
}
private Stream _stream; // the stream to read
private string _streamName; // name of the stream, typically a file name
private XmlTextReader _reader; // the XmlTextReader over the stream
private StringWriter _cachedStringWriter; // cached string writer used by CopySection()
private ConfigurationSchemaErrors _schemaErrors; // accumulated errors
private int _lastLineNumber; // last line number after a call to CopyXmlNode()
private int _lastLinePosition; // last line position after a call to CopyXmlNode()
internal XmlUtil(Stream stream, string name, bool readToFirstElement) :
this(stream, name, readToFirstElement, new ConfigurationSchemaErrors()) {}
[SuppressMessage("Microsoft.Security.Xml", "CA3054:DoNotAllowDtdOnXmlTextReader", Justification="Reading trusted input")]
internal XmlUtil(Stream stream, string name, bool readToFirstElement, ConfigurationSchemaErrors schemaErrors) {
try {
_streamName = name;
_stream = stream;
_reader = new XmlTextReader(_stream);
// config reads never require a resolver
_reader.XmlResolver = null;
_schemaErrors = schemaErrors;
_lastLineNumber = 1;
_lastLinePosition = 1;
//
// When parsing config that we don't intend to copy, skip all content
// before the first element.
//
if (readToFirstElement) {
_reader.WhitespaceHandling = WhitespaceHandling.None;
bool done = false;
while (!done && _reader.Read()) {
switch (_reader.NodeType) {
case XmlNodeType.XmlDeclaration:
case XmlNodeType.Comment:
case XmlNodeType.DocumentType:
break;
case XmlNodeType.Element:
done = true;
break;
default:
throw new ConfigurationErrorsException(SR.GetString(SR.Config_base_unrecognized_element), this);
}
}
}
}
catch {
ReleaseResources();
throw;
}
}
private void ReleaseResources() {
if (_reader != null) {
// closing _reader will also close underlying _stream
_reader.Close();
_reader = null;
}
else if (_stream != null) {
_stream.Close();
}
_stream = null;
if (_cachedStringWriter != null) {
_cachedStringWriter.Close();
_cachedStringWriter = null;
}
}
public void Dispose() {
ReleaseResources();
}
public string Filename {
get { return _streamName; }
}
public int LineNumber {
get { return Reader.LineNumber; }
}
//
// Return the line position of the reader, compensating for the reader's offset
// for nodes such as an XmlElement.
//
internal int TrueLinePosition {
get {
int trueLinePosition = Reader.LinePosition - GetPositionOffset(Reader.NodeType);
Debug.Assert(trueLinePosition > 0, "trueLinePosition > 0");
return trueLinePosition;
}
}
internal XmlTextReader Reader {
get { return _reader; }
}
internal ConfigurationSchemaErrors SchemaErrors {
get { return _schemaErrors; }
}
//
// Read until the Next Element element, or we hit
// the end of the file.
//
internal void ReadToNextElement() {
while (_reader.Read()) {
if (_reader.MoveToContent() == XmlNodeType.Element) {
// We found an element, so return
return;
}
}
// We must of hit end of file
}
//
// Skip this element and its children, then read to next start element,
// or until we hit end of file.
//
internal void SkipToNextElement() {
_reader.Skip();
_reader.MoveToContent();
while (!_reader.EOF && _reader.NodeType != XmlNodeType.Element) {
_reader.Read();
_reader.MoveToContent();
}
}
//
// Read to the next start element, and verify that all XML nodes read are permissible.
//
internal void StrictReadToNextElement(ExceptionAction action) {
while (_reader.Read()) {
// optimize for the common case
if (_reader.NodeType == XmlNodeType.Element) {
return;
}
VerifyIgnorableNodeType(action);
}
}
//
// Skip this element and its children, then read to next start element,
// or until we hit end of file. Verify that nodes that are read after the
// skipped element are permissible.
//
internal void StrictSkipToNextElement(ExceptionAction action) {
_reader.Skip();
while (!_reader.EOF && _reader.NodeType != XmlNodeType.Element) {
VerifyIgnorableNodeType(action);
_reader.Read();
}
}
//
// StrictSkipToOurParentsEndElement
//
// Skip until we hit the end element for our parent, and verify
// that nodes at the parent level are permissible.
//
internal void StrictSkipToOurParentsEndElement(ExceptionAction action) {
int currentDepth = _reader.Depth;
// Skip everything at out current level
while (_reader.Depth >= currentDepth) {
_reader.Skip();
}
while (!_reader.EOF && _reader.NodeType != XmlNodeType.EndElement) {
VerifyIgnorableNodeType(action);
_reader.Read();
}
}
//
// Add an error if the node type is not permitted by the configuration schema.
//
internal void VerifyIgnorableNodeType(ExceptionAction action) {
XmlNodeType nodeType = _reader.NodeType;
if (nodeType != XmlNodeType.Comment && nodeType != XmlNodeType.EndElement) {
ConfigurationException ex = new ConfigurationErrorsException(
SR.GetString(SR.Config_base_unrecognized_element),
this);
SchemaErrors.AddError(ex, action);
}
}
//
// Add an error if there are attributes that have not been examined,
// and are therefore unrecognized.
//
internal void VerifyNoUnrecognizedAttributes(ExceptionAction action) {
if (_reader.MoveToNextAttribute()) {
AddErrorUnrecognizedAttribute(action);
}
}
//
// Add an error if the retrieved attribute is null,
// and therefore not present.
//
internal bool VerifyRequiredAttribute(
object o, string attrName, ExceptionAction action) {
if (o == null) {
AddErrorRequiredAttribute(attrName, action);
return false;
}
else {
return true;
}
}
//
// Functions to handle parsing errors
//
internal void AddErrorUnrecognizedAttribute(ExceptionAction action) {
ConfigurationErrorsException ex = new ConfigurationErrorsException(
SR.GetString(SR.Config_base_unrecognized_attribute, _reader.Name),
this);
SchemaErrors.AddError(ex, action);
}
internal void AddErrorRequiredAttribute(string attrib, ExceptionAction action) {
ConfigurationErrorsException ex = new ConfigurationErrorsException(
SR.GetString(SR.Config_missing_required_attribute, attrib, _reader.Name),
this);
SchemaErrors.AddError(ex, action);
}
internal void AddErrorReservedAttribute(ExceptionAction action) {
ConfigurationErrorsException ex = new ConfigurationErrorsException(
SR.GetString(SR.Config_reserved_attribute, _reader.Name),
this);
SchemaErrors.AddError(ex, action);
}
internal void AddErrorUnrecognizedElement(ExceptionAction action) {
ConfigurationErrorsException ex = new ConfigurationErrorsException(
SR.GetString(SR.Config_base_unrecognized_element),
this);
SchemaErrors.AddError(ex, action);
}
internal void VerifyAndGetNonEmptyStringAttribute(ExceptionAction action, out string newValue) {
if (!String.IsNullOrEmpty(_reader.Value)) {
newValue = _reader.Value;
}
else {
newValue = null;
ConfigurationException ex = new ConfigurationErrorsException(
SR.GetString(SR.Empty_attribute, _reader.Name),
this);
SchemaErrors.AddError(ex, action);
}
}
// VerifyAndGetBooleanAttribute
//
// Verify and Retrieve the Boolean Attribute. If it is not
// a valid value then log an error and set the value to a given default.
//
internal void VerifyAndGetBooleanAttribute(
ExceptionAction action, bool defaultValue, out bool newValue) {
if (_reader.Value == "true") {
newValue = true;
}
else if (_reader.Value == "false") {
newValue = false;
}
else {
// Unrecognized value
newValue = defaultValue;
ConfigurationErrorsException ex = new ConfigurationErrorsException(
SR.GetString(SR.Config_invalid_boolean_attribute, _reader.Name),
this);
SchemaErrors.AddError(ex, action);
}
}
//
// Copy an XML element, then continue copying until we've hit the next element
// or exited this depth.
//
internal bool CopyOuterXmlToNextElement(XmlUtilWriter utilWriter, bool limitDepth) {
CopyElement(utilWriter);
// Copy until reaching the next element, or if limitDepth == true until we've exited this depth.
return CopyReaderToNextElement(utilWriter, limitDepth);
}
//
// Copy an XML element but skip all its child elements, then continue copying until we've hit the next element.
//
internal bool SkipChildElementsAndCopyOuterXmlToNextElement(XmlUtilWriter utilWriter) {
bool isEmptyElement = _reader.IsEmptyElement;
int startingLine = _reader.LineNumber;
#if DBG
int depth = _reader.Depth;
#endif
Debug.Assert(_reader.NodeType == XmlNodeType.Element, "_reader.NodeType == XmlNodeType.Element");
CopyXmlNode(utilWriter);
// See if we need to skip any child element
if (!isEmptyElement) {
while (_reader.NodeType != XmlNodeType.EndElement) {
// Skip all the inner child elements
if (_reader.NodeType == XmlNodeType.Element) {
_reader.Skip();
// We need to skip all the whitespaces following a skipped element.
// - If the whitespaces don't contain /r/n, then it's okay to skip them
// as part of the element.
// - If the whitespaces contain /r/n, not skipping them will result
// in a redundant emtpy line being copied.
if (_reader.NodeType == XmlNodeType.Whitespace) {
_reader.Skip();
}
}
else {
// We want to preserve other content, e.g. comments.
CopyXmlNode(utilWriter);
}
}
if (_reader.LineNumber != startingLine) {
// The whitespace in front of the EndElement was skipped above.
// We need to append spaces to compensate for that.
utilWriter.AppendSpacesToLinePosition(TrueLinePosition);
}
#if DBG
Debug.Assert(_reader.Depth == depth, "We should be at the same depth as the opening Element");
#endif
// Copy the end element.
CopyXmlNode(utilWriter);
}
return CopyReaderToNextElement(utilWriter, true);
}
//
// Copy the reader until we hit an element, or we've exited the current depth.
//
internal bool CopyReaderToNextElement(XmlUtilWriter utilWriter, bool limitDepth) {
bool moreToRead = true;
// Set the depth if we limit copying to this depth
int depth;
if (limitDepth) {
// there is nothing in the element
if (_reader.NodeType == XmlNodeType.EndElement)
return true;
depth = _reader.Depth;
}
else {
depth = 0;
}
// Copy nodes until we've reached the desired depth, or until we hit an element.
do {
if (_reader.NodeType == XmlNodeType.Element)
break;
if (_reader.Depth < depth) {
break;
}
moreToRead = CopyXmlNode(utilWriter);
} while (moreToRead);
return moreToRead;
}
//
// Skip over the current element and copy until the next element.
// This function removes the one blank line that would otherwise
// be inserted by simply skipping and copying to the next element
// in a situation like this:
//
// <!-- end of previous configSection -->
// <configSectionToDelete>
// <content />
// <moreContent />
// </configSectionToDelete>
// <!-- end of configSectionToDelete -->
// <nextConfigSection />
//
internal bool SkipAndCopyReaderToNextElement(XmlUtilWriter utilWriter, bool limitDepth) {
Debug.Assert(_reader.NodeType == XmlNodeType.Element, "_reader.NodeType == XmlNodeType.Element");
// If the last line before the element is not blank, then we do not have to
// remove the blank line.
if (!utilWriter.IsLastLineBlank) {
_reader.Skip();
return CopyReaderToNextElement(utilWriter, limitDepth);
}
// Set the depth if we limit copying to this depth
int depth;
if (limitDepth) {
depth = _reader.Depth;
}
else {
depth = 0;
}
// Skip over the element
_reader.Skip();
int lineNumberOfEndElement = _reader.LineNumber;
// Read until we hit a a non-whitespace node or reach the end
while (!_reader.EOF) {
if (_reader.NodeType != XmlNodeType.Whitespace) {
//
// If the next non-whitepace node is on another line,
// seek back to the beginning of the current blank line,
// skip a blank line of whitespace, and copy the remaining whitespace.
//
if (_reader.LineNumber > lineNumberOfEndElement) {
utilWriter.SeekToLineStart();
utilWriter.AppendWhiteSpace(lineNumberOfEndElement + 1, 1, LineNumber, TrueLinePosition);
}
break;
}
_reader.Read();
}
// Copy nodes until we've reached the desired depth, or until we hit an element.
while (!_reader.EOF) {
if (_reader.NodeType == XmlNodeType.Element)
break;
if (_reader.Depth < depth) {
break;
}
CopyXmlNode(utilWriter);
};
return !_reader.EOF;
}
//
// Copy an XML element and its children, up to and including the end element.
//
private void CopyElement(XmlUtilWriter utilWriter) {
Debug.Assert(_reader.NodeType== XmlNodeType.Element, "_reader.NodeType== XmlNodeType.Element");
int depth = _reader.Depth;
bool isEmptyElement = _reader.IsEmptyElement;
// Copy current node
CopyXmlNode(utilWriter);
// Copy nodes while the depth is greater than the current depth.
while (_reader.Depth > depth) {
CopyXmlNode(utilWriter);
}
// Copy the end element.
if (!isEmptyElement) {
CopyXmlNode(utilWriter);
}
}
//
// Copy a single XML node, attempting to preserve whitespace.
// A side effect of this method is to advance the reader to the next node.
//
// PERFORMANCE NOTE: this function is used at runtime to copy a configuration section,
// and at designtime to copy an entire XML document.
//
// At designtime, this function needs to be able to copy a <!DOCTYPE declaration.
// Copying a <!DOCTYPE declaration is expensive, because due to limitations of the
// XmlReader API, we must track the position of the writer to accurately format it.
// Tracking the position of the writer is expensive, as it requires examining every
// character that is written for newline characters, and maintaining the seek position
// of the underlying stream at each new line, which in turn requires a stream flush.
//
// This function must NEVER require tracking the writer position to copy the Xml nodes
// that are used in a configuration section.
//
internal bool CopyXmlNode(XmlUtilWriter utilWriter) {
//
// For nodes that have a closing string, such as "<element >"
// the XmlReader API does not give us the location of the closing string, e.g. ">".
// To correctly determine the location of the closing part, we advance the reader,
// determine the position of the next node, then work backwards to add whitespace
// and add the closing string.
//
string close = null;
int lineNumber = -1;
int linePosition = -1;
int readerLineNumber = 0;
int readerLinePosition = 0;
int writerLineNumber = 0;
int writerLinePosition = 0;
if (utilWriter.TrackPosition) {
readerLineNumber = _reader.LineNumber;
readerLinePosition = _reader.LinePosition;
writerLineNumber = utilWriter.LineNumber;
writerLinePosition = utilWriter.LinePosition;
}
// We test the node type in the likely order of decreasing occurrence.
XmlNodeType nodeType = _reader.NodeType;
if (nodeType == XmlNodeType.Whitespace) {
utilWriter.Write(_reader.Value);
}
else if (nodeType == XmlNodeType.Element) {
close = (_reader.IsEmptyElement) ? "/>" : ">";
// get the line position after the element declaration:
// <element attr="value"
// ^
// linePosition
//
lineNumber = _reader.LineNumber;
linePosition = _reader.LinePosition + _reader.Name.Length;
utilWriter.Write('<');
utilWriter.Write(_reader.Name);
//
// Note that there is no way to get spacing between attribute name and value
// For example:
//
// <elem attr="value" />
//
// is reported with the same position as
//
// <elem attr = "value" />
//
// The first example has no spaces around '=', the second example does.
//
while (_reader.MoveToNextAttribute()) {
// get line position of the attribute declaration
// <element attr="value"
// ^
// attrLinePosition
//
int attrLineNumber = _reader.LineNumber;
int attrLinePosition = _reader.LinePosition;
// Write the whitespace before the attribute
utilWriter.AppendRequiredWhiteSpace(lineNumber, linePosition, attrLineNumber, attrLinePosition);
// Write the attribute and value
int charactersWritten = utilWriter.Write(_reader.Name);
charactersWritten += utilWriter.Write('=');
charactersWritten += utilWriter.AppendAttributeValue(_reader);
// Update position. Note that the attribute value is escaped to always be on a single line.
lineNumber = attrLineNumber;
linePosition = attrLinePosition + charactersWritten;
}
}
else if (nodeType == XmlNodeType.EndElement) {
close = ">";
// get line position after the end element declaration:
// </element >
// ^
// linePosition
//
lineNumber = _reader.LineNumber;
linePosition = _reader.LinePosition + _reader.Name.Length;
utilWriter.Write("</");
utilWriter.Write(_reader.Name);
}
else if (nodeType == XmlNodeType.Comment) {
utilWriter.AppendComment(_reader.Value);
}
else if (nodeType == XmlNodeType.Text) {
utilWriter.AppendEscapeTextString(_reader.Value);
}
else if (nodeType == XmlNodeType.XmlDeclaration) {
close = "?>";
// get line position after the xml declaration:
// <?xml version="1.0"
// ^
// linePosition
//
lineNumber = _reader.LineNumber;
linePosition = _reader.LinePosition + 3;
utilWriter.Write("<?xml");
//
// Note that there is no way to get spacing between attribute name and value
// For example:
//
// <?xml attr="value" ?>
//
// is reported with the same position as
//
// <?xml attr = "value" ?>
//
// The first example has no spaces around '=', the second example does.
//
while (_reader.MoveToNextAttribute()) {
// get line position of the attribute declaration
// <?xml version="1.0"
// ^
// attrLinePosition
//
int attrLineNumber = _reader.LineNumber;
int attrLinePosition = _reader.LinePosition;
// Write the whitespace before the attribute
utilWriter.AppendRequiredWhiteSpace(lineNumber, linePosition, attrLineNumber, attrLinePosition);
// Write the attribute and value
int charactersWritten = utilWriter.Write(_reader.Name);
charactersWritten += utilWriter.Write('=');
charactersWritten += utilWriter.AppendAttributeValue(_reader);
// Update position. Note that the attribute value is escaped to always be on a single line.
lineNumber = attrLineNumber;
linePosition = attrLinePosition + charactersWritten;
}
// Position reader at beginning of node
_reader.MoveToElement();
}
else if (nodeType == XmlNodeType.SignificantWhitespace) {
utilWriter.Write(_reader.Value);
}
else if (nodeType == XmlNodeType.ProcessingInstruction) {
//
// Note that there is no way to get spacing between attribute name and value
// For example:
//
// <?pi "value" ?>
//
// is reported with the same position as
//
// <?pi "value" ?>
//
// The first example has one space between 'pi' and "value", the second has multiple spaces.
//
utilWriter.AppendProcessingInstruction(_reader.Name, _reader.Value);
}
else if (nodeType == XmlNodeType.EntityReference) {
utilWriter.AppendEntityRef(_reader.Name);
}
else if (nodeType == XmlNodeType.CDATA) {
utilWriter.AppendCData(_reader.Value);
}
else if (nodeType == XmlNodeType.DocumentType) {
//
// XmlNodeType.DocumentType has the following format:
//
// <!DOCTYPE rootElementName {(SYSTEM uriRef)|(PUBLIC id uriRef)} {[ dtdDecls ]} >
//
// The reader only gives us the position of 'rootElementName', so we must track what was
// written before "<!DOCTYPE" in order to correctly determine the position of the
// <!DOCTYPE tag
//
Debug.Assert(utilWriter.TrackPosition, "utilWriter.TrackPosition");
int c = utilWriter.Write("<!DOCTYPE");
// Write the space between <!DOCTYPE and the rootElementName
utilWriter.AppendRequiredWhiteSpace(_lastLineNumber, _lastLinePosition + c, _reader.LineNumber, _reader.LinePosition);
// Write the rootElementName
utilWriter.Write(_reader.Name);
// Get the dtd declarations, if any
string dtdValue = null;
if (_reader.HasValue) {
dtdValue = _reader.Value;
}
// get line position after the !DOCTYPE declaration:
// <!DOCTYPE rootElement SYSTEM rootElementDtdUri >
// ^
// linePosition
lineNumber = _reader.LineNumber;
linePosition = _reader.LinePosition + _reader.Name.Length;
// Note that there is no way to get the spacing after PUBLIC or SYSTEM attributes and their values
if (_reader.MoveToFirstAttribute()) {
// Write the space before SYSTEM or PUBLIC
utilWriter.AppendRequiredWhiteSpace(lineNumber, linePosition, _reader.LineNumber, _reader.LinePosition);
// Write SYSTEM or PUBLIC and the 1st value of the attribute
string attrName = _reader.Name;
utilWriter.Write(attrName);
utilWriter.AppendSpace();
utilWriter.AppendAttributeValue(_reader);
_reader.MoveToAttribute(0);
// If PUBLIC, write the second value of the attribute
if (attrName == "PUBLIC") {
_reader.MoveToAttribute(1);
utilWriter.AppendSpace();
utilWriter.AppendAttributeValue(_reader);
_reader.MoveToAttribute(1);
}
}
// If there is a dtd, write it
if (dtdValue != null && dtdValue.Length > 0) {
utilWriter.Write(" [");
utilWriter.Write(dtdValue);
utilWriter.Write(']');
}
utilWriter.Write('>');
}
// Advance the _reader so we can get the position of the next node.
bool moreToRead = _reader.Read();
nodeType = _reader.NodeType;
// Close the node we are copying.
if (close != null) {
//
// Find the position of the close string, for example:
//
// <element > <subElement />
// ^
// closeLinePosition
//
int startOffset = GetPositionOffset(nodeType);
int closeLineNumber = _reader.LineNumber;
int closeLinePosition = _reader.LinePosition - startOffset - close.Length;
// Add whitespace up to the position of the close string
utilWriter.AppendWhiteSpace(lineNumber, linePosition, closeLineNumber, closeLinePosition);
// Write the close string
utilWriter.Write(close);
}
//
// Track the position of the reader based on the position of the reader
// before we copied this node and what we have written in copying the node.
// This allows us to determine the position of the <!DOCTYPE tag.
//
if (utilWriter.TrackPosition) {
_lastLineNumber = (readerLineNumber - writerLineNumber) + utilWriter.LineNumber;
if (writerLineNumber == utilWriter.LineNumber) {
_lastLinePosition = (readerLinePosition - writerLinePosition) + utilWriter.LinePosition;
}
else {
_lastLinePosition = utilWriter.LinePosition;
}
}
return moreToRead;
}
// RetrieveFullOpenElementTag
//
// Asuming that we are at an element, retrieve the text for that element
// and attributes that can be serialized to an xml file.
//
private string RetrieveFullOpenElementTag() {
StringBuilder element;
Debug.Assert(_reader.NodeType == XmlNodeType.Element,
"_reader.NodeType == NodeType.Element");
// Start with element tag name
element = new StringBuilder(64);
element.Append("<");
element.Append(_reader.Name);
// Add attributes
while (_reader.MoveToNextAttribute()) {
element.Append(" ");
element.Append(_reader.Name);
element.Append("=");
element.Append('\"');
element.Append(_reader.Value);
element.Append('\"');
}
// Now close the element tag
element.Append(">");
return element.ToString();
}
//
// Copy or replace an element node.
// If the element is an empty element, replace it with a formatted start element if either:
// * The contents of the start element string need updating.
// * The element needs to contain child elements.
//
// If the element is empty and is replaced with a start/end element pair, return a
// end element string with whitespace formatting; otherwise return null.
//
internal string UpdateStartElement(XmlUtilWriter utilWriter, string updatedStartElement, bool needsChildren, int linePosition, int indent) {
Debug.Assert(_reader.NodeType == XmlNodeType.Element, "_reader.NodeType == NodeType.Element");
string endElement = null;
bool needsEndElement = false;
string elementName;
elementName = _reader.Name;
// If the element is empty, determine if a new end element is needed.
if (_reader.IsEmptyElement) {
if (updatedStartElement == null && needsChildren) {
updatedStartElement = RetrieveFullOpenElementTag();
}
needsEndElement = (updatedStartElement != null);
}
if (updatedStartElement == null) {
//
// If no changes to the start element are required, just copy it.
//
CopyXmlNode(utilWriter);
}
else {
//
// Format a new start element/end element pair
//
string updatedEndElement = "</" + elementName + ">";
string updatedElement = updatedStartElement + updatedEndElement;
string formattedElement = FormatXmlElement(updatedElement, linePosition, indent, true);
//
// Get the start and end element strings from the formatted element.
//
int iEndElement = formattedElement.LastIndexOf('\n') + 1;
string startElement;
if (needsEndElement) {
endElement = formattedElement.Substring(iEndElement);
// Include a newline in the start element as we are expanding an empty element.
startElement = formattedElement.Substring(0, iEndElement);
}
else {
// Omit the newline from the start element.
startElement = formattedElement.Substring(0, iEndElement - 2);
}
// Write the new start element.
utilWriter.Write(startElement);
// Skip over the existing start element.
_reader.Read();
}
return endElement;
}
//
// Create the cached string writer if it does not exist,
// otherwise reuse the existing buffer.
//
private void ResetCachedStringWriter() {
if (_cachedStringWriter == null) {
_cachedStringWriter = new StringWriter(new StringBuilder(64), CultureInfo.InvariantCulture);
}
else {
_cachedStringWriter.GetStringBuilder().Length = 0;
}
}
//
// Copy a configuration section to a string, and advance the reader.
//
internal string CopySection() {
ResetCachedStringWriter();
// Preserve whitespace for sections for backcompat
WhitespaceHandling originalHandling = _reader.WhitespaceHandling;
_reader.WhitespaceHandling = WhitespaceHandling.All;
// Create string writer to write to
XmlUtilWriter utilWriter = new XmlUtilWriter(_cachedStringWriter, false);
// Copy the element
CopyElement(utilWriter);
// Reset whitespace handling
_reader.WhitespaceHandling = originalHandling;
if ((originalHandling == WhitespaceHandling.None) &&
(Reader.NodeType == XmlNodeType.Whitespace)) {
// If we were previously suppose to skip whitespace, and now we
// are at it, then lets jump to the next item
_reader.Read();
}
utilWriter.Flush();
string s = ((StringWriter)utilWriter.Writer).ToString();
return s;
}
// Format an Xml element to be written to the config file.
// Params:
// xmlElement - the element
// linePosition - start position of the element
// indent - indent for each depth
// skipFirstIndent - skip indent for the first element?
//
[SuppressMessage("Microsoft.Security.Xml", "CA3054:DoNotAllowDtdOnXmlTextReader", Justification="Reading trusted input")]
static internal string FormatXmlElement(string xmlElement, int linePosition, int indent, bool skipFirstIndent) {
XmlParserContext context = new XmlParserContext(null, null, null, XmlSpace.Default, Encoding.Unicode);
XmlTextReader reader = new XmlTextReader(xmlElement, XmlNodeType.Element, context);
StringWriter stringWriter = new StringWriter(new StringBuilder(64), CultureInfo.InvariantCulture);
XmlUtilWriter utilWriter = new XmlUtilWriter(stringWriter, false);
// append newline before indent?
bool newLine = false;
// last node visited was text?
bool lastWasText = false;
// width of line from end of indentation
int lineWidth;
// length of the stringbuilder after last indent with newline
int sbLengthLastNewLine = 0;
while (reader.Read()) {
XmlNodeType nodeType = reader.NodeType;
if (lastWasText) {
utilWriter.Flush();
lineWidth = sbLengthLastNewLine - ((StringWriter)utilWriter.Writer).GetStringBuilder().Length;
}
else {
lineWidth = 0;
}
switch (nodeType) {
case XmlNodeType.CDATA:
case XmlNodeType.Element:
case XmlNodeType.EndElement:
case XmlNodeType.Comment:
// Do not indent if the last node was text - doing so would add whitespace
// that is included as part of the text.
if (!skipFirstIndent && !lastWasText) {
utilWriter.AppendIndent(linePosition, indent, reader.Depth, newLine);
if (newLine) {
utilWriter.Flush();
sbLengthLastNewLine = ((StringWriter)utilWriter.Writer).GetStringBuilder().Length;
}
}
break;
default:
break;
}
lastWasText = false;
switch (nodeType) {
case XmlNodeType.Whitespace:
break;
case XmlNodeType.SignificantWhitespace:
utilWriter.Write(reader.Value);
break;
case XmlNodeType.CDATA:
utilWriter.AppendCData(reader.Value);
break;
case XmlNodeType.ProcessingInstruction:
utilWriter.AppendProcessingInstruction(reader.Name, reader.Value);
break;
case XmlNodeType.Comment:
utilWriter.AppendComment(reader.Value);
break;
case XmlNodeType.Text:
utilWriter.AppendEscapeTextString(reader.Value);
lastWasText = true;
break;
case XmlNodeType.Element:
{
// Write "<elem"
utilWriter.Write('<');
utilWriter.Write(reader.Name);
lineWidth += reader.Name.Length + 2;
int c = reader.AttributeCount;
for (int i = 0; i < c; i++) {
// Add new line if we've exceeded the line width
bool writeSpace;
if (lineWidth > MAX_LINE_WIDTH) {
utilWriter.AppendIndent(linePosition, indent, reader.Depth - 1, true);
lineWidth = indent;
writeSpace = false;
utilWriter.Flush();
sbLengthLastNewLine = ((StringWriter)utilWriter.Writer).GetStringBuilder().Length;
}
else {
writeSpace = true;
}
// Write the attribute
reader.MoveToNextAttribute();
utilWriter.Flush();
int startLength = ((StringWriter)utilWriter.Writer).GetStringBuilder().Length;
if (writeSpace) {
utilWriter.AppendSpace();
}
utilWriter.Write(reader.Name);
utilWriter.Write('=');
utilWriter.AppendAttributeValue(reader);
utilWriter.Flush();
lineWidth += ((StringWriter)utilWriter.Writer).GetStringBuilder().Length - startLength;
}
}
// position reader back on element
reader.MoveToElement();
// write closing tag
if (reader.IsEmptyElement) {
utilWriter.Write(" />");
}
else {
utilWriter.Write('>');
}
break;
case XmlNodeType.EndElement:
utilWriter.Write("</");
utilWriter.Write(reader.Name);
utilWriter.Write('>');
break;
case XmlNodeType.EntityReference:
utilWriter.AppendEntityRef(reader.Name);
break;
// Ignore <?xml and <!DOCTYPE nodes
case XmlNodeType.XmlDeclaration:
case XmlNodeType.DocumentType:
default:
break;
}
// put each new element on a new line
newLine = true;
// do not skip any more indents
skipFirstIndent = false;
}
utilWriter.Flush();
string s = ((StringWriter)utilWriter.Writer).ToString();
return s;
}
}
}
|