New tests.
[mono.git] / mcs / class / System.XML / System.Xml / XmlTextWriter2.cs
index 714d510e10406043c7d7641df3054307ec1b5cb6..a47d0edc80b5fb13c25696cb5891d83240d5f2d2 100644 (file)
@@ -195,6 +195,13 @@ namespace Mono.Xml
                        }
                }
 
+               enum XmlDeclState {
+                       Allow,
+                       Ignore,
+                       Auto,
+                       Prohibit,
+               }
+
                // Instance fields
 
                Stream base_stream;
@@ -209,7 +216,7 @@ namespace Mono.Xml
                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;
@@ -222,6 +229,7 @@ namespace Mono.Xml
                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;
@@ -232,6 +240,8 @@ namespace Mono.Xml
 
                char quote_char = '"';
 
+               bool v2;
+
                // Constructors
 
                public XmlTextWriter (string filename, Encoding encoding)
@@ -250,22 +260,47 @@ namespace Mono.Xml
 
                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)
@@ -274,13 +309,14 @@ namespace Mono.Xml
 
                        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)
@@ -300,41 +336,12 @@ namespace Mono.Xml
 #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
@@ -422,10 +429,16 @@ namespace Mono.Xml
 
                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 ();
@@ -468,6 +481,15 @@ namespace Mono.Xml
                        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");
@@ -486,8 +508,7 @@ namespace Mono.Xml
                        }
                        writer.Write ("?>");
 
-                       output_xmldecl = false;
-                       state = WriteState.Prolog;
+                       xmldecl_state = XmlDeclState.Ignore;
                }
 
                public override void WriteEndDocument ()
@@ -524,7 +545,7 @@ namespace Mono.Xml
                                throw StateError ("DocType");
                        node_state = XmlNodeType.DocumentType;
 
-                       if (output_xmldecl)
+                       if (xmldecl_state == XmlDeclState.Auto)
                                OutputAutoStartDocument ();
 
                        WriteIndent ();
@@ -593,8 +614,14 @@ namespace Mono.Xml
                        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.
@@ -607,7 +634,7 @@ namespace Mono.Xml
                                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 ();
@@ -654,7 +681,7 @@ namespace Mono.Xml
                        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);
@@ -704,7 +731,7 @@ namespace Mono.Xml
 
                        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) {
@@ -744,21 +771,25 @@ namespace Mono.Xml
                        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);
@@ -766,8 +797,6 @@ namespace Mono.Xml
                                }
                                writer.Write (info.LocalName);
                                writer.Write ('>');
-                       } else {
-                               writer.Write (" />");
                        }
 
                        state = WriteState.Content;
@@ -780,6 +809,14 @@ namespace Mono.Xml
                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");
 
@@ -804,7 +841,7 @@ namespace Mono.Xml
                                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
@@ -819,10 +856,14 @@ namespace Mono.Xml
                                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)
@@ -831,7 +872,7 @@ namespace Mono.Xml
                        }
 
                        if (indent_attributes)
-                               WriteIndent ();
+                               WriteIndentAttribute ();
                        else if (state != WriteState.Start)
                                writer.Write (' ');
 
@@ -876,14 +917,14 @@ namespace Mono.Xml
                                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);
                                        }
@@ -929,14 +970,25 @@ namespace Mono.Xml
                                        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":
@@ -973,14 +1025,17 @@ namespace Mono.Xml
                        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 '&#2D;'.");
                        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);
@@ -1003,7 +1058,7 @@ namespace Mono.Xml
                        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);
@@ -1027,7 +1082,7 @@ namespace Mono.Xml
                            XmlChar.IndexOfNonWhitespace (text) >= 0)
                                throw ArgumentError ("WriteWhitespace method accepts only whitespaces.");
 
-                       ShiftStateTopLevel ("Whitespace", true, false);
+                       ShiftStateTopLevel ("Whitespace", true, false, true);
 
                        writer.Write (text);
                }
@@ -1036,7 +1091,7 @@ namespace Mono.Xml
                {
                        if (text == null)
                                text = String.Empty;
-                       ShiftStateContent ("Text", false);
+                       ShiftStateContent ("CData", false);
 
                        if (StringUtil.IndexOf (text, "]]>") >= 0)
                                throw ArgumentError ("CDATA section must not contain ']]>'.");
@@ -1047,7 +1102,7 @@ namespace Mono.Xml
 
                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);
 
@@ -1063,7 +1118,7 @@ namespace Mono.Xml
 
                        // 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);
                }
@@ -1102,7 +1157,7 @@ namespace Mono.Xml
                        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);
@@ -1144,9 +1199,7 @@ namespace Mono.Xml
 
                        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);
@@ -1184,7 +1237,7 @@ namespace Mono.Xml
                {
                        CheckChunkRange (buffer, index, count);
 
-                       ShiftStateContent ("Text", false);
+                       ShiftStateContent ("BinHex", true);
 
                        XmlConvert.WriteBinHex (buffer, index, count, writer);
                }
@@ -1193,7 +1246,7 @@ namespace Mono.Xml
                {
                        CheckChunkRange (buffer, index, count);
 
-                       ShiftStateContent ("Text", true);
+                       ShiftStateContent ("Chars", true);
 
                        WriteEscapedBuffer (buffer, index, count,
                                state == WriteState.Attribute);
@@ -1203,7 +1256,7 @@ namespace Mono.Xml
                {
                        CheckChunkRange (buffer, index, count);
 
-                       ShiftStateContent ("Text", false);
+                       ShiftStateContent ("Raw text", false);
 
                        writer.Write (buffer, index, count);
                }
@@ -1212,22 +1265,33 @@ namespace Mono.Xml
 
                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 ()
+               {
+                       WriteIndentCore (-1, false);
+               }
+
+               void WriteIndentAttribute ()
+               {
+                       if (!WriteIndentCore (0, true))
+                               writer.Write (' '); // space is required instead.
+               }
+
+               bool WriteIndentCore (int nestFix, bool attribute)
                {
                        if (!indent)
-                               return;
-                       writer.Write (newline);
-                       for (int i = 0; i < open_count; i++)
+                               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 ()
@@ -1237,7 +1301,7 @@ namespace Mono.Xml
                        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
@@ -1246,7 +1310,9 @@ namespace Mono.Xml
                        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;
@@ -1255,12 +1321,24 @@ namespace Mono.Xml
                                        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;
                }
 
@@ -1276,8 +1354,9 @@ namespace Mono.Xml
                        case WriteState.Start:
                                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:
@@ -1286,14 +1365,12 @@ namespace Mono.Xml
                                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)