2005-02-23 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / GenericOutputter.cs
index df90154ae0c877f746212018b37cf611cac5e4fd..3faa3e212178d7a0a30eddc90bab32dcf4303a67 100644 (file)
@@ -3,14 +3,38 @@
 //
 // Authors:
 //     Oleg Tkachenko (oleg@tkachenko.com)
+//     Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
 //     
-// (C) 2003 Oleg Tkachenko
+// (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
 {
@@ -20,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
@@ -35,44 +64,103 @@ namespace Mono.Xml.Xsl
                int pendingAttributesPos = 0;
                //Namespace manager. Subject to optimization.
                private XmlNamespaceManager _nsManager;
+               private ArrayList _currentNsPrefixes;
+               private Hashtable _currentNamespaceDecls;
+               // See CheckState(). This is just a cache.
+               private ArrayList newNamespaces = new ArrayList();
                //Name table
                private NameTable _nt;
-               
-               private GenericOutputter (Hashtable outputs)
+               // Specified encoding (for TextWriter output)
+               Encoding _encoding;
+               //Determines whether xsl:copy can output attribute-sets or not.
+               bool _canProcessAttributes;
+               bool _insideCData;
+               bool _isVariable;
+               bool _omitXmlDeclaration;
+               int _xpCount;
+
+               private GenericOutputter (Hashtable outputs, Encoding encoding)
                {
+                       _encoding = encoding;
                        _outputs = outputs;
                        _currentOutput = (XslOutput)outputs [String.Empty];
-                       _state = WriteState.Start;
+                       _state = WriteState.Prolog;
                        //TODO: Optimize using nametable
                        _nt = new NameTable ();
                        _nsManager = new XmlNamespaceManager (_nt);
+                       _currentNsPrefixes = new ArrayList ();
+                       _currentNamespaceDecls = new Hashtable ();
+                       _omitXmlDeclaration = false;
+               }
+
+               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;
+                       _omitXmlDeclaration = true; // .Net never writes XML declaration via XmlWriter
                }
 
-               public GenericOutputter (XmlWriter writer, Hashtable outputs) 
-                       : this (outputs)
+               public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
+                       : this (outputs, encoding)
                {
-                       _emitter = new XmlWriterEmitter (writer);                       
+                       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: //TODO: handle xml vs html
+                               default: // .Custom format is not supported, only handled as unknown
+                               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 (pendingTextWriter, xslOutput);
+                                       break;
                                case OutputMethod.XML:
-                                       //TODO: XmlTextEmitter goes here
-                                       //_emitter = new XmlTextEmitter (writer);
-                                       _emitter = new XmlWriterEmitter (new XmlTextWriter (writer));                                   
+                                       XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
+                                       if (xslOutput.Indent == "yes")
+                                               w.Formatting = Formatting.Indented;
+
+                                       _emitter = new XmlWriterEmitter (w);                                    
+                                       if (!_omitXmlDeclaration && !xslOutput.OmitXmlDeclaration)
+                                               _emitter.WriteStartDocument (
+                                                       _encoding != null ? _encoding : xslOutput.Encoding,
+                                                       xslOutput.Standalone);
+
                                        break;
-                               case OutputMethod.HTML:
-                                       throw new NotImplementedException ("HTML output method is not implemented yet.");
                                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.");
-                       }                                               
+                       }
+                       pendingTextWriter = null;
                }
 
                /// <summary>
@@ -80,72 +168,119 @@ namespace Mono.Xml.Xsl
                /// when it's appropriate.
                /// </summary>
                private void CheckState ()
-               {               
+               {
                        if (_state == WriteState.Element) {
                                //Push scope to allow to unwind namespaces scope back in WriteEndElement
                                //Subject to optimization - avoid redundant push/pop by moving 
                                //namespaces to WriteStartElement
-                               _nsManager.PushScope ();
                                //Emit pending attributes
-                               for (int i = 0; i < pendingAttributesPos; i++) {
+                               newNamespaces.Clear (); //remember indexes of new prefexes
+                               _nsManager.PushScope ();
+                               for (int i = 0; i < _currentNsPrefixes.Count; i++) 
+                               {
+                                       string prefix = (string) _currentNsPrefixes [i];
+                                       string uri = _currentNamespaceDecls [prefix] as string;
+                                       
+                                       if (_nsManager.LookupNamespace (prefix, false) == uri)
+                                               continue;
+
+                                       newNamespaces.Add(i);
+                                       _nsManager.AddNamespace (prefix, uri);
+                               }
+                               for (int i = 0; i < pendingAttributesPos; i++) 
+                               {
                                        Attribute attr = pendingAttributes [i];
-                                       _emitter.WriteAttributeString (attr.Prefix, attr.LocalName, attr.Namespace, attr.Value);
-                               }       
+                                       string prefix = _nsManager.LookupPrefix (attr.Namespace, false);
+                                       if (prefix == null) {
+                                               prefix = attr.Prefix;
+                                               _nsManager.AddNamespace (prefix, attr.Namespace);
+                                       }
+                                       
+                                       Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
+                               }
+                               for (int i = 0; i < newNamespaces.Count; i++)
+                               {
+                                       string prefix = (string) _currentNsPrefixes [(int)newNamespaces[i]];
+                                       string uri = _currentNamespaceDecls [prefix] as string;
+                                       
+                                       if (prefix != String.Empty)
+                                               Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
+                                       else
+                                               Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
+                               }
+                               _currentNsPrefixes.Clear ();
+                               _currentNamespaceDecls.Clear ();
                                //Attributes flushed, state is Content now                              
                                _state = WriteState.Content;
-                       }               
+                       }
+                       _canProcessAttributes = false;
                }
 
                #region Outputter's methods implementation
