|
//------------------------------------------------------------------------------
// <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(); }
}
}
}
}
}
|