//
// 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;
}