UNPKG

fontoxpath

Version:

A minimalistic XPath 3.1 engine in JavaScript

264 lines (243 loc) 8.43 kB
import castToType from '../../dataTypes/castToType'; import isSubtypeOf from '../../dataTypes/isSubtypeOf'; import { equal as dateTimeEqual, lessThan as dateTimeLessThan, greaterThan as dateTimeGreaterThan } from '../../dataTypes/valueTypes/DateTime'; import { lessThan as yearMonthDurationLessThan, greaterThan as yearMonthDurationGreaterThan } from '../../dataTypes/valueTypes/YearMonthDuration'; import { lessThan as dayTimeDurationLessThan, greaterThan as dayTimeDurationGreaterThan } from '../../dataTypes/valueTypes/DayTimeDuration'; // Use partial application to get to a comparer faster function areBothStringOrAnyURI (a, b) { return (isSubtypeOf(a, 'xs:string') || isSubtypeOf(a, 'xs:anyURI')) && (isSubtypeOf(b, 'xs:string') || isSubtypeOf(b, 'xs:anyURI')); } function generateCompareFunction (operator, typeA, typeB, dynamicContext) { let castFunctionForValueA = null; let castFunctionForValueB = null; if (isSubtypeOf(typeA, 'xs:untypedAtomic') && isSubtypeOf(typeB, 'xs:untypedAtomic')) { typeA = typeB = 'xs:string'; } else if (isSubtypeOf(typeA, 'xs:untypedAtomic')) { castFunctionForValueA = val => castToType(val, typeB); typeA = typeB; } else if (isSubtypeOf(typeB, 'xs:untypedAtomic')) { castFunctionForValueB = val => castToType(val, typeA); typeB = typeA; } function applyCastFunctions (valA, valB) { return { castA: castFunctionForValueA ? castFunctionForValueA(valA) : valA, castB: castFunctionForValueB ? castFunctionForValueB(valB) : valB }; } if (isSubtypeOf(typeA, 'xs:QName') && isSubtypeOf(typeB, 'xs:QName')) { if (operator === 'eq') { return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value.namespaceURI === castB.value.namespaceURI && castA.value.localPart === castB.value.localPart; }; } if (operator === 'ne') { return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value.namespaceURI !== castB.value.namespaceURI || castA.value.localPart !== castB.value.localPart; }; } throw new Error('XPTY0004: Only the "eq" and "ne" comparison is defined for xs:QName'); } function areBothSubtypeOf (type) { return isSubtypeOf(typeA, type) && isSubtypeOf(typeB, type); } if (areBothSubtypeOf('xs:boolean') || areBothSubtypeOf('xs:string') || areBothSubtypeOf('xs:numeric') || areBothSubtypeOf('xs:anyURI') || areBothSubtypeOf('xs:hexBinary') || areBothSubtypeOf('xs:base64Binary') || areBothStringOrAnyURI(typeA, typeB)) { switch (operator) { case 'eq': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value === castB.value; }; case 'ne': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value !== castB.value; }; case 'lt': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value < castB.value; }; case 'le': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value <= castB.value; }; case 'gt': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value > castB.value; }; case 'ge': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value >= castB.value; }; } } if (areBothSubtypeOf('xs:dateTime') || areBothSubtypeOf('xs:date') || areBothSubtypeOf('xs:time')) { switch (operator) { case 'eq': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return dateTimeEqual(castA.value, castB.value, dynamicContext.getImplicitTimezone()); }; case 'ne': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return !dateTimeEqual(castA.value, castB.value, dynamicContext.getImplicitTimezone()); }; case 'lt': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return dateTimeLessThan(castA.value, castB.value, dynamicContext.getImplicitTimezone()); }; case 'le': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return dateTimeEqual(castA.value, castB.value, dynamicContext.getImplicitTimezone()) || dateTimeLessThan(castA.value, castB.value, dynamicContext.getImplicitTimezone()); }; case 'gt': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return dateTimeGreaterThan(castA.value, castB.value, dynamicContext.getImplicitTimezone()); }; case 'ge': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return dateTimeEqual(castA.value, castB.value, dynamicContext.getImplicitTimezone()) || dateTimeGreaterThan(castA.value, castB.value, dynamicContext.getImplicitTimezone()); }; } } if (areBothSubtypeOf('xs:gYearMonth') || areBothSubtypeOf('xs:gYear') || areBothSubtypeOf('xs:gMonthDay') || areBothSubtypeOf('xs:gMonth') || areBothSubtypeOf('xs:gDay')) { switch (operator) { case 'eq': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return dateTimeEqual(castA.value, castB.value, dynamicContext.getImplicitTimezone()); }; case 'ne': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return !dateTimeEqual(castA.value, castB.value, dynamicContext.getImplicitTimezone()); }; } } if (areBothSubtypeOf('xs:yearMonthDuration')) { switch (operator) { case 'lt': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return yearMonthDurationLessThan(castA.value, castB.value); }; case 'le': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value.equals(castB.value) || yearMonthDurationLessThan(castA.value, castB.value); }; case 'gt': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return yearMonthDurationGreaterThan(castA.value, castB.value); }; case 'ge': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value.equals(castB.value) || yearMonthDurationGreaterThan(castA.value, castB.value); }; } } if (areBothSubtypeOf('xs:dayTimeDuration')) { switch (operator) { case 'eq': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value.equals(castB.value); }; case 'lt': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return dayTimeDurationLessThan(castA.value, castB.value); }; case 'le': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value.equals(castB.value) || dayTimeDurationLessThan(castA.value, castB.value); }; case 'gt': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return dayTimeDurationGreaterThan(castA.value, castB.value); }; case 'ge': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value.equals(castB.value) || dayTimeDurationGreaterThan(castA.value, castB.value); }; } } if (areBothSubtypeOf('xs:duration')) { switch (operator) { case 'eq': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return castA.value.equals(castB.value); }; case 'ne': return (a, b) => { const { castA, castB } = applyCastFunctions(a, b); return !castA.value.equals(castB.value); }; } } throw new Error(`XPTY0004: ${operator} not available for ${typeA} and ${typeB}`); } const comparatorsByTypingKey = Object.create(null); /** * @param {string} operator * @param {../../dataTypes/AtomicValue} valueA * @param {../../dataTypes/AtomicValue} valueB * @param {../../DynamicContext} dynamicContext */ export default function valueCompare (operator, valueA, valueB, dynamicContext) { // https://www.w3.org/TR/xpath-3/#doc-xpath31-ValueComp const typingKey = `${valueA.type}~${valueB.type}~${operator}`; let prefabComparator = comparatorsByTypingKey[typingKey]; if (!prefabComparator) { prefabComparator = comparatorsByTypingKey[typingKey] = generateCompareFunction(operator, valueA.type, valueB.type, dynamicContext); } return prefabComparator(valueA, valueB); }