2004-08-25 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / GenericOutputter.cs
index fbe74655400c8e3bed3465493b6567842ab5fc68..17cbffb2668283c4075ff74987c3da2379e65661 100644 (file)
@@ -8,10 +8,33 @@
 // (C) 2003 Oleg Tkachenko, Atsushi Enomoto
 //
 
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
 using System;
 using System.Collections;
+using System.Globalization;
 using System.Xml;
 using System.IO;
+using System.Text;
 
 namespace Mono.Xml.Xsl
 {
@@ -21,12 +44,17 @@ namespace Mono.Xml.Xsl
        /// Implements attributes dublicate checking, nemaspace stuff and
        /// choosing of right Emitter implementation.
        /// </summary>
-       public class GenericOutputter : Outputter {     
+       internal class GenericOutputter : Outputter {   
                private Hashtable _outputs;
                //Current xsl:output 
                private XslOutput _currentOutput;
                //Underlying emitter
                private Emitter _emitter;
+               // destination TextWriter,
+               // which is pended until the actual output is determined.
+               private TextWriter pendingTextWriter;
+               // also, whitespaces before the first element are cached.
+               StringBuilder pendingFirstSpaces;
                //Outputting state
                private WriteState _state;
                // Collection of pending attributes. TODO: Can we make adding an attribute
@@ -40,12 +68,16 @@ namespace Mono.Xml.Xsl
                private Hashtable _currentNamespaceDecls;
                //Name table
                private NameTable _nt;
+               // Specified encoding (for TextWriter output)
+               Encoding _encoding;
                //Determines whether xsl:copy can output attribute-sets or not.
-               bool canProcessAttributes;
-               bool insideCData;
+               bool _canProcessAttributes;
+               bool _insideCData;
+               bool _isVariable;
 
-               private GenericOutputter (Hashtable outputs)
+               private GenericOutputter (Hashtable outputs, Encoding encoding)
                {
+                       _encoding = encoding;
                        _outputs = outputs;
                        _currentOutput = (XslOutput)outputs [String.Empty];
                        _state = WriteState.Start;
@@ -56,35 +88,68 @@ namespace Mono.Xml.Xsl
                        _currentNamespaceDecls = new Hashtable ();
                }
 
-               public GenericOutputter (XmlWriter writer, Hashtable outputs) 
-                       : this (outputs)
+               public GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding) 
+                       : this (writer, outputs, encoding, false)
+               {
+               }
+                
+               internal GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding, bool isVariable)
+                       : this (outputs, encoding)
                {
                        _emitter = new XmlWriterEmitter (writer);
                        _state = writer.WriteState;
+                       _isVariable = isVariable;
+               }
+
+               public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
+                       : this (outputs, encoding)
+               {
+                       this.pendingTextWriter = writer;
+               }
+
+                
+                internal GenericOutputter (TextWriter writer, Hashtable outputs)
+                        : this (writer, outputs, null)
+                {
+                }
+
+                internal GenericOutputter (XmlWriter writer, Hashtable outputs)
+                        : this (writer, outputs, null)
+                {
+                }
+                
+               private Emitter Emitter {
+                       get {
+                               if (_emitter == null)
+                                       DetermineOutputMethod (null, null);
+                               return _emitter;
+                       }
                }
 
-               public GenericOutputter (TextWriter writer, Hashtable outputs)
-                       : this (outputs)
-               {                       
-                       XslOutput xslOutput = (XslOutput)outputs [String.Empty];
+               private void DetermineOutputMethod (string localName, string ns)
+               {
+                       XslOutput xslOutput = (XslOutput)_outputs [String.Empty];
                        switch (xslOutput.Method) {
-                               
+                               case OutputMethod.Unknown:
+                                       if (localName != null && String.Compare (localName, "html", true, CultureInfo.InvariantCulture) == 0 && ns == String.Empty)
+                                               goto case OutputMethod.HTML;
+                                       goto case OutputMethod.XML;
                                case OutputMethod.HTML:
-                                       _emitter = new HtmlEmitter (writer, xslOutput);
+                                       _emitter = new HtmlEmitter (pendingTextWriter, xslOutput);
                                        break;
-                               case OutputMethod.Unknown: //TODO: handle xml vs html
                                case OutputMethod.XML:
-                                       XmlTextWriter w = new XmlTextWriter (writer);
+                                       XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
                                        if (xslOutput.Indent == "yes")
                                                w.Formatting = Formatting.Indented;
                                        _emitter = new XmlWriterEmitter (w);                                    
                                        break;
                                case OutputMethod.Text:
-                                       _emitter = new TextEmitter (writer);
+                                       _emitter = new TextEmitter (pendingTextWriter);
                                        break;
                                case OutputMethod.Custom:
-                                       throw new NotImplementedException ("Custom output method is not implemented yet.");
-                       }                                               
+                                       throw new NotSupportedException ("Custom output method is not supported in this version.");
+                       }
+                       pendingTextWriter = null;
                }
 
                /// <summary>
@@ -106,42 +171,54 @@ namespace Mono.Xml.Xsl
                                        Attribute attr = pendingAttributes [i];
                                        string prefix = attr.Prefix;
                                        if (prefix == String.Empty)
-                                               prefix = _nsManager.LookupPrefix (attr.Namespace);
-                                       _emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
+                                               prefix = _nsManager.LookupPrefix (attr.Namespace, false);
+                                       Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
                                }
