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;
16 namespace Mono.Xml.Xsl
19 /// Generic implemenatation of the Outputter.
20 /// Works as a buffer between Transformation classes and an Emitter.
21 /// Implements attributes dublicate checking, nemaspace stuff and
22 /// choosing of right Emitter implementation.
24 public class GenericOutputter : Outputter {
25 private Hashtable _outputs;
27 private XslOutput _currentOutput;
29 private Emitter _emitter;
31 private WriteState _state;
32 // Collection of pending attributes. TODO: Can we make adding an attribute
33 // O(1)? I'm not sure it is that important (this would only really make a difference
34 // if elements had like 10 attributes, which is very rare).
35 Attribute [] pendingAttributes = new Attribute [10];
36 int pendingAttributesPos = 0;
37 //Namespace manager. Subject to optimization.
38 private XmlNamespaceManager _nsManager;
39 private ArrayList _currentNsPrefixes;
40 private Hashtable _currentNamespaceDecls;
42 private NameTable _nt;
43 //Determines whether xsl:copy can output attribute-sets or not.
44 bool canProcessAttributes;
47 private GenericOutputter (Hashtable outputs)
50 _currentOutput = (XslOutput)outputs [String.Empty];
51 _state = WriteState.Start;
52 //TODO: Optimize using nametable
53 _nt = new NameTable ();
54 _nsManager = new XmlNamespaceManager (_nt);
55 _currentNsPrefixes = new ArrayList ();
56 _currentNamespaceDecls = new Hashtable ();
59 public GenericOutputter (XmlWriter writer, Hashtable outputs)
62 _emitter = new XmlWriterEmitter (writer);
63 _state = writer.WriteState;
66 public GenericOutputter (TextWriter writer, Hashtable outputs)
69 XslOutput xslOutput = (XslOutput)outputs [String.Empty];
70 switch (xslOutput.Method) {
72 case OutputMethod.HTML:
73 _emitter = new HtmlEmitter (writer, xslOutput);
75 case OutputMethod.Unknown: //TODO: handle xml vs html
76 case OutputMethod.XML:
77 XmlTextWriter w = new XmlTextWriter (writer);
78 if (xslOutput.Indent == "yes")
79 w.Formatting = Formatting.Indented;
80 _emitter = new XmlWriterEmitter (w);
82 case OutputMethod.Text:
83 _emitter = new TextEmitter (writer);
85 case OutputMethod.Custom:
86 throw new NotImplementedException ("Custom output method is not implemented yet.");
91 /// Checks output state and flushes pending attributes and namespaces
92 /// when it's appropriate.
94 private void CheckState ()
96 if (_state == WriteState.Start)
97 WriteStartDocument ();
99 if (_state == WriteState.Element) {
100 //Push scope to allow to unwind namespaces scope back in WriteEndElement
101 //Subject to optimization - avoid redundant push/pop by moving
102 //namespaces to WriteStartElement
103 _nsManager.PushScope ();
104 //Emit pending attributes
105 for (int i = 0; i < pendingAttributesPos; i++) {
106 Attribute attr = pendingAttributes [i];
107 string prefix = attr.Prefix;
108 if (prefix == String.Empty)
109 prefix = _nsManager.LookupPrefix (attr.Namespace);
110 _emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
112 foreach (string prefix in _currentNsPrefixes) {
113 string uri = _currentNamespaceDecls [prefix] as string;
114 if (prefix != String.Empty)
115 _emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri);
117 _emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri);
119 _currentNsPrefixes.Clear ();
120 _currentNamespaceDecls.Clear ();
121 //Attributes flushed, state is Content now
122 _state = WriteState.Content;
124 canProcessAttributes = false;
127 #region Outputter's methods implementation
129 public override void WriteStartDocument ()
131 if (!_currentOutput.OmitXmlDeclaration)
132 _emitter.WriteStartDocument (_currentOutput.Standalone);
134 _state = WriteState.Prolog;
137 public override void WriteEndDocument ()
139 _emitter.WriteEndDocument ();
143 public override void WriteStartElement (string prefix, string localName, string nsURI)
145 if (_state == WriteState.Start)
146 WriteStartDocument ();
148 if (_state == WriteState.Prolog) {
149 //Seems to be the first element - take care of Doctype
150 // Note that HTML does not require SYSTEM identifier.
151 if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null)
152 _emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName,
153 _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem);
156 _emitter.WriteStartElement (prefix, localName, nsURI);
157 _state = WriteState.Element;
158 pendingAttributesPos = 0;
159 canProcessAttributes = true;
162 public override void WriteEndElement ()
164 WriteEndElementInternal (false);
167 public override void WriteFullEndElement()
169 WriteEndElementInternal (true);
172 private void WriteEndElementInternal (bool fullEndElement)
176 _emitter.WriteFullEndElement ();
178 _emitter.WriteEndElement ();
179 _state = WriteState.Content;
180 //Pop namespace scope
181 _nsManager.PopScope ();
184 public override void WriteAttributeString (string prefix, string localName, string nsURI, string value)
186 if (prefix == String.Empty && nsURI != String.Empty) {
187 prefix = "xp_" + _nsCount;
188 _nsManager.AddNamespace (prefix, nsURI);
189 _currentNsPrefixes.Add (prefix);
190 _currentNamespaceDecls.Add (prefix, nsURI);
194 //Put attribute to pending attributes collection, replacing namesake one
195 for (int i = 0; i < pendingAttributesPos; i++) {
196 Attribute attr = pendingAttributes [i];
198 if (attr.LocalName == localName && attr.Namespace == nsURI) {
199 pendingAttributes [i].Value = value;
200 //Keep prefix (e.g. when literal attribute is overriden by xsl:attribute)
201 if (attr.Prefix == String.Empty && prefix != String.Empty)
202 pendingAttributes [i].Prefix = prefix;
207 if (pendingAttributesPos == pendingAttributes.Length) {
208 Attribute [] old = pendingAttributes;
209 pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1];
210 if (pendingAttributesPos > 0)
211 Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos);
213 pendingAttributes [pendingAttributesPos].Prefix = prefix;
214 pendingAttributes [pendingAttributesPos].Namespace = nsURI;
215 pendingAttributes [pendingAttributesPos].LocalName = localName;
216 pendingAttributes [pendingAttributesPos].Value = value;
217 pendingAttributesPos++;
220 public override void WriteNamespaceDecl (string prefix, string nsUri)
222 if (_nsManager.LookupNamespace (prefix) == nsUri)
225 if (prefix == String.Empty) {
227 if (_nsManager.DefaultNamespace != nsUri)
228 _nsManager.AddNamespace (prefix, nsUri);
229 } else if (_nsManager.LookupPrefix (nsUri) == null)
230 //That's new namespace - add it to the collection
231 _nsManager.AddNamespace (prefix, nsUri);
233 if (_currentNamespaceDecls [prefix] as string != nsUri) {
234 if (!_currentNsPrefixes.Contains (prefix))
235 _currentNsPrefixes.Add (prefix);
236 _currentNamespaceDecls [prefix] = nsUri;
240 public override void WriteComment (string text)
243 _emitter.WriteComment (text);
246 public override void WriteProcessingInstruction (string name, string text)
249 _emitter.WriteProcessingInstruction (name, text);
252 public override void WriteString (string text)
256 _emitter.WriteCDataSection (text);
258 _emitter.WriteString (text);
261 public override void WriteRaw (string data)
264 _emitter.WriteRaw (data);
267 public override void WriteWhitespace (string text)
270 _emitter.WriteWhitespace (text);
273 public override void Done ()
276 _state = WriteState.Closed;
279 public override bool CanProcessAttributes {
280 get { return canProcessAttributes; }
283 public override WriteState WriteState { get { return _state; } }
285 public override bool InsideCDataSection {
286 get { return insideCData; }
287 set { insideCData = value; }