eebd0d843f52ac79f7c8d27d06493ffb8ecf1eb5
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / QIL / QilXmlReader.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="QilXmlReader.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.IO;
12 using System.Globalization;
13 using System.Text;
14 using System.Text.RegularExpressions;
15 using System.Reflection;
16 using System.Xml;
17 using System.Xml.Schema;
18 using System.Xml.Xsl;
19
20 namespace System.Xml.Xsl.Qil {
21
22     /// <summary>
23     /// Read the output of QilXmlWriter.
24     /// </summary>
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;
31
32         private QilFactory f;
33         private XmlReader r;
34         private Stack<QilList> stk;
35         private bool inFwdDecls;
36         private Dictionary<string, QilNode> scope, fwdDecls;
37
38         static QilXmlReader() {
39             nameToFactoryMethod = new Dictionary<string, MethodInfo>();
40
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();
44                 int i;
45
46                 // Only match methods that take QilNode parameters
47                 for (i = 0; i < parms.Length; i++) {
48                     if (parms[i].ParameterType != typeof(QilNode))
49                         break;
50                 }
51
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;
56                 }
57             }
58         }
59
60         public QilXmlReader(XmlReader r) {
61             this.r = r;
62             this.f = new QilFactory();
63         }
64
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>();
70
71             this.stk.Push(f.Sequence());
72
73             while (r.Read()) {
74                 switch (r.NodeType) {
75                     case XmlNodeType.Element:
76                         bool emptyElem = r.IsEmptyElement;
77
78                         // XmlReader does not give an event for empty elements, so synthesize one
79                         if (StartElement() && emptyElem)
80                             EndElement();
81                         break;
82
83                     case XmlNodeType.EndElement:
84                         EndElement();
85                         break;
86
87                     case XmlNodeType.Whitespace:
88                     case XmlNodeType.SignificantWhitespace:
89                     case XmlNodeType.XmlDeclaration:
90                     case XmlNodeType.Comment:
91                     case XmlNodeType.ProcessingInstruction:
92                         break;
93
94                     default:
95                         Debug.Fail("Unexpected event " + r.NodeType + ", value " + r.Value);
96                         break;
97                 }
98             }
99
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];
103         }
104
105         private bool StartElement() {
106             QilNode nd;
107             ReaderAnnotation ann = new ReaderAnnotation();
108             string s;
109
110             // Special case certain element names
111             s = r.LocalName;
112             switch (r.LocalName) {
113                 case "LiteralString":
114                     nd = f.LiteralString(ReadText());
115                     break;
116
117                 case "LiteralInt32":
118                     nd = f.LiteralInt32(Int32.Parse(ReadText(), CultureInfo.InvariantCulture));
119                     break;
120
121                 case "LiteralInt64":
122                     nd = f.LiteralInt64(Int64.Parse(ReadText(), CultureInfo.InvariantCulture));
123                     break;
124
125                 case "LiteralDouble":
126                     nd = f.LiteralDouble(Double.Parse(ReadText(), CultureInfo.InvariantCulture));
127                     break;
128
129                 case "LiteralDecimal":
130                     nd = f.LiteralDecimal(Decimal.Parse(ReadText(), CultureInfo.InvariantCulture));
131                     break;
132
133                 case "LiteralType":
134                     nd = f.LiteralType(ParseType(ReadText()));
135                     break;
136
137                 case "LiteralQName":
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");
141                     break;
142
143                 case "For":
144                 case "Let":
145                 case "Parameter":
146                 case "Function":
147                 case "RefTo":
148                     ann.Id = r.GetAttribute("id");
149                     ann.Name = ParseName(r.GetAttribute("name"));
150                     goto default;
151
152                 case "XsltInvokeEarlyBound":
153                     ann.ClrNamespace = r.GetAttribute("clrNamespace");
154                     goto default;
155
156                 case "ForwardDecls":
157                     this.inFwdDecls = true;
158                     goto default;
159
160                 default:
161                     // Create sequence
162                     nd = f.Sequence();
163                     break;
164             }
165
166             // Save xml type and source line information
167             ann.XmlType = ParseType(r.GetAttribute("xmlType"));;
168             nd.SourceLine = ParseLineInfo(r.GetAttribute("lineInfo"));
169             nd.Annotation = ann;
170
171             if (nd is QilList) {
172                 // Push new parent list onto stack
173                 this.stk.Push((QilList) nd);
174                 return true;
175             }
176
177             // Add node to its parent's list
178             this.stk.Peek().Add(nd);
179             return false;
180         }
181
182         private void EndElement() {
183             MethodInfo facMethod = null;
184             object[] facArgs;
185             QilList list;
186             QilNode nd;
187             ReaderAnnotation ann;
188
189             list = this.stk.Pop();
190             ann = (ReaderAnnotation) list.Annotation;
191
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]);
198
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;
205                                 break;
206
207                             case QilNodeType.FunctionList:
208                                 qil.FunctionList = (QilList) list[i];
209                                 break;
210
211                             case QilNodeType.GlobalVariableList:
212                                 qil.GlobalVariableList = (QilList) list[i];
213                                 break;
214
215                             case QilNodeType.GlobalParameterList:
216                                 qil.GlobalParameterList = (QilList) list[i];
217                                 break;
218                         }
219                     }
220                     nd = qil;
221                     break;
222                 }
223
224                 case "ForwardDecls":
225                     this.inFwdDecls = false;
226                     return;
227
228                 case "Parameter":
229                 case "Let":
230                 case "For":
231                 case "Function": {
232                     string id = ann.Id;
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");
236
237                     // Create node (may be discarded later if it was already declared in forward declarations section)
238                     switch (r.LocalName) {
239                         case "Parameter":
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);
244                             else
245                                 nd = f.Parameter(list[0], name, ann.XmlType);
246                             break;
247
248                         case "Let":
249                             Debug.Assert(list.Count == (this.inFwdDecls ? 0 : 1), "Let '" + id + "' must have 0 or 1 arguments");
250                             if (this.inFwdDecls)
251                                 nd = f.Let(f.Unknown(ann.XmlType));
252                             else
253                                 nd = f.Let(list[0]);
254                             break;
255
256                         case "For":
257                             Debug.Assert(list.Count == 1, "For '" + id + "' must have 1 argument");
258                             nd = f.For(list[0]);
259                             break;
260
261                         default:
262                             Debug.Assert(list.Count == (this.inFwdDecls ? 2 : 3), "Function '" + id + "' must have 2 or 3 arguments");
263                             if (this.inFwdDecls)
264                                 nd = f.Function(list[0], list[1], ann.XmlType);
265                             else
266                                 nd = f.Function(list[0], list[1], list[2], ann.XmlType != null ? ann.XmlType : list[1].XmlType);
267                             break;
268                     }
269
270                     // Set DebugName
271                     if (name != null)
272                         ((QilReference) nd).DebugName = name.ToString();
273
274                     if (this.inFwdDecls) {
275                         Debug.Assert(!this.scope.ContainsKey(id), "Multiple nodes have id '" + id + "'");
276                         this.fwdDecls[id] = nd;
277                         this.scope[id] = nd;
278                     }
279                     else {
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);
285
286                             if (list.Count > 0) nd[0] = list[0];
287                             if (list.Count > 1) nd[1] = list[1];
288                         }
289                         else {
290                             // Put reference in scope
291                             Debug.Assert(!this.scope.ContainsKey(id), "Id '" + id + "' is already in scope");
292                             this.scope[id] = nd;
293                         }
294                     }
295                     nd.Annotation = ann;
296                     break;
297                 }
298
299                 case "RefTo": {
300                     // Lookup reference
301                     string id = ann.Id;
302                     Debug.Assert(id != null, r.LocalName + " must have an id attribute");
303
304                     Debug.Assert(this.scope.ContainsKey(id), "Id '" + id + "' is not in scope");
305                     this.stk.Peek().Add(this.scope[id]);
306                     return;
307                 }
308
309                 case "Sequence":
310                     nd = f.Sequence(list);
311                     break;
312
313                 case "FunctionList":
314                     nd = f.FunctionList(list);
315                     break;
316
317                 case "GlobalVariableList":
318                     nd = f.GlobalVariableList(list);
319                     break;
320
321                 case "GlobalParameterList":
322                     nd = f.GlobalParameterList(list);
323                     break;
324
325                 case "ActualParameterList":
326                     nd = f.ActualParameterList(list);
327                     break;
328
329                 case "FormalParameterList":
330                     nd = f.FormalParameterList(list);
331                     break;
332
333                 case "SortKeyList":
334                     nd = f.SortKeyList(list);
335                     break;
336
337                 case "BranchList":
338                     nd = f.BranchList(list);
339                     break;
340
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];
347
348                     foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) {
349                         Type t = asm.GetType(ann.ClrNamespace);
350                         if (t != null) {
351                             mi = t.GetMethod(name.LocalName);
352                             break;
353                         }
354                     }
355
356                     Debug.Assert(mi != null, "Cannot find method " + ann.ClrNamespace + "." + name.ToString());
357
358                     nd = f.XsltInvokeEarlyBound(name, f.LiteralObject(mi), list[1], ann.XmlType);
359                     break;
360                 }
361
362                 default: {
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");
367
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];
372
373                     // Create node and set its properties
374                     nd = (QilNode) facMethod.Invoke(f, facArgs);
375                     break;
376                 }
377             }
378
379             nd.SourceLine = list.SourceLine;
380
381             // Add node to its parent's list
382             this.stk.Peek().Add(nd);
383         }
384
385         private string ReadText() {
386             string s = string.Empty;
387
388             if (!r.IsEmptyElement) {
389                 while (r.Read()) {
390                     switch (r.NodeType) {
391                         case XmlNodeType.Text:
392                         case XmlNodeType.SignificantWhitespace:
393                         case XmlNodeType.Whitespace:
394                             s += r.Value;
395                             continue;
396                     }
397
398                     break;
399                 }
400             }
401
402             return s;
403         }
404
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)
414                 );
415             }
416             return null;
417         }
418
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");
423
424                 XmlQueryCardinality qc = new XmlQueryCardinality(m.Groups[1].Value);
425                 bool strict = bool.Parse(m.Groups[3].Value);
426
427                 string[] codes = m.Groups[2].Value.Split('|');
428                 XmlQueryType[] types = new XmlQueryType[codes.Length];
429
430                 for (int i = 0; i < codes.Length; i++)
431                     types[i] = XmlQueryTypeFactory.Type((XmlTypeCode)Enum.Parse(typeof(XmlTypeCode), codes[i]), strict);
432
433                 return XmlQueryTypeFactory.Product(XmlQueryTypeFactory.Choice(types), qc);
434             }
435             return null;
436         }
437
438         private QilName ParseName(string name) {
439             string prefix, local, uri;
440             int idx;
441
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);
448                 }
449                 else {
450                     uri = string.Empty;
451                 }
452
453                 // Parse QName
454                 ValidateNames.ParseQNameThrow(name, out prefix, out local);
455
456                 return f.LiteralQName(local, uri, prefix);
457             }
458             return null;
459         }
460
461         private class ReaderAnnotation {
462             public string Id;
463             public QilName Name;
464             public XmlQueryType XmlType;
465             public string ClrNamespace;
466         }
467     }
468 }