2005-03-03 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 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.Collections;
34 using System.Globalization;
35 using System.Xml;
36 using System.IO;
37 using System.Text;
38
39 namespace Mono.Xml.Xsl
40 {
41         /// <summary>
42         /// Generic implemenatation of the Outputter.
43         /// Works as a buffer between Transformation classes and an Emitter.
44         /// Implements attributes dublicate checking, nemaspace stuff and
45         /// choosing of right Emitter implementation.
46         /// </summary>
47         internal class GenericOutputter : Outputter {   
48                 private Hashtable _outputs;
49                 //Current xsl:output 
50                 private XslOutput _currentOutput;
51                 //Underlying emitter
52                 private Emitter _emitter;
53                 // destination TextWriter,
54                 // which is pended until the actual output is determined.
55                 private TextWriter pendingTextWriter;
56                 // also, whitespaces before the first element are cached.
57                 StringBuilder pendingFirstSpaces;
58                 //Outputting state
59                 private WriteState _state;
60                 // Collection of pending attributes. TODO: Can we make adding an attribute
61                 // O(1)? I'm not sure it is that important (this would only really make a difference
62                 // if elements had like 10 attributes, which is very rare).
63                 Attribute [] pendingAttributes = new Attribute [10];
64                 int pendingAttributesPos = 0;
65                 //Namespace manager. Subject to optimization.
66                 private XmlNamespaceManager _nsManager;
67                 private ArrayList _currentNsPrefixes;
68                 private Hashtable _currentNamespaceDecls;
69                 // See CheckState(). This is just a cache.
70                 private ArrayList newNamespaces = new ArrayList();
71                 //Name table
72                 private NameTable _nt;
73                 // Specified encoding (for TextWriter output)
74                 Encoding _encoding;
75                 //Determines whether xsl:copy can output attribute-sets or not.
76                 bool _canProcessAttributes;
77                 bool _insideCData;
78                 bool _isVariable;
79                 bool _omitXmlDeclaration;
80                 int _xpCount;
81
82                 private GenericOutputter (Hashtable outputs, Encoding encoding)
83                 {
84                         _encoding = encoding;
85                         _outputs = outputs;
86                         _currentOutput = (XslOutput)outputs [String.Empty];
87                         _state = WriteState.Prolog;
88                         //TODO: Optimize using nametable
89                         _nt = new NameTable ();
90                         _nsManager = new XmlNamespaceManager (_nt);
91                         _currentNsPrefixes = new ArrayList ();
92                         _currentNamespaceDecls = new Hashtable ();
93                         _omitXmlDeclaration = false;
94                 }
95
96                 public GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding) 
97                         : this (writer, outputs, encoding, false)
98                 {
99                 }
100                 
101                 internal GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding, bool isVariable)
102                         : this (outputs, encoding)
103                 {
104                         _emitter = new XmlWriterEmitter (writer);
105                         _state = writer.WriteState;
106                         _isVariable = isVariable;
107                         _omitXmlDeclaration = true; // .Net never writes XML declaration via XmlWriter
108                 }
109
110                 public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
111                         : this (outputs, encoding)
112                 {
113                         this.pendingTextWriter = writer;
114                 }
115
116                 
117                 internal GenericOutputter (TextWriter writer, Hashtable outputs)
118                         : this (writer, outputs, null)
119                 {
120                 }
121
122                 internal GenericOutputter (XmlWriter writer, Hashtable outputs)
123                         : this (writer, outputs, null)
124                 {
125                 }
126                 
127                 private Emitter Emitter {
128                         get {
129                                 if (_emitter == null)
130                                         DetermineOutputMethod (null, null);
131                                 return _emitter;
132                         }
133                 }
134
135                 private void DetermineOutputMethod (string localName, string ns)
136                 {
137                         XslOutput xslOutput = (XslOutput)_outputs [String.Empty];
138                         switch (xslOutput.Method) {
139                                 default: // .Custom format is not supported, only handled as unknown
140                                 case OutputMethod.Unknown:
141                                         if (localName != null && String.Compare (localName, "html", true, CultureInfo.InvariantCulture) == 0 && ns == String.Empty)
142                                                 goto case OutputMethod.HTML;
143                                         goto case OutputMethod.XML;
144                                 case OutputMethod.HTML:
145                                         _emitter = new HtmlEmitter (pendingTextWriter, xslOutput);
146                                         break;
147                                 case OutputMethod.XML:
148                                         XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
149                                         if (xslOutput.Indent == "yes")
150                                                 w.Formatting = Formatting.Indented;
151
152                                         _emitter = new XmlWriterEmitter (w);                                    
153                                         if (!_omitXmlDeclaration && !xslOutput.OmitXmlDeclaration)
154                                                 _emitter.WriteStartDocument (
155                                                         _encoding != null ? _encoding : xslOutput.Encoding,
156                                                         xslOutput.Standalone);
157
158                                         break;
159                                 case OutputMethod.Text:
160                                         _emitter = new TextEmitter (pendingTextWriter);
161                                         break;
162                         }
163                         pendingTextWriter = null;
164                 }
165
166                 /// <summary>
167                 /// Checks output state and flushes pending attributes and namespaces 
168                 /// when it's appropriate.
169                 /// </summary>
170                 private void CheckState ()
171                 {
172                         if (_state == WriteState.Element) {
173                                 //Push scope to allow to unwind namespaces scope back in WriteEndElement
174                                 //Subject to optimization - avoid redundant push/pop by moving 
175                                 //namespaces to WriteStartElement
176                                 //Emit pending attributes
177                                 newNamespaces.Clear (); //remember indexes of new prefexes
178                                 _nsManager.PushScope ();
179                                 for (int i = 0; i < _currentNsPrefixes.Count; i++) 
180                                 {
181                                         string prefix = (string) _currentNsPrefixes [i];
182                                         string uri = _currentNamespaceDecls [prefix] as string;
183                                         
184                                         if (_nsManager.LookupNamespace (prefix, false) == uri)
185                                                 continue;
186
187                                         newNamespaces.Add(i);
188                                         _nsManager.AddNamespace (prefix, uri);
189                                 }
190                                 for (int i = 0; i < pendingAttributesPos; i++) 
191                                 {
192                                         Attribute attr = pendingAttributes [i];
193                                         string prefix = _nsManager.LookupPrefix (attr.Namespace, false);
194                                         if (prefix == null) {
195                                                 prefix = attr.Prefix;
196                                                 _nsManager.AddNamespace (prefix, attr.Namespace);
197                                         }
198                                         
199                                         Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
200                                 }
201                                 for (int i = 0; i < newNamespaces.Count; i++)
202                                 {
203                                         string prefix = (string) _currentNsPrefixes [(int)newNamespaces[i]];
204                                         string uri = _currentNamespaceDecls [prefix] as string;
205                                         
206                                         if (prefix != String.Empty)
207                                                 Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
208                                         else
209                                                 Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
210                                 }
211                                 _currentNsPrefixes.Clear ();
212                                 _currentNamespaceDecls.Clear ();
213                                 //Attributes flushed, state is Content now                              
214                                 _state = WriteState.Content;
215                         }
216                         _canProcessAttributes = false;
217                 }
218
219                 #region Outputter's methods implementation
220
221                 public override void WriteStartElement (string prefix, string localName, string nsURI)
222                 {
223                         if (_emitter == null) {
224                                 this.DetermineOutputMethod (localName, nsURI);
225                                 if (pendingFirstSpaces != null) {
226                                         WriteWhitespace (pendingFirstSpaces.ToString ());
227                                         pendingFirstSpaces = null;
228                                 }
229                         }
230
231                         if (_state == WriteState.Prolog) {
232                                 //Seems to be the first element - take care of Doctype
233                                 // Note that HTML does not require SYSTEM identifier.
234                                 if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
235                                         Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
236                                                 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
237                         }
238                         CheckState ();
239                         if (nsURI == String.Empty)
240                                 prefix = String.Empty;
241                         Emitter.WriteStartElement (prefix, localName, nsURI);
242                         _state = WriteState.Element;
243                         if (_nsManager.LookupNamespace (prefix, false) != nsURI)
244                                 _nsManager.AddNamespace (prefix, nsURI);
245                         pendingAttributesPos = 0;
246                         _canProcessAttributes = true;
247                 }
248
249                 public override void WriteEndElement ()
250                 {
251                         WriteEndElementInternal (false);
252                 }
253
254                 public override void WriteFullEndElement()
255                 {
256                         WriteEndElementInternal (true);
257                 }
258
259                 private void WriteEndElementInternal (bool fullEndElement)
260                 {
261                         CheckState ();
262                         if (fullEndElement)
263                                 Emitter.WriteFullEndElement ();
264                         else
265                                 Emitter.WriteEndElement ();
266                         _state = WriteState.Content;
267                         //Pop namespace scope
268                         _nsManager.PopScope ();
269                 }
270
271                 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
272                 {
273                         if (prefix == String.Empty && nsURI != String.Empty ||
274                                 prefix == "xml" && nsURI != XmlNamespaceManager.XmlnsXml)
275                                 prefix = "xp_" + _xpCount++;
276
277                         //Put attribute to pending attributes collection, replacing namesake one
278                         for (int i = 0; i < pendingAttributesPos; i++) {
279                                 Attribute attr = pendingAttributes [i];
280                                 
281                                 if (attr.LocalName == localName && attr.Namespace == nsURI) {
282                                         pendingAttributes [i].Value = value;
283                                         //Keep prefix (e.g. when literal attribute is overriden by xsl:attribute)
284                                         if (attr.Prefix == String.Empty && prefix != String.Empty)
285                                                 pendingAttributes [i].Prefix = prefix;
286                                         return;
287                                 }
288                         }
289                         
290                         if (pendingAttributesPos == pendingAttributes.Length) {
291                                 Attribute [] old = pendingAttributes;
292                                 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
293                                 if (pendingAttributesPos > 0)
294                                         Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
295                         }
296                         pendingAttributes [pendingAttributesPos].Prefix = prefix;
297                         pendingAttributes [pendingAttributesPos].Namespace = nsURI;
298                         pendingAttributes [pendingAttributesPos].LocalName = localName;
299                         pendingAttributes [pendingAttributesPos].Value = value;
300                         pendingAttributesPos++;
301                 }
302
303                 public override void WriteNamespaceDecl (string prefix, string nsUri)
304                 {
305                         if (_nsManager.LookupNamespace (prefix, false) == nsUri)
306                                 return; // do nothing
307
308                         for (int i = 0; i < pendingAttributesPos; i++) {
309                                 Attribute attr = pendingAttributes [i];
310                                 if (attr.Prefix == prefix || attr.Namespace == nsUri)
311                                         return; //don't touch explicitly declared attributes
312                         }
313                         if (_currentNamespaceDecls [prefix] as string != nsUri) {
314                                 if (!_currentNsPrefixes.Contains (prefix))
315                                         _currentNsPrefixes.Add (prefix);
316                                 _currentNamespaceDecls [prefix] = nsUri;
317                         }
318                 }
319                                         
320                 public override void WriteComment (string text)
321                 {
322                         CheckState ();
323                         Emitter.WriteComment (text);
324                 }
325
326                 public override void WriteProcessingInstruction (string name, string text)
327                 {
328                         CheckState ();
329                         Emitter.WriteProcessingInstruction (name, text);
330                 }
331
332                 public override void WriteString (string text)
333                 {
334                         CheckState ();
335                         if (_insideCData)
336                                 Emitter.WriteCDataSection (text);
337                         else
338                                 Emitter.WriteString (text);
339                 }
340
341                 public override void WriteRaw (string data)
342                 {
343                         CheckState ();
344                         Emitter.WriteRaw (data);
345                 }
346
347                 public override void WriteWhitespace (string text)
348                 {
349                         if (_emitter == null) {
350                                 if (pendingFirstSpaces == null)
351                                         pendingFirstSpaces = new StringBuilder ();
352                                 pendingFirstSpaces.Append (text);
353                                 if (_state == WriteState.Start)
354                                         _state = WriteState.Prolog;
355                         } else {
356                                 CheckState ();
357                                 Emitter.WriteWhitespace (text);
358                         }
359                 }
360
361                 public override void Done ()
362                 {
363                         Emitter.Done ();
364                         _state = WriteState.Closed;
365                 }
366
367                 public override bool CanProcessAttributes {
368                         get { return _canProcessAttributes; }
369                 }
370
371                 public override bool InsideCDataSection {
372                         get { return _insideCData; }
373                         set { _insideCData = value; }
374                 }
375                 #endregion
376         }
377 }