|
//------------------------------------------------------------------------------
// <copyright file="QilXmlReader.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.IO;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Xsl;
namespace System.Xml.Xsl.Qil {
/// <summary>
/// Read the output of QilXmlWriter.
/// </summary>
/// <remarks>This internal class allows roundtripping between the Xml serialization format for
/// QIL and the in-memory data structure.</remarks>
internal sealed class QilXmlReader {
private static Regex lineInfoRegex = new Regex(@"\[(\d+),(\d+) -- (\d+),(\d+)\]");
private static Regex typeInfoRegex = new Regex(@"(\w+);([\w|\|]+);(\w+)");
private static Dictionary<string, MethodInfo> nameToFactoryMethod;
private QilFactory f;
private XmlReader r;
private Stack<QilList> stk;
private bool inFwdDecls;
private Dictionary<string, QilNode> scope, fwdDecls;
static QilXmlReader() {
nameToFactoryMethod = new Dictionary<string, MethodInfo>();
// Build table that maps QilNodeType name to factory method info
foreach (MethodInfo mi in typeof(QilFactory).GetMethods(BindingFlags.Public | BindingFlags.Instance)) {
ParameterInfo[] parms = mi.GetParameters();
int i;
// Only match methods that take QilNode parameters
for (i = 0; i < parms.Length; i++) {
if (parms[i].ParameterType != typeof(QilNode))
break;
}
if (i == parms.Length) {
// Enter the method that takes the maximum number of parameters
if (!nameToFactoryMethod.ContainsKey(mi.Name) || nameToFactoryMethod[mi.Name].GetParameters().Length < parms.Length)
nameToFactoryMethod[mi.Name] = mi;
}
}
}
public QilXmlReader(XmlReader r) {
this.r = r;
this.f = new QilFactory();
}
public QilExpression Read() {
this.stk = new Stack<QilList>();
this.inFwdDecls = false;
this.scope = new Dictionary<string, QilNode>();
this.fwdDecls = new Dictionary<string, QilNode>();
this.stk.Push(f.Sequence());
while (r.Read()) {
switch (r.NodeType) {
case XmlNodeType.Element:
bool emptyElem = r.IsEmptyElement;
// XmlReader does not give an event for empty elements, so synthesize one
if (StartElement() && emptyElem)
EndElement();
break;
case XmlNodeType.EndElement:
EndElement();
break;
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
case XmlNodeType.XmlDeclaration:
case XmlNodeType.Comment:
case XmlNodeType.ProcessingInstruction:
break;
default:
Debug.Fail("Unexpected event " + r.NodeType + ", value " + r.Value);
break;
}
}
Debug.Assert(this.fwdDecls.Keys.Count == 0, "One or more forward declarations were never defined");
Debug.Assert(this.stk.Peek()[0].NodeType == QilNodeType.QilExpression, "Serialized qil tree did not contain a QilExpression node");
return (QilExpression) this.stk.Peek()[0];
}
private bool StartElement() {
QilNode nd;
ReaderAnnotation ann = new ReaderAnnotation();
string s;
// Special case certain element names
s = r.LocalName;
switch (r.LocalName) {
case "LiteralString":
nd = f.LiteralString(ReadText());
break;
case "LiteralInt32":
nd = f.LiteralInt32(Int32.Parse(ReadText(), CultureInfo.InvariantCulture));
break;
case "LiteralInt64":
nd = f.LiteralInt64(Int64.Parse(ReadText(), CultureInfo.InvariantCulture));
break;
case "LiteralDouble":
nd = f.LiteralDouble(Double.Parse(ReadText(), CultureInfo.InvariantCulture));
break;
case "LiteralDecimal":
nd = f.LiteralDecimal(Decimal.Parse(ReadText(), CultureInfo.InvariantCulture));
break;
case "LiteralType":
nd = f.LiteralType(ParseType(ReadText()));
break;
case "LiteralQName":
nd = ParseName(r.GetAttribute("name"));
Debug.Assert(nd != null, "LiteralQName element must have a name attribute");
Debug.Assert(r.IsEmptyElement, "LiteralQName element must be empty");
break;
case "For":
case "Let":
case "Parameter":
case "Function":
case "RefTo":
ann.Id = r.GetAttribute("id");
ann.Name = ParseName(r.GetAttribute("name"));
goto default;
case "XsltInvokeEarlyBound":
ann.ClrNamespace = r.GetAttribute("clrNamespace");
goto default;
case "ForwardDecls":
this.inFwdDecls = true;
goto default;
default:
// Create sequence
nd = f.Sequence();
break;
}
// Save xml type and source line information
ann.XmlType = ParseType(r.GetAttribute("xmlType"));;
nd.SourceLine = ParseLineInfo(r.GetAttribute("lineInfo"));
nd.Annotation = ann;
if (nd is QilList) {
// Push new parent list onto stack
this.stk.Push((QilList) nd);
return true;
}
// Add node to its parent's list
this.stk.Peek().Add(nd);
return false;
}
private void EndElement() {
MethodInfo facMethod = null;
object[] facArgs;
QilList list;
QilNode nd;
ReaderAnnotation ann;
list = this.stk.Pop();
ann = (ReaderAnnotation) list.Annotation;
// Special case certain element names
string s = r.LocalName;
switch (r.LocalName) {
case "QilExpression": {
Debug.Assert(list.Count > 0, "QilExpression node requires a Root expression");
QilExpression qil = f.QilExpression(list[list.Count - 1]);
// Be flexible on order and presence of QilExpression children
for (int i = 0; i < list.Count - 1; i++) {
switch (list[i].NodeType) {
case QilNodeType.True:
case QilNodeType.False:
qil.IsDebug = list[i].NodeType == QilNodeType.True;
break;
case QilNodeType.FunctionList:
qil.FunctionList = (QilList) list[i];
break;
case QilNodeType.GlobalVariableList:
qil.GlobalVariableList = (QilList) list[i];
break;
case QilNodeType.GlobalParameterList:
qil.GlobalParameterList = (QilList) list[i];
break;
}
}
nd = qil;
break;
}
case "ForwardDecls":
this.inFwdDecls = false;
return;
case "Parameter":
case "Let":
case "For":
case "Function": {
string id = ann.Id;
QilName name = ann.Name;
Debug.Assert(id != null, r.LocalName + " must have an id attribute");
Debug.Assert(!this.inFwdDecls || ann.XmlType != null, "Forward decl for " + r.LocalName + " '" + id + "' must have an xmlType attribute");
// Create node (may be discarded later if it was already declared in forward declarations section)
switch (r.LocalName) {
case "Parameter":
Debug.Assert(list.Count <= (this.inFwdDecls ? 0 : 1), "Parameter '" + id + "' must have 0 or 1 arguments");
Debug.Assert(ann.XmlType != null, "Parameter '" + id + "' must have an xmlType attribute");
if (this.inFwdDecls || list.Count == 0)
nd = f.Parameter(null, name, ann.XmlType);
else
nd = f.Parameter(list[0], name, ann.XmlType);
break;
case "Let":
Debug.Assert(list.Count == (this.inFwdDecls ? 0 : 1), "Let '" + id + "' must have 0 or 1 arguments");
if (this.inFwdDecls)
nd = f.Let(f.Unknown(ann.XmlType));
else
nd = f.Let(list[0]);
break;
case "For":
Debug.Assert(list.Count == 1, "For '" + id + "' must have 1 argument");
nd = f.For(list[0]);
break;
default:
Debug.Assert(list.Count == (this.inFwdDecls ? 2 : 3), "Function '" + id + "' must have 2 or 3 arguments");
if (this.inFwdDecls)
nd = f.Function(list[0], list[1], ann.XmlType);
else
nd = f.Function(list[0], list[1], list[2], ann.XmlType != null ? ann.XmlType : list[1].XmlType);
break;
}
// Set DebugName
if (name != null)
((QilReference) nd).DebugName = name.ToString();
if (this.inFwdDecls) {
Debug.Assert(!this.scope.ContainsKey(id), "Multiple nodes have id '" + id + "'");
this.fwdDecls[id] = nd;
this.scope[id] = nd;
}
else {
if (this.fwdDecls.ContainsKey(id)) {
// Replace forward declaration
Debug.Assert(r.LocalName == Enum.GetName(typeof(QilNodeType), nd.NodeType), "Id '" + id + "' is not not bound to a " + r.LocalName + " forward decl");
nd = this.fwdDecls[id];
this.fwdDecls.Remove(id);
if (list.Count > 0) nd[0] = list[0];
if (list.Count > 1) nd[1] = list[1];
}
else {
// Put reference in scope
Debug.Assert(!this.scope.ContainsKey(id), "Id '" + id + "' is already in scope");
this.scope[id] = nd;
}
}
nd.Annotation = ann;
break;
}
case "RefTo": {
// Lookup reference
string id = ann.Id;
Debug.Assert(id != null, r.LocalName + " must have an id attribute");
Debug.Assert(this.scope.ContainsKey(id), "Id '" + id + "' is not in scope");
this.stk.Peek().Add(this.scope[id]);
return;
}
case "Sequence":
nd = f.Sequence(list);
break;
case "FunctionList":
nd = f.FunctionList(list);
break;
case "GlobalVariableList":
nd = f.GlobalVariableList(list);
break;
case "GlobalParameterList":
nd = f.GlobalParameterList(list);
break;
case "ActualParameterList":
nd = f.ActualParameterList(list);
break;
case "FormalParameterList":
nd = f.FormalParameterList(list);
break;
case "SortKeyList":
nd = f.SortKeyList(list);
break;
case "BranchList":
nd = f.BranchList(list);
break;
case "XsltInvokeEarlyBound": {
Debug.Assert(ann.ClrNamespace != null, "XsltInvokeEarlyBound must have a clrNamespace attribute");
Debug.Assert(list.Count == 2, "XsltInvokeEarlyBound must have exactly 2 arguments");
Debug.Assert(list.XmlType != null, "XsltInvokeEarlyBound must have an xmlType attribute");
MethodInfo mi = null;
QilName name = (QilName) list[0];
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) {
Type t = asm.GetType(ann.ClrNamespace);
if (t != null) {
mi = t.GetMethod(name.LocalName);
break;
}
}
Debug.Assert(mi != null, "Cannot find method " + ann.ClrNamespace + "." + name.ToString());
nd = f.XsltInvokeEarlyBound(name, f.LiteralObject(mi), list[1], ann.XmlType);
break;
}
default: {
// Find factory method which will be used to construct the Qil node
Debug.Assert(nameToFactoryMethod.ContainsKey(r.LocalName), "Method " + r.LocalName + " could not be found on QilFactory");
facMethod = nameToFactoryMethod[r.LocalName];
Debug.Assert(facMethod.GetParameters().Length == list.Count, "NodeType " + r.LocalName + " does not allow " + list.Count + " parameters");
// Create factory method arguments
facArgs = new object[list.Count];
for (int i = 0; i < facArgs.Length; i++)
facArgs[i] = list[i];
// Create node and set its properties
nd = (QilNode) facMethod.Invoke(f, facArgs);
break;
}
}
nd.SourceLine = list.SourceLine;
// Add node to its parent's list
this.stk.Peek().Add(nd);
}
private string ReadText() {
string s = string.Empty;
if (!r.IsEmptyElement) {
while (r.Read()) {
switch (r.NodeType) {
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
case XmlNodeType.Whitespace:
s += r.Value;
continue;
}
break;
}
}
return s;
}
private ISourceLineInfo ParseLineInfo(string s) {
if (s != null && s.Length > 0) {
Match m = lineInfoRegex.Match(s);
Debug.Assert(m.Success && m.Groups.Count == 5, "Malformed lineInfo attribute");
return new SourceLineInfo("",
Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture),
Int32.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture),
Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture),
Int32.Parse(m.Groups[4].Value, CultureInfo.InvariantCulture)
);
}
return null;
}
private XmlQueryType ParseType(string s) {
if (s != null && s.Length > 0) {
Match m = typeInfoRegex.Match(s);
Debug.Assert(m.Success && m.Groups.Count == 4, "Malformed Type info");
XmlQueryCardinality qc = new XmlQueryCardinality(m.Groups[1].Value);
bool strict = bool.Parse(m.Groups[3].Value);
string[] codes = m.Groups[2].Value.Split('|');
XmlQueryType[] types = new XmlQueryType[codes.Length];
for (int i = 0; i < codes.Length; i++)
types[i] = XmlQueryTypeFactory.Type((XmlTypeCode)Enum.Parse(typeof(XmlTypeCode), codes[i]), strict);
return XmlQueryTypeFactory.Product(XmlQueryTypeFactory.Choice(types), qc);
}
return null;
}
private QilName ParseName(string name) {
string prefix, local, uri;
int idx;
if (name != null && name.Length > 0) {
// If name contains '}' character, then namespace is non-empty
idx = name.LastIndexOf('}');
if (idx != -1 && name[0] == '{') {
uri = name.Substring(1, idx - 1);
name = name.Substring(idx + 1);
}
else {
uri = string.Empty;
}
// Parse QName
ValidateNames.ParseQNameThrow(name, out prefix, out local);
return f.LiteralQName(local, uri, prefix);
}
return null;
}
private class ReaderAnnotation {
public string Id;
public QilName Name;
public XmlQueryType XmlType;
public string ClrNamespace;
}
}
}
|