//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] //------------------------------------------------------------------------------ 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 { /// /// If an annotation implements this interface, then QilXmlWriter will call ToString() on the annotation /// and serialize the result (if non-empty). /// interface IQilAnnotation { string Name { get; } }; /// /// An example of QilVisitor. Prints the QilExpression tree as XML. /// /// /// 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. /// 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. /// Feel free to subclass this visitor to customize its behavior. /// 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) }; /// /// Construct a QilXmlWriter. /// public QilXmlWriter(XmlWriter writer) : this(writer, Options.Annotations | Options.TypeInfo | Options.LineInfo | Options.NodeIdentity | Options.NodeLocation) { } /// /// Construct a QilXmlWriter. /// public QilXmlWriter(XmlWriter writer, Options options) { this.writer = writer; this.ngen = new NameGenerator(); this.options = options; } /// /// Serialize a QilExpression graph as XML. /// /// the QilExpression graph public void ToXml(QilNode node) { VisitAssumeReference(node); } //----------------------------------------------- // QilXmlWrite methods //----------------------------------------------- /// /// Write all annotations as comments: /// 1. string -- /// 2. IQilAnnotation -- /// 3. IList -- recursively call WriteAnnotations for each object in list /// 4. otherwise, do not write the annotation /// 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) { IList list = (IList) 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); } /// /// Called in order to write out source line information. /// 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 )); } /// /// Called in order to write out the xml type of a node. /// protected virtual void WriteXmlType(QilNode node) { this.writer.WriteAttributeString("xmlType", node.XmlType.ToString((this.options & Options.RoundTripTypeInfo) != 0 ? "S" : "G")); } //----------------------------------------------- // QilVisitor overrides //----------------------------------------------- /// /// Override certain node types in order to add additional attributes, suppress children, etc. /// 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); } /// /// Write references to functions or iterators like this: . /// 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; } /// /// Scan through the external parameters, global variables, and function list for forward references. /// protected override QilNode VisitQilExpression(QilExpression qil) { IList fdecls = new ForwardRefFinder().Find(qil); if (fdecls != null && fdecls.Count > 0) { this.writer.WriteStartElement("ForwardDecls"); foreach (QilNode n in fdecls) { // i.e. 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); } /// /// Serialize literal types using either "S" or "G" formatting, depending on the option which has been set. /// protected override QilNode VisitLiteralType(QilLiteral value) { this.writer.WriteString(((XmlQueryType) value).ToString((this.options & Options.TypeInfo) != 0 ? "G" : "S")); return value; } /// /// Serialize literal QName as three separate attributes. /// protected override QilNode VisitLiteralQName(QilName value) { this.writer.WriteAttributeString("name", value.ToString()); return value; } //----------------------------------------------- // QilScopedVisitor overrides //----------------------------------------------- /// /// Annotate this iterator or function with a generated name. /// protected override void BeginScope(QilNode node) { this.ngen.NameOf(node); } /// /// Clear the name annotation on this iterator or function. /// protected override void EndScope(QilNode node) { this.ngen.ClearName(node); } /// /// By default, call WriteStartElement for every node type. /// 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); } /// /// By default, call WriteEndElement for every node type. /// protected override void AfterVisit(QilNode node) { this.writer.WriteEndElement(); base.AfterVisit(node); } //----------------------------------------------- // Helper methods //----------------------------------------------- /// /// Find list of all iterators and functions which are referenced before they have been declared. /// internal class ForwardRefFinder : QilVisitor { private List fwdrefs = new List(); private List backrefs = new List(); public IList Find(QilExpression qil) { Visit(qil); return this.fwdrefs; } /// /// Add iterators and functions to backrefs list as they are visited. /// protected override QilNode Visit(QilNode node) { if (node is QilIterator || node is QilFunction) this.backrefs.Add(node); return base.Visit(node); } /// /// If reference is not in scope, then it must be a forward reference. /// 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; /// /// Construct a new name generator with prefix "$" and alphabetical mode. /// public NameGenerator() { string prefix = "$"; len = zero = prefix.Length; start = 'a'; end = 'z'; name = new StringBuilder(prefix, len + 2); name.Append(start); } /// /// Skolem function for names. /// /// a unique name beginning with the prefix 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; } /// /// Lookup or generate a name for a node. Uses annotations to store the name on the node. /// /// the node /// the node name (unique across nodes) 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; } /// /// Clear name annotation from a node. /// /// the node public void ClearName(QilNode n) { if (n.Annotation is NameAnnotation) n.Annotation = ((NameAnnotation)n.Annotation).PriorAnnotation; } /// /// Class used to hold our annotations on the graph /// private class NameAnnotation : ListBase { 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(); } } } } } }