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