fontoxpath
Version:
A minimalistic XPath 3.1 engine in JavaScript
280 lines (259 loc) • 8 kB
JavaScript
import isSameMapKey from './isSameMapKey';
import mapGet from './builtInFunctions.maps.get';
import Sequence from '../dataTypes/Sequence';
import createAtomicValue from '../dataTypes/createAtomicValue';
import MapValue from '../dataTypes/MapValue';
import zipSingleton from '../util/zipSingleton';
/**
* @param {../DynamicContext} dynamicContext
* @param {!Sequence} mapSequence
* @param {!Sequence} optionMap
* @return {!Sequence}
*/
function mapMerge (dynamicContext, mapSequence, optionMap) {
const duplicateKey = Sequence.singleton(createAtomicValue('duplicates', 'xs:string'));
const duplicationHandlingValueSequence = mapGet(dynamicContext, optionMap, duplicateKey);
/**
* @type {string}
*/
const duplicationHandlingStrategy = duplicationHandlingValueSequence.isEmpty() ? 'use-first' : duplicationHandlingValueSequence.first().value;
return mapSequence.mapAll(
allValues =>
Sequence.singleton(new MapValue(allValues.reduce((resultingKeyValuePairs, map) => {
map.keyValuePairs.forEach(function (keyValuePair) {
const existingPairIndex = resultingKeyValuePairs.findIndex(function (existingPair) {
return isSameMapKey(existingPair.key, keyValuePair.key);
});
if (existingPairIndex >= 0) {
// Duplicate keys, use options to determine what to do
switch (duplicationHandlingStrategy) {
case 'reject':
throw new Error('FOJS0003: Duplicate encountered when merging maps.');
case 'use-last':
// Use this one
resultingKeyValuePairs.splice(existingPairIndex, 1, keyValuePair);
return;
case 'combine':
resultingKeyValuePairs.splice(
existingPairIndex,
1,
{
key: keyValuePair.key,
value: new Sequence(
resultingKeyValuePairs[existingPairIndex].value.getAllValues()
.concat(keyValuePair.value.getAllValues()))
});
return;
case 'use-any':
case 'use-first':
default:
return;
}
}
resultingKeyValuePairs.push(keyValuePair);
});
return resultingKeyValuePairs;
}, []))));
}
/**
* @param {../DynamicContext} _dynamicContext
* @param {!Sequence} mapSequence
* @param {!Sequence} keySequence
* @param {!Sequence} newValueSequence
* @return {!Sequence}
*/
function mapPut (_dynamicContext, mapSequence, keySequence, newValueSequence) {
return zipSingleton([mapSequence, keySequence], ([map, newKey]) => {
/**
* @type {Array<{key: ../dataTypes/Value, value: Sequence}>}
*/
const resultingKeyValuePairs = map.keyValuePairs.concat();
const indexOfExistingPair = resultingKeyValuePairs.findIndex(function (existingPair) {
return isSameMapKey(existingPair.key, newKey);
});
if (indexOfExistingPair >= 0) {
// Duplicate keys, use options to determine what to do
resultingKeyValuePairs.splice(
indexOfExistingPair,
1,
{
key: newKey,
value: newValueSequence
});
}
else {
resultingKeyValuePairs.push({
key: newKey,
value: newValueSequence
});
}
return Sequence.singleton(new MapValue(resultingKeyValuePairs));
});
}
/**
* @param {../DynamicContext} _dynamicContext
* @param {!Sequence} keySequence
* @param {!Sequence} value
* @return {!Sequence}
*/
function mapEntry (_dynamicContext, keySequence, value) {
return keySequence.map(onlyKey => new MapValue([{ key: onlyKey, value: value }]));
}
/**
* @param {../DynamicContext} _dynamicContext
* @param {!Sequence} mapSequence
* @return {!Sequence}
*/
function mapSize (_dynamicContext, mapSequence) {
return mapSequence.map(onlyMap => createAtomicValue(onlyMap.keyValuePairs.length, 'xs:integer'));
}
/**
* @param {../DynamicContext} _dynamicContext
* @param {!Sequence} mapSequence
* @return {!Sequence}
*/
function mapKeys (_dynamicContext, mapSequence) {
return zipSingleton([mapSequence], ([map]) => new Sequence(map.keyValuePairs.map(pair => pair.key)));
}
/**
* @param {../DynamicContext} _dynamicContext
* @param {!Sequence} mapSequence
* @param {!Sequence} keySequence
* @return {!Sequence}
*/
function mapContains (_dynamicContext, mapSequence, keySequence) {
return zipSingleton([mapSequence, keySequence], ([map, key]) => {
const doesContain = map.keyValuePairs.some(pair => isSameMapKey(pair.key, key));
return doesContain ? Sequence.singletonTrueSequence() : Sequence.singletonFalseSequence();
});
}
/**
* @param {../DynamicContext} _dynamicContext
* @param {!Sequence} mapSequence
* @param {!Sequence} keySequence
* @return {!Sequence}
*/
function mapRemove (_dynamicContext, mapSequence, keySequence) {
return zipSingleton([mapSequence], ([map]) => {
/**
* @type {Array<{key: ../dataTypes/Value, value: Sequence}>}
*/
const resultingKeyValuePairs = map.keyValuePairs.concat();
return keySequence.mapAll(keys => {
keys.forEach(function (key) {
const indexOfExistingPair = resultingKeyValuePairs.findIndex(existingPair => isSameMapKey(existingPair.key, key));
if (indexOfExistingPair >= 0) {
resultingKeyValuePairs.splice(
indexOfExistingPair,
1);
}
});
return Sequence.singleton(new MapValue(resultingKeyValuePairs));
});
});
}
/**
* @param {../DynamicContext} dynamicContext
* @param {!Sequence} mapSequence
* @param {!Sequence} functionItemSequence
* @return {!Sequence}
*/
function mapForEach (dynamicContext, mapSequence, functionItemSequence) {
return zipSingleton([mapSequence, functionItemSequence], ([map, functionItem]) => {
/**
* @type {Array<{key: ../dataTypes/Value, value: Sequence}>}
*/
const resultingKeyValuePairs = map.keyValuePairs.map(function (keyValuePair) {
const newValue = functionItem.value.call(
undefined,
dynamicContext,
Sequence.singleton(keyValuePair.key),
keyValuePair.value);
return {
key: keyValuePair.key,
value: newValue
};
});
return Sequence.singleton(new MapValue(resultingKeyValuePairs));
});
}
export default {
declarations: [
{
name: 'map:contains',
argumentTypes: ['map(*)', 'xs:anyAtomicType'],
returnType: 'xs:boolean',
callFunction: mapContains
},
{
name: 'map:entry',
argumentTypes: ['xs:anyAtomicType', 'item()*'],
returnType: 'map(*)',
callFunction: mapEntry
},
{
name: 'map:for-each',
// TODO: reimplement type checking by parsing the types
// argumentTypes: ['map(*)', 'function(xs:anyAtomicType, item()*) as item()*'],
argumentTypes: ['map(*)', 'function(*)'],
returnType: 'item()*',
callFunction: mapForEach
},
{
name: 'map:get',
argumentTypes: ['map(*)', 'xs:anyAtomicType'],
returnType: 'item()*',
callFunction: mapGet
},
{
name: 'map:keys',
argumentTypes: ['map(*)'],
returnType: 'xs:anyAtomicType*',
callFunction: mapKeys
},
{
name: 'map:merge',
argumentTypes: ['map(*)*', 'map(*)'],
returnType: 'map(*)',
callFunction: mapMerge
},
{
name: 'map:merge',
argumentTypes: ['map(*)*'],
returnType: 'map(*)',
callFunction: function (dynamicContext, maps) {
return mapMerge(
dynamicContext,
maps,
Sequence.singleton(new MapValue([{
key: createAtomicValue('duplicates', 'xs:string'),
value: Sequence.singleton(createAtomicValue('use-first', 'xs:string'))
}])));
}
},
{
name: 'map:put',
argumentTypes: ['map(*)', 'xs:anyAtomicType', 'item()*'],
returnType: 'map(*)',
callFunction: mapPut
},
{
name: 'map:remove',
argumentTypes: ['map(*)', 'xs:anyAtomicType*'],
returnType: 'map(*)',
callFunction: mapRemove
},
{
name: 'map:size',
argumentTypes: ['map(*)'],
returnType: 'xs:integer',
callFunction: mapSize
}
],
functions: {
get: mapGet,
merge: mapMerge,
put: mapPut,
size: mapSize
}
};