519 lines
20 KiB
C#
519 lines
20 KiB
C#
|
//------------------------------------------------------------------------------
|
||
|
// <copyright file="DocumentXmlWriter.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
// <owner current="true" primary="true">[....]</owner>
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Diagnostics;
|
||
|
using System.Xml.Schema;
|
||
|
|
||
|
namespace System.Xml {
|
||
|
enum DocumentXmlWriterType {
|
||
|
InsertSiblingAfter,
|
||
|
InsertSiblingBefore,
|
||
|
PrependChild,
|
||
|
AppendChild,
|
||
|
AppendAttribute,
|
||
|
ReplaceToFollowingSibling,
|
||
|
}
|
||
|
|
||
|
// Implements a XmlWriter that augments a XmlDocument.
|
||
|
sealed class DocumentXmlWriter : XmlRawWriter, IXmlNamespaceResolver {
|
||
|
enum State {
|
||
|
Error,
|
||
|
Attribute,
|
||
|
Prolog,
|
||
|
Fragment,
|
||
|
Content,
|
||
|
|
||
|
Last, // always last
|
||
|
}
|
||
|
|
||
|
enum Method {
|
||
|
WriteXmlDeclaration,
|
||
|
WriteStartDocument,
|
||
|
WriteEndDocument,
|
||
|
WriteDocType,
|
||
|
WriteStartElement,
|
||
|
WriteEndElement,
|
||
|
WriteFullEndElement,
|
||
|
WriteStartAttribute,
|
||
|
WriteEndAttribute,
|
||
|
WriteStartNamespaceDeclaration,
|
||
|
WriteEndNamespaceDeclaration,
|
||
|
WriteCData,
|
||
|
WriteComment,
|
||
|
WriteProcessingInstruction,
|
||
|
WriteEntityRef,
|
||
|
WriteWhitespace,
|
||
|
WriteString,
|
||
|
}
|
||
|
|
||
|
DocumentXmlWriterType type; // writer type
|
||
|
XmlNode start; // context node
|
||
|
XmlDocument document; // context document
|
||
|
XmlNamespaceManager namespaceManager; // context namespace manager
|
||
|
State state; // current state
|
||
|
XmlNode write; // current node
|
||
|
List<XmlNode> fragment; // top level node cache
|
||
|
XmlWriterSettings settings; // wrapping writer settings
|
||
|
DocumentXPathNavigator navigator; // context for replace
|
||
|
XmlNode end; // context for replace
|
||
|
|
||
|
public DocumentXmlWriter(DocumentXmlWriterType type, XmlNode start, XmlDocument document) {
|
||
|
this.type = type;
|
||
|
this.start = start;
|
||
|
this.document = document;
|
||
|
|
||
|
state = StartState();
|
||
|
fragment = new List<XmlNode>();
|
||
|
settings = new XmlWriterSettings();
|
||
|
settings.ReadOnly = false;
|
||
|
settings.CheckCharacters = false;
|
||
|
settings.CloseOutput = false;
|
||
|
settings.ConformanceLevel = (state == State.Prolog ? ConformanceLevel.Document : ConformanceLevel.Fragment);
|
||
|
settings.ReadOnly = true;
|
||
|
}
|
||
|
|
||
|
public XmlNamespaceManager NamespaceManager {
|
||
|
set {
|
||
|
namespaceManager = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override XmlWriterSettings Settings {
|
||
|
get {
|
||
|
return settings;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void SetSettings(XmlWriterSettings value) {
|
||
|
settings = value;
|
||
|
}
|
||
|
|
||
|
public DocumentXPathNavigator Navigator {
|
||
|
set {
|
||
|
navigator = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public XmlNode EndNode {
|
||
|
set {
|
||
|
end = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override void WriteXmlDeclaration(XmlStandalone standalone) {
|
||
|
VerifyState(Method.WriteXmlDeclaration);
|
||
|
if (standalone != XmlStandalone.Omit) {
|
||
|
XmlNode node = document.CreateXmlDeclaration("1.0", string.Empty, standalone == XmlStandalone.Yes ? "yes" : "no");
|
||
|
AddChild(node, write);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override void WriteXmlDeclaration(string xmldecl) {
|
||
|
VerifyState(Method.WriteXmlDeclaration);
|
||
|
string version, encoding, standalone;
|
||
|
XmlLoader.ParseXmlDeclarationValue(xmldecl, out version, out encoding, out standalone);
|
||
|
XmlNode node = document.CreateXmlDeclaration(version, encoding, standalone);
|
||
|
AddChild(node, write);
|
||
|
}
|
||
|
|
||
|
public override void WriteStartDocument() {
|
||
|
VerifyState(Method.WriteStartDocument);
|
||
|
}
|
||
|
|
||
|
public override void WriteStartDocument(bool standalone) {
|
||
|
VerifyState(Method.WriteStartDocument);
|
||
|
}
|
||
|
|
||
|
public override void WriteEndDocument() {
|
||
|
VerifyState(Method.WriteEndDocument);
|
||
|
}
|
||
|
|
||
|
public override void WriteDocType(string name, string pubid, string sysid, string subset) {
|
||
|
VerifyState(Method.WriteDocType);
|
||
|
XmlNode node = document.CreateDocumentType(name, pubid, sysid, subset);
|
||
|
AddChild(node, write);
|
||
|
}
|
||
|
|
||
|
public override void WriteStartElement(string prefix, string localName, string ns) {
|
||
|
VerifyState(Method.WriteStartElement);
|
||
|
XmlNode node = document.CreateElement(prefix, localName, ns);
|
||
|
AddChild(node, write);
|
||
|
write = node;
|
||
|
}
|
||
|
|
||
|
public override void WriteEndElement() {
|
||
|
VerifyState(Method.WriteEndElement);
|
||
|
if (write == null) {
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
write = write.ParentNode;
|
||
|
}
|
||
|
|
||
|
internal override void WriteEndElement(string prefix, string localName, string ns) {
|
||
|
WriteEndElement();
|
||
|
}
|
||
|
|
||
|
public override void WriteFullEndElement() {
|
||
|
VerifyState(Method.WriteFullEndElement);
|
||
|
XmlElement elem = write as XmlElement;
|
||
|
if (elem == null) {
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
elem.IsEmpty = false;
|
||
|
write = elem.ParentNode;
|
||
|
}
|
||
|
|
||
|
internal override void WriteFullEndElement(string prefix, string localName, string ns) {
|
||
|
WriteFullEndElement();
|
||
|
}
|
||
|
|
||
|
internal override void StartElementContent() {
|
||
|
// nop
|
||
|
}
|
||
|
|
||
|
public override void WriteStartAttribute(string prefix, string localName, string ns) {
|
||
|
VerifyState(Method.WriteStartAttribute);
|
||
|
XmlAttribute attr = document.CreateAttribute(prefix, localName, ns);
|
||
|
AddAttribute(attr, write);
|
||
|
write = attr;
|
||
|
}
|
||
|
|
||
|
public override void WriteEndAttribute() {
|
||
|
VerifyState(Method.WriteEndAttribute);
|
||
|
XmlAttribute attr = write as XmlAttribute;
|
||
|
if (attr == null) {
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
if (!attr.HasChildNodes) {
|
||
|
XmlNode node = document.CreateTextNode(string.Empty);
|
||
|
AddChild(node, attr);
|
||
|
}
|
||
|
write = attr.OwnerElement;
|
||
|
}
|
||
|
|
||
|
internal override void WriteNamespaceDeclaration(string prefix, string ns) {
|
||
|
this.WriteStartNamespaceDeclaration(prefix);
|
||
|
this.WriteString(ns);
|
||
|
this.WriteEndNamespaceDeclaration();
|
||
|
}
|
||
|
|
||
|
internal override bool SupportsNamespaceDeclarationInChunks {
|
||
|
get {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override void WriteStartNamespaceDeclaration(string prefix) {
|
||
|
VerifyState(Method.WriteStartNamespaceDeclaration);
|
||
|
XmlAttribute attr;
|
||
|
if (prefix.Length == 0) {
|
||
|
attr = document.CreateAttribute(prefix, document.strXmlns, document.strReservedXmlns);
|
||
|
}
|
||
|
else {
|
||
|
attr = document.CreateAttribute(document.strXmlns, prefix, document.strReservedXmlns);
|
||
|
}
|
||
|
AddAttribute(attr, write);
|
||
|
write = attr;
|
||
|
}
|
||
|
|
||
|
internal override void WriteEndNamespaceDeclaration() {
|
||
|
VerifyState(Method.WriteEndNamespaceDeclaration);
|
||
|
XmlAttribute attr = write as XmlAttribute;
|
||
|
if (attr == null) {
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
if (!attr.HasChildNodes) {
|
||
|
XmlNode node = document.CreateTextNode(string.Empty);
|
||
|
AddChild(node, attr);
|
||
|
}
|
||
|
write = attr.OwnerElement;
|
||
|
}
|
||
|
|
||
|
public override void WriteCData(string text) {
|
||
|
VerifyState(Method.WriteCData);
|
||
|
XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
|
||
|
XmlNode node = document.CreateCDataSection(text);
|
||
|
AddChild(node, write);
|
||
|
}
|
||
|
|
||
|
public override void WriteComment(string text) {
|
||
|
VerifyState(Method.WriteComment);
|
||
|
XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
|
||
|
XmlNode node = document.CreateComment(text);
|
||
|
AddChild(node, write);
|
||
|
}
|
||
|
|
||
|
public override void WriteProcessingInstruction(string name, string text) {
|
||
|
VerifyState(Method.WriteProcessingInstruction);
|
||
|
XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
|
||
|
XmlNode node = document.CreateProcessingInstruction(name, text);
|
||
|
AddChild(node, write);
|
||
|
}
|
||
|
|
||
|
public override void WriteEntityRef(string name) {
|
||
|
VerifyState(Method.WriteEntityRef);
|
||
|
XmlNode node = document.CreateEntityReference(name);
|
||
|
AddChild(node, write);
|
||
|
//
|
||
|
}
|
||
|
|
||
|
public override void WriteCharEntity(char ch) {
|
||
|
WriteString(new string(ch, 1));
|
||
|
}
|
||
|
|
||
|
public override void WriteWhitespace(string text) {
|
||
|
VerifyState(Method.WriteWhitespace);
|
||
|
XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
|
||
|
if (document.PreserveWhitespace) {
|
||
|
XmlNode node = document.CreateWhitespace(text);
|
||
|
AddChild(node, write);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void WriteString(string text) {
|
||
|
VerifyState(Method.WriteString);
|
||
|
XmlConvert.VerifyCharData(text, ExceptionType.ArgumentException);
|
||
|
XmlNode node = document.CreateTextNode(text);
|
||
|
AddChild(node, write);
|
||
|
}
|
||
|
|
||
|
public override void WriteSurrogateCharEntity(char lowCh, char highCh) {
|
||
|
WriteString(new string(new char[] {highCh, lowCh}));
|
||
|
}
|
||
|
|
||
|
public override void WriteChars(char[] buffer, int index, int count) {
|
||
|
WriteString(new string(buffer, index, count));
|
||
|
}
|
||
|
|
||
|
public override void WriteRaw(char[] buffer, int index, int count) {
|
||
|
WriteString(new string(buffer, index, count));
|
||
|
}
|
||
|
|
||
|
public override void WriteRaw(string data) {
|
||
|
WriteString(data);
|
||
|
}
|
||
|
|
||
|
public override void Close() {
|
||
|
// nop
|
||
|
}
|
||
|
|
||
|
internal override void Close(WriteState currentState) {
|
||
|
if (currentState == WriteState.Error) {
|
||
|
return;
|
||
|
}
|
||
|
try {
|
||
|
switch (type) {
|
||
|
case DocumentXmlWriterType.InsertSiblingAfter:
|
||
|
XmlNode parent = start.ParentNode;
|
||
|
if (parent == null) {
|
||
|
throw new InvalidOperationException(Res.GetString(Res.Xpn_MissingParent));
|
||
|
}
|
||
|
for (int i = fragment.Count - 1; i >= 0; i--) {
|
||
|
parent.InsertAfter(fragment[i], start);
|
||
|
}
|
||
|
break;
|
||
|
case DocumentXmlWriterType.InsertSiblingBefore:
|
||
|
parent = start.ParentNode;
|
||
|
if (parent == null) {
|
||
|
throw new InvalidOperationException(Res.GetString(Res.Xpn_MissingParent));
|
||
|
}
|
||
|
for (int i = 0; i < fragment.Count; i++) {
|
||
|
parent.InsertBefore(fragment[i], start);
|
||
|
}
|
||
|
break;
|
||
|
case DocumentXmlWriterType.PrependChild:
|
||
|
for (int i = fragment.Count - 1; i >= 0; i--) {
|
||
|
start.PrependChild(fragment[i]);
|
||
|
}
|
||
|
break;
|
||
|
case DocumentXmlWriterType.AppendChild:
|
||
|
for (int i = 0; i < fragment.Count; i++) {
|
||
|
start.AppendChild(fragment[i]);
|
||
|
}
|
||
|
break;
|
||
|
case DocumentXmlWriterType.AppendAttribute:
|
||
|
CloseWithAppendAttribute();
|
||
|
break;
|
||
|
case DocumentXmlWriterType.ReplaceToFollowingSibling:
|
||
|
if (fragment.Count == 0) {
|
||
|
throw new InvalidOperationException(Res.GetString(Res.Xpn_NoContent));
|
||
|
}
|
||
|
CloseWithReplaceToFollowingSibling();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
finally {
|
||
|
fragment.Clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CloseWithAppendAttribute() {
|
||
|
XmlElement elem = start as XmlElement;
|
||
|
Debug.Assert(elem != null);
|
||
|
XmlAttributeCollection attrs = elem.Attributes;
|
||
|
for (int i = 0; i < fragment.Count; i++) {
|
||
|
XmlAttribute attr = fragment[i] as XmlAttribute;
|
||
|
Debug.Assert(attr != null);
|
||
|
int offset = attrs.FindNodeOffsetNS(attr);
|
||
|
if (offset != -1
|
||
|
&& ((XmlAttribute)attrs.nodes[offset]).Specified) {
|
||
|
throw new XmlException(Res.Xml_DupAttributeName, attr.Prefix.Length == 0 ? attr.LocalName : string.Concat(attr.Prefix, ":", attr.LocalName));
|
||
|
}
|
||
|
}
|
||
|
for (int i = 0; i < fragment.Count; i++) {
|
||
|
XmlAttribute attr = fragment[i] as XmlAttribute;
|
||
|
Debug.Assert(attr != null);
|
||
|
attrs.Append(attr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CloseWithReplaceToFollowingSibling() {
|
||
|
XmlNode parent = start.ParentNode;
|
||
|
if (parent == null) {
|
||
|
throw new InvalidOperationException(Res.GetString(Res.Xpn_MissingParent));
|
||
|
}
|
||
|
if (start != end) {
|
||
|
if (!DocumentXPathNavigator.IsFollowingSibling(start, end)) {
|
||
|
throw new InvalidOperationException(Res.GetString(Res.Xpn_BadPosition));
|
||
|
}
|
||
|
if (start.IsReadOnly) {
|
||
|
throw new InvalidOperationException(Res.GetString(Res.Xdom_Node_Modify_ReadOnly));
|
||
|
}
|
||
|
DocumentXPathNavigator.DeleteToFollowingSibling(start.NextSibling, end);
|
||
|
}
|
||
|
XmlNode fragment0 = fragment[0];
|
||
|
parent.ReplaceChild(fragment0, start);
|
||
|
for (int i = fragment.Count - 1; i >= 1; i--) {
|
||
|
parent.InsertAfter(fragment[i], fragment0);
|
||
|
}
|
||
|
navigator.ResetPosition(fragment0);
|
||
|
}
|
||
|
|
||
|
public override void Flush() {
|
||
|
// nop
|
||
|
}
|
||
|
|
||
|
IDictionary<string,string> IXmlNamespaceResolver.GetNamespacesInScope(XmlNamespaceScope scope) {
|
||
|
return namespaceManager.GetNamespacesInScope(scope);
|
||
|
}
|
||
|
|
||
|
string IXmlNamespaceResolver.LookupNamespace(string prefix) {
|
||
|
return namespaceManager.LookupNamespace(prefix);
|
||
|
}
|
||
|
|
||
|
string IXmlNamespaceResolver.LookupPrefix(string namespaceName) {
|
||
|
return namespaceManager.LookupPrefix(namespaceName);
|
||
|
}
|
||
|
|
||
|
void AddAttribute(XmlAttribute attr, XmlNode parent) {
|
||
|
if (parent == null) {
|
||
|
fragment.Add(attr);
|
||
|
}
|
||
|
else {
|
||
|
XmlElement elem = parent as XmlElement;
|
||
|
if (elem == null) {
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
elem.Attributes.Append(attr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AddChild(XmlNode node, XmlNode parent) {
|
||
|
if (parent == null) {
|
||
|
fragment.Add(node);
|
||
|
}
|
||
|
else {
|
||
|
parent.AppendChild(node);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
State StartState() {
|
||
|
XmlNodeType nodeType = XmlNodeType.None;
|
||
|
|
||
|
switch (type) {
|
||
|
case DocumentXmlWriterType.InsertSiblingAfter:
|
||
|
case DocumentXmlWriterType.InsertSiblingBefore:
|
||
|
XmlNode parent = start.ParentNode;
|
||
|
if (parent != null) {
|
||
|
nodeType = parent.NodeType;
|
||
|
}
|
||
|
if (nodeType == XmlNodeType.Document) {
|
||
|
return State.Prolog;
|
||
|
}
|
||
|
else if (nodeType == XmlNodeType.DocumentFragment) {
|
||
|
return State.Fragment;
|
||
|
}
|
||
|
break;
|
||
|
case DocumentXmlWriterType.PrependChild:
|
||
|
case DocumentXmlWriterType.AppendChild:
|
||
|
nodeType = start.NodeType;
|
||
|
if (nodeType == XmlNodeType.Document) {
|
||
|
return State.Prolog;
|
||
|
}
|
||
|
else if (nodeType == XmlNodeType.DocumentFragment) {
|
||
|
return State.Fragment;
|
||
|
}
|
||
|
break;
|
||
|
case DocumentXmlWriterType.AppendAttribute:
|
||
|
return State.Attribute;
|
||
|
case DocumentXmlWriterType.ReplaceToFollowingSibling:
|
||
|
break;
|
||
|
}
|
||
|
return State.Content;
|
||
|
}
|
||
|
|
||
|
static State[] changeState = {
|
||
|
// State.Error, State.Attribute,State.Prolog, State.Fragment, State.Content,
|
||
|
|
||
|
// Method.XmlDeclaration:
|
||
|
State.Error, State.Error, State.Prolog, State.Content, State.Error,
|
||
|
// Method.StartDocument:
|
||
|
State.Error, State.Error, State.Error, State.Error, State.Error,
|
||
|
// Method.EndDocument:
|
||
|
State.Error, State.Error, State.Error, State.Error, State.Error,
|
||
|
// Method.DocType:
|
||
|
State.Error, State.Error, State.Prolog, State.Error, State.Error,
|
||
|
// Method.StartElement:
|
||
|
State.Error, State.Error, State.Content, State.Content, State.Content,
|
||
|
// Method.EndElement:
|
||
|
State.Error, State.Error, State.Error, State.Error, State.Content,
|
||
|
// Method.FullEndElement:
|
||
|
State.Error, State.Error, State.Error, State.Error, State.Content,
|
||
|
// Method.StartAttribute:
|
||
|
State.Error, State.Content, State.Error, State.Error, State.Content,
|
||
|
// Method.EndAttribute:
|
||
|
State.Error, State.Error, State.Error, State.Error, State.Content,
|
||
|
// Method.StartNamespaceDeclaration:
|
||
|
State.Error, State.Content, State.Error, State.Error, State.Content,
|
||
|
// Method.EndNamespaceDeclaration:
|
||
|
State.Error, State.Error, State.Error, State.Error, State.Content,
|
||
|
// Method.CData:
|
||
|
State.Error, State.Error, State.Error, State.Content, State.Content,
|
||
|
// Method.Comment:
|
||
|
State.Error, State.Error, State.Prolog, State.Content, State.Content,
|
||
|
// Method.ProcessingInstruction:
|
||
|
State.Error, State.Error, State.Prolog, State.Content, State.Content,
|
||
|
// Method.EntityRef:
|
||
|
State.Error, State.Error, State.Error, State.Content, State.Content,
|
||
|
// Method.Whitespace:
|
||
|
State.Error, State.Error, State.Prolog, State.Content, State.Content,
|
||
|
// Method.String:
|
||
|
State.Error, State.Error, State.Error, State.Content, State.Content,
|
||
|
};
|
||
|
|
||
|
void VerifyState(Method method) {
|
||
|
state = changeState[(int)method * (int)State.Last + (int)state];
|
||
|
if (state == State.Error) {
|
||
|
throw new InvalidOperationException(Res.GetString(Res.Xml_ClosedOrError));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|