Ignore SynchronizationAttribute on MT
[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                                                 { // ADD
198                                                         // empty prefix is not allowed
199                                                         // for non-local attributes.
200                                                         prefix = "xp_" + _xpCount++;
201                                                 //if (existing != prefix) {
202                                                         while (_nsManager.LookupNamespace (prefix) != null)
203                                                                 prefix = "xp_" + _xpCount++;
204                                                         newNamespaces.Add (prefix);
205                                                         _currentNamespaceDecls.Add (prefix, attr.Namespace);
206                                                         _nsManager.AddNamespace (prefix, attr.Namespace);
207                                                 //}
208                                                 } // ADD
209                                         }
210                                         Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
211                                 }
212                                 for (int i = 0; i < newNamespaces.Count; i++)
213                                 {
214                                         string prefix = (string) newNamespaces [i];
215                                         string uri = _currentNamespaceDecls [prefix] as string;
216                                         
217                                         if (prefix != String.Empty)
218                                                 Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
219                                         else
220                                                 Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
221                                 }
222                                 _currentNamespaceDecls.Clear ();
223                                 //Attributes flushed, state is Content now                              
224                                 _state = WriteState.Content;
225                                 newNamespaces.Clear ();
226                         }
227                         _canProcessAttributes = false;
228                 }
229
230                 #region Outputter's methods implementation
231
232                 public override void WriteStartElement (string prefix, string localName, string nsURI)
233                 {
234                         if (_emitter == null) {
235                                 this.DetermineOutputMethod (localName, nsURI);
236                                 if (pendingFirstSpaces != null) {
237                                         WriteWhitespace (pendingFirstSpaces.ToString ());
238                                         pendingFirstSpaces = null;
239                                 }
240                         }
241
242                         if (_state == WriteState.Prolog) {
243                                 //Seems to be the first element - take care of Doctype
244                                 // Note that HTML does not require SYSTEM identifier.
245                                 if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
246                                         Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
247                                                 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
248                         }
249                         CheckState ();
250                         if (nsURI == String.Empty)
251                                 prefix = String.Empty;
252                         Emitter.WriteStartElement (prefix, localName, nsURI);
253                         _state = WriteState.Element;
254                         if (_nsManager.LookupNamespace (prefix, false) != nsURI)
255 //                              _nsManager.AddNamespace (prefix, nsURI);
256                                 _currentNamespaceDecls [prefix] = nsURI;
257                         pendingAttributesPos = 0;
258                         _canProcessAttributes = true;
259                 }
260
261                 public override void WriteEndElement ()
262                 {
263                         WriteEndElementInternal (false);
264                 }
265
266                 public override void WriteFullEndElement()
267                 {
268                         WriteEndElementInternal (true);
269                 }
270
271                 private void WriteEndElementInternal (bool fullEndElement)
272                 {
273                         CheckState ();
274                         if (fullEndElement)
275                                 Emitter.WriteFullEndElement ();
276                         else
277                                 Emitter.WriteEndElement ();
278                         _state = WriteState.Content;
279                         //Pop namespace scope
280                         _nsManager.PopScope ();
281                 }
282
283                 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
284                 {
285                         //Put attribute to pending attributes collection, replacing namesake one
286                         for (int i = 0; i < pendingAttributesPos; i++) {
287                                 Attribute attr = pendingAttributes [i];
288                                 
289                                 if (attr.LocalName == localName && attr.Namespace == nsURI) {
290                                         pendingAttributes [i].Value = value;
291                                         pendingAttributes [i].Prefix = prefix;
292                                         return;
293                                 }
294                         }
295                         
296                         if (pendingAttributesPos == pendingAttributes.Length) {
297                                 Attribute [] old = pendingAttributes;
298                                 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
299                                 if (pendingAttributesPos > 0)
300                                         Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
301                         }
302                         pendingAttributes [pendingAttributesPos].Prefix = prefix;
303                         pendingAttributes [pendingAttributesPos].Namespace = nsURI;
304                         pendingAttributes [pendingAttributesPos].LocalName = localName;
305                         pendingAttributes [pendingAttributesPos].Value = value;
306                         pendingAttributesPos++;
307                 }
308
309                 public override void WriteNamespaceDecl (string prefix, string nsUri)
310                 {
311                         if (_nsManager.LookupNamespace (prefix, false) == nsUri)
312                                 return; // do nothing
313
314                         for (int i = 0; i < pendingAttributesPos; i++) {
315                                 Attribute attr = pendingAttributes [i];
316                                 if (attr.Prefix == prefix || attr.Namespace == nsUri)
317                                         return; //don't touch explicitly declared attributes
318                         }
319                         if (_currentNamespaceDecls [prefix] as string != nsUri)
320                                 _currentNamespaceDecls [prefix] = nsUri;
321                 }
322                                         
323                 public override void WriteComment (string text)
324                 {
325                         CheckState ();
326                         Emitter.WriteComment (text);
327                 }
328
329                 public override void WriteProcessingInstruction (string name, string text)
330                 {
331                         CheckState ();
332                         Emitter.WriteProcessingInstruction (name, text);
333                 }
334
335                 public override void WriteString (string text)
336                 {
337                         CheckState ();
338                         if (_insideCData)
339                                 Emitter.WriteCDataSection (text);
340                         // This weird check is required to reject Doctype
341                         // after non-whitespace nodes but also to allow 
342                         // Doctype after whitespace nodes. It especially
343                         // happens when there is an xsl:text before the
344                         // document element (e.g. BVTs_bvt066 testcase).
345                         else if (_state != WriteState.Content &&
346                                  text.Length > 0 && XmlChar.IsWhitespace (text))
347                                 Emitter.WriteWhitespace (text);
348                         else
349                                 Emitter.WriteString (text);
350                 }
351
352                 public override void WriteRaw (string data)
353                 {
354                         CheckState ();
355                         Emitter.WriteRaw (data);
356                 }
357
358                 public override void WriteWhitespace (string text)
359                 {
360                         if (_emitter == null) {
361                                 if (pendingFirstSpaces == null)
362                                         pendingFirstSpaces = new StringBuilder ();
363                                 pendingFirstSpaces.Append (text);
364                                 if (_state == WriteState.Start)
365                                         _state = WriteState.Prolog;
366                         } else {
367                                 CheckState ();
368                                 Emitter.WriteWhitespace (text);
369                         }
370                 }
371
372                 public override void Done ()
373                 {
374                         Emitter.Done ();
375                         _state = WriteState.Closed;
376                 }
377
378                 public override bool CanProcessAttributes {
379                         get { return _canProcessAttributes; }
380                 }
381
382                 public override bool InsideCDataSection {
383                         get { return _insideCData; }
384                         set { _insideCData = value; }
385                 }
386                 #endregion
387         }
388 }