-               
-               public override void WriteStartDocument ()
-               {                       
-                       if (!_currentOutput.OmitXmlDeclaration)
-                               _emitter.WriteStartDocument (_currentOutput.Standalone);
-                       
-                       _state = WriteState.Prolog;
-               }
-               
-               public override void WriteEndDocument ()
-               {
-                       _emitter.WriteEndDocument ();                           
-               }
 
                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.Prolog) {
                                //Seems to be the first element - take care of Doctype
-                               if (_currentOutput.DoctypeSystem != null)
-                                       _emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
+                               // Note that HTML does not require SYSTEM identifier.
+                               if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
+                                       Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
                                                _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
                        }
                        CheckState ();
-                       _emitter.WriteStartElement (prefix, localName, nsURI);
-                       _state = WriteState.Element;                                            
+                       Emitter.WriteStartElement (prefix, localName, nsURI);
+                       _state = WriteState.Element;
+                       if (_nsManager.LookupNamespace (prefix, false) != nsURI)
+                               _nsManager.AddNamespace (prefix, nsURI);
                        pendingAttributesPos = 0;
+                       _canProcessAttributes = true;
                }
 
                public override void WriteEndElement ()
+               {
+                       WriteEndElementInternal (false);
+               }
+
+               public override void WriteFullEndElement()
+               {
+                       WriteEndElementInternal (true);
+               }
+
+               private void WriteEndElementInternal (bool fullEndElement)
                {
                        CheckState ();
-                       _emitter.WriteEndElement ();
+                       if (fullEndElement)
+                               Emitter.WriteFullEndElement ();
+                       else
+                               Emitter.WriteEndElement ();
                        _state = WriteState.Content;
                        //Pop namespace scope
                        _nsManager.PopScope ();
                }
 
                public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
-               {                                                                               
+               {
+                       if (prefix == String.Empty && nsURI != String.Empty ||
+                               prefix == "xml" && nsURI != XmlNamespaceManager.XmlnsXml)
+                               prefix = "xp_" + _xpCount++;
+
                        //Put attribute to pending attributes collection, replacing namesake one
                        for (int i = 0; i < pendingAttributesPos; i++) {
                                Attribute attr = pendingAttributes [i];
                                
                                if (attr.LocalName == localName && attr.Namespace == nsURI) {
-                                       attr.Value = value;
+                                       pendingAttributes [i].Value = value;
                                        //Keep prefix (e.g. when literal attribute is overriden by xsl:attribute)
                                        if (attr.Prefix == String.Empty && prefix != String.Empty)
-                                               attr.Prefix = prefix;
-                                       
+                                               pendingAttributes [i].Prefix = prefix;
                                        return;
                                }
                        }
@@ -165,48 +300,76 @@ namespace Mono.Xml.Xsl
 
                public override void WriteNamespaceDecl (string prefix, string nsUri)
                {
-                       if (prefix == String.Empty) {
-                               //Default namespace
-                               if (_nsManager.DefaultNamespace != nsUri) {
-                                       _nsManager.AddNamespace (prefix, nsUri);
-                                       _emitter.WriteAttributeString ("", "xmlns", "", nsUri);
-                               }
-                       } else if (_nsManager.LookupPrefix (nsUri) == null) {
-                               //That's new namespace - add it to the collection and emit
-                               _nsManager.AddNamespace (prefix, nsUri);
-                               _emitter.WriteAttributeString ("xmlns", prefix, null, nsUri);
-                       }                       
+                       if (_nsManager.LookupNamespace (prefix, false) == nsUri)
+                               return; // do nothing
+
+                       for (int i = 0; i < pendingAttributesPos; i++) {
+                               Attribute attr = pendingAttributes [i];
+                               if (attr.Prefix == prefix || attr.Namespace == nsUri)
+                                       return; //don't touch explicitly declared attributes
+                       }
+                       if (_currentNamespaceDecls [prefix] as string != nsUri) {
+                               if (!_currentNsPrefixes.Contains (prefix))
+                                       _currentNsPrefixes.Add (prefix);
+                               _currentNamespaceDecls [prefix] = nsUri;
+                       }
                }
                                        
                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 ();
-                       _emitter.WriteString (text);
+                       if (_insideCData)
+                               Emitter.WriteCDataSection (text);
+                       else
+                               Emitter.WriteString (text);
                }
 
                public override void WriteRaw (string data)
                {
                        CheckState ();
-                       _emitter.WriteRaw (data);
+                       Emitter.WriteRaw (data);
+               }
+
+               public override void WriteWhitespace (string 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; }
+               }
+
+               public override bool InsideCDataSection {
+                       get { return _insideCData; }
+                       set { _insideCData = value; }
+               }
                #endregion
        }
 }