}
}
+ enum XmlDeclState {
+ Allow,
+ Ignore,
+ Auto,
+ Prohibit,
+ }
+
// Instance fields
Stream base_stream;
bool close_output_stream = true;
bool ignore_encoding;
bool namespaces = true;
- bool output_xmldecl = false;
+ XmlDeclState xmldecl_state = XmlDeclState.Allow;
bool check_character_validity;
NewLineHandling newline_handling = NewLineHandling.None;
+ bool is_document_entity;
WriteState state = WriteState.Start;
XmlNodeType node_state = XmlNodeType.None;
XmlNamespaceManager nsmanager;
XmlNodeInfo [] elements = new XmlNodeInfo [10];
Stack new_local_namespaces = new Stack ();
ArrayList explicit_nsdecls = new ArrayList ();
+ NamespaceHandling namespace_handling;
bool indent;
int indent_count = 2;
char quote_char = '"';
+ bool v2;
+
// Constructors
public XmlTextWriter (string filename, Encoding encoding)
public XmlTextWriter (TextWriter writer)
{
+ if (writer == null)
+ throw new ArgumentNullException ("writer");
+ ignore_encoding = (writer.Encoding == null);
Initialize (writer);
allow_doc_fragment = true;
}
#if NET_2_0
- XmlTextWriter (
- TextWriter writer, XmlWriterSettings settings)
+ internal XmlTextWriter (
+ TextWriter writer, XmlWriterSettings settings, bool closeOutput)
{
+ v2 = true;
+
if (settings == null)
settings = new XmlWriterSettings ();
Initialize (writer);
- close_output_stream = settings.CloseOutput;
+ close_output_stream = closeOutput;
allow_doc_fragment =
- settings.ConformanceLevel != System.Xml.ConformanceLevel.Document;
+ settings.ConformanceLevel != ConformanceLevel.Document;
+ switch (settings.ConformanceLevel) {
+ case ConformanceLevel.Auto:
+ xmldecl_state = settings.OmitXmlDeclaration ? XmlDeclState.Ignore : XmlDeclState.Allow;
+ break;
+ case ConformanceLevel.Document:
+ // LAMESPEC:
+ // On MSDN, XmlWriterSettings.OmitXmlDeclaration is documented as:
+ // "The XML declaration is always written if
+ // ConformanceLevel is set to Document, even
+ // if OmitXmlDeclaration is set to true. "
+ // but it is incorrect. It does consider
+ // OmitXmlDeclaration property.
+ xmldecl_state = settings.OmitXmlDeclaration ? XmlDeclState.Ignore : XmlDeclState.Auto;
+ break;
+ case ConformanceLevel.Fragment:
+ xmldecl_state = XmlDeclState.Prohibit;
+ break;
+ }
+ if (settings.Indent)
+ Formatting = Formatting.Indented;
indent_string = settings.IndentChars == null ?
String.Empty : settings.IndentChars;
if (settings.NewLineChars != null)
check_character_validity = settings.CheckCharacters;
newline_handling = settings.NewLineHandling;
- if (settings.OmitXmlDeclaration)
- output_xmldecl = false;
+ namespace_handling = settings.NamespaceHandling;
}
#endif
void Initialize (TextWriter writer)
{
+ if (writer == null)
+ throw new ArgumentNullException ("writer");
XmlNameTable name_table = new NameTable ();
this.writer = writer;
if (writer is StreamWriter)
#if NET_2_0
// 2.0 XmlWriterSettings support
- internal bool CheckCharacters {
- set { check_character_validity = value; }
- }
-
- internal bool CloseOutput {
- set { close_output_stream = value; }
- }
-
// As for ConformanceLevel, MS.NET is inconsistent with
// MSDN documentation. For example, even if ConformanceLevel
// is set as .Auto, multiple WriteStartDocument() calls
// result in an error.
// ms-help://MS.NETFramework.v20.en/wd_xml/html/7db8802b-53d8-4735-a637-4d2d2158d643.htm
- [MonoTODO]
- internal ConformanceLevel ConformanceLevel {
- set {
- allow_doc_fragment = (value == System.Xml.ConformanceLevel.Fragment);
- }
- }
-
- internal string IndentChars {
- set { indent_string = (value == null) ? String.Empty : value; }
- }
-
- internal string NewLineChars {
- set { newline = (value == null) ? String.Empty : value; }
- }
-
- internal bool NewLineOnAttributes {
- set { indent_attributes = value; }
- }
- internal bool OmitXmlDeclaration {
- set { output_xmldecl = !value; }
- }
#endif
// Literal Output Control
public Formatting Formatting {
get { return indent ? Formatting.Indented : Formatting.None; }
set {
- if (state != WriteState.Start)
- throw InvalidOperation ("Formatting must be set before it is actually used to write output.");
+ // Someone thinks it should be settable even
+ // after writing some content (bug #78148).
+ // I totally disagree but here is the fix.
+
+ //if (state != WriteState.Start)
+ // throw InvalidOperation ("Formatting must be set before it is actually used to write output.");
indent = (value == Formatting.Indented);
}
}
public override void Close ()
{
- if (state == WriteState.Attribute)
- WriteEndAttribute ();
- while (open_count > 0)
- WriteEndElement ();
+#if NET_2_0
+ if (state != WriteState.Error) {
+#endif
+ if (state == WriteState.Attribute)
+ WriteEndAttribute ();
+ while (open_count > 0)
+ WriteEndElement ();
+#if NET_2_0
+ }
+#endif
if (close_output_stream)
writer.Close ();
public override void WriteStartDocument ()
{
WriteStartDocumentCore (false, false);
+ is_document_entity = true;
}
public override void WriteStartDocument (bool standalone)
{
WriteStartDocumentCore (true, standalone);
+ is_document_entity = true;
}
void WriteStartDocumentCore (bool outputStd, bool standalone)
if (state != WriteState.Start)
throw StateError ("XmlDeclaration");
+ switch (xmldecl_state) {
+ case XmlDeclState.Ignore:
+ return;
+ case XmlDeclState.Prohibit:
+ throw InvalidOperation ("WriteStartDocument cannot be called when ConformanceLevel is Fragment.");
+ }
+
+ state = WriteState.Prolog;
+
writer.Write ("<?xml version=");
writer.Write (quote_char);
writer.Write ("1.0");
}
writer.Write ("?>");
- output_xmldecl = false;
- state = WriteState.Prolog;
+ xmldecl_state = XmlDeclState.Ignore;
}
public override void WriteEndDocument ()
WriteEndElement ();
state = WriteState.Start;
+ is_document_entity = false;
}
// DocType Declaration
throw StateError ("DocType");
node_state = XmlNodeType.DocumentType;
- if (output_xmldecl)
+ if (xmldecl_state == XmlDeclState.Auto)
OutputAutoStartDocument ();
WriteIndent ();
if (!namespaces && prefix.Length > 0)
throw ArgumentError ("Namespace prefix is disabled in this XmlTextWriter.");
- if (prefix.Length > 0 && namespaceUri == null)
- throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
+ // If namespace URI is empty, then either prefix
+ // must be empty as well, or there is an
+ // existing namespace mapping for the prefix.
+ if (prefix.Length > 0 && namespaceUri == null) {
+ namespaceUri = nsmanager.LookupNamespace (prefix, false);
+ if (namespaceUri == null || namespaceUri.Length == 0)
+ throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
+ }
// Considering the fact that WriteStartAttribute()
// automatically changes argument namespaceURI, this
// is kind of silly implementation. See bug #77094.
throw new ArgumentException ("A prefix cannot be equivalent to \"xml\" in case-insensitive match.");
- if (output_xmldecl)
+ if (xmldecl_state == XmlDeclState.Auto)
OutputAutoStartDocument ();
if (state == WriteState.Element)
CloseStartElement ();
open_count++;
if (namespaces && namespaceUri != null) {
- string oldns = nsmanager.LookupNamespace (prefix);
+ string oldns = nsmanager.LookupNamespace (prefix, false);
if (oldns != namespaceUri) {
nsmanager.AddNamespace (prefix, namespaceUri);
new_local_namespaces.Push (prefix);
for (int i = idx; i < explicit_nsdecls.Count; i++) {
string prefix = (string) explicit_nsdecls [i];
- string ns = nsmanager.LookupNamespace (prefix);
+ string ns = nsmanager.LookupNamespace (prefix, false);
if (ns == null)
continue; // superceded
if (prefix.Length > 0) {
if (open_count == 0)
throw InvalidOperation ("There is no more open element.");
- bool isEmpty = state != WriteState.Content;
+ // bool isEmpty = state != WriteState.Content;
CloseStartElementCore ();
nsmanager.PopScope ();
- bool doIndent = !elements [open_count - 1].HasSimple;
- if (open_count > 1)
- doIndent &= !elements [open_count - 2].HasSimple;
+
+ if (state == WriteState.Element) {
+ if (full)
+ writer.Write ('>');
+ else
+ writer.Write (" />");
+ }
+
+ if (full || state == WriteState.Content)
+ WriteIndentEndElement ();
+
XmlNodeInfo info = elements [--open_count];
if (full || state == WriteState.Content) {
- if (state == WriteState.Element)
- writer.Write ('>');
- if (doIndent && (full || !isEmpty))
- DoWriteIndent ();
writer.Write ("</");
if (info.Prefix.Length > 0) {
writer.Write (info.Prefix);
}
writer.Write (info.LocalName);
writer.Write ('>');
- } else {
- writer.Write (" />");
}
state = WriteState.Content;
public override void WriteStartAttribute (
string prefix, string localName, string namespaceUri)
{
+ // LAMESPEC: this violates the expected behavior of
+ // this method, as it incorrectly allows unbalanced
+ // output of attributes. Microfot changes description
+ // on its behavior at their will, regardless of
+ // ECMA description.
+ if (state == WriteState.Attribute)
+ WriteEndAttribute ();
+
if (state != WriteState.Element && state != WriteState.Start)
throw StateError ("Attribute");
if (prefix == "xml")
namespaceUri = XmlNamespace;
// infer namespace URI.
- else if ((object) namespaceUri == null) {
+ else if ((object) namespaceUri == null || (v2 && namespaceUri.Length == 0)) {
if (isNSDecl)
namespaceUri = XmlnsNamespace;
else
if (isNSDecl && namespaceUri != XmlnsNamespace)
throw ArgumentError (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));
- // If namespace URI is empty, then prefix
- // must be empty as well.
- if (prefix.Length > 0 && namespaceUri.Length == 0)
- throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
+ // If namespace URI is empty, then either prefix
+ // must be empty as well, or there is an
+ // existing namespace mapping for the prefix.
+ if (prefix.Length > 0 && namespaceUri.Length == 0) {
+ namespaceUri = nsmanager.LookupNamespace (prefix, false);
+ if (namespaceUri == null || namespaceUri.Length == 0)
+ throw ArgumentError ("Namespace URI must not be null when prefix is not an empty string.");
+ }
// Dive into extremely complex procedure.
if (!isNSDecl && namespaceUri.Length > 0)
}
if (indent_attributes)
- WriteIndent ();
+ WriteIndentAttribute ();
else if (state != WriteState.Start)
writer.Write (' ');
bool mockup = false;
if (prefix.Length == 0) {
prefix = LookupPrefix (ns);
- if (prefix != null)
+ if (prefix != null && prefix.Length > 0)
return prefix;
mockup = true;
} else {
prefix = nsmanager.NameTable.Add (prefix);
- string existing = nsmanager.LookupNamespace (prefix);
+ string existing = nsmanager.LookupNamespace (prefix, true);
if (existing == ns)
return prefix;
if (existing != null) {
// See code comment on the head of
// this source file.
nsmanager.RemoveNamespace (prefix, existing);
- if (nsmanager.LookupNamespace (prefix) != existing) {
+ if (nsmanager.LookupNamespace (prefix, true) != existing) {
mockup = true;
nsmanager.AddNamespace (prefix, existing);
}
if (preserved_name.Length > 0 &&
value.Length == 0)
throw ArgumentError ("Non-empty prefix must be mapped to non-empty namespace URI.");
- string existing = nsmanager.LookupNamespace (preserved_name);
- explicit_nsdecls.Add (preserved_name);
- if (open_count > 0 &&
- elements [open_count - 1].NS == String.Empty &&
- elements [open_count - 1].Prefix == preserved_name)
- ; // do nothing
- else if (existing != value)
- nsmanager.AddNamespace (preserved_name, value);
+ string existing = nsmanager.LookupNamespace (preserved_name, false);
+
+ // consider OmitDuplicates here.
+ if ((namespace_handling & NamespaceHandling.OmitDuplicates) == 0 || existing != value)
+ explicit_nsdecls.Add (preserved_name);
+
+ if (open_count > 0) {
+
+ if (v2 &&
+ elements [open_count - 1].Prefix == preserved_name &&
+ elements [open_count - 1].NS != value)
+ throw new XmlException (String.Format ("Cannot redefine the namespace for prefix '{0}' used at current element", preserved_name));
+
+ if (elements [open_count - 1].NS == String.Empty &&
+ elements [open_count - 1].Prefix == preserved_name)
+ ; // do nothing
+ else if (existing != value)
+ nsmanager.AddNamespace (preserved_name, value);
+ }
} else {
switch (preserved_name) {
case "lang":
if (text == null)
throw ArgumentError ("text");
- WriteIndent ();
-
if (text.Length > 0 && text [text.Length - 1] == '-')
throw ArgumentError ("An input string to WriteComment method must not end with '-'. Escape it with 'D;'.");
if (StringUtil.IndexOf (text, "--") > 0)
throw ArgumentError ("An XML comment cannot end with \"-\".");
- ShiftStateTopLevel ("Comment", false, false);
+ if (state == WriteState.Attribute || state == WriteState.Element)
+ CloseStartElement ();
+
+ WriteIndent ();
+
+ ShiftStateTopLevel ("Comment", false, false, false);
writer.Write ("<!--");
writer.Write (text);
if (StringUtil.IndexOf (text, "?>") > 0)
throw ArgumentError ("Processing instruction cannot contain \"?>\" as its value.");
- ShiftStateTopLevel ("ProcessingInstruction", false, name == "xml");
+ ShiftStateTopLevel ("ProcessingInstruction", false, name == "xml", false);
writer.Write ("<?");
writer.Write (name);
XmlChar.IndexOfNonWhitespace (text) >= 0)
throw ArgumentError ("WriteWhitespace method accepts only whitespaces.");
- ShiftStateTopLevel ("Whitespace", true, false);
+ ShiftStateTopLevel ("Whitespace", true, false, true);
writer.Write (text);
}
{
if (text == null)
text = String.Empty;
- ShiftStateContent ("Text", false);
+ ShiftStateContent ("CData", false);
if (StringUtil.IndexOf (text, "]]>") >= 0)
throw ArgumentError ("CDATA section must not contain ']]>'.");
writer.Write ("<![CDATA[");
- CheckTextValidity (text);
- writer.Write (text);
+ WriteCheckedString (text);
writer.Write ("]]>");
}
public override void WriteString (string text)
{
- if (text == null || text.Length == 0)
+ if (text == null || (text.Length == 0 && !v2))
return; // do nothing, including state transition.
ShiftStateContent ("Text", true);
- CheckTextValidity (text);
WriteEscapedString (text, state == WriteState.Attribute);
}
// LAMESPEC: It rejects XMLDecl while it allows
// DocType which could consist of non well-formed XML.
- ShiftStateTopLevel ("Raw string", true, true);
+ ShiftStateTopLevel ("Raw string", true, true, true);
- writer.Write (raw, false);
+ writer.Write (raw);
}
public override void WriteCharEntity (char ch)
if (!XmlChar.IsName (name))
throw ArgumentError ("Argument name must be a valid XML name.");
- ShiftStateContent ("Character", true);
+ ShiftStateContent ("Entity reference", true);
writer.Write ('&');
writer.Write (name);
ShiftStateContent ("QName", true);
- string prefix =
- state == WriteState.Content || ns.Length > 0 ?
- LookupPrefix (ns) : String.Empty;
+ string prefix = ns.Length > 0 ? LookupPrefix (ns) : String.Empty;
if (prefix == null) {
if (state == WriteState.Attribute)
prefix = MockupPrefix (ns, false);
{
CheckChunkRange (buffer, index, count);
- ShiftStateContent ("Text", false);
+ ShiftStateContent ("BinHex", true);
XmlConvert.WriteBinHex (buffer, index, count, writer);
}
{
CheckChunkRange (buffer, index, count);
- ShiftStateContent ("Text", false);
+ ShiftStateContent ("Chars", true);
WriteEscapedBuffer (buffer, index, count,
state == WriteState.Attribute);
{
CheckChunkRange (buffer, index, count);
- ShiftStateContent ("Text", false);
+ ShiftStateContent ("Raw text", false);
writer.Write (buffer, index, count);
}
void WriteIndent ()
{
- if (!indent || (open_count > 0 &&
- elements [open_count - 1].HasSimple))
- return;
- if (state != WriteState.Start)
- writer.Write (newline);
- for (int i = 0; i < open_count; i++)
- writer.Write (indent_string);
+ WriteIndentCore (0, false);
}
- void DoWriteIndent ()
+ void WriteIndentEndElement ()
{
- if (!indent)
- return;
- writer.Write (newline);
- for (int i = 0; i < open_count; i++)
- writer.Write (indent_string);
+ WriteIndentCore (-1, false);
}
- void CheckTextValidity (string text)
+ void WriteIndentAttribute ()
{
- if (!check_character_validity)
- return;
- int idx = XmlChar.IndexOfInvalid (text, true);
- if (idx >= 0)
- throw ArgumentError (String.Format ("Invalid text character was found at string index {0}: &#x{1:X};", idx, (int) text [idx]));
+ if (!WriteIndentCore (0, true))
+ writer.Write (' '); // space is required instead.
+ }
+
+ bool WriteIndentCore (int nestFix, bool attribute)
+ {
+ if (!indent)
+ return false;
+ for (int i = open_count - 1; i >= 0; i--)
+ if (!attribute && elements [i].HasSimple)
+ return false;
+
+ if (state != WriteState.Start)
+ writer.Write (newline);
+ for (int i = 0; i < open_count + nestFix; i++)
+ writer.Write (indent_string);
+ return true;
}
void OutputAutoStartDocument ()
WriteStartDocumentCore (false, false);
}
- void ShiftStateTopLevel (string occured, bool allowAttribute, bool dontCheckXmlDecl)
+ void ShiftStateTopLevel (string occured, bool allowAttribute, bool dontCheckXmlDecl, bool isCharacter)
{
switch (state) {
#if NET_2_0
case WriteState.Closed:
throw StateError (occured);
case WriteState.Start:
- if (output_xmldecl && !dontCheckXmlDecl)
+ if (isCharacter)
+ CheckMixedContentState ();
+ if (xmldecl_state == XmlDeclState.Auto && !dontCheckXmlDecl)
OutputAutoStartDocument ();
state = WriteState.Prolog;
break;
break;
goto case WriteState.Closed;
case WriteState.Element:
+ if (isCharacter)
+ CheckMixedContentState ();
CloseStartElement ();
break;
+ case WriteState.Content:
+ if (isCharacter)
+ CheckMixedContentState ();
+ break;
}
- if (open_count > 0 &&
- state != WriteState.Attribute)
+ }
+
+ void CheckMixedContentState ()
+ {
+// if (open_count > 0 &&
+// state != WriteState.Attribute)
+// elements [open_count - 1].HasSimple = true;
+ if (open_count > 0)
elements [open_count - 1].HasSimple = true;
}
throw StateError (occured);
case WriteState.Prolog:
case WriteState.Start:
- if (!allow_doc_fragment)
+ if (!allow_doc_fragment || is_document_entity)
goto case WriteState.Closed;
- if (output_xmldecl)
+ if (xmldecl_state == XmlDeclState.Auto)
OutputAutoStartDocument ();
+ CheckMixedContentState ();
state = WriteState.Content;
break;
case WriteState.Attribute:
goto case WriteState.Closed;
case WriteState.Element:
CloseStartElement ();
+ CheckMixedContentState ();
break;
case WriteState.Content:
+ CheckMixedContentState ();
break;
}
-
- if (open_count > 0 &&
- state != WriteState.Attribute)
- elements [open_count - 1].HasSimple = true;
}
void WriteEscapedString (string text, bool isAttribute)
int idx = text.IndexOfAny (escaped);
if (idx >= 0) {
char [] arr = text.ToCharArray ();
- writer.Write (arr, 0, idx);
+ WriteCheckedBuffer (arr, 0, idx);
WriteEscapedBuffer (
arr, idx, arr.Length - idx, isAttribute);
} else {
- writer.Write (text);
+ WriteCheckedString (text);
}
}
- void WriteEscapedBuffer (char [] text, int index, int length,
- bool isAttribute)
+ void WriteCheckedString (string s)
{
+ int i = XmlChar.IndexOfInvalid (s, true);
+ if (i >= 0) {
+ char [] arr = s.ToCharArray ();
+ writer.Write (arr, 0, i);
+ WriteCheckedBuffer (arr, i, arr.Length - i);
+ } else {
+ // no invalid character.
+ writer.Write (s);
+ }
+ }
- if (check_character_validity) {
- int idx = XmlChar.IndexOfInvalid (text, index, length, true);
- if (idx >= 0)
+ void WriteCheckedBuffer (char [] text, int idx, int length)
+ {
+ int start = idx;
+ int end = idx + length;
+ while ((idx = XmlChar.IndexOfInvalid (text, start, length, true)) >= 0) {
+ if (check_character_validity) // actually this is one time pass.
throw ArgumentError (String.Format ("Input contains invalid character at {0} : &#x{1:X};", idx, (int) text [idx]));
+ if (start < idx)
+ writer.Write (text, start, idx - start);
+ writer.Write ("&#x");
+ writer.Write (((int) text [idx]).ToString (
+ "X",
+ CultureInfo.InvariantCulture));
+ writer.Write (';');
+ length -= idx - start + 1;
+ start = idx + 1;
}
+ if (start < end)
+ writer.Write (text, start, end - start);
+ }
+ void WriteEscapedBuffer (char [] text, int index, int length,
+ bool isAttribute)
+ {
int start = index;
int end = index + length;
for (int i = start; i < end; i++) {
case '<':
case '>':
if (start < i)
- writer.Write (text, start, i - start);
+ WriteCheckedBuffer (text, start, i - start);
writer.Write ('&');
switch (text [i]) {
case '&': writer.Write ("amp;"); break;
goto case '\n';
case '\n':
if (start < i)
- writer.Write (text, start, i - start);
+ WriteCheckedBuffer (text, start, i - start);
if (isAttribute) {
writer.Write (text [i] == '\r' ?
"
" : "
");
start = i + 1;
}
if (start < end)
- writer.Write (text, start, end - start);
+ WriteCheckedBuffer (text, start, end - start);
}
// Exceptions