UNPKG

fontoxpath

Version:

A minimalistic XPath 3.1 engine in JavaScript

410 lines (366 loc) 12.2 kB
import arrayGet from './builtInFunctions.arrays.get'; import isSubtypeOf from '../dataTypes/isSubtypeOf'; import Sequence from '../dataTypes/Sequence'; import createAtomicValue from '../dataTypes/createAtomicValue'; import ArrayValue from '../dataTypes/ArrayValue'; import zipSingleton from '../util/zipSingleton'; import concatSequences from '../util/concatSequences'; import { DONE_TOKEN, ready } from '../util/iterators'; function arraySize (_dynamicContext, arraySequence) { return zipSingleton( [arraySequence], ([array]) => Sequence.singleton(createAtomicValue(array.members.length, 'xs:integer'))); } function arrayPut (_dynamicContext, arraySequence, positionSequence, itemSequence) { return zipSingleton( [positionSequence, arraySequence], ([position, array]) => { const positionValue = position.value; if (positionValue <= 0 || positionValue > array.members.length) { throw new Error('FOAY0001: array position out of bounds.'); } const newMembers = array.members.concat(); newMembers.splice(positionValue - 1, 1, itemSequence); return Sequence.singleton(new ArrayValue(newMembers)); }); } function arrayAppend (_dynamicContext, arraySequence, itemSequence) { return zipSingleton( [arraySequence], ([array]) => { const newMembers = array.members.concat([itemSequence]); return Sequence.singleton(new ArrayValue(newMembers)); }); } function arraySubarray (_dynamicContext, arraySequence, startSequence, lengthSequence) { return zipSingleton( [arraySequence, startSequence, lengthSequence], ([array, start, length]) => { const startValue = start.value; const lengthValue = length.value; if (startValue > array.members.length || startValue <= 0) { throw new Error('FOAY0001: subarray start out of bounds.'); } if (lengthValue < 0) { throw new Error('FOAY0002: subarray length out of bounds.'); } if (startValue + lengthValue > array.members.length + 1) { throw new Error('FOAY0001: subarray start + length out of bounds.'); } const newMembers = array.members.slice(startValue - 1, lengthValue + startValue - 1); return Sequence.singleton(new ArrayValue(newMembers)); }); } function arrayRemove (_dynamicContext, arraySequence, positionSequence) { return zipSingleton( [arraySequence], ([array]) => positionSequence.mapAll(allIndices => { const indicesToRemove = allIndices.map(value => value.value) // Sort them in reverse order, to keep them stable .sort((a, b) => b - a) .filter((item, i, all) => all[i - 1] !== item); const newMembers = array.members.concat(); for (let i = 0, l = indicesToRemove.length; i < l; ++i) { const position = indicesToRemove[i]; if (position > array.members.length || position <= 0) { throw new Error('FOAY0001: subarray position out of bounds.'); } newMembers.splice(position - 1, 1); } return Sequence.singleton(new ArrayValue(newMembers)); }) ); } function arrayInsertBefore (_dynamicContext, arraySequence, positionSequence, itemSequence) { return zipSingleton( [arraySequence, positionSequence], ([array, position]) => { const positionValue = position.value; if (positionValue > array.members.length + 1 || positionValue <= 0) { throw new Error('FOAY0001: subarray position out of bounds.'); } const newMembers = array.members.concat(); newMembers.splice(positionValue - 1, 0, itemSequence); return Sequence.singleton(new ArrayValue(newMembers)); }); } function arrayReverse (_dynamicContext, arraySequence) { return zipSingleton( [arraySequence], ([array]) => Sequence.singleton(new ArrayValue(array.members.concat().reverse()))); } function arrayJoin (_dynamicContext, arraySequence) { return arraySequence.mapAll(allArrays => { const newMembers = allArrays.reduce( (joinedMembers, array) => joinedMembers.concat(array.members), []); return Sequence.singleton(new ArrayValue(newMembers)); }); } function arrayForEach (dynamicContext, arraySequence, functionItemSequence) { return zipSingleton( [arraySequence, functionItemSequence], ([array, functionItem]) => { const newMembers = array.members.map(function (member) { return functionItem.value.call(undefined, dynamicContext, member); }); return Sequence.singleton(new ArrayValue(newMembers)); }); } function arrayFilter (dynamicContext, arraySequence, functionItemSequence) { return zipSingleton( [arraySequence, functionItemSequence], ([array, functionItem]) => { /** * @type {!Array<!Sequence>} */ const filterResultSequences = array.members.map(member => functionItem.value.call( undefined, dynamicContext, member)); const effectiveBooleanValues = []; let done = false; return new Sequence({ next: () => { if (done) { return DONE_TOKEN; } let allReady = true; for (let i = 0, l = array.members.length; i < l; ++i) { if (effectiveBooleanValues[i] && effectiveBooleanValues[i].ready) { continue; } const filterResult = filterResultSequences[i]; const ebv = filterResult.tryGetEffectiveBooleanValue(); if (!ebv.ready) { allReady = false; } effectiveBooleanValues[i] = ebv; } if (!allReady) { return { done: false, ready: false, promise: Promise.all(effectiveBooleanValues.map( filterResult => filterResult.ready ? Promise.resolve() : filterResult.promise)) }; } const newMembers = array.members.filter((_, i) => effectiveBooleanValues[i].value); done = true; return ready(new ArrayValue(newMembers)); } }); }); } function arrayFoldLeft (dynamicContext, arraySequence, startSequence, functionItemSequence) { return zipSingleton( [arraySequence, functionItemSequence], ([array, functionItem]) => array.members.reduce( (accum, member) => functionItem.value.call(undefined, dynamicContext, accum, member), startSequence)); } function arrayFoldRight (dynamicContext, arraySequence, startSequence, functionItemSequence) { return zipSingleton( [arraySequence, functionItemSequence], ([array, functionItem]) => array.members.reduceRight( (accum, member) => functionItem.value.call(undefined, dynamicContext, accum, member), startSequence)); } function arrayForEachPair (dynamicContext, arraySequenceA, arraySequenceB, functionItemSequence) { return zipSingleton( [arraySequenceA, arraySequenceB, functionItemSequence], ([arrayA, arrayB, functionItem]) => { const newMembers = []; for (let i = 0, l = Math.min(arrayA.members.length, arrayB.members.length); i < l; ++i) { newMembers[i] = functionItem.value.call(undefined, dynamicContext, arrayA.members[i], arrayB.members[i]); } return Sequence.singleton(new ArrayValue(newMembers)); }); } function arraySort (dynamicContext, arraySequence) { return zipSingleton( [arraySequence], ([array]) => { const atomizedMembers = array.members.map(member => member.atomize(dynamicContext)); return zipSingleton( atomizedMembers, atomizedItems => { const permutations = array.members .map((_, i) => i) .sort((indexA, indexB) => atomizedItems[indexA].value > atomizedItems[indexB].value ? 1 : -1); return Sequence.singleton( new ArrayValue(permutations.map(i => array.members[i])) ); }); }); } function arrayFlatten (_dynamicContext, itemSequence) { return itemSequence.mapAll(items => items.reduce(function flattenItem (flattenedItems, item) { if (isSubtypeOf(item.type, 'array(*)')) { return item.members.reduce( (flattenedItemsOfMember, member) => member.mapAll( allValues => allValues.reduce(flattenItem, flattenedItemsOfMember)), flattenedItems); } return concatSequences([flattenedItems, Sequence.singleton(item)]); }, Sequence.empty())); } export default { declarations: [ { name: 'array:size', argumentTypes: ['array(*)'], returnType: 'xs:integer', callFunction: arraySize }, { name: 'array:get', argumentTypes: ['array(*)', 'xs:integer'], returnType: 'item()*', callFunction: arrayGet }, { name: 'array:put', argumentTypes: ['array(*)', 'xs:integer', 'item()*'], returnType: 'array(*)', callFunction: arrayPut }, { name: 'array:append', argumentTypes: ['array(*)', 'item()*'], returnType: 'array(*)', callFunction: arrayAppend }, { name: 'array:subarray', argumentTypes: ['array(*)', 'xs:integer', 'xs:integer'], returnType: 'array(*)', callFunction: arraySubarray }, { name: 'array:subarray', argumentTypes: ['array(*)', 'xs:integer'], returnType: 'array(*)', callFunction: function (dynamicContext, arraySequence, startSequence) { const lengthSequence = Sequence.singleton(createAtomicValue( arraySequence.first().members.length - startSequence.first().value + 1, 'xs:integer')); return arraySubarray( dynamicContext, arraySequence, startSequence, lengthSequence); } }, { name: 'array:remove', argumentTypes: ['array(*)', 'xs:integer*'], returnType: 'array(*)', callFunction: arrayRemove }, { name: 'array:insert-before', argumentTypes: ['array(*)', 'xs:integer', 'item()*'], returnType: 'array(*)', callFunction: arrayInsertBefore }, { name: 'array:head', argumentTypes: ['array(*)'], returnType: 'item()*', callFunction: function (dynamicContext, arraySequence) { return arrayGet(dynamicContext, arraySequence, Sequence.singleton(createAtomicValue(1, 'xs:integer'))); } }, { name: 'array:tail', argumentTypes: ['array(*)'], returnType: 'item()*', callFunction: function (dynamicContext, arraySequence) { return arrayRemove(dynamicContext, arraySequence, Sequence.singleton(createAtomicValue(1, 'xs:integer'))); } }, { name: 'array:reverse', argumentTypes: ['array(*)'], returnType: 'array(*)', callFunction: arrayReverse }, { name: 'array:join', argumentTypes: ['array(*)*'], returnType: 'array(*)', callFunction: arrayJoin }, { name: 'array:for-each', // TODO: reimplement type checking by parsing the types // argumentTypes: ['array(*)', 'function(item()*) as item()*)] argumentTypes: ['array(*)', 'function(*)'], returnType: 'array(*)', callFunction: arrayForEach }, { name: 'array:filter', // TODO: reimplement type checking by parsing the types // argumentTypes: ['array(*)', 'function(item()*) as xs:boolean)] argumentTypes: ['array(*)', 'function(*)'], returnType: 'array(*)', callFunction: arrayFilter }, { name: 'array:fold-left', // TODO: reimplement type checking by parsing the types // argumentTypes: ['array(*)', 'item()*', 'function(item()*, item()*) as item())] argumentTypes: ['array(*)', 'item()*', 'function(*)'], returnType: 'item()*', callFunction: arrayFoldLeft }, { name: 'array:fold-right', // TODO: reimplement type checking by parsing the types // argumentTypes: ['array(*)', 'item()*', 'function(item()*, item()*) as item())] argumentTypes: ['array(*)', 'item()*', 'function(*)'], returnType: 'item()*', callFunction: arrayFoldRight }, { name: 'array:for-each-pair', // TODO: reimplement type checking by parsing the types // argumentTypes: ['array(*)', 'item()*', 'function(item()*, item()*) as item())] argumentTypes: ['array(*)', 'array(*)', 'function(*)'], returnType: 'array(*)', callFunction: arrayForEachPair }, { name: 'array:sort', argumentTypes: ['array(*)'], returnType: 'array(*)', callFunction: arraySort }, { name: 'array:flatten', argumentTypes: ['item()*'], returnType: 'item()*', callFunction: arrayFlatten } ], functions: { append: arrayAppend, flatten: arrayFlatten, foldLeft: arrayFoldLeft, foldRight: arrayFoldRight, forEach: arrayForEach, forEachPair: arrayForEachPair, filter: arrayFilter, get: arrayGet, insertBefore: arrayInsertBefore, join: arrayJoin, put: arrayPut, remove: arrayRemove, reverse: arrayReverse, size: arraySize, sort: arraySort, subarray: arraySubarray } };