fontoxpath
Version:
A minimalistic XPath 3.1 engine in JavaScript
166 lines (154 loc) • 4.41 kB
JavaScript
import DynamicContext from '../DynamicContext';
import Sequence from '../dataTypes/Sequence';
import createNodeValue from '../dataTypes/createNodeValue';
import createAtomicValue from '../dataTypes/createAtomicValue';
import createDoublyIterableSequence from '../util/createDoublyIterableSequence';
import { DONE_TOKEN, ready, notReady } from '../util/iterators';
/**
* @param {!../DynamicContext} dynamicContext
* @param {!../dataTypes/Sequence} query
* @param {!../dataTypes/Sequence} args
*/
function fontoxpathEvaluate (dynamicContext, query, args) {
let resultIterator;
let queryString;
return new Sequence({
next: () => {
if (!resultIterator) {
const queryValue = query.value().next();
if (!queryValue.ready) {
return queryValue;
}
queryString = queryValue.value.value;
const variables = {};
args.first().keyValuePairs.reduce((expandedArgs, arg) => {
expandedArgs[arg.key.value] = createDoublyIterableSequence(arg.value);
return expandedArgs;
}, variables);
// Take off the context item
const contextItemSequence = variables['.'] ? variables['.']() : Sequence.empty();
delete variables['.'];
const selector = dynamicContext.createSelectorFromXPath(queryString);
const context = contextItemSequence.isEmpty() ? {
contextItem: null,
contextSequence: contextItemSequence,
contextItemIndex: -1,
variables,
domFacade: dynamicContext.domFacade,
resolveNamespacePrefix: dynamicContext.resolveNamespacePrefix,
createSelectorFromXPath: dynamicContext.createSelectorFromXPath
} : {
contextItem: contextItemSequence.first(),
contextSequence: contextItemSequence,
contextItemIndex: 0,
variables,
domFacade: dynamicContext.domFacade,
resolveNamespacePrefix: dynamicContext.resolveNamespacePrefix,
createSelectorFromXPath: dynamicContext.createSelectorFromXPath
};
const innerDynamicContext = new DynamicContext(context);
resultIterator = selector.evaluate(innerDynamicContext).value();
}
return resultIterator.next();
}
});
}
/**
* @param {../DynamicContext} _dynamicContext
* @param {!Sequence} val
* @param {!Sequence} howLong
* @return {!Sequence}
*/
function fontoxpathSleep (_dynamicContext, val, howLong) {
let doneWithSleep = false;
let readyPromise;
const valueIterator = val.value();
return new Sequence({
next: () => {
if (!readyPromise) {
/**
* @type {!../util/iterators.AsyncResult}
*/
const time = howLong ? howLong.tryGetFirst() : ready(createAtomicValue(0, 'xs:integer'));
if (!time.ready) {
return notReady(readyPromise);
}
readyPromise = new Promise(
resolve => setTimeout(() => {
doneWithSleep = true;
resolve();
}, time.value.value)
);
}
if (!doneWithSleep) {
return notReady(readyPromise);
}
return valueIterator.next();
}
});
}
function fontoxpathFetch (_dynamicContext, url) {
let doneWithFetch = false;
let result = null;
let done = false;
let readyPromise = null;
return new Sequence({
next: () => {
if (!readyPromise) {
const urlValue = url.value().next();
if (!urlValue.ready) {
return urlValue;
}
readyPromise = fetch(urlValue.value.value)
.then(response => response.text())
.then(text => new DOMParser().parseFromString(text, 'application/xml'))
.then(doc => {
doneWithFetch = true;
result = doc;
});
}
if (!doneWithFetch) {
return notReady(readyPromise);
}
if (!done) {
done = true;
return ready(createNodeValue(result));
}
return DONE_TOKEN;
}
});
}
export default {
declarations: [
{
name: 'fontoxpath:evaluate',
argumentTypes: ['xs:string', 'map(*)'],
returnType: 'item()*',
callFunction: fontoxpathEvaluate
},
{
name: 'fontoxpath:sleep',
argumentTypes: ['item()*', 'xs:numeric'],
returnType: 'item()*',
callFunction: fontoxpathSleep
},
{
name: 'fontoxpath:sleep',
argumentTypes: ['item()*'],
returnType: 'item()*',
callFunction: fontoxpathSleep
},
{
name: 'fontoxpath:fetch',
argumentTypes: ['xs:string', 'map(*)'],
returnType: 'item()*',
callFunction: fontoxpathFetch
},
{
name: 'fontoxpath:fetch',
argumentTypes: ['xs:string'],
returnType: 'item()*',
callFunction: fontoxpathFetch
}
]
};