File: System\Xml\Xsl\QIL\QilXmlWriter.cs
Project: ndp\fx\src\XmlUtils\System.Data.SqlXml.csproj (System.Data.SqlXml)
//------------------------------------------------------------------------------
// <copyright file="QilXmlWriter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Xml;
 
namespace System.Xml.Xsl.Qil {
 
    /// <summary>
    /// If an annotation implements this interface, then QilXmlWriter will call ToString() on the annotation
    /// and serialize the result (if non-empty).
    /// </summary>
    interface IQilAnnotation {
        string Name { get; }
    };
 
 
    /// <summary>
    /// An example of QilVisitor.  Prints the QilExpression tree as XML.
    /// </summary>
    /// <remarks>
    /// <para>The QilXmlWriter Visits every node in the tree, printing out an XML representation of
    /// each node.  Several formatting options are available, including whether or not to include annotations
    /// and type information.  When full information is printed out, the graph can be reloaded from
    /// its serialized form using QilXmlReader.</para>
    /// <para>The XML format essentially uses one XML element for each node in the QIL graph.
    /// Node properties such as type information are serialized as XML attributes.
    /// Annotations are serialized as processing-instructions in front of a node.</para>
    /// <para>Feel free to subclass this visitor to customize its behavior.</para>
    /// </remarks>
    internal class QilXmlWriter : QilScopedVisitor {
        protected XmlWriter     writer;
        protected Options       options;
        private   NameGenerator ngen;
 
        [Flags]
        public enum Options {
            None = 0,               // No options selected
            Annotations = 1,        // Print annotations
            TypeInfo = 2,           // Print type information using "G" option
            RoundTripTypeInfo = 4,  // Print type information using "S" option
            LineInfo = 8,           // Print source line information
            NodeIdentity = 16,      // Print node identity (only works if QIL_TRACE_NODE_CREATION is defined)
            NodeLocation = 32,      // Print node creation location (only works if QIL_TRACE_NODE_CREATION is defined)
        };
 
        /// <summary>
        /// Construct a QilXmlWriter.
        /// </summary>
        public QilXmlWriter(XmlWriter writer) : this(writer, Options.Annotations | Options.TypeInfo | Options.LineInfo | Options.NodeIdentity | Options.NodeLocation) {
        }
 
        /// <summary>
        /// Construct a QilXmlWriter.
        /// </summary>
        public QilXmlWriter(XmlWriter writer, Options options) {
            this.writer = writer;
            this.ngen = new NameGenerator();
            this.options = options;
        }
 
        /// <summary>
        /// Serialize a QilExpression graph as XML.
        /// </summary>
        /// <param name="q">the QilExpression graph</param>
        public void ToXml(QilNode node) {
            VisitAssumeReference(node);
        }
 
        //-----------------------------------------------
        // QilXmlWrite methods
        //-----------------------------------------------
 
        /// <summary>
        /// Write all annotations as comments:
        ///     1. string -- <!-- (string) ann -->
        ///     2. IQilAnnotation -- <!-- ann.Name = ann.ToString() -->
        ///     3. IList<object> -- recursively call WriteAnnotations for each object in list
        ///     4. otherwise, do not write the annotation
        /// </summary>
        protected virtual void WriteAnnotations(object ann) {
            string s = null, name = null;
 
            if (ann == null) {
                return;
            }
            else if (ann is string) {
                s = ann as string;
            }
            else if (ann is IQilAnnotation) {
                // Get annotation's name and string value
                IQilAnnotation qilann = ann as IQilAnnotation;
                name = qilann.Name;
                s = ann.ToString();
            }
            else if (ann is IList<object>) {
                IList<object> list = (IList<object>) ann;
 
                foreach (object annItem in list)
                    WriteAnnotations(annItem);
                return;
            }
 
            if (s != null && s.Length != 0)
                this.writer.WriteComment(name != null && name.Length != 0 ? name + ": " + s : s);
        }
 
        /// <summary>
        /// Called in order to write out source line information.
        /// </summary>
        protected virtual void WriteLineInfo(QilNode node) {
            this.writer.WriteAttributeString("lineInfo", string.Format(CultureInfo.InvariantCulture, "[{0},{1} -- {2},{3}]", 
                node.SourceLine.Start.Line, node.SourceLine.Start.Pos,
                node.SourceLine.End.Line  , node.SourceLine.End.Pos
                ));
        }
 
