1 //------------------------------------------------------------------------------
2 // <copyright file="XmlQueryRuntime.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
10 using System.Xml.XPath;
11 using System.Xml.Schema;
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.Diagnostics;
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;
25 namespace System.Xml.Xsl.Runtime {
26 using Res = System.Xml.Utils.Res;
29 /// XmlQueryRuntime is passed as the first parameter to all generated query methods.
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
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;
44 // Global variables and parameters
45 private string[] globalNames;
46 private object[] globalValues;
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)
55 private XmlQueryType[] types;
58 private XmlCollation[] collations;
61 private DocumentOrderComparer docOrderCmp;
64 private ArrayList[] indexes;
66 // Output construction
67 private XmlQueryOutput output;
68 private Stack<XmlQueryOutput> stkOutput;
71 //-----------------------------------------------
73 //-----------------------------------------------
76 /// This constructor is internal so that external users cannot construct it (and therefore we do not have to test it separately).
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;
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);
91 this.earlyInfo = data.EarlyBound;
92 this.earlyObjects = (this.earlyInfo != null) ? new object[earlyInfo.Length] : null;
94 // Global variables and parameters
95 this.globalNames = data.GlobalNames;
96 this.globalValues = (this.globalNames != null) ? new object[this.globalNames.Length] : null;
99 this.nameTableQuery = this.ctxt.QueryNameTable;
100 this.atomizedNames = 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];
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]);
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]);
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];
130 for (i = 0; i < filters.Length; i++)
131 this.filters[i] = XmlNavNameFilter.Create(this.atomizedNames[filters[i].Left], this.atomizedNames[filters[i].Right]);
134 // Prefix maping lists
135 this.prefixMappingsList = data.PrefixMappingsList;
138 this.types = data.Types;
141 this.collations = data.Collations;
144 this.docOrderCmp = new DocumentOrderComparer();
149 // Output construction
150 this.stkOutput = new Stack<XmlQueryOutput>(16);
151 this.output = new XmlQueryOutput(this, seqWrt);
155 //-----------------------------------------------
156 // Debugger Utility Methods
157 //-----------------------------------------------
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
163 public string[] DebugGetGlobalNames() {
164 return this.globalNames;
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.
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];
183 /// Set the value of a global value having the specified name. If there is no such value, this method is a no-op.
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.");
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);
199 /// Convert sequence to it's appropriate XSLT type and return to caller.
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;
207 else if (item is RtfNavigator) {
208 return ((RtfNavigator) item).ToNavigator();
216 //-----------------------------------------------
217 // Early-Bound Library Objects
218 //-----------------------------------------------
220 internal const BindingFlags EarlyBoundFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
221 internal const BindingFlags LateBoundFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
224 /// Return the object that manages external user context information such as data sources, parameters, extension objects, etc.
226 public XmlQueryContext ExternalContext {
227 get { return this.ctxt; }
231 /// Return the object that manages the state needed to implement various Xslt functions.
233 public XsltLibrary XsltFunctions {
235 if (this.xsltLib == null) {
236 this.xsltLib = new XsltLibrary(this);
244 /// Get the early-bound extension object identified by "index". If it does not yet exist, create an instance using the
245 /// corresponding ConstructorInfo.
247 public object GetEarlyBoundObject(int index) {
249 Debug.Assert(this.earlyObjects != null && index < this.earlyObjects.Length, "Early bound object does not exist");
251 obj = this.earlyObjects[index];
253 // Early-bound object does not yet exist, so create it now
254 obj = this.earlyInfo[index].CreateObject();
255 this.earlyObjects[index] = obj;
262 /// Return true if the early bound object identified by "namespaceUri" contains a method that matches "name".
264 public bool EarlyBoundFunctionExists(string name, string namespaceUri) {
265 if (this.earlyInfo == null)
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();
277 //-----------------------------------------------
278 // Global variables and parameters
279 //-----------------------------------------------
282 /// Return true if the global value specified by idxValue was previously computed.
284 public bool IsGlobalComputed(int index) {
285 return this.globalValues[index] != null;
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.
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];
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.
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;
307 //-----------------------------------------------
308 // Names, prefix mappings, and name filters
309 //-----------------------------------------------
312 /// Return the name table used to atomize all names used by the query.
314 public XmlNameTable NameTable {
315 get { return this.nameTableQuery; }
319 /// Get the atomized name at the specified index in the array of names.
321 public string GetAtomizedName(int index) {
322 Debug.Assert(this.atomizedNames != null);
323 return this.atomizedNames[index];
327 /// Get the name filter at the specified index in the array of filters.
329 public XmlNavigatorFilter GetNameFilter(int index) {
330 Debug.Assert(this.filters != null);
331 return this.filters[index];
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
340 public XmlNavigatorFilter GetTypeFilter(XPathNodeType nodeType) {
341 if (nodeType == XPathNodeType.All)
342 return XmlNavNeverFilter.Create();
344 if (nodeType == XPathNodeType.Attribute)
345 return XmlNavAttrFilter.Create();
347 return XmlNavTypeFilter.Create(nodeType);
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.
354 public XmlQualifiedName ParseTagName(string tagName, int indexPrefixMappings) {
355 string prefix, localName, ns;
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);
363 /// Parse the specified tag name (foo:bar). Return an XmlQualifiedName consisting of the parsed local name
364 /// and the specified namespace.
366 public XmlQualifiedName ParseTagName(string tagName, string ns) {
367 string prefix, localName;
369 // Parse the tagName as a prefix, localName pair
370 ValidateNames.ParseQNameThrow(tagName, out prefix, out localName);
371 return new XmlQualifiedName(localName, ns);
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.
378 internal void ParseTagName(string tagName, int idxPrefixMappings, out string prefix, out string localName, out string ns) {
379 Debug.Assert(this.prefixMappingsList != null);
381 // Parse the tagName as a prefix, localName pair
382 ValidateNames.ParseQNameThrow(tagName, out prefix, out localName);
384 // Map the prefix to a namespace URI
386 foreach (StringPair pair in this.prefixMappingsList[idxPrefixMappings]) {
387 if (prefix == pair.Left) {
393 // Throw exception if prefix could not be resolved
395 // Check for mappings that are always in-scope
396 if (prefix.Length == 0)
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;
404 throw new XslTransformException(Res.Xslt_InvalidPrefix, prefix);
409 /// Return true if the nav1's LocalName and NamespaceURI properties equal nav2's corresponding properties.
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;
417 return (n1.LocalName == n2.LocalName) && (n1.NamespaceURI == n2.NamespaceURI);
421 /// Return true if the specified navigator's LocalName and NamespaceURI properties equal the argument names.
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);
430 // Use string comparison
431 return (GetAtomizedName(indexLocalName) == navigator.LocalName) && (GetAtomizedName(indexNamespaceUri) == navigator.NamespaceURI);
435 //-----------------------------------------------
437 //-----------------------------------------------
440 /// Get the array of xml types that are used within this query.
442 internal XmlQueryType[] XmlTypes {
443 get { return this.types; }
447 /// Get the Xml query type at the specified index in the array of types.
449 internal XmlQueryType GetXmlType(int idxType) {
450 Debug.Assert(this.types != null);
451 return this.types[idxType];
455 /// Forward call to ChangeTypeXsltArgument(XmlQueryType, object, Type).
457 public object ChangeTypeXsltArgument(int indexType, object value, Type destinationType) {
458 return ChangeTypeXsltArgument(GetXmlType(indexType), value, destinationType);
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.
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);
471 switch (xmlType.TypeCode) {
472 case XmlTypeCode.String:
473 if (destinationType == XsltConvert.DateTimeType)
474 value = XsltConvert.ToDateTime((string) value);
477 case XmlTypeCode.Double:
478 if (destinationType != XsltConvert.DoubleType)
479 value = Convert.ChangeType(value, destinationType, CultureInfo.InvariantCulture);
482 case XmlTypeCode.Node:
483 Debug.Assert(xmlType != XmlQueryTypeFactory.Node && xmlType != XmlQueryTypeFactory.NodeS,
484 "Rtf values should have been eliminated by caller.");
486 if (destinationType == XsltConvert.XPathNodeIteratorType) {
487 value = new XPathArrayIterator((IList) value);
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];
494 for (int i = 0; i < seq.Count; i++)
495 navArray[i] = seq[i];
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);
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];
516 RtfNavigator rtf = item as RtfNavigator;
518 value = rtf.ToNavigator();
520 value = new XPathArrayIterator((IList) value);
524 value = item.TypedValue;
529 value = new XPathArrayIterator((IList) value);
535 Debug.Assert(destinationType.IsAssignableFrom(value.GetType()), "ChangeType from type " + value.GetType().Name + " to type " + destinationType.Name + " failed");
540 /// Forward call to ChangeTypeXsltResult(XmlQueryType, object)
542 public object ChangeTypeXsltResult(int indexType, object value) {
543 return ChangeTypeXsltResult(GetXmlType(indexType), value);
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.
550 internal object ChangeTypeXsltResult(XmlQueryType xmlType, object value) {
552 throw new XslTransformException(Res.Xslt_ItemNull, string.Empty);
554 switch (xmlType.TypeCode) {
555 case XmlTypeCode.String:
556 if (value.GetType() == XsltConvert.DateTimeType)
557 value = XsltConvert.ToString((DateTime) value);
560 case XmlTypeCode.Double:
561 if (value.GetType() != XsltConvert.DoubleType)
562 value = ((IConvertible) value).ToDouble(null);
566 case XmlTypeCode.Node:
567 if (!xmlType.IsSingleton) {
568 XPathArrayIterator iter = value as XPathArrayIterator;
570 // Special-case XPathArrayIterator in order to avoid copies
571 if (iter != null && iter.AsList is XmlQueryNodeSequence) {
572 value = iter.AsList as XmlQueryNodeSequence;
575 // Iterate over list and ensure it only contains nodes
576 XmlQueryNodeSequence seq = new XmlQueryNodeSequence();
577 IList list = value as IList;
580 for (int i = 0; i < list.Count; i++)
581 seq.Add(EnsureNavigator(list[i]));
584 foreach (object o in (IEnumerable) value)
585 seq.Add(EnsureNavigator(o));
591 // Always sort node-set by document order
592 value = ((XmlQueryNodeSequence) value).DocOrderDistinct(this.docOrderCmp);
596 case XmlTypeCode.Item: {
597 Type sourceType = value.GetType();
598 IXPathNavigable navigable;
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));
606 case XmlTypeCode.Double:
607 value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Double), ((IConvertible) value).ToDouble(null)));
610 case XmlTypeCode.String:
611 if (sourceType == XsltConvert.DateTimeType)
612 value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), XsltConvert.ToString((DateTime) value)));
614 value = new XmlQueryItemSequence(new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), value));
617 case XmlTypeCode.Node:
618 // Support XPathNavigator[]
619 value = ChangeTypeXsltResult(XmlQueryTypeFactory.NodeS, value);
622 case XmlTypeCode.Item:
623 // Support XPathNodeIterator
624 if (value is XPathNodeIterator) {
625 value = ChangeTypeXsltResult(XmlQueryTypeFactory.NodeS, value);
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);
635 value = new XmlQueryNodeSequence(navigable.CreateNavigator());
639 throw new XslTransformException(Res.Xslt_UnsupportedClrType, sourceType.Name);
645 Debug.Assert(XmlILTypeHelper.GetStorageType(xmlType).IsAssignableFrom(value.GetType()), "Xml type " + xmlType + " is not represented in ILGen as " + value.GetType().Name);
650 /// Ensure that "value" is a navigator and not null.
652 private static XPathNavigator EnsureNavigator(object value) {
653 XPathNavigator nav = value as XPathNavigator;
656 throw new XslTransformException(Res.Xslt_ItemNull, string.Empty);
662 /// Return true if the type of every item in "seq" matches the xml type identified by "idxType".
664 public bool MatchesXmlType(IList<XPathItem> seq, int indexType) {
665 XmlQueryType typBase = GetXmlType(indexType);
666 XmlQueryCardinality card;
669 case 0: card = XmlQueryCardinality.Zero; break;
670 case 1: card = XmlQueryCardinality.One; break;
671 default: card = XmlQueryCardinality.More; break;
674 if (!(card <= typBase.Cardinality))
677 typBase = typBase.Prime;
678 for (int i = 0; i < seq.Count; i++) {
679 if (!CreateXmlType(seq[0]).IsSubtypeOf(typBase))
687 /// Return true if the type of "item" matches the xml type identified by "idxType".
689 public bool MatchesXmlType(XPathItem item, int indexType) {
690 return CreateXmlType(item).IsSubtypeOf(GetXmlType(indexType));
694 /// Return true if the type of "seq" is a subtype of a singleton type identified by "code".
696 public bool MatchesXmlType(IList<XPathItem> seq, XmlTypeCode code) {
700 return MatchesXmlType(seq[0], code);
704 /// Return true if the type of "item" is a subtype of the type identified by "code".
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;
711 // Handle node code and AnyAtomicType
713 case XmlTypeCode.AnyAtomicType: return !item.IsNode;
714 case XmlTypeCode.Node: return item.IsNode;
715 case XmlTypeCode.Item: return true;
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;
734 Debug.Fail("XmlTypeCode " + code + " was not fully handled.");
739 /// Create an XmlQueryType that represents the type of "item".
741 private XmlQueryType CreateXmlType(XPathItem item) {
744 RtfNavigator rtf = item as RtfNavigator;
746 return XmlQueryTypeFactory.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);
756 return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), nav.XmlType, nav.SchemaInfo.SchemaElement.IsNillable);
758 case XPathNodeType.Attribute:
759 if (nav.XmlType == null)
760 return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), DatatypeImplementation.UntypedAtomicType, false);
762 return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.New(nav.LocalName, nav.NamespaceURI), nav.XmlType, false);
765 return XmlQueryTypeFactory.Type(nav.NodeType, XmlQualifiedNameTest.Wildcard, XmlSchemaComplexType.AnyType, false);
769 return XmlQueryTypeFactory.Type((XmlSchemaSimpleType)item.XmlType, true);
773 //-----------------------------------------------
775 //-----------------------------------------------
778 /// Get a collation that was statically created.
780 public XmlCollation GetCollation(int index) {
781 Debug.Assert(this.collations != null);
782 return this.collations[index];
786 /// Create a collation from a string.
788 public XmlCollation CreateCollation(string collation) {
789 return XmlCollation.Create(collation);
793 //-----------------------------------------------
794 // Document Ordering and Identity
795 //-----------------------------------------------
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.
801 public int ComparePosition(XPathNavigator navigatorThis, XPathNavigator navigatorThat) {
802 return this.docOrderCmp.Compare(navigatorThis, navigatorThat);
806 /// Get a comparer which guarantees a stable ordering among nodes, even those from different documents.
808 public IList<XPathNavigator> DocOrderDistinct(IList<XPathNavigator> seq) {
812 XmlQueryNodeSequence nodeSeq = (XmlQueryNodeSequence) seq;
814 nodeSeq = new XmlQueryNodeSequence(seq);
816 return nodeSeq.DocOrderDistinct(this.docOrderCmp);
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.
823 public string GenerateId(XPathNavigator navigator) {
824 return string.Concat("ID", this.docOrderCmp.GetDocumentIndex(navigator).ToString(CultureInfo.InvariantCulture), navigator.UniqueId);
828 //-----------------------------------------------
830 //-----------------------------------------------
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.
836 public bool FindIndex(XPathNavigator context, int indexId, out XmlILIndex index) {
837 XPathNavigator navRoot;
838 ArrayList docIndexes;
839 Debug.Assert(context != null);
841 // Get root of document
842 navRoot = context.Clone();
843 navRoot.MoveToRoot();
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];
860 // Return a new, empty index
861 index = new XmlILIndex();
866 /// Add a newly built index over the specified "context" document to the existing collection of indexes.
868 public void AddNewIndex(XPathNavigator context, int indexId, XmlILIndex index) {
869 XPathNavigator navRoot;
870 ArrayList docIndexes;
871 Debug.Assert(context != null);
873 // Get root of document
874 navRoot = context.Clone();
875 navRoot.MoveToRoot();
877 // Ensure that a slot exists for the new index
878 if (this.indexes == null) {
879 this.indexes = new ArrayList[indexId + 4];
881 else if (indexId >= this.indexes.Length) {
883 ArrayList[] indexesNew = new ArrayList[indexId + 4];
884 Array.Copy(this.indexes, 0, indexesNew, 0, this.indexes.Length);
885 this.indexes = indexesNew;
888 docIndexes = (ArrayList) this.indexes[indexId];
889 if (docIndexes == null) {
890 docIndexes = new ArrayList();
891 this.indexes[indexId] = docIndexes;
894 docIndexes.Add(navRoot);
895 docIndexes.Add(index);
899 //-----------------------------------------------
900 // Output construction
901 //-----------------------------------------------
904 /// Get output writer object.
906 public XmlQueryOutput Output {
907 get { return this.output; }
911 /// Start construction of a nested sequence of items. Return a new XmlQueryOutput that will be
912 /// used to construct this new sequence.
914 public void StartSequenceConstruction(out XmlQueryOutput output) {
915 // Push current writer
916 this.stkOutput.Push(this.output);
918 // Create new writers
919 output = this.output = new XmlQueryOutput(this, new XmlCachedSequenceWriter());
923 /// End construction of a nested sequence of items and return the items as an IList<XPathItem>
924 /// internal class. Return previous XmlQueryOutput.
926 public IList<XPathItem> EndSequenceConstruction(out XmlQueryOutput output) {
927 IList<XPathItem> seq = ((XmlCachedSequenceWriter) this.output.SequenceWriter).ResultSequence;
929 // Restore previous XmlQueryOutput
930 output = this.output = this.stkOutput.Pop();
936 /// Start construction of an Rtf. Return a new XmlQueryOutput object that will be used to construct this Rtf.
938 public void StartRtfConstruction(string baseUri, out XmlQueryOutput output) {
939 // Push current writer
940 this.stkOutput.Push(this.output);
942 // Create new XmlQueryOutput over an Rtf writer
943 output = this.output = new XmlQueryOutput(this, new XmlEventCache(baseUri, true));
947 /// End construction of an Rtf and return it as an RtfNavigator. Return previous XmlQueryOutput object.
949 public XPathNavigator EndRtfConstruction(out XmlQueryOutput output) {
950 XmlEventCache events;
952 events = (XmlEventCache) this.output.Writer;
954 // Restore previous XmlQueryOutput
955 output = this.output = this.stkOutput.Pop();
957 // Return Rtf as an RtfNavigator
959 return new RtfTreeNavigator(events, this.nameTableQuery);
963 /// Construct a new RtfTextNavigator from the specified "text". This is much more efficient than calling
964 /// StartNodeConstruction(), StartRtf(), WriteString(), EndRtf(), and EndNodeConstruction().
966 public XPathNavigator TextRtfConstruction(string text, string baseUri) {
967 return new RtfTextNavigator(text, baseUri);
971 //-----------------------------------------------
973 //-----------------------------------------------
976 /// Report query execution information to event handler.
978 public void SendMessage(string message) {
979 this.ctxt.OnXsltMessageEncountered(message);
983 /// Throw an Xml exception having the specified message text.
985 public void ThrowException(string text) {
986 throw new XslTransformException(text);
990 /// Position navThis to the same location as navThat.
992 internal static XPathNavigator SyncToNavigator(XPathNavigator navigatorThis, XPathNavigator navigatorThat) {
993 if (navigatorThis == null || !navigatorThis.MoveTo(navigatorThat))
994 return navigatorThat.Clone();
996 return navigatorThis;
1000 /// Function is called in Debug mode on each time context node change.
1002 public static int OnCurrentNodeChanged(XPathNavigator currentNode) {
1003 IXmlLineInfo lineInfo = currentNode as IXmlLineInfo;
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);
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)) {
1019 if ((object)nav.LocalName == (object)node.LocalName) {
1022 } while (nav.MoveToNextNamespace(XPathNamespaceScope.Local));
1029 private static void OnCurrentNodeChanged2(string baseUri, int lineNumber, int linePosition) {}