5 // Oleg Tkachenko (oleg@tkachenko.com)
6 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
8 // (C) 2003 Oleg Tkachenko, Atsushi Enomoto
12 using System.Collections;
13 using System.Collections.Specialized;
18 namespace Mono.Xml.Xsl
21 /// Generic implemenatation of the Outputter.
22 /// Works as a buffer between Transformation classes and an Emitter.
23 /// Implements attributes dublicate checking, nemaspace stuff and
24 /// choosing of right Emitter implementation.
26 internal class GenericOutputter : Outputter {
27 private Hashtable _outputs;
29 private XslOutput _currentOutput;
31 private Emitter _emitter;
32 // destination TextWriter,
33 // which is pended until the actual output is determined.
34 private TextWriter pendingTextWriter;
35 // also, whitespaces before the first element are cached.
36 StringBuilder pendingFirstSpaces;
38 private WriteState _state;
39 // Collection of pending attributes. TODO: Can we make adding an attribute
40 // O(1)? I'm not sure it is that important (this would only really make a difference
41 // if elements had like 10 attributes, which is very rare).
42 Attribute [] pendingAttributes = new Attribute [10];
43 int pendingAttributesPos = 0;
44 //Namespace manager. Subject to optimization.
45 private XmlNamespaceManager _nsManager;
46 private StringCollection _currentNsPrefixes;
47 private Hashtable _currentNamespaceDecls;
49 private NameTable _nt;
50 // Specified encoding (for TextWriter output)
52 //Determines whether xsl:copy can output attribute-sets or not.
53 bool _canProcessAttributes;
57 private GenericOutputter (Hashtable outputs, Encoding encoding)
61 _currentOutput = (XslOutput)outputs [String.Empty];
62 _state = WriteState.Start;
63 //TODO: Optimize using nametable
64 _nt = new NameTable ();
65 _nsManager = new XmlNamespaceManager (_nt);
66 _currentNsPrefixes = new StringCollection ();
67 _currentNamespaceDecls = new Hashtable ();
70 public GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding)
71 : this (writer, outputs, encoding, false)
75 internal GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding, bool isVariable)
76 : this (outputs, encoding)
78 _emitter = new XmlWriterEmitter (writer);
79 _state = writer.WriteState;
80 _isVariable = isVariable;
83 public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
84 : this (outputs, encoding)
86 this.pendingTextWriter = writer;
90 internal GenericOutputter (TextWriter writer, Hashtable outputs)
91 : this (writer, outputs, null)
95 internal GenericOutputter (XmlWriter writer, Hashtable outputs)
96 : this (writer, outputs, null)
100 private Emitter Emitter {
102 if (_emitter == null)
103 DetermineOutputMethod (null, null);
108 private void DetermineOutputMethod (string localName, string ns)
110 XslOutput xslOutput = (XslOutput)_outputs [String.Empty];
111 switch (xslOutput.Method) {
112 case OutputMethod.Unknown:
113 if (localName != null && localName.ToLower () == "html" && ns == String.Empty)
114 goto case OutputMethod.HTML;
115 goto case OutputMethod.XML;
116 case OutputMethod.HTML:
117 _emitter = new HtmlEmitter (pendingTextWriter, xslOutput);
119 case OutputMethod.XML:
120 XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
121 if (xslOutput.Indent == "yes")
122 w.Formatting = Formatting.Indented;
123 _emitter = new XmlWriterEmitter (w);
125 case OutputMethod.Text:
126 _emitter = new TextEmitter (pendingTextWriter);
128 case OutputMethod.Custom:
129 throw new NotSupportedException ("Custom output method is not supported in this version.");
131 pendingTextWriter = null;
135 /// Checks output state and flushes pending attributes and namespaces
136 /// when it's appropriate.
138 private void CheckState ()
140 if (_state == WriteState.Start)
141 WriteStartDocument ();
143 if (_state == WriteState.Element) {
144 //Push scope to allow to unwind namespaces scope back in WriteEndElement
145 //Subject to optimization - avoid redundant push/pop by moving
146 //namespaces to WriteStartElement
147 _nsManager.PushScope ();
148 //Emit pending attributes
149 for (int i = 0; i < pendingAttributesPos; i++) {
150 Attribute attr = pendingAttributes [i];
151 string prefix = attr.Prefix;
152 if (prefix == String.Empty)
153 prefix = _nsManager.LookupPrefix (attr.Namespace);
154 Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
156 for (int i = 0; i < _currentNsPrefixes.Count; i++) {
157 string prefix = _currentNsPrefixes [i];
158 string uri = _currentNamespaceDecls [prefix] as string;
159 if (prefix != String.Empty)
160 Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
162 Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
164 _currentNsPrefixes.Clear ();
165 _currentNamespaceDecls.Clear ();
166 //Attributes flushed, state is Content now
167 _state = WriteState.Content;
169 _canProcessAttributes = false;
172 #region Outputter's methods implementation
174 public override void WriteStartDocument ()
179 if (!_currentOutput.OmitXmlDeclaration)
180 Emitter.WriteStartDocument (_encoding != null ? _encoding : _currentOutput.Encoding, _currentOutput.Standalone);
182 _state = WriteState.Prolog;
185 public override void WriteEndDocument ()
187 Emitter.WriteEndDocument ();
191 public override void WriteStartElement (string prefix, string localName, string nsURI)
193 if (_emitter == null) {
194 this.DetermineOutputMethod (localName, nsURI);
195 if (pendingFirstSpaces != null) {
196 WriteWhitespace (pendingFirstSpaces.ToString ());
197 pendingFirstSpaces = null;
201 if (_state == WriteState.Start)
202 WriteStartDocument ();
204 if (_state == WriteState.Prolog) {
205 //Seems to be the first element - take care of Doctype
206 // Note that HTML does not require SYSTEM identifier.
207 if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
208 Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName,
209 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
212 Emitter.WriteStartElement (prefix, localName, nsURI);
213 _state = WriteState.Element;
214 pendingAttributesPos = 0;
215 _canProcessAttributes = true;
218 public override void WriteEndElement ()
220 WriteEndElementInternal (false);
223 public override void WriteFullEndElement()
225 WriteEndElementInternal (true);
228 private void WriteEndElementInternal (bool fullEndElement)
232 Emitter.WriteFullEndElement ();
234 Emitter.WriteEndElement ();
235 _state = WriteState.Content;
236 //Pop namespace scope
237 _nsManager.PopScope ();
240 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
242 if (prefix == String.Empty && nsURI != String.Empty) {
243 prefix = "xp_" + _nsCount;
244 _nsManager.AddNamespace (prefix, nsURI);
245 _currentNsPrefixes.Add (prefix);
246 _currentNamespaceDecls.Add (prefix, nsURI);
250 //Put attribute to pending attributes collection, replacing namesake one
251 for (int i = 0; i < pendingAttributesPos; i++) {
252 Attribute attr = pendingAttributes [i];
254 if (attr.LocalName == localName && attr.Namespace == nsURI) {
255 pendingAttributes [i].Value = value;
256 //Keep prefix (e.g. when literal attribute is overriden by xsl:attribute)
257 if (attr.Prefix == String.Empty && prefix != String.Empty)
258 pendingAttributes [i].Prefix = prefix;
263 if (pendingAttributesPos == pendingAttributes.Length) {
264 Attribute [] old = pendingAttributes;
265 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
266 if (pendingAttributesPos > 0)
267 Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
269 pendingAttributes [pendingAttributesPos].Prefix = prefix;
270 pendingAttributes [pendingAttributesPos].Namespace = nsURI;
271 pendingAttributes [pendingAttributesPos].LocalName = localName;
272 pendingAttributes [pendingAttributesPos].Value = value;
273 pendingAttributesPos++;
276 public override void WriteNamespaceDecl (string prefix, string nsUri)
278 if (_nsManager.LookupNamespace (prefix) == nsUri)
281 if (prefix == String.Empty) {
283 if (_nsManager.DefaultNamespace != nsUri)
284 _nsManager.AddNamespace (prefix, nsUri);
285 } else if (_nsManager.LookupPrefix (nsUri) == null)
286 //That's new namespace - add it to the collection
287 _nsManager.AddNamespace (prefix, nsUri);
289 if (_currentNamespaceDecls [prefix] as string != nsUri) {
290 if (!_currentNsPrefixes.Contains (prefix))
291 _currentNsPrefixes.Add (prefix);
292 _currentNamespaceDecls [prefix] = nsUri;
296 public override void WriteComment (string text)
299 Emitter.WriteComment (text);
302 public override void WriteProcessingInstruction (string name, string text)
305 Emitter.WriteProcessingInstruction (name, text);
308 public override void WriteString (string text)
312 Emitter.WriteCDataSection (text);
314 Emitter.WriteString (text);
317 public override void WriteRaw (string data)
320 Emitter.WriteRaw (data);
323 public override void WriteWhitespace (string text)
325 if (_emitter == null) {
326 if (pendingFirstSpaces == null)
327 pendingFirstSpaces = new StringBuilder ();
328 pendingFirstSpaces.Append (text);
329 if (_state == WriteState.Start)
330 _state = WriteState.Prolog;
333 Emitter.WriteWhitespace (text);
337 public override void Done ()
340 _state = WriteState.Closed;
343 public override bool CanProcessAttributes {
344 get { return _canProcessAttributes; }
347 public override WriteState WriteState { get { return _state; } }
349 public override bool InsideCDataSection {
350 get { return _insideCData; }
351 set { _insideCData = value; }