        /// <summary>
        /// Called in order to write out the xml type of a node.
        /// </summary>
        protected virtual void WriteXmlType(QilNode node) {
            this.writer.WriteAttributeString("xmlType", node.XmlType.ToString((this.options & Options.RoundTripTypeInfo) != 0 ? "S" : "G"));
        }
 
 
        //-----------------------------------------------
        // QilVisitor overrides
        //-----------------------------------------------
 
        /// <summary>
        /// Override certain node types in order to add additional attributes, suppress children, etc.
        /// </summary>
        protected override QilNode VisitChildren(QilNode node) {
            if (node is QilLiteral) {
                // If literal is not handled elsewhere, print its string value
                this.writer.WriteValue(Convert.ToString(((QilLiteral) node).Value, CultureInfo.InvariantCulture));
                return node;
            }
            else if (node is QilReference) {
                QilReference reference = (QilReference) node;
 
                // Write the generated identifier for this iterator
                this.writer.WriteAttributeString("id", this.ngen.NameOf(node));
 
                // Write the debug name of this reference (if it's defined) as a "name" attribute
                if (reference.DebugName != null)
                    this.writer.WriteAttributeString("name", reference.DebugName.ToString());
 
                if (node.NodeType == QilNodeType.Parameter) {
                    // Don't visit parameter's name, or its default value if it is null
                    QilParameter param = (QilParameter) node;
 
                    if (param.DefaultValue != null)
                        VisitAssumeReference(param.DefaultValue);
 
                    return node;
                }
             }
 
            return base.VisitChildren(node);
        }
 
        /// <summary>
        /// Write references to functions or iterators like this: <RefTo id="$a"/>.
        /// </summary>
        protected override QilNode VisitReference(QilNode node) {
            QilReference reference = (QilReference) node;
            string name = ngen.NameOf(node);
            if (name == null)
                name = "OUT-OF-SCOPE REFERENCE";
 
            this.writer.WriteStartElement("RefTo");
            this.writer.WriteAttributeString("id", name);
            if (reference.DebugName != null)
                this.writer.WriteAttributeString("name", reference.DebugName.ToString());
            this.writer.WriteEndElement();
 
            return node;
        }
 
        /// <summary>
        /// Scan through the external parameters, global variables, and function list for forward references.
        /// </summary>
        protected override QilNode VisitQilExpression(QilExpression qil) {
            IList<QilNode> fdecls = new ForwardRefFinder().Find(qil);
            if (fdecls != null && fdecls.Count > 0) {
                this.writer.WriteStartElement("ForwardDecls");
                foreach (QilNode n in fdecls) {
                    // i.e. <Function id="$a"/>
                    this.writer.WriteStartElement(Enum.GetName(typeof(QilNodeType), n.NodeType));
                    this.writer.WriteAttributeString("id", this.ngen.NameOf(n));
                    WriteXmlType(n);
 
                    if (n.NodeType == QilNodeType.Function) {
                        // Visit Arguments and SideEffects operands
                        Visit(n[0]);
                        Visit(n[2]);
                    }
 
                    this.writer.WriteEndElement();
                }
                this.writer.WriteEndElement();
            }
 
            return VisitChildren(qil);
        }
 
        /// <summary>
        /// Serialize literal types using either "S" or "G" formatting, depending on the option which has been set.
        /// </summary>
        protected override QilNode VisitLiteralType(QilLiteral value) {
            this.writer.WriteString(((XmlQueryType) value).ToString((this.options & Options.TypeInfo) != 0 ? "G" : "S"));
            return value;
        }
 
        /// <summary>
        /// Serialize literal QName as three separate attributes.
        /// </summary>
        protected override QilNode VisitLiteralQName(QilName value) {
            this.writer.WriteAttributeString("name", value.ToString());
            return value;
        }
 
 
        //-----------------------------------------------
        // QilScopedVisitor overrides
        //-----------------------------------------------
 
        /// <summary>
        /// Annotate this iterator or function with a generated name.
        /// </summary>
        protected override void BeginScope(QilNode node) {
            this.ngen.NameOf(node);
        }
 
        /// <summary>
        /// Clear the name annotation on this iterator or function.
        /// </summary>
        protected override void EndScope(QilNode node) {
            this.ngen.ClearName(node);
        }
 
