//
// Authors:
// Oleg Tkachenko (oleg@tkachenko.com)
+// Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
//
-// (C) 2003 Oleg Tkachenko
+// (C) 2003 Oleg Tkachenko, Atsushi Enomoto
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
+using System.Globalization;
using System.Xml;
using System.IO;
+using System.Text;
namespace Mono.Xml.Xsl
{
/// Implements attributes dublicate checking, nemaspace stuff and
/// choosing of right Emitter implementation.
/// </summary>
- public class GenericOutputter : Outputter {
+ 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).
- ArrayList pendingAttributes = new ArrayList ();
+ Attribute [] pendingAttributes = new Attribute [10];
+ int pendingAttributesPos = 0;
//Namespace manager. Subject to optimization.
private XmlNamespaceManager _nsManager;
+ private ArrayList _currentNsPrefixes;
+ private Hashtable _currentNamespaceDecls;
+ // See CheckState(). This is just a cache.
+ private ArrayList newNamespaces = new ArrayList();
//Name table
private NameTable _nt;
-
- private GenericOutputter (Hashtable outputs)
+ // Specified encoding (for TextWriter output)
+ Encoding _encoding;
+ //Determines whether xsl:copy can output attribute-sets or not.
+ bool _canProcessAttributes;
+ bool _insideCData;
+ bool _isVariable;
+ bool _omitXmlDeclaration;
+ int _xpCount;
+
+ private GenericOutputter (Hashtable outputs, Encoding encoding)
{
+ _encoding = encoding;
_outputs = outputs;
_currentOutput = (XslOutput)outputs [String.Empty];
- _state = WriteState.Start;
+ _state = WriteState.Prolog;
//TODO: Optimize using nametable
_nt = new NameTable ();
_nsManager = new XmlNamespaceManager (_nt);
+ _currentNsPrefixes = new ArrayList ();
+ _currentNamespaceDecls = new Hashtable ();
+ _omitXmlDeclaration = false;
+ }
+
+ 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;
+ _omitXmlDeclaration = true; // .Net never writes XML declaration via XmlWriter
}
- public GenericOutputter (XmlWriter writer, Hashtable outputs)
- : this (outputs)
+ public GenericOutputter (TextWriter writer, Hashtable outputs, Encoding encoding)
+ : this (outputs, encoding)
{
- _emitter = new XmlWriterEmitter (writer);
+ 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;
+ }
}
- public GenericOutputter (TextWriter writer, Hashtable outputs)
- : this (outputs)
- {
- XslOutput xslOutput = (XslOutput)outputs [String.Empty];
+ private void DetermineOutputMethod (string localName, string ns)
+ {
+ XslOutput xslOutput = (XslOutput)_outputs [String.Empty];
switch (xslOutput.Method) {
- case OutputMethod.Unknown: //TODO: handle xml vs html
+ default: // .Custom format is not supported, only handled as unknown
+ 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:
- //TODO: XmlTextEmitter goes here
- //_emitter = new XmlTextEmitter (writer);
- _emitter = new XmlWriterEmitter (new XmlTextWriter (writer));
+ XmlTextWriter w = new XmlTextWriter (pendingTextWriter);
+ if (xslOutput.Indent == "yes")
+ w.Formatting = Formatting.Indented;
+
+ _emitter = new XmlWriterEmitter (w);
+ if (!_omitXmlDeclaration && !xslOutput.OmitXmlDeclaration)
+ _emitter.WriteStartDocument (
+ _encoding != null ? _encoding : xslOutput.Encoding,
+ xslOutput.Standalone);
+
break;
- case OutputMethod.HTML:
- throw new NotImplementedException ("HTML output method is not implemented yet.");
case OutputMethod.Text:
- _emitter = new TextEmitter (writer);
+ _emitter = new TextEmitter (pendingTextWriter);
break;
- case OutputMethod.Custom:
- throw new NotImplementedException ("Custom output method is not implemented yet.");
- }
+ }
+ pendingTextWriter = null;
}
/// <summary>
/// when it's appropriate.
/// </summary>
private void CheckState ()
- {
+ {
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
- foreach (Attribute attr in pendingAttributes) {
- _emitter.WriteAttributeString (attr.Prefix, attr.QName.Name, attr.QName.Namespace, attr.Value);
- }
+ newNamespaces.Clear (); //remember indexes of new prefexes
+ _nsManager.PushScope ();
+ for (int i = 0; i < _currentNsPrefixes.Count; i++)
+ {
+ string prefix = (string) _currentNsPrefixes [i];
+ string uri = _currentNamespaceDecls [prefix] as string;
+
+ if (_nsManager.LookupNamespace (prefix, false) == uri)
+ continue;
+
+ newNamespaces.Add(i);
+ _nsManager.AddNamespace (prefix, uri);
+ }
+ for (int i = 0; i < pendingAttributesPos; i++)
+ {
+ Attribute attr = pendingAttributes [i];
+ string prefix = _nsManager.LookupPrefix (attr.Namespace, false);
+ if (prefix == null) {
+ prefix = attr.Prefix;
+ _nsManager.AddNamespace (prefix, attr.Namespace);
+ }
+
+ Emitter.WriteAttributeString (prefix, attr.LocalName, attr.Namespace, attr.Value);
+ }
+ for (int i = 0; i < newNamespaces.Count; i++)
+ {
+ string prefix = (string) _currentNsPrefixes [(int)newNamespaces[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 (!_currentOutput.OmitXmlDeclaration)
- _emitter.WriteStartDocument (_currentOutput.Standalone);
-
- _state = WriteState.Prolog;
- }
-
- public override void WriteEndDocument ()
- {
- _emitter.WriteEndDocument ();
- }
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.Prolog) {
//Seems to be the first element - take care of Doctype
- if (_currentOutput.DoctypeSystem != null)
- _emitter.WriteDocType (prefix + (prefix==null? ":" : "") + localName,
+ // 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;
- pendingAttributes.Clear ();
+ Emitter.WriteStartElement (prefix, localName, nsURI);
+ _state = WriteState.Element;
+ if (_nsManager.LookupNamespace (prefix, false) != nsURI)
+ _nsManager.AddNamespace (prefix, nsURI);
+ pendingAttributesPos = 0;
+ _canProcessAttributes = true;
}
public override void WriteEndElement ()
+ {
+ WriteEndElementInternal (false);
+ }
+
+ public override void WriteFullEndElement()
+ {
+ WriteEndElementInternal (true);
+ }
+
+ private void WriteEndElementInternal (bool fullEndElement)
{
CheckState ();
- _emitter.WriteEndElement ();
+ 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 == "xml" && nsURI != XmlNamespaceManager.XmlnsXml)
+ prefix = "xp_" + _xpCount++;
+
//Put attribute to pending attributes collection, replacing namesake one
- XmlQualifiedName qName = new XmlQualifiedName (localName, nsURI);
-
- foreach (Attribute attr in pendingAttributes) {
- if (attr.QName == qName) {
- attr.Value = value;
+ 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)
- attr.Prefix = prefix;
-
+ pendingAttributes [i].Prefix = prefix;
return;
}
}
- pendingAttributes.Add (new Attribute (prefix, qName, value));
+ 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 (prefix == String.Empty) {
- //Default namespace
- if (_nsManager.DefaultNamespace != nsUri) {
- _nsManager.AddNamespace (prefix, nsUri);
- _emitter.WriteAttributeString ("", "xmlns", "", nsUri);
- }
- } else if (_nsManager.LookupPrefix (nsUri) == null) {
- //That's new namespace - add it to the collection and emit
- _nsManager.AddNamespace (prefix, nsUri);
- _emitter.WriteAttributeString ("xmlns", prefix, null, nsUri);
- }
+ if (_nsManager.LookupNamespace (prefix, false) == nsUri)
+ return; // do nothing
+
+ for (int i = 0; i < pendingAttributesPos; i++) {
+ Attribute attr = pendingAttributes [i];
+ if (attr.Prefix == prefix || attr.Namespace == nsUri)
+ return; //don't touch explicitly declared attributes
+ }
+ 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);
+ Emitter.WriteComment (text);
}
public override void WriteProcessingInstruction (string name, string text)
{
CheckState ();
- _emitter.WriteProcessingInstruction (name, text);
+ Emitter.WriteProcessingInstruction (name, text);
}
public override void WriteString (string text)
{
CheckState ();
- _emitter.WriteString (text);
+ if (_insideCData)
+ Emitter.WriteCDataSection (text);
+ else
+ Emitter.WriteString (text);
}
public override void WriteRaw (string data)
{
CheckState ();
- _emitter.WriteRaw (data);
+ 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 ();
+ Emitter.Done ();
_state = WriteState.Closed;
}
+
+ public override bool CanProcessAttributes {
+ get { return _canProcessAttributes; }
+ }
+
+ public override bool InsideCDataSection {
+ get { return _insideCData; }
+ set { _insideCData = value; }
+ }
#endregion
}
}