Project

Profile

Help

StaticContext.Processor in SaxonCS ExtensionFunctionCall: only available in XPath but not XSLT or XQuery?

Added by Martin Honnen over 1 year ago

With SaxonCS 11.4.1, I have some extension functions that need to access the current Processor object in its Call method, I thought the way to do that would be by storing e.g.

    public class GrammarUriFunctionCall : ExtensionFunctionCall
    {
        Processor processor;
        public override void SupplyStaticContext(StaticContext context)
        {
            base.SupplyStaticContext(context);
            processor = context.Processor;
        }

...

but when I do that my extension function works fine in XPath but not in XQuery or XSLT, there I am told processor is null.

System.NullReferenceException
  HResult=0x80004003
  Nachricht = Object reference not set to an instance of an object.
  Quelle = CoffeeSacksNETLib
  Stapelüberwachung:
   bei CoffeeSacksNETLib.GrammarUriFunctionCall.Call(XdmValue[] arguments, DynamicContext context) in C:\Users\marti\source\repos\CoffeeSacksNETLib\CoffeeSacksNETLib\GrammarUriFunctionCall.cs: Zeile16

The DynamicContext https://www.saxonica.com/html/documentation11/dotnetdoc/Saxon/Api/DynamicContext.html doesn't have a Processor property and its Implementation leads to lower level Saxon APIs. Is the above null expected for XSLT/XQuery?

What is the right way in an extension function called from XQuery or XSLT to access the Processor in SaxonCS?

using Saxon.Api;
using org.nineml.coffeefilter;

namespace CoffeeSacksNETLib
{
    public class GrammarUriFunctionCall : ExtensionFunctionCall
    {
        Processor processor;
        public override XdmValue Call(XdmValue[] arguments, DynamicContext context)
        {
            string uri = arguments[0][0].StringValue;
            InvisibleXmlParser parser = new InvisibleXml().getParser(new java.net.URI(uri));

            if (parser.constructed())
            {
                return processor.NewDocumentBuilder().Build(new StringReader(parser.getCompiledParser()));
            }
            else
            {
                return processor.NewDocumentBuilder().Build(new StringReader(parser.getFailedParse().getTree()));
            }
        }

        public override void SupplyStaticContext(StaticContext context)
        {
            base.SupplyStaticContext(context);
            processor = context.Processor;
        }
    }
}

Replies (7)

Please register to reply

RE: StaticContext.Processor in SaxonCS ExtensionFunctionCall: only available in XPath but not XSLT or XQuery? - Added by Martin Honnen over 1 year ago

Found a way to get at the Processor from the dynamic context:

            if (processor == null)
            {
                processor = context.Implementation.getConfiguration().getProcessor() as Processor;
            }

Still wondering why the first approach with the static context works with XPath only but not XSLT or XQuery.

RE: StaticContext.Processor in SaxonCS ExtensionFunctionCall: only available in XPath but not XSLT or XQuery? - Added by Michael Kay over 1 year ago

We've got a unit test that is doing this from XSLT and it's working just fine.

My guess would be that Configuration.getProcessor() for some reason is set to the underlying Saxon.Hej.a9api.Processor, in which case the StaticContext.Processor property will be returned as null. But I don't know why that would happen.

RE: StaticContext.Processor in SaxonCS ExtensionFunctionCall: only available in XPath but not XSLT or XQuery? - Added by Martin Honnen over 1 year ago

I have tried to isolate the problem in a small test project but there it didn't occur, the Processor from the StaticContext, captured in SupplyStaticContext, seemed to be available both in XPath and XQuery.

But now have tried to proceed with the main project and have also tried to store e.g.

        private Uri baseUri;

       public override void SupplyStaticContext(StaticContext context)
        {
            base.SupplyStaticContext(context);
            processor = context.Processor;
            baseUri = context.BaseUri;
        }

to use the baseUri field in the Call as well, to run into the same problem, now debugged in more details: the SupplyStaticContext runs and sets the processor and baseUri fields correctly but when the Call method is called it seems to be called on a different object instance which has the fields as null values.

Now on reading up on https://www.saxonica.com/html/documentation11/dotnetdoc/Saxon/Api/ExtensionFunctionDefinition.html#DependsOnFocus saying

It should also return true (despite the property name) if the function makes use of parts of the static context that vary from one part of the query or stylesheet to another. Setting the property to true inhibits certain Saxon optimizations, such as extracting the call from a loop, or moving it into a global variable.

and https://www.saxonica.com/html/documentation11/dotnetdoc/Saxon/Api/ExtensionFunctionCall.html#CopyLocalData(ExtensionFunctionCall) I guess I have to do some more work on the function.

RE: StaticContext.Processor in SaxonCS ExtensionFunctionCall: only available in XPath but not XSLT or XQuery? - Added by Martin Honnen over 1 year ago

I am now trying e.g.

        public override void CopyLocalData(ExtensionFunctionCall destination)
        {
            base.CopyLocalData(destination);
            var destinationCasted = (GrammarUriFunctionCall)destination;
            destinationCasted.baseUri = baseUri;
            destinationCasted.processor = processor;
        }

but a breakpoint inside of that CopyLocalData method is never hit while the Call method is run on a different instance than the one where SupplyStaticContext set the baseUri and processor fields.

I need to look at that with some rest, I guess, so far it looks as if I haven't understood what the functions are supposed to do.

RE: StaticContext.Processor in SaxonCS ExtensionFunctionCall: only available in XPath but not XSLT or XQuery? - Added by Martin Honnen over 1 year ago

I have now reproduced the problem in my smaller sample project and have a test case that fails, the processor is null:

System.NullReferenceException : Object reference not set to an instance of an object.

  Stapelüberwachung: 
Example1FunctionCall.Call(XdmValue[] arguments, DynamicContext context) Zeile 13
ExtensionFunctionCallWrapper.call(XPathContext context, Sequence[] arguments)
IntegratedFunctionCall.iterate(XPathContext context)
Expression.evaluateItem(XPathContext context)
SimpleStepExpression.iterate(XPathContext context)
FilterExpression.iterate(XPathContext context)
Expression.process(Outputter output, XPathContext context)
ElementCreator.processLeavingTail(Outputter out, XPathContext context, NodeInfo copiedNode)
ElementCreator.processLeavingTail(Outputter output, XPathContext context)
Instruction.process(Outputter output, XPathContext context)
ExpressionTool.getIteratorFromProcessMethod(Expression exp, XPathContext context)
Instruction.iterate(XPathContext context)
XQueryExpression.iterator(DynamicQueryContext env)
XQueryEvaluator.evaluateSingle()
XQueryEvaluator.EvaluateSingle()
Tests.XQueryTest3() Zeile 65

I need some advice whether the code of the extension function is still incomplete and that CopyLocalData is wrong or an additional function or property needs to be set or whether that is some quirk with SaxonCS.

Ignore the fact that the sample function is duplicating existing fn:parse-xml functionality, in my real use case I need the processor to create a DocumentBuilder and later the baseUri from the static context and can't access them with the values being null although the SupplyStaticContext initializes them and the CopyLocalData copies them (but doesn't seem to be called).

