// // GenericOutputter.cs // // Authors: // Oleg Tkachenko (oleg@tkachenko.com) // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp) // // (C) 2003 Oleg Tkachenko, Atsushi Enomoto // using System; using System.Collections; using System.Globalization; using System.Xml; using System.IO; using System.Text; namespace Mono.Xml.Xsl { /// /// Generic implemenatation of the Outputter. /// Works as a buffer between Transformation classes and an Emitter. /// Implements attributes dublicate checking, nemaspace stuff and /// choosing of right Emitter implementation. /// internal class GenericOutputter : Outputter { private Hashtable _outputs; //Current xsl:output private XslOutput _currentOutput; //Underlying emitter private Emitter _emitter; // destination TextWriter, // which is pended until the actual output is determined. private TextWriter pendingTextWriter; // also, whitespaces before the first element are cached. StringBuilder pendingFirstSpaces; //Outputting state private WriteState _state; // Collection of pending attributes. TODO: Can we make adding an attribute // O(1)? I'm not sure it is that important (this would only really make a difference // if elements had like 10 attributes, which is very rare). Attribute [] pendingAttributes = new Attribute [10]; int pendingAttributesPos = 0; //Namespace manager. Subject to optimization. private XmlNamespaceManager _nsManager; private ArrayList _currentNsPrefixes; private Hashtable _currentNamespaceDecls; //Name table private NameTable _nt; // Specified encoding (for TextWriter output) Encoding _encoding; //Determines whether xsl:copy can output attribute-sets or not. bool _canProcessAttributes; bool _insideCData; bool _isVariable; private GenericOutputter (Hashtable outputs, Encoding encoding) { _encoding = encoding; _outputs = outputs; _currentOutput = (XslOutput)outputs [String.Empty]; _state = WriteState.Start; //TODO: Optimize using nametable _nt = new NameTable (); _nsManager = new XmlNamespaceManager (_nt); _currentNsPrefixes = new ArrayList (); _currentNamespaceDecls = new Hashtable (); } public GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding) : this (writer, outputs, encoding, false) { } internal GenericOutputter (XmlWriter writer, Hashtable outputs, Encoding encoding, bool isVariable) : this (outputs, encoding) { _emitter = new XmlWriterEmitter (writer); _state = writer.WriteState; _isVariable = isVariable; } public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding) : this (outputs, encoding) { this.pendingTextWriter = writer; } internal GenericOutputter (TextWriter writer, Hashtable outputs) : this (writer, outputs, null) { } internal GenericOutputter (XmlWriter writer, Hashtable outputs) : this (writer, outputs, null) { } private Emitter Emitter { get { if (_emitter == null) DetermineOutputMethod (null, null); return _emitter; } } private void DetermineOutputMethod (string localName, string ns) { XslOutput xslOutput = (XslOutput)_outputs [String.Empty]; switch (xslOutput.Method) { case OutputMethod.Unknown: if (localName != null && String.Compare (localName, "html", true, CultureInfo.InvariantCulture) == 0 && ns == String.Empty) goto case OutputMethod.HTML; goto case OutputMethod.XML; case OutputMethod.HTML: _emitter = new HtmlEmitter (pendingTextWriter, xslOutput); break; case OutputMethod.XML: XmlTextWriter w = new XmlTextWriter (pendingTextWriter); if (xslOutput.Indent == "yes") w.Formatting = Formatting.Indented; _emitter = new XmlWriterEmitter (w); break; case OutputMethod.Text: _emitter = new TextEmitter (pendingTextWriter); break; case OutputMethod.Custom: throw new NotSupportedException ("Custom output method is not supported in this version."); } pendingTextWriter = null; } /// /// Checks output state and flushes pending attributes and namespaces /// when it's appropriate. /// private void CheckState () { if (_state == WriteState.Start) WriteStartDocument (); if (_state == WriteState.Element) { //Push scope to allow to unwind namespaces scope back in WriteEndElement //Subject to optimization - avoid redundant push/pop by moving //namespaces to WriteStartElement _nsManager.PushScope (); //Emit pending attributes for (int i = 0; i < pendingAttributesPos; i++) { Attribute attr = pendingAttributes [i]; string prefix = attr.Prefix; if (prefix == String.Empty) prefix = _nsManager.LookupPrefix (attr.Namespace); Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value); } for (int i = 0; i < _currentNsPrefixes.Count; i++) { string prefix = (string) _currentNsPrefixes [i]; string uri = _currentNamespaceDecls [prefix] as string; if (prefix != String.Empty) Emitter.WriteAttributeString ("xmlns", prefix, XmlNamespaceManager.XmlnsXmlns, uri); else Emitter.WriteAttributeString (String.Empty, "xmlns", XmlNamespaceManager.XmlnsXmlns, uri); } _currentNsPrefixes.Clear (); _currentNamespaceDecls.Clear (); //Attributes flushed, state is Content now _state = WriteState.Content; } _canProcessAttributes = false; } #region Outputter's methods implementation public override void WriteStartDocument () { if (_isVariable) return; if (!_currentOutput.OmitXmlDeclaration) Emitter.WriteStartDocument (_encoding != null ? _encoding : _currentOutput.Encoding, _currentOutput.Standalone); _state = WriteState.Prolog; } public override void WriteEndDocument () { Emitter.WriteEndDocument (); } int _nsCount; public override void WriteStartElement (string prefix, string localName, string nsURI) { if (_emitter == null) { this.DetermineOutputMethod (localName, nsURI); if (pendingFirstSpaces != null) { WriteWhitespace (pendingFirstSpaces.ToString ()); pendingFirstSpaces = null; } } if (_state == WriteState.Start) WriteStartDocument (); if (_state == WriteState.Prolog) { //Seems to be the first element - take care of Doctype // Note that HTML does not require SYSTEM identifier. if (_currentOutput.DoctypePublic != null || _currentOutput.DoctypeSystem != null) Emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName, _currentOutput.DoctypePublic, _currentOutput.DoctypeSystem); } CheckState (); Emitter.WriteStartElement (prefix, localName, nsURI); _state = WriteState.Element; pendingAttributesPos = 0; _canProcessAttributes = true; } public override void WriteEndElement () { WriteEndElementInternal (false); } public override void WriteFullEndElement() { WriteEndElementInternal (true); } private void WriteEndElementInternal (bool fullEndElement) { CheckState (); if (fullEndElement) Emitter.WriteFullEndElement (); else Emitter.WriteEndElement (); _state = WriteState.Content; //Pop namespace scope _nsManager.PopScope (); } public override void WriteAttributeString (string prefix, string localName, string nsURI, string value) { if (prefix == String.Empty && nsURI != String.Empty) { prefix = "xp_" + _nsCount; _nsManager.AddNamespace (prefix, nsURI); _currentNsPrefixes.Add (prefix); _currentNamespaceDecls.Add (prefix, nsURI); _nsCount++; } //Put attribute to pending attributes collection, replacing namesake one for (int i = 0; i < pendingAttributesPos; i++) { Attribute attr = pendingAttributes [i]; if (attr.LocalName == localName && attr.Namespace == nsURI) { pendingAttributes [i].Value = value; //Keep prefix (e.g. when literal attribute is overriden by xsl:attribute) if (attr.Prefix == String.Empty && prefix != String.Empty) pendingAttributes [i].Prefix = prefix; return; } } if (pendingAttributesPos == pendingAttributes.Length) { Attribute [] old = pendingAttributes; pendingAttributes = new Attribute [pendingAttributesPos * 2 + 1]; if (pendingAttributesPos > 0) Array.Copy (old, 0, pendingAttributes, 0, pendingAttributesPos); } pendingAttributes [pendingAttributesPos].Prefix = prefix; pendingAttributes [pendingAttributesPos].Namespace = nsURI; pendingAttributes [pendingAttributesPos].LocalName = localName; pendingAttributes [pendingAttributesPos].Value = value; pendingAttributesPos++; } public override void WriteNamespaceDecl (string prefix, string nsUri) { if (_nsManager.LookupNamespace (prefix) == nsUri) return; if (prefix == String.Empty) { //Default namespace if (_nsManager.DefaultNamespace != nsUri) _nsManager.AddNamespace (prefix, nsUri); } else if (_nsManager.LookupPrefix (nsUri) == null) //That's new namespace - add it to the collection _nsManager.AddNamespace (prefix, nsUri); if (_currentNamespaceDecls [prefix] as string != nsUri) { if (!_currentNsPrefixes.Contains (prefix)) _currentNsPrefixes.Add (prefix); _currentNamespaceDecls [prefix] = nsUri; } } public override void WriteComment (string text) { CheckState (); Emitter.WriteComment (text); } public override void WriteProcessingInstruction (string name, string text) { CheckState (); Emitter.WriteProcessingInstruction (name, text); } public override void WriteString (string text) { CheckState (); if (_insideCData) Emitter.WriteCDataSection (text); else Emitter.WriteString (text); } public override void WriteRaw (string data) { CheckState (); Emitter.WriteRaw (data); } public override void WriteWhitespace (string text) { if (_emitter == null) { if (pendingFirstSpaces == null) pendingFirstSpaces = new StringBuilder (); pendingFirstSpaces.Append (text); if (_state == WriteState.Start) _state = WriteState.Prolog; } else { CheckState (); Emitter.WriteWhitespace (text); } } public override void Done () { Emitter.Done (); _state = WriteState.Closed; } public override bool CanProcessAttributes { get { return _canProcessAttributes; } } public override WriteState WriteState { get { return _state; } } public override bool InsideCDataSection { get { return _insideCData; } set { _insideCData = value; } } #endregion } }