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.Collections.Specialized;
35 using System.Globalization;
40 namespace Mono.Xml.Xsl
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.
48 internal class GenericOutputter : Outputter {
49 private Hashtable _outputs;
51 private XslOutput _currentOutput;
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;
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();
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 _currentNamespaceDecls = new ListDictionary ();
92 _omitXmlDeclaration = false;
95 public GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding)
96 : this (writer, outputs, encoding, false)
100 internal GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding, bool isVariable)
101 : this (outputs, encoding)
103 _emitter = new XmlWriterEmitter (writer);
104 _state = writer.WriteState;
105 // _isVariable = isVariable;
106 _omitXmlDeclaration = true; // .Net never writes XML declaration via XmlWriter
109 public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
110 : this (outputs, encoding)
112 this.pendingTextWriter = writer;
116 internal GenericOutputter (TextWriter writer, Hashtable outputs)
117 : this (writer, outputs, null)
121 internal GenericOutputter (XmlWriter writer, Hashtable outputs)
122 : this (writer, outputs, null)
126 private Emitter Emitter {
128 if (_emitter == null)
129 DetermineOutputMethod (null, null);
134 private void DetermineOutputMethod (string localName, string ns)
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);
146 case OutputMethod.XML:
147 XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
148 if (xslOutput.Indent == "yes")
149 w.Formatting = Formatting.Indented;
151 _emitter = new XmlWriterEmitter (w);
152 if (!_omitXmlDeclaration && !xslOutput.OmitXmlDeclaration)
153 _emitter.WriteStartDocument (
154 _encoding != null ? _encoding : xslOutput.Encoding,
155 xslOutput.Standalone);
158 case OutputMethod.Text:
159 _emitter = new TextEmitter (pendingTextWriter);
162 pendingTextWriter = null;
166 /// Checks output state and flushes pending attributes and namespaces
167 /// when it's appropriate.
169 private void CheckState ()
171 if (_state == WriteState.Element) {
172 //Emit pending attributes
173 _nsManager.PushScope ();
174 foreach (string prefix in _currentNamespaceDecls.Keys)
176 string uri = _currentNamespaceDecls [prefix] as string;
178 if (_nsManager.LookupNamespace (prefix, false) == uri)
181 newNamespaces.Add (prefix);
182 _nsManager.AddNamespace (prefix, uri);
184 for (int i = 0; i < pendingAttributesPos; i++)
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)
195 if (attr.Namespace.Length > 0) {
196 if (prefix == null || prefix == String.Empty)
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);
210 Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
212 for (int i = 0; i < newNamespaces.Count; i++)
214 string prefix = (string) newNamespaces [i];
215 string uri = _currentNamespaceDecls [prefix] as string;
217 if (prefix != String.Empty)
218 Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
220 Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
222 _currentNamespaceDecls.Clear ();
223 //Attributes flushed, state is Content now
224 _state = WriteState.Content;
225 newNamespaces.Clear ();
227 _canProcessAttributes = false;
230 #region Outputter's methods implementation
232 public override void WriteStartElement (string prefix, string localName, string nsURI)
234 if (_emitter == null) {
235 this.DetermineOutputMethod (localName, nsURI);
236 if (pendingFirstSpaces != null) {
237 WriteWhitespace (pendingFirstSpaces.ToString ());
238 pendingFirstSpaces = null;
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);
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;
261 public override void WriteEndElement ()
263 WriteEndElementInternal (false);
266 public override void WriteFullEndElement()
268 WriteEndElementInternal (true);
271 private void WriteEndElementInternal (bool fullEndElement)
275 Emitter.WriteFullEndElement ();
277 Emitter.WriteEndElement ();
278 _state = WriteState.Content;
279 //Pop namespace scope
280 _nsManager.PopScope ();
283 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
285 //Put attribute to pending attributes collection, replacing namesake one
286 for (int i = 0; i < pendingAttributesPos; i++) {
287 Attribute attr = pendingAttributes [i];
289 if (attr.LocalName == localName && attr.Namespace == nsURI) {
290 pendingAttributes [i].Value = value;
291 pendingAttributes [i].Prefix = prefix;
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);
302 pendingAttributes [pendingAttributesPos].Prefix = prefix;
303 pendingAttributes [pendingAttributesPos].Namespace = nsURI;
304 pendingAttributes [pendingAttributesPos].LocalName = localName;
305 pendingAttributes [pendingAttributesPos].Value = value;
306 pendingAttributesPos++;
309 public override void WriteNamespaceDecl (string prefix, string nsUri)
311 if (_nsManager.LookupNamespace (prefix, false) == nsUri)
312 return; // do nothing
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
319 if (_currentNamespaceDecls [prefix] as string != nsUri)
320 _currentNamespaceDecls [prefix] = nsUri;
323 public override void WriteComment (string text)
326 Emitter.WriteComment (text);
329 public override void WriteProcessingInstruction (string name, string text)
332 Emitter.WriteProcessingInstruction (name, text);
335 public override void WriteString (string text)
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);
349 Emitter.WriteString (text);
352 public override void WriteRaw (string data)
355 Emitter.WriteRaw (data);
358 public override void WriteWhitespace (string text)
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;
368 Emitter.WriteWhitespace (text);
372 public override void Done ()
375 _state = WriteState.Closed;
378 public override bool CanProcessAttributes {
379 get { return _canProcessAttributes; }
382 public override bool InsideCDataSection {
383 get { return _insideCData; }
384 set { _insideCData = value; }