fontoxpath icon indicating copy to clipboard operation
fontoxpath copied to clipboard

Evaluating xpath with context in a sequence

Open egh opened this issue 4 years ago • 5 comments
trafficstars

Hi,

I am not an xpath expert by any means, so please bear with me.

I am not sure how to get this expected result:

import { evaluateXPathToNodes, evaluateXPathToNumber } from "fontoxpath";
import { sync } from "slimdom-sax-parser";
test("node list context", () => {
  const xml = "<doc><item>item1</item><item>item2</item><item>item3</item></doc>";
  const dom = sync(xml);
  const nodeList = evaluateXPathToNodes("/doc/item", dom);
  expect(evaluateXPathToNumber("position()", nodeList[1])).toEqual(2);
});

Is there something that I am missing in understanding how xpath is expected to work? I would expect that there is some way to get position() to evaluate to 2 in this case, since it is the second item in the result set. I tried implementing a IDomFacade but that didn't seem to be used.

Is there any advice you can provide here? The code seems to include the idea of a contextSequence in the DynamicContext but I can't seem to pass that in to the evaluate xpath functions.

egh avatar Apr 08 '21 17:04 egh

Hello there!

Indeed, to make the XPath APIs easy to use, you can only input a singleton sequence as the start (parameter called contextItem). That is usually exactly what you want. In most cases, you have an element and you want to traverse a path or determine some property of that element. In your case, making this a single XPath query just works: /doc/item/position()[2] or something.

I think you have a question behind that question. Are you executing XPaths in multiple phases with the same context? Are you implementing some kind of spec? I think your question makes sense, I would love some more context so I can think along!

If you want to override the semantics of the position() function, we might have something to achieve that.

Regards,

Martin

DrRataplan avatar Apr 09 '21 07:04 DrRataplan

@DrRataplan Thanks for getting back to me! Indeed, there is some background: I was implementing a proof of concept XSLT processor in TypeScript (https://github.com/egh/xjslt).

So in this case, we might be iterating through a node set with for-each or apply-templates, and the template might be checking if this node was the last item in the set, for example.

Hope that helps! Thanks for the work.

egh avatar Apr 09 '21 19:04 egh

Awesome! I was wondering when someone would be brave enough to implement XSLT with FontoXPath.

When I tried to hack an XSLT together, I ran into a number of 'quirks'. For example, XSLT uses XPaths like <xslt:template match="p"> to match any paragraphs. Blindly compiling this to XPath would result in an XPath that traverses the child axis. Turns out XSLT does not really use XPath as the value of a match attribute, rather a syntax called XSL Patterns: https://www.w3.org/TR/xslt-30/#patterns. This is also why we use vanilla XPaths in Fonto instead: our template engine basically matches nodes to selectors that look like self::p.

For the semantics of a position() function, I think we could consider some new API that just gives you access to way lower level APIs. Something like evaluateTodoSomeLogicalName(xpath: IAST|string, contextSequence: {contextItems: Value[], contextItemIndex: number}, additionalOptions...) as Value[]. With Value being {type: typeString, value: Node|number|string|boolean} or something.

This way, you can just input a whole sequence into FontoXPath. We will take care of the proper results of any functions that use the context. I bet you're also going to be needing direct access to the actual values XPath resolves to, not the JavaScript lookalikes. In other words, I bet you're going to need to know something is an xs:decimal as opposed to some numeric type. Same with array() and map(). That's why I added a Value type there.

I think I'm OK with building such an API, and I am of course even more willing to accept such a PR :wink:, but before I want to give the go-ahead, I'd like to know your thoughts on the Patterns issue I outlined above.

Regards,

Martin

DrRataplan avatar Apr 12 '21 08:04 DrRataplan

Hi Martin - Excellent, thanks for all the info. As this is an experimental implementation, the XSLT version has been a moving target. I was originally implementing against XSLT 1, which uses a simpler algorithm to determine a match. I am now trying to target XSLT 2, which uses a more complex algorithm which is nonetheless slightly different from XSLT 3.

As far as I can tell, however, all of these can be implemented according to spec with (a) some compile-time syntax checking and processing to generate the xpath and then interpreting the results of evaluating the xpath appropriately. But I am not sure, I've only implemented the easier algorithm of XSLT 1 and I haven't yet implemented, e.g. evaluating an xpath with variables containing node sets, or sorting, or anything other than just evaluating an xpath against the document.

In summary, I think that you are correct that a lower-level API is probably the appropriate solution, but at this stage of my implementation I can't be sure. I'd be happy to circle back later with a PR implementing this low-level API call. And yes, the actual values is something else I'm going to need to be implementing: I've just implemented variable but without bothering types or the as attribute.

egh avatar Apr 12 '21 16:04 egh

I think the PR #411 might solve a lot of these issues. I just ran into the ANY type atomizing attributes. This PR will bring a new return type that just is the (value of the) result you return, close to zero changes regarding attributes, sometimes returning arrays, etcetera.

DrRataplan avatar Aug 31 '21 15:08 DrRataplan