| File: XmlAttributePreservingWriter.cs | Web Access |
| Project: src\xdt\src\Microsoft.Web.XmlTransform\Microsoft.Web.XmlTransform.csproj (Microsoft.Web.XmlTransform) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.IO; using System.Diagnostics; using System.Globalization; namespace Microsoft.Web.XmlTransform { internal class XmlAttributePreservingWriter : XmlWriter { private XmlTextWriter xmlWriter; private AttributeTextWriter textWriter; public XmlAttributePreservingWriter(string fileName, Encoding encoding) : this(encoding == null ? new StreamWriter(fileName) : new StreamWriter(fileName, false, encoding)) { } public XmlAttributePreservingWriter(Stream w, Encoding encoding) : this(encoding == null ? new StreamWriter(w) : new StreamWriter(w, encoding)) { } public XmlAttributePreservingWriter(TextWriter textWriter) { this.textWriter = new AttributeTextWriter(textWriter); this.xmlWriter = new XmlTextWriter(this.textWriter); } public void WriteAttributeWhitespace(string whitespace) { Debug.Assert(IsOnlyWhitespace(whitespace)); // Make sure we're in the right place to write // whitespace between attributes if (WriteState == WriteState.Attribute) { WriteEndAttribute(); } else if (WriteState != WriteState.Element) { throw new InvalidOperationException(); } // We don't write right away. We're going to wait until an // attribute is being written textWriter.AttributeLeadingWhitespace = whitespace; } public void WriteAttributeTrailingWhitespace(string whitespace) { Debug.Assert(IsOnlyWhitespace(whitespace)); if (WriteState == WriteState.Attribute) { WriteEndAttribute(); } else if (WriteState != WriteState.Element) { throw new InvalidOperationException(); } textWriter.Write(whitespace); } public string SetAttributeNewLineString(string newLineString) { string old = textWriter.AttributeNewLineString; if (newLineString == null && xmlWriter.Settings != null) { newLineString = xmlWriter.Settings.NewLineChars; } if (newLineString == null) { newLineString = "\r\n"; } textWriter.AttributeNewLineString = newLineString; return old; } private bool IsOnlyWhitespace(string whitespace) { foreach (char whitespaceCharacter in whitespace) { if (!Char.IsWhiteSpace(whitespaceCharacter)) { return false; } } return true; } #region SkippingTextWriter class private class AttributeTextWriter : TextWriter { enum State { Writing, WaitingForAttributeLeadingSpace, ReadingAttribute, Buffering, FlushingBuffer, WritingComment, } #region private data members State state = State.Writing; StringBuilder writeBuffer = null; private TextWriter baseWriter; string leadingWhitespace = null; int lineNumber = 1; int linePosition = 1; int maxLineLength = 160; string newLineString = "\r\n"; #endregion public AttributeTextWriter(TextWriter baseWriter) : base(CultureInfo.InvariantCulture) { this.baseWriter = baseWriter; } public string AttributeLeadingWhitespace { set { leadingWhitespace = value; } } public string AttributeNewLineString { get { return newLineString; } set { newLineString = value; } } public void StartAttribute() { Debug.Assert(state == State.Writing); ChangeState(State.WaitingForAttributeLeadingSpace); } public void EndAttribute() { Debug.Assert(state == State.ReadingAttribute); WriteQueuedAttribute(); } public void StartComment() { Debug.Assert(state == State.Writing || state == State.Buffering); ChangeState(State.WritingComment); } public void EndComment() { Debug.Assert(state == State.WritingComment); ChangeState(State.Writing); } public int MaxLineLength { get { return maxLineLength; } set { maxLineLength = value; } } public override void Write(char value) { UpdateState(value); switch (state) { case State.WaitingForAttributeLeadingSpace: if (value == ' ') { ChangeState(State.ReadingAttribute); break; } goto case State.Writing; case State.Writing: case State.WritingComment: case State.FlushingBuffer: ReallyWriteCharacter(value); break; case State.ReadingAttribute: case State.Buffering: writeBuffer.Append(value); break; } } private void UpdateState(char value) { if (state == State.WritingComment) { // We never want to do any formatting while writing a comment, // so don't leave this state. return; } // This logic prevents writing the leading space that // XmlTextWriter wants to put before "/>". switch (value) { case ' ': if (state == State.Writing) { ChangeState(State.Buffering); } break; case '/': break; case '>': if (state == State.Buffering) { string currentBuffer = writeBuffer.ToString(); if (currentBuffer.EndsWith(" /", StringComparison.Ordinal)) { // We've identified the string " />" at the // end of the buffer, so remove the space writeBuffer.Remove(currentBuffer.LastIndexOf(' '), 1); } ChangeState(State.Writing); } break; default: if (state == State.Buffering) { ChangeState(State.Writing); } break; } } private void ChangeState(State newState) { if (state != newState) { State oldState = state; state = newState; // Handle buffer management for different states if (StateRequiresBuffer(newState)) { CreateBuffer(); } else if (StateRequiresBuffer(oldState)) { FlushBuffer(); } } } private bool StateRequiresBuffer(State state) { return state == State.Buffering || state == State.ReadingAttribute; } private void CreateBuffer() { Debug.Assert(writeBuffer == null); if (writeBuffer == null) { writeBuffer = new StringBuilder(); } } private void FlushBuffer() { Debug.Assert(writeBuffer != null); if (writeBuffer != null) { State oldState = state; try { state = State.FlushingBuffer; Write(writeBuffer.ToString()); writeBuffer = null; } finally { state = oldState; } } } private void ReallyWriteCharacter(char value) { baseWriter.Write(value); if (value == '\n') { lineNumber++; linePosition = 1; } else { linePosition++; } } private void WriteQueuedAttribute() { // Write leading whitespace if (leadingWhitespace != null) { writeBuffer.Insert(0, leadingWhitespace); leadingWhitespace = null; } else { int lineLength = linePosition + writeBuffer.Length + 1; if (lineLength > MaxLineLength) { writeBuffer.Insert(0, AttributeNewLineString); } else { writeBuffer.Insert(0, ' '); } } // Flush the buffer and start writing characters again ChangeState(State.Writing); } public override Encoding Encoding { get { return baseWriter.Encoding; } } public override void Flush() { baseWriter.Flush(); } public override void Close() { baseWriter.Close(); } } #endregion #region XmlWriter implementation public override void Close() { xmlWriter.Close(); } public override void Flush() { xmlWriter.Flush(); } public override string LookupPrefix(string ns) { return xmlWriter.LookupPrefix(ns); } public override void WriteBase64(byte[] buffer, int index, int count) { xmlWriter.WriteBase64(buffer, index, count); } public override void WriteCData(string text) { xmlWriter.WriteCData(text); } public override void WriteCharEntity(char ch) { xmlWriter.WriteCharEntity(ch); } public override void WriteChars(char[] buffer, int index, int count) { xmlWriter.WriteChars(buffer, index, count); } public override void WriteComment(string text) { textWriter.StartComment(); xmlWriter.WriteComment(text); textWriter.EndComment(); } public override void WriteDocType(string name, string pubid, string sysid, string subset) { xmlWriter.WriteDocType(name, pubid, sysid, subset); } public override void WriteEndAttribute() { xmlWriter.WriteEndAttribute(); textWriter.EndAttribute(); } public override void WriteEndDocument() { xmlWriter.WriteEndDocument(); } public override void WriteEndElement() { xmlWriter.WriteEndElement(); } public override void WriteEntityRef(string name) { xmlWriter.WriteEntityRef(name); } public override void WriteFullEndElement() { xmlWriter.WriteFullEndElement(); } public override void WriteProcessingInstruction(string name, string text) { xmlWriter.WriteProcessingInstruction(name, text); } public override void WriteRaw(string data) { xmlWriter.WriteRaw(data); } public override void WriteRaw(char[] buffer, int index, int count) { xmlWriter.WriteRaw(buffer, index, count); } public override void WriteStartAttribute(string prefix, string localName, string ns) { textWriter.StartAttribute(); xmlWriter.WriteStartAttribute(prefix, localName, ns); } public override void WriteStartDocument(bool standalone) { xmlWriter.WriteStartDocument(standalone); } public override void WriteStartDocument() { xmlWriter.WriteStartDocument(); } public override void WriteStartElement(string prefix, string localName, string ns) { xmlWriter.WriteStartElement(prefix, localName, ns); } public override WriteState WriteState { get { return xmlWriter.WriteState; } } public override void WriteString(string text) { xmlWriter.WriteString(text); } public override void WriteSurrogateCharEntity(char lowChar, char highChar) { xmlWriter.WriteSurrogateCharEntity(lowChar, highChar); } public override void WriteWhitespace(string ws) { xmlWriter.WriteWhitespace(ws); } #endregion } }