e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
469 lines
18 KiB
C#
469 lines
18 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="QilXmlReader.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">[....]</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;
|
|
}
|
|
}
|
|
}
|