| 
									
										
										
										
											2016-08-03 10:59:49 +00:00
										 |  |  | //------------------------------------------------------------------------------ | 
					
						
							|  |  |  | // <copyright file="XmlQueryContext.cs" company="Microsoft"> | 
					
						
							|  |  |  | //     Copyright (c) Microsoft Corporation.  All rights reserved. | 
					
						
							|  |  |  | // </copyright> | 
					
						
							| 
									
										
										
										
											2017-08-21 15:34:15 +00:00
										 |  |  | // <owner current="true" primary="true">Microsoft</owner> | 
					
						
							| 
									
										
										
										
											2016-08-03 10:59:49 +00:00
										 |  |  | //------------------------------------------------------------------------------ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | using System; | 
					
						
							|  |  |  | using System.Collections; | 
					
						
							|  |  |  | using System.Collections.Generic; | 
					
						
							|  |  |  | using System.ComponentModel; | 
					
						
							|  |  |  | using System.Diagnostics; | 
					
						
							|  |  |  | using System.IO; | 
					
						
							|  |  |  | using System.Xml; | 
					
						
							|  |  |  | using System.Xml.Schema; | 
					
						
							|  |  |  | using System.Xml.XPath; | 
					
						
							|  |  |  | using System.Runtime.Versioning; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace System.Xml.Xsl.Runtime { | 
					
						
							|  |  |  |     using Res = System.Xml.Utils.Res; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /// <summary> | 
					
						
							|  |  |  |     /// The context of a query consists of all user-provided information which influences the operation of the | 
					
						
							|  |  |  |     /// query. The context manages the following information: | 
					
						
							|  |  |  |     /// | 
					
						
							|  |  |  |     ///   1. Input data sources, including the default data source if one exists | 
					
						
							|  |  |  |     ///   2. Extension objects | 
					
						
							|  |  |  |     ///   3. External parameters | 
					
						
							|  |  |  |     /// </summary> | 
					
						
							|  |  |  |     [EditorBrowsable(EditorBrowsableState.Never)] | 
					
						
							|  |  |  |     public sealed class XmlQueryContext { | 
					
						
							|  |  |  |         private XmlQueryRuntime runtime; | 
					
						
							|  |  |  |         private XPathNavigator defaultDataSource; | 
					
						
							|  |  |  |         private XmlResolver dataSources; | 
					
						
							|  |  |  |         private Hashtable dataSourceCache; | 
					
						
							|  |  |  |         private XsltArgumentList argList; | 
					
						
							|  |  |  |         private XmlExtensionFunctionTable extFuncsLate; | 
					
						
							|  |  |  |         private WhitespaceRuleLookup wsRules; | 
					
						
							|  |  |  |         private QueryReaderSettings readerSettings; // If we create reader out of stream we will use these settings | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// This constructor is internal so that external users cannot construct it (and therefore we do not have to test it separately). | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         [ResourceConsumption(ResourceScope.Machine)] | 
					
						
							|  |  |  |         [ResourceExposure(ResourceScope.Machine)] | 
					
						
							|  |  |  |         internal XmlQueryContext(XmlQueryRuntime runtime, object defaultDataSource, XmlResolver dataSources, XsltArgumentList argList, WhitespaceRuleLookup wsRules) { | 
					
						
							|  |  |  |             this.runtime = runtime; | 
					
						
							|  |  |  |             this.dataSources = dataSources; | 
					
						
							|  |  |  |             this.dataSourceCache = new Hashtable(); | 
					
						
							|  |  |  |             this.argList = argList; | 
					
						
							|  |  |  |             this.wsRules = wsRules; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (defaultDataSource is XmlReader) { | 
					
						
							|  |  |  |                 this.readerSettings = new QueryReaderSettings((XmlReader) defaultDataSource); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else { | 
					
						
							|  |  |  |                 // Consider allowing users to set DefaultReaderSettings in XsltArgumentList | 
					
						
							|  |  |  |                 // readerSettings = argList.DefaultReaderSettings; | 
					
						
							|  |  |  |                 this.readerSettings = new QueryReaderSettings(new NameTable()); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (defaultDataSource is string) { | 
					
						
							|  |  |  |                 // Load the default document from a Uri | 
					
						
							|  |  |  |                 this.defaultDataSource = GetDataSource(defaultDataSource as string, null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (this.defaultDataSource == null) | 
					
						
							|  |  |  |                     throw new XslTransformException(Res.XmlIl_UnknownDocument, defaultDataSource as string); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else if (defaultDataSource != null) { | 
					
						
							|  |  |  |                 this.defaultDataSource = ConstructDocument(defaultDataSource, null, null); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         //----------------------------------------------- | 
					
						
							|  |  |  |         // Input data sources | 
					
						
							|  |  |  |         //----------------------------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Returns the name table that should be used in the query to atomize search names and to load | 
					
						
							|  |  |  |         /// new documents. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         public XmlNameTable QueryNameTable { | 
					
						
							|  |  |  |             get { return this.readerSettings.NameTable; } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Returns the name table used by the default data source, or null if there is no default data source. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         public XmlNameTable DefaultNameTable { | 
					
						
							|  |  |  |             get { return this.defaultDataSource != null ? this.defaultDataSource.NameTable : null; } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Return the document which is queried by default--i.e. no data source is explicitly selected in the query. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         public XPathNavigator DefaultDataSource { | 
					
						
							|  |  |  |             get { | 
					
						
							|  |  |  |                 // Throw exception if there is no default data source to return | 
					
						
							|  |  |  |                 if (this.defaultDataSource == null) | 
					
						
							|  |  |  |                     throw new XslTransformException(Res.XmlIl_NoDefaultDocument, string.Empty); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return this.defaultDataSource; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Fetch the data source specified by "uriRelative" and "uriBase" from the XmlResolver that the user provided. | 
					
						
							|  |  |  |         /// If the resolver returns a stream or reader, create an instance of XPathDocument.  If the resolver returns an | 
					
						
							|  |  |  |         /// XPathNavigator, return the navigator.  Throw an exception if no data source was found. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         [ResourceConsumption(ResourceScope.Machine)] | 
					
						
							|  |  |  |         [ResourceExposure(ResourceScope.Machine)] | 
					
						
							|  |  |  |         public XPathNavigator GetDataSource(string uriRelative, string uriBase) { | 
					
						
							|  |  |  |             object input; | 
					
						
							|  |  |  |             Uri uriResolvedBase, uriResolved; | 
					
						
							|  |  |  |             XPathNavigator nav = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 // If the data source has already been retrieved, then return the data source from the cache. | 
					
						
							|  |  |  |                 uriResolvedBase = (uriBase != null) ? this.dataSources.ResolveUri(null, uriBase) : null; | 
					
						
							|  |  |  |                 uriResolved = this.dataSources.ResolveUri(uriResolvedBase, uriRelative); | 
					
						
							|  |  |  |                 if (uriResolved != null) | 
					
						
							|  |  |  |                     nav = this.dataSourceCache[uriResolved] as XPathNavigator; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (nav == null) { | 
					
						
							|  |  |  |                     // Get the entity from the resolver and ensure it is cached as a document | 
					
						
							|  |  |  |                     input = this.dataSources.GetEntity(uriResolved, null, null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (input != null) { | 
					
						
							|  |  |  |                         // Construct a document from the entity and add the document to the cache | 
					
						
							|  |  |  |                         nav = ConstructDocument(input, uriRelative, uriResolved); | 
					
						
							|  |  |  |                         this.dataSourceCache.Add(uriResolved, nav); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             catch (XslTransformException) { | 
					
						
							|  |  |  |                 // Don't need to wrap XslTransformException | 
					
						
							|  |  |  |                 throw; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             catch (Exception e) { | 
					
						
							|  |  |  |                 if (!XmlException.IsCatchableException(e)) { | 
					
						
							|  |  |  |                     throw; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 throw new XslTransformException(e, Res.XmlIl_DocumentLoadError, uriRelative); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return nav; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Ensure that "dataSource" is cached as an XPathDocument and return a navigator over the document. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         private XPathNavigator ConstructDocument(object dataSource, string uriRelative, Uri uriResolved) { | 
					
						
							|  |  |  |             Debug.Assert(dataSource != null, "GetType() below assumes dataSource is not null"); | 
					
						
							|  |  |  |             Stream stream = dataSource as Stream; | 
					
						
							|  |  |  |             if (stream != null) { | 
					
						
							|  |  |  |                 // Create document from stream | 
					
						
							|  |  |  |                 XmlReader reader = readerSettings.CreateReader(stream, uriResolved != null ? uriResolved.ToString() : null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 try { | 
					
						
							|  |  |  |                     // Create WhitespaceRuleReader if whitespace should be stripped | 
					
						
							|  |  |  |                     return new XPathDocument(WhitespaceRuleReader.CreateReader(reader, this.wsRules), XmlSpace.Preserve).CreateNavigator(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 finally { | 
					
						
							|  |  |  |                     // Always close reader that was opened here | 
					
						
							|  |  |  |                     reader.Close(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else if (dataSource is XmlReader) { | 
					
						
							|  |  |  |                 // Create document from reader | 
					
						
							|  |  |  |                 // Create WhitespaceRuleReader if whitespace should be stripped | 
					
						
							|  |  |  |                 return new XPathDocument(WhitespaceRuleReader.CreateReader(dataSource as XmlReader, this.wsRules), XmlSpace.Preserve).CreateNavigator(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else if (dataSource is IXPathNavigable) { | 
					
						
							|  |  |  |                 if (this.wsRules != null) | 
					
						
							|  |  |  |                     throw new XslTransformException(Res.XmlIl_CantStripNav, string.Empty); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return (dataSource as IXPathNavigable).CreateNavigator(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             Debug.Assert(uriRelative != null, "Relative URI should not be null"); | 
					
						
							|  |  |  |             throw new XslTransformException(Res.XmlIl_CantResolveEntity, uriRelative, dataSource.GetType().ToString()); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         //----------------------------------------------- | 
					
						
							|  |  |  |         // External parameters | 
					
						
							|  |  |  |         //----------------------------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Get a named parameter from the external argument list.  Return null if no argument list was provided, or if | 
					
						
							|  |  |  |         /// there is no parameter by that name. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         public object GetParameter(string localName, string namespaceUri) { | 
					
						
							|  |  |  |             return (this.argList != null) ? this.argList.GetParam(localName, namespaceUri) : null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         //----------------------------------------------- | 
					
						
							|  |  |  |         // Extension objects | 
					
						
							|  |  |  |         //----------------------------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Return the extension object that is mapped to the specified namespace, or null if no object is mapped. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         public object GetLateBoundObject(string namespaceUri) { | 
					
						
							|  |  |  |             return (this.argList != null) ? this.argList.GetExtensionObject(namespaceUri) : null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Return true if the late bound object identified by "namespaceUri" contains a method that matches "name". | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         public bool LateBoundFunctionExists(string name, string namespaceUri) { | 
					
						
							|  |  |  |             object instance; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (this.argList == null) | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             instance = this.argList.GetExtensionObject(namespaceUri); | 
					
						
							|  |  |  |             if (instance == null) | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return new XmlExtensionFunction(name, namespaceUri, -1, instance.GetType(), XmlQueryRuntime.LateBoundFlags).CanBind(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Get a late-bound extension object from the external argument list.  Bind to a method on the object and invoke it, | 
					
						
							|  |  |  |         /// passing "args" as arguments. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         public IList<XPathItem> InvokeXsltLateBoundFunction(string name, string namespaceUri, IList<XPathItem>[] args) { | 
					
						
							|  |  |  |             object instance; | 
					
						
							|  |  |  |             object[] objActualArgs; | 
					
						
							|  |  |  |             XmlQueryType xmlTypeFormalArg; | 
					
						
							|  |  |  |             Type clrTypeFormalArg; | 
					
						
							|  |  |  |             object objRet; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Get external object instance from argument list (throw if either the list or the instance doesn't exist) | 
					
						
							|  |  |  |             instance = (this.argList != null) ? this.argList.GetExtensionObject(namespaceUri) : null; | 
					
						
							|  |  |  |             if (instance == null) | 
					
						
							|  |  |  |                 throw new XslTransformException(Res.XmlIl_UnknownExtObj, namespaceUri); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Bind to a method on the instance object | 
					
						
							|  |  |  |             if (this.extFuncsLate == null) | 
					
						
							|  |  |  |                 this.extFuncsLate = new XmlExtensionFunctionTable(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Bind to the instance, looking for a matching method (throws if no matching method) | 
					
						
							|  |  |  |             XmlExtensionFunction extFunc = this.extFuncsLate.Bind(name, namespaceUri, args.Length, instance.GetType(), XmlQueryRuntime.LateBoundFlags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Create array which will contain the actual arguments | 
					
						
							|  |  |  |             objActualArgs = new object[args.Length]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for (int i = 0; i < args.Length; i++) { | 
					
						
							|  |  |  |                 // 1. Assume that the input value can only have one of the following 5 Xslt types: | 
					
						
							|  |  |  |                 //      xs:double, xs:string, xs:boolean, node* (can be rtf) | 
					
						
							|  |  |  |                 // 2. Convert each Rtf value to a NodeSet containing one node.  Now the value may only have one of the 4 Xslt types. | 
					
						
							|  |  |  |                 // 3. Convert from one of the 4 Xslt internal types to the Xslt internal type which is closest to the formal | 
					
						
							|  |  |  |                 //    argument's Xml type (inferred from the Clr type of the formal argument). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 xmlTypeFormalArg = extFunc.GetXmlArgumentType(i); | 
					
						
							|  |  |  |                 switch (xmlTypeFormalArg.TypeCode) { | 
					
						
							|  |  |  |                     case XmlTypeCode.Boolean:   objActualArgs[i] = XsltConvert.ToBoolean(args[i]); break; | 
					
						
							|  |  |  |                     case XmlTypeCode.Double:    objActualArgs[i] = XsltConvert.ToDouble(args[i]); break; | 
					
						
							|  |  |  |                     case XmlTypeCode.String:    objActualArgs[i] = XsltConvert.ToString(args[i]); break; | 
					
						
							|  |  |  |                     case XmlTypeCode.Node: | 
					
						
							|  |  |  |                         if (xmlTypeFormalArg.IsSingleton) | 
					
						
							|  |  |  |                             objActualArgs[i] = XsltConvert.ToNode(args[i]); | 
					
						
							|  |  |  |                         else | 
					
						
							|  |  |  |                             objActualArgs[i] = XsltConvert.ToNodeSet(args[i]); | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     case XmlTypeCode.Item: | 
					
						
							|  |  |  |                         objActualArgs[i] = args[i]; | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     default: | 
					
						
							|  |  |  |                         Debug.Fail("This XmlTypeCode should never be inferred from a Clr type: " + xmlTypeFormalArg.TypeCode); | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // 4. Change the Clr representation to the Clr type of the formal argument | 
					
						
							|  |  |  |                 clrTypeFormalArg = extFunc.GetClrArgumentType(i); | 
					
						
							|  |  |  |                 if (xmlTypeFormalArg.TypeCode == XmlTypeCode.Item || !clrTypeFormalArg.IsAssignableFrom(objActualArgs[i].GetType())) | 
					
						
							|  |  |  |                     objActualArgs[i] = this.runtime.ChangeTypeXsltArgument(xmlTypeFormalArg, objActualArgs[i], clrTypeFormalArg); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // 1. Invoke the late bound method | 
					
						
							|  |  |  |             objRet = extFunc.Invoke(instance, objActualArgs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // 2. Convert to IList<XPathItem> | 
					
						
							|  |  |  |             if (objRet == null && extFunc.ClrReturnType == XsltConvert.VoidType) | 
					
						
							|  |  |  |                 return XmlQueryNodeSequence.Empty; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return (IList<XPathItem>) this.runtime.ChangeTypeXsltResult(XmlQueryTypeFactory.ItemS, objRet); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         //----------------------------------------------- | 
					
						
							|  |  |  |         // Event | 
					
						
							|  |  |  |         //----------------------------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /// <summary> | 
					
						
							|  |  |  |         /// Fire the XsltMessageEncounteredEvent, passing the specified text as the message. | 
					
						
							|  |  |  |         /// </summary> | 
					
						
							|  |  |  |         public void OnXsltMessageEncountered(string message) { | 
					
						
							|  |  |  |             XsltMessageEncounteredEventHandler onMessage = (this.argList != null) ? argList.xsltMessageEncountered : null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (onMessage != null) | 
					
						
							|  |  |  |                 onMessage(this, new XmlILQueryEventArgs(message)); | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 Console.WriteLine(message); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /// <summary> | 
					
						
							|  |  |  |     /// Simple implementation of XsltMessageEncounteredEventArgs. | 
					
						
							|  |  |  |     /// </summary> | 
					
						
							|  |  |  |     internal class XmlILQueryEventArgs : XsltMessageEncounteredEventArgs { | 
					
						
							|  |  |  |         private string message; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         public XmlILQueryEventArgs(string message) { | 
					
						
							|  |  |  |             this.message = message; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         public override string Message { | 
					
						
							|  |  |  |             get { return this.message; } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |