Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / QIL / QilXmlWriter.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="QilXmlWriter.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7 using System;
8 using System.Collections;
9 using System.Collections.Generic;
10 using System.Diagnostics;
11 using System.Globalization;
12 using System.Text;
13 using System.Xml;
14
15 namespace System.Xml.Xsl.Qil {
16
17     /// <summary>
18     /// If an annotation implements this interface, then QilXmlWriter will call ToString() on the annotation
19     /// and serialize the result (if non-empty).
20     /// </summary>
21     interface IQilAnnotation {
22         string Name { get; }
23     };
24
25
26     /// <summary>
27     /// An example of QilVisitor.  Prints the QilExpression tree as XML.
28     /// </summary>
29     /// <remarks>
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>
38     /// </remarks>
39     internal class QilXmlWriter : QilScopedVisitor {
40         protected XmlWriter     writer;
41         protected Options       options;
42         private   NameGenerator ngen;
43
44         [Flags]
45         public enum Options {
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)
53         };
54
55         /// <summary>
56         /// Construct a QilXmlWriter.
57         /// </summary>
58         public QilXmlWriter(XmlWriter writer) : this(writer, Options.Annotations | Options.TypeInfo | Options.LineInfo | Options.NodeIdentity | Options.NodeLocation) {
59         }
60
61         /// <summary>
62         /// Construct a QilXmlWriter.
63         /// </summary>
64         public QilXmlWriter(XmlWriter writer, Options options) {
65             this.writer = writer;
66             this.ngen = new NameGenerator();
67             this.options = options;
68         }
69
70         /// <summary>
71         /// Serialize a QilExpression graph as XML.
72         /// </summary>
73         /// <param name="q">the QilExpression graph</param>
74         public void ToXml(QilNode node) {
75             VisitAssumeReference(node);
76         }
77
78         //-----------------------------------------------
79         // QilXmlWrite methods
80         //-----------------------------------------------
81
82         /// <summary>
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
88         /// </summary>
89         protected virtual void WriteAnnotations(object ann) {
90             string s = null, name = null;
91
92             if (ann == null) {
93                 return;
94             }
95             else if (ann is string) {
96                 s = ann as string;
97             }
98             else if (ann is IQilAnnotation) {
99                 // Get annotation's name and string value
100                 IQilAnnotation qilann = ann as IQilAnnotation;
101                 name = qilann.Name;
102                 s = ann.ToString();
103             }
104             else if (ann is IList<object>) {
105                 IList<object> list = (IList<object>) ann;
106
107                 foreach (object annItem in list)
108                     WriteAnnotations(annItem);
109                 return;
110             }
111
112             if (s != null && s.Length != 0)
113                 this.writer.WriteComment(name != null && name.Length != 0 ? name + ": " + s : s);
114         }
115
116         /// <summary>
117         /// Called in order to write out source line information.
118         /// </summary>
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
123                 ));
124         }
125
126         /// <summary>
127         /// Called in order to write out the xml type of a node.
128         /// </summary>
129         protected virtual void WriteXmlType(QilNode node) {
130             this.writer.WriteAttributeString("xmlType", node.XmlType.ToString((this.options & Options.RoundTripTypeInfo) != 0 ? "S" : "G"));
131         }
132
133
134         //-----------------------------------------------
135         // QilVisitor overrides
136         //-----------------------------------------------
137
138         /// <summary>
139         /// Override certain node types in order to add additional attributes, suppress children, etc.
140         /// </summary>
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));
145                 return node;
146             }
147             else if (node is QilReference) {
148                 QilReference reference = (QilReference) node;
149
150                 // Write the generated identifier for this iterator
151                 this.writer.WriteAttributeString("id", this.ngen.NameOf(node));
152
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());
156
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;
160
161                     if (param.DefaultValue != null)
162                         VisitAssumeReference(param.DefaultValue);
163
164                     return node;
165                 }
166              }
167  
168             return base.VisitChildren(node);
169         }
170
171         /// <summary>
172         /// Write references to functions or iterators like this: <RefTo id="$a"/>.
173         /// </summary>
174         protected override QilNode VisitReference(QilNode node) {
175             QilReference reference = (QilReference) node;
176             string name = ngen.NameOf(node);
177             if (name == null)
178                 name = "OUT-OF-SCOPE REFERENCE";
179
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();
185
186             return node;
187         }
188
189         /// <summary>
190         /// Scan through the external parameters, global variables, and function list for forward references.
191         /// </summary>
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));
200                     WriteXmlType(n);
201
202                     if (n.NodeType == QilNodeType.Function) {
203                         // Visit Arguments and SideEffects operands
204                         Visit(n[0]);
205                         Visit(n[2]);
206                     }
207
208                     this.writer.WriteEndElement();
209                 }
210                 this.writer.WriteEndElement();
211             }
212
213             return VisitChildren(qil);
214         }
215
216         /// <summary>
217         /// Serialize literal types using either "S" or "G" formatting, depending on the option which has been set.
218         /// </summary>
219         protected override QilNode VisitLiteralType(QilLiteral value) {
220             this.writer.WriteString(((XmlQueryType) value).ToString((this.options & Options.TypeInfo) != 0 ? "G" : "S"));
221             return value;
222         }
223
224         /// <summary>
225         /// Serialize literal QName as three separate attributes.
226         /// </summary>
227         protected override QilNode VisitLiteralQName(QilName value) {
228             this.writer.WriteAttributeString("name", value.ToString());
229             return value;
230         }
231
232
233         //-----------------------------------------------
234         // QilScopedVisitor overrides
235         //-----------------------------------------------
236
237         /// <summary>
238         /// Annotate this iterator or function with a generated name.
239         /// </summary>
240         protected override void BeginScope(QilNode node) {
241             this.ngen.NameOf(node);
242         }
243
244         /// <summary>
245         /// Clear the name annotation on this iterator or function.
246         /// </summary>
247         protected override void EndScope(QilNode node) {
248             this.ngen.ClearName(node);
249         }
250
251         /// <summary>
252         /// By default, call WriteStartElement for every node type.
253         /// </summary>
254         protected override void BeforeVisit(QilNode node) {
255             base.BeforeVisit(node);
256
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);
261
262             // Call WriteStartElement
263             this.writer.WriteStartElement("", Enum.GetName(typeof(QilNodeType), node.NodeType), "");
264
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));
269
270             if ((this.options & Options.NodeLocation) != 0)
271                 this.writer.WriteAttributeString("nodeLoc", node.NodeLocation);
272 #endif
273             if ((this.options & (Options.TypeInfo | Options.RoundTripTypeInfo)) != 0)
274                 WriteXmlType(node);
275
276             if ((this.options & Options.LineInfo) != 0 && node.SourceLine != null) 
277                 WriteLineInfo(node);
278         }
279
280         /// <summary>
281         /// By default, call WriteEndElement for every node type.
282         /// </summary>
283         protected override void AfterVisit(QilNode node) {
284             this.writer.WriteEndElement();
285
286             base.AfterVisit(node);
287         }
288
289
290         //-----------------------------------------------
291         // Helper methods
292         //-----------------------------------------------
293
294         /// <summary>
295         /// Find list of all iterators and functions which are referenced before they have been declared.
296         /// </summary>
297         internal class ForwardRefFinder : QilVisitor {
298             private List<QilNode> fwdrefs = new List<QilNode>();
299             private List<QilNode> backrefs = new List<QilNode>();
300
301             public IList<QilNode> Find(QilExpression qil) {
302                 Visit(qil);
303                 return this.fwdrefs;
304             }
305
306             /// <summary>
307             /// Add iterators and functions to backrefs list as they are visited.
308             /// </summary>
309             protected override QilNode Visit(QilNode node) {
310                 if (node is QilIterator || node is QilFunction)
311                     this.backrefs.Add(node);
312
313                 return base.Visit(node);
314             }
315
316             /// <summary>
317             /// If reference is not in scope, then it must be a forward reference.
318             /// </summary>
319             protected override QilNode VisitReference(QilNode node) {
320                 if (!this.backrefs.Contains(node) && !this.fwdrefs.Contains(node))
321                     this.fwdrefs.Add(node);
322
323                 return node;
324             }
325         }
326
327         //=================================== Helper class: NameGenerator =========================================
328
329         private sealed class NameGenerator {
330             StringBuilder name;
331             int len;
332             int zero;
333             char start;
334             char end;
335             
336             /// <summary>
337             /// Construct a new name generator with prefix "$" and alphabetical mode.
338             /// </summary>
339             public NameGenerator()
340             {
341                 string prefix = "$";
342                 len = zero = prefix.Length;
343                 start = 'a';
344                 end = 'z';
345                 name = new StringBuilder(prefix, len + 2);
346                 name.Append(start);
347             }
348
349             /// <summary>
350             /// Skolem function for names.
351             /// </summary>
352             /// <returns>a unique name beginning with the prefix</returns>
353             public string NextName()
354             {
355                 string result = name.ToString();
356
357                 char c = name[len];
358                 if (c == end)
359                 {
360                     name[len] = start;
361                     int i = len;
362                     for ( ; i-- > zero && name[i]==end; )
363                         name[i] = start;
364
365                     if (i < zero)
366                     {
367                         len++;
368                         name.Append(start);
369                     }
370                     else
371                         name[i]++;
372                 }
373                 else
374                     name[len] = ++c;
375
376                 return result;
377             }
378
379             /// <summary>
380             /// Lookup or generate a name for a node.  Uses annotations to store the name on the node.
381             /// </summary>
382             /// <param name="i">the node</param>
383             /// <returns>the node name (unique across nodes)</returns>
384             public string NameOf(QilNode n)
385             {
386                 string name = null;
387
388                 object old = n.Annotation;
389                 NameAnnotation a = old as NameAnnotation;
390                 if (a == null)
391                 {
392                     name = NextName();
393                     n.Annotation = new NameAnnotation(name, old);
394                 }
395                 else
396                 {
397                     name = a.Name;
398                 }
399                 return name;
400             }
401
402             /// <summary>
403             /// Clear name annotation from a node.
404             /// </summary>
405             /// <param name="n">the node</param>
406             public void ClearName(QilNode n)
407             {
408                 if (n.Annotation is NameAnnotation)
409                     n.Annotation = ((NameAnnotation)n.Annotation).PriorAnnotation;
410             }
411
412             /// <summary>
413             /// Class used to hold our annotations on the graph
414             /// </summary>
415             private class NameAnnotation : ListBase<object>
416             {
417                 public string Name;
418                 public object PriorAnnotation;
419
420                 public NameAnnotation(string s, object a)
421                 {
422                     Name = s;
423                     PriorAnnotation = a;
424                 }
425
426                 public override int Count {
427                     get { return 1; }
428                 }
429
430                 public override object this[int index] {
431                     get {
432                         if (index == 0)
433                             return PriorAnnotation;
434
435                         throw new IndexOutOfRangeException();
436                     }
437                     set { throw new NotSupportedException(); }
438                 }
439             }
440         }
441     }
442 }