Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / Runtime / XmlQueryRuntime.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlQueryRuntime.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.IO;
9 using System.Xml;
10 using System.Xml.XPath;
11 using System.Xml.Schema;
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.Diagnostics;
15 using System.Text;
16 using System.Globalization;
17 using System.Reflection;
18 using System.Reflection.Emit;
19 using System.Xml.Xsl.Qil;
20 using System.Xml.Xsl.IlGen;
21 using System.ComponentModel;
22 using MS.Internal.Xml.XPath;
23 using System.Runtime.Versioning;
24
25 namespace System.Xml.Xsl.Runtime {
26     using Res = System.Xml.Utils.Res;
27
28     /// <summary>
29     /// XmlQueryRuntime is passed as the first parameter to all generated query methods.
30     ///
31     /// XmlQueryRuntime contains runtime support for generated ILGen queries:
32     ///   1. Stack of output writers (stack handles nested document construction)
33     ///   2. Manages list of all xml types that are used within the query
34     ///   3. Manages list of all atomized names that are used within the query
35     /// </summary>
36     [EditorBrowsable(EditorBrowsableState.Never)]
37     public sealed class XmlQueryRuntime {
38         // Early-Bound Library Objects
39         private XmlQueryContext ctxt;
40         private XsltLibrary xsltLib;
41         private EarlyBoundInfo[] earlyInfo;
42         private object[] earlyObjects;
43
44         // Global variables and parameters
45         private string[] globalNames;
46         private object[] globalValues;
47
48         // Names, prefix mappings, and name filters
49         private XmlNameTable nameTableQuery;
50         private string[] atomizedNames;             // Names after atomization
51         private XmlNavigatorFilter[] filters;       // Name filters (contain atomized names)
52         private StringPair[][] prefixMappingsList;  // Lists of prefix mappings (used to resolve computed names)
53
54         // Xml types
55         private XmlQueryType[] types;
56
57         // Collations
58         private XmlCollation[] collations;
59
60         // Document ordering
61         private DocumentOrderComparer docOrderCmp;
62
63         // Indexes
64         private ArrayList[] indexes;
65
66         // Output construction
67         private XmlQueryOutput output;
68         private Stack<XmlQueryOutput> stkOutput;
69
70
71         //-----------------------------------------------
72         // Constructors
73         //-----------------------------------------------
74
75         /// <summary>
76         /// This constructor is internal so that external users cannot construct it (and therefore we do not have to test it separately).
77         /// </summary>
78         [ResourceConsumption(ResourceScope.Machine)]
79         [ResourceExposure(ResourceScope.Machine)]
80         internal XmlQueryRuntime(XmlQueryStaticData data, object defaultDataSource, XmlResolver dataSources, XsltArgumentList argList, XmlSequenceWriter seqWrt) {
81             Debug.Assert(data != null);
82             string[] names = data.Names;
83             Int32Pair[] filters = data.Filters;
84             WhitespaceRuleLookup wsRules;
85             int i;
86
87             // Early-Bound Library Objects
88             wsRules = (data.WhitespaceRules != null && data.WhitespaceRules.Count != 0) ? new WhitespaceRuleLookup(data.WhitespaceRules) : null;
89             this.ctxt = new XmlQueryContext(this, defaultDataSource, dataSources, argList, wsRules);
90             this.xsltLib = null;
91             this.earlyInfo = data.EarlyBound;
92             this.earlyObjects = (this.earlyInfo != null) ? new object[earlyInfo.Length] : null;
93
94             // Global variables and parameters
95             this.globalNames = data.GlobalNames;
96             this.globalValues = (this.globalNames != null) ? new object[this.globalNames.Length] : null;
97
98             // Names
99             this.nameTableQuery = this.ctxt.QueryNameTable;
100             this.atomizedNames = null;
101
102             if (names != null) {
103                 // Atomize all names in "nameTableQuery".  Use names from the default data source's
104                 // name table when possible.
105                 XmlNameTable nameTableDefault = ctxt.DefaultNameTable;
106                 this.atomizedNames = new string[names.Length];
107
108                 if (nameTableDefault != this.nameTableQuery && nameTableDefault != null) {
109                     // Ensure that atomized names from the default data source are added to the
110                     // name table used in this query
111                     for (i = 0; i < names.Length; i++) {
112                         string name = nameTableDefault.Get(names[i]);
113                         this.atomizedNames[i] = this.nameTableQuery.Add(name ?? names[i]);
114                     }
115                 }
116                 else {
117                     // Enter names into nametable used in this query
118                     for (i = 0; i < names.Length; i++)
119                         this.atomizedNames[i] = this.nameTableQuery.Add(names[i]);
120                 }
121             }
122
123             // Name filters
124             this.filters = null;
125             if (filters != null) {
126                 // Construct name filters.  Each pair of integers in the filters[] array specifies the
127                 // (localName, namespaceUri) of the NameFilter to be created.
128                 this.filters = new XmlNavigatorFilter[filters.Length];
129
130                 for (i = 0; i < filters.Length; i++)
131                     this.filters[i] = XmlNavNameFilter.Create(this.atomizedNames[filters[i].Left], this.atomizedNames[filters[i].Right]);
132             }
133
134             // Prefix maping lists
135             this.prefixMappingsList = data.PrefixMappingsList;
136
137             // Xml types
138             this.types = data.Types;
139
140             // Xml collations
141             this.collations = data.Collations;
142
143             // Document ordering
144             this.docOrderCmp = new DocumentOrderComparer();
145
146             // Indexes
147             this.indexes = null;
148
149             // Output construction
150             this.stkOutput = new Stack<XmlQueryOutput>(16);
151             this.output = new XmlQueryOutput(this, seqWrt);
152         }
153
154
155         //-----------------------------------------------
156         // Debugger Utility Methods
157         //-----------------------------------------------
158
159         /// <summary>
160         /// Return array containing the names of all the global variables and parameters used in this query, in this format:
161         ///     {namespace}prefix:local-name
162         /// </summary>
163         public string[] DebugGetGlobalNames() {
164             return this.globalNames;
165         }
166
167         /// <summary>
168         /// Get the value of a global value having the specified name.  Always return the global value as a list of XPathItem.
169         /// Return null if there is no global value having the specified name.
170         /// </summary>
171         public IList DebugGetGlobalValue(string name) {
172             for (int idx = 0; idx < this.globalNames.Length; idx++) {
173                 if (this.globalNames[idx] == name) {
174                     Debug.Assert(IsGlobalComputed(idx), "Cannot get the value of a global value until it has been computed.");
175                     Debug.Assert(this.globalValues[idx] is IList<XPathItem>, "Only debugger should call this method, and all global values should have type item* in debugging scenarios.");
176                     return (IList) this.globalValues[idx];
177                 }
178             }
179             return null;
180         }
181
182         /// <summary>
183         /// Set the value of a global value having the specified name.  If there is no such value, this method is a no-op.
184         /// </summary>
185         public void DebugSetGlobalValue(string name, object value) {
186             for (int idx = 0; idx < this.globalNames.Length; idx++) {
187                 if (this.globalNames[idx] == name) {
188                     Debug.Assert(IsGlobalComputed(idx), "Cannot get the value of a global value until it has been computed.");
189                     Debug.Assert(this.globalValues[idx] is IList<XPathItem>, "Only debugger should call this method, and all global values should have type item* in debugging scenarios.");
190
191                     // Always convert "value" to a list of XPathItem using the item* converter
192                     this.globalValues[idx] = (IList<XPathItem>) XmlAnyListConverter.ItemList.ChangeType(value, typeof(XPathItem[]), null);
193                     break;
194                 }
195             }
196         }
197
198         /// <summary>
199         /// Convert sequence to it's appropriate XSLT type and return to caller.
200         /// </summary>
201         public object DebugGetXsltValue(IList seq) {
202             if (seq != null && seq.Count == 1) {
203                 XPathItem item = seq[0] as XPathItem;
204                 if (item != null && !item.IsNode) {
205                     return item.TypedValue;
206                 }
207                 else if (item is RtfNavigator) {
208                     return ((RtfNavigator) item).ToNavigator();
209                 }
210             }
211
212             return seq;
213         }
214
215
216         //-----------------------------------------------
217         // Early-Bound Library Objects
218         //-----------------------------------------------
219
220         internal const BindingFlags EarlyBoundFlags     = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
221         internal const BindingFlags LateBoundFlags      = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
222
223         /// <summary>
224         /// Return the object that manages external user context information such as data sources, parameters, extension objects, etc.
225         /// </summary>
226         public XmlQueryContext ExternalContext {
227             get { return this.ctxt; }
228         }
229
230         /// <summary>
231         /// Return the object that manages the state needed to implement various Xslt functions.
232         /// </summary>
233         public XsltLibrary XsltFunctions {
234             get {
235                 if (this.xsltLib == null) {
236                     this.xsltLib = new XsltLibrary(this);
237                 }
238
239                 return this.xsltLib;
240             }
241         }
242
243         /// <summary>
244         /// Get the early-bound extension object identified by "index".  If it does not yet exist, create an instance using the
245         /// corresponding ConstructorInfo.
246         /// </summary>
247         public object GetEarlyBoundObject(int index) {
248             object obj;
249             Debug.Assert(this.earlyObjects != null && index < this.earlyObjects.Length, "Early bound object does not exist");
250
251             obj = this.earlyObjects[index];
252             if (obj == null) {
253                 // Early-bound object does not yet exist, so create it now
254                 obj = this.earlyInfo[index].CreateObject();
255                 this.earlyObjects[index] = obj;
256             }
257
258             return obj;
259         }
260
261         /// <summary>
262         /// Return true if the early bound object identified by "namespaceUri" contains a method that matches "name".
263         /// </summary>
264         public bool EarlyBoundFunctionExists(string name, string namespaceUri) {
265             if (this.earlyInfo == null)
266                 return false;
267
268             for (int idx = 0; idx < this.earlyInfo.Length; idx++) {
269                 if (namespaceUri == this.earlyInfo[idx].NamespaceUri)
270                     return new XmlExtensionFunction(name, namespaceUri, -1, this.earlyInfo[idx].EarlyBoundType, EarlyBoundFlags).CanBind();
271             }
272
273             return false;
274         }
275
276
277         //-----------------------------------------------
278         // Global variables and parameters
279         //-----------------------------------------------
280
281         /// <summary>
282         /// Return true if the global value specified by idxValue was previously computed.
283         /// </summary>
284         public bool IsGlobalComputed(int index) {
285             return this.globalValues[index] != null;
286         }
287
288         /// <summary>
289         /// Return the value that is bound to the global variable or parameter specified by idxValue.
290         /// If the value has not yet been computed, then compute it now and store it in this.globalValues.
291         /// </summary>
292         public object GetGlobalValue(int index) {
293             Debug.Assert(IsGlobalComputed(index), "Cannot get the value of a global value until it has been computed.");
294             return this.globalValues[index];
295         }
296
297         /// <summary>
298         /// Return the value that is bound to the global variable or parameter specified by idxValue.
299         /// If the value has not yet been computed, then compute it now and store it in this.globalValues.
300         /// </summary>
301         public void SetGlobalValue(int index, object value) {
302             Debug.Assert(!IsGlobalComputed(index), "Global value should only be set once.");
303             this.globalValues[index] = value;
304         }
305
306
307         //-----------------------------------------------
308         // Names, prefix mappings, and name filters
309         //-----------------------------------------------
310
311         /// <summary>
312         /// Return the name table used to atomize all names used by the query.
313         /// </summary>
314         public XmlNameTable NameTable {
315             get { return this.nameTableQuery; }
316         }
317
318         /// <summary>
319         /// Get the atomized name at the specified index in the array of names.
320         /// </summary>
321         public string GetAtomizedName(int index) {
322             Debug.Assert(this.atomizedNames != null);
323             return this.atomizedNames[index];
324         }
325
326         /// <summary>
327         /// Get the name filter at the specified index in the array of filters.
328         /// </summary>
329         public XmlNavigatorFilter GetNameFilter(int index) {
330             Debug.Assert(this.filters != null);
331             return this.filters[index];
332         }
333
334         /// <summary>
335         /// XPathNodeType.All: Filters all nodes
336         /// XPathNodeType.Attribute: Filters attributes
337         /// XPathNodeType.Namespace: Not allowed
338         /// XPathNodeType.XXX: Filters all nodes *except* those having XPathNodeType.XXX
339         /// </summary>
340         public XmlNavigatorFilter GetTypeFilter(XPathNodeType nodeType) {
341             if (nodeType == XPathNodeType.All)
342                 return XmlNavNeverFilter.Create();
343
344             if (nodeType == XPathNodeType.Attribute)
345                 return XmlNavAttrFilter.Create();
346
347             return XmlNavTypeFilter.Create(nodeType);
348         }
349
350         /// <summary>
351         /// Parse the specified tag name (foo:bar) and resolve the resulting prefix.  If the prefix cannot be resolved,
352         /// then throw an error.  Return an XmlQualifiedName.
353         /// </summary>
354         public XmlQualifiedName ParseTagName(string tagName, int indexPrefixMappings) {
355             string prefix, localName, ns;
356
357             // Parse the tagName as a prefix, localName pair and resolve the prefix
358             ParseTagName(tagName, indexPrefixMappings, out prefix, out localName, out ns);
359             return new XmlQualifiedName(localName, ns);
360         }
361
362         /// <summary>
363         /// Parse the specified tag name (foo:bar).  Return an XmlQualifiedName consisting of the parsed local name
364         /// and the specified namespace.
365         /// </summary>
366         public XmlQualifiedName ParseTagName(string tagName, string ns) {
367             string prefix, localName;
368
369             // Parse the tagName as a prefix, localName pair
370             ValidateNames.ParseQNameThrow(tagName, out prefix, out localName);
371             return new XmlQualifiedName(localName, ns);
372         }
373
374         /// <summary>
375         /// Parse the specified tag name (foo:bar) and resolve the resulting prefix.  If the prefix cannot be resolved,
376         /// then throw an error.  Return the prefix, localName, and namespace URI.
377         /// </summary>
378         internal void ParseTagName(string tagName, int idxPrefixMappings, out string prefix, out string localName, out string ns) {
379             Debug.Assert(this.prefixMappingsList != null);
380
381             // Parse the tagName as a prefix, localName pair
382             ValidateNames.ParseQNameThrow(tagName, out prefix, out localName);
383
384             // Map the prefix to a namespace URI
385             ns = null;
386             foreach (StringPair pair in this.prefixMappingsList[idxPrefixMappings]) {
387                 if (prefix == pair.Left) {
388                     ns = pair.Right;
389                     break;
390                 }
391             }
392
393             // Throw exception if prefix could not be resolved
394             if (ns == null) {
395                 // Check for mappings that are always in-scope
396                 if (prefix.Length == 0)
397                     ns = "";
398                 else if (prefix.Equals("xml"))
399                     ns = XmlReservedNs.NsXml;
400                 // It is not correct to resolve xmlns prefix in XPath but removing it would be a breaking change.
401                 else if (prefix.Equals("xmlns"))
402                     ns = XmlReservedNs.NsXmlNs;
403                 else
404                     throw new XslTransformException(Res.Xslt_InvalidPrefix, prefix);
405             }
406         }
407
408         /// <summary>
409         /// Return true if the nav1's LocalName and NamespaceURI properties equal nav2's corresponding properties.
410         /// </summary>
411         public bool IsQNameEqual(XPathNavigator n1, XPathNavigator n2) {
412             if ((object) n1.NameTable == (object) n2.NameTable) {
413                 // Use atomized comparison
414                 return (object) n1.LocalName == (object) n2.LocalName && (object) n1.NamespaceURI == (object) n2.NamespaceURI;
415             }
416
417             return (n1.LocalName == n2.LocalName) && (n1.NamespaceURI == n2.NamespaceURI);
418         }
419
420         /// <summary>
421         /// Return true if the specified navigator's LocalName and NamespaceURI properties equal the argument names.
422         /// </summary>
423         public bool IsQNameEqual(XPathNavigator navigator, int indexLocalName, int indexNamespaceUri) {
424             if ((object) navigator.NameTable == (object) this.nameTableQuery) {
425                 // Use atomized comparison
426                 return ((object) GetAtomizedName(indexLocalName) == (object) navigator.LocalName &&
427                         (object) GetAtomizedName(indexNamespaceUri) == (object) navigator.NamespaceURI);
428             }
429
430             // Use string comparison
431             return (GetAtomizedName(indexLocalName) == navigator.LocalName) && (GetAtomizedName(indexNamespaceUri) == navigator.NamespaceURI);
432         }
433
434
435         //-----------------------------------------------
436         // Xml types
437         //-----------------------------------------------
438
439         /// <summary>
440         /// Get the array of xml types that are used within this query.
441         /// </summary>
442         internal XmlQueryType[] XmlTypes {
443             get { return this.types; }
444         }
445
446         /// <summary>
447         /// Get the Xml query type at the specified index in the array of types.
448         /// </summary>
449         internal XmlQueryType GetXmlType(int idxType) {
450             Debug.Assert(this.types != null);
451             return this.types[idxType];
452         }
453
454         /// <summary>
455         /// Forward call to ChangeTypeXsltArgument(XmlQueryType, object, Type).
456         /// </summary>
457         public object ChangeTypeXsltArgument(int indexType, object value, Type destinationType) {
458             return ChangeTypeXsltArgument(GetXmlType(indexType), value, destinationType);
459         }
460
461         /// <summary>
462         /// Convert from the Clr type of "value" to Clr type "destinationType" using V1 Xslt rules.
463         /// These rules include converting any Rtf values to Nodes.
464         /// </summary>
465         internal object ChangeTypeXsltArgument(XmlQueryType xmlType, object value, Type destinationType) {
466             Debug.Assert(XmlILTypeHelper.GetStorageType(xmlType).IsAssignableFrom(value.GetType()),
467                          "Values passed to ChangeTypeXsltArgument should be in ILGen's default Clr representation.");
468             Debug.Assert(destinationType == XsltConvert.ObjectType || !destinationType.IsAssignableFrom(value.GetType()),
469                          "No need to call ChangeTypeXsltArgument since value is already assignable to destinationType " + destinationType);
470
471             switch (xmlType.TypeCode) {
472                 case XmlTypeCode.String:
473                     if (destinationType == XsltConvert.DateTimeType)
474                         value = XsltConvert.ToDateTime((string) value);
475                     break;
476
477                 case XmlTypeCode.Double:
478                     if (destinationType != XsltConvert.DoubleType)
479                         value = Convert.ChangeType(value, destinationType, CultureInfo.InvariantCulture);
480                     break;
481
482                 case XmlTypeCode.Node:
483                     Debug.Assert(xmlType != XmlQueryTypeFactory.Node && xmlType != XmlQueryTypeFactory.NodeS,
484                                  "Rtf values should have been eliminated by caller.");
485
486                     if (destinationType == XsltConvert.XPathNodeIteratorType) {
487                         value = new XPathArrayIterator((IList) value);
488                     }
489                     else if (destinationType == XsltConvert.XPathNavigatorArrayType) {
490                         // Copy sequence to XPathNavigator[]
491                         IList<XPathNavigator> seq = (IList<XPathNavigator>) value;
492                         XPathNavigator[] navArray = new XPathNavigator[seq.Count];
493
494                         for (int i = 0; i < seq.Count; i++)
495                             navArray[i] = seq[i];
496
497                         value = navArray;
498                     }
499                     break;
500
501                 case XmlTypeCode.Item: {
502                     // Only typeof(object) is supported as a destination type
503                     if (destinationType != XsltConvert.ObjectType)
504                         throw new XslTransformException(Res.Xslt_UnsupportedClrType, destinationType.Name);
505
506                     // Convert to default, backwards-compatible representation
507                     //   1. NodeSet: System.Xml.XPath.XPathNodeIterator
508                     //   2. Rtf: System.Xml.XPath.XPathNavigator
509                     //   3. Other:   Default V1 representation
510                     IList<XPathItem> seq = (IList<XPathItem>) value;
511                     if (seq.Count == 1) {
512                         XPathItem item = seq[0];
513
514                         if (item.IsNode) {
515                             // Node or Rtf
516                             RtfNavigator rtf = item as RtfNavigator;
517                             if (rtf != null)
518                                 value = rtf.ToNavigator();
519                             else
520                                 value = new XPathArrayIterator((IList) value);
521                         }
522                         else {
523                             // Atomic value
524                             value = item.TypedValue;
525                         }
526                     }
527                     else {
528                         // Nodeset
529                         value = new XPathArrayIterator((IList) value);
530                     }
531                     break;
532                 }
533             }
534
535             Debug.Assert(destinationType.IsAssignableFrom(value.GetType()), "ChangeType from type " + value.GetType().Name + " to type " + destinationType.Name + " failed");
536             return value;
537         }
538
539         /// <summary>
540         /// Forward call to ChangeTypeXsltResult(XmlQueryType, object)
541         /// </summary>
542         public object ChangeTypeXsltResult(int indexType, object value) {
543             return ChangeTypeXsltResult(GetXmlType(indexType), value);
544         }
545
546         /// <summary>
547         /// Convert from the Clr type of "value" to the default Clr type that ILGen uses to represent the xml type, using
548         /// the conversion rules of the xml type.
549         /// </summary>
550         internal object ChangeTypeXsltResult(XmlQueryType xmlType, object value) {
551             if (value == null)
552                 throw new XslTransformException(Res.Xslt_ItemNull, string.Empty);
553
554             switch (xmlType.TypeCode) {
555                 case XmlTypeCode.String:
556                     if (value.GetType() == XsltConvert.DateTimeType)
557                         value = XsltConvert.ToString((DateTime) value);
558                     break;
559
560                 case XmlTypeCode.Double:
561                     if (value.GetType() != XsltConvert.DoubleType)
562                         value = ((IConvertible) value).ToDouble(null);
563
564                     break;
565
566                 case XmlTypeCode.Node:
567                     if (!xmlType.IsSingleton) {
568                         XPathArrayIterator iter = value as XPathArrayIterator;
569
570                         // Special-case XPathArrayIterator in order to avoid copies
571                         if (iter != null && iter.AsList is XmlQueryNodeSequence) {
572                             value = iter.AsList as XmlQueryNodeSequence;
573                         }
574                         else {
575                             // Iterate over list and ensure it only contains nodes
576                             XmlQueryNodeSequence seq = new XmlQueryNodeSequence();
577                             IList list = value as IList;
578
579                             if (list != null) {
580                                 for (int i = 0; i < list.Count; i++)
581                                     seq.Add(EnsureNavigator(list[i]));
582                             }
583                             else {
584                                 foreach (object o in (IEnumerable) value)
585                                     seq.Add(EnsureNavigator(o));
586                             }
587
588                             value = seq;
589                         }
590
591                         // Always sort node-set by document order
592                         value = ((XmlQueryNodeSequence) value).DocOrderDistinct(this.docOrderCmp);
593                     }
594                     break;
595
596                 case XmlTypeCode.Item: {
597                     Type sourceType = value.GetType();
598                     IXPathNavigable navigable;
599
600                     // If static type is item, then infer type based on dynamic value
601                     switch (XsltConvert.InferXsltType(sourceType).TypeCode) {
602                         case XmlTypeCode.Boolean:
603                             value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Boolean), value));
604                             break;
605
606                         case XmlTypeCode.Double:
607                             value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Double), ((IConvertible) value).ToDouble(null)));
608                             break;
609
610                         case XmlTypeCode.String:
611                             if (sourceType == XsltConvert.DateTimeType)
612                                 value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), XsltConvert.ToString((DateTime) value)));
613                             else
614                                 value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), value));
615                             break;
616
617                         case XmlTypeCode.Node:
618                             // Support XPathNavigator[]
619                             value = ChangeTypeXsltResult(XmlQueryTypeFactory.NodeS, value);
620                             break;
621
622                         case XmlTypeCode.Item:
623                             // Support XPathNodeIterator
624                             if (value is XPathNodeIterator) {
625                                 value = ChangeTypeXsltResult(XmlQueryTypeFactory.NodeS, value);
626                                 break;
627                             }
628
629                             // Support IXPathNavigable and XPathNavigator
630                             navigable = value as IXPathNavigable;
631                             if (navigable != null) {
632                                 if (value is XPathNavigator)
633                                     value = new XmlQueryNodeSequence((XPathNavigator) value);
634                                 else
635                                     value = new XmlQueryNodeSequence(navigable.CreateNavigator());
636                                 break;
637                             }
638
639                             throw new XslTransformException(Res.Xslt_UnsupportedClrType, sourceType.Name);
640                     }
641                     break;
642                 }
643             }
644
645             Debug.Assert(XmlILTypeHelper.GetStorageType(xmlType).IsAssignableFrom(value.GetType()), "Xml type " + xmlType + " is not represented in ILGen as " + value.GetType().Name);
646             return value;
647         }
648
649         /// <summary>
650         /// Ensure that "value" is a navigator and not null.
651         /// </summary>
652         private static XPathNavigator EnsureNavigator(object value) {
653             XPathNavigator nav = value as XPathNavigator;
654
655             if (nav == null)
656                 throw new XslTransformException(Res.Xslt_ItemNull, string.Empty);
657
658             return nav;
659         }
660
661         /// <summary>
662         /// Return true if the type of every item in "seq" matches the xml type identified by "idxType".
663         /// </summary>
664         public bool MatchesXmlType(IList<XPathItem> seq, int indexType) {
665             XmlQueryType typBase = GetXmlType(indexType);
666             XmlQueryCardinality card;
667
668             switch (seq.Count) {
669                 case 0: card = XmlQueryCardinality.Zero; break;
670                 case 1: card = XmlQueryCardinality.One; break;
671                 default: card = XmlQueryCardinality.More; break;
672             }
673
674             if (!(card <= typBase.Cardinality))
675                 return false;
676
677             typBase = typBase.Prime;
678             for (int i = 0; i < seq.Count; i++) {
679                 if (!CreateXmlType(seq[0]).IsSubtypeOf(typBase))
680                     return false;
681             }
682
683             return true;
684         }
685
686         /// <summary>
687         /// Return true if the type of "item" matches the xml type identified by "idxType".
688         /// </summary>
689         public bool MatchesXmlType(XPathItem item, int indexType) {
690             return CreateXmlType(item).IsSubtypeOf(GetXmlType(indexType));
691         }
692
693         /// <summary>
694         /// Return true if the type of "seq" is a subtype of a singleton type identified by "code".
695         /// </summary>
696         public bool MatchesXmlType(IList<XPathItem> seq, XmlTypeCode code) {
697             if (seq.Count != 1)
698                 return false;
699
700             return MatchesXmlType(seq[0], code);
701         }
702
703         /// <summary>
704         /// Return true if the type of "item" is a subtype of the type identified by "code".
705         /// </summary>
706         public bool MatchesXmlType(XPathItem item, XmlTypeCode code) {
707             // All atomic type codes appear after AnyAtomicType
708             if (code > XmlTypeCode.AnyAtomicType)
709                     return !item.IsNode && item.XmlType.TypeCode == code;
710
711             // Handle node code and AnyAtomicType
712             switch (code) {
713                 case XmlTypeCode.AnyAtomicType: return !item.IsNode;
714                 case XmlTypeCode.Node: return item.IsNode;
715                 case XmlTypeCode.Item: return true;
716                 default:
717                     if (!item.IsNode)
718                         return false;
719
720                     switch (((XPathNavigator) item).NodeType) {
721                         case XPathNodeType.Root: return code == XmlTypeCode.Document;
722                         case XPathNodeType.Element: return code == XmlTypeCode.Element;
723                         case XPathNodeType.Attribute: return code == XmlTypeCode.Attribute;
724                         case XPathNodeType.Namespace: return code == XmlTypeCode.Namespace;
725                         case XPathNodeType.Text: return code == XmlTypeCode.Text;
726                         case XPathNodeType.SignificantWhitespace: return code == XmlTypeCode.Text;
727                         case XPathNodeType.Whitespace: return code == XmlTypeCode.Text;
728                         case XPathNodeType.ProcessingInstruction: return code == XmlTypeCode.ProcessingInstruction;
729                         case XPathNodeType.Comment: return code == XmlTypeCode.Comment;
730                     }
731                     break;
732             }
733
734             Debug.Fail("XmlTypeCode " + code + " was not fully handled.");
735             return false;
736         }
737
738         /// <summary>
739         /// Create an XmlQueryType that represents the type of "item".
740         /// </summary>
741         private XmlQueryType CreateXmlType(XPathItem item) {
742             if (item.IsNode) {
743                 // Rtf
744                 RtfNavigator rtf = item as RtfNavigator;
745                 if (rtf != null)
746                     return XmlQueryTypeFactory.Node;
747
748                 // Node
749                 XPathNavigator nav = (XPathNavigator) item;
750                 switch (nav.NodeType) {
751                     case XPathNodeType.Root:
752                     case XPathNodeType.Element:
753                         if (nav.XmlType == null)
754                             return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), XmlSchemaComplexType.UntypedAnyType, false);
755
756                         return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), nav.XmlType, nav.SchemaInfo.SchemaElement.IsNillable);
757
758                     case XPathNodeType.Attribute:
759                         if (nav.XmlType == null)
760                             return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), DatatypeImplementation.UntypedAtomicType, false);
761
762                         return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), nav.XmlType, false);
763                 }
764
765                 return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.Wildcard, XmlSchemaComplexType.AnyType, false);
766             }
767
768             // Atomic value
769             return XmlQueryTypeFactory.Type((XmlSchemaSimpleType)item.XmlType, true);
770         }
771
772
773         //-----------------------------------------------
774         // Xml collations
775         //-----------------------------------------------
776
777         /// <summary>
778         /// Get a collation that was statically created.
779         /// </summary>
780         public XmlCollation GetCollation(int index) {
781             Debug.Assert(this.collations != null);
782             return this.collations[index];
783         }
784
785         /// <summary>
786         /// Create a collation from a string.
787         /// </summary>
788         public XmlCollation CreateCollation(string collation) {
789             return XmlCollation.Create(collation);
790         }
791
792
793         //-----------------------------------------------
794         // Document Ordering and Identity
795         //-----------------------------------------------
796
797         /// <summary>
798         /// Compare the relative positions of two navigators.  Return -1 if navThis is before navThat, 1 if after, and
799         /// 0 if they are positioned to the same node.
800         /// </summary>
801         public int ComparePosition(XPathNavigator navigatorThis, XPathNavigator navigatorThat) {
802             return this.docOrderCmp.Compare(navigatorThis, navigatorThat);
803         }
804
805         /// <summary>
806         /// Get a comparer which guarantees a stable ordering among nodes, even those from different documents.
807         /// </summary>
808         public IList<XPathNavigator> DocOrderDistinct(IList<XPathNavigator> seq) {
809             if (seq.Count <= 1)
810                 return seq;
811
812             XmlQueryNodeSequence nodeSeq = (XmlQueryNodeSequence) seq;
813             if (nodeSeq == null)
814                 nodeSeq = new XmlQueryNodeSequence(seq);
815
816             return nodeSeq.DocOrderDistinct(this.docOrderCmp);
817         }
818
819         /// <summary>
820         /// Generate a unique string identifier for the specified node.  Do this by asking the navigator for an identifier
821         /// that is unique within the document, and then prepend a document index.
822         /// </summary>
823         public string GenerateId(XPathNavigator navigator) {
824             return string.Concat("ID", this.docOrderCmp.GetDocumentIndex(navigator).ToString(CultureInfo.InvariantCulture), navigator.UniqueId);
825         }
826
827
828         //-----------------------------------------------
829         // Indexes
830         //-----------------------------------------------
831
832         /// <summary>
833         /// If an index having the specified Id has already been created over the "context" document, then return it
834         /// in "index" and return true.  Otherwise, create a new, empty index and return false.
835         /// </summary>
836         public bool FindIndex(XPathNavigator context, int indexId, out XmlILIndex index) {
837             XPathNavigator navRoot;
838             ArrayList docIndexes;
839             Debug.Assert(context != null);
840
841             // Get root of document
842             navRoot = context.Clone();
843             navRoot.MoveToRoot();
844
845             // Search pre-existing indexes in order to determine whether the specified index has already been created
846             if (this.indexes != null && indexId < this.indexes.Length) {
847                 docIndexes = (ArrayList) this.indexes[indexId];
848                 if (docIndexes != null) {
849                     // Search for an index defined over the specified document
850                     for (int i = 0; i < docIndexes.Count; i += 2) {
851                         // If we find a matching document, then return the index saved in the next slot
852                         if (((XPathNavigator) docIndexes[i]).IsSamePosition(navRoot)) {
853                             index = (XmlILIndex) docIndexes[i + 1];
854                             return true;
855                         }
856                     }
857                 }
858             }
859
860             // Return a new, empty index
861             index = new XmlILIndex();
862             return false;
863         }
864
865         /// <summary>
866         /// Add a newly built index over the specified "context" document to the existing collection of indexes.
867         /// </summary>
868         public void AddNewIndex(XPathNavigator context, int indexId, XmlILIndex index) {
869             XPathNavigator navRoot;
870             ArrayList docIndexes;
871             Debug.Assert(context != null);
872
873             // Get root of document
874             navRoot = context.Clone();
875             navRoot.MoveToRoot();
876
877             // Ensure that a slot exists for the new index
878             if (this.indexes == null) {
879                 this.indexes = new ArrayList[indexId + 4];
880             }
881             else if (indexId >= this.indexes.Length) {
882                 // Resize array
883                 ArrayList[] indexesNew = new ArrayList[indexId + 4];
884                 Array.Copy(this.indexes, 0, indexesNew, 0, this.indexes.Length);
885                 this.indexes = indexesNew;
886             }
887
888             docIndexes = (ArrayList) this.indexes[indexId];
889             if (docIndexes == null) {
890                 docIndexes = new ArrayList();
891                 this.indexes[indexId] = docIndexes;
892             }
893
894             docIndexes.Add(navRoot);
895             docIndexes.Add(index);
896         }
897
898
899         //-----------------------------------------------
900         // Output construction
901         //-----------------------------------------------
902
903         /// <summary>
904         /// Get output writer object.
905         /// </summary>
906         public XmlQueryOutput Output {
907             get { return this.output; }
908         }
909
910         /// <summary>
911         /// Start construction of a nested sequence of items. Return a new XmlQueryOutput that will be
912         /// used to construct this new sequence.
913         /// </summary>
914         public void StartSequenceConstruction(out XmlQueryOutput output) {
915             // Push current writer
916             this.stkOutput.Push(this.output);
917
918             // Create new writers
919             output = this.output = new XmlQueryOutput(this, new XmlCachedSequenceWriter());
920         }
921
922         /// <summary>
923         /// End construction of a nested sequence of items and return the items as an IList<XPathItem>
924         /// internal class.  Return previous XmlQueryOutput.
925         /// </summary>
926         public IList<XPathItem> EndSequenceConstruction(out XmlQueryOutput output) {
927             IList<XPathItem> seq = ((XmlCachedSequenceWriter) this.output.SequenceWriter).ResultSequence;
928
929             // Restore previous XmlQueryOutput
930             output = this.output = this.stkOutput.Pop();
931
932             return seq;
933         }
934
935         /// <summary>
936         /// Start construction of an Rtf. Return a new XmlQueryOutput object that will be used to construct this Rtf.
937         /// </summary>
938         public void StartRtfConstruction(string baseUri, out XmlQueryOutput output) {
939             // Push current writer
940             this.stkOutput.Push(this.output);
941
942             // Create new XmlQueryOutput over an Rtf writer
943             output = this.output = new XmlQueryOutput(this, new XmlEventCache(baseUri, true));
944         }
945
946         /// <summary>
947         /// End construction of an Rtf and return it as an RtfNavigator.  Return previous XmlQueryOutput object.
948         /// </summary>
949         public XPathNavigator EndRtfConstruction(out XmlQueryOutput output) {
950             XmlEventCache events;
951
952             events = (XmlEventCache) this.output.Writer;
953
954             // Restore previous XmlQueryOutput
955             output = this.output = this.stkOutput.Pop();
956
957             // Return Rtf as an RtfNavigator
958             events.EndEvents();
959             return new RtfTreeNavigator(events, this.nameTableQuery);
960         }
961
962         /// <summary>
963         /// Construct a new RtfTextNavigator from the specified "text".  This is much more efficient than calling
964         /// StartNodeConstruction(), StartRtf(), WriteString(), EndRtf(), and EndNodeConstruction().
965         /// </summary>
966         public XPathNavigator TextRtfConstruction(string text, string baseUri) {
967             return new RtfTextNavigator(text, baseUri);
968         }
969
970
971         //-----------------------------------------------
972         // Miscellaneous
973         //-----------------------------------------------
974
975         /// <summary>
976         /// Report query execution information to event handler.
977         /// </summary>
978         public void SendMessage(string message) {
979             this.ctxt.OnXsltMessageEncountered(message);
980         }
981
982         /// <summary>
983         /// Throw an Xml exception having the specified message text.
984         /// </summary>
985         public void ThrowException(string text) {
986             throw new XslTransformException(text);
987         }
988
989         /// <summary>
990         /// Position navThis to the same location as navThat.
991         /// </summary>
992         internal static XPathNavigator SyncToNavigator(XPathNavigator navigatorThis, XPathNavigator navigatorThat) {
993             if (navigatorThis == null || !navigatorThis.MoveTo(navigatorThat))
994                 return navigatorThat.Clone();
995
996             return navigatorThis;
997         }
998
999         /// <summary>
1000         /// Function is called in Debug mode on each time context node change.
1001         /// </summary>
1002         public static int OnCurrentNodeChanged(XPathNavigator currentNode) {
1003             IXmlLineInfo lineInfo = currentNode as IXmlLineInfo;
1004
1005             // In case of a namespace node, check whether it is inherited or locally defined
1006             if (lineInfo != null && ! (currentNode.NodeType == XPathNodeType.Namespace && IsInheritedNamespace(currentNode))) {
1007                 OnCurrentNodeChanged2(currentNode.BaseURI, lineInfo.LineNumber, lineInfo.LinePosition);
1008             }
1009             return 0;
1010         }
1011
1012         // 'true' if current Namespace "inherited" from it's parent. Not defined localy.
1013         private static bool IsInheritedNamespace(XPathNavigator node) {
1014             Debug.Assert(node.NodeType == XPathNodeType.Namespace);
1015             XPathNavigator nav = node.Clone();
1016             if (nav.MoveToParent()) {
1017                 if (nav.MoveToFirstNamespace(XPathNamespaceScope.Local)) {
1018                     do {
1019                         if ((object)nav.LocalName == (object)node.LocalName) {
1020                             return false;
1021                         }
1022                     } while (nav.MoveToNextNamespace(XPathNamespaceScope.Local));
1023                 }
1024             }
1025             return true;
1026         }
1027
1028
1029         private static void OnCurrentNodeChanged2(string baseUri, int lineNumber, int linePosition) {}
1030     }
1031 }