2006-02-21 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.Collections.Specialized;
35 using System.Globalization;
36 using System.Xml;
37 using System.IO;
38 using System.Text;
39
40 namespace Mono.Xml.Xsl
41 {
42         /// <summary>
43         /// Generic implemenatation of the Outputter.
44         /// Works as a buffer between Transformation classes and an Emitter.
45         /// Implements attributes dublicate checking, nemaspace stuff and
46         /// choosing of right Emitter implementation.
47         /// </summary>
48         internal class GenericOutputter : Outputter {   
49                 private Hashtable _outputs;
50                 //Current xsl:output 
51                 private XslOutput _currentOutput;
52                 //Underlying emitter
53                 private Emitter _emitter;
54                 // destination TextWriter,
55                 // which is pended until the actual output is determined.
56                 private TextWriter pendingTextWriter;
57                 // also, whitespaces before the first element are cached.
58                 StringBuilder pendingFirstSpaces;
59                 //Outputting state
60                 private WriteState _state;
61                 // Collection of pending attributes. TODO: Can we make adding an attribute
62                 // O(1)? I'm not sure it is that important (this would only really make a difference
63                 // if elements had like 10 attributes, which is very rare).
64                 Attribute [] pendingAttributes = new Attribute [10];
65                 int pendingAttributesPos = 0;
66                 //Namespace manager. Subject to optimization.
67                 private XmlNamespaceManager _nsManager;
68                 private ListDictionary _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                         _currentNamespaceDecls = new ListDictionary ();
92                         _omitXmlDeclaration = false;
93                 }
94
95                 public GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding) 
96                         : this (writer, outputs, encoding, false)
97                 {
98                 }
99                 
100                 internal GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding, bool isVariable)
101                         : this (outputs, encoding)
102                 {
103                         _emitter = new XmlWriterEmitter (writer);
104                         _state = writer.WriteState;
105 //                      _isVariable = isVariable;
106                         _omitXmlDeclaration = true; // .Net never writes XML declaration via XmlWriter
107                 }
108
109                 public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
110                         : this (outputs, encoding)
111                 {
112                         this.pendingTextWriter = writer;
113                 }
114
115                 
116                 internal GenericOutputter (TextWriter writer, Hashtable outputs)
117                         : this (writer, outputs, null)
118                 {
119                 }
120
121                 internal GenericOutputter (XmlWriter writer, Hashtable outputs)
122                         : this (writer, outputs, null)
123                 {
124                 }
125                 
126                 private Emitter Emitter {
127                         get {
128                                 if (_emitter == null)
129                                         DetermineOutputMethod (null, null);
130                                 return _emitter;
131                         }
132                 }
133
134                 private void DetermineOutputMethod (string localName, string ns)
135                 {
136                         XslOutput xslOutput = (XslOutput)_outputs [String.Empty];
137                         switch (xslOutput.Method) {
138                                 default: // .Custom format is not supported, only handled as unknown
139                                 case OutputMethod.Unknown:
140                                         if (localName != null && String.Compare (localName, "html", true, CultureInfo.InvariantCulture) == 0 && ns == String.Empty)
141                                                 goto case OutputMethod.HTML;
142                                         goto case OutputMethod.XML;
143                                 case OutputMethod.HTML:
144                                         _emitter = new HtmlEmitter (pendingTextWriter, xslOutput);
145                                         break;
146                                 case OutputMethod.XML:
147                                         XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
148                                         if (xslOutput.Indent == "yes")
149                                                 w.Formatting = Formatting.Indented;
150
151                                         _emitter = new XmlWriterEmitter (w);
152                                         if (!_omitXmlDeclaration && !xslOutput.OmitXmlDeclaration)
153                                                 _emitter.WriteStartDocument (
154                                                         _encoding != null ? _encoding : xslOutput.Encoding,
155                                                         xslOutput.Standalone);
156
157                                         break;
158                                 case OutputMethod.Text:
159                                         _emitter = new TextEmitter (pendingTextWriter);
160                                         break;
161                         }
162                         pendingTextWriter = null;
163                 }
164
165                 /// <summary>
166                 /// Checks output state and flushes pending attributes and namespaces 
167                 /// when it's appropriate.
168                 /// </summary>
169                 private void CheckState ()
170                 {
171                         if (_state == WriteState.Element) {
172                                 //Emit pending attributes
173                                 _nsManager.PushScope ();
174                                 foreach (string prefix in _currentNamespaceDecls.Keys)
175                                 {
176                                         string uri = _currentNamespaceDecls [prefix] as string;
177                                         
178                                         if (_nsManager.LookupNamespace (prefix, false) == uri)
179                                                 continue;
180
181                                         newNamespaces.Add (prefix);
182                                         _nsManager.AddNamespace (prefix, uri);
183                                 }
184                                 for (int i = 0; i < pendingAttributesPos; i++) 
185                                 {
186                                         Attribute attr = pendingAttributes [i];
187                                         string prefix = attr.Prefix;
188                                         if (prefix == XmlNamespaceManager.PrefixXml &&
189                                                 attr.Namespace != XmlNamespaceManager.XmlnsXml)
190                                                 // don't allow mapping from "xml" to other namespaces.
191                                                 prefix = String.Empty;
192                                         string existing = _nsManager.LookupPrefix (attr.Namespace, false);
193                                         if (prefix.Length == 0 && attr.Namespace.Length > 0)
194                                                 prefix = existing;
195                                         if (attr.Namespace.Length > 0) {
196                                                 if (prefix == null || prefix == String.Empty)
197                                                         // empty prefix is not allowed
198                                                         // for non-local attributes.
199                                                         prefix = "xp_" + _xpCount++;
200                                                 if (existing != prefix) {
201                                                         newNamespaces.Add (prefix);
202                                                         _currentNamespaceDecls.Add (prefix, attr.Namespace);
203                                                         _nsManager.AddNamespace (prefix, attr.Namespace);
204                                                 }
205                                         }
206                                         Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
207                                 }
208                                 for (int i = 0; i < newNamespaces.Count; i++)
209                                 {
210                                         string prefix = (string) newNamespaces [i];
211                                         string uri = _currentNamespaceDecls [prefix] as string;
212                                         
213                                         if (prefix != String.Empty)
214                                                 Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
215                                         else
216                                                 Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
217                                 }
218                                 _currentNamespaceDecls.Clear ();
219                                 //Attributes flushed, state is Content now                              
220                                 _state = WriteState.Content;
221                                 newNamespaces.Clear ();
222                         }
223                         _canProcessAttributes = false;
224                 }
225
226                 #region Outputter's methods implementation
227
228                 public override void WriteStartElement (string prefix, string localName, string nsURI)
229                 {
230                         if (_emitter == null) {
231                                 this.DetermineOutputMethod (localName, nsURI);
232                                 if (pendingFirstSpaces != null) {
233                                         WriteWhitespace (pendingFirstSpaces.ToString ());
234                                         pendingFirstSpaces = null;
235                                 }
236                         }
237
238                         if (_state == WriteState.Prolog) {
239                                 //Seems to be the first element - take care of Doctype
240                                 // Note that HTML does not require SYSTEM identifier.
241                                 if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
242                                         Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
243                                                 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
244                         }
245                         CheckState ();
246                         if (nsURI == String.Empty)
247                                 prefix = String.Empty;
248                         Emitter.WriteStartElement (prefix, localName, nsURI);
249                         _state = WriteState.Element;
250                         if (_nsManager.LookupNamespace (prefix, false) != nsURI)
251 //                              _nsManager.AddNamespace (prefix, nsURI);
252                                 _currentNamespaceDecls [prefix] = nsURI;
253                         pendingAttributesPos = 0;
254                         _canProcessAttributes = true;
255                 }
256
257                 public override void WriteEndElement ()
258                 {
259                         WriteEndElementInternal (false);
260                 }
261
262                 public override void WriteFullEndElement()
263                 {
264                         WriteEndElementInternal (true);
265                 }
266
267                 private void WriteEndElementInternal (bool fullEndElement)
268                 {
269                         CheckState ();
270                         if (fullEndElement)
271                                 Emitter.WriteFullEndElement ();
272                         else
273                                 Emitter.WriteEndElement ();
274                         _state = WriteState.Content;
275                         //Pop namespace scope
276                         _nsManager.PopScope ();
277                 }
278
279                 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
280                 {
281                         //Put attribute to pending attributes collection, replacing namesake one
282                         for (int i = 0; i < pendingAttributesPos; i++) {
283                                 Attribute attr = pendingAttributes [i];
284                                 
285                                 if (attr.LocalName == localName && attr.Namespace == nsURI) {
286                                         pendingAttributes [i].Value = value;
287                                         pendingAttributes [i].Prefix = prefix;
288                                         return;
289                                 }
290                         }
291                         
292                         if (pendingAttributesPos == pendingAttributes.Length) {
293                                 Attribute [] old = pendingAttributes;
294                                 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
295                                 if (pendingAttributesPos > 0)
296                                         Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
297                         }
298                         pendingAttributes [pendingAttributesPos].Prefix = prefix;
299                         pendingAttributes [pendingAttributesPos].Namespace = nsURI;
300                         pendingAttributes [pendingAttributesPos].LocalName = localName;
301                         pendingAttributes [pendingAttributesPos].Value = value;
302                         pendingAttributesPos++;
303                 }
304
305                 public override void WriteNamespaceDecl (string prefix, string nsUri)
306                 {
307                         if (_nsManager.LookupNamespace (prefix, false) == nsUri)
308                                 return; // do nothing
309
310                         for (int i = 0; i < pendingAttributesPos; i++) {
311                                 Attribute attr = pendingAttributes [i];
312                                 if (attr.Prefix == prefix || attr.Namespace == nsUri)
313                                         return; //don't touch explicitly declared attributes
314                         }
315                         if (_currentNamespaceDecls [prefix] as string != nsUri)
316                                 _currentNamespaceDecls [prefix] = nsUri;
317                 }
318                                         
319                 public override void WriteComment (string text)
320                 {
321                         CheckState ();
322                         Emitter.WriteComment (text);
323                 }
324
325                 public override void WriteProcessingInstruction (string name, string text)
326                 {
327                         CheckState ();
328                         Emitter.WriteProcessingInstruction (name, text);
329                 }
330
331                 public override void WriteString (string text)
332                 {
333                         CheckState ();
334                         if (_insideCData)
335                                 Emitter.WriteCDataSection (text);
336                         // This weird check is required to reject Doctype
337                         // after non-whitespace nodes but also to allow 
338                         // Doctype after whitespace nodes. It especially
339                         // happens when there is an xsl:text before the
340                         // document element (e.g. BVTs_bvt066 testcase).
341                         else if (_state != WriteState.Content &&
342                                  text.Length > 0 && XmlChar.IsWhitespace (text))
343                                 Emitter.WriteWhitespace (text);
344                         else
345                                 Emitter.WriteString (text);
346                 }
347
348                 public override void WriteRaw (string data)
349                 {
350                         CheckState ();
351                         Emitter.WriteRaw (data);
352                 }
353
354                 public override void WriteWhitespace (string text)
355                 {
356                         if (_emitter == null) {
357                                 if (pendingFirstSpaces == null)
358                                         pendingFirstSpaces = new StringBuilder ();
359                                 pendingFirstSpaces.Append (text);
360                                 if (_state == WriteState.Start)
361                                         _state = WriteState.Prolog;
362                         } else {
363                                 CheckState ();
364                                 Emitter.WriteWhitespace (text);
365                         }
366                 }
367
368                 public override void Done ()
369                 {
370                         Emitter.Done ();
371                         _state = WriteState.Closed;
372                 }
373
374                 public override bool CanProcessAttributes {
375                         get { return _canProcessAttributes; }
376                 }
377
378                 public override bool InsideCDataSection {
379                         get { return _insideCData; }
380                         set { _insideCData = value; }
381                 }
382                 #endregion
383         }
384 }