-                               foreach (string prefix in _currentNsPrefixes) {
+                               for (int i = 0; i < _currentNsPrefixes.Count; i++) {
+                                       string prefix = (string) _currentNsPrefixes [i];
                                        string uri = _currentNamespaceDecls [prefix] as string;
                                        if (prefix != String.Empty)
-                                               _emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
+                                               Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
                                        else
-                                               _emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
+                                               Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
                                }
                                _currentNsPrefixes.Clear ();
                                _currentNamespaceDecls.Clear ();
                                //Attributes flushed, state is Content now                              
                                _state = WriteState.Content;
                        }
-                       canProcessAttributes = false;
+                       _canProcessAttributes = false;
                }
 
                #region Outputter's methods implementation
                
                public override void WriteStartDocument ()
-               {                       
+               {
+                       if (_isVariable)
+                               return;
+
                        if (!_currentOutput.OmitXmlDeclaration)
-                               _emitter.WriteStartDocument (_currentOutput.Standalone);
+                               Emitter.WriteStartDocument (_encoding != null ? _encoding : _currentOutput.Encoding, _currentOutput.Standalone);
                        
                        _state = WriteState.Prolog;
                }
                
                public override void WriteEndDocument ()
                {
-                       _emitter.WriteEndDocument ();                           
+                       Emitter.WriteEndDocument ();                            
                }
 
                int _nsCount;
                public override void WriteStartElement (string prefix, string localName, string nsURI)
                {
+                       if (_emitter == null) {
+                               this.DetermineOutputMethod (localName, nsURI);
+                               if (pendingFirstSpaces != null) {
+                                       WriteWhitespace (pendingFirstSpaces.ToString ());
+                                       pendingFirstSpaces = null;
+                               }
+                       }
+
                        if (_state == WriteState.Start)
                                WriteStartDocument ();
 
@@ -149,14 +226,14 @@ namespace Mono.Xml.Xsl
                                //Seems to be the first element - take care of Doctype
                                // Note that HTML does not require SYSTEM identifier.
                                if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
-                                       _emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
+                                       Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
                                                _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
                        }
                        CheckState ();
-                       _emitter.WriteStartElement (prefix, localName, nsURI);
+                       Emitter.WriteStartElement (prefix, localName, nsURI);
                        _state = WriteState.Element;                                            
                        pendingAttributesPos = 0;
-                       canProcessAttributes = true;
+                       _canProcessAttributes = true;
                }
 
                public override void WriteEndElement ()
@@ -173,9 +250,9 @@ namespace Mono.Xml.Xsl
                {
                        CheckState ();
                        if (fullEndElement)
-                               _emitter.WriteFullEndElement ();
+                               Emitter.WriteFullEndElement ();
                        else
-                               _emitter.WriteEndElement ();
+                               Emitter.WriteEndElement ();
                        _state = WriteState.Content;
                        //Pop namespace scope
                        _nsManager.PopScope ();
@@ -219,14 +296,14 @@ namespace Mono.Xml.Xsl
 
                public override void WriteNamespaceDecl (string prefix, string nsUri)
                {
-                       if (_nsManager.LookupNamespace (prefix) == nsUri)
+                       if (_nsManager.LookupNamespace (prefix, false) == nsUri)
                                return;
 
                        if (prefix == String.Empty) {
                                //Default namespace
                                if (_nsManager.DefaultNamespace != nsUri)
                                        _nsManager.AddNamespace (prefix, nsUri);
-                       } else if (_nsManager.LookupPrefix (nsUri) == null)
+                       } else if (_nsManager.LookupPrefix (nsUri, false) == null)
                                //That's new namespace - add it to the collection
                                _nsManager.AddNamespace (prefix, nsUri);
 
@@ -240,51 +317,59 @@ namespace Mono.Xml.Xsl
                public override void WriteComment (string text)
                {
                        CheckState ();
-                       _emitter.WriteComment (text);
+                       Emitter.WriteComment (text);
                }
 
                public override void WriteProcessingInstruction (string name, string text)
                {
                        CheckState ();
-                       _emitter.WriteProcessingInstruction (name, text);
+                       Emitter.WriteProcessingInstruction (name, text);
                }
 
                public override void WriteString (string text)
                {
                        CheckState ();
-                       if (insideCData)
-                               _emitter.WriteCDataSection (text);
+                       if (_insideCData)
+                               Emitter.WriteCDataSection (text);
                        else
-                               _emitter.WriteString (text);
+                               Emitter.WriteString (text);
                }
 
                public override void WriteRaw (string data)
                {
                        CheckState ();
-                       _emitter.WriteRaw (data);
+                       Emitter.WriteRaw (data);
                }
 
                public override void WriteWhitespace (string text)
                {
-                       CheckState ();
-                       _emitter.WriteString (text);
+                       if (_emitter == null) {
+                               if (pendingFirstSpaces == null)
+                                       pendingFirstSpaces = new StringBuilder ();
+                               pendingFirstSpaces.Append (text);
+                               if (_state == WriteState.Start)
+                                       _state = WriteState.Prolog;
+                       } else {
+                               CheckState ();
+                               Emitter.WriteWhitespace (text);
+                       }
                }
 
                public override void Done ()
                {
-                       _emitter.Done ();
+                       Emitter.Done ();
                        _state = WriteState.Closed;
                }
 
                public override bool CanProcessAttributes {
-                       get { return canProcessAttributes; }
+                       get { return _canProcessAttributes; }
                }
 
                public override WriteState WriteState { get { return _state; } }
 
                public override bool InsideCDataSection {
-                       get { return insideCData; }
-                       set { insideCData = value; }
+                       get { return _insideCData; }
+                       set { _insideCData = value; }
                }
                #endregion
        }