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)
197 // empty prefix is not allowed
198 // for non-local attributes.
199 prefix = "xp_" + _xpCount++;
200 if (existing != prefix) {
201 newNamespaces.Add (prefix);
202 _currentNamespaceDecls.Add (prefix, attr.Namespace);
203 _nsManager.AddNamespace (prefix, attr.Namespace);
206 Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
208 for (int i = 0; i < newNamespaces.Count; i++)
210 string prefix = (string) newNamespaces [i];
211 string uri = _currentNamespaceDecls [prefix] as string;
213 if (prefix != String.Empty)
214 Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
216 Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
218 _currentNamespaceDecls.Clear ();
219 //Attributes flushed, state is Content now
220 _state = WriteState.Content;
221 newNamespaces.Clear ();
223 _canProcessAttributes = false;
226 #region Outputter's methods implementation
228 public override void WriteStartElement (string prefix, string localName, string nsURI)
230 if (_emitter == null) {
231 this.DetermineOutputMethod (localName, nsURI);
232 if (pendingFirstSpaces != null) {
233 WriteWhitespace (pendingFirstSpaces.ToString ());
234 pendingFirstSpaces = null;
238 if (_state == WriteState.Prolog) {
239 //Seems to be the first element - take care of Doctype
240 // Note that HTML does not require SYSTEM identifier.
241 if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
242 Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName,
243 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
246 if (nsURI == String.Empty)
247 prefix = String.Empty;
248 Emitter.WriteStartElement (prefix, localName, nsURI);
249 _state = WriteState.Element;
250 if (_nsManager.LookupNamespace (prefix, false) != nsURI)
251 // _nsManager.AddNamespace (prefix, nsURI);
252 _currentNamespaceDecls [prefix] = nsURI;
253 pendingAttributesPos = 0;
254 _canProcessAttributes = true;
257 public override void WriteEndElement ()
259 WriteEndElementInternal (false);
262 public override void WriteFullEndElement()
264 WriteEndElementInternal (true);
267 private void WriteEndElementInternal (bool fullEndElement)
271 Emitter.WriteFullEndElement ();
273 Emitter.WriteEndElement ();
274 _state = WriteState.Content;
275 //Pop namespace scope
276 _nsManager.PopScope ();
279 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
281 //Put attribute to pending attributes collection, replacing namesake one
282 for (int i = 0; i < pendingAttributesPos; i++) {
283 Attribute attr = pendingAttributes [i];
285 if (attr.LocalName == localName && attr.Namespace == nsURI) {
286 pendingAttributes [i].Value = value;
287 pendingAttributes [i].Prefix = prefix;
292 if (pendingAttributesPos == pendingAttributes.Length) {
293 Attribute [] old = pendingAttributes;
294 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
295 if (pendingAttributesPos > 0)
296 Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
298 pendingAttributes [pendingAttributesPos].Prefix = prefix;
299 pendingAttributes [pendingAttributesPos].Namespace = nsURI;
300 pendingAttributes [pendingAttributesPos].LocalName = localName;
301 pendingAttributes [pendingAttributesPos].Value = value;
302 pendingAttributesPos++;
305 public override void WriteNamespaceDecl (string prefix, string nsUri)
307 if (_nsManager.LookupNamespace (prefix, false) == nsUri)
308 return; // do nothing
310 for (int i = 0; i < pendingAttributesPos; i++) {
311 Attribute attr = pendingAttributes [i];
312 if (attr.Prefix == prefix || attr.Namespace == nsUri)
313 return; //don't touch explicitly declared attributes
315 if (_currentNamespaceDecls [prefix] as string != nsUri)
316 _currentNamespaceDecls [prefix] = nsUri;
319 public override void WriteComment (string text)
322 Emitter.WriteComment (text);
325 public override void WriteProcessingInstruction (string name, string text)
328 Emitter.WriteProcessingInstruction (name, text);
331 public override void WriteString (string text)
335 Emitter.WriteCDataSection (text);
336 // This weird check is required to reject Doctype
337 // after non-whitespace nodes but also to allow
338 // Doctype after whitespace nodes. It especially
339 // happens when there is an xsl:text before the
340 // document element (e.g. BVTs_bvt066 testcase).
341 else if (_state != WriteState.Content &&
342 text.Length > 0 && XmlChar.IsWhitespace (text))
343 Emitter.WriteWhitespace (text);
345 Emitter.WriteString (text);
348 public override void WriteRaw (string data)
351 Emitter.WriteRaw (data);
354 public override void WriteWhitespace (string text)
356 if (_emitter == null) {
357 if (pendingFirstSpaces == null)
358 pendingFirstSpaces = new StringBuilder ();
359 pendingFirstSpaces.Append (text);
360 if (_state == WriteState.Start)
361 _state = WriteState.Prolog;
364 Emitter.WriteWhitespace (text);
368 public override void Done ()
371 _state = WriteState.Closed;
374 public override bool CanProcessAttributes {
375 get { return _canProcessAttributes; }
378 public override bool InsideCDataSection {
379 get { return _insideCData; }
380 set { _insideCData = value; }