2003-09-20 Ben Maurer <bmaurer@users.sourceforge.net>
[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 //      
7 // (C) 2003 Oleg Tkachenko
8 //
9
10 using System;
11 using System.Collections;
12 using System.Xml;
13 using System.IO;
14
15 namespace Mono.Xml.Xsl
16 {
17         /// <summary>
18         /// Generic implemenatation of the Outputter.
19         /// Works as a buffer between Transformation classes and an Emitter.
20         /// Implements attributes dublicate checking, nemaspace stuff and
21         /// choosing of right Emitter implementation.
22         /// </summary>
23         public class GenericOutputter : Outputter {     
24                 private Hashtable _outputs;
25                 //Current xsl:output 
26                 private XslOutput _currentOutput;
27                 //Underlying emitter
28                 private Emitter _emitter;
29                 //Outputting state
30                 private WriteState _state;
31                 // Collection of pending attributes. TODO: Can we make adding an attribute
32                 // O(1)? I'm not sure it is that important (this would only really make a difference
33                 // if elements had like 10 attributes, which is very rare).
34                 Attribute [] pendingAttributes = new Attribute [10];
35                 int pendingAttributesPos = 0;
36                 //Namespace manager. Subject to optimization.
37                 private XmlNamespaceManager _nsManager;
38                 //Name table
39                 private NameTable _nt;
40                 
41                 private GenericOutputter (Hashtable outputs)
42                 {
43                         _outputs = outputs;
44                         _currentOutput = (XslOutput)outputs [String.Empty];
45                         _state = WriteState.Start;
46                         //TODO: Optimize using nametable
47                         _nt = new NameTable ();
48                         _nsManager = new XmlNamespaceManager (_nt);
49                 }
50
51                 public GenericOutputter (XmlWriter writer, Hashtable outputs) 
52                         : this (outputs)
53                 {
54                         _emitter = new XmlWriterEmitter (writer);                       
55                 }
56
57                 public GenericOutputter (TextWriter writer, Hashtable outputs)
58                         : this (outputs)
59                 {                       
60                         XslOutput xslOutput = (XslOutput)outputs [String.Empty];
61                         switch (xslOutput.Method) {
62                                 case OutputMethod.Unknown: //TODO: handle xml vs html
63                                 case OutputMethod.XML:
64                                         //TODO: XmlTextEmitter goes here
65                                         //_emitter = new XmlTextEmitter (writer);
66                                         _emitter = new XmlWriterEmitter (new XmlTextWriter (writer));                                   
67                                         break;
68                                 case OutputMethod.HTML:
69                                         throw new NotImplementedException ("HTML output method is not implemented yet.");
70                                 case OutputMethod.Text:
71                                         _emitter = new TextEmitter (writer);
72                                         break;
73                                 case OutputMethod.Custom:
74                                         throw new NotImplementedException ("Custom output method is not implemented yet.");
75                         }                                               
76                 }
77
78                 /// <summary>
79                 /// Checks output state and flushes pending attributes and namespaces 
80                 /// when it's appropriate.
81                 /// </summary>
82                 private void CheckState ()
83                 {               
84                         if (_state == WriteState.Element) {
85                                 //Push scope to allow to unwind namespaces scope back in WriteEndElement
86                                 //Subject to optimization - avoid redundant push/pop by moving 
87                                 //namespaces to WriteStartElement
88                                 _nsManager.PushScope ();
89                                 //Emit pending attributes
90                                 for (int i = 0; i < pendingAttributesPos; i++) {
91                                         Attribute attr = pendingAttributes [i];
92                                         _emitter.WriteAttributeString (attr.Prefix, attr.LocalName, attr.Namespace, attr.Value);
93                                 }       
94                                 //Attributes flushed, state is Content now                              
95                                 _state = WriteState.Content;
96                         }               
97                 }
98
99                 #region Outputter's methods implementation
100                 
101                 public override void WriteStartDocument ()
102                 {                       
103                         if (!_currentOutput.OmitXmlDeclaration)
104                                 _emitter.WriteStartDocument (_currentOutput.Standalone);
105                         
106                         _state = WriteState.Prolog;
107                 }
108                 
109                 public override void WriteEndDocument ()
110                 {
111                         _emitter.WriteEndDocument ();                           
112                 }
113
114                 public override void WriteStartElement (string prefix, string localName, string nsURI)
115                 {
116                         if (_state == WriteState.Prolog) {
117                                 //Seems to be the first element - take care of Doctype
118                                 if (_currentOutput.DoctypeSystem != null)
119                                         _emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, 
120                                                 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
121                         }
122                         CheckState ();
123                         _emitter.WriteStartElement (prefix, localName, nsURI);
124                         _state = WriteState.Element;                                            
125                         pendingAttributesPos = 0;
126                 }
127
128                 public override void WriteEndElement ()
129                 {
130                         CheckState ();
131                         _emitter.WriteEndElement ();
132                         _state = WriteState.Content;
133                         //Pop namespace scope
134                         _nsManager.PopScope ();
135                 }
136
137                 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
138                 {                                                                               
139                         //Put attribute to pending attributes collection, replacing namesake one
140                         for (int i = 0; i < pendingAttributesPos; i++) {
141                                 Attribute attr = pendingAttributes [i];
142                                 
143                                 if (attr.LocalName == localName && attr.Namespace == nsURI) {
144                                         attr.Value = value;
145                                         //Keep prefix (e.g. when literal attribute is overriden by xsl:attribute)
146                                         if (attr.Prefix == String.Empty && prefix != String.Empty)
147                                                 attr.Prefix = prefix;
148                                         
149                                         return;
150                                 }
151                         }
152                         
153                         if (pendingAttributesPos == pendingAttributes.Length) {
154                                 Attribute [] old = pendingAttributes;
155                                 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
156                                 if (pendingAttributesPos > 0)
157                                         Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
158                         }
159                         pendingAttributes [pendingAttributesPos].Prefix = prefix;
160                         pendingAttributes [pendingAttributesPos].Namespace = nsURI;
161                         pendingAttributes [pendingAttributesPos].LocalName = localName;
162                         pendingAttributes [pendingAttributesPos].Value = value;
163                         pendingAttributesPos++;
164                 }
165
166                 public override void WriteNamespaceDecl (string prefix, string nsUri)
167                 {
168                         if (prefix == String.Empty) {
169                                 //Default namespace
170                                 if (_nsManager.DefaultNamespace != nsUri) {
171                                         _nsManager.AddNamespace (prefix, nsUri);
172                                         _emitter.WriteAttributeString ("", "xmlns", "", nsUri);
173                                 }
174                         } else if (_nsManager.LookupPrefix (nsUri) == null) {
175                                 //That's new namespace - add it to the collection and emit
176                                 _nsManager.AddNamespace (prefix, nsUri);
177                                 _emitter.WriteAttributeString ("xmlns", prefix, null, nsUri);
178                         }                       
179                 }
180                                         
181                 public override void WriteComment (string text)
182                 {
183                         CheckState ();
184                         _emitter.WriteComment (text);
185                 }
186
187                 public override void WriteProcessingInstruction (string name, string text)
188                 {
189                         CheckState ();
190                         _emitter.WriteProcessingInstruction (name, text);
191                 }
192
193                 public override void WriteString (string text)
194                 {
195                         CheckState ();
196                         _emitter.WriteString (text);
197                 }
198
199                 public override void WriteRaw (string data)
200                 {
201                         CheckState ();
202                         _emitter.WriteRaw (data);
203                 }
204
205                 public override void Done ()
206                 {
207                         _emitter.Done ();
208                         _state = WriteState.Closed;
209                 }
210                 #endregion
211         }
212 }