Test case:

using Saxon.Api;
using SaxonCSExtensionStaticContextTestLib;

namespace SaxonCSExtensionStaticContextTests
{
    public class Tests
    {
        static Processor processor;
        [SetUp]
        public void Setup()
        {
            processor = new();
            processor.RegisterExtensionFunction(new Example1Function());
        }

//[snip]

        [Test]
        public void XQueryTest3()
        {
            var xqueryCompiler = processor.NewXQueryCompiler();
            var xqueryCode = @"
declare namespace mf = 'http://example.com/mf';
let $doc1 := mf:parse-xml('<root><item>a</item><item>b</item><item>1</item></root>')
return 
  <root>{ $doc1//item[matches(., '\p{L}+')] }</root>
";
            var xquery = xqueryCompiler.Compile(xqueryCode).Load();
            var result = xquery.EvaluateSingle();
            Assert.IsInstanceOf<XdmNode>(result);
        }
    }
}

Example extension function code

using Saxon.Api;

namespace SaxonCSExtensionStaticContextTestLib
{
    public class Example1Function : ExtensionFunctionDefinition
    {
        public override QName FunctionName => new QName("http://example.com/mf", "parse-xml");

    public override int MinimumNumberOfArguments => 1;

    public override int MaximumNumberOfArguments => 1;

    public override XdmSequenceType[] ArgumentTypes => new XdmSequenceType[] {
            new XdmSequenceType(XdmAtomicType.BuiltInAtomicType(QName.XS_STRING), XdmSequenceType.ONE)
        };

    public override ExtensionFunctionCall MakeFunctionCall()
    {
        return new Example1FunctionCall();
    }

    public override XdmSequenceType ResultType(XdmSequenceType[] ArgumentTypes)
    {
        return new XdmSequenceType(XdmAnyNodeType.Instance, XdmSequenceType.ONE);
    }
}
}
using Saxon.Api;

namespace SaxonCSExtensionStaticContextTestLib
{
    public class Example1FunctionCall : ExtensionFunctionCall
    {

        Processor processor;
        public override XdmValue Call(XdmValue[] arguments, DynamicContext context)
        {
            string inputString = arguments[0][0].StringValue;

            return processor.NewDocumentBuilder().Build(new StringReader(inputString));
        }

        public override void CopyLocalData(ExtensionFunctionCall destination)
        {
            base.CopyLocalData(destination);
            var castedDestination = (Example1FunctionCall)destination;
            castedDestination.processor = processor;
        }

        public override void SupplyStaticContext(StaticContext context)
        {
            base.SupplyStaticContext(context);
            processor = context.Processor;
        }
    }
}

RE: StaticContext.Processor in SaxonCS ExtensionFunctionCall: only available in XPath but not XSLT or XQuery? - Added by Martin Honnen over 1 year ago

Further debugging of the sample .NET projects shows that the CopyLocalData doesn't seem to be called at all but the Call() call is done on a separate instance than the SupplyStaticContext() happened on.

I have tried a similar example with SaxonJ EE 11.4 and run it through the debugger, the the copyLocalData is called (and that way the code works as wanted).

Some current summary is that SaxonCS fails to call CopyLocalData after creating a new ExtensionFunctionCall of some concrete subclass implementation.

    (1-7/7)

    Please register to reply