|
// 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;
namespace Microsoft.Web.XmlTransform
{
public class XmlFileInfoDocument : XmlDocument, IDisposable
{
private Encoding _textEncoding = null;
private XmlTextReader _reader = null;
private XmlAttributePreservationProvider _preservationProvider = null;
private bool _firstLoad = true;
private string _fileName = null;
private int _lineNumberOffset = 0;
private int _linePositionOffset = 0;
public override void Load(string filename) {
LoadFromFileName(filename);
_firstLoad = false;
}
public override void Load(XmlReader reader) {
_reader = reader as XmlTextReader;
if (_reader != null) {
_fileName = _reader.BaseURI;
}
base.Load(reader);
if (_reader != null) {
_textEncoding = _reader.Encoding;
}
_firstLoad = false;
}
private void LoadFromFileName(string filename) {
_fileName = filename;
StreamReader reader = null;
try {
if (PreserveWhitespace) {
_preservationProvider = new XmlAttributePreservationProvider(filename);
}
reader = new StreamReader(filename, true);
LoadFromTextReader(reader);
}
finally {
if (_preservationProvider != null)
{
_preservationProvider.Close();
_preservationProvider = null;
}
if (reader != null) {
reader.Close();
}
}
}
private void LoadFromTextReader(TextReader textReader) {
StreamReader streamReader = textReader as StreamReader;
if (streamReader != null) {
FileStream fileStream = streamReader.BaseStream as FileStream;
if (fileStream != null) {
_fileName = fileStream.Name;
}
_textEncoding = GetEncodingFromStream(streamReader.BaseStream);
}
_reader = new XmlTextReader(_fileName, textReader);
base.Load(_reader);
if (_textEncoding == null) {
_textEncoding = _reader.Encoding;
}
}
private Encoding GetEncodingFromStream(Stream stream) {
Encoding encoding = null;
if (stream.CanSeek) {
byte[] buffer = new byte[3];
#if NET
stream.ReadExactly(buffer, 0, buffer.Length);
#else
int offset = 0;
int count = buffer.Length;
while (count > 0)
{
int read = stream.Read(buffer, offset, count);
if (read <= 0)
{
throw new EndOfStreamException();
}
offset += read;
count -= read;
}
#endif
if (buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF)
encoding = Encoding.UTF8;
else if (buffer[0] == 0xFE && buffer[1] == 0xFF)
encoding = Encoding.BigEndianUnicode;
else if (buffer[0] == 0xFF && buffer[1] == 0xFE)
encoding = Encoding.Unicode;
else if (buffer[0] == 0x2B && buffer[1] == 0x2F && buffer[2] == 0x76)
// 'Encoding.UTF7' is obsolete: 'The UTF-7 encoding is insecure and should not be used. Consider using UTF-8 instead.'
#pragma warning disable SYSLIB0001
encoding = Encoding.UTF7;
#pragma warning restore SYSLIB0001
// Reset the stream
stream.Seek(0, SeekOrigin.Begin);
}
return encoding;
}
internal XmlNode CloneNodeFromOtherDocument(XmlNode element) {
XmlTextReader oldReader = _reader;
string oldFileName = _fileName;
XmlNode clone = null;
try {
IXmlLineInfo lineInfo = element as IXmlLineInfo;
if (lineInfo != null) {
_reader = new XmlTextReader(new StringReader(element.OuterXml));
_lineNumberOffset = lineInfo.LineNumber - 1;
_linePositionOffset = lineInfo.LinePosition - 2;
_fileName = element.OwnerDocument.BaseURI;
clone = ReadNode(_reader);
}
else {
_fileName = null;
_reader = null;
clone = ReadNode(new XmlTextReader(new StringReader(element.OuterXml)));
}
}
finally {
_lineNumberOffset = 0;
_linePositionOffset = 0;
_fileName = oldFileName;
_reader = oldReader;
}
return clone;
}
internal bool HasErrorInfo {
get {
return _reader != null;
}
}
internal string FileName {
get {
return _fileName;
}
}
private int CurrentLineNumber {
get {
return _reader != null ? _reader.LineNumber + _lineNumberOffset : 0;
}
}
private int CurrentLinePosition {
get {
return _reader != null ? _reader.LinePosition + _linePositionOffset : 0;
}
}
private bool FirstLoad {
get {
return _firstLoad;
}
}
private XmlAttributePreservationProvider PreservationProvider {
get {
return _preservationProvider;
}
}
private Encoding TextEncoding {
get {
if (_textEncoding != null) {
return _textEncoding;
}
else {
// Copied from base implementation of XmlDocument
if (HasChildNodes) {
XmlDeclaration declaration = FirstChild as XmlDeclaration;
if (declaration != null) {
string value = declaration.Encoding;
if (value.Length > 0) {
return System.Text.Encoding.GetEncoding(value);
}
}
}
}
return null;
}
}
public override void Save(string filename)
{
XmlWriter xmlWriter = null;
using (Stream file = File.Create(filename))
{
try
{
if (PreserveWhitespace)
{
XmlFormatter.Format(this);
xmlWriter = new XmlAttributePreservingWriter(file, TextEncoding);
}
else
{
XmlTextWriter textWriter = new XmlTextWriter(file, TextEncoding);
textWriter.Formatting = Formatting.Indented;
xmlWriter = textWriter;
}
WriteTo(xmlWriter);
}
finally
{
if (xmlWriter != null)
{
xmlWriter.Flush();
xmlWriter.Close();
}
}
}
}
public override void Save(Stream w)
{
XmlWriter xmlWriter = null;
try {
if (PreserveWhitespace) {
XmlFormatter.Format(this);
xmlWriter = new XmlAttributePreservingWriter(w, TextEncoding);
}
else {
XmlTextWriter textWriter = new XmlTextWriter(w, TextEncoding);
textWriter.Formatting = Formatting.Indented;
xmlWriter = textWriter;
}
WriteTo(xmlWriter);
}
finally {
if (xmlWriter != null) {
xmlWriter.Flush();
}
}
}
public override XmlElement CreateElement(string prefix, string localName, string namespaceURI) {
if (HasErrorInfo) {
return new XmlFileInfoElement(prefix, localName, namespaceURI, this);
}
else {
return base.CreateElement(prefix, localName, namespaceURI);
}
}
public override XmlAttribute CreateAttribute(string prefix, string localName, string namespaceURI) {
if (HasErrorInfo) {
return new XmlFileInfoAttribute(prefix, localName, namespaceURI, this);
}
else {
return base.CreateAttribute(prefix, localName, namespaceURI);
}
}
internal bool IsNewNode(XmlNode node) {
// The transformation engine will only add elements. Anything
// else that gets added must be contained by a new element.
// So to determine what's new, we search up the tree for a new
// element that contains this node.
XmlFileInfoElement element = FindContainingElement(node) as XmlFileInfoElement;
return element != null && !element.IsOriginal;
}
private XmlElement FindContainingElement(XmlNode node) {
while (node != null && !(node is XmlElement)) {
node = node.ParentNode;
}
return node as XmlElement;
}
#region XmlElement override
private class XmlFileInfoElement : XmlElement, IXmlLineInfo, IXmlFormattableAttributes
{
private int lineNumber;
private int linePosition;
private bool isOriginal;
private XmlAttributePreservationDict preservationDict = null;
internal XmlFileInfoElement(string prefix, string localName, string namespaceUri, XmlFileInfoDocument document)
: base(prefix, localName, namespaceUri, document) {
lineNumber = document.CurrentLineNumber;
linePosition = document.CurrentLinePosition;
isOriginal = document.FirstLoad;
if (document.PreservationProvider != null) {
preservationDict = document.PreservationProvider.GetDictAtPosition(lineNumber, linePosition - 1);
}
if (preservationDict == null) {
preservationDict = new XmlAttributePreservationDict();
}
}
public override void WriteTo(XmlWriter w) {
string prefix = Prefix;
if (!String.IsNullOrEmpty(NamespaceURI)) {
prefix = w.LookupPrefix(NamespaceURI);
if (prefix == null) {
prefix = Prefix;
}
}
w.WriteStartElement(prefix, LocalName, NamespaceURI);
XmlAttributePreservingWriter preservingWriter = w as XmlAttributePreservingWriter;
if (preservingWriter == null || preservationDict == null) {
WriteAttributesTo(w);
}
else {
WritePreservedAttributesTo(preservingWriter);
}
if (IsEmpty) {
w.WriteEndElement();
}
else {
WriteContentTo(w);
w.WriteFullEndElement();
}
}
private void WriteAttributesTo(XmlWriter w) {
XmlAttributeCollection attrs = Attributes;
for (int i = 0; i < attrs.Count; i += 1) {
XmlAttribute attr = attrs[i];
attr.WriteTo(w);
}
}
private void WritePreservedAttributesTo(XmlAttributePreservingWriter preservingWriter) {
preservationDict.WritePreservedAttributes(preservingWriter, Attributes);
}
#region IXmlLineInfo Members
public bool HasLineInfo() {
return true;
}
public int LineNumber {
get {
return lineNumber;
}
}
public int LinePosition {
get {
return linePosition;
}
}
public bool IsOriginal {
get {
return isOriginal;
}
}
#endregion
#region IXmlFormattableNode Members
void IXmlFormattableAttributes.FormatAttributes(XmlFormatter formatter) {
preservationDict.UpdatePreservationInfo(Attributes, formatter);
}
string IXmlFormattableAttributes.AttributeIndent {
get {
return preservationDict.GetAttributeNewLineString(null);
}
}
#endregion
}
#endregion
#region XmlAttribute override
private class XmlFileInfoAttribute : XmlAttribute, IXmlLineInfo
{
private int lineNumber;
private int linePosition;
internal XmlFileInfoAttribute(string prefix, string localName, string namespaceUri, XmlFileInfoDocument document)
: base(prefix, localName, namespaceUri, document) {
lineNumber = document.CurrentLineNumber;
linePosition = document.CurrentLinePosition;
}
#region IXmlLineInfo Members
public bool HasLineInfo() {
return true;
}
public int LineNumber {
get {
return lineNumber;
}
}
public int LinePosition {
get {
return linePosition;
}
}
#endregion
}
#endregion
#region Dispose Pattern
protected virtual void Dispose(bool disposing)
{
if (_reader != null)
{
_reader.Close();
_reader = null;
}
if (_preservationProvider != null)
{
_preservationProvider.Close();
_preservationProvider = null;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~XmlFileInfoDocument()
{
Debug.Fail("call dispose please");
Dispose(false);
}
#endregion
}
}
|