        /// <summary>
        /// By default, call WriteStartElement for every node type.
        /// </summary>
        protected override void BeforeVisit(QilNode node) {
            base.BeforeVisit(node);
 
            // Write the annotations in front of the element, to avoid issues with attributes
            // and make it easier to round-trip
            if ((this.options & Options.Annotations) != 0)
                WriteAnnotations(node.Annotation);
 
            // Call WriteStartElement
            this.writer.WriteStartElement("", Enum.GetName(typeof(QilNodeType), node.NodeType), "");
 
            // Write common attributes
#if QIL_TRACE_NODE_CREATION
            if ((this.options & Options.NodeIdentity) != 0)
                this.writer.WriteAttributeString("nodeId", node.NodeId.ToString(CultureInfo.InvariantCulture));
 
            if ((this.options & Options.NodeLocation) != 0)
                this.writer.WriteAttributeString("nodeLoc", node.NodeLocation);
#endif
            if ((this.options & (Options.TypeInfo | Options.RoundTripTypeInfo)) != 0)
                WriteXmlType(node);
 
            if ((this.options & Options.LineInfo) != 0 && node.SourceLine != null) 
                WriteLineInfo(node);
        }
 
        /// <summary>
        /// By default, call WriteEndElement for every node type.
        /// </summary>
        protected override void AfterVisit(QilNode node) {
            this.writer.WriteEndElement();
 
            base.AfterVisit(node);
        }
 
 
        //-----------------------------------------------
        // Helper methods
        //-----------------------------------------------
 
        /// <summary>
        /// Find list of all iterators and functions which are referenced before they have been declared.
        /// </summary>
        internal class ForwardRefFinder : QilVisitor {
            private List<QilNode> fwdrefs = new List<QilNode>();
            private List<QilNode> backrefs = new List<QilNode>();
 
            public IList<QilNode> Find(QilExpression qil) {
                Visit(qil);
                return this.fwdrefs;
            }
 
            /// <summary>
            /// Add iterators and functions to backrefs list as they are visited.
            /// </summary>
            protected override QilNode Visit(QilNode node) {
                if (node is QilIterator || node is QilFunction)
                    this.backrefs.Add(node);
 
                return base.Visit(node);
            }
 
            /// <summary>
            /// If reference is not in scope, then it must be a forward reference.
            /// </summary>
            protected override QilNode VisitReference(QilNode node) {
                if (!this.backrefs.Contains(node) && !this.fwdrefs.Contains(node))
                    this.fwdrefs.Add(node);
 
                return node;
            }
        }
 
        //=================================== Helper class: NameGenerator =========================================
 
        private sealed class NameGenerator {
            StringBuilder name;
            int len;
            int zero;
            char start;
            char end;
            
            /// <summary>
            /// Construct a new name generator with prefix "$" and alphabetical mode.
            /// </summary>
            public NameGenerator()
            {
                string prefix = "$";
                len = zero = prefix.Length;
                start = 'a';
                end = 'z';
                name = new StringBuilder(prefix, len + 2);
                name.Append(start);
            }
 
            /// <summary>
            /// Skolem function for names.
            /// </summary>
            /// <returns>a unique name beginning with the prefix</returns>
            public string NextName()
            {
                string result = name.ToString();
 
                char c = name[len];
                if (c == end)
                {
                    name[len] = start;
                    int i = len;
                    for ( ; i-- > zero && name[i]==end; )
                        name[i] = start;
 
                    if (i < zero)
                    {
                        len++;
                        name.Append(start);
                    }
                    else
                        name[i]++;
                }
                else
                    name[len] = ++c;
 
                return result;
            }
 
            /// <summary>
            /// Lookup or generate a name for a node.  Uses annotations to store the name on the node.
            /// </summary>
            /// <param name="i">the node</param>
            /// <returns>the node name (unique across nodes)</returns>
            public string NameOf(QilNode n)
            {
                string name = null;
 
                object old = n.Annotation;
                NameAnnotation a = old as NameAnnotation;
                if (a == null)
                {
                    name = NextName();
                    n.Annotation = new NameAnnotation(name, old);
                }
                else
                {
                    name = a.Name;
                }
                return name;
            }
 
            /// <summary>
            /// Clear name annotation from a node.
            /// </summary>
            /// <param name="n">the node</param>
            public void ClearName(QilNode n)
            {
                if (n.Annotation is NameAnnotation)
                    n.Annotation = ((NameAnnotation)n.Annotation).PriorAnnotation;
            }
 
            /// <summary>
            /// Class used to hold our annotations on the graph
            /// </summary>
            private class NameAnnotation : ListBase<object>
            {
                public string Name;
                public object PriorAnnotation;
 
                public NameAnnotation(string s, object a)
                {
                    Name = s;
                    PriorAnnotation = a;
                }
 
                public override int Count {
                    get { return 1; }
                }
 
                public override object this[int index] {
                    get {
                        if (index == 0)
                            return PriorAnnotation;
 
                        throw new IndexOutOfRangeException();
                    }
                    set { throw new NotSupportedException(); }
                }
            }
        }
    }
}