5 // Oleg Tkachenko (oleg@tkachenko.com)
6 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
8 // (C) 2003 Oleg Tkachenko, Atsushi Enomoto
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:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
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.
33 using System.Collections;
34 using System.Globalization;
39 namespace Mono.Xml.Xsl
42 /// Generic implemenatation of the Outputter.
43 /// Works as a buffer between Transformation classes and an Emitter.
44 /// Implements attributes dublicate checking, nemaspace stuff and
45 /// choosing of right Emitter implementation.
47 internal class GenericOutputter : Outputter {
48 private Hashtable _outputs;
50 private XslOutput _currentOutput;
52 private Emitter _emitter;
53 // destination TextWriter,
54 // which is pended until the actual output is determined.
55 private TextWriter pendingTextWriter;
56 // also, whitespaces before the first element are cached.
57 StringBuilder pendingFirstSpaces;
59 private WriteState _state;
60 // Collection of pending attributes. TODO: Can we make adding an attribute
61 // O(1)? I'm not sure it is that important (this would only really make a difference
62 // if elements had like 10 attributes, which is very rare).
63 Attribute [] pendingAttributes = new Attribute [10];
64 int pendingAttributesPos = 0;
65 //Namespace manager. Subject to optimization.
66 private XmlNamespaceManager _nsManager;
67 private ArrayList _currentNsPrefixes;
68 private Hashtable _currentNamespaceDecls;
69 // See CheckState(). This is just a cache.
70 private ArrayList newNamespaces = new ArrayList();
72 private NameTable _nt;
73 // Specified encoding (for TextWriter output)
75 //Determines whether xsl:copy can output attribute-sets or not.
76 bool _canProcessAttributes;
79 bool _omitXmlDeclaration;
82 private GenericOutputter (Hashtable outputs, Encoding encoding)
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 _currentNsPrefixes = new ArrayList ();
92 _currentNamespaceDecls = new Hashtable ();
93 _omitXmlDeclaration = false;
96 public GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding)
97 : this (writer, outputs, encoding, false)
101 internal GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding, bool isVariable)
102 : this (outputs, encoding)
104 _emitter = new XmlWriterEmitter (writer);
105 _state = writer.WriteState;
106 _isVariable = isVariable;
107 _omitXmlDeclaration = true; // .Net never writes XML declaration via XmlWriter
110 public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
111 : this (outputs, encoding)
113 this.pendingTextWriter = writer;
117 internal GenericOutputter (TextWriter writer, Hashtable outputs)
118 : this (writer, outputs, null)
122 internal GenericOutputter (XmlWriter writer, Hashtable outputs)
123 : this (writer, outputs, null)
127 private Emitter Emitter {
129 if (_emitter == null)
130 DetermineOutputMethod (null, null);
135 private void DetermineOutputMethod (string localName, string ns)
137 XslOutput xslOutput = (XslOutput)_outputs [String.Empty];
138 switch (xslOutput.Method) {
139 default: // .Custom format is not supported, only handled as unknown
140 case OutputMethod.Unknown:
141 if (localName != null && String.Compare (localName, "html", true, CultureInfo.InvariantCulture) == 0 && ns == String.Empty)
142 goto case OutputMethod.HTML;
143 goto case OutputMethod.XML;
144 case OutputMethod.HTML:
145 _emitter = new HtmlEmitter (pendingTextWriter, xslOutput);
147 case OutputMethod.XML:
148 XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
149 if (xslOutput.Indent == "yes")
150 w.Formatting = Formatting.Indented;
152 _emitter = new XmlWriterEmitter (w);
153 if (!_omitXmlDeclaration && !xslOutput.OmitXmlDeclaration)
154 _emitter.WriteStartDocument (
155 _encoding != null ? _encoding : xslOutput.Encoding,
156 xslOutput.Standalone);
159 case OutputMethod.Text:
160 _emitter = new TextEmitter (pendingTextWriter);
163 pendingTextWriter = null;
167 /// Checks output state and flushes pending attributes and namespaces
168 /// when it's appropriate.
170 private void CheckState ()
172 if (_state == WriteState.Element) {
173 //Push scope to allow to unwind namespaces scope back in WriteEndElement
174 //Subject to optimization - avoid redundant push/pop by moving
175 //namespaces to WriteStartElement
176 //Emit pending attributes
177 newNamespaces.Clear (); //remember indexes of new prefexes
178 _nsManager.PushScope ();
179 for (int i = 0; i < _currentNsPrefixes.Count; i++)
181 string prefix = (string) _currentNsPrefixes [i];
182 string uri = _currentNamespaceDecls [prefix] as string;
184 if (_nsManager.LookupNamespace (prefix, false) == uri)
187 newNamespaces.Add(i);
188 _nsManager.AddNamespace (prefix, uri);
190 for (int i = 0; i < pendingAttributesPos; i++)
192 Attribute attr = pendingAttributes [i];
193 string prefix = _nsManager.LookupPrefix (attr.Namespace, false);
194 if (prefix == null) {
195 prefix = attr.Prefix;
196 _nsManager.AddNamespace (prefix, attr.Namespace);
199 Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
201 for (int i = 0; i < newNamespaces.Count; i++)
203 string prefix = (string) _currentNsPrefixes [(int)newNamespaces[i]];
204 string uri = _currentNamespaceDecls [prefix] as string;
206 if (prefix != String.Empty)
207 Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
209 Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
211 _currentNsPrefixes.Clear ();
212 _currentNamespaceDecls.Clear ();
213 //Attributes flushed, state is Content now
214 _state = WriteState.Content;
216 _canProcessAttributes = false;
219 #region Outputter's methods implementation
221 public override void WriteStartElement (string prefix, string localName, string nsURI)
223 if (_emitter == null) {
224 this.DetermineOutputMethod (localName, nsURI);
225 if (pendingFirstSpaces != null) {
226 WriteWhitespace (pendingFirstSpaces.ToString ());
227 pendingFirstSpaces = null;
231 if (_state == WriteState.Prolog) {
232 //Seems to be the first element - take care of Doctype
233 // Note that HTML does not require SYSTEM identifier.
234 if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
235 Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName,
236 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
239 if (nsURI == String.Empty)
240 prefix = String.Empty;
241 Emitter.WriteStartElement (prefix, localName, nsURI);
242 _state = WriteState.Element;
243 if (_nsManager.LookupNamespace (prefix, false) != nsURI)
244 _nsManager.AddNamespace (prefix, nsURI);
245 pendingAttributesPos = 0;
246 _canProcessAttributes = true;
249 public override void WriteEndElement ()
251 WriteEndElementInternal (false);
254 public override void WriteFullEndElement()
256 WriteEndElementInternal (true);
259 private void WriteEndElementInternal (bool fullEndElement)
263 Emitter.WriteFullEndElement ();
265 Emitter.WriteEndElement ();
266 _state = WriteState.Content;
267 //Pop namespace scope
268 _nsManager.PopScope ();
271 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
273 if (prefix == String.Empty && nsURI != String.Empty ||
274 prefix == "xml" && nsURI != XmlNamespaceManager.XmlnsXml)
275 prefix = "xp_" + _xpCount++;
277 //Put attribute to pending attributes collection, replacing namesake one
278 for (int i = 0; i < pendingAttributesPos; i++) {
279 Attribute attr = pendingAttributes [i];
281 if (attr.LocalName == localName && attr.Namespace == nsURI) {
282 pendingAttributes [i].Value = value;
283 //Keep prefix (e.g. when literal attribute is overriden by xsl:attribute)
284 if (attr.Prefix == String.Empty && prefix != String.Empty)
285 pendingAttributes [i].Prefix = prefix;
290 if (pendingAttributesPos == pendingAttributes.Length) {
291 Attribute [] old = pendingAttributes;
292 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
293 if (pendingAttributesPos > 0)
294 Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
296 pendingAttributes [pendingAttributesPos].Prefix = prefix;
297 pendingAttributes [pendingAttributesPos].Namespace = nsURI;
298 pendingAttributes [pendingAttributesPos].LocalName = localName;
299 pendingAttributes [pendingAttributesPos].Value = value;
300 pendingAttributesPos++;
303 public override void WriteNamespaceDecl (string prefix, string nsUri)
305 if (_nsManager.LookupNamespace (prefix, false) == nsUri)
306 return; // do nothing
308 for (int i = 0; i < pendingAttributesPos; i++) {
309 Attribute attr = pendingAttributes [i];
310 if (attr.Prefix == prefix || attr.Namespace == nsUri)
311 return; //don't touch explicitly declared attributes
313 if (_currentNamespaceDecls [prefix] as string != nsUri) {
314 if (!_currentNsPrefixes.Contains (prefix))
315 _currentNsPrefixes.Add (prefix);
316 _currentNamespaceDecls [prefix] = nsUri;
320 public override void WriteComment (string text)
323 Emitter.WriteComment (text);
326 public override void WriteProcessingInstruction (string name, string text)
329 Emitter.WriteProcessingInstruction (name, text);
332 public override void WriteString (string text)
336 Emitter.WriteCDataSection (text);
338 Emitter.WriteString (text);
341 public override void WriteRaw (string data)
344 Emitter.WriteRaw (data);
347 public override void WriteWhitespace (string text)
349 if (_emitter == null) {
350 if (pendingFirstSpaces == null)
351 pendingFirstSpaces = new StringBuilder ();
352 pendingFirstSpaces.Append (text);
353 if (_state == WriteState.Start)
354 _state = WriteState.Prolog;
357 Emitter.WriteWhitespace (text);
361 public override void Done ()
364 _state = WriteState.Closed;
367 public override bool CanProcessAttributes {
368 get { return _canProcessAttributes; }
371 public override bool InsideCDataSection {
372 get { return _insideCData; }
373 set { _insideCData = value; }