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; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|