1 //------------------------------------------------------------------------------
2 // <copyright file="QilXmlReader.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;
12 using System.Globalization;
14 using System.Text.RegularExpressions;
15 using System.Reflection;
17 using System.Xml.Schema;
20 namespace System.Xml.Xsl.Qil {
23 /// Read the output of QilXmlWriter.
25 /// <remarks>This internal class allows roundtripping between the Xml serialization format for
26 /// QIL and the in-memory data structure.</remarks>
27 internal sealed class QilXmlReader {
28 private static Regex lineInfoRegex = new Regex(@"\[(\d+),(\d+) -- (\d+),(\d+)\]");
29 private static Regex typeInfoRegex = new Regex(@"(\w+);([\w|\|]+);(\w+)");
30 private static Dictionary<string, MethodInfo> nameToFactoryMethod;
34 private Stack<QilList> stk;
35 private bool inFwdDecls;
36 private Dictionary<string, QilNode> scope, fwdDecls;
38 static QilXmlReader() {
39 nameToFactoryMethod = new Dictionary<string, MethodInfo>();
41 // Build table that maps QilNodeType name to factory method info
42 foreach (MethodInfo mi in typeof(QilFactory).GetMethods(BindingFlags.Public | BindingFlags.Instance)) {
43 ParameterInfo[] parms = mi.GetParameters();
46 // Only match methods that take QilNode parameters
47 for (i = 0; i < parms.Length; i++) {
48 if (parms[i].ParameterType != typeof(QilNode))
52 if (i == parms.Length) {
53 // Enter the method that takes the maximum number of parameters
54 if (!nameToFactoryMethod.ContainsKey(mi.Name) || nameToFactoryMethod[mi.Name].GetParameters().Length < parms.Length)
55 nameToFactoryMethod[mi.Name] = mi;
60 public QilXmlReader(XmlReader r) {
62 this.f = new QilFactory();
65 public QilExpression Read() {
66 this.stk = new Stack<QilList>();
67 this.inFwdDecls = false;
68 this.scope = new Dictionary<string, QilNode>();
69 this.fwdDecls = new Dictionary<string, QilNode>();
71 this.stk.Push(f.Sequence());
75 case XmlNodeType.Element:
76 bool emptyElem = r.IsEmptyElement;
78 // XmlReader does not give an event for empty elements, so synthesize one
79 if (StartElement() && emptyElem)
83 case XmlNodeType.EndElement:
87 case XmlNodeType.Whitespace:
88 case XmlNodeType.SignificantWhitespace:
89 case XmlNodeType.XmlDeclaration:
90 case XmlNodeType.Comment:
91 case XmlNodeType.ProcessingInstruction:
95 Debug.Fail("Unexpected event " + r.NodeType + ", value " + r.Value);
100 Debug.Assert(this.fwdDecls.Keys.Count == 0, "One or more forward declarations were never defined");
101 Debug.Assert(this.stk.Peek()[0].NodeType == QilNodeType.QilExpression, "Serialized qil tree did not contain a QilExpression node");
102 return (QilExpression) this.stk.Peek()[0];
105 private bool StartElement() {
107 ReaderAnnotation ann = new ReaderAnnotation();
110 // Special case certain element names
112 switch (r.LocalName) {
113 case "LiteralString":
114 nd = f.LiteralString(ReadText());
118 nd = f.LiteralInt32(Int32.Parse(ReadText(), CultureInfo.InvariantCulture));
122 nd = f.LiteralInt64(Int64.Parse(ReadText(), CultureInfo.InvariantCulture));
125 case "LiteralDouble":
126 nd = f.LiteralDouble(Double.Parse(ReadText(), CultureInfo.InvariantCulture));
129 case "LiteralDecimal":
130 nd = f.LiteralDecimal(Decimal.Parse(ReadText(), CultureInfo.InvariantCulture));
134 nd = f.LiteralType(ParseType(ReadText()));
138 nd = ParseName(r.GetAttribute("name"));
139 Debug.Assert(nd != null, "LiteralQName element must have a name attribute");
140 Debug.Assert(r.IsEmptyElement, "LiteralQName element must be empty");
148 ann.Id = r.GetAttribute("id");
149 ann.Name = ParseName(r.GetAttribute("name"));
152 case "XsltInvokeEarlyBound":
153 ann.ClrNamespace = r.GetAttribute("clrNamespace");
157 this.inFwdDecls = true;
166 // Save xml type and source line information
167 ann.XmlType = ParseType(r.GetAttribute("xmlType"));;
168 nd.SourceLine = ParseLineInfo(r.GetAttribute("lineInfo"));
172 // Push new parent list onto stack
173 this.stk.Push((QilList) nd);
177 // Add node to its parent's list
178 this.stk.Peek().Add(nd);
182 private void EndElement() {
183 MethodInfo facMethod = null;
187 ReaderAnnotation ann;
189 list = this.stk.Pop();
190 ann = (ReaderAnnotation) list.Annotation;
192 // Special case certain element names
193 string s = r.LocalName;
194 switch (r.LocalName) {
195 case "QilExpression": {
196 Debug.Assert(list.Count > 0, "QilExpression node requires a Root expression");
197 QilExpression qil = f.QilExpression(list[list.Count - 1]);
199 // Be flexible on order and presence of QilExpression children
200 for (int i = 0; i < list.Count - 1; i++) {
201 switch (list[i].NodeType) {
202 case QilNodeType.True:
203 case QilNodeType.False:
204 qil.IsDebug = list[i].NodeType == QilNodeType.True;
207 case QilNodeType.FunctionList:
208 qil.FunctionList = (QilList) list[i];
211 case QilNodeType.GlobalVariableList:
212 qil.GlobalVariableList = (QilList) list[i];
215 case QilNodeType.GlobalParameterList:
216 qil.GlobalParameterList = (QilList) list[i];
225 this.inFwdDecls = false;
233 QilName name = ann.Name;
234 Debug.Assert(id != null, r.LocalName + " must have an id attribute");
235 Debug.Assert(!this.inFwdDecls || ann.XmlType != null, "Forward decl for " + r.LocalName + " '" + id + "' must have an xmlType attribute");
237 // Create node (may be discarded later if it was already declared in forward declarations section)
238 switch (r.LocalName) {
240 Debug.Assert(list.Count <= (this.inFwdDecls ? 0 : 1), "Parameter '" + id + "' must have 0 or 1 arguments");
241 Debug.Assert(ann.XmlType != null, "Parameter '" + id + "' must have an xmlType attribute");
242 if (this.inFwdDecls || list.Count == 0)
243 nd = f.Parameter(null, name, ann.XmlType);
245 nd = f.Parameter(list[0], name, ann.XmlType);
249 Debug.Assert(list.Count == (this.inFwdDecls ? 0 : 1), "Let '" + id + "' must have 0 or 1 arguments");
251 nd = f.Let(f.Unknown(ann.XmlType));
257 Debug.Assert(list.Count == 1, "For '" + id + "' must have 1 argument");
262 Debug.Assert(list.Count == (this.inFwdDecls ? 2 : 3), "Function '" + id + "' must have 2 or 3 arguments");
264 nd = f.Function(list[0], list[1], ann.XmlType);
266 nd = f.Function(list[0], list[1], list[2], ann.XmlType != null ? ann.XmlType : list[1].XmlType);
272 ((QilReference) nd).DebugName = name.ToString();
274 if (this.inFwdDecls) {
275 Debug.Assert(!this.scope.ContainsKey(id), "Multiple nodes have id '" + id + "'");
276 this.fwdDecls[id] = nd;
280 if (this.fwdDecls.ContainsKey(id)) {
281 // Replace forward declaration
282 Debug.Assert(r.LocalName == Enum.GetName(typeof(QilNodeType), nd.NodeType), "Id '" + id + "' is not not bound to a " + r.LocalName + " forward decl");
283 nd = this.fwdDecls[id];
284 this.fwdDecls.Remove(id);
286 if (list.Count > 0) nd[0] = list[0];
287 if (list.Count > 1) nd[1] = list[1];
290 // Put reference in scope
291 Debug.Assert(!this.scope.ContainsKey(id), "Id '" + id + "' is already in scope");
302 Debug.Assert(id != null, r.LocalName + " must have an id attribute");
304 Debug.Assert(this.scope.ContainsKey(id), "Id '" + id + "' is not in scope");
305 this.stk.Peek().Add(this.scope[id]);
310 nd = f.Sequence(list);
314 nd = f.FunctionList(list);
317 case "GlobalVariableList":
318 nd = f.GlobalVariableList(list);
321 case "GlobalParameterList":
322 nd = f.GlobalParameterList(list);
325 case "ActualParameterList":
326 nd = f.ActualParameterList(list);
329 case "FormalParameterList":
330 nd = f.FormalParameterList(list);
334 nd = f.SortKeyList(list);
338 nd = f.BranchList(list);
341 case "XsltInvokeEarlyBound": {
342 Debug.Assert(ann.ClrNamespace != null, "XsltInvokeEarlyBound must have a clrNamespace attribute");
343 Debug.Assert(list.Count == 2, "XsltInvokeEarlyBound must have exactly 2 arguments");
344 Debug.Assert(list.XmlType != null, "XsltInvokeEarlyBound must have an xmlType attribute");
345 MethodInfo mi = null;
346 QilName name = (QilName) list[0];
348 foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) {
349 Type t = asm.GetType(ann.ClrNamespace);
351 mi = t.GetMethod(name.LocalName);
356 Debug.Assert(mi != null, "Cannot find method " + ann.ClrNamespace + "." + name.ToString());
358 nd = f.XsltInvokeEarlyBound(name, f.LiteralObject(mi), list[1], ann.XmlType);
363 // Find factory method which will be used to construct the Qil node
364 Debug.Assert(nameToFactoryMethod.ContainsKey(r.LocalName), "Method " + r.LocalName + " could not be found on QilFactory");
365 facMethod = nameToFactoryMethod[r.LocalName];
366 Debug.Assert(facMethod.GetParameters().Length == list.Count, "NodeType " + r.LocalName + " does not allow " + list.Count + " parameters");
368 // Create factory method arguments
369 facArgs = new object[list.Count];
370 for (int i = 0; i < facArgs.Length; i++)
371 facArgs[i] = list[i];
373 // Create node and set its properties
374 nd = (QilNode) facMethod.Invoke(f, facArgs);
379 nd.SourceLine = list.SourceLine;
381 // Add node to its parent's list
382 this.stk.Peek().Add(nd);
385 private string ReadText() {
386 string s = string.Empty;
388 if (!r.IsEmptyElement) {
390 switch (r.NodeType) {
391 case XmlNodeType.Text:
392 case XmlNodeType.SignificantWhitespace:
393 case XmlNodeType.Whitespace:
405 private ISourceLineInfo ParseLineInfo(string s) {
406 if (s != null && s.Length > 0) {
407 Match m = lineInfoRegex.Match(s);
408 Debug.Assert(m.Success && m.Groups.Count == 5, "Malformed lineInfo attribute");
409 return new SourceLineInfo("",
410 Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture),
411 Int32.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture),
412 Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture),
413 Int32.Parse(m.Groups[4].Value, CultureInfo.InvariantCulture)
419 private XmlQueryType ParseType(string s) {
420 if (s != null && s.Length > 0) {
421 Match m = typeInfoRegex.Match(s);
422 Debug.Assert(m.Success && m.Groups.Count == 4, "Malformed Type info");
424 XmlQueryCardinality qc = new XmlQueryCardinality(m.Groups[1].Value);
425 bool strict = bool.Parse(m.Groups[3].Value);
427 string[] codes = m.Groups[2].Value.Split('|');
428 XmlQueryType[] types = new XmlQueryType[codes.Length];
430 for (int i = 0; i < codes.Length; i++)
431 types[i] = XmlQueryTypeFactory.Type((XmlTypeCode)Enum.Parse(typeof(XmlTypeCode), codes[i]), strict);
433 return XmlQueryTypeFactory.Product(XmlQueryTypeFactory.Choice(types), qc);
438 private QilName ParseName(string name) {
439 string prefix, local, uri;
442 if (name != null && name.Length > 0) {
443 // If name contains '}' character, then namespace is non-empty
444 idx = name.LastIndexOf('}');
445 if (idx != -1 && name[0] == '{') {
446 uri = name.Substring(1, idx - 1);
447 name = name.Substring(idx + 1);
454 ValidateNames.ParseQNameThrow(name, out prefix, out local);
456 return f.LiteralQName(local, uri, prefix);
461 private class ReaderAnnotation {
464 public XmlQueryType XmlType;
465 public string ClrNamespace;