New test.
[mono.git] / mcs / class / System.XML / System.Xml / XmlTextWriter2.cs
index 6ac77c08e01a4f90430261ecf6bdce764e914e60..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,8 +309,7 @@ namespace Mono.Xml
 
                        check_character_validity = settings.CheckCharacters;
                        newline_handling = settings.NewLineHandling;
-                       if (settings.OmitXmlDeclaration)
-                               output_xmldecl = false;
+                       namespace_handling = settings.NamespaceHandling;
                }
 #endif
 
@@ -302,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
@@ -424,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 ();
@@ -470,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");
@@ -488,8 +508,7 @@ namespace Mono.Xml
                        }
                        writer.Write ("?>");
 
-                       output_xmldecl = false;
-                       state = WriteState.Prolog;
+                       xmldecl_state = XmlDeclState.Ignore;
                }
 
                public override void WriteEndDocument ()
@@ -526,7 +545,7 @@ namespace Mono.Xml
                                throw StateError ("DocType");
                        node_state = XmlNodeType.DocumentType;
 
-                       if (output_xmldecl)
+                       if (xmldecl_state == XmlDeclState.Auto)
                                OutputAutoStartDocument ();
 
                        WriteIndent ();
@@ -595,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.
@@ -609,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 ();
@@ -746,7 +771,7 @@ 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 ();
 
@@ -784,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");
 
@@ -808,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
@@ -823,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)
@@ -835,7 +872,7 @@ namespace Mono.Xml
                        }
 
                        if (indent_attributes)
-                               WriteIndent ();
+                               WriteIndentAttribute ();
                        else if (state != WriteState.Start)
                                writer.Write (' ');
 
@@ -934,13 +971,24 @@ namespace Mono.Xml
                                            value.Length == 0)
                                                throw ArgumentError ("Non-empty prefix must be mapped to non-empty namespace URI.");
                                        string existing = nsmanager.LookupNamespace (preserved_name, false);
-                                       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);
+
+                                       // 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":
@@ -1054,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);
 
@@ -1151,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);
@@ -1219,26 +1265,33 @@ namespace Mono.Xml
 
                void WriteIndent ()
                {
-                       WriteIndentCore (0);
+                       WriteIndentCore (0, false);
                }
 
                void WriteIndentEndElement ()
                {
-                       WriteIndentCore (-1);
+                       WriteIndentCore (-1, false);
                }
 
-               void WriteIndentCore (int nestFix)
+               void WriteIndentAttribute ()
+               {
+                       if (!WriteIndentCore (0, true))
+                               writer.Write (' '); // space is required instead.
+               }
+
+               bool WriteIndentCore (int nestFix, bool attribute)
                {
                        if (!indent)
-                               return;
+                               return false;
                        for (int i = open_count - 1; i >= 0; i--)
-                               if (elements [i].HasSimple)
-                                       return;
+                               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 ()
@@ -1259,7 +1312,7 @@ namespace Mono.Xml
                        case WriteState.Start:
                                if (isCharacter)
                                        CheckMixedContentState ();
-                               if (output_xmldecl && !dontCheckXmlDecl)
+                               if (xmldecl_state == XmlDeclState.Auto && !dontCheckXmlDecl)
                                        OutputAutoStartDocument ();
                                state = WriteState.Prolog;
                                break;
@@ -1301,7 +1354,7 @@ 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;