fontoxpath
fontoxpath copied to clipboard
Evaluating xpath with context in a sequence
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.
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 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.
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
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.
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.