e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1448 lines
59 KiB
C#
1448 lines
59 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="XmlQueryOutput.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">[....]</owner>
|
|
//------------------------------------------------------------------------------
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Xml;
|
|
using System.Xml.XPath;
|
|
|
|
namespace System.Xml.Xsl.Runtime {
|
|
using Res = System.Xml.Utils.Res;
|
|
|
|
internal enum XmlState {
|
|
WithinSequence = 0, // Adding items to a top-level sequence
|
|
EnumAttrs, // Adding attributes to an element
|
|
WithinContent, // Adding content to an element
|
|
WithinAttr, // Adding text to an attribute
|
|
WithinNmsp, // Adding text to an namespace
|
|
WithinComment, // Adding text to a comment
|
|
WithinPI, // Adding text to a processing instruction
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// At run-time, a number of checks may need to be made in order to generate the correct sequence of calls
|
|
/// to XmlRawWriter:
|
|
/// 1. Well-formedness: Illegal state transitions, StartContent detection, no-content element detection
|
|
/// 2. Cached attributes: In XSLT, attributes override previously constructed attributes with the same name,
|
|
/// meaning that attribute names and values cannot be prematurely sent to XmlRawWriter.
|
|
/// 3. Cached namespaces: All namespaces are tracked in order to ensure adequate namespaces and to ensure
|
|
/// minimal (or something close) namespaces.
|
|
/// </summary>
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public sealed class XmlQueryOutput : XmlWriter {
|
|
// Never set these fields directly--instead use corresponding properties
|
|
private XmlRawWriter xwrt; // Output to XmlRawWriter--get and set this using the Writer property
|
|
|
|
// It is OK to set these properties directly
|
|
private XmlQueryRuntime runtime; // The XmlQueryRuntime instance that keeps global state
|
|
private XmlAttributeCache attrCache; // Cache used to detect duplicate attributes
|
|
private int depth; // Depth of the currently constructing tree
|
|
private XmlState xstate; // Current XML state
|
|
private XmlSequenceWriter seqwrt; // Current XmlSequenceWriter
|
|
private XmlNamespaceManager nsmgr; // Output namespace manager
|
|
private int cntNmsp; // Number of pending namespaces
|
|
private Dictionary<string, string> conflictPrefixes; // Remembers prefixes that were auto-generated previously in case they can be reused
|
|
private int prefixIndex; // Counter used to auto-generate non-conflicting attribute prefixes
|
|
private string piTarget/*nmspPrefix*/; // Cache pi target or namespace prefix
|
|
private StringConcat nodeText; // Cache pi, comment, or namespace text
|
|
private Stack<string> stkNames; // Keep stack of name parts computed during StartElement
|
|
private XPathNodeType rootType; // NodeType of the root of the tree
|
|
|
|
private Dictionary<string, string> usedPrefixes = new Dictionary<string, string>(); //The prefies that used in the current scope
|
|
|
|
/// <summary>
|
|
/// This constructor is internal so that external users cannot construct it (and therefore we do not have to test it separately).
|
|
/// Initialize output state to accept top-level sequences.
|
|
/// </summary>
|
|
internal XmlQueryOutput(XmlQueryRuntime runtime, XmlSequenceWriter seqwrt) {
|
|
this.runtime = runtime;
|
|
this.seqwrt = seqwrt;
|
|
this.xstate = XmlState.WithinSequence;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This constructor is internal so that external users cannot construct it (and therefore we do not have to test it separately).
|
|
/// Initialize output state to accept Rtf content (top-level sequences are therefore prohibited).
|
|
/// </summary>
|
|
internal XmlQueryOutput(XmlQueryRuntime runtime, XmlEventCache xwrt) {
|
|
this.runtime = runtime;
|
|
this.xwrt = xwrt;
|
|
this.xstate = XmlState.WithinContent;
|
|
this.depth = 1;
|
|
this.rootType = XPathNodeType.Root;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sequence writer to which output is directed by this class.
|
|
/// </summary>
|
|
internal XmlSequenceWriter SequenceWriter {
|
|
get { return this.seqwrt; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raw writer to which output is directed by this class.
|
|
/// </summary>
|
|
internal XmlRawWriter Writer {
|
|
get { return this.xwrt; }
|
|
set {
|
|
// If new writer might remove itself from pipeline, have it callback on this method when it's ready to go
|
|
IRemovableWriter removable = value as IRemovableWriter;
|
|
if (removable != null)
|
|
removable.OnRemoveWriterEvent = SetWrappedWriter;
|
|
|
|
this.xwrt = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method will be called if "xwrt" is a writer which no longer needs to be part of the pipeline and
|
|
/// wishes to replace itself with a different writer. For example, the auto-detect writer replaces itself
|
|
/// with the Html or Xml writer once it has determined which output mode to use.
|
|
/// </summary>
|
|
private void SetWrappedWriter(XmlRawWriter writer) {
|
|
// Reuse XmlAttributeCache so that it doesn't have to be recreated every time
|
|
if (Writer is XmlAttributeCache)
|
|
this.attrCache = (XmlAttributeCache) Writer;
|
|
|
|
Writer = writer;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------
|
|
// XmlWriter methods
|
|
//-----------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override void WriteStartDocument() {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override void WriteStartDocument(bool standalone) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override void WriteEndDocument() {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override void WriteDocType(string name, string pubid, string sysid, string subset) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before calling XmlRawWriter.WriteStartElement(), perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public override void WriteStartElement(string prefix, string localName, string ns) {
|
|
Debug.Assert(prefix != null && localName != null && localName.Length != 0 && ns != null, "Invalid argument");
|
|
Debug.Assert(ValidateNames.ValidateName(prefix, localName, ns, XPathNodeType.Element, ValidateNames.Flags.All), "Name validation failed");
|
|
|
|
// Xml state transitions
|
|
ConstructWithinContent(XPathNodeType.Element);
|
|
|
|
// Call XmlRawWriter.WriteStartElement
|
|
WriteStartElementUnchecked(prefix, localName, ns);
|
|
|
|
// Ensure that element's namespace declaration is declared
|
|
WriteNamespaceDeclarationUnchecked(prefix, ns);
|
|
|
|
// Cache attributes in order to detect duplicates
|
|
if (this.attrCache == null)
|
|
this.attrCache = new XmlAttributeCache();
|
|
|
|
this.attrCache.Init(Writer);
|
|
Writer = this.attrCache;
|
|
this.attrCache = null;
|
|
|
|
// Push element names onto a stack
|
|
PushElementNames(prefix, localName, ns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before calling XmlRawWriter.WriteEndElement(), perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public override void WriteEndElement() {
|
|
string prefix, localName, ns;
|
|
|
|
// Determine whether element had no content
|
|
if (this.xstate == XmlState.EnumAttrs) {
|
|
// No content, so call StartElementContent now
|
|
StartElementContentUnchecked();
|
|
}
|
|
|
|
// Call XmlRawWriter.WriteEndElement
|
|
PopElementNames(out prefix, out localName, out ns);
|
|
WriteEndElementUnchecked(prefix, localName, ns);
|
|
|
|
// Xml state transitions
|
|
if (this.depth == 0)
|
|
EndTree();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Same as calling WriteEndElement().
|
|
/// </summary>
|
|
public override void WriteFullEndElement() {
|
|
WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before calling XmlRawWriter.WriteStartAttribute(), perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public override void WriteStartAttribute(string prefix, string localName, string ns) {
|
|
Debug.Assert(prefix != null && localName != null && ns != null, "Invalid argument");
|
|
|
|
if (prefix.Length == 5 && prefix == "xmlns") {
|
|
// Handle namespace attributes that are not sent directly to WriteNamespaceDeclaration
|
|
WriteStartNamespace(localName);
|
|
}
|
|
else {
|
|
// All other attributes
|
|
Debug.Assert(ValidateNames.ValidateName(prefix, localName, ns, XPathNodeType.Attribute, ValidateNames.Flags.All));
|
|
|
|
// Xml state transitions
|
|
ConstructInEnumAttrs(XPathNodeType.Attribute);
|
|
|
|
// Check for prefix conflicts and possibly declare prefix
|
|
if (ns.Length != 0 && this.depth != 0)
|
|
prefix = CheckAttributePrefix(prefix, ns);
|
|
|
|
// Output the attribute
|
|
WriteStartAttributeUnchecked(prefix, localName, ns);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before calling XmlRawWriter.WriteEndAttribute(), perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public override void WriteEndAttribute() {
|
|
if (this.xstate == XmlState.WithinNmsp) {
|
|
WriteEndNamespace();
|
|
}
|
|
else {
|
|
WriteEndAttributeUnchecked();
|
|
|
|
if (this.depth == 0)
|
|
EndTree();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing a comment, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public override void WriteComment(string text) {
|
|
WriteStartComment();
|
|
WriteCommentString(text);
|
|
WriteEndComment();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing a processing instruction, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public override void WriteProcessingInstruction(string target, string text) {
|
|
WriteStartProcessingInstruction(target);
|
|
WriteProcessingInstructionString(text);
|
|
WriteEndProcessingInstruction();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override void WriteEntityRef(string name) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override void WriteCharEntity(char ch) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Treat whitespace as regular text.
|
|
/// </summary>
|
|
public override void WriteWhitespace(string ws) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing text, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public override void WriteString(string text) {
|
|
WriteString(text, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing text, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public override void WriteChars(char[] buffer, int index, int count) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write text, but do not escape special characters.
|
|
/// </summary>
|
|
public override void WriteRaw(char[] buffer, int index, int count) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write text, but do not escape special characters.
|
|
/// </summary>
|
|
public override void WriteRaw(string data) {
|
|
WriteString(data, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write CData text as regular text.
|
|
/// </summary>
|
|
public override void WriteCData(string text) {
|
|
WriteString(text, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override void WriteBase64(byte[] buffer, int index, int count) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override WriteState WriteState {
|
|
get { throw new NotSupportedException(); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// No-op.
|
|
/// </summary>
|
|
public override void Close() {
|
|
}
|
|
|
|
/// <summary>
|
|
/// No-op.
|
|
/// </summary>
|
|
public override void Flush() {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override string LookupPrefix(string ns) {
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override XmlSpace XmlSpace {
|
|
get { throw new NotSupportedException(); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should never be called.
|
|
/// </summary>
|
|
public override string XmlLang {
|
|
get { throw new NotSupportedException(); }
|
|
}
|
|
|
|
|
|
//-----------------------------------------------
|
|
// XmlQueryOutput methods (XmlSequenceWriter)
|
|
//-----------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Call XmlSequenceWriter.StartTree() in order to start construction of a new tree.
|
|
/// </summary>
|
|
public void StartTree(XPathNodeType rootType) {
|
|
Debug.Assert(this.xstate == XmlState.WithinSequence, "StartTree cannot be called in the " + this.xstate + " state.");
|
|
Writer = this.seqwrt.StartTree(rootType, this.nsmgr, this.runtime.NameTable);
|
|
this.rootType = rootType;
|
|
this.xstate = (rootType == XPathNodeType.Attribute || rootType == XPathNodeType.Namespace) ? XmlState.EnumAttrs : XmlState.WithinContent;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call XmlSequenceWriter.EndTree().
|
|
/// </summary>
|
|
public void EndTree() {
|
|
Debug.Assert(this.xstate == XmlState.EnumAttrs || this.xstate == XmlState.WithinContent, "EndTree cannot be called in the " + this.xstate + " state.");
|
|
this.seqwrt.EndTree();
|
|
this.xstate = XmlState.WithinSequence;
|
|
Writer = null;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------
|
|
// XmlQueryOutput methods (XmlRawWriter)
|
|
//-----------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Call XmlRawWriter.WriteStartElement() with prefix, local-name, ns, and schema type.
|
|
/// </summary>
|
|
public void WriteStartElementUnchecked(string prefix, string localName, string ns) {
|
|
Debug.Assert(this.xstate == XmlState.WithinContent, "WriteStartElement cannot be called in the " + this.xstate + " state.");
|
|
if (this.nsmgr != null) this.nsmgr.PushScope();
|
|
Writer.WriteStartElement(prefix, localName, ns);
|
|
//reset when enter element
|
|
usedPrefixes.Clear();
|
|
usedPrefixes[prefix] = ns;
|
|
this.xstate = XmlState.EnumAttrs;
|
|
this.depth++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call XmlRawWriter.WriteStartElement() with empty prefix, ns, and null schema type.
|
|
/// </summary>
|
|
public void WriteStartElementUnchecked(string localName) {
|
|
WriteStartElementUnchecked(string.Empty, localName, string.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call XmlRawWriter.StartElementContent().
|
|
/// </summary>
|
|
public void StartElementContentUnchecked() {
|
|
Debug.Assert(this.xstate == XmlState.EnumAttrs, "StartElementContent cannot be called in the " + this.xstate + " state.");
|
|
|
|
// Output any cached namespaces
|
|
if (this.cntNmsp != 0)
|
|
WriteCachedNamespaces();
|
|
|
|
Writer.StartElementContent();
|
|
this.xstate = XmlState.WithinContent;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call XmlRawWriter.WriteEndElement() with prefix, local-name, and ns.
|
|
/// </summary>
|
|
public void WriteEndElementUnchecked(string prefix, string localName, string ns) {
|
|
Debug.Assert(this.xstate == XmlState.EnumAttrs || this.xstate == XmlState.WithinContent, "WriteEndElement cannot be called in the " + this.xstate + " state.");
|
|
Writer.WriteEndElement(prefix, localName, ns);
|
|
this.xstate = XmlState.WithinContent;
|
|
this.depth--;
|
|
if (this.nsmgr != null) this.nsmgr.PopScope();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call XmlRawWriter.WriteEndElement() with empty prefix, ns.
|
|
/// </summary>
|
|
public void WriteEndElementUnchecked(string localName) {
|
|
WriteEndElementUnchecked(string.Empty, localName, string.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// XmlRawWriter.WriteStartAttribute() with prefix, local-name, ns, and schema type.
|
|
/// </summary>
|
|
public void WriteStartAttributeUnchecked(string prefix, string localName, string ns) {
|
|
Debug.Assert(this.xstate == XmlState.EnumAttrs, "WriteStartAttribute cannot be called in the " + this.xstate + " state.");
|
|
Writer.WriteStartAttribute(prefix, localName, ns);
|
|
this.xstate = XmlState.WithinAttr;
|
|
this.depth++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// XmlRawWriter.WriteStartAttribute() with empty prefix, ns, and null schema type.
|
|
/// </summary>
|
|
public void WriteStartAttributeUnchecked(string localName) {
|
|
WriteStartAttributeUnchecked(string.Empty, localName, string.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// XmlRawWriter.WriteEndAttribute().
|
|
/// </summary>
|
|
public void WriteEndAttributeUnchecked() {
|
|
Debug.Assert(this.xstate == XmlState.WithinAttr, "WriteEndAttribute cannot be called in the " + this.xstate + " state.");
|
|
Writer.WriteEndAttribute();
|
|
this.xstate = XmlState.EnumAttrs;
|
|
this.depth--;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a new namespace declaration -- xmlns:prefix="ns" -- to the set of in-scope declarations.
|
|
/// NOTE: This method should only be called if caller can guarantee that the current state is EnumAttrs
|
|
/// and that there will be no namespace conflicts in the current scope (e.g. trying to map the
|
|
/// same prefix to different namespaces within the same element start tag). If no such
|
|
/// guarantees exist, then WriteNamespaceDeclaration() should be called instead.
|
|
/// </summary>
|
|
public void WriteNamespaceDeclarationUnchecked(string prefix, string ns) {
|
|
Debug.Assert(prefix != null && ns != null);
|
|
Debug.Assert(this.xstate == XmlState.EnumAttrs, "WriteNamespaceDeclaration cannot be called in the " + this.xstate + " state.");
|
|
|
|
// xmlns:foo="" is illegal
|
|
Debug.Assert(prefix.Length == 0 || ns.Length != 0);
|
|
|
|
if (this.depth == 0) {
|
|
// At top-level, so write namespace declaration directly to output
|
|
Writer.WriteNamespaceDeclaration(prefix, ns);
|
|
return;
|
|
}
|
|
|
|
if (this.nsmgr == null) {
|
|
// If namespace manager has no namespaces, then xmlns="" is in scope by default
|
|
if (ns.Length == 0 && prefix.Length == 0)
|
|
return;
|
|
|
|
this.nsmgr = new XmlNamespaceManager(this.runtime.NameTable);
|
|
this.nsmgr.PushScope();
|
|
}
|
|
|
|
if (this.nsmgr.LookupNamespace(prefix) != ns)
|
|
AddNamespace(prefix, ns);
|
|
|
|
usedPrefixes[prefix] = ns;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a text block to the XmlRawWriter.
|
|
/// </summary>
|
|
public void WriteStringUnchecked(string text) {
|
|
Debug.Assert(this.xstate != XmlState.WithinSequence && this.xstate != XmlState.EnumAttrs, "WriteTextBlock cannot be called in the " + this.xstate + " state.");
|
|
Writer.WriteString(text);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a text block without escaping special characters.
|
|
/// </summary>
|
|
public void WriteRawUnchecked(string text) {
|
|
Debug.Assert(this.xstate != XmlState.WithinSequence && this.xstate != XmlState.EnumAttrs, "WriteTextBlockNoEntities cannot be called in the " + this.xstate + " state.");
|
|
Writer.WriteRaw(text);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------
|
|
// XmlQueryOutput methods
|
|
//-----------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Before calling XmlSequenceWriter.StartTree(), perform checks to ensure well-formedness.
|
|
/// </summary>
|
|
public void WriteStartRoot() {
|
|
Debug.Assert(this.depth == 0, "Root node can only be constructed at top-level.");
|
|
if (this.xstate != XmlState.WithinSequence)
|
|
ThrowInvalidStateError(XPathNodeType.Root);
|
|
|
|
StartTree(XPathNodeType.Root);
|
|
this.depth++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call XmlSequenceWriter.EndTree() and reset state.
|
|
/// </summary>
|
|
public void WriteEndRoot() {
|
|
Debug.Assert(this.depth == 1, "Root node can only be constructed at top-level.");
|
|
this.depth--;
|
|
EndTree();
|
|
}
|
|
|
|
/// <summary>
|
|
/// WriteStartElement() with empty prefix, ns.
|
|
/// </summary>
|
|
public void WriteStartElementLocalName(string localName) {
|
|
WriteStartElement(string.Empty, localName, string.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// WriteStartAttribute() with empty prefix, ns, and null schema type.
|
|
/// </summary>
|
|
public void WriteStartAttributeLocalName(string localName) {
|
|
WriteStartAttribute(string.Empty, localName, string.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an element with a name that is computed from a "prefix:localName" tag name and a set of prefix mappings.
|
|
/// </summary>
|
|
public void WriteStartElementComputed(string tagName, int prefixMappingsIndex) {
|
|
WriteStartComputed(XPathNodeType.Element, tagName, prefixMappingsIndex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an element with a name that is computed from a "prefix:localName" tag name and a namespace URI.
|
|
/// </summary>
|
|
public void WriteStartElementComputed(string tagName, string ns) {
|
|
WriteStartComputed(XPathNodeType.Element, tagName, ns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an element with a name that is copied from the navigator.
|
|
/// </summary>
|
|
public void WriteStartElementComputed(XPathNavigator navigator) {
|
|
WriteStartComputed(XPathNodeType.Element, navigator);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an element with a name that is derived from the XmlQualifiedName.
|
|
/// </summary>
|
|
public void WriteStartElementComputed(XmlQualifiedName name) {
|
|
WriteStartComputed(XPathNodeType.Element, name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an attribute with a name that is computed from a "prefix:localName" tag name and a set of prefix mappings.
|
|
/// </summary>
|
|
public void WriteStartAttributeComputed(string tagName, int prefixMappingsIndex) {
|
|
WriteStartComputed(XPathNodeType.Attribute, tagName, prefixMappingsIndex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an attribute with a name that is computed from a "prefix:localName" tag name and a namespace URI.
|
|
/// </summary>
|
|
public void WriteStartAttributeComputed(string tagName, string ns) {
|
|
WriteStartComputed(XPathNodeType.Attribute, tagName, ns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an attribute with a name that is copied from the navigator.
|
|
/// </summary>
|
|
public void WriteStartAttributeComputed(XPathNavigator navigator) {
|
|
WriteStartComputed(XPathNodeType.Attribute, navigator);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an attribute with a name that is derived from the XmlQualifiedName.
|
|
/// </summary>
|
|
public void WriteStartAttributeComputed(XmlQualifiedName name) {
|
|
WriteStartComputed(XPathNodeType.Attribute, name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before calling XmlRawWriter.WriteNamespaceDeclaration(), perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public void WriteNamespaceDeclaration(string prefix, string ns) {
|
|
string nsExisting;
|
|
Debug.Assert(prefix != null && ns != null);
|
|
|
|
ConstructInEnumAttrs(XPathNodeType.Namespace);
|
|
|
|
if (this.nsmgr == null) {
|
|
// If namespace manager has not yet been created, then there is no possibility of conflict
|
|
WriteNamespaceDeclarationUnchecked(prefix, ns);
|
|
}
|
|
else {
|
|
nsExisting = this.nsmgr.LookupNamespace(prefix);
|
|
if (ns != nsExisting) {
|
|
// prefix = "", ns = "", nsExisting --> Look for xmlns="", found xmlns="foo"
|
|
// prefix = "", ns, nsExisting = null --> Look for xmlns="uri", no uri found
|
|
// prefix = "", ns, nsExisting = "" --> Look for xmlns="uri", found xmlns=""
|
|
// prefix = "", ns, nsExisting --> Look for xmlns="uri", found xmlns="uri2"
|
|
// prefix, ns, nsExisting = null --> Look for xmlns:foo="uri", no uri found
|
|
// prefix, ns, nsExisting --> Look for xmlns:foo="uri", found xmlns:foo="uri2"
|
|
|
|
// If the prefix is mapped to a uri,
|
|
if (nsExisting != null) {
|
|
// Then throw an error except if the prefix trying to redefine is already used
|
|
if (usedPrefixes.ContainsKey(prefix)) {
|
|
throw new XslTransformException(Res.XmlIl_NmspConflict, new string[] { prefix.Length == 0 ? "" : ":", prefix, ns, nsExisting });
|
|
}
|
|
}
|
|
|
|
// Add namespace to manager and write it to output
|
|
AddNamespace(prefix, ns);
|
|
}
|
|
}
|
|
|
|
if (this.depth == 0)
|
|
EndTree();
|
|
|
|
usedPrefixes[prefix] = ns;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing a namespace, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public void WriteStartNamespace(string prefix) {
|
|
Debug.Assert(prefix != null, "Invalid argument");
|
|
|
|
// Handle namespace attributes that are not sent directly to WriteNamespaceDeclaration
|
|
ConstructInEnumAttrs(XPathNodeType.Namespace);
|
|
this.piTarget/*nmspPrefix*/ = prefix;
|
|
this.nodeText.Clear();
|
|
|
|
this.xstate = XmlState.WithinNmsp;
|
|
this.depth++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache the namespace's text.
|
|
/// </summary>
|
|
public void WriteNamespaceString(string text) {
|
|
Debug.Assert(this.xstate == XmlState.WithinNmsp, "WriteNamespaceString cannot be called in the " + this.xstate + " state.");
|
|
this.nodeText.ConcatNoDelimiter(text);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing a namespace, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public void WriteEndNamespace() {
|
|
Debug.Assert(this.xstate == XmlState.WithinNmsp, "WriteEndNamespace cannot be called in the " + this.xstate + " state.");
|
|
|
|
this.xstate = XmlState.EnumAttrs;
|
|
this.depth--;
|
|
|
|
// Write cached namespace attribute
|
|
WriteNamespaceDeclaration(this.piTarget/*nmspPrefix*/, this.nodeText.GetResult());
|
|
|
|
if (this.depth == 0)
|
|
EndTree();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing a comment, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public void WriteStartComment() {
|
|
// Xml state transitions
|
|
ConstructWithinContent(XPathNodeType.Comment);
|
|
|
|
this.nodeText.Clear();
|
|
this.xstate = XmlState.WithinComment;
|
|
this.depth++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache the comment's text.
|
|
/// </summary>
|
|
public void WriteCommentString(string text) {
|
|
Debug.Assert(this.xstate == XmlState.WithinComment, "WriteCommentString cannot be called in the " + this.xstate + " state.");
|
|
this.nodeText.ConcatNoDelimiter(text);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing a comment, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public void WriteEndComment() {
|
|
Debug.Assert(this.xstate == XmlState.WithinComment, "WriteEndComment cannot be called in the " + this.xstate + " state.");
|
|
|
|
Writer.WriteComment(this.nodeText.GetResult());
|
|
|
|
this.xstate = XmlState.WithinContent;
|
|
this.depth--;
|
|
|
|
if (this.depth == 0)
|
|
EndTree();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing a processing instruction, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public void WriteStartProcessingInstruction(string target) {
|
|
// Xml state transitions
|
|
ConstructWithinContent(XPathNodeType.ProcessingInstruction);
|
|
|
|
// Verify PI name
|
|
ValidateNames.ValidateNameThrow("", target, "", XPathNodeType.ProcessingInstruction, ValidateNames.Flags.AllExceptPrefixMapping);
|
|
|
|
this.piTarget = target;
|
|
this.nodeText.Clear();
|
|
|
|
this.xstate = XmlState.WithinPI;
|
|
this.depth++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache the processing instruction's text.
|
|
/// </summary>
|
|
public void WriteProcessingInstructionString(string text) {
|
|
Debug.Assert(this.xstate == XmlState.WithinPI, "WriteProcessingInstructionString cannot be called in the " + this.xstate + " state.");
|
|
this.nodeText.ConcatNoDelimiter(text);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing a processing instruction, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
public void WriteEndProcessingInstruction() {
|
|
Debug.Assert(this.xstate == XmlState.WithinPI, "WriteEndProcessingInstruction cannot be called in the " + this.xstate + " state.");
|
|
|
|
Writer.WriteProcessingInstruction(this.piTarget, this.nodeText.GetResult());
|
|
|
|
this.xstate = XmlState.WithinContent;
|
|
this.depth--;
|
|
|
|
// Xml state transitions
|
|
if (this.depth == 0)
|
|
EndTree();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an item to output. If currently constructing an Xml tree, then the item is always copied.
|
|
/// At the top-level, the item's identity is preserved unless it's an atomic value.
|
|
/// </summary>
|
|
public void WriteItem(XPathItem item) {
|
|
if (item.IsNode) {
|
|
XPathNavigator navigator = (XPathNavigator) item;
|
|
|
|
// If this is a top-level node, write a reference to it; else copy it by value
|
|
if (this.xstate == XmlState.WithinSequence)
|
|
this.seqwrt.WriteItem(navigator);
|
|
else
|
|
CopyNode(navigator);
|
|
}
|
|
else {
|
|
// Call WriteItem for atomic values
|
|
Debug.Assert(this.xstate == XmlState.WithinSequence, "Values can only be written at the top-level.");
|
|
this.seqwrt.WriteItem(item);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy a node by value to output according to Xslt rules:
|
|
/// 1. Identity is never preserved
|
|
/// 2. If the item is an Rtf, preserve serialization hints when copying.
|
|
/// 3. If the item is a Root node, copy the children of the Root
|
|
/// </summary>
|
|
public void XsltCopyOf(XPathNavigator navigator) {
|
|
RtfNavigator navRtf = navigator as RtfNavigator;
|
|
|
|
if (navRtf != null) {
|
|
// Copy Rtf
|
|
navRtf.CopyToWriter(this);
|
|
}
|
|
else if (navigator.NodeType == XPathNodeType.Root) {
|
|
// Copy children of root
|
|
if (navigator.MoveToFirstChild()) {
|
|
do {
|
|
CopyNode(navigator);
|
|
}
|
|
while (navigator.MoveToNext());
|
|
|
|
navigator.MoveToParent();
|
|
}
|
|
}
|
|
else {
|
|
// Copy node
|
|
CopyNode(navigator);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin shallow copy of the navigator's current node to output. Returns true if EndCopy
|
|
/// should be called to complete the copy operation.
|
|
/// Automatically copies all in-scope namespaces on elements.
|
|
/// </summary>
|
|
public bool StartCopy(XPathNavigator navigator) {
|
|
// StartDocument is a no-op
|
|
if (navigator.NodeType == XPathNodeType.Root)
|
|
return true;
|
|
|
|
if (StartCopy(navigator, true)) {
|
|
Debug.Assert(navigator.NodeType == XPathNodeType.Element, "StartCopy should return true only for Element nodes.");
|
|
|
|
// Copy namespaces to output
|
|
CopyNamespaces(navigator, XPathNamespaceScope.ExcludeXml);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// End shallow copy of the navigator's current node. Should be called only for Element and Document nodes.
|
|
/// </summary>
|
|
public void EndCopy(XPathNavigator navigator) {
|
|
if (navigator.NodeType == XPathNodeType.Element)
|
|
WriteEndElement();
|
|
else
|
|
Debug.Assert(navigator.NodeType == XPathNodeType.Root, "EndCopy should only be called for Element and Document nodes.");
|
|
}
|
|
|
|
|
|
//-----------------------------------------------
|
|
// Helper methods
|
|
//-----------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Add an in-scope namespace.
|
|
/// </summary>
|
|
private void AddNamespace(string prefix, string ns) {
|
|
this.nsmgr.AddNamespace(prefix, ns);
|
|
this.cntNmsp++;
|
|
usedPrefixes[prefix] = ns;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Before writing text, perform various checks to ensure well-formedness.
|
|
/// </summary>
|
|
private void WriteString(string text, bool disableOutputEscaping) {
|
|
Debug.Assert(text != null, "Invalid argument");
|
|
|
|
// Xml state transitions
|
|
switch (this.xstate) {
|
|
case XmlState.WithinSequence:
|
|
// Start constructing new tree
|
|
StartTree(XPathNodeType.Text);
|
|
goto case XmlState.WithinContent;
|
|
|
|
case XmlState.WithinContent:
|
|
if (disableOutputEscaping)
|
|
WriteRawUnchecked(text);
|
|
else
|
|
WriteStringUnchecked(text);
|
|
break;
|
|
|
|
case XmlState.EnumAttrs:
|
|
// Enumerating attributes, so write text as element content
|
|
StartElementContentUnchecked();
|
|
goto case XmlState.WithinContent;
|
|
|
|
case XmlState.WithinAttr:
|
|
WriteStringUnchecked(text);
|
|
break;
|
|
|
|
case XmlState.WithinNmsp:
|
|
WriteNamespaceString(text);
|
|
break;
|
|
|
|
case XmlState.WithinComment:
|
|
// Comment text
|
|
WriteCommentString(text);
|
|
break;
|
|
|
|
case XmlState.WithinPI:
|
|
// PI text
|
|
WriteProcessingInstructionString(text);
|
|
break;
|
|
|
|
default:
|
|
Debug.Assert(false, "Text cannot be output in the " + this.xstate + " state.");
|
|
break;
|
|
}
|
|
|
|
if (this.depth == 0)
|
|
EndTree();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deep copy the subtree that is rooted at this navigator's current position to output. If the current
|
|
/// item is an element, copy all in-scope namespace nodes.
|
|
/// </summary>
|
|
private void CopyNode(XPathNavigator navigator) {
|
|
XPathNodeType nodeType;
|
|
int depthStart = this.depth;
|
|
Debug.Assert(navigator != null);
|
|
|
|
while (true) {
|
|
if (StartCopy(navigator, this.depth == depthStart)) {
|
|
nodeType = navigator.NodeType;
|
|
Debug.Assert(nodeType == XPathNodeType.Element, "StartCopy should return true only for Element nodes.");
|
|
|
|
// Copy attributes
|
|
if (navigator.MoveToFirstAttribute()) {
|
|
do {
|
|
StartCopy(navigator, false);
|
|
}
|
|
while (navigator.MoveToNextAttribute());
|
|
navigator.MoveToParent();
|
|
}
|
|
|
|
// Copy namespaces in document order (navigator returns them in reverse document order)
|
|
CopyNamespaces(navigator, (this.depth - 1 == depthStart) ? XPathNamespaceScope.ExcludeXml : XPathNamespaceScope.Local);
|
|
|
|
StartElementContentUnchecked();
|
|
|
|
// If children exist, move down to next level
|
|
if (navigator.MoveToFirstChild())
|
|
continue;
|
|
|
|
EndCopy(navigator, (this.depth - 1) == depthStart);
|
|
}
|
|
|
|
// No children
|
|
while (true) {
|
|
if (this.depth == depthStart) {
|
|
// The entire subtree has been copied
|
|
return;
|
|
}
|
|
|
|
if (navigator.MoveToNext()) {
|
|
// Found a sibling, so break to outer loop
|
|
break;
|
|
}
|
|
|
|
// No siblings, so move up to previous level
|
|
navigator.MoveToParent();
|
|
|
|
EndCopy(navigator, (this.depth - 1) == depthStart);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin shallow copy of the navigator's current node to output. Returns true if EndCopy
|
|
/// should be called to complete the copy operation.
|
|
/// </summary>
|
|
private bool StartCopy(XPathNavigator navigator, bool callChk) {
|
|
bool mayHaveChildren = false;
|
|
|
|
switch (navigator.NodeType) {
|
|
case XPathNodeType.Element:
|
|
// If checks need to be made, call XmlQueryOutput.WriteStartElement
|
|
if (callChk) {
|
|
WriteStartElement(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI);
|
|
}
|
|
else {
|
|
WriteStartElementUnchecked(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI);
|
|
}
|
|
|
|
mayHaveChildren = true;
|
|
break;
|
|
|
|
case XPathNodeType.Attribute:
|
|
// If checks need to be made, call XmlQueryOutput.WriteStartAttribute
|
|
if (callChk) {
|
|
WriteStartAttribute(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI);
|
|
}
|
|
else {
|
|
WriteStartAttributeUnchecked(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI);
|
|
}
|
|
|
|
// Write attribute text
|
|
WriteString(navigator.Value);
|
|
|
|
// If checks need to be made, call XmlQueryOutput.WriteEndAttribute
|
|
if (callChk) {
|
|
WriteEndAttribute();
|
|
}
|
|
else {
|
|
WriteEndAttributeUnchecked();
|
|
}
|
|
break;
|
|
|
|
case XPathNodeType.Namespace:
|
|
// If checks need to be made, call XmlQueryOutput.WriteNamespaceDeclaration
|
|
if (callChk) {
|
|
// Do not allow namespaces to be copied after attributes
|
|
XmlAttributeCache attrCache = Writer as XmlAttributeCache;
|
|
if (attrCache != null && attrCache.Count != 0)
|
|
throw new XslTransformException(Res.XmlIl_NmspAfterAttr, string.Empty);
|
|
|
|
WriteNamespaceDeclaration(navigator.LocalName, navigator.Value);
|
|
}
|
|
else {
|
|
WriteNamespaceDeclarationUnchecked(navigator.LocalName, navigator.Value);
|
|
}
|
|
break;
|
|
|
|
case XPathNodeType.Text:
|
|
case XPathNodeType.SignificantWhitespace:
|
|
case XPathNodeType.Whitespace:
|
|
// If checks need to be made, call XmlQueryOutput.WriteString
|
|
if (callChk) {
|
|
WriteString(navigator.Value, false);
|
|
}
|
|
else {
|
|
// No flags are set, so this is simple element text (attributes, comments, pi's copy own text)
|
|
WriteStringUnchecked(navigator.Value);
|
|
}
|
|
break;
|
|
|
|
case XPathNodeType.Root:
|
|
// Document node is invalid except at the top-level
|
|
Debug.Assert(this.xstate != XmlState.WithinSequence, "StartCopy should not called if state is WithinSequence");
|
|
ThrowInvalidStateError(XPathNodeType.Root);
|
|
break;
|
|
|
|
case XPathNodeType.Comment:
|
|
WriteStartComment();
|
|
WriteCommentString(navigator.Value);
|
|
WriteEndComment();
|
|
break;
|
|
|
|
case XPathNodeType.ProcessingInstruction:
|
|
WriteStartProcessingInstruction(navigator.LocalName);
|
|
WriteProcessingInstructionString(navigator.Value);
|
|
WriteEndProcessingInstruction();
|
|
break;
|
|
|
|
default:
|
|
Debug.Assert(false);
|
|
break;
|
|
}
|
|
|
|
return mayHaveChildren;
|
|
}
|
|
|
|
/// <summary>
|
|
/// End shallow copy of the navigator's current node to output. This method should only be called if StartCopy
|
|
/// returned true.
|
|
/// </summary>
|
|
private void EndCopy(XPathNavigator navigator, bool callChk) {
|
|
Debug.Assert(navigator.NodeType == XPathNodeType.Element);
|
|
Debug.Assert(this.xstate == XmlState.WithinContent, "EndCopy cannot be called in the " + this.xstate + " state.");
|
|
|
|
if (callChk)
|
|
WriteEndElement();
|
|
else
|
|
WriteEndElementUnchecked(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy all namespaces of the specified type (in-scope, exclude-xml, local) in document order to output.
|
|
/// </summary>
|
|
private void CopyNamespaces(XPathNavigator navigator, XPathNamespaceScope nsScope) {
|
|
Debug.Assert(navigator.NodeType == XPathNodeType.Element, "Only elements have namespaces to copy");
|
|
|
|
// Default namespace undeclaration isn't included in navigator's namespace list, so add it now
|
|
if (navigator.NamespaceURI.Length == 0) {
|
|
Debug.Assert(navigator.LocalName.Length != 0, "xmlns:foo='' isn't allowed");
|
|
WriteNamespaceDeclarationUnchecked(string.Empty, string.Empty);
|
|
}
|
|
|
|
// Since the namespace list is arranged in reverse-document order, recursively reverse it.
|
|
if (navigator.MoveToFirstNamespace(nsScope)) {
|
|
CopyNamespacesHelper(navigator, nsScope);
|
|
navigator.MoveToParent();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recursive helper function that reverses order of the namespaces retrieved by MoveToFirstNamespace and
|
|
/// MoveToNextNamespace.
|
|
/// </summary>
|
|
private void CopyNamespacesHelper(XPathNavigator navigator, XPathNamespaceScope nsScope) {
|
|
string prefix = navigator.LocalName;
|
|
string ns = navigator.Value;
|
|
|
|
if (navigator.MoveToNextNamespace(nsScope))
|
|
CopyNamespacesHelper(navigator, nsScope);
|
|
|
|
// No possibility for conflict, since we're copying namespaces from well-formed element
|
|
WriteNamespaceDeclarationUnchecked(prefix, ns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensure that state transitions to WithinContent.
|
|
/// </summary>
|
|
private void ConstructWithinContent(XPathNodeType rootType) {
|
|
Debug.Assert(rootType == XPathNodeType.Element || rootType == XPathNodeType.Comment || rootType == XPathNodeType.ProcessingInstruction);
|
|
|
|
switch (this.xstate) {
|
|
case XmlState.WithinSequence:
|
|
// If state is WithinSequence, call XmlSequenceWriter.StartTree
|
|
StartTree(rootType);
|
|
this.xstate = XmlState.WithinContent;
|
|
break;
|
|
|
|
case XmlState.WithinContent:
|
|
// Already within element content
|
|
break;
|
|
|
|
case XmlState.EnumAttrs:
|
|
// Start element content
|
|
StartElementContentUnchecked();
|
|
break;
|
|
|
|
default:
|
|
// Construction is not allowed in this state
|
|
ThrowInvalidStateError(rootType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensure that state transitions to EnumAttrs.
|
|
/// </summary>
|
|
private void ConstructInEnumAttrs(XPathNodeType rootType) {
|
|
Debug.Assert(rootType == XPathNodeType.Attribute || rootType == XPathNodeType.Namespace);
|
|
|
|
switch (this.xstate) {
|
|
case XmlState.WithinSequence:
|
|
StartTree(rootType);
|
|
this.xstate = XmlState.EnumAttrs;
|
|
break;
|
|
|
|
case XmlState.EnumAttrs:
|
|
// Already in EnumAttrs state
|
|
break;
|
|
|
|
default:
|
|
// Construction is not allowed in this state
|
|
ThrowInvalidStateError(rootType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Namespace declarations are added to this.nsmgr. Just before element content has begun, write out
|
|
/// all namespaces that were declared locally on the element.
|
|
/// </summary>
|
|
private void WriteCachedNamespaces() {
|
|
string prefix, ns;
|
|
|
|
while (this.cntNmsp != 0) {
|
|
// Output each prefix->ns mapping pair
|
|
Debug.Assert(this.nsmgr != null);
|
|
this.cntNmsp--;
|
|
this.nsmgr.GetNamespaceDeclaration(this.cntNmsp, out prefix, out ns);
|
|
Writer.WriteNamespaceDeclaration(prefix, ns);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the type of node that is under construction given the specified XmlState.
|
|
/// </summary>
|
|
private XPathNodeType XmlStateToNodeType(XmlState xstate) {
|
|
switch (xstate) {
|
|
case XmlState.EnumAttrs: return XPathNodeType.Element;
|
|
case XmlState.WithinContent: return XPathNodeType.Element;
|
|
case XmlState.WithinAttr: return XPathNodeType.Attribute;
|
|
case XmlState.WithinComment: return XPathNodeType.Comment;
|
|
case XmlState.WithinPI: return XPathNodeType.ProcessingInstruction;
|
|
}
|
|
|
|
Debug.Assert(false, xstate.ToString() + " is not a valid XmlState.");
|
|
return XPathNodeType.Element;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If attribute's prefix conflicts with other prefixes then redeclare the prefix. If the prefix has
|
|
/// not yet been declared, then add it to the namespace manager.
|
|
/// </summary>
|
|
private string CheckAttributePrefix(string prefix, string ns) {
|
|
string nsExisting;
|
|
Debug.Assert(prefix.Length != 0 && ns.Length != 0);
|
|
|
|
// Ensure that this attribute's prefix does not conflict with previously declared prefixes in this scope
|
|
if (this.nsmgr == null) {
|
|
// If namespace manager has no namespaces, then there is no possibility of conflict
|
|
WriteNamespaceDeclarationUnchecked(prefix, ns);
|
|
}
|
|
else {
|
|
while (true) {
|
|
// If prefix is already mapped to a different namespace,
|
|
nsExisting = this.nsmgr.LookupNamespace(prefix);
|
|
if (nsExisting != ns) {
|
|
|
|
// Then if the prefix is already mapped,
|
|
if (nsExisting != null) {
|
|
// Then there is a conflict that must be resolved by finding another prefix
|
|
// Always find a new prefix, even if the conflict didn't occur in the current scope
|
|
// This decision allows more aggressive namespace analysis at compile-time
|
|
prefix = RemapPrefix(prefix, ns, false);
|
|
continue;
|
|
}
|
|
|
|
// Add the mapping to the current scope
|
|
AddNamespace(prefix, ns);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return prefix;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remaps an element or attribute prefix using the following rules:
|
|
///
|
|
/// 1. If another in-scope prefix is already mapped to "ns", then use that
|
|
/// 2. Otherwise, if a prefix was previously mapped to "ns" by this method, then use that
|
|
/// 3. Otherwise, generate a new prefix of the form 'xp_??', where ?? is a stringized counter
|
|
///
|
|
/// These rules tend to reduce the number of unique prefixes used throughout the tree.
|
|
/// </summary>
|
|
private string RemapPrefix(string prefix, string ns, bool isElemPrefix) {
|
|
string genPrefix;
|
|
Debug.Assert(prefix != null && ns != null && ns.Length != 0);
|
|
|
|
if (this.conflictPrefixes == null)
|
|
this.conflictPrefixes = new Dictionary<string, string>(16);
|
|
|
|
if (this.nsmgr == null) {
|
|
this.nsmgr = new XmlNamespaceManager(this.runtime.NameTable);
|
|
this.nsmgr.PushScope();
|
|
}
|
|
|
|
// Rule #1: If another in-scope prefix is already mapped to "ns", then use that
|
|
genPrefix = this.nsmgr.LookupPrefix(ns);
|
|
if (genPrefix != null) {
|
|
// Can't use an empty prefix for an attribute
|
|
if (isElemPrefix || genPrefix.Length != 0)
|
|
goto ReturnPrefix;
|
|
}
|
|
|
|
// Rule #2: Otherwise, if a prefix was previously mapped to "ns" by this method, then use that
|
|
// Make sure that any previous prefix is different than "prefix"
|
|
if (this.conflictPrefixes.TryGetValue(ns, out genPrefix) && genPrefix != prefix) {
|
|
// Can't use an empty prefix for an attribute
|
|
if (isElemPrefix || genPrefix.Length != 0)
|
|
goto ReturnPrefix;
|
|
}
|
|
|
|
// Rule #3: Otherwise, generate a new prefix of the form 'xp_??', where ?? is a stringized counter
|
|
genPrefix = "xp_" + (this.prefixIndex++).ToString(CultureInfo.InvariantCulture);
|
|
|
|
|
|
ReturnPrefix:
|
|
// Save generated prefix so that it can be possibly be reused later
|
|
this.conflictPrefixes[ns] = genPrefix;
|
|
|
|
return genPrefix;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an element or attribute with a name that is computed from a "prefix:localName" tag name and a set of prefix mappings.
|
|
/// </summary>
|
|
private void WriteStartComputed(XPathNodeType nodeType, string tagName, int prefixMappingsIndex) {
|
|
string prefix, localName, ns;
|
|
|
|
// Parse the tag name and map the prefix to a namespace
|
|
runtime.ParseTagName(tagName, prefixMappingsIndex, out prefix, out localName, out ns);
|
|
|
|
// Validate the name parts
|
|
prefix = EnsureValidName(prefix, localName, ns, nodeType);
|
|
|
|
if (nodeType == XPathNodeType.Element)
|
|
WriteStartElement(prefix, localName, ns);
|
|
else
|
|
WriteStartAttribute(prefix, localName, ns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an element or attribute with a name that is computed from a "prefix:localName" tag name and a namespace URI.
|
|
/// </summary>
|
|
private void WriteStartComputed(XPathNodeType nodeType, string tagName, string ns) {
|
|
string prefix, localName;
|
|
|
|
// Parse the tagName as a prefix, localName pair
|
|
ValidateNames.ParseQNameThrow(tagName, out prefix, out localName);
|
|
|
|
// Validate the name parts
|
|
prefix = EnsureValidName(prefix, localName, ns, nodeType);
|
|
|
|
if (nodeType == XPathNodeType.Element)
|
|
WriteStartElement(prefix, localName, ns);
|
|
else
|
|
WriteStartAttribute(prefix, localName, ns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an element or attribute with a name that is copied from the navigator.
|
|
/// </summary>
|
|
private void WriteStartComputed(XPathNodeType nodeType, XPathNavigator navigator) {
|
|
string prefix, localName, ns;
|
|
|
|
prefix = navigator.Prefix;
|
|
localName = navigator.LocalName;
|
|
ns = navigator.NamespaceURI;
|
|
|
|
if (navigator.NodeType != nodeType) {
|
|
// Validate the name parts
|
|
prefix = EnsureValidName(prefix, localName, ns, nodeType);
|
|
}
|
|
|
|
if (nodeType == XPathNodeType.Element)
|
|
WriteStartElement(prefix, localName, ns);
|
|
else
|
|
WriteStartAttribute(prefix, localName, ns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write an element or attribute with a name that is derived from the XmlQualifiedName.
|
|
/// </summary>
|
|
private void WriteStartComputed(XPathNodeType nodeType, XmlQualifiedName name) {
|
|
string prefix;
|
|
Debug.Assert(ValidateNames.ParseNCName(name.Name, 0) == name.Name.Length);
|
|
|
|
// Validate the name parts
|
|
prefix = (name.Namespace.Length != 0) ? RemapPrefix(string.Empty, name.Namespace, nodeType == XPathNodeType.Element) : string.Empty;
|
|
prefix = EnsureValidName(prefix, name.Name, name.Namespace, nodeType);
|
|
|
|
if (nodeType == XPathNodeType.Element)
|
|
WriteStartElement(prefix, name.Name, name.Namespace);
|
|
else
|
|
WriteStartAttribute(prefix, name.Name, name.Namespace);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensure that the specified name parts are valid according to Xml 1.0 and Namespace 1.0 rules. Try to remap
|
|
/// the prefix in order to attain validity. Throw if validity is not possible. Otherwise, return the (possibly
|
|
/// remapped) prefix.
|
|
/// </summary>
|
|
private string EnsureValidName(string prefix, string localName, string ns, XPathNodeType nodeType) {
|
|
if (!ValidateNames.ValidateName(prefix, localName, ns, nodeType, ValidateNames.Flags.AllExceptNCNames)) {
|
|
// Name parts are not valid as is. Try to re-map the prefix.
|
|
prefix = (ns.Length != 0) ? RemapPrefix(string.Empty, ns, nodeType == XPathNodeType.Element) : string.Empty;
|
|
|
|
// Throw if validation does not work this time
|
|
ValidateNames.ValidateNameThrow(prefix, localName, ns, nodeType, ValidateNames.Flags.AllExceptNCNames);
|
|
}
|
|
|
|
return prefix;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Push element name parts onto the stack.
|
|
/// </summary>
|
|
private void PushElementNames(string prefix, string localName, string ns) {
|
|
// Push the name parts onto a stack
|
|
if (this.stkNames == null)
|
|
this.stkNames = new Stack<string>(15);
|
|
|
|
this.stkNames.Push(prefix);
|
|
this.stkNames.Push(localName);
|
|
this.stkNames.Push(ns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pop element name parts from the stack.
|
|
/// </summary>
|
|
private void PopElementNames(out string prefix, out string localName, out string ns) {
|
|
Debug.Assert(this.stkNames != null);
|
|
|
|
ns = this.stkNames.Pop();
|
|
localName = this.stkNames.Pop();
|
|
prefix = this.stkNames.Pop();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Throw an invalid state transition error.
|
|
/// </summary>
|
|
private void ThrowInvalidStateError(XPathNodeType constructorType) {
|
|
switch (constructorType) {
|
|
case XPathNodeType.Element:
|
|
case XPathNodeType.Root:
|
|
case XPathNodeType.Text:
|
|
case XPathNodeType.Comment:
|
|
case XPathNodeType.ProcessingInstruction:
|
|
throw new XslTransformException(Res.XmlIl_BadXmlState, new string[] {constructorType.ToString(), XmlStateToNodeType(this.xstate).ToString()});
|
|
|
|
case XPathNodeType.Attribute:
|
|
case XPathNodeType.Namespace:
|
|
if (this.depth == 1)
|
|
throw new XslTransformException(Res.XmlIl_BadXmlState, new string[] {constructorType.ToString(), this.rootType.ToString()});
|
|
|
|
if (this.xstate == XmlState.WithinContent)
|
|
throw new XslTransformException(Res.XmlIl_BadXmlStateAttr, string.Empty);
|
|
|
|
goto case XPathNodeType.Element;
|
|
|
|
default:
|
|
throw new XslTransformException(Res.XmlIl_BadXmlState, new string[] {"Unknown", XmlStateToNodeType(this.xstate).ToString()});
|
|
}
|
|
}
|
|
}
|
|
}
|