diff --git a/src/expressions/adaptJavaScriptValueToXPathValue.ts b/src/expressions/adaptJavaScriptValueToXPathValue.ts index 1a62ed064..00fd21462 100644 --- a/src/expressions/adaptJavaScriptValueToXPathValue.ts +++ b/src/expressions/adaptJavaScriptValueToXPathValue.ts @@ -1,7 +1,7 @@ import { NodePointer } from '../domClone/Pointer'; import DomFacade from '../domFacade/DomFacade'; import { UntypedExternalValue, ValidValue } from '../types/createTypedValueFactory'; -import ArrayValue from './dataTypes/ArrayValue'; +import ArrayValue, { ABSENT_JSON_ARRAY } from './dataTypes/ArrayValue'; import { getValidatorForType } from './dataTypes/builtins/dataTypeValidatorByType'; import createAtomicValue, { falseBoolean, trueBoolean } from './dataTypes/createAtomicValue'; import createPointerValue from './dataTypes/createPointerValue'; @@ -56,7 +56,8 @@ export function adaptSingleJavaScriptValue(value: ValidValue, domFacade: DomFaca : sequenceFactory.singleton(adaptedValue); return createDoublyIterableSequence(adaptedSequence); - }) + }), + value ); } // Make it a map @@ -75,7 +76,8 @@ export function adaptSingleJavaScriptValue(value: ValidValue, domFacade: DomFaca key: createAtomicValue(key, ValueType.XSSTRING), value: createDoublyIterableSequence(adaptedSequence), }; - }) + }), + mapValue as any ); } // This code will be reached if the passed value is not actually a ValidValue. This can happen diff --git a/src/expressions/arrays/CurlyArrayConstructor.ts b/src/expressions/arrays/CurlyArrayConstructor.ts index 9b1fb5146..923c5db50 100644 --- a/src/expressions/arrays/CurlyArrayConstructor.ts +++ b/src/expressions/arrays/CurlyArrayConstructor.ts @@ -1,4 +1,4 @@ -import ArrayValue from '../dataTypes/ArrayValue'; +import ArrayValue, { ABSENT_JSON_ARRAY } from '../dataTypes/ArrayValue'; import sequenceFactory from '../dataTypes/sequenceFactory'; import { SequenceType } from '../dataTypes/Value'; import DynamicContext from '../DynamicContext'; @@ -29,7 +29,7 @@ class CurlyArrayConstructor extends Expression { public evaluate(dynamicContext: DynamicContext, executionParameters: ExecutionParameters) { if (this._members.length === 0) { - return sequenceFactory.singleton(new ArrayValue([])); + return sequenceFactory.singleton(new ArrayValue([], ABSENT_JSON_ARRAY)); } return this._members[0] .evaluateMaybeStatically(dynamicContext, executionParameters) @@ -38,7 +38,8 @@ class CurlyArrayConstructor extends Expression { new ArrayValue( allValues.map((item) => createDoublyIterableSequence(sequenceFactory.singleton(item)) - ) + ), + ABSENT_JSON_ARRAY ) ) ); diff --git a/src/expressions/arrays/SquareArrayConstructor.ts b/src/expressions/arrays/SquareArrayConstructor.ts index 90ca89539..2e0ec560c 100644 --- a/src/expressions/arrays/SquareArrayConstructor.ts +++ b/src/expressions/arrays/SquareArrayConstructor.ts @@ -1,4 +1,4 @@ -import ArrayValue from '../dataTypes/ArrayValue'; +import ArrayValue, { ABSENT_JSON_ARRAY } from '../dataTypes/ArrayValue'; import sequenceFactory from '../dataTypes/sequenceFactory'; import { SequenceType } from '../dataTypes/Value'; import DynamicContext from '../DynamicContext'; @@ -34,7 +34,8 @@ class SquareArrayConstructor extends Expression { createDoublyIterableSequence( entry.evaluateMaybeStatically(dynamicContext, executionParameters) ) - ) + ), + ABSENT_JSON_ARRAY ) ); } diff --git a/src/expressions/dataTypes/ArrayValue.ts b/src/expressions/dataTypes/ArrayValue.ts index a1c64816d..23bd0d019 100644 --- a/src/expressions/dataTypes/ArrayValue.ts +++ b/src/expressions/dataTypes/ArrayValue.ts @@ -2,12 +2,16 @@ import arrayGet from '../functions/builtInFunctions_arrays_get'; import { BUILT_IN_NAMESPACE_URIS } from '../staticallyKnownNamespaces'; import FunctionValue from './FunctionValue'; import ISequence from './ISequence'; +import { JsonArray } from './MapValue'; import sequenceFactory from './sequenceFactory'; import { SequenceMultiplicity, ValueType } from './Value'; +export const ABSENT_JSON_ARRAY = Symbol('ABSENT_JSON_ARRAY'); + class ArrayValue extends FunctionValue { public members: (() => ISequence)[]; - constructor(members: (() => ISequence)[]) { + public jsonArray: JsonArray | typeof ABSENT_JSON_ARRAY; + constructor(members: (() => ISequence)[], jsonArray: JsonArray | typeof ABSENT_JSON_ARRAY) { super({ value: (dynamicContext, executionParameters, staticContext, key) => arrayGet( @@ -26,6 +30,7 @@ class ArrayValue extends FunctionValue { }); this.type = ValueType.ARRAY; this.members = members; + this.jsonArray = jsonArray; } } diff --git a/src/expressions/dataTypes/MapValue.ts b/src/expressions/dataTypes/MapValue.ts index 06b57671b..53bb2dc47 100644 --- a/src/expressions/dataTypes/MapValue.ts +++ b/src/expressions/dataTypes/MapValue.ts @@ -5,9 +5,19 @@ import ISequence from './ISequence'; import sequenceFactory from './sequenceFactory'; import Value, { SequenceMultiplicity, ValueType } from './Value'; +export type JsonObject = { + [key: string]: JsonObject | string | number | boolean | JsonArray; +}; +export type JsonArray = (JsonObject | JsonArray | string | number | boolean)[]; +export const AbsentJsonObject = Symbol('absentJsonObject'); + class MapValue extends FunctionValue { public keyValuePairs: { key: Value; value: () => ISequence }[]; - constructor(keyValuePairs: { key: Value; value: () => ISequence }[]) { + public jsonObject: JsonObject | typeof AbsentJsonObject; + constructor( + keyValuePairs: { key: Value; value: () => ISequence }[], + jsonObject: JsonObject | typeof AbsentJsonObject + ) { super({ // argumentTypes: [{ type: 'item()', isRestArgument: false }], argumentTypes: [{ type: ValueType.ITEM, mult: SequenceMultiplicity.EXACTLY_ONE }], @@ -26,6 +36,7 @@ class MapValue extends FunctionValue { }); this.type = ValueType.MAP; this.keyValuePairs = keyValuePairs; + this.jsonObject = jsonObject; } } export default MapValue; diff --git a/src/expressions/functions/builtInFunctions_arrays.ts b/src/expressions/functions/builtInFunctions_arrays.ts index fd122ebcd..30d026e9f 100644 --- a/src/expressions/functions/builtInFunctions_arrays.ts +++ b/src/expressions/functions/builtInFunctions_arrays.ts @@ -1,4 +1,4 @@ -import ArrayValue from '../dataTypes/ArrayValue'; +import ArrayValue, { ABSENT_JSON_ARRAY } from '../dataTypes/ArrayValue'; import createAtomicValue from '../dataTypes/createAtomicValue'; import FunctionValue, { FunctionSignature } from '../dataTypes/FunctionValue'; import ISequence from '../dataTypes/ISequence'; @@ -48,7 +48,7 @@ const arrayPut: FunctionDefinitionType = ( } const newMembers = (array as ArrayValue).members.concat(); newMembers.splice(positionValue - 1, 1, createDoublyIterableSequence(itemSequence)); - return sequenceFactory.singleton(new ArrayValue(newMembers)); + return sequenceFactory.singleton(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); }); }; @@ -63,7 +63,7 @@ const arrayAppend: FunctionDefinitionType = ( const newMembers = (array as ArrayValue).members.concat([ createDoublyIterableSequence(itemSequence), ]); - return sequenceFactory.singleton(new ArrayValue(newMembers)); + return sequenceFactory.singleton(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); }); }; @@ -97,7 +97,7 @@ const arraySubarray: FunctionDefinitionType = ( startValue - 1, lengthValue + startValue - 1 ); - return sequenceFactory.singleton(new ArrayValue(newMembers)); + return sequenceFactory.singleton(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); } ); }; @@ -126,7 +126,7 @@ const arrayRemove: FunctionDefinitionType = ( newMembers.splice(position - 1, 1); } - return sequenceFactory.singleton(new ArrayValue(newMembers)); + return sequenceFactory.singleton(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); }) ); }; @@ -148,7 +148,7 @@ const arrayInsertBefore: FunctionDefinitionType = ( const newMembers = (array as ArrayValue).members.concat(); newMembers.splice(positionValue - 1, 0, createDoublyIterableSequence(itemSequence)); - return sequenceFactory.singleton(new ArrayValue(newMembers)); + return sequenceFactory.singleton(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); }); }; @@ -159,7 +159,9 @@ const arrayReverse: FunctionDefinitionType = ( arraySequence ) => { return zipSingleton([arraySequence], ([array]) => - sequenceFactory.singleton(new ArrayValue((array as ArrayValue).members.concat().reverse())) + sequenceFactory.singleton( + new ArrayValue((array as ArrayValue).members.concat().reverse(), ABSENT_JSON_ARRAY) + ) ); }; @@ -174,7 +176,7 @@ const arrayJoin: FunctionDefinitionType = ( (joinedMembers, array) => joinedMembers.concat((array as ArrayValue).members), [] ); - return sequenceFactory.singleton(new ArrayValue(newMembers)); + return sequenceFactory.singleton(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); }); }; @@ -206,7 +208,7 @@ const arrayForEach: FunctionDefinitionType = ( ) ); }); - return sequenceFactory.singleton(new ArrayValue(newMembers)); + return sequenceFactory.singleton(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); }); }; @@ -250,7 +252,7 @@ const arrayFilter: FunctionDefinitionType = ( (_, i) => effectiveBooleanValues[i] ); done = true; - return ready(new ArrayValue(newMembers)); + return ready(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); }, }); }); @@ -371,7 +373,7 @@ const arrayForEachPair: FunctionDefinitionType = ( ); } - return sequenceFactory.singleton(new ArrayValue(newMembers)); + return sequenceFactory.singleton(new ArrayValue(newMembers, ABSENT_JSON_ARRAY)); } ); }; @@ -444,7 +446,10 @@ const arraySortCallback = ( }); return sequenceFactory.singleton( - new ArrayValue(allValues.map((item) => () => sequenceFactory.create(item))) + new ArrayValue( + allValues.map((item) => () => sequenceFactory.create(item)), + ABSENT_JSON_ARRAY + ) ); }; const arraySort: FunctionDefinitionType = ( diff --git a/src/expressions/functions/builtInFunctions_json.ts b/src/expressions/functions/builtInFunctions_json.ts index 815de5296..09999fd2d 100644 --- a/src/expressions/functions/builtInFunctions_json.ts +++ b/src/expressions/functions/builtInFunctions_json.ts @@ -1,7 +1,7 @@ -import ArrayValue from '../dataTypes/ArrayValue'; +import ArrayValue, { ABSENT_JSON_ARRAY } from '../dataTypes/ArrayValue'; import createAtomicValue from '../dataTypes/createAtomicValue'; import ISequence from '../dataTypes/ISequence'; -import MapValue from '../dataTypes/MapValue'; +import MapValue, { AbsentJsonObject } from '../dataTypes/MapValue'; import sequenceFactory from '../dataTypes/sequenceFactory'; import { SequenceMultiplicity, ValueType } from '../dataTypes/Value'; import { BUILT_IN_NAMESPACE_URIS } from '../staticallyKnownNamespaces'; @@ -15,7 +15,8 @@ function convert(obj: any): ISequence { if (Array.isArray(obj)) { return sequenceFactory.singleton( new ArrayValue( - obj.map((subObject) => createDoublyIterableSequence(convert(subObject))) + obj.map((subObject) => createDoublyIterableSequence(convert(subObject))), + ABSENT_JSON_ARRAY ) ); } @@ -31,7 +32,8 @@ function convert(obj: any): ISequence { key: createAtomicValue(key, ValueType.XSSTRING), value: createDoublyIterableSequence(convert(objThingy[key])), }; - }) + }), + AbsentJsonObject ) ); case 'number': diff --git a/src/expressions/functions/builtInFunctions_maps.ts b/src/expressions/functions/builtInFunctions_maps.ts index 3a8681942..b152668bf 100644 --- a/src/expressions/functions/builtInFunctions_maps.ts +++ b/src/expressions/functions/builtInFunctions_maps.ts @@ -1,5 +1,5 @@ import createAtomicValue from '../dataTypes/createAtomicValue'; -import MapValue from '../dataTypes/MapValue'; +import MapValue, { AbsentJsonObject } from '../dataTypes/MapValue'; import sequenceFactory from '../dataTypes/sequenceFactory'; import { SequenceMultiplicity, ValueType } from '../dataTypes/Value'; import { BUILT_IN_NAMESPACE_URIS } from '../staticallyKnownNamespaces'; @@ -78,7 +78,8 @@ const mapMerge: FunctionDefinitionType = ( resultingKeyValuePairs.push(keyValuePair); }); return resultingKeyValuePairs; - }, []) + }, []), + AbsentJsonObject ) ) ); @@ -109,7 +110,7 @@ const mapPut: FunctionDefinitionType = ( value: createDoublyIterableSequence(newValueSequence), }); } - return sequenceFactory.singleton(new MapValue(resultingKeyValuePairs)); + return sequenceFactory.singleton(new MapValue(resultingKeyValuePairs, AbsentJsonObject)); }); }; @@ -121,7 +122,11 @@ const mapEntry: FunctionDefinitionType = ( value ) => { return keySequence.map( - (onlyKey) => new MapValue([{ key: onlyKey, value: createDoublyIterableSequence(value) }]) + (onlyKey) => + new MapValue( + [{ key: onlyKey, value: createDoublyIterableSequence(value) }], + AbsentJsonObject + ) ); }; @@ -182,7 +187,9 @@ const mapRemove: FunctionDefinitionType = ( resultingKeyValuePairs.splice(indexOfExistingPair, 1); } }); - return sequenceFactory.singleton(new MapValue(resultingKeyValuePairs)); + return sequenceFactory.singleton( + new MapValue(resultingKeyValuePairs, AbsentJsonObject) + ); }); }); }; @@ -287,15 +294,18 @@ const declarations: BuiltinDeclarationType[] = [ staticContext, maps, sequenceFactory.singleton( - new MapValue([ - { - key: createAtomicValue('duplicates', ValueType.XSSTRING), - value: () => - sequenceFactory.singleton( - createAtomicValue('use-first', ValueType.XSSTRING) - ), - }, - ]) + new MapValue( + [ + { + key: createAtomicValue('duplicates', ValueType.XSSTRING), + value: () => + sequenceFactory.singleton( + createAtomicValue('use-first', ValueType.XSSTRING) + ), + }, + ], + AbsentJsonObject + ) ) ); }, diff --git a/src/expressions/functions/builtInFunctions_numeric.ts b/src/expressions/functions/builtInFunctions_numeric.ts index 32572441f..fb89aff3f 100644 --- a/src/expressions/functions/builtInFunctions_numeric.ts +++ b/src/expressions/functions/builtInFunctions_numeric.ts @@ -5,7 +5,7 @@ import createAtomicValue from '../dataTypes/createAtomicValue'; import FunctionValue from '../dataTypes/FunctionValue'; import ISequence from '../dataTypes/ISequence'; import isSubtypeOf from '../dataTypes/isSubtypeOf'; -import MapValue from '../dataTypes/MapValue'; +import MapValue, { AbsentJsonObject } from '../dataTypes/MapValue'; import sequenceFactory from '../dataTypes/sequenceFactory'; import { SequenceMultiplicity, ValueType } from '../dataTypes/Value'; import DynamicContext from '../DynamicContext'; @@ -210,54 +210,59 @@ const fnRandomNumberGenerator: FunctionDefinitionType = ( ) => { // Ignore the optional seed, as Math.random does not support a seed return sequenceFactory.singleton( - new MapValue([ - { - key: createAtomicValue('number', ValueType.XSSTRING), - value: () => - sequenceFactory.singleton(createAtomicValue(Math.random(), ValueType.XSDOUBLE)), - }, - { - key: createAtomicValue('next', ValueType.XSSTRING), - value: () => - sequenceFactory.singleton( - new FunctionValue({ - value: fnRandomNumberGenerator, - isAnonymous: true, - localName: '', - namespaceURI: '', - argumentTypes: [], - arity: 0, - returnType: { - type: ValueType.MAP, - mult: SequenceMultiplicity.EXACTLY_ONE, - }, - }) - ), - }, - { - key: createAtomicValue('permute', ValueType.XSSTRING), - value: () => - sequenceFactory.singleton( - new FunctionValue({ - value: returnRandomItemFromSequence, - isAnonymous: true, - localName: '', - namespaceURI: '', - argumentTypes: [ - { + new MapValue( + [ + { + key: createAtomicValue('number', ValueType.XSSTRING), + value: () => + sequenceFactory.singleton( + createAtomicValue(Math.random(), ValueType.XSDOUBLE) + ), + }, + { + key: createAtomicValue('next', ValueType.XSSTRING), + value: () => + sequenceFactory.singleton( + new FunctionValue({ + value: fnRandomNumberGenerator, + isAnonymous: true, + localName: '', + namespaceURI: '', + argumentTypes: [], + arity: 0, + returnType: { + type: ValueType.MAP, + mult: SequenceMultiplicity.EXACTLY_ONE, + }, + }) + ), + }, + { + key: createAtomicValue('permute', ValueType.XSSTRING), + value: () => + sequenceFactory.singleton( + new FunctionValue({ + value: returnRandomItemFromSequence, + isAnonymous: true, + localName: '', + namespaceURI: '', + argumentTypes: [ + { + type: ValueType.ITEM, + mult: SequenceMultiplicity.ZERO_OR_MORE, + }, + ], + arity: 1, + returnType: { type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_MORE, }, - ], - arity: 1, - returnType: { - type: ValueType.ITEM, - mult: SequenceMultiplicity.ZERO_OR_MORE, - }, - }) - ), - }, - ]) + }) + ), + }, + ], + AbsentJsonObject + ) ); }; diff --git a/src/expressions/maps/MapConstructor.ts b/src/expressions/maps/MapConstructor.ts index 220bb5c43..9d0dfb575 100644 --- a/src/expressions/maps/MapConstructor.ts +++ b/src/expressions/maps/MapConstructor.ts @@ -1,5 +1,5 @@ import atomize from '../dataTypes/atomize'; -import MapValue from '../dataTypes/MapValue'; +import MapValue, { AbsentJsonObject } from '../dataTypes/MapValue'; import sequenceFactory from '../dataTypes/sequenceFactory'; import { SequenceType } from '../dataTypes/Value'; import DynamicContext from '../DynamicContext'; @@ -58,7 +58,8 @@ class MapConstructor extends Expression { executionParameters ) ), - })) + })), + AbsentJsonObject ) ) ); diff --git a/src/parsing/convertXDMReturnValue.ts b/src/parsing/convertXDMReturnValue.ts index 7c73352b4..1fac713bb 100644 --- a/src/parsing/convertXDMReturnValue.ts +++ b/src/parsing/convertXDMReturnValue.ts @@ -8,9 +8,9 @@ import ISequence from '../expressions/dataTypes/ISequence'; import isSubtypeOf from '../expressions/dataTypes/isSubtypeOf'; import MapValue from '../expressions/dataTypes/MapValue'; import sequenceFactory from '../expressions/dataTypes/sequenceFactory'; -import Value, { ValueType } from '../expressions/dataTypes/Value'; +import { ValueType } from '../expressions/dataTypes/Value'; import ExecutionParameters from '../expressions/ExecutionParameters'; -import { IIterator, IterationHint } from '../expressions/util/iterators'; +import { IterationHint } from '../expressions/util/iterators'; import transformXPathItemToJavascriptObject, { transformArrayToArray, transformMapToObject, @@ -149,11 +149,8 @@ export default function convertXDMReturnValue< if (!isSubtypeOf(first.type, ValueType.MAP)) { throw new Error('Expected XPath ' + expression + ' to resolve to a map'); } - const transformedMap = transformMapToObject( - first as MapValue, - executionParameters - ).next(IterationHint.NONE); - return transformedMap.value as IReturnTypes[TReturnType]; + const transformedMap = transformMapToObject(first as MapValue, executionParameters); + return transformedMap as IReturnTypes[TReturnType]; } case ReturnType.ARRAY: { @@ -169,8 +166,8 @@ export default function convertXDMReturnValue< const transformedArray = transformArrayToArray( first as ArrayValue, executionParameters - ).next(IterationHint.NONE); - return transformedArray.value as IReturnTypes[TReturnType]; + ); + return transformedArray as IReturnTypes[TReturnType]; } case ReturnType.NUMBERS: { @@ -185,24 +182,19 @@ export default function convertXDMReturnValue< case ReturnType.ASYNC_ITERATOR: { const it = rawResults.value; - let transformedValueGenerator: IIterator = null; let done = false; const getNextResult = () => { while (!done) { - if (!transformedValueGenerator) { - const value = it.next(IterationHint.NONE); - if (value.done) { - done = true; - break; - } - transformedValueGenerator = transformXPathItemToJavascriptObject( - value.value, - executionParameters - ); + const value = it.next(IterationHint.NONE); + if (value.done) { + done = true; + break; } - const transformedValue = transformedValueGenerator.next(IterationHint.NONE); - transformedValueGenerator = null; - return transformedValue; + const transformedValue = transformXPathItemToJavascriptObject( + value.value, + executionParameters + ); + return { done: false, value: transformedValue }; } return Promise.resolve({ done: true, @@ -223,9 +215,9 @@ export default function convertXDMReturnValue< }), }; } else { - toReturn = ({ + toReturn = { next: () => new Promise((resolve) => resolve(getNextResult())), - } as unknown) as AsyncIterableIterator; + } as unknown as AsyncIterableIterator; } return toReturn as IReturnTypes[TReturnType]; } @@ -254,15 +246,15 @@ export default function convertXDMReturnValue< const transformedArray = transformArrayToArray( first as ArrayValue, executionParameters - ).next(IterationHint.NONE); - return transformedArray.value as IReturnTypes[TReturnType]; + ); + return transformedArray as IReturnTypes[TReturnType]; } if (isSubtypeOf(first.type, ValueType.MAP)) { const transformedMap = transformMapToObject( first as MapValue, executionParameters - ).next(IterationHint.NONE); - return transformedMap.value as IReturnTypes[TReturnType]; + ); + return transformedMap as IReturnTypes[TReturnType]; } return atomizeSingleValue(first, executionParameters).first() .value as IReturnTypes[TReturnType]; diff --git a/src/registerCustomXPathFunction.ts b/src/registerCustomXPathFunction.ts index 2ea4dfc8f..333bd09af 100644 --- a/src/registerCustomXPathFunction.ts +++ b/src/registerCustomXPathFunction.ts @@ -71,10 +71,7 @@ function adaptXPathValueToJavascriptValue( if (valueSequence.isEmpty()) { return null; } - return transformXPathItemToJavascriptObject( - valueSequence.first(), - executionParameters - ).next(IterationHint.NONE).value; + return transformXPathItemToJavascriptObject(valueSequence.first(), executionParameters); } if ( @@ -85,20 +82,14 @@ function adaptXPathValueToJavascriptValue( if (isSubtypeOf(value.type, ValueType.ATTRIBUTE)) { throw new Error('Cannot pass attribute nodes to custom functions'); } - return transformXPathItemToJavascriptObject(value, executionParameters).next( - IterationHint.NONE - ).value; + return transformXPathItemToJavascriptObject(value, executionParameters); }); } - return transformXPathItemToJavascriptObject(valueSequence.first(), executionParameters).next( - IterationHint.NONE - ).value; + return transformXPathItemToJavascriptObject(valueSequence.first(), executionParameters); } -function splitFunctionName( - name: string | { localName: string; namespaceURI: string } -): { +function splitFunctionName(name: string | { localName: string; namespaceURI: string }): { localName: string; namespaceURI: string; } { diff --git a/src/transformXPathItemToJavascriptObject.ts b/src/transformXPathItemToJavascriptObject.ts index e89311cfe..f37ab466f 100644 --- a/src/transformXPathItemToJavascriptObject.ts +++ b/src/transformXPathItemToJavascriptObject.ts @@ -1,114 +1,85 @@ import { NodePointer } from './domClone/Pointer'; import realizeDom from './domClone/realizeDom'; -import ArrayValue from './expressions/dataTypes/ArrayValue'; +import ArrayValue, { ABSENT_JSON_ARRAY } from './expressions/dataTypes/ArrayValue'; import isSubtypeOf from './expressions/dataTypes/isSubtypeOf'; -import MapValue from './expressions/dataTypes/MapValue'; +import MapValue, { AbsentJsonObject } from './expressions/dataTypes/MapValue'; import Value, { ValueType } from './expressions/dataTypes/Value'; import DateTime from './expressions/dataTypes/valueTypes/DateTime'; import QName from './expressions/dataTypes/valueTypes/QName'; import ExecutionParameters from './expressions/ExecutionParameters'; -import { DONE_TOKEN, IIterator, IterationHint, ready } from './expressions/util/iterators'; +import { ValidValue } from './types/createTypedValueFactory'; export function transformMapToObject( map: MapValue, executionParameters: ExecutionParameters -): IIterator { - const mapObj: { [s: string]: Value } = {}; - let i = 0; - let done = false; - let transformedValueIterator: IIterator = null; - return { - next: () => { - if (done) { - return DONE_TOKEN; - } - while (i < map.keyValuePairs.length) { - // Assume the keys for a map are strings. - const key = map.keyValuePairs[i].key.value as string; +): { [s: string]: ValidValue } { + if (map.jsonObject !== AbsentJsonObject) { + return map.jsonObject; + } - if (!transformedValueIterator) { - const keyValuePair = map.keyValuePairs[i]; - const val = keyValuePair - .value() - .switchCases({ - default: (seq) => seq, - multiple: () => { - throw new Error( - `Serialization error: The value of an entry in a map is expected to be a single item or an empty sequence. Use arrays when putting multiple values in a map. The value of the key ${keyValuePair.key.value} holds multiple items` - ); - }, - }) - .first(); - if (val === null) { - mapObj[key] = null; - i++; - continue; - } + const mapObj: { [s: string]: ValidValue } = {}; + for (let i = 0; i < map.keyValuePairs.length; ++i) { + // Assume the keys for a map are strings. + const key = map.keyValuePairs[i].key.value as string; - transformedValueIterator = transformXPathItemToJavascriptObject( - val, - executionParameters + const keyValuePair = map.keyValuePairs[i]; + const val = keyValuePair + .value() + .switchCases({ + default: (seq) => seq, + multiple: () => { + throw new Error( + `Serialization error: The value of an entry in a map is expected to be a single item or an empty sequence. Use arrays when putting multiple values in a map. The value of the key ${keyValuePair.key.value} holds multiple items` ); - } - const transformedValue = transformedValueIterator.next(IterationHint.NONE); - transformedValueIterator = null; - mapObj[key] = transformedValue.value; - i++; - } - done = true; - return ready(mapObj); - }, - }; + }, + }) + .first(); + if (val === null) { + mapObj[key] = null; + i++; + continue; + } + + const transformedValue = transformXPathItemToJavascriptObject(val, executionParameters); + mapObj[key] = transformedValue; + } + return mapObj; } export function transformArrayToArray( array: ArrayValue, executionParameters: ExecutionParameters -): IIterator { - const arr: Value[] = []; - let i = 0; - let done = false; - let transformedMemberGenerator: IIterator = null; - return { - next: () => { - if (done) { - return DONE_TOKEN; - } - while (i < array.members.length) { - if (!transformedMemberGenerator) { - const val = array.members[i]() - .switchCases({ - default: (seq) => seq, - multiple: () => { - throw new Error( - `Serialization error: The value of an entry in an array is expected to be a single item or an empty sequence. Use nested arrays when putting multiple values in an array.` - ); - }, - }) - .first(); - if (val === null) { - arr[i++] = null; - continue; - } - transformedMemberGenerator = transformXPathItemToJavascriptObject( - val, - executionParameters +): ValidValue[] { + if (array.jsonArray !== ABSENT_JSON_ARRAY) { + return array.jsonArray; + } + + const arr: ValidValue[] = []; + for (let i = 0; i < array.members.length; ++i) { + const val = array.members[i]() + .switchCases({ + default: (seq) => seq, + multiple: () => { + throw new Error( + `Serialization error: The value of an entry in an array is expected to be a single item or an empty sequence. Use nested arrays when putting multiple values in an array.` ); - } - const transformedValue = transformedMemberGenerator.next(IterationHint.NONE); - transformedMemberGenerator = null; - arr[i++] = transformedValue.value; - } - done = true; - return ready(arr); - }, - }; + }, + }) + .first(); + if (val === null) { + arr[i] = null; + continue; + } + const transformedValue = transformXPathItemToJavascriptObject(val, executionParameters); + arr[i] = transformedValue; + } + return arr; } export default function transformXPathItemToJavascriptObject( value: Value, executionParameters: ExecutionParameters -): IIterator { +): ValidValue { if (isSubtypeOf(value.type, ValueType.MAP)) { return transformMapToObject(value as MapValue, executionParameters); } @@ -117,9 +88,7 @@ export default function transformXPathItemToJavascriptObject( } if (isSubtypeOf(value.type, ValueType.XSQNAME)) { const qualifiedName = value.value as QName; - return { - next: () => ready(`Q{${qualifiedName.namespaceURI || ''}}${qualifiedName.localName}`), - }; + return `Q{${qualifiedName.namespaceURI || ''}}${qualifiedName.localName}`; } // Make it actual here @@ -133,9 +102,7 @@ export default function transformXPathItemToJavascriptObject( case ValueType.XSGMONTH: case ValueType.XSGDAY: { const temporalValue = value.value as DateTime; - return { - next: () => ready(temporalValue.toJavaScriptDate()), - }; + return temporalValue.toJavaScriptDate(); } case ValueType.ATTRIBUTE: case ValueType.NODE: @@ -145,14 +112,10 @@ export default function transformXPathItemToJavascriptObject( case ValueType.PROCESSINGINSTRUCTION: case ValueType.COMMENT: { const nodeValue = value.value as NodePointer; - return { - next: () => ready(realizeDom(nodeValue, executionParameters, false)), - }; + return realizeDom(nodeValue, executionParameters, false); } default: - return { - next: () => ready(value.value), - }; + return value.value; } } diff --git a/test/specs/parsing/evaluateXPath.tests.ts b/test/specs/parsing/evaluateXPath.tests.ts index f843ce0c1..dfebdc334 100644 --- a/test/specs/parsing/evaluateXPath.tests.ts +++ b/test/specs/parsing/evaluateXPath.tests.ts @@ -247,6 +247,20 @@ describe('evaluateXPath', () => { it('returns a nested array', () => { chai.assert.deepEqual(evaluateXPathToArray('[1, [2, 2.5], 3]'), [1, [2, 2.5], 3]); }); + it('keeps references intact when it can', () => { + const arr = [1, 2, 3]; + // Note the strictEqual here: We want reference equality + chai.assert.strictEqual(evaluateXPathToArray('$arr', null, null, { arr }), arr); + }); + it('keeps references intact when it can: through functions', () => { + const arr = [1, 2, [3, 4]]; + // Note the strictEqual here: We want reference equality + chai.assert.strictEqual( + evaluateXPathToArray('$arr?3', null, null, { arr }), + arr[2] as number[] + ); + }); + it('Transfroms singleton sequences to null', () => { chai.assert.deepEqual(evaluateXPathToArray('[1, (), 3]'), [1, null, 3]); }); @@ -265,6 +279,16 @@ describe('evaluateXPath', () => { it('returns a nested map', () => { chai.assert.deepEqual(evaluateXPathToMap('map{1:map{2:3}}'), { 1: { 2: 3 } }); }); + it('keeps references intact when it can: simple case', () => { + const map = { a: 1, b: 2 }; + // Note the strictEqual here: We want reference equality + chai.assert.strictEqual(evaluateXPathToMap('$map', null, null, { map }), map); + }); + it('keeps references intact when it can: through functions', () => { + const map = { a: 1, b: { c: 2 } }; + // Note the strictEqual here: We want reference equality + chai.assert.strictEqual(evaluateXPathToMap('$map?b', null, null, { map }), map.b); + }); it('Transfroms singleton sequences to null', () => { chai.assert.deepEqual(evaluateXPathToMap('map{1:()}'), { 1: null }); });