1 //------------------------------------------------------------------------------
2 // <copyright file="QilXmlWriter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 using System.Collections;
9 using System.Collections.Generic;
10 using System.Diagnostics;
11 using System.Globalization;
15 namespace System.Xml.Xsl.Qil {
18 /// If an annotation implements this interface, then QilXmlWriter will call ToString() on the annotation
19 /// and serialize the result (if non-empty).
21 interface IQilAnnotation {
27 /// An example of QilVisitor. Prints the QilExpression tree as XML.
30 /// <para>The QilXmlWriter Visits every node in the tree, printing out an XML representation of
31 /// each node. Several formatting options are available, including whether or not to include annotations
32 /// and type information. When full information is printed out, the graph can be reloaded from
33 /// its serialized form using QilXmlReader.</para>
34 /// <para>The XML format essentially uses one XML element for each node in the QIL graph.
35 /// Node properties such as type information are serialized as XML attributes.
36 /// Annotations are serialized as processing-instructions in front of a node.</para>
37 /// <para>Feel free to subclass this visitor to customize its behavior.</para>
39 internal class QilXmlWriter : QilScopedVisitor {
40 protected XmlWriter writer;
41 protected Options options;
42 private NameGenerator ngen;
46 None = 0, // No options selected
47 Annotations = 1, // Print annotations
48 TypeInfo = 2, // Print type information using "G" option
49 RoundTripTypeInfo = 4, // Print type information using "S" option
50 LineInfo = 8, // Print source line information
51 NodeIdentity = 16, // Print node identity (only works if QIL_TRACE_NODE_CREATION is defined)
52 NodeLocation = 32, // Print node creation location (only works if QIL_TRACE_NODE_CREATION is defined)
56 /// Construct a QilXmlWriter.
58 public QilXmlWriter(XmlWriter writer) : this(writer, Options.Annotations | Options.TypeInfo | Options.LineInfo | Options.NodeIdentity | Options.NodeLocation) {
62 /// Construct a QilXmlWriter.
64 public QilXmlWriter(XmlWriter writer, Options options) {
66 this.ngen = new NameGenerator();
67 this.options = options;
71 /// Serialize a QilExpression graph as XML.
73 /// <param name="q">the QilExpression graph</param>
74 public void ToXml(QilNode node) {
75 VisitAssumeReference(node);
78 //-----------------------------------------------
79 // QilXmlWrite methods
80 //-----------------------------------------------
83 /// Write all annotations as comments:
84 /// 1. string -- <!-- (string) ann -->
85 /// 2. IQilAnnotation -- <!-- ann.Name = ann.ToString() -->
86 /// 3. IList<object> -- recursively call WriteAnnotations for each object in list
87 /// 4. otherwise, do not write the annotation
89 protected virtual void WriteAnnotations(object ann) {
90 string s = null, name = null;
95 else if (ann is string) {
98 else if (ann is IQilAnnotation) {
99 // Get annotation's name and string value
100 IQilAnnotation qilann = ann as IQilAnnotation;
104 else if (ann is IList<object>) {
105 IList<object> list = (IList<object>) ann;
107 foreach (object annItem in list)
108 WriteAnnotations(annItem);
112 if (s != null && s.Length != 0)
113 this.writer.WriteComment(name != null && name.Length != 0 ? name + ": " + s : s);
117 /// Called in order to write out source line information.
119 protected virtual void WriteLineInfo(QilNode node) {
120 this.writer.WriteAttributeString("lineInfo", string.Format(CultureInfo.InvariantCulture, "[{0},{1} -- {2},{3}]",
121 node.SourceLine.Start.Line, node.SourceLine.Start.Pos,
122 node.SourceLine.End.Line , node.SourceLine.End.Pos
127 /// Called in order to write out the xml type of a node.
129 protected virtual void WriteXmlType(QilNode node) {
130 this.writer.WriteAttributeString("xmlType", node.XmlType.ToString((this.options & Options.RoundTripTypeInfo) != 0 ? "S" : "G"));
134 //-----------------------------------------------
135 // QilVisitor overrides
136 //-----------------------------------------------
139 /// Override certain node types in order to add additional attributes, suppress children, etc.
141 protected override QilNode VisitChildren(QilNode node) {
142 if (node is QilLiteral) {
143 // If literal is not handled elsewhere, print its string value
144 this.writer.WriteValue(Convert.ToString(((QilLiteral) node).Value, CultureInfo.InvariantCulture));
147 else if (node is QilReference) {
148 QilReference reference = (QilReference) node;
150 // Write the generated identifier for this iterator
151 this.writer.WriteAttributeString("id", this.ngen.NameOf(node));
153 // Write the debug name of this reference (if it's defined) as a "name" attribute
154 if (reference.DebugName != null)
155 this.writer.WriteAttributeString("name", reference.DebugName.ToString());
157 if (node.NodeType == QilNodeType.Parameter) {
158 // Don't visit parameter's name, or its default value if it is null
159 QilParameter param = (QilParameter) node;
161 if (param.DefaultValue != null)
162 VisitAssumeReference(param.DefaultValue);
168 return base.VisitChildren(node);
172 /// Write references to functions or iterators like this: <RefTo id="$a"/>.
174 protected override QilNode VisitReference(QilNode node) {
175 QilReference reference = (QilReference) node;
176 string name = ngen.NameOf(node);
178 name = "OUT-OF-SCOPE REFERENCE";
180 this.writer.WriteStartElement("RefTo");
181 this.writer.WriteAttributeString("id", name);
182 if (reference.DebugName != null)
183 this.writer.WriteAttributeString("name", reference.DebugName.ToString());
184 this.writer.WriteEndElement();
190 /// Scan through the external parameters, global variables, and function list for forward references.
192 protected override QilNode VisitQilExpression(QilExpression qil) {
193 IList<QilNode> fdecls = new ForwardRefFinder().Find(qil);
194 if (fdecls != null && fdecls.Count > 0) {
195 this.writer.WriteStartElement("ForwardDecls");
196 foreach (QilNode n in fdecls) {
197 // i.e. <Function id="$a"/>
198 this.writer.WriteStartElement(Enum.GetName(typeof(QilNodeType), n.NodeType));
199 this.writer.WriteAttributeString("id", this.ngen.NameOf(n));
202 if (n.NodeType == QilNodeType.Function) {
203 // Visit Arguments and SideEffects operands
208 this.writer.WriteEndElement();
210 this.writer.WriteEndElement();
213 return VisitChildren(qil);
217 /// Serialize literal types using either "S" or "G" formatting, depending on the option which has been set.
219 protected override QilNode VisitLiteralType(QilLiteral value) {
220 this.writer.WriteString(((XmlQueryType) value).ToString((this.options & Options.TypeInfo) != 0 ? "G" : "S"));
225 /// Serialize literal QName as three separate attributes.
227 protected override QilNode VisitLiteralQName(QilName value) {
228 this.writer.WriteAttributeString("name", value.ToString());
233 //-----------------------------------------------
234 // QilScopedVisitor overrides
235 //-----------------------------------------------
238 /// Annotate this iterator or function with a generated name.
240 protected override void BeginScope(QilNode node) {
241 this.ngen.NameOf(node);
245 /// Clear the name annotation on this iterator or function.
247 protected override void EndScope(QilNode node) {
248 this.ngen.ClearName(node);
252 /// By default, call WriteStartElement for every node type.
254 protected override void BeforeVisit(QilNode node) {
255 base.BeforeVisit(node);
257 // Write the annotations in front of the element, to avoid issues with attributes
258 // and make it easier to round-trip
259 if ((this.options & Options.Annotations) != 0)
260 WriteAnnotations(node.Annotation);
262 // Call WriteStartElement
263 this.writer.WriteStartElement("", Enum.GetName(typeof(QilNodeType), node.NodeType), "");
265 // Write common attributes
266 #if QIL_TRACE_NODE_CREATION
267 if ((this.options & Options.NodeIdentity) != 0)
268 this.writer.WriteAttributeString("nodeId", node.NodeId.ToString(CultureInfo.InvariantCulture));
270 if ((this.options & Options.NodeLocation) != 0)
271 this.writer.WriteAttributeString("nodeLoc", node.NodeLocation);
273 if ((this.options & (Options.TypeInfo | Options.RoundTripTypeInfo)) != 0)
276 if ((this.options & Options.LineInfo) != 0 && node.SourceLine != null)
281 /// By default, call WriteEndElement for every node type.
283 protected override void AfterVisit(QilNode node) {
284 this.writer.WriteEndElement();
286 base.AfterVisit(node);
290 //-----------------------------------------------
292 //-----------------------------------------------
295 /// Find list of all iterators and functions which are referenced before they have been declared.
297 internal class ForwardRefFinder : QilVisitor {
298 private List<QilNode> fwdrefs = new List<QilNode>();
299 private List<QilNode> backrefs = new List<QilNode>();
301 public IList<QilNode> Find(QilExpression qil) {
307 /// Add iterators and functions to backrefs list as they are visited.
309 protected override QilNode Visit(QilNode node) {
310 if (node is QilIterator || node is QilFunction)
311 this.backrefs.Add(node);
313 return base.Visit(node);
317 /// If reference is not in scope, then it must be a forward reference.
319 protected override QilNode VisitReference(QilNode node) {
320 if (!this.backrefs.Contains(node) && !this.fwdrefs.Contains(node))
321 this.fwdrefs.Add(node);
327 //=================================== Helper class: NameGenerator =========================================
329 private sealed class NameGenerator {
337 /// Construct a new name generator with prefix "$" and alphabetical mode.
339 public NameGenerator()
342 len = zero = prefix.Length;
345 name = new StringBuilder(prefix, len + 2);
350 /// Skolem function for names.
352 /// <returns>a unique name beginning with the prefix</returns>
353 public string NextName()
355 string result = name.ToString();
362 for ( ; i-- > zero && name[i]==end; )
380 /// Lookup or generate a name for a node. Uses annotations to store the name on the node.
382 /// <param name="i">the node</param>
383 /// <returns>the node name (unique across nodes)</returns>
384 public string NameOf(QilNode n)
388 object old = n.Annotation;
389 NameAnnotation a = old as NameAnnotation;
393 n.Annotation = new NameAnnotation(name, old);
403 /// Clear name annotation from a node.
405 /// <param name="n">the node</param>
406 public void ClearName(QilNode n)
408 if (n.Annotation is NameAnnotation)
409 n.Annotation = ((NameAnnotation)n.Annotation).PriorAnnotation;
413 /// Class used to hold our annotations on the graph
415 private class NameAnnotation : ListBase<object>
418 public object PriorAnnotation;
420 public NameAnnotation(string s, object a)
426 public override int Count {
430 public override object this[int index] {
433 return PriorAnnotation;
435 throw new IndexOutOfRangeException();
437 set { throw new NotSupportedException(); }