fontoxpath
Version:
A minimalistic XPath 3.1 engine in JavaScript
148 lines (135 loc) • 4.19 kB
JavaScript
import Selector from '../Selector';
import Sequence from '../dataTypes/Sequence';
import isSubtypeOf from '../dataTypes/isSubtypeOf';
import { sortNodeValues, compareNodePositions } from '../dataTypes/documentOrderUtils';
import { DONE_TOKEN, ready } from '../util/iterators';
/**
* @param {string} intersectOrExcept
* @param {!IDomFacade} domFacade
* @param {!Sequence} sequence
* @param {number} expectedResultOrder
*
* @return {!Sequence}
*/
function ensureSortedSequence (intersectOrExcept, domFacade, sequence, expectedResultOrder) {
sequence = sequence.map(value => {
if (!isSubtypeOf(value.type, 'node()')) {
throw new Error(`XPTY0004: Sequences given to ${intersectOrExcept} should only contain nodes.`);
}
return value;
});
if (expectedResultOrder === Selector.RESULT_ORDERINGS.SORTED) {
return sequence;
}
if (expectedResultOrder === Selector.RESULT_ORDERINGS.REVERSE_SORTED) {
return sequence.mapAll(allItems => new Sequence(allItems.reverse()));
}
// Unsorted
return sequence.mapAll(allItems => new Sequence(sortNodeValues(domFacade, allItems)));
}
/**
* The 'intersect' expression: intersect and except
* @extends {Selector}
*/
class IntersectExcept extends Selector {
/**
* @param {string} intersectOrExcept
* @param {!Selector} expression1
* @param {!Selector} expression2
*/
constructor (intersectOrExcept, expression1, expression2) {
const maxSpecificity = expression1.specificity.compareTo(expression2.specificity) > 0 ?
expression1.specificity :
expression2.specificity;
super(maxSpecificity, {
canBeStaticallyEvaluated: expression1.canBeStaticallyEvaluated && expression2.canBeStaticallyEvaluated
});
this._intersectOrExcept = intersectOrExcept;
this._expression1 = expression1;
this._expression2 = expression2;
}
evaluate (dynamicContext) {
const firstResult = ensureSortedSequence(
this._intersectOrExcept,
dynamicContext.domFacade,
this._expression1.evaluate(dynamicContext),
this._expression1.expectedResultOrder);
const secondResult = ensureSortedSequence(
this._intersectOrExcept,
dynamicContext.domFacade,
this._expression2.evaluate(dynamicContext),
this._expression2.expectedResultOrder);
const firstIterator = firstResult.value();
const secondIterator = secondResult.value();
let firstValue = null;
let secondValue = null;
let done = false;
let secondIteratorDone = false;
return new Sequence({
next: () => {
if (done) {
return DONE_TOKEN;
}
while (!secondIteratorDone) {
if (!firstValue) {
const itrResult = firstIterator.next();
if (!itrResult.ready) {
return itrResult;
}
if (itrResult.done) {
// Since ∅ \ X = ∅ and ∅ ∩ X = ∅, we are done.
done = true;
return DONE_TOKEN;
}
firstValue = itrResult.value;
}
if (!secondValue) {
const itrResult = secondIterator.next();
if (!itrResult.ready) {
return itrResult;
}
if (itrResult.done) {
secondIteratorDone = true;
break;
}
secondValue = itrResult.value;
}
if (firstValue.value === secondValue.value) {
const toReturn = ready(firstValue);
firstValue = null;
secondValue = null;
if (this._intersectOrExcept === 'intersect') {
return toReturn;
}
continue;
}
const comparisonResult = compareNodePositions(dynamicContext.domFacade, firstValue, secondValue);
if (comparisonResult < 0) {
const toReturn = ready(firstValue);
firstValue = null;
if (this._intersectOrExcept === 'except') {
return toReturn;
}
}
else {
secondValue = null;
}
}
// The second array is empty.
if (this._intersectOrExcept === 'except') {
// Since X \ ∅ = X, we can output all items of X
if (firstValue !== null) {
const toReturn = ready(firstValue);
firstValue = null;
return toReturn;
}
return firstIterator.next();
}
// Since X ∩ ∅ = ∅, we are done.
done = true;
return DONE_TOKEN;
}
});
}
}
export default IntersectExcept;