2004-02-16 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / GenericOutputter.cs
1 //
2 // GenericOutputter.cs
3 //
4 // Authors:
5 //      Oleg Tkachenko (oleg@tkachenko.com)
6 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 //      
8 // (C) 2003 Oleg Tkachenko, Atsushi Enomoto
9 //
10
11 using System;
12 using System.Collections;
13 using System.Collections.Specialized;
14 using System.Xml;
15 using System.IO;
16 using System.Text;
17
18 namespace Mono.Xml.Xsl
19 {
20         /// <summary>
21         /// Generic implemenatation of the Outputter.
22         /// Works as a buffer between Transformation classes and an Emitter.
23         /// Implements attributes dublicate checking, nemaspace stuff and
24         /// choosing of right Emitter implementation.
25         /// </summary>
26         internal class GenericOutputter : Outputter {   
27                 private Hashtable _outputs;
28                 //Current xsl:output 
29                 private XslOutput _currentOutput;
30                 //Underlying emitter
31                 private Emitter _emitter;
32                 // destination TextWriter,
33                 // which is pended until the actual output is determined.
34                 private TextWriter pendingTextWriter;
35                 // also, whitespaces before the first element are cached.
36                 StringBuilder pendingFirstSpaces;
37                 //Outputting state
38                 private WriteState _state;
39                 // Collection of pending attributes. TODO: Can we make adding an attribute
40                 // O(1)? I'm not sure it is that important (this would only really make a difference
41                 // if elements had like 10 attributes, which is very rare).
42                 Attribute [] pendingAttributes = new Attribute [10];
43                 int pendingAttributesPos = 0;
44                 //Namespace manager. Subject to optimization.
45                 private XmlNamespaceManager _nsManager;
46                 private StringCollection _currentNsPrefixes;
47                 private Hashtable _currentNamespaceDecls;
48                 //Name table
49                 private NameTable _nt;
50                 // Specified encoding (for TextWriter output)
51                 Encoding _encoding;
52                 //Determines whether xsl:copy can output attribute-sets or not.
53                 bool _canProcessAttributes;
54                 bool _insideCData;
55                 bool _isVariable;
56
57                 private GenericOutputter (Hashtable outputs, Encoding encoding)
58                 {
59                         _encoding = encoding;
60                         _outputs = outputs;
61                         _currentOutput = (XslOutput)outputs [String.Empty];
62                         _state = WriteState.Start;
63                         //TODO: Optimize using nametable
64                         _nt = new NameTable ();
65                         _nsManager = new XmlNamespaceManager (_nt);
66                         _currentNsPrefixes = new StringCollection ();
67                         _currentNamespaceDecls = new Hashtable ();
68                 }
69
70                 public GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding) 
71                         : this (writer, outputs, encoding, false)
72                 {
73                 }
74                 
75                 internal GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding, bool isVariable)
76                         : this (outputs, encoding)
77                 {
78                         _emitter = new XmlWriterEmitter (writer);
79                         _state = writer.WriteState;
80                         _isVariable = isVariable;
81                 }
82
83                 public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
84                         : this (outputs, encoding)
85                 {
86                         this.pendingTextWriter = writer;
87                 }
88
89                 
90                 internal GenericOutputter (TextWriter writer, Hashtable outputs)
91                         : this (writer, outputs, null)
92                 {
93                 }
94
95                 internal GenericOutputter (XmlWriter writer, Hashtable outputs)
96                         : this (writer, outputs, null)
97                 {
98                 }
99                 
100                 private Emitter Emitter {
101                         get {
102                                 if (_emitter == null)
103                                         DetermineOutputMethod (null, null);
104                                 return _emitter;
105                         }
106                 }
107
108                 private void DetermineOutputMethod (string localName, string ns)
109                 {
110                         XslOutput xslOutput = (XslOutput)_outputs [String.Empty];
111                         switch (xslOutput.Method) {
112                                 case OutputMethod.Unknown:
113                                         if (localName != null && localName.ToLower () == "html" && ns == String.Empty)
114                                                 goto case OutputMethod.HTML;
115                                         goto case OutputMethod.XML;
116                                 case OutputMethod.HTML:
117                                         _emitter = new HtmlEmitter (pendingTextWriter, xslOutput);
118                                         break;
119                                 case OutputMethod.XML:
120                                         XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
121                                         if (xslOutput.Indent == "yes")
122                                                 w.Formatting = Formatting.Indented;
123                                         _emitter = new XmlWriterEmitter (w);                                    
124                                         break;
125                                 case OutputMethod.Text:
126                                         _emitter = new TextEmitter (pendingTextWriter);
127                                         break;
128                                 case OutputMethod.Custom:
129                                         throw new NotSupportedException ("Custom output method is not supported in this version.");
130                         }
131                         pendingTextWriter = null;
132                 }
133
134                 /// <summary>
135                 /// Checks output state and flushes pending attributes and namespaces 
136                 /// when it's appropriate.
137                 /// </summary>
138                 private void CheckState ()
139                 {
140                         if (_state == WriteState.Start)
141                                 WriteStartDocument ();
142
143                         if (_state == WriteState.Element) {
144                                 //Push scope to allow to unwind namespaces scope back in WriteEndElement
145                                 //Subject to optimization - avoid redundant push/pop by moving 
146                                 //namespaces to WriteStartElement
147                                 _nsManager.PushScope ();
148                                 //Emit pending attributes
149                                 for (int i = 0; i < pendingAttributesPos; i++) {
150                                         Attribute attr = pendingAttributes [i];
151                                         string prefix = attr.Prefix;
152                                         if (prefix == String.Empty)
153                                                 prefix = _nsManager.LookupPrefix (attr.Namespace);
154                                         Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
155                                 }
156                                 for (int i = 0; i < _currentNsPrefixes.Count; i++) {
157                                         string prefix = _currentNsPrefixes [i];
158                                         string uri = _currentNamespaceDecls [prefix] as string;
159                                         if (prefix != String.Empty)
160                                                 Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
161                                         else
162                                                 Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
163                                 }
164                                 _currentNsPrefixes.Clear ();
165                                 _currentNamespaceDecls.Clear ();
166                                 //Attributes flushed, state is Content now                              
167                                 _state = WriteState.Content;
168                         }
169                         _canProcessAttributes = false;
170                 }
171
172                 #region Outputter's methods implementation
173                 
174                 public override void WriteStartDocument ()
175                 {
176                         if (_isVariable)
177                                 return;
178
179                         if (!_currentOutput.OmitXmlDeclaration)
180                                 Emitter.WriteStartDocument (_encoding != null ? _encoding : _currentOutput.Encoding, _currentOutput.Standalone);
181                         
182                         _state = WriteState.Prolog;
183                 }
184                 
185                 public override void WriteEndDocument ()
186                 {
187                         Emitter.WriteEndDocument ();                            
188                 }
189
190                 int _nsCount;
191                 public override void WriteStartElement (string prefix, string localName, string nsURI)
192                 {
193                         if (_emitter == null) {
194                                 this.DetermineOutputMethod (localName, nsURI);
195                                 if (pendingFirstSpaces != null) {
196                                         WriteWhitespace (pendingFirstSpaces.ToString ());
197                                         pendingFirstSpaces = null;
198                                 }
199                         }
200
201                         if (_state == WriteState.Start)
202                                 WriteStartDocument ();
203
204                         if (_state == WriteState.Prolog) {
205                                 //Seems to be the first element - take care of Doctype
206                                 // Note that HTML does not require SYSTEM identifier.
207                                 if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
208                                         Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
209                                                 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
210                         }
211                         CheckState ();
212                         Emitter.WriteStartElement (prefix, localName, nsURI);
213                         _state = WriteState.Element;                                            
214                         pendingAttributesPos = 0;
215                         _canProcessAttributes = true;
216                 }
217
218                 public override void WriteEndElement ()
219                 {
220                         WriteEndElementInternal (false);
221                 }
222
223                 public override void WriteFullEndElement()
224                 {
225                         WriteEndElementInternal (true);
226                 }
227
228                 private void WriteEndElementInternal (bool fullEndElement)
229                 {
230                         CheckState ();
231                         if (fullEndElement)
232                                 Emitter.WriteFullEndElement ();
233                         else
234                                 Emitter.WriteEndElement ();
235                         _state = WriteState.Content;
236                         //Pop namespace scope
237                         _nsManager.PopScope ();
238                 }
239
240                 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
241                 {
242                         if (prefix == String.Empty && nsURI != String.Empty) {
243                                 prefix = "xp_" + _nsCount;
244                                 _nsManager.AddNamespace (prefix, nsURI);
245                                 _currentNsPrefixes.Add (prefix);
246                                 _currentNamespaceDecls.Add (prefix, nsURI);
247                                 _nsCount++;
248                         }
249
250                         //Put attribute to pending attributes collection, replacing namesake one
251                         for (int i = 0; i < pendingAttributesPos; i++) {
252                                 Attribute attr = pendingAttributes [i];
253                                 
254                                 if (attr.LocalName == localName && attr.Namespace == nsURI) {
255                                         pendingAttributes [i].Value = value;
256                                         //Keep prefix (e.g. when literal attribute is overriden by xsl:attribute)
257                                         if (attr.Prefix == String.Empty && prefix != String.Empty)
258                                                 pendingAttributes [i].Prefix = prefix;
259                                         return;
260                                 }
261                         }
262                         
263                         if (pendingAttributesPos == pendingAttributes.Length) {
264                                 Attribute [] old = pendingAttributes;
265                                 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
266                                 if (pendingAttributesPos > 0)
267                                         Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
268                         }
269                         pendingAttributes [pendingAttributesPos].Prefix = prefix;
270                         pendingAttributes [pendingAttributesPos].Namespace = nsURI;
271                         pendingAttributes [pendingAttributesPos].LocalName = localName;
272                         pendingAttributes [pendingAttributesPos].Value = value;
273                         pendingAttributesPos++;
274                 }
275
276                 public override void WriteNamespaceDecl (string prefix, string nsUri)
277                 {
278                         if (_nsManager.LookupNamespace (prefix) == nsUri)
279                                 return;
280
281                         if (prefix == String.Empty) {
282                                 //Default namespace
283                                 if (_nsManager.DefaultNamespace != nsUri)
284                                         _nsManager.AddNamespace (prefix, nsUri);
285                         } else if (_nsManager.LookupPrefix (nsUri) == null)
286                                 //That's new namespace - add it to the collection
287                                 _nsManager.AddNamespace (prefix, nsUri);
288
289                         if (_currentNamespaceDecls [prefix] as string != nsUri) {
290                                 if (!_currentNsPrefixes.Contains (prefix))
291                                         _currentNsPrefixes.Add (prefix);
292                                 _currentNamespaceDecls [prefix] = nsUri;
293                         }
294                 }
295                                         
296                 public override void WriteComment (string text)
297                 {
298                         CheckState ();
299                         Emitter.WriteComment (text);
300                 }
301
302                 public override void WriteProcessingInstruction (string name, string text)
303                 {
304                         CheckState ();
305                         Emitter.WriteProcessingInstruction (name, text);
306                 }
307
308                 public override void WriteString (string text)
309                 {
310                         CheckState ();
311                         if (_insideCData)
312                                 Emitter.WriteCDataSection (text);
313                         else
314                                 Emitter.WriteString (text);
315                 }
316
317                 public override void WriteRaw (string data)
318                 {
319                         CheckState ();
320                         Emitter.WriteRaw (data);
321                 }
322
323                 public override void WriteWhitespace (string text)
324                 {
325                         if (_emitter == null) {
326                                 if (pendingFirstSpaces == null)
327                                         pendingFirstSpaces = new StringBuilder ();
328                                 pendingFirstSpaces.Append (text);
329                                 if (_state == WriteState.Start)
330                                         _state = WriteState.Prolog;
331                         } else {
332                                 CheckState ();
333                                 Emitter.WriteWhitespace (text);
334                         }
335                 }
336
337                 public override void Done ()
338                 {
339                         Emitter.Done ();
340                         _state = WriteState.Closed;
341                 }
342
343                 public override bool CanProcessAttributes {
344                         get { return _canProcessAttributes; }
345                 }
346
347                 public override WriteState WriteState { get { return _state; } }
348
349                 public override bool InsideCDataSection {
350                         get { return _insideCData; }
351                         set { _insideCData = value; }
352                 }
353                 #endregion
354         }
355 }