├── .bundlemonrc ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ ├── bundlesize.yml │ ├── download-qt3 │ └── action.yml │ ├── linter.yml │ └── main.yml ├── .gitignore ├── .gitmodules ├── .npmrc ├── .nycrc ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── api-extractor.json ├── build.js ├── demo ├── .gitignore ├── index.html ├── main.ts ├── package-lock.json ├── package.json ├── tsconfig.json └── tslint.json ├── fuzzers ├── corpus_based_fuzzer.ts ├── engine.ts ├── fuzzer.ts ├── index.ts ├── iso_corpus.ts ├── mutators.ts ├── tsconfig.json ├── tslint.json └── xpath-31-function-catalog-corpus.ts ├── karma.conf.ts ├── package-lock.json ├── package.json ├── performance ├── backend.benchmark.ts ├── compare.benchmark.ts ├── evaluateXPath.benchmark.ts ├── tslint.json ├── union.benchmark.ts └── utils │ └── loadFile.ts ├── src ├── documentWriter │ ├── IDocumentWriter.ts │ ├── domBackedDocumentWriter.ts │ └── wrapExternalDocumentWriter.ts ├── domClone │ ├── Pointer.ts │ ├── deepCloneNode.ts │ └── realizeDom.ts ├── domFacade │ ├── ConcreteNode.ts │ ├── DomFacade.ts │ ├── ExternalDomFacade.ts │ └── IDomFacade.ts ├── evaluateUpdatingExpression.ts ├── evaluateUpdatingExpressionSync.ts ├── evaluateXPath.ts ├── evaluateXPathToArray.ts ├── evaluateXPathToAsyncIterator.ts ├── evaluateXPathToBoolean.ts ├── evaluateXPathToFirstNode.ts ├── evaluateXPathToMap.ts ├── evaluateXPathToNodes.ts ├── evaluateXPathToNumber.ts ├── evaluateXPathToNumbers.ts ├── evaluateXPathToString.ts ├── evaluateXPathToStrings.ts ├── evaluationUtils │ ├── PositionedError.ts │ ├── buildEvaluationContext.ts │ ├── convertUpdateResultToTransferable.ts │ └── printAndRethrowError.ts ├── executePendingUpdateList.ts ├── expressions │ ├── Context.ts │ ├── DynamicContext.ts │ ├── ExecutionParameters.ts │ ├── ExecutionSpecificStaticContext.ts │ ├── Expression.ts │ ├── FlworExpression.ts │ ├── ForExpression.ts │ ├── LetExpression.ts │ ├── NamedFunctionRef.ts │ ├── OrderByExpression.ts │ ├── PossiblyUpdatingExpression.ts │ ├── Specificity.ts │ ├── StaticContext.ts │ ├── UnfocusableDynamicContext.ts │ ├── UpdatingExpressionResult.ts │ ├── VarRef.ts │ ├── WhereExpression.ts │ ├── XPathErrors.ts │ ├── adaptJavaScriptValueToXPathValue.ts │ ├── arrays │ │ ├── CurlyArrayConstructor.ts │ │ └── SquareArrayConstructor.ts │ ├── axes │ │ ├── AncestorAxis.ts │ │ ├── AttributeAxis.ts │ │ ├── ChildAxis.ts │ │ ├── DescendantAxis.ts │ │ ├── FollowingAxis.ts │ │ ├── FollowingSiblingAxis.ts │ │ ├── ParentAxis.ts │ │ ├── PrecedingAxis.ts │ │ ├── PrecedingSiblingAxis.ts │ │ ├── SelfAxis.ts │ │ └── validateContextNode.ts │ ├── conditional │ │ └── IfExpression.ts │ ├── dataTypes │ │ ├── ArrayValue.ts │ │ ├── AtomicValue.ts │ │ ├── ETypeNames.ts │ │ ├── FunctionValue.ts │ │ ├── ISequence.ts │ │ ├── MapValue.ts │ │ ├── Sequences │ │ │ ├── ArrayBackedSequence.ts │ │ │ ├── EmptySequence.ts │ │ │ ├── IteratorBackedSequence.ts │ │ │ ├── SingletonSequence.ts │ │ │ └── getEffectiveBooleanValue.ts │ │ ├── Value.ts │ │ ├── Variety.ts │ │ ├── atomize.ts │ │ ├── builtins │ │ │ ├── builtinDataTypesByType.ts │ │ │ ├── builtinModels.ts │ │ │ └── dataTypeValidatorByType.ts │ │ ├── canCastToType.ts │ │ ├── castToType.ts │ │ ├── casting │ │ │ ├── AtomicValueDataType.ts │ │ │ ├── CastResult.ts │ │ │ ├── castToAnyURI.ts │ │ │ ├── castToBase64Binary.ts │ │ │ ├── castToBoolean.ts │ │ │ ├── castToDate.ts │ │ │ ├── castToDateTime.ts │ │ │ ├── castToDayTimeDuration.ts │ │ │ ├── castToDecimal.ts │ │ │ ├── castToDouble.ts │ │ │ ├── castToDuration.ts │ │ │ ├── castToFloat.ts │ │ │ ├── castToFloatLikeType.ts │ │ │ ├── castToGDay.ts │ │ │ ├── castToGMonth.ts │ │ │ ├── castToGMonthDay.ts │ │ │ ├── castToGYear.ts │ │ │ ├── castToGYearMonth.ts │ │ │ ├── castToHexBinary.ts │ │ │ ├── castToInteger.ts │ │ │ ├── castToNumeric.ts │ │ │ ├── castToString.ts │ │ │ ├── castToStringLikeType.ts │ │ │ ├── castToTime.ts │ │ │ ├── castToUntypedAtomic.ts │ │ │ ├── castToYearMonthDuration.ts │ │ │ └── tryCastToType.ts │ │ ├── createAtomicValue.ts │ │ ├── createPointerValue.ts │ │ ├── documentOrderUtils.ts │ │ ├── facets │ │ │ ├── comparators │ │ │ │ └── decimalComparator.ts │ │ │ └── facetsByDataType.ts │ │ ├── isSubtypeOf.ts │ │ ├── promoteToType.ts │ │ ├── sequenceFactory.ts │ │ ├── typeHelpers.ts │ │ └── valueTypes │ │ │ ├── AbstractDuration.ts │ │ │ ├── DateTime.ts │ │ │ ├── DayTimeDuration.ts │ │ │ ├── Duration.ts │ │ │ ├── QName.ts │ │ │ └── YearMonthDuration.ts │ ├── debug │ │ ├── StackTraceEntry.ts │ │ └── StackTraceGenerator.ts │ ├── functions │ │ ├── FunctionCall.ts │ │ ├── FunctionDefinitionType.ts │ │ ├── FunctionOperationErrors.ts │ │ ├── InlineFunction.ts │ │ ├── argumentHelper.ts │ │ ├── argumentListToString.ts │ │ ├── builtInFunctions.ts │ │ ├── builtInFunctions_arrays.ts │ │ ├── builtInFunctions_arrays_get.ts │ │ ├── builtInFunctions_boolean.ts │ │ ├── builtInFunctions_context.ts │ │ ├── builtInFunctions_dataTypeConstructors.ts │ │ ├── builtInFunctions_datetime.ts │ │ ├── builtInFunctions_debugging.ts │ │ ├── builtInFunctions_duration.ts │ │ ├── builtInFunctions_error.ts │ │ ├── builtInFunctions_fontoxpath.ts │ │ ├── builtInFunctions_functions.ts │ │ ├── builtInFunctions_identifiers.ts │ │ ├── builtInFunctions_json.ts │ │ ├── builtInFunctions_maps.ts │ │ ├── builtInFunctions_maps_get.ts │ │ ├── builtInFunctions_math.ts │ │ ├── builtInFunctions_node.ts │ │ ├── builtInFunctions_numeric.ts │ │ ├── builtInFunctions_operators.ts │ │ ├── builtInFunctions_qnames.ts │ │ ├── builtInFunctions_sequences.ts │ │ ├── builtInFunctions_sequences_deepEqual.ts │ │ ├── builtInFunctions_string.ts │ │ ├── convertItemsToCommonType.ts │ │ ├── functionRegistry.ts │ │ ├── generateId.ts │ │ └── isSameMapKey.ts │ ├── literals │ │ └── Literal.ts │ ├── maps │ │ └── MapConstructor.ts │ ├── operators │ │ ├── IntersectExcept.ts │ │ ├── SequenceOperator.ts │ │ ├── SimpleMapOperator.ts │ │ ├── Union.ts │ │ ├── UniversalExpression.ts │ │ ├── arithmetic │ │ │ ├── BinaryEvaluationFunctionMap.ts │ │ │ ├── BinaryOperator.ts │ │ │ └── Unary.ts │ │ ├── boolean │ │ │ ├── AndOperator.ts │ │ │ └── OrOperator.ts │ │ ├── compares │ │ │ ├── GeneralCompare.ts │ │ │ ├── NodeCompare.ts │ │ │ ├── ValueCompare.ts │ │ │ └── arePointersEqual.ts │ │ └── types │ │ │ ├── CastAsOperator.ts │ │ │ ├── CastableAsOperator.ts │ │ │ └── InstanceOfOperator.ts │ ├── path │ │ ├── AbsolutePathExpression.ts │ │ ├── ContextItemExpression.ts │ │ └── PathExpression.ts │ ├── postfix │ │ ├── Filter.ts │ │ ├── Lookup.ts │ │ ├── UnaryLookup.ts │ │ └── evaluateLookup.ts │ ├── quantified │ │ └── QuantifiedExpression.ts │ ├── staticallyKnownNamespaces.ts │ ├── tests │ │ ├── KindTest.ts │ │ ├── NameTest.ts │ │ ├── PITest.ts │ │ ├── TestAbstractExpression.ts │ │ └── TypeTest.ts │ ├── util │ │ ├── Bucket.ts │ │ ├── Random.ts │ │ ├── atomizeSequence.ts │ │ ├── concatSequences.ts │ │ ├── createChildGenerator.ts │ │ ├── createDescendantGenerator.ts │ │ ├── createDoublyIterableSequence.ts │ │ ├── createSingleValueIterator.ts │ │ ├── iterators.ts │ │ ├── sequenceEvery.ts │ │ ├── sortedSequenceUtils.ts │ │ └── zipSingleton.ts │ ├── xquery-update │ │ ├── DeleteExpression.ts │ │ ├── IPendingUpdate.ts │ │ ├── InsertExpression.ts │ │ ├── RenameExpression.ts │ │ ├── ReplaceExpression.ts │ │ ├── TransformExpression.ts │ │ ├── UpdatingExpression.ts │ │ ├── UpdatingFunctionDefinitionType.ts │ │ ├── UpdatingFunctionValue.ts │ │ ├── XQueryUpdateFacilityErrors.ts │ │ ├── applyPulPrimitives.ts │ │ ├── createPendingUpdateFromTransferable.ts │ │ ├── pendingUpdates │ │ │ ├── DeletePendingUpdate.ts │ │ │ ├── InsertAfterPendingUpdate.ts │ │ │ ├── InsertAttributesPendingUpdate.ts │ │ │ ├── InsertBeforePendingUpdate.ts │ │ │ ├── InsertIntoAsFirstPendingUpdate.ts │ │ │ ├── InsertIntoAsLastPendingUpdate.ts │ │ │ ├── InsertIntoPendingUpdate.ts │ │ │ ├── InsertPendingUpdate.ts │ │ │ ├── RenamePendingUpdate.ts │ │ │ ├── ReplaceElementContentPendingUpdate.ts │ │ │ ├── ReplaceNodePendingUpdate.ts │ │ │ └── ReplaceValuePendingUpdate.ts │ │ ├── pulPrimitives.ts │ │ └── pulRoutines.ts │ └── xquery │ │ ├── AttributeConstructor.ts │ │ ├── CommentConstructor.ts │ │ ├── ElementConstructor.ts │ │ ├── ElementConstructorContent.ts │ │ ├── PIConstructor.ts │ │ ├── SwitchExpression.ts │ │ ├── TextConstructor.ts │ │ ├── TypeSwitchExpression.ts │ │ ├── XQueryErrors.ts │ │ └── nameExpression.ts ├── getBuckets.ts ├── index.ts ├── jsCodegen │ ├── CodeGenContext.ts │ ├── JavaScriptCompiledXPath.ts │ ├── compileAstToJavaScript.ts │ ├── compileXPathToJavaScript.ts │ ├── emitAxis.ts │ ├── emitBaseExpr.ts │ ├── emitCompare.ts │ ├── emitFunctionCallExpr.ts │ ├── emitHelpers.ts │ ├── emitLiterals.ts │ ├── emitLogicalExpr.ts │ ├── emitOperand.ts │ ├── emitPathExpr.ts │ ├── emitTest.ts │ ├── escapeJavaScriptString.ts │ ├── executeJavaScriptCompiledXPath.ts │ └── runtimeLib.ts ├── nodesFactory │ ├── DomBackedNodesFactory.ts │ ├── INodesFactory.ts │ ├── ISimpleNodesFactory.ts │ └── wrapExternalNodesFactory.ts ├── parseScript.ts ├── parsing │ ├── astHelper.ts │ ├── compileAstToExpression.ts │ ├── compiledExpressionCache.ts │ ├── convertXDMReturnValue.ts │ ├── convertXmlToAst.ts │ ├── evaluableExpressionToString.ts │ ├── globalModuleCache.ts │ ├── literalParser.ts │ ├── nameParser.ts │ ├── normalizeEndOfLines.ts │ ├── parseExpression.ts │ ├── parsingFunctions.ts │ ├── parsingUtils.ts │ ├── processProlog.ts │ ├── prscParser.ts │ ├── staticallyCompileXPath.ts │ ├── tokens.ts │ ├── typesParser.ts │ └── whitespaceParser.ts ├── performance.ts ├── precompileXPath.ts ├── registerCustomXPathFunction.ts ├── registerXQueryModule.ts ├── transformXPathItemToJavascriptObject.ts ├── typeInference │ ├── AnnotationContext.ts │ ├── README.md │ ├── annotateAST.ts │ ├── annotateArrayConstructor.ts │ ├── annotateArrowExpr.ts │ ├── annotateBinaryOperator.ts │ ├── annotateCastOperators.ts │ ├── annotateCompareOperator.ts │ ├── annotateContextItemExpr.ts │ ├── annotateDynamicFunctionInvocationExpr.ts │ ├── annotateFlworExpression.ts │ ├── annotateFunctionCall.ts │ ├── annotateIfThenElseExpr.ts │ ├── annotateInstanceOfExpr.ts │ ├── annotateLogicalOperator.ts │ ├── annotateMapConstructor.ts │ ├── annotateNamedFunctionRef.ts │ ├── annotatePathExpr.ts │ ├── annotateQuantifiedExpr.ts │ ├── annotateRangeSequenceOperator.ts │ ├── annotateSequenceOperator.ts │ ├── annotateSetOperators.ts │ ├── annotateSimpleMapExpr.ts │ ├── annotateStringConcatenateOperator.ts │ ├── annotateTypeSwitchOperator.ts │ ├── annotateUnaryLookup.ts │ ├── annotateUnaryOperator.ts │ └── annotateVarRef.ts └── types │ ├── Options.ts │ ├── Types.ts │ └── createTypedValueFactory.ts ├── test ├── .eslintrc.js ├── assets │ ├── failingXQUTSXQueryXTestNames.csv │ ├── failingXQueryXTestNames.csv │ ├── jsCodeGenReport.csv │ ├── overrides │ │ └── XQUTS │ │ │ └── Queries │ │ │ └── XQueryX │ │ │ ├── RenameExpressions │ │ │ └── complex-renames-q8-test2.xqx │ │ │ └── ReplaceExpressions │ │ │ ├── complex-replacevalues-q1.xqx │ │ │ ├── complex-replacevalues-q10-test2.xqx │ │ │ ├── complex-replacevalues-q11-test2.xqx │ │ │ ├── complex-replacevalues-q13-test2.xqx │ │ │ ├── complex-replacevalues-q2.xqx │ │ │ ├── complex-replacevalues-q5-test2.xqx │ │ │ └── complex-replacevalues-q8-test2.xqx │ ├── runnableTestSets.csv │ ├── unrunnableTestCases.csv │ └── unrunnableXQUTSTestCases.csv ├── browsertests.ts ├── corpusfuzzertests.ts ├── helpers │ ├── evaluateXPathToAsyncSingleton.ts │ ├── getPerformanceTests.ts │ ├── getSkippedTests.ts │ ├── jsonMlMapper.ts │ ├── qt3TestsTools.ts │ └── testFs.ts ├── install-assets.ps1 ├── install-assets.sh ├── qt3tests.ts ├── qt3testsBenchmark.ts ├── qt3testsXQueryX.ts ├── specs │ ├── DomFacade.tests.ts │ ├── annotation.tests.ts │ ├── expressions │ │ ├── Specificity.tests.ts │ │ ├── adaptJavaScriptValueToXPathValue.tests.ts │ │ ├── dataTypes │ │ │ ├── castToType.tests.ts │ │ │ ├── documentOrderUtils.tests.ts │ │ │ └── valueTypes │ │ │ │ ├── DateTime.tests.ts │ │ │ │ └── Duration.tests.ts │ │ ├── functions │ │ │ ├── argumentListToString.tests.ts │ │ │ └── functionRegistry.tests.ts │ │ ├── jsCodegen │ │ │ ├── compare.tests.ts │ │ │ └── string.tests.ts │ │ ├── postfix │ │ │ └── Filter.tests.ts │ │ └── util │ │ │ └── concatSequences.tests.ts │ ├── parsing │ │ ├── LetExpression.tests.ts │ │ ├── VarRef.tests.ts │ │ ├── arrays │ │ │ ├── ArrayConstructor.tests.ts │ │ │ ├── arrayFunctions.tests.ts │ │ │ └── arrayPredicates.tests.ts │ │ ├── asyncXPath.xq │ │ ├── axes │ │ │ ├── AncestorAxis.tests.ts │ │ │ ├── AttributeAxis.tests.ts │ │ │ ├── ChildAxis.tests.ts │ │ │ ├── DescendantAxis.tests.ts │ │ │ ├── FollowingAxis.tests.ts │ │ │ ├── FollowingSiblingAxis.tests.ts │ │ │ ├── ParentAxis.tests.ts │ │ │ ├── PrecedingAxis.tests.ts │ │ │ ├── PrecedingSibling.tests.ts │ │ │ └── SelfAxis.tests.ts │ │ ├── comments │ │ │ └── comments.tests.ts │ │ ├── compareSpecificity.tests.ts │ │ ├── conditional │ │ │ └── IfExpression.tests.ts │ │ ├── createSelectorFromXPath.tests.ts │ │ ├── createSelectorFromXPathAsync.tests.ts │ │ ├── debug │ │ │ └── stackTrace.tests.ts │ │ ├── deprecatedFeatures.tests.ts │ │ ├── evaluateXPath.tests.ts │ │ ├── flwor.tests.ts │ │ ├── forExpression.tests.ts │ │ ├── functions │ │ │ ├── FunctionCall.tests.ts │ │ │ ├── builtInFunctions.dataTypeConstructors.tests.ts │ │ │ ├── builtInFunctions.functions.tests.ts │ │ │ ├── builtInFunctions.math.tests.ts │ │ │ ├── builtinFunctions.context.tests.ts │ │ │ ├── builtinFunctions.datetime.tests.ts │ │ │ ├── builtinFunctions.duration.tests.ts │ │ │ ├── builtinFunctions.sequences.tests.ts │ │ │ ├── functionPlaceholder.tests.ts │ │ │ ├── functions.boolean.tests.ts │ │ │ ├── functions.debugging.tests.ts │ │ │ ├── functions.error.tests.ts │ │ │ ├── functions.fontoxpath.tests.ts │ │ │ ├── functions.json.tests.ts │ │ │ ├── functions.node.tests.ts │ │ │ ├── functions.numeric.tests.ts │ │ │ ├── functions.qnames.tests.ts │ │ │ ├── functions.string.tests.ts │ │ │ ├── functions.tests.ts │ │ │ └── inlineFunctions.tests.ts │ │ ├── getBucketForSelector.tests.ts │ │ ├── getBucketsForNode.tests.ts │ │ ├── indexOf.tests.ts │ │ ├── jsCodegen │ │ │ ├── astRejection.tests.ts │ │ │ ├── axes.tests.ts │ │ │ ├── blns.tests.ts │ │ │ ├── compare.tests.ts │ │ │ ├── compareAttributes.tests.ts │ │ │ ├── evaluateXPathWithJsCodegen.ts │ │ │ ├── functionCall.tests.ts │ │ │ ├── nodeTests.tests.ts │ │ │ ├── operators.tests.ts │ │ │ ├── pathExpr.tests.ts │ │ │ ├── predicates.tests.ts │ │ │ ├── returnValues.tests.ts │ │ │ └── wildcard.tests.ts │ │ ├── literals │ │ │ └── Literal.tests.ts │ │ ├── mainModules.tests.ts │ │ ├── maps │ │ │ ├── MapConstructor.tests.ts │ │ │ └── mapFunctions.tests.ts │ │ ├── operators │ │ │ ├── CastableAsOperator.tests.ts │ │ │ ├── IntersectExcept.tests.ts │ │ │ ├── SimpleMapOperator.tests.ts │ │ │ ├── UnionOperator.tests.ts │ │ │ ├── boolean │ │ │ │ ├── AndOperator.tests.ts │ │ │ │ ├── OrOperator.tests.ts │ │ │ │ └── operatorPrecedence.tests.ts │ │ │ ├── castOperator.tests.ts │ │ │ ├── compares │ │ │ │ └── Compare.tests.ts │ │ │ ├── numeric │ │ │ │ ├── BinaryOperator.tests.ts │ │ │ │ └── Unary.tests.ts │ │ │ ├── sequenceOperator.tests.ts │ │ │ ├── stringConcat.tests.ts │ │ │ └── types │ │ │ │ └── InstanceOfOperator.tests.ts │ │ ├── parseScript.tests.ts │ │ ├── path │ │ │ ├── AbsolutePathExpression.tests.ts │ │ │ └── PathExpression.tests.ts │ │ ├── performance.tests.ts │ │ ├── postfix │ │ │ ├── Filter.tests.ts │ │ │ └── Lookup.tests.ts │ │ ├── predicates │ │ │ └── predicates.tests.ts │ │ ├── quantified │ │ │ └── QuantifiedExpression.tests.ts │ │ ├── registerCustomXPathFunction.tests.ts │ │ ├── registerXQueryModule.tests.ts │ │ ├── tests │ │ │ ├── KindTest.tests.ts │ │ │ ├── NameTest.tests.ts │ │ │ └── PITest.tests.ts │ │ ├── usesHints.tests.ts │ │ ├── xquery-updating │ │ │ ├── DeleteExpression.tests.ts │ │ │ ├── InsertExpression.tests.ts │ │ │ ├── RenameExpression.tests.ts │ │ │ ├── ReplaceExpression.tests.ts │ │ │ ├── TransformExpression.tests.ts │ │ │ ├── assertUpdateList.ts │ │ │ ├── evaluateUpdatingExpression.tests.ts │ │ │ ├── evaluateUpdatingExpressionSync.tests.ts │ │ │ ├── executePendingUpdateList.tests.ts │ │ │ └── updatingFunctions.tests.ts │ │ └── xquery │ │ │ ├── AttributeConstructor.tests.ts │ │ │ ├── DefaultElementNamespace.tests.ts │ │ │ ├── DefaultFunctionNamespace.tests.ts │ │ │ ├── ElementConstructor.tests.ts │ │ │ ├── PIConstructor.tests.ts │ │ │ ├── SwitchExpression.tests.ts │ │ │ ├── TextConstructor.tests.ts │ │ │ ├── Typeswitch.tests.ts │ │ │ └── VariableDeclaration.tests.ts │ └── types │ │ └── createTypedValueFactory.tests.ts ├── testCodegenFacade.ts ├── testhook.js ├── tsconfig.json ├── tslint.json ├── xQueryXUtils.ts ├── xqutsTests.ts └── xqutsTestsXQueryX.ts └── tsconfig.json /.bundlemonrc: -------------------------------------------------------------------------------- 1 | { 2 | "baseDir": "./dist", 3 | "files": [ 4 | { 5 | "path": "fontoxpath.js" 6 | }, 7 | { 8 | "path": "fontoxpath.esm.js" 9 | } 10 | ], 11 | "reportOutput": [ 12 | "github" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/bundlesize.yml: -------------------------------------------------------------------------------- 1 | name: Check bundle size 2 | on: 3 | push: 4 | branches: ['master'] 5 | pull_request: 6 | types: [synchronize, opened, reopened] 7 | 8 | jobs: 9 | build: 10 | name: Compute bundle size 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: '20' 17 | - name: Install dependencies 18 | run: npm ci 19 | - name: override CI_COMMIT_SHA 20 | if: github.event_name == 'pull_request' 21 | run: echo "CI_COMMIT_SHA=${{ github.event.pull_request.head.sha}}" >> $GITHUB_ENV 22 | 23 | - name: Run BundleMon 24 | run: npx bundlemon 25 | -------------------------------------------------------------------------------- /.github/workflows/download-qt3/action.yml: -------------------------------------------------------------------------------- 1 | runs: 2 | using: "composite" 3 | steps: 4 | - run: mkdir -p ./test/assets/XQUTS ./test/assets/QT3TS 5 | shell: bash 6 | - run: curl -L https://github.com/LeoWoerteler/QT3TS/archive/master.tar.gz | tar -xz -C ./test/assets/QT3TS --strip-components=1 7 | shell: bash 8 | - run: curl -L https://github.com/LeoWoerteler/XQUTS/archive/master.tar.gz | tar -xz -C ./test/assets/XQUTS --strip-components=1 9 | shell: bash 10 | - run: unzip -q test/assets/QT3TS/xqueryx.zip -d ./test/assets/QT3TS/ 11 | shell: bash 12 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Linting 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: 16 14 | - name: Install dependencies 15 | run: npm ci; 16 | - name: Linter 17 | run: npm run lint; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /node_modules 3 | /performance/lib 4 | /test/assets/XQUTS 5 | /test/assets/runnablePerformanceTestNames.csv 6 | /test/built 7 | /built 8 | /demo/built 9 | /dist 10 | /.nyc_output 11 | /tmp 12 | /.tscc_temp 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/assets/qt3tests"] 2 | path = test/assets/qt3tests 3 | url = git@github.com:w3c/qt3tests.git 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" 2 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register", 3 | "extension": [ 4 | ".js", 5 | ".ts" 6 | ], 7 | "reporter": [ 8 | "text-summary", 9 | "html", 10 | "lcov" 11 | ], 12 | "instrument": true, 13 | "sourceMap": true, 14 | "include": [ 15 | "src/**/*.ts", 16 | "test/**/*.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "useTabs": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveshare.features": "experimental", 3 | "liveshare.languages.allowGuestCommandControl": true, 4 | "files.watcherExclude": { 5 | "**/built/**": true, 6 | "**/coverage/**": true, 7 | "**/dist/**": true 8 | }, 9 | "mocha.enabled": false, 10 | "typescript.preferences.importModuleSpecifier": "relative" 11 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fonto Group BV, Martin Middel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "compiler": { 4 | "tsconfigFilePath": "./tsconfig.json" 5 | }, 6 | "apiReport": { 7 | "enabled": false 8 | }, 9 | "docModel": { 10 | "enabled": true, 11 | "apiJsonFilePath": "dist/fontoxpath.api.json" 12 | 13 | }, 14 | "mainEntryPointFilePath": "built/index.d.ts", 15 | "dtsRollup": { 16 | "enabled": true, 17 | "untrimmedFilePath": "dist/fontoxpath.d.ts" 18 | }, 19 | "messages": { 20 | "compilerMessageReporting": { 21 | "default": { 22 | "logLevel": "warning" 23 | } 24 | }, 25 | "extractorMessageReporting": { 26 | "default": { 27 | "logLevel": "warning" 28 | } 29 | }, 30 | "tsdocMessageReporting": { 31 | "default": { 32 | "logLevel": "warning" 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /web_modules 3 | /built 4 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite" 4 | }, 5 | "dependencies": { 6 | "typescript": "^4.5.5", 7 | "vite": "^5.4.9" 8 | }, 9 | "devDependencies": { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["dom"], 5 | "outDir": "./built", 6 | "baseUrl": "..", 7 | "target": "es2017", 8 | "module": "esnext", 9 | "inlineSourceMap": true, 10 | "inlineSources": true, 11 | "moduleResolution": "node", 12 | "plugins": [ 13 | { 14 | "transform": "ts-transform-import-path-rewrite", 15 | "import": "transform", 16 | "alias": { 17 | "^(fontoxpath)$": "/src", 18 | "^(xspattern)$": "/web_modules/xspattern.js", 19 | "^(prsc)$": "/web_modules/prsc.js" 20 | }, 21 | "after": true, 22 | "afterDeclarations": true, 23 | "type": "config" 24 | }, 25 | { 26 | "transform": "@zoltu/typescript-transformer-append-js-extension/output/index.js", 27 | "after": true 28 | } 29 | ] 30 | }, 31 | "include": ["../src/**/*", "./main.ts"] 32 | } 33 | -------------------------------------------------------------------------------- /demo/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": ["../test/tslint.json"], 4 | "rules": { 5 | "no-console": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fuzzers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "lib": [ 5 | "ES2020" 6 | ], 7 | "paths": { 8 | "fontoxpath": [ 9 | "../dist/fontoxpath.esm.js" 10 | ] 11 | }, 12 | "strict": false, 13 | "allowJs": true, 14 | "types": [ 15 | "mocha", 16 | "node" 17 | ], 18 | "moduleResolution": "node", 19 | "target": "es2020", 20 | "esModuleInterop": true 21 | }, 22 | "include": [ 23 | "./**/*.ts" 24 | ] 25 | } -------------------------------------------------------------------------------- /fuzzers/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": ["../test/tslint.json"] 4 | } 5 | -------------------------------------------------------------------------------- /fuzzers/xpath-31-function-catalog-corpus.ts: -------------------------------------------------------------------------------- 1 | import { ICorpusLoader } from 'corpus_based_fuzzer'; 2 | import { evaluateXPathToStrings } from 'fontoxpath'; 3 | import fs from 'fs'; 4 | import { parseXmlDocument } from 'slimdom'; 5 | 6 | export default new (class XPath31FunctionCatalog implements ICorpusLoader { 7 | name: string = 'xpath-31-function-catalog'; 8 | 9 | get(): string[] { 10 | // Load corpus from the W3C XPath standard 11 | const specDoc = parseXmlDocument(fs.readFileSync('./fuzzers/xpath-31-function-catalog.xml', 'utf8')); 12 | return evaluateXPathToStrings( 13 | '//Q{http://www.w3.org/xpath-functions/spec/namespace}expression/text()', 14 | specDoc 15 | ); 16 | } 17 | })(); 18 | -------------------------------------------------------------------------------- /karma.conf.ts: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | frameworks: ['mocha', 'chai', 'karma-typescript'], 4 | files: ['src/**/*.ts', 'test/browsertests.ts'], 5 | preprocessors: { 6 | 'src/**/*.ts': 'karma-typescript', 7 | 'test/browsertests.ts': 'karma-typescript', 8 | }, 9 | reporters: ['progress'], 10 | port: 9876, // karma web server port 11 | colors: true, 12 | logLevel: config.LOG_INFO, 13 | browsers: ['ChromeHeadless', 'FirefoxHeadless'], 14 | autoWatch: false, 15 | singleRun: true, 16 | concurrency: Infinity, 17 | karmaTypescriptConfig: { 18 | tsconfig: 'test/tsconfig.json', 19 | }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /performance/evaluateXPath.benchmark.ts: -------------------------------------------------------------------------------- 1 | import benchmarkRunner from '@fontoxml/fonto-benchmark-runner'; 2 | import { Document } from 'slimdom'; 3 | import { domFacade, evaluateXPath } from '../src/index'; 4 | 5 | let document: Document; 6 | 7 | function setup() { 8 | document = new Document(); 9 | } 10 | 11 | benchmarkRunner.addBenchmark( 12 | 'evaluateXPath', 13 | () => { 14 | evaluateXPath('true()', document, domFacade); 15 | }, 16 | setup 17 | ); 18 | -------------------------------------------------------------------------------- /performance/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": ["../test/tslint.json"] 4 | } 5 | -------------------------------------------------------------------------------- /performance/union.benchmark.ts: -------------------------------------------------------------------------------- 1 | import benchmarkRunner from '@fontoxml/fonto-benchmark-runner'; 2 | import { Document, parseXmlDocument } from 'slimdom'; 3 | import { domFacade, evaluateXPath } from '../src/index'; 4 | 5 | let document: Document; 6 | 7 | function setup() { 8 | document = parseXmlDocument(` 9 | 10 | xpath.playground.fontoxml.com 11 | This is a learning tool for XML, XPath and XQuery. 12 | 13 | ${new Array(1000).fill('with some content')} 14 | 15 | `); 16 | } 17 | 18 | benchmarkRunner.addBenchmark( 19 | 'union', 20 | () => { 21 | evaluateXPath('//tip | reverse(//tip)', document, domFacade); 22 | }, 23 | setup 24 | ); 25 | -------------------------------------------------------------------------------- /performance/utils/loadFile.ts: -------------------------------------------------------------------------------- 1 | declare var window: any; 2 | 3 | export default async function loadFile(filename: string): Promise { 4 | if (typeof window !== 'undefined' && window.fetch) { 5 | const request = new window.Request(`${window.location}${filename}`); 6 | const response = await window.fetch(request); 7 | return response.text(); 8 | } else { 9 | const fs = await import('fs'); 10 | return fs.readFileSync(filename).toString(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/documentWriter/IDocumentWriter.ts: -------------------------------------------------------------------------------- 1 | import { Document, Element, Node } from '../types/Types'; 2 | 3 | /** 4 | * @public 5 | */ 6 | export default interface IDocumentWriter { 7 | insertBefore(parent: Element | Document, newNode: Node, referenceNode: Node | null): void; 8 | removeAttributeNS(node: Element, namespace: string, name: string): void; 9 | removeChild(parent: Element | Document, child: Node): void; 10 | setAttributeNS(node: Element, namespace: string, name: string, value: string): void; 11 | setData(node: Node, data: string): void; 12 | } 13 | -------------------------------------------------------------------------------- /src/documentWriter/domBackedDocumentWriter.ts: -------------------------------------------------------------------------------- 1 | import IDocumentWriter from './IDocumentWriter'; 2 | 3 | class DomBackedDocumentWriter implements IDocumentWriter { 4 | public insertBefore(parent: Element, newNode: Node, referenceNode: Node) { 5 | return parent['insertBefore'](newNode, referenceNode); 6 | } 7 | 8 | public removeAttributeNS(node: Element, namespace: string, name: string) { 9 | return node['removeAttributeNS'](namespace, name); 10 | } 11 | 12 | public removeChild(parent: Element, child: Node) { 13 | return parent['removeChild'](child); 14 | } 15 | 16 | public setAttributeNS(node: Element, namespace: string, name: string, value: string) { 17 | node['setAttributeNS'](namespace, name, value); 18 | } 19 | 20 | public setData(node: Comment | Text | ProcessingInstruction, data: string) { 21 | node['data'] = data; 22 | } 23 | } 24 | 25 | export default new DomBackedDocumentWriter(); 26 | -------------------------------------------------------------------------------- /src/documentWriter/wrapExternalDocumentWriter.ts: -------------------------------------------------------------------------------- 1 | import { Document, Element, Node } from '../types/Types'; 2 | import IDocumentWriter from './IDocumentWriter'; 3 | class WrappingDocumentWriter implements IDocumentWriter { 4 | private _externalDocumentWriter: IDocumentWriter; 5 | 6 | constructor(externalDocumentWriter: IDocumentWriter) { 7 | this._externalDocumentWriter = externalDocumentWriter; 8 | } 9 | 10 | public insertBefore(parent: Element | Document, newNode: Node, referenceNode: Node) { 11 | return this._externalDocumentWriter['insertBefore'](parent, newNode, referenceNode); 12 | } 13 | 14 | public removeAttributeNS(node: Element, namespace: string, name: string) { 15 | return this._externalDocumentWriter['removeAttributeNS'](node, namespace, name); 16 | } 17 | 18 | public removeChild(parent: Element | Document, child: Node) { 19 | return this._externalDocumentWriter['removeChild'](parent, child); 20 | } 21 | 22 | public setAttributeNS(node: Element, namespace: string, name: string, value: string) { 23 | this._externalDocumentWriter['setAttributeNS'](node, namespace, name, value); 24 | } 25 | 26 | public setData(node: Node, data: string) { 27 | this._externalDocumentWriter['setData'](node, data); 28 | } 29 | } 30 | 31 | export default function wrapExternalDocumentWriter(externalDocumentWriter: IDocumentWriter) { 32 | return new WrappingDocumentWriter(externalDocumentWriter); 33 | } 34 | -------------------------------------------------------------------------------- /src/evaluateXPathToArray.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { Options } from './types/Options'; 4 | 5 | /** 6 | * Evaluates an XPath on the given contextNode. Returns the result as an array, if the result is an XPath array. 7 | * 8 | * @public 9 | * 10 | * @param selector - The selector to execute. Supports XPath 3.1. 11 | * @param contextItem - The node from which to run the XPath. 12 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 13 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 14 | * @param options - Extra options for evaluating this XPath. 15 | * 16 | * @returns The array result, as a JavaScript array with atomized values. 17 | */ 18 | export default function evaluateXPathToArray( 19 | selector: EvaluableExpression, 20 | contextItem?: any | null, 21 | domFacade?: IDomFacade | null, 22 | variables?: { [s: string]: any } | null, 23 | options?: Options | null, 24 | ): any[] { 25 | return evaluateXPath( 26 | selector, 27 | contextItem, 28 | domFacade, 29 | variables, 30 | evaluateXPath.ARRAY_TYPE, 31 | options, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/evaluateXPathToAsyncIterator.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { Options } from './types/Options'; 4 | 5 | /** 6 | * Evaluates an XPath on the given contextNode. Returns the result as an async iterator 7 | * 8 | * @public 9 | * 10 | * @param selector - The selector to execute. Supports XPath 3.1. 11 | * @param contextItem - The node from which to run the XPath. 12 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 13 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 14 | * @param options - Extra options for evaluating this XPath. 15 | * 16 | * @returns An async iterator to the return values. 17 | */ 18 | export default function evaluateXPathToAsyncIterator( 19 | selector: EvaluableExpression, 20 | contextItem?: any | null, 21 | domFacade?: IDomFacade | null, 22 | variables?: { [s: string]: any } | null, 23 | options?: Options | null, 24 | ): AsyncIterableIterator { 25 | return evaluateXPath( 26 | selector, 27 | contextItem, 28 | domFacade, 29 | variables, 30 | evaluateXPath.ASYNC_ITERATOR_TYPE, 31 | options, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/evaluateXPathToBoolean.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { Options } from './types/Options'; 4 | 5 | /** 6 | * Evaluates an XPath on the given contextNode. 7 | * 8 | * @public 9 | * 10 | * @param selector - The selector to execute. Supports XPath 3.1. 11 | * @param contextItem - The node from which to run the XPath. 12 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 13 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 14 | * @param options - Extra options for evaluating this XPath. 15 | * 16 | * @returns A boolean result 17 | */ 18 | export default function evaluateXPathToBoolean( 19 | selector: EvaluableExpression, 20 | contextItem?: any | null, 21 | domFacade?: IDomFacade | null, 22 | variables?: { [s: string]: any } | null, 23 | options?: Options | null, 24 | ): boolean { 25 | return evaluateXPath( 26 | selector, 27 | contextItem, 28 | domFacade, 29 | variables, 30 | evaluateXPath.BOOLEAN_TYPE, 31 | options, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/evaluateXPathToFirstNode.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { ReturnType } from './parsing/convertXDMReturnValue'; 4 | import { Options } from './types/Options'; 5 | import { Node } from './types/Types'; 6 | 7 | /** 8 | * Evaluates an XPath on the given contextNode. Returns the first node result. 9 | * 10 | * @public 11 | * 12 | * @param selector - The selector to execute. Supports XPath 3.1. 13 | * @param contextItem - The node from which to run the XPath. 14 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 15 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 16 | * @param options - Extra options for evaluating this XPath. 17 | * 18 | * @returns The first matching node, in the order defined by the XPath. 19 | */ 20 | export default function evaluateXPathToFirstNode( 21 | selector: EvaluableExpression, 22 | contextItem?: any | null, 23 | domFacade?: IDomFacade | null, 24 | variables?: { [s: string]: any } | null, 25 | options?: Options | null, 26 | ): T | null { 27 | return evaluateXPath( 28 | selector, 29 | contextItem, 30 | domFacade, 31 | variables, 32 | evaluateXPath.FIRST_NODE_TYPE, 33 | options, 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/evaluateXPathToMap.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { Options } from './types/Options'; 4 | 5 | /** 6 | * Evaluates an XPath on the given contextNode. Returns the result as a map, if the result is an XPath map. 7 | * 8 | * @public 9 | * 10 | * @param selector - The selector to execute. Supports XPath 3.1. 11 | * @param contextItem - The node from which to run the XPath. 12 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 13 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 14 | * @param options - Extra options for evaluating this XPath. 15 | * 16 | * @returns The map result, as an object. Because of JavaScript 17 | * constraints, key 1 and '1' are the same. The values in this map are 18 | * the JavaScript simple types. See evaluateXPath for more details in 19 | * mapping types. 20 | */ 21 | export default function evaluateXPathToMap( 22 | selector: EvaluableExpression, 23 | contextItem?: any | null, 24 | domFacade?: IDomFacade | null, 25 | variables?: { [s: string]: any } | null, 26 | options?: Options | null, 27 | ): { [s: string]: any } { 28 | return evaluateXPath( 29 | selector, 30 | contextItem, 31 | domFacade, 32 | variables, 33 | evaluateXPath.MAP_TYPE, 34 | options, 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/evaluateXPathToNodes.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { ReturnType } from './parsing/convertXDMReturnValue'; 4 | import { Options } from './types/Options'; 5 | import { Node } from './types/Types'; 6 | 7 | /** 8 | * Evaluates an XPath on the given contextNode. Returns all nodes the XPath resolves to. 9 | * Returns result in the order defined in the XPath. The path operator ('/'), the union operator ('union' and '|') will sort. 10 | * This implies (//A, //B) resolves to all A nodes, followed by all B nodes, both in document order, but not merged. 11 | * However: (//A | //B) resolves to all A and B nodes in document order. 12 | * 13 | * @public 14 | * 15 | * @param selector - The selector to execute. Supports XPath 3.1. 16 | * @param contextItem - The node from which to run the XPath. 17 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 18 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 19 | * @param options - Extra options for evaluating this XPath. 20 | * 21 | * @returns All matching Nodes, in the order defined by the XPath. 22 | */ 23 | export default function evaluateXPathToNodes( 24 | selector: EvaluableExpression, 25 | contextItem?: any | null, 26 | domFacade?: IDomFacade | null, 27 | variables?: { [s: string]: any } | null, 28 | options?: Options | null, 29 | ): T[] { 30 | return evaluateXPath( 31 | selector, 32 | contextItem, 33 | domFacade, 34 | variables, 35 | evaluateXPath.NODES_TYPE, 36 | options, 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/evaluateXPathToNumber.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { Options } from './types/Options'; 4 | 5 | /** 6 | * Evaluates an XPath on the given contextNode. Returns the numeric result. 7 | * 8 | * @public 9 | * 10 | * @param selector - The selector to execute. Supports XPath 3.1. 11 | * @param contextItem - The node from which to run the XPath. 12 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 13 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 14 | * @param options - Extra options for evaluating this XPath. 15 | * 16 | * @returns The numerical result. 17 | */ 18 | export default function evaluateXPathToNumber( 19 | selector: EvaluableExpression, 20 | contextItem?: any | null, 21 | domFacade?: IDomFacade | null, 22 | variables?: { [s: string]: any } | null, 23 | options?: Options | null, 24 | ): number { 25 | return evaluateXPath( 26 | selector, 27 | contextItem, 28 | domFacade, 29 | variables, 30 | evaluateXPath.NUMBER_TYPE, 31 | options, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/evaluateXPathToNumbers.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { Options } from './types/Options'; 4 | 5 | /** 6 | * Evaluates an XPath on the given contextNode. Returns the numeric result. 7 | * 8 | * @public 9 | * 10 | * @param selector - The selector to execute. Supports XPath 3.1. 11 | * @param contextItem - The node from which to run the XPath. 12 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 13 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 14 | * @param options - Extra options for evaluating this XPath. 15 | * 16 | * @returns The numerical results. 17 | */ 18 | export default function evaluateXPathToNumbers( 19 | selector: EvaluableExpression, 20 | contextItem?: any | null, 21 | domFacade?: IDomFacade | null, 22 | variables?: { [s: string]: any } | null, 23 | options?: Options | null, 24 | ): number[] { 25 | return evaluateXPath( 26 | selector, 27 | contextItem, 28 | domFacade, 29 | variables, 30 | evaluateXPath.NUMBERS_TYPE, 31 | options, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/evaluateXPathToString.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { Options } from './types/Options'; 4 | 5 | /** 6 | * Evaluates an XPath on the given contextNode. Returns the string result as if the XPath is wrapped in string(...). 7 | * 8 | * @public 9 | * 10 | * @param selector - The selector to execute. Supports XPath 3.1. 11 | * @param contextItem - The node from which to run the XPath. 12 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 13 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 14 | * @param options - Extra options for evaluating this XPath. 15 | * 16 | * @returns The string result. 17 | */ 18 | export default function evaluateXPathToString( 19 | selector: EvaluableExpression, 20 | contextItem?: any | null, 21 | domFacade?: IDomFacade | null, 22 | variables?: { [s: string]: any } | null, 23 | options?: Options | null, 24 | ): string { 25 | return evaluateXPath( 26 | selector, 27 | contextItem, 28 | domFacade, 29 | variables, 30 | evaluateXPath.STRING_TYPE, 31 | options, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/evaluateXPathToStrings.ts: -------------------------------------------------------------------------------- 1 | import IDomFacade from './domFacade/IDomFacade'; 2 | import evaluateXPath, { EvaluableExpression } from './evaluateXPath'; 3 | import { Options } from './types/Options'; 4 | 5 | /** 6 | * Evaluates an XPath on the given contextNode. Returns the string result as if the XPath is wrapped in string(...). 7 | * 8 | * @public 9 | * 10 | * @param selector - The selector to execute. Supports XPath 3.1. 11 | * @param contextItem - The node from which to run the XPath. 12 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 13 | * @param variables - Extra variables (name to value). Values can be number, string, boolean, nodes or object literals and arrays. 14 | * @param options - Extra options for evaluating this XPath. 15 | * 16 | * @returns The string result. 17 | */ 18 | export default function evaluateXPathToStrings( 19 | selector: EvaluableExpression, 20 | contextItem?: any | null, 21 | domFacade?: IDomFacade | null, 22 | variables?: { [s: string]: any } | null, 23 | options?: Options | null, 24 | ): string[] { 25 | return evaluateXPath( 26 | selector, 27 | contextItem, 28 | domFacade, 29 | variables, 30 | evaluateXPath.STRINGS_TYPE, 31 | options, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/evaluationUtils/PositionedError.ts: -------------------------------------------------------------------------------- 1 | import { SourceRange } from '../expressions/debug/StackTraceGenerator'; 2 | 3 | export class PositionedError extends Error { 4 | public readonly position: SourceRange; 5 | 6 | constructor(message: string, position: SourceRange) { 7 | super(message); 8 | this.position = { 9 | end: { 10 | column: position.end.column, 11 | line: position.end.line, 12 | offset: position.end.offset, 13 | }, 14 | start: { 15 | column: position.start.column, 16 | line: position.start.line, 17 | offset: position.start.offset, 18 | }, 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/evaluationUtils/convertUpdateResultToTransferable.ts: -------------------------------------------------------------------------------- 1 | import { EvaluableExpression } from '..'; 2 | import sequenceFactory from '../expressions/dataTypes/sequenceFactory'; 3 | import ExecutionParameters from '../expressions/ExecutionParameters'; 4 | import UpdatingExpressionResult from '../expressions/UpdatingExpressionResult'; 5 | import convertXDMReturnValue, { IReturnTypes } from '../parsing/convertXDMReturnValue'; 6 | import { Node } from '../types/Types'; 7 | 8 | export default function convertUpdateResultToTransferable< 9 | TNode extends Node, 10 | TReturnType extends keyof IReturnTypes, 11 | >( 12 | result: UpdatingExpressionResult, 13 | script: EvaluableExpression | string, 14 | returnType: TReturnType, 15 | executionParameters: ExecutionParameters, 16 | ): { pendingUpdateList: object[]; xdmValue: IReturnTypes[TReturnType] } { 17 | return { 18 | ['pendingUpdateList']: result.pendingUpdateList.map((update) => 19 | update.toTransferable(executionParameters), 20 | ), 21 | ['xdmValue']: convertXDMReturnValue( 22 | script, 23 | sequenceFactory.create(result.xdmValue), 24 | returnType, 25 | executionParameters, 26 | ), 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/executePendingUpdateList.ts: -------------------------------------------------------------------------------- 1 | import domBackedDocumentWriter from './documentWriter/domBackedDocumentWriter'; 2 | import IDocumentWriter from './documentWriter/IDocumentWriter'; 3 | import wrapExternalDocumentWriter from './documentWriter/wrapExternalDocumentWriter'; 4 | import DomFacade from './domFacade/DomFacade'; 5 | import ExternalDomFacade from './domFacade/ExternalDomFacade'; 6 | import IDomFacade from './domFacade/IDomFacade'; 7 | import createPendingUpdateFromTransferable from './expressions/xquery-update/createPendingUpdateFromTransferable'; 8 | import { applyUpdates } from './expressions/xquery-update/pulRoutines'; 9 | import INodesFactory from './nodesFactory/INodesFactory'; 10 | import wrapExternalNodesFactory from './nodesFactory/wrapExternalNodesFactory'; 11 | 12 | /** 13 | * @public 14 | * 15 | * @param pendingUpdateList - The updateScript to execute. 16 | * @param domFacade - The domFacade (or DomFacade like interface) for retrieving relations. 17 | * @param nodesFactory - The nodesFactory for creating nodes. 18 | * @param documentWriter - The documentWriter for writing changes. 19 | */ 20 | export default function executePendingUpdateList( 21 | pendingUpdateList: object[], 22 | domFacade?: IDomFacade, 23 | nodesFactory?: INodesFactory, 24 | documentWriter?: IDocumentWriter, 25 | ): void { 26 | const newDomFacade = new DomFacade(domFacade ? domFacade : new ExternalDomFacade()); 27 | 28 | documentWriter = documentWriter 29 | ? wrapExternalDocumentWriter(documentWriter) 30 | : domBackedDocumentWriter; 31 | 32 | nodesFactory = nodesFactory ? (nodesFactory = wrapExternalNodesFactory(nodesFactory)) : null; 33 | 34 | const pul = pendingUpdateList.map(createPendingUpdateFromTransferable); 35 | applyUpdates(pul, null, null, newDomFacade, nodesFactory, documentWriter); 36 | } 37 | -------------------------------------------------------------------------------- /src/expressions/Context.ts: -------------------------------------------------------------------------------- 1 | import { LexicalQualifiedName, ResolvedQualifiedName } from '../types/Options'; 2 | import ISequence from './dataTypes/ISequence'; 3 | import DynamicContext from './DynamicContext'; 4 | import ExecutionParameters from './ExecutionParameters'; 5 | import { FunctionProperties } from './functions/functionRegistry'; 6 | 7 | export default interface IContext { 8 | registeredDefaultFunctionNamespaceURI: string | null; 9 | registeredVariableBindingByHashKey: { [s: string]: string }[]; 10 | registeredVariableDeclarationByHashKey: { 11 | [hash: string]: ( 12 | dynamicContext: DynamicContext, 13 | executionParameters: ExecutionParameters, 14 | ) => ISequence; 15 | }; 16 | lookupFunction( 17 | namespaceURI: string, 18 | localName: string, 19 | arity: number, 20 | skipExternal?: boolean, 21 | ): FunctionProperties | null; 22 | lookupVariable(namespaceURI: string | null, localName: string): string | null; 23 | resolveFunctionName(lexicalQName: LexicalQualifiedName, arity: number): ResolvedQualifiedName; 24 | resolveNamespace(prefix: string, useExternalResolver?: boolean): string | null; 25 | } 26 | -------------------------------------------------------------------------------- /src/expressions/ExecutionParameters.ts: -------------------------------------------------------------------------------- 1 | import IDocumentWriter from '../documentWriter/IDocumentWriter'; 2 | import { NodePointer, TinyNode } from '../domClone/Pointer'; 3 | import { ConcreteNode } from '../domFacade/ConcreteNode'; 4 | import DomFacade from '../domFacade/DomFacade'; 5 | import INodesFactory from '../nodesFactory/INodesFactory'; 6 | import { Logger } from '../types/Options'; 7 | 8 | export default class ExecutionParameters { 9 | constructor( 10 | public readonly debug: boolean, 11 | public readonly disableCache: boolean, 12 | public readonly domFacade: DomFacade, 13 | public readonly nodesFactory: INodesFactory, 14 | public readonly documentWriter: IDocumentWriter, 15 | public readonly currentContext: any, 16 | public readonly rootPointerByRootNodeMap: Map, 17 | public readonly logger?: Logger, 18 | public readonly xmlSerializer?: XMLSerializer, 19 | ) {} 20 | } 21 | -------------------------------------------------------------------------------- /src/expressions/Specificity.ts: -------------------------------------------------------------------------------- 1 | const SPECIFICITY_DIMENSIONS = ['external', 'attribute', 'nodeName', 'nodeType', 'universal']; 2 | const NUMBER_OF_SPECIFICITY_DIMENSIONS = SPECIFICITY_DIMENSIONS.length; 3 | 4 | class Specificity { 5 | public static ATTRIBUTE_KIND = 'attribute'; 6 | public static EXTERNAL_KIND = 'external'; 7 | public static NODENAME_KIND = 'nodeName'; 8 | public static NODETYPE_KIND = 'nodeType'; 9 | public static UNIVERSAL_KIND = 'universal'; 10 | 11 | private _counts: number[]; 12 | 13 | constructor(countsByKind: { [s: string]: number }) { 14 | this._counts = SPECIFICITY_DIMENSIONS.map((specificityKind) => { 15 | return countsByKind[specificityKind] || 0; 16 | }); 17 | 18 | if (Object.keys(countsByKind).some((kind) => !SPECIFICITY_DIMENSIONS.includes(kind))) { 19 | throw new Error('Invalid specificity kind passed'); 20 | } 21 | } 22 | 23 | /** 24 | * @return new specificity with the combined counts 25 | */ 26 | public add(otherSpecificity: Specificity): Specificity { 27 | const sum = SPECIFICITY_DIMENSIONS.reduce((countsByKind, specificityKind, index) => { 28 | countsByKind[specificityKind] = this._counts[index] + otherSpecificity._counts[index]; 29 | return countsByKind; 30 | }, Object.create(null)); 31 | 32 | return new Specificity(sum); 33 | } 34 | 35 | /** 36 | * @return -1 if specificity is less than otherSpecificity, 1 if it is greater, 0 if equal 37 | */ 38 | public compareTo(otherSpecificity: Specificity): -1 | 0 | 1 { 39 | for (let i = 0; i < NUMBER_OF_SPECIFICITY_DIMENSIONS; ++i) { 40 | if (otherSpecificity._counts[i] < this._counts[i]) { 41 | return 1; 42 | } 43 | if (otherSpecificity._counts[i] > this._counts[i]) { 44 | return -1; 45 | } 46 | } 47 | return 0; 48 | } 49 | } 50 | export default Specificity; 51 | -------------------------------------------------------------------------------- /src/expressions/UnfocusableDynamicContext.ts: -------------------------------------------------------------------------------- 1 | import sequenceFactory from './dataTypes/sequenceFactory'; 2 | import DynamicContext from './DynamicContext'; 3 | 4 | type VariableBindings = { variableBindings: { [s: string]: any } }; 5 | 6 | export default class UnfocusableDynamicContext extends DynamicContext { 7 | constructor(bindings: VariableBindings) { 8 | super({ 9 | contextItem: null, 10 | contextItemIndex: -1, 11 | contextSequence: sequenceFactory.empty(), 12 | variableBindings: bindings.variableBindings, 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/expressions/UpdatingExpressionResult.ts: -------------------------------------------------------------------------------- 1 | import Value from './dataTypes/Value'; 2 | import { IPendingUpdate } from './xquery-update/IPendingUpdate'; 3 | export default class UpdatingExpressionResult { 4 | public pendingUpdateList: IPendingUpdate[]; 5 | public xdmValue: Value[]; 6 | constructor(values: Value[], pendingUpdateList: IPendingUpdate[]) { 7 | this.xdmValue = values; 8 | this.pendingUpdateList = pendingUpdateList; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/expressions/XPathErrors.ts: -------------------------------------------------------------------------------- 1 | import { ValueType, valueTypeToString } from './dataTypes/Value'; 2 | 3 | export const errFORG0001 = (value: string, type: ValueType, reason?: string) => 4 | new Error( 5 | `FORG0001: Cannot cast ${value} to ${valueTypeToString(type)}${ 6 | reason ? `, ${reason}` : '' 7 | }`, 8 | ); 9 | export const errXPDY0002 = (message: string) => new Error(`XPDY0002: ${message}`); 10 | export const errXPTY0004 = (message: string) => new Error(`XPTY0004: ${message}`); 11 | export const errFOTY0013 = (type: ValueType) => 12 | new Error(`FOTY0013: Atomization is not supported for ${valueTypeToString(type)}.`); 13 | export const errXPST0081 = (prefix: string) => 14 | new Error(`XPST0081: The prefix ${prefix} could not be resolved.`); 15 | -------------------------------------------------------------------------------- /src/expressions/arrays/CurlyArrayConstructor.ts: -------------------------------------------------------------------------------- 1 | import ArrayValue from '../dataTypes/ArrayValue'; 2 | import sequenceFactory from '../dataTypes/sequenceFactory'; 3 | import { SequenceType } from '../dataTypes/Value'; 4 | import DynamicContext from '../DynamicContext'; 5 | import ExecutionParameters from '../ExecutionParameters'; 6 | import Expression from '../Expression'; 7 | import Specificity from '../Specificity'; 8 | import createDoublyIterableSequence from '../util/createDoublyIterableSequence'; 9 | 10 | class CurlyArrayConstructor extends Expression { 11 | private _members: Expression[]; 12 | constructor(members: Expression[], type: SequenceType) { 13 | super( 14 | new Specificity({ 15 | [Specificity.EXTERNAL_KIND]: 1, 16 | }), 17 | members, 18 | { 19 | canBeStaticallyEvaluated: members.every( 20 | (member) => member.canBeStaticallyEvaluated, 21 | ), 22 | }, 23 | false, 24 | type, 25 | ); 26 | 27 | this._members = members; 28 | } 29 | 30 | public evaluate(dynamicContext: DynamicContext, executionParameters: ExecutionParameters) { 31 | if (this._members.length === 0) { 32 | return sequenceFactory.singleton(new ArrayValue([])); 33 | } 34 | return this._members[0] 35 | .evaluateMaybeStatically(dynamicContext, executionParameters) 36 | .mapAll((allValues) => 37 | sequenceFactory.singleton( 38 | new ArrayValue( 39 | allValues.map((item) => 40 | createDoublyIterableSequence(sequenceFactory.singleton(item)), 41 | ), 42 | ), 43 | ), 44 | ); 45 | } 46 | } 47 | export default CurlyArrayConstructor; 48 | -------------------------------------------------------------------------------- /src/expressions/arrays/SquareArrayConstructor.ts: -------------------------------------------------------------------------------- 1 | import ArrayValue from '../dataTypes/ArrayValue'; 2 | import sequenceFactory from '../dataTypes/sequenceFactory'; 3 | import { SequenceType } from '../dataTypes/Value'; 4 | import DynamicContext from '../DynamicContext'; 5 | import ExecutionParameters from '../ExecutionParameters'; 6 | import Expression from '../Expression'; 7 | import Specificity from '../Specificity'; 8 | import createDoublyIterableSequence from '../util/createDoublyIterableSequence'; 9 | 10 | class SquareArrayConstructor extends Expression { 11 | private _members: Expression[]; 12 | constructor(members: Expression[], type: SequenceType) { 13 | super( 14 | new Specificity({ 15 | [Specificity.EXTERNAL_KIND]: 1, 16 | }), 17 | members, 18 | { 19 | canBeStaticallyEvaluated: members.every( 20 | (member) => member.canBeStaticallyEvaluated, 21 | ), 22 | }, 23 | false, 24 | type, 25 | ); 26 | 27 | this._members = members; 28 | } 29 | 30 | public evaluate(dynamicContext: DynamicContext, executionParameters: ExecutionParameters) { 31 | return sequenceFactory.singleton( 32 | new ArrayValue( 33 | this._members.map((entry) => 34 | createDoublyIterableSequence( 35 | entry.evaluateMaybeStatically(dynamicContext, executionParameters), 36 | ), 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | export default SquareArrayConstructor; 43 | -------------------------------------------------------------------------------- /src/expressions/axes/SelfAxis.ts: -------------------------------------------------------------------------------- 1 | import ISequence from '../dataTypes/ISequence'; 2 | import sequenceFactory from '../dataTypes/sequenceFactory'; 3 | import DynamicContext from '../DynamicContext'; 4 | import ExecutionParameters from '../ExecutionParameters'; 5 | import Expression, { RESULT_ORDERINGS } from '../Expression'; 6 | import TestAbstractExpression from '../tests/TestAbstractExpression'; 7 | import { Bucket, intersectBuckets } from '../util/Bucket'; 8 | import validateContextNode from './validateContextNode'; 9 | 10 | class SelfAxis extends Expression { 11 | private readonly _bucket: Bucket; 12 | private readonly _selector: TestAbstractExpression; 13 | 14 | constructor(selector: TestAbstractExpression, filterBucket: Bucket) { 15 | super(selector.specificity, [selector], { 16 | resultOrder: RESULT_ORDERINGS.SORTED, 17 | subtree: true, 18 | peer: true, 19 | canBeStaticallyEvaluated: false, 20 | }); 21 | 22 | this._selector = selector; 23 | this._bucket = intersectBuckets(this._selector.getBucket(), filterBucket); 24 | } 25 | 26 | public evaluate( 27 | dynamicContext: DynamicContext, 28 | executionParameters: ExecutionParameters, 29 | ): ISequence { 30 | validateContextNode(dynamicContext.contextItem); 31 | 32 | const isMatch = this._selector.evaluateToBoolean( 33 | dynamicContext, 34 | dynamicContext.contextItem, 35 | executionParameters, 36 | ); 37 | return isMatch 38 | ? sequenceFactory.singleton(dynamicContext.contextItem) 39 | : sequenceFactory.empty(); 40 | } 41 | 42 | public override getBucket(): Bucket { 43 | return this._bucket; 44 | } 45 | } 46 | export default SelfAxis; 47 | -------------------------------------------------------------------------------- /src/expressions/axes/validateContextNode.ts: -------------------------------------------------------------------------------- 1 | import { NodePointer } from '../../domClone/Pointer'; 2 | import isSubtypeOf from '../dataTypes/isSubtypeOf'; 3 | import Value, { ValueType } from '../dataTypes/Value'; 4 | import { errXPDY0002 } from '../XPathErrors'; 5 | 6 | export default function validateContextNode(value: Value): NodePointer { 7 | if (value === null) { 8 | throw errXPDY0002('context is absent, it needs to be present to use axes.'); 9 | } 10 | if (!isSubtypeOf(value.type, ValueType.NODE)) { 11 | throw new Error('XPTY0020: Axes can only be applied to nodes.'); 12 | } 13 | 14 | return value.value as NodePointer; 15 | } 16 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/ArrayValue.ts: -------------------------------------------------------------------------------- 1 | import arrayGet from '../functions/builtInFunctions_arrays_get'; 2 | import { BUILT_IN_NAMESPACE_URIS } from '../staticallyKnownNamespaces'; 3 | import FunctionValue from './FunctionValue'; 4 | import ISequence from './ISequence'; 5 | import sequenceFactory from './sequenceFactory'; 6 | import { SequenceMultiplicity, ValueType } from './Value'; 7 | 8 | class ArrayValue extends FunctionValue { 9 | public members: (() => ISequence)[]; 10 | constructor(members: (() => ISequence)[]) { 11 | super({ 12 | value: (dynamicContext, executionParameters, staticContext, key) => 13 | arrayGet( 14 | dynamicContext, 15 | executionParameters, 16 | staticContext, 17 | sequenceFactory.singleton(this), 18 | key, 19 | ), 20 | localName: 'get', 21 | namespaceURI: BUILT_IN_NAMESPACE_URIS.ARRAY_NAMESPACE_URI, 22 | // argumentTypes: [{ type: { kind: BaseType.XSINTEGER, seqType: SequenceType.EXACTLY_ONE }, isResArgument: false }], 23 | argumentTypes: [{ type: ValueType.XSINTEGER, mult: SequenceMultiplicity.EXACTLY_ONE }], 24 | arity: 1, 25 | returnType: { type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_MORE }, 26 | }); 27 | this.type = ValueType.ARRAY; 28 | this.members = members; 29 | } 30 | } 31 | 32 | export default ArrayValue; 33 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/AtomicValue.ts: -------------------------------------------------------------------------------- 1 | import { ValueType } from './Value'; 2 | 3 | type AtomicValue = { 4 | type: ValueType; 5 | value: any; 6 | }; 7 | 8 | export default AtomicValue; 9 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/MapValue.ts: -------------------------------------------------------------------------------- 1 | import mapGet from '../functions/builtInFunctions_maps_get'; 2 | import { BUILT_IN_NAMESPACE_URIS } from '../staticallyKnownNamespaces'; 3 | import FunctionValue from './FunctionValue'; 4 | import ISequence from './ISequence'; 5 | import sequenceFactory from './sequenceFactory'; 6 | import Value, { SequenceMultiplicity, ValueType } from './Value'; 7 | 8 | class MapValue extends FunctionValue { 9 | public keyValuePairs: { key: Value; value: () => ISequence }[]; 10 | constructor(keyValuePairs: { key: Value; value: () => ISequence }[]) { 11 | super({ 12 | // argumentTypes: [{ type: 'item()', isRestArgument: false }], 13 | argumentTypes: [{ type: ValueType.ITEM, mult: SequenceMultiplicity.EXACTLY_ONE }], 14 | arity: 1, 15 | localName: 'get', 16 | namespaceURI: BUILT_IN_NAMESPACE_URIS.MAP_NAMESPACE_URI, 17 | value: (dynamicContext, executionParameters, staticContext, key) => 18 | mapGet( 19 | dynamicContext, 20 | executionParameters, 21 | staticContext, 22 | sequenceFactory.singleton(this), 23 | key, 24 | ), 25 | returnType: { type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_MORE }, 26 | }); 27 | this.type = ValueType.MAP; 28 | this.keyValuePairs = keyValuePairs; 29 | } 30 | } 31 | export default MapValue; 32 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/Sequences/EmptySequence.ts: -------------------------------------------------------------------------------- 1 | import { DONE_TOKEN, IIterator } from '../../util/iterators'; 2 | import ISequence, { SwitchCasesCases } from '../ISequence'; 3 | import Value from '../Value'; 4 | 5 | export default class EmptySequence implements ISequence { 6 | public value: IIterator; 7 | 8 | constructor() { 9 | this.value = { 10 | next: () => DONE_TOKEN, 11 | }; 12 | } 13 | 14 | public expandSequence(): ISequence { 15 | return this; 16 | } 17 | 18 | public filter(_callback: (value: Value, i: number, sequence: ISequence) => boolean): ISequence { 19 | return this; 20 | } 21 | 22 | public first(): Value | null { 23 | return null; 24 | } 25 | 26 | public getAllValues(): Value[] { 27 | return []; 28 | } 29 | 30 | public getEffectiveBooleanValue(): boolean { 31 | return false; 32 | } 33 | 34 | public getLength(): number { 35 | return 0; 36 | } 37 | 38 | public isEmpty(): boolean { 39 | return true; 40 | } 41 | 42 | public isSingleton(): boolean { 43 | return false; 44 | } 45 | 46 | public map(_callback: (value: Value, i: number, sequence: ISequence) => Value): ISequence { 47 | return this; 48 | } 49 | 50 | public mapAll(callback: (allValues: Value[]) => ISequence): ISequence { 51 | return callback([]); 52 | } 53 | 54 | public switchCases(cases: SwitchCasesCases): ISequence { 55 | if (cases.empty) { 56 | return cases.empty(this); 57 | } 58 | return cases.default(this); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/Sequences/getEffectiveBooleanValue.ts: -------------------------------------------------------------------------------- 1 | import { errFORG0006 } from '../../functions/FunctionOperationErrors'; 2 | import isSubtypeOf from '../isSubtypeOf'; 3 | import Value, { ValueType, valueTypeToString } from '../Value'; 4 | 5 | export default function getEffectiveBooleanValue(value: Value): boolean { 6 | const jsValue = value.value; 7 | 8 | // If its operand is a sequence whose first item is a node, fn:boolean returns true. 9 | if (isSubtypeOf(value.type, ValueType.NODE)) { 10 | return true; 11 | } 12 | 13 | // If its operand is a singleton value of type xs:boolean or derived from xs:boolean, fn:boolean returns the value of its operand unchanged. 14 | if (isSubtypeOf(value.type, ValueType.XSBOOLEAN)) { 15 | return jsValue as boolean; 16 | } 17 | 18 | // If its operand is a singleton value of type xs:string, xs:anyURI, xs:untypedAtomic, or a type derived from one of these, fn:boolean returns false if the operand value has zero length; otherwise it returns true. 19 | if ( 20 | isSubtypeOf(value.type, ValueType.XSSTRING) || 21 | isSubtypeOf(value.type, ValueType.XSANYURI) || 22 | isSubtypeOf(value.type, ValueType.XSUNTYPEDATOMIC) 23 | ) { 24 | return (jsValue as string).length !== 0; 25 | } 26 | 27 | // If its operand is a singleton value of any numeric type or derived from a numeric type, fn:boolean returns false if the operand value is NaN or is numerically equal to zero; otherwise it returns true. 28 | if (isSubtypeOf(value.type, ValueType.XSNUMERIC)) { 29 | return !isNaN(jsValue as number) && jsValue !== 0; 30 | } 31 | 32 | // In all other cases, fn:boolean raises a type error [err:FORG0006]FO31. 33 | throw errFORG0006( 34 | `Cannot determine the effective boolean value of a value with the type ${valueTypeToString( 35 | value.type, 36 | )}`, 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/Variety.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An enumerator for the variety to get rid of the String comparisons. 3 | */ 4 | 5 | export enum Variety { 6 | PRIMITIVE, 7 | DERIVED, 8 | LIST, 9 | UNION, 10 | } 11 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/canCastToType.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from './AtomicValue'; 2 | import tryCastToType from './casting/tryCastToType'; 3 | import { ValueType } from './Value'; 4 | 5 | export default function canCastToType(value: AtomicValue, type: ValueType): boolean { 6 | const result = tryCastToType(value, type); 7 | return result.successful; 8 | } 9 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/castToType.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from './AtomicValue'; 2 | import tryCastToType from './casting/tryCastToType'; 3 | import { ValueType } from './Value'; 4 | 5 | export default function castToType(value: AtomicValue, type: ValueType): AtomicValue { 6 | const result = tryCastToType(value, type); 7 | if (result.successful === true) { 8 | return result.value; 9 | } 10 | throw result.error; 11 | } 12 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/AtomicValueDataType.ts: -------------------------------------------------------------------------------- 1 | import DateTime from '../valueTypes/DateTime'; 2 | import Duration from '../valueTypes/Duration'; 3 | 4 | type AtomicValueDataType = number | string | boolean | DateTime | Duration; 5 | 6 | export default AtomicValueDataType; 7 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/CastResult.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | 3 | type ErrorResult = { 4 | error: Error; 5 | successful: false; 6 | }; 7 | type SuccessResult = { 8 | successful: true; 9 | value: AtomicValue; 10 | }; 11 | 12 | type CastResult = ErrorResult | SuccessResult; 13 | 14 | export default CastResult; 15 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToAnyURI.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { SequenceMultiplicity, ValueType } from '../Value'; 4 | import CastResult from './CastResult'; 5 | 6 | const createAnyURIValue = (value: any): AtomicValue => createAtomicValue(value, ValueType.XSANYURI); 7 | 8 | export default function castToAnyURI( 9 | instanceOf: (typeName: ValueType) => boolean, 10 | ): (value: any) => CastResult { 11 | if (instanceOf(ValueType.XSSTRING) || instanceOf(ValueType.XSUNTYPEDATOMIC)) { 12 | return (value) => ({ 13 | successful: true, 14 | value: createAnyURIValue(value), 15 | }); 16 | } 17 | 18 | return () => ({ 19 | successful: false, 20 | error: new Error( 21 | 'XPTY0004: Casting not supported from given type to xs:anyURI or any of its derived types.', 22 | ), 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToBase64Binary.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { SequenceMultiplicity, ValueType } from '../Value'; 4 | import CastResult from './CastResult'; 5 | 6 | const createBase64BinaryValue = (value: any): AtomicValue => 7 | createAtomicValue(value, ValueType.XSBASE64BINARY); 8 | 9 | function hexToString(hex: string) { 10 | let text = ''; 11 | for (let i = 0; i < hex.length; i += 2) { 12 | text += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); 13 | } 14 | return text; 15 | } 16 | 17 | // This declaration is needed, as we don't depend anymore on lib.dom. 18 | declare let btoa: (s: string) => string; 19 | 20 | export default function castToBase64Binary( 21 | instanceOf: (typeName: ValueType) => boolean, 22 | ): (value: any) => CastResult { 23 | if (instanceOf(ValueType.XSHEXBINARY)) { 24 | return (value) => ({ 25 | successful: true, 26 | value: createBase64BinaryValue(btoa(hexToString(value))), 27 | }); 28 | } 29 | if (instanceOf(ValueType.XSSTRING) || instanceOf(ValueType.XSUNTYPEDATOMIC)) { 30 | return (value) => ({ 31 | successful: true, 32 | value: createBase64BinaryValue(value), 33 | }); 34 | } 35 | return () => ({ 36 | error: new Error( 37 | 'XPTY0004: Casting not supported from given type to xs:base64Binary or any of its derived types.', 38 | ), 39 | successful: false, 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToBoolean.ts: -------------------------------------------------------------------------------- 1 | import { falseBoolean, trueBoolean } from '../createAtomicValue'; 2 | import { ValueType } from '../Value'; 3 | import CastResult from './CastResult'; 4 | 5 | export default function castToBoolean( 6 | instanceOf: (typeName: ValueType) => boolean, 7 | ): (value: any) => CastResult { 8 | if (instanceOf(ValueType.XSNUMERIC)) { 9 | return (value) => ({ 10 | successful: true, 11 | value: value === 0 || isNaN(value) ? falseBoolean : trueBoolean, 12 | }); 13 | } 14 | if (instanceOf(ValueType.XSSTRING) || instanceOf(ValueType.XSUNTYPEDATOMIC)) { 15 | return (value) => { 16 | switch (value) { 17 | case 'true': 18 | case '1': 19 | return { 20 | successful: true, 21 | value: trueBoolean, 22 | }; 23 | case 'false': 24 | case '0': 25 | return { 26 | successful: true, 27 | value: falseBoolean, 28 | }; 29 | default: 30 | return { 31 | successful: false, 32 | error: new Error( 33 | 'XPTY0004: Casting not supported from given type to xs:boolean or any of its derived types.', 34 | ), 35 | }; 36 | } 37 | }; 38 | } 39 | return () => ({ 40 | successful: false, 41 | error: new Error( 42 | 'XPTY0004: Casting not supported from given type to xs:boolean or any of its derived types.', 43 | ), 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToDate.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { ValueType } from '../Value'; 4 | import DateTime from '../valueTypes/DateTime'; 5 | import CastResult from './CastResult'; 6 | 7 | const createDateValue = (value: any): AtomicValue => createAtomicValue(value, ValueType.XSDATE); 8 | 9 | export default function castToDate( 10 | instanceOf: (typeName: ValueType) => boolean, 11 | ): (value: any) => CastResult { 12 | if (instanceOf(ValueType.XSDATETIME)) { 13 | return (value: DateTime) => ({ 14 | successful: true, 15 | value: createDateValue(value.convertToType(ValueType.XSDATE)), 16 | }); 17 | } 18 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 19 | return (value) => ({ 20 | successful: true, 21 | value: createDateValue(DateTime.fromString(value)), 22 | }); 23 | } 24 | return () => ({ 25 | successful: false, 26 | error: new Error( 27 | 'XPTY0004: Casting not supported from given type to xs:date or any of its derived types.', 28 | ), 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToDateTime.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { ValueType } from '../Value'; 4 | import DateTime from '../valueTypes/DateTime'; 5 | import CastResult from './CastResult'; 6 | 7 | const createDateTimeValue = (value: DateTime): AtomicValue => 8 | createAtomicValue(value, ValueType.XSDATETIME); 9 | 10 | export default function castToDateTime( 11 | instanceOf: (typeName: ValueType) => boolean, 12 | ): (value: any) => CastResult { 13 | if (instanceOf(ValueType.XSDATE)) { 14 | return (value: DateTime) => ({ 15 | successful: true, 16 | value: createDateTimeValue(value.convertToType(ValueType.XSDATETIME)), 17 | }); 18 | } 19 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 20 | return (value) => ({ 21 | successful: true, 22 | value: createDateTimeValue(DateTime.fromString(value)), 23 | }); 24 | } 25 | return () => ({ 26 | successful: false, 27 | error: new Error( 28 | 'XPTY0004: Casting not supported from given type to xs:dateTime or any of its derived types.', 29 | ), 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToDayTimeDuration.ts: -------------------------------------------------------------------------------- 1 | import createAtomicValue from '../createAtomicValue'; 2 | import { ValueType } from '../Value'; 3 | import DayTimeDuration from '../valueTypes/DayTimeDuration'; 4 | import CastResult from './CastResult'; 5 | 6 | const createDayTimeDurationValue = (value: DayTimeDuration) => 7 | createAtomicValue(value, ValueType.XSDAYTIMEDURATION); 8 | 9 | export default function castToDayTimeDuration( 10 | instanceOf: (typeName: ValueType) => boolean, 11 | ): (value: any) => CastResult { 12 | if (instanceOf(ValueType.XSDURATION) && !instanceOf(ValueType.XSYEARMONTHDURATION)) { 13 | return (value) => ({ 14 | successful: true, 15 | value: createDayTimeDurationValue(value.getDayTimeDuration()), 16 | }); 17 | } 18 | if (instanceOf(ValueType.XSYEARMONTHDURATION)) { 19 | return () => ({ 20 | successful: true, 21 | value: createDayTimeDurationValue(DayTimeDuration.fromString('PT0.0S')), 22 | }); 23 | } 24 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 25 | return (value) => { 26 | const parsedDuration = DayTimeDuration.fromString(value); 27 | if (parsedDuration) { 28 | return { 29 | successful: true, 30 | value: createDayTimeDurationValue(parsedDuration), 31 | }; 32 | } 33 | return { 34 | successful: false, 35 | error: new Error(`FORG0001: Can not cast ${value} to xs:dayTimeDuration`), 36 | }; 37 | }; 38 | } 39 | return () => ({ 40 | successful: false, 41 | error: new Error( 42 | 'XPTY0004: Casting not supported from given type to xs:dayTimeDuration or any of its derived types.', 43 | ), 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToDouble.ts: -------------------------------------------------------------------------------- 1 | import createAtomicValue from '../createAtomicValue'; 2 | import { ValueType } from '../Value'; 3 | import CastResult from './CastResult'; 4 | import castToFloatLikeType from './castToFloatLikeType'; 5 | 6 | export default function castToDouble( 7 | instanceOf: (typeName: ValueType) => boolean, 8 | ): (value: any) => CastResult { 9 | const caster = castToFloatLikeType(instanceOf, ValueType.XSDOUBLE); 10 | return (value) => { 11 | const castResult = caster(value); 12 | if (!castResult.successful) { 13 | return castResult as CastResult; 14 | } 15 | return { 16 | successful: true, 17 | value: createAtomicValue(castResult.value, ValueType.XSDOUBLE), 18 | }; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToDuration.ts: -------------------------------------------------------------------------------- 1 | import createAtomicValue from '../createAtomicValue'; 2 | import { ValueType } from '../Value'; 3 | import Duration from '../valueTypes/Duration'; 4 | import CastResult from './CastResult'; 5 | 6 | const createDurationValue = (value: Duration) => createAtomicValue(value, ValueType.XSDURATION); 7 | 8 | export default function castToDuration( 9 | instanceOf: (typeName: ValueType) => boolean, 10 | ): (value: any) => CastResult { 11 | if (instanceOf(ValueType.XSYEARMONTHDURATION)) { 12 | return (value) => ({ 13 | successful: true, 14 | value: createDurationValue(Duration.fromYearMonthDuration(value)), 15 | }); 16 | } 17 | if (instanceOf(ValueType.XSDAYTIMEDURATION)) { 18 | return (value) => ({ 19 | successful: true, 20 | value: createDurationValue(Duration.fromDayTimeDuration(value)), 21 | }); 22 | } 23 | if (instanceOf(ValueType.XSDURATION)) { 24 | return (value) => ({ 25 | successful: true, 26 | value: createDurationValue(value), 27 | }); 28 | } 29 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 30 | return (value) => { 31 | const parsedDuration = Duration.fromString(value); 32 | if (parsedDuration) { 33 | return { 34 | successful: true, 35 | value: createDurationValue(parsedDuration), 36 | }; 37 | } 38 | return { 39 | successful: false, 40 | error: new Error(`FORG0001: Can not cast ${value} to xs:duration`), 41 | }; 42 | }; 43 | } 44 | return () => ({ 45 | successful: false, 46 | error: new Error( 47 | 'XPTY0004: Casting not supported from given type to xs:duration or any of its derived types.', 48 | ), 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToFloat.ts: -------------------------------------------------------------------------------- 1 | import createAtomicValue from '../createAtomicValue'; 2 | import { ValueType } from '../Value'; 3 | import CastResult from './CastResult'; 4 | import castToFloatLikeType from './castToFloatLikeType'; 5 | 6 | export default function castToFloat( 7 | instanceOf: (typeName: ValueType) => boolean, 8 | ): (value: any) => CastResult { 9 | const caster = castToFloatLikeType(instanceOf, ValueType.XSFLOAT); 10 | return (value) => { 11 | const castResult = caster(value); 12 | if (!castResult.successful) { 13 | return castResult as CastResult; 14 | } 15 | return { 16 | successful: true, 17 | value: createAtomicValue(castResult.value, ValueType.XSFLOAT), 18 | }; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToFloatLikeType.ts: -------------------------------------------------------------------------------- 1 | import { errFORG0001 } from '../../../expressions/XPathErrors'; 2 | import { ValueType } from '../Value'; 3 | 4 | export default function castToFloatLikeType( 5 | instanceOf: (typeName: ValueType) => boolean, 6 | to: ValueType, 7 | ): (value: any) => { successful: true; value: number } | { error: Error; successful: false } { 8 | if (instanceOf(ValueType.XSNUMERIC)) { 9 | return (value) => ({ 10 | successful: true, 11 | value, 12 | }); 13 | } 14 | if (instanceOf(ValueType.XSBOOLEAN)) { 15 | return (value) => ({ 16 | successful: true, 17 | value: value ? 1 : 0, 18 | }); 19 | } 20 | if (instanceOf(ValueType.XSSTRING) || instanceOf(ValueType.XSUNTYPEDATOMIC)) { 21 | return (value) => { 22 | switch (value) { 23 | case 'NaN': 24 | return { 25 | successful: true, 26 | value: NaN, 27 | }; 28 | case 'INF': 29 | case '+INF': 30 | return { 31 | successful: true, 32 | value: Infinity, 33 | }; 34 | case '-INF': 35 | return { 36 | successful: true, 37 | value: -Infinity, 38 | }; 39 | case '0': 40 | case '+0': 41 | return { 42 | successful: true, 43 | value: 0, 44 | }; 45 | case '-0': 46 | return { 47 | successful: true, 48 | value: -0, 49 | }; 50 | } 51 | const floatValue = parseFloat(value); 52 | if (!isNaN(floatValue)) { 53 | return { 54 | successful: true, 55 | value: floatValue, 56 | }; 57 | } 58 | return { 59 | successful: false, 60 | error: errFORG0001(value, to), 61 | }; 62 | }; 63 | } 64 | return () => ({ 65 | successful: false, 66 | error: new Error( 67 | `XPTY0004: Casting not supported from given type to ${to} or any of its derived types.`, 68 | ), 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToGDay.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { ValueType } from '../Value'; 4 | import DateTime from '../valueTypes/DateTime'; 5 | import CastResult from './CastResult'; 6 | 7 | const createGDayValue = (value: any): AtomicValue => createAtomicValue(value, ValueType.XSGDAY); 8 | 9 | export default function castToGDay( 10 | instanceOf: (typeName: ValueType) => boolean, 11 | ): (value: any) => CastResult { 12 | if (instanceOf(ValueType.XSDATE) || instanceOf(ValueType.XSDATETIME)) { 13 | return (value: DateTime) => ({ 14 | successful: true, 15 | value: createGDayValue(value.convertToType(ValueType.XSGDAY)), 16 | }); 17 | } 18 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 19 | return (value) => ({ 20 | successful: true, 21 | value: createGDayValue(DateTime.fromString(value)), 22 | }); 23 | } 24 | return () => ({ 25 | successful: false, 26 | error: new Error( 27 | 'XPTY0004: Casting not supported from given type to xs:gDay or any of its derived types.', 28 | ), 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToGMonth.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { ValueType } from '../Value'; 4 | import DateTime from '../valueTypes/DateTime'; 5 | import CastResult from './CastResult'; 6 | 7 | const createGMonthValue = (value: any): AtomicValue => createAtomicValue(value, ValueType.XSGMONTH); 8 | 9 | export default function castToGMonth( 10 | instanceOf: (typeName: ValueType) => boolean, 11 | ): (value: any) => CastResult { 12 | if (instanceOf(ValueType.XSDATE) || instanceOf(ValueType.XSDATETIME)) { 13 | return (value: DateTime) => ({ 14 | successful: true, 15 | value: createGMonthValue(value.convertToType(ValueType.XSGMONTH)), 16 | }); 17 | } 18 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 19 | return (value) => ({ 20 | successful: true, 21 | value: createGMonthValue(DateTime.fromString(value)), 22 | }); 23 | } 24 | return () => ({ 25 | successful: false, 26 | error: new Error( 27 | 'XPTY0004: Casting not supported from given type to xs:gMonth or any of its derived types.', 28 | ), 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToGMonthDay.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { ValueType } from '../Value'; 4 | import DateTime from '../valueTypes/DateTime'; 5 | import CastResult from './CastResult'; 6 | 7 | const createGMonthDayValue = (value: any): AtomicValue => 8 | createAtomicValue(value, ValueType.XSGMONTHDAY); 9 | 10 | export default function castToGMonthDay( 11 | instanceOf: (typeName: ValueType) => boolean, 12 | ): (value: any) => CastResult { 13 | if (instanceOf(ValueType.XSDATE) || instanceOf(ValueType.XSDATETIME)) { 14 | return (value) => ({ 15 | successful: true, 16 | value: createGMonthDayValue(value.convertToType(ValueType.XSGMONTHDAY)), 17 | }); 18 | } 19 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 20 | return (value) => ({ 21 | successful: true, 22 | value: createGMonthDayValue(DateTime.fromString(value)), 23 | }); 24 | } 25 | return () => ({ 26 | successful: false, 27 | error: new Error( 28 | 'XPTY0004: Casting not supported from given type to xs:gMonthDay or any of its derived types.', 29 | ), 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToGYear.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { SequenceMultiplicity, ValueType } from '../Value'; 4 | import DateTime from '../valueTypes/DateTime'; 5 | import CastResult from './CastResult'; 6 | 7 | const createGYearValue = (value: any): AtomicValue => createAtomicValue(value, ValueType.XSGYEAR); 8 | 9 | export default function castToGYear( 10 | instanceOf: (typeName: ValueType) => boolean, 11 | ): (value: any) => CastResult { 12 | if (instanceOf(ValueType.XSDATE) || instanceOf(ValueType.XSDATETIME)) { 13 | return (value) => ({ 14 | successful: true, 15 | value: createGYearValue(value.convertToType(ValueType.XSGYEAR)), 16 | }); 17 | } 18 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 19 | return (value) => ({ 20 | successful: true, 21 | value: createGYearValue(DateTime.fromString(value)), 22 | }); 23 | } 24 | return () => ({ 25 | successful: false, 26 | error: new Error( 27 | 'XPTY0004: Casting not supported from given type to xs:gYear or any of its derived types.', 28 | ), 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToGYearMonth.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { ValueType } from '../Value'; 4 | import DateTime from '../valueTypes/DateTime'; 5 | import CastResult from './CastResult'; 6 | 7 | const createGYearMonthValue = (value: any): AtomicValue => 8 | createAtomicValue(value, ValueType.XSGYEARMONTH); 9 | 10 | export default function castToGYearMonth( 11 | instanceOf: (typeName: ValueType) => boolean, 12 | ): (value: any) => CastResult { 13 | if (instanceOf(ValueType.XSDATE) || instanceOf(ValueType.XSDATETIME)) { 14 | return (value) => ({ 15 | successful: true, 16 | value: createGYearMonthValue(value.convertToType(ValueType.XSGYEARMONTH)), 17 | }); 18 | } 19 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 20 | return (value) => ({ 21 | successful: true, 22 | value: createGYearMonthValue(DateTime.fromString(value)), 23 | }); 24 | } 25 | return () => ({ 26 | successful: false, 27 | error: new Error( 28 | 'XPTY0004: Casting not supported from given type to xs:gYearMonth or any of its derived types.', 29 | ), 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToHexBinary.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { SequenceMultiplicity, ValueType } from '../Value'; 4 | import CastResult from './CastResult'; 5 | 6 | const createHexBinaryValue = (value: any): AtomicValue => 7 | createAtomicValue(value, ValueType.XSHEXBINARY); 8 | 9 | function stringToHex(s: string) { 10 | let hex = ''; 11 | for (let i = 0, l = s.length; i < l; i++) { 12 | hex += Number(s.charCodeAt(i)).toString(16); 13 | } 14 | return hex.toUpperCase(); 15 | } 16 | 17 | // This declaration is needed, as we don't depend anymore on lib.dom. 18 | declare let atob: (s: string) => string; 19 | 20 | export default function castToHexBinary( 21 | instanceOf: (typeName: ValueType) => boolean, 22 | ): (value: any) => CastResult { 23 | if (instanceOf(ValueType.XSBASE64BINARY)) { 24 | return (value) => ({ 25 | successful: true, 26 | value: createHexBinaryValue(stringToHex(atob(value))), 27 | }); 28 | } 29 | if (instanceOf(ValueType.XSSTRING) || instanceOf(ValueType.XSUNTYPEDATOMIC)) { 30 | return (value) => ({ 31 | successful: true, 32 | value: createHexBinaryValue(value), 33 | }); 34 | } 35 | return () => ({ 36 | successful: false, 37 | error: new Error( 38 | 'XPTY0004: Casting not supported from given type to xs:hexBinary or any of its derived types.', 39 | ), 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToNumeric.ts: -------------------------------------------------------------------------------- 1 | import { ValueType } from '../Value'; 2 | import CastResult from './CastResult'; 3 | 4 | const numericTypes = [ 5 | ValueType.XSDOUBLE, 6 | ValueType.XSFLOAT, 7 | ValueType.XSDECIMAL, 8 | ValueType.XSINTEGER, 9 | ]; 10 | 11 | export default function castToNumeric( 12 | inputType: ValueType, 13 | castToPrimitiveType: ( 14 | inputType: ValueType, 15 | outputType: ValueType, 16 | ) => (value: any) => CastResult, 17 | ): (value: any) => CastResult { 18 | return (value) => { 19 | for (const outputType of numericTypes) { 20 | const result = castToPrimitiveType(inputType, outputType)(value); 21 | if (result.successful) { 22 | return result; 23 | } 24 | } 25 | // In the case of failure, return the result of the last attempt 26 | return { 27 | successful: false, 28 | error: new Error( 29 | `XPTY0004: Casting not supported from "${value}" given type to xs:numeric or any of its derived types.`, 30 | ), 31 | }; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToString.ts: -------------------------------------------------------------------------------- 1 | import createAtomicValue from '../createAtomicValue'; 2 | import { ValueType } from '../Value'; 3 | import CastResult from './CastResult'; 4 | import castToStringLikeType from './castToStringLikeType'; 5 | 6 | export default function castToString( 7 | instanceOf: (typeName: ValueType) => boolean, 8 | ): (value: any) => CastResult { 9 | const caster = castToStringLikeType(instanceOf); 10 | return (value) => { 11 | const castResult = caster(value); 12 | if (!castResult.successful) { 13 | return castResult; 14 | } 15 | 16 | return { 17 | successful: true, 18 | value: createAtomicValue(castResult.value, ValueType.XSSTRING), 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToTime.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../AtomicValue'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { SequenceMultiplicity, ValueType } from '../Value'; 4 | import DateTime from '../valueTypes/DateTime'; 5 | import CastResult from './CastResult'; 6 | 7 | const createTimeValue = (value: any): AtomicValue => createAtomicValue(value, ValueType.XSTIME); 8 | 9 | export default function castToTime( 10 | instanceOf: (typeName: ValueType) => boolean, 11 | ): (value: any) => CastResult { 12 | if (instanceOf(ValueType.XSDATETIME)) { 13 | return (value) => ({ 14 | successful: true, 15 | value: createTimeValue(value.convertToType(ValueType.XSTIME)), 16 | }); 17 | } 18 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 19 | return (value) => ({ 20 | successful: true, 21 | value: createTimeValue(DateTime.fromString(value)), 22 | }); 23 | } 24 | return (value) => ({ 25 | successful: false, 26 | error: new Error( 27 | 'XPTY0004: Casting not supported from given type to xs:time or any of its derived types.', 28 | ), 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToUntypedAtomic.ts: -------------------------------------------------------------------------------- 1 | import createAtomicValue from '../createAtomicValue'; 2 | import { ValueType } from '../Value'; 3 | import CastResult from './CastResult'; 4 | import castToStringLikeType from './castToStringLikeType'; 5 | 6 | export default function castToUntypedAtomic( 7 | instanceOf: (typeName: ValueType) => boolean, 8 | ): (value: any) => CastResult { 9 | const caster = castToStringLikeType(instanceOf); 10 | return (value) => { 11 | const castResult = caster(value); 12 | if (!castResult.successful) { 13 | return castResult; 14 | } 15 | 16 | return { 17 | successful: true, 18 | value: createAtomicValue(castResult.value, ValueType.XSUNTYPEDATOMIC), 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/casting/castToYearMonthDuration.ts: -------------------------------------------------------------------------------- 1 | import { errFORG0001 } from '../../../expressions/XPathErrors'; 2 | import createAtomicValue from '../createAtomicValue'; 3 | import { ValueType } from '../Value'; 4 | import YearMonthDuration from '../valueTypes/YearMonthDuration'; 5 | import CastResult from './CastResult'; 6 | 7 | const createYearMonthDurationValue = (value: YearMonthDuration) => 8 | createAtomicValue(value, ValueType.XSYEARMONTHDURATION); 9 | 10 | export default function castToYearMonthDuration( 11 | instanceOf: (typeName: ValueType) => boolean, 12 | ): (value: any) => CastResult { 13 | if (instanceOf(ValueType.XSDURATION) && !instanceOf(ValueType.XSDAYTIMEDURATION)) { 14 | return (value) => ({ 15 | successful: true, 16 | value: createYearMonthDurationValue(value.getYearMonthDuration()), 17 | }); 18 | } 19 | if (instanceOf(ValueType.XSDAYTIMEDURATION)) { 20 | return (_value) => ({ 21 | successful: true, 22 | value: createYearMonthDurationValue(YearMonthDuration.fromString('P0M')), 23 | }); 24 | } 25 | if (instanceOf(ValueType.XSUNTYPEDATOMIC) || instanceOf(ValueType.XSSTRING)) { 26 | return (value) => { 27 | const parsedDuration = YearMonthDuration.fromString(value); 28 | if (parsedDuration) { 29 | return { 30 | successful: true, 31 | value: createYearMonthDurationValue(parsedDuration), 32 | }; 33 | } 34 | return { 35 | successful: false, 36 | error: errFORG0001(value, ValueType.XSYEARMONTHDURATION), 37 | }; 38 | }; 39 | } 40 | return () => ({ 41 | successful: false, 42 | error: new Error( 43 | 'XPTY0004: Casting not supported from given type to xs:yearMonthDuration or any of its derived types.', 44 | ), 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/createAtomicValue.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from './AtomicValue'; 2 | import builtinDataTypesByType from './builtins/builtinDataTypesByType'; 3 | import { ValueType } from './Value'; 4 | 5 | export default function createAtomicValue(value: any, type: ValueType): AtomicValue { 6 | if (!builtinDataTypesByType[type]) { 7 | throw new Error('Unknown type'); 8 | } 9 | 10 | return { 11 | type, 12 | value, 13 | }; 14 | } 15 | 16 | export const trueBoolean = createAtomicValue(true, ValueType.XSBOOLEAN); 17 | export const falseBoolean = createAtomicValue(false, ValueType.XSBOOLEAN); 18 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/createPointerValue.ts: -------------------------------------------------------------------------------- 1 | import { NodePointer } from '../../domClone/Pointer'; 2 | import DomFacade from '../../domFacade/DomFacade'; 3 | import Value, { SequenceMultiplicity, ValueType } from './Value'; 4 | 5 | function getNodeSubType(pointer: NodePointer, domFacade: DomFacade): ValueType { 6 | switch (domFacade.getNodeType(pointer)) { 7 | case 2: 8 | return ValueType.ATTRIBUTE; 9 | case 1: 10 | return ValueType.ELEMENT; 11 | case 3: 12 | case 4: // CDATA nodes are text too 13 | return ValueType.TEXT; 14 | case 7: 15 | return ValueType.PROCESSINGINSTRUCTION; 16 | case 8: 17 | return ValueType.COMMENT; 18 | case 9: 19 | return ValueType.DOCUMENTNODE; 20 | default: 21 | return ValueType.NODE; 22 | } 23 | } 24 | 25 | export default function createPointerValue(pointer: NodePointer, domFacade: DomFacade): Value { 26 | const nodeValue: Value = { type: getNodeSubType(pointer, domFacade), value: pointer }; 27 | return nodeValue; 28 | } 29 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/facets/comparators/decimalComparator.ts: -------------------------------------------------------------------------------- 1 | function padFraction(fractionString: string, maxLength: number) { 2 | return fractionString.padEnd(maxLength, '0'); 3 | } 4 | 5 | export default function decimalComparator(value1: string | number, value2: string | number) { 6 | if ((value1 === '0' || value1 === '-0') && (value2 === '0' || value2 === '-0')) { 7 | return 0; 8 | } 9 | 10 | const regex = /(?:\+|(-))?(\d+)?(?:\.(\d+))?/; 11 | const match1 = regex.exec(value1 + ''); 12 | const match2 = regex.exec(value2 + ''); 13 | 14 | const positive1 = !match1[1]; 15 | const positive2 = !match2[1]; 16 | const val1 = (match1[2] || '').replace(/^0*/, ''); 17 | const val2 = (match2[2] || '').replace(/^0*/, ''); 18 | const fraction1 = match1[3] || ''; 19 | const fraction2 = match2[3] || ''; 20 | 21 | if (positive1 && !positive2) { 22 | return 1; 23 | } 24 | if (!positive1 && positive2) { 25 | return -1; 26 | } 27 | 28 | const bothPositive = positive1 && positive2; 29 | if (val1.length > val2.length) { 30 | return bothPositive ? 1 : -1; 31 | } 32 | if (val1.length < val2.length) { 33 | return bothPositive ? -1 : 1; 34 | } 35 | 36 | if (val1 > val2) { 37 | return bothPositive ? 1 : -1; 38 | } 39 | if (val1 < val2) { 40 | return bothPositive ? -1 : 1; 41 | } 42 | 43 | const maxLengthFractions = Math.max(fraction1.length, fraction2.length); 44 | const paddedFraction1 = padFraction(fraction1, maxLengthFractions); 45 | const paddedFraction2 = padFraction(fraction2, maxLengthFractions); 46 | 47 | if (paddedFraction1 > paddedFraction2) { 48 | return bothPositive ? 1 : -1; 49 | } 50 | if (paddedFraction1 < paddedFraction2) { 51 | return bothPositive ? -1 : 1; 52 | } 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/isSubtypeOf.ts: -------------------------------------------------------------------------------- 1 | import builtinDataTypesByType, { TypeModel } from './builtins/builtinDataTypesByType'; 2 | import { ValueType } from './Value'; 3 | import { Variety } from './Variety'; 4 | 5 | function isSubtypeOfType(subType: TypeModel, superType: TypeModel): boolean { 6 | if (superType.variety === Variety.UNION) { 7 | // It is a union type, which can only be the topmost types 8 | return !!superType.memberTypes.find((memberType) => isSubtypeOfType(subType, memberType)); 9 | } 10 | 11 | while (subType) { 12 | if (subType.type === superType.type) { 13 | return true; 14 | } 15 | if (subType.variety === Variety.UNION) { 16 | return !!subType.memberTypes.find((memberType) => 17 | isSubtypeOf(memberType.type, superType.type), 18 | ); 19 | } 20 | subType = subType.parent; 21 | } 22 | return false; 23 | } 24 | 25 | /** 26 | * xs:int is a subtype of xs:integer 27 | * xs:decimal is a subtype of xs:numeric 28 | * xs:NMTOKENS is a subtype of xs:NM TOKEN 29 | */ 30 | export default function isSubtypeOf(baseSubType: ValueType, baseSuperType: ValueType): boolean { 31 | if (baseSubType === baseSuperType) { 32 | return true; 33 | } 34 | 35 | const superType: TypeModel = builtinDataTypesByType[baseSuperType]; 36 | const subType: TypeModel = builtinDataTypesByType[baseSubType]; 37 | 38 | return isSubtypeOfType(subType, superType); 39 | } 40 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/promoteToType.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from './AtomicValue'; 2 | import createAtomicValue from './createAtomicValue'; 3 | import isSubtypeOf from './isSubtypeOf'; 4 | import Value, { ValueType } from './Value'; 5 | 6 | export default function promoteToType(value: Value, type: ValueType): AtomicValue { 7 | if (isSubtypeOf(value.type, ValueType.XSNUMERIC)) { 8 | if (isSubtypeOf(value.type, ValueType.XSFLOAT)) { 9 | if (type === ValueType.XSDOUBLE) { 10 | return createAtomicValue(value.value, ValueType.XSDOUBLE); 11 | } 12 | return null; 13 | } 14 | if (isSubtypeOf(value.type, ValueType.XSDECIMAL)) { 15 | if (type === ValueType.XSFLOAT) { 16 | return createAtomicValue(value.value, ValueType.XSFLOAT); 17 | } 18 | if (type === ValueType.XSDOUBLE) { 19 | return createAtomicValue(value.value, ValueType.XSDOUBLE); 20 | } 21 | } 22 | return null; 23 | } 24 | 25 | if (isSubtypeOf(value.type, ValueType.XSANYURI)) { 26 | if (type === ValueType.XSSTRING) { 27 | return createAtomicValue(value.value, ValueType.XSSTRING); 28 | } 29 | } 30 | return null; 31 | } 32 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/sequenceFactory.ts: -------------------------------------------------------------------------------- 1 | import { IIterator } from '../util/iterators'; 2 | import { falseBoolean, trueBoolean } from './createAtomicValue'; 3 | import ISequence from './ISequence'; 4 | import ArrayBackedSequence from './Sequences/ArrayBackedSequence'; 5 | import EmptySequence from './Sequences/EmptySequence'; 6 | import IteratorBackedSequence from './Sequences/IteratorBackedSequence'; 7 | import SingletonSequence from './Sequences/SingletonSequence'; 8 | import Value from './Value'; 9 | 10 | const emptySequence = new EmptySequence(); 11 | 12 | function create( 13 | value: Value | Value[] | IIterator | null = null, 14 | predictedLength: number | null | undefined = null, 15 | ): ISequence { 16 | if (value === null) { 17 | return emptySequence; 18 | } 19 | if (Array.isArray(value)) { 20 | switch (value.length) { 21 | case 0: 22 | return emptySequence; 23 | case 1: 24 | return new SingletonSequence(sequenceFactory, value[0]); 25 | default: 26 | return new ArrayBackedSequence(sequenceFactory, value); 27 | } 28 | } 29 | 30 | if ((value as IIterator).next) { 31 | return new IteratorBackedSequence( 32 | sequenceFactory, 33 | value as IIterator, 34 | predictedLength, 35 | ); 36 | } 37 | return new SingletonSequence(sequenceFactory, value as Value); 38 | } 39 | 40 | const sequenceFactory = { 41 | create, 42 | 43 | singleton: (value: Value): ISequence => { 44 | return new SingletonSequence(sequenceFactory, value); 45 | }, 46 | 47 | empty: () => { 48 | return create(); 49 | }, 50 | 51 | singletonTrueSequence: () => { 52 | return create(trueBoolean); 53 | }, 54 | 55 | singletonFalseSequence: () => { 56 | return create(falseBoolean); 57 | }, 58 | }; 59 | 60 | export default sequenceFactory; 61 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/valueTypes/AbstractDuration.ts: -------------------------------------------------------------------------------- 1 | abstract class AbstractDuration { 2 | public equals(other: AbstractDuration) { 3 | return ( 4 | this.getRawMonths() === other.getRawMonths() && 5 | this.getRawSeconds() === other.getRawSeconds() 6 | ); 7 | } 8 | 9 | public getDays() { 10 | return 0; 11 | } 12 | 13 | public getHours() { 14 | return 0; 15 | } 16 | 17 | public getMinutes() { 18 | return 0; 19 | } 20 | 21 | public getMonths() { 22 | return 0; 23 | } 24 | public getRawMonths() { 25 | return 0; 26 | } 27 | 28 | public getRawSeconds() { 29 | return 0; 30 | } 31 | 32 | public getSeconds() { 33 | return 0; 34 | } 35 | 36 | public getYears() { 37 | return 0; 38 | } 39 | 40 | public isPositive() { 41 | return true; 42 | } 43 | } 44 | 45 | export default AbstractDuration; 46 | -------------------------------------------------------------------------------- /src/expressions/dataTypes/valueTypes/QName.ts: -------------------------------------------------------------------------------- 1 | class QName { 2 | public localName: string; 3 | public namespaceURI: string; 4 | public prefix: string; 5 | 6 | /** 7 | * @param prefix The prefix of the QName, empty string if absent 8 | * @param namespaceURI The namespaceURI of the QName, empty string if absent 9 | * @param localName The localName of the QName, contains no colons 10 | */ 11 | constructor(prefix: string, namespaceURI: string | null, localName: string) { 12 | this.namespaceURI = namespaceURI || null; 13 | this.prefix = prefix || ''; 14 | this.localName = localName; 15 | } 16 | 17 | public buildPrefixedName?() { 18 | return this.prefix ? this.prefix + ':' + this.localName : this.localName; 19 | } 20 | } 21 | 22 | export default QName; 23 | -------------------------------------------------------------------------------- /src/expressions/debug/StackTraceEntry.ts: -------------------------------------------------------------------------------- 1 | import { PositionedError } from '../../evaluationUtils/PositionedError'; 2 | import { SourceRange } from './StackTraceGenerator'; 3 | export class StackTraceEntry { 4 | public comment: string; 5 | public innerExpressionType: string; 6 | public innerTrace: Error | PositionedError | StackTraceEntry; 7 | public location: SourceRange; 8 | 9 | constructor( 10 | location: SourceRange, 11 | innerExpressionType: string, 12 | comment: string, 13 | innerTrace: Error | StackTraceEntry, 14 | ) { 15 | this.location = location; 16 | this.innerExpressionType = innerExpressionType; 17 | this.comment = comment; 18 | this.innerTrace = innerTrace; 19 | } 20 | 21 | public getErrorLocation(): SourceRange { 22 | return this.innerTrace instanceof Error 23 | ? this.location 24 | : this.innerTrace.getErrorLocation(); 25 | } 26 | 27 | public makeStackTrace(): string[] { 28 | let innerStackTrace: string[]; 29 | if (this.innerTrace instanceof PositionedError) { 30 | // We are dealing with a nested positioned error 31 | innerStackTrace = ['Inner error:', this.innerTrace.message]; 32 | } else if (this.innerTrace instanceof Error) { 33 | innerStackTrace = [this.innerTrace.toString()]; 34 | } else { 35 | innerStackTrace = this.innerTrace.makeStackTrace(); 36 | } 37 | innerStackTrace.push( 38 | ` at <${this.innerExpressionType}${this.comment ? ` (${this.comment})` : ''}>:${ 39 | this.location.start.line 40 | }:${this.location.start.column} - ${this.location.end.line}:${ 41 | this.location.end.column 42 | }`, 43 | ); 44 | return innerStackTrace; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/expressions/functions/FunctionDefinitionType.ts: -------------------------------------------------------------------------------- 1 | import ISequence from '../dataTypes/ISequence'; 2 | import DynamicContext from '../DynamicContext'; 3 | import ExecutionParameters from '../ExecutionParameters'; 4 | import StaticContext from '../StaticContext'; 5 | 6 | type FunctionDefinitionType = ( 7 | dynamicContext: DynamicContext, 8 | executionParameters: ExecutionParameters, 9 | staticContext: StaticContext, 10 | ...sequences: ISequence[] 11 | ) => FunctionReturnType; 12 | 13 | export default FunctionDefinitionType; 14 | -------------------------------------------------------------------------------- /src/expressions/functions/FunctionOperationErrors.ts: -------------------------------------------------------------------------------- 1 | export const errFOAY0001 = () => new Error('FOAY0001: Array index out of bounds'); 2 | 3 | export const errFORG0006 = ( 4 | message: string = 'A wrong argument type was specified in a function call.', 5 | ) => new Error(`FORG0006: ${message}`); 6 | -------------------------------------------------------------------------------- /src/expressions/functions/argumentListToString.ts: -------------------------------------------------------------------------------- 1 | import ISequence from '../dataTypes/ISequence'; 2 | import { valueTypeToString } from '../dataTypes/Value'; 3 | 4 | export default function argumentListToString(argumentList: ISequence[]) { 5 | return argumentList 6 | .map((argument) => { 7 | if (argument === null) { 8 | return 'placeholder'; 9 | } 10 | if (argument.isEmpty()) { 11 | return 'item()?'; 12 | } 13 | 14 | if (argument.isSingleton()) { 15 | return valueTypeToString(argument.first().type) || 'item()'; 16 | } 17 | return valueTypeToString(argument.first().type) + '+'; 18 | }) 19 | .map((types) => `${types}`) 20 | .join(', '); 21 | } 22 | -------------------------------------------------------------------------------- /src/expressions/functions/builtInFunctions_arrays_get.ts: -------------------------------------------------------------------------------- 1 | import ArrayValue from '../dataTypes/ArrayValue'; 2 | import FunctionDefinitionType from './FunctionDefinitionType'; 3 | 4 | const arrayGet: FunctionDefinitionType = ( 5 | _dynamicContext, 6 | _executionParameters, 7 | _staticContext, 8 | arraySequence, 9 | positionSequence, 10 | ) => { 11 | return positionSequence.mapAll(([position]) => 12 | arraySequence.mapAll(([array]) => { 13 | const positionValue = position.value; 14 | if (positionValue <= 0 || positionValue > (array as ArrayValue).members.length) { 15 | throw new Error('FOAY0001: array position out of bounds.'); 16 | } 17 | return (array as ArrayValue).members[positionValue - 1](); 18 | }), 19 | ); 20 | }; 21 | 22 | export default arrayGet; 23 | -------------------------------------------------------------------------------- /src/expressions/functions/builtInFunctions_maps_get.ts: -------------------------------------------------------------------------------- 1 | import MapValue from '../dataTypes/MapValue'; 2 | import sequenceFactory from '../dataTypes/sequenceFactory'; 3 | import zipSingleton from '../util/zipSingleton'; 4 | import FunctionDefinitionType from './FunctionDefinitionType'; 5 | import isSameMapKey from './isSameMapKey'; 6 | 7 | const mapGet: FunctionDefinitionType = ( 8 | _dynamicContext, 9 | _executionParameters, 10 | _staticContext, 11 | mapSequence, 12 | key, 13 | ) => { 14 | return zipSingleton([mapSequence, key], ([map, keyValue]) => { 15 | const matchingPair = (map as MapValue).keyValuePairs.find((keyValuePair) => { 16 | return isSameMapKey(keyValuePair.key, keyValue); 17 | }); 18 | 19 | if (!matchingPair) { 20 | return sequenceFactory.empty(); 21 | } 22 | return matchingPair.value(); 23 | }); 24 | }; 25 | 26 | export default mapGet; 27 | -------------------------------------------------------------------------------- /src/expressions/functions/generateId.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteNode } from '../../domFacade/ConcreteNode'; 2 | import { NodePointer, TinyNode } from '../../domClone/Pointer'; 3 | 4 | const generateIdMap: WeakMap = new WeakMap(); 5 | let generateIdCounter = 0; 6 | 7 | function generateId(ptr: NodePointer): string { 8 | const node = ptr.node; 9 | if (!generateIdMap.has(node)) { 10 | generateIdMap.set(node, `id${++generateIdCounter}`); 11 | } 12 | return generateIdMap.get(node); 13 | } 14 | 15 | export default generateId; 16 | -------------------------------------------------------------------------------- /src/expressions/literals/Literal.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../dataTypes/AtomicValue'; 2 | import createAtomicValue from '../dataTypes/createAtomicValue'; 3 | import ISequence from '../dataTypes/ISequence'; 4 | import sequenceFactory from '../dataTypes/sequenceFactory'; 5 | import { SequenceType, ValueType } from '../dataTypes/Value'; 6 | import Expression, { RESULT_ORDERINGS } from '../Expression'; 7 | import Specificity from '../Specificity'; 8 | 9 | class Literal extends Expression { 10 | private _createValueSequence: () => ISequence; 11 | 12 | constructor(jsValue: string, type: SequenceType) { 13 | super( 14 | new Specificity({}), 15 | [], 16 | { 17 | canBeStaticallyEvaluated: true, 18 | resultOrder: RESULT_ORDERINGS.SORTED, 19 | }, 20 | false, 21 | type, 22 | ); 23 | 24 | let value: AtomicValue; 25 | switch (type.type) { 26 | case ValueType.XSINTEGER: 27 | value = createAtomicValue(parseInt(jsValue, 10), type.type); 28 | break; 29 | case ValueType.XSSTRING: 30 | value = createAtomicValue(jsValue + '', type.type); 31 | break; 32 | case ValueType.XSDECIMAL: 33 | case ValueType.XSDOUBLE: 34 | value = createAtomicValue(parseFloat(jsValue), type.type); 35 | break; 36 | default: 37 | throw new TypeError('Type ' + type + ' not expected in a literal'); 38 | } 39 | 40 | this._createValueSequence = () => sequenceFactory.singleton(value); 41 | } 42 | 43 | public evaluate() { 44 | return this._createValueSequence(); 45 | } 46 | } 47 | 48 | export default Literal; 49 | -------------------------------------------------------------------------------- /src/expressions/operators/SequenceOperator.ts: -------------------------------------------------------------------------------- 1 | import ISequence from '../dataTypes/ISequence'; 2 | import sequenceFactory from '../dataTypes/sequenceFactory'; 3 | import { SequenceType } from '../dataTypes/Value'; 4 | import DynamicContext from '../DynamicContext'; 5 | import ExecutionParameters from '../ExecutionParameters'; 6 | import Expression, { RESULT_ORDERINGS } from '../Expression'; 7 | import PossiblyUpdatingExpression from '../PossiblyUpdatingExpression'; 8 | import Specificity from '../Specificity'; 9 | import concatSequences from '../util/concatSequences'; 10 | 11 | /** 12 | * The Sequence selector evaluates its operands and returns them as a single sequence 13 | */ 14 | class SequenceOperator extends PossiblyUpdatingExpression { 15 | constructor(expressions: Expression[], type: SequenceType) { 16 | super( 17 | expressions.reduce((specificity, selector) => { 18 | return specificity.add(selector.specificity); 19 | }, new Specificity({})), 20 | expressions, 21 | { 22 | resultOrder: RESULT_ORDERINGS.UNSORTED, 23 | canBeStaticallyEvaluated: expressions.every( 24 | (selector) => selector.canBeStaticallyEvaluated, 25 | ), 26 | }, 27 | type, 28 | ); 29 | } 30 | 31 | public performFunctionalEvaluation( 32 | dynamicContext: DynamicContext, 33 | _executionParameters: ExecutionParameters, 34 | sequenceCallbacks: ((innerDynamicContext: DynamicContext) => ISequence)[], 35 | ) { 36 | if (!sequenceCallbacks.length) { 37 | return sequenceFactory.empty(); 38 | } 39 | return concatSequences(sequenceCallbacks.map((cb) => cb(dynamicContext))); 40 | } 41 | } 42 | 43 | export default SequenceOperator; 44 | -------------------------------------------------------------------------------- /src/expressions/operators/UniversalExpression.ts: -------------------------------------------------------------------------------- 1 | import sequenceFactory from '../dataTypes/sequenceFactory'; 2 | import Expression from '../Expression'; 3 | import Specificity from '../Specificity'; 4 | 5 | class UniversalExpression extends Expression { 6 | constructor() { 7 | super( 8 | new Specificity({ 9 | [Specificity.UNIVERSAL_KIND]: 1, 10 | }), 11 | [], 12 | { 13 | canBeStaticallyEvaluated: true, 14 | }, 15 | ); 16 | } 17 | 18 | public evaluate() { 19 | return sequenceFactory.singletonTrueSequence(); 20 | } 21 | } 22 | export default UniversalExpression; 23 | -------------------------------------------------------------------------------- /src/expressions/operators/compares/arePointersEqual.ts: -------------------------------------------------------------------------------- 1 | import { GraftPoint, NodePointer } from '../../../domClone/Pointer'; 2 | 3 | function areGraftAncestorsSame(graftAncestor1: GraftPoint, graftAncestor2: GraftPoint): boolean { 4 | if (graftAncestor1 === graftAncestor2) { 5 | return true; 6 | } 7 | 8 | if ( 9 | graftAncestor1 && 10 | graftAncestor2 && 11 | graftAncestor1.offset === graftAncestor2.offset && 12 | graftAncestor1.parent === graftAncestor2.parent 13 | ) { 14 | return areGraftAncestorsSame(graftAncestor1.graftAncestor, graftAncestor2.graftAncestor); 15 | } 16 | 17 | return false; 18 | } 19 | 20 | function arePointersEqual(pointer1: NodePointer, pointer2: NodePointer) { 21 | if ( 22 | pointer1 === pointer2 || 23 | (pointer1.node === pointer2.node && 24 | areGraftAncestorsSame(pointer1.graftAncestor, pointer2.graftAncestor)) 25 | ) { 26 | return true; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | export default arePointersEqual; 33 | -------------------------------------------------------------------------------- /src/expressions/path/ContextItemExpression.ts: -------------------------------------------------------------------------------- 1 | import sequenceFactory from '../dataTypes/sequenceFactory'; 2 | import { SequenceType } from '../dataTypes/Value'; 3 | import DynamicContext from '../DynamicContext'; 4 | import ExecutionParameters from '../ExecutionParameters'; 5 | import Expression, { RESULT_ORDERINGS } from '../Expression'; 6 | import Specificity from '../Specificity'; 7 | import { errXPDY0002 } from '../XPathErrors'; 8 | 9 | class ContextItemExpression extends Expression { 10 | constructor(type: SequenceType) { 11 | super( 12 | new Specificity({}), 13 | [], 14 | { 15 | resultOrder: RESULT_ORDERINGS.SORTED, 16 | }, 17 | false, 18 | type, 19 | ); 20 | } 21 | 22 | public evaluate(dynamicContext: DynamicContext, _executionParameters: ExecutionParameters) { 23 | if (dynamicContext.contextItem === null) { 24 | throw errXPDY0002('context is absent, it needs to be present to use the "." operator'); 25 | } 26 | return sequenceFactory.singleton(dynamicContext.contextItem); 27 | } 28 | } 29 | export default ContextItemExpression; 30 | -------------------------------------------------------------------------------- /src/expressions/postfix/Lookup.ts: -------------------------------------------------------------------------------- 1 | import EmptySequence from '../dataTypes/Sequences/EmptySequence'; 2 | import DynamicContext from '../DynamicContext'; 3 | import ExecutionParameters from '../ExecutionParameters'; 4 | import Expression from '../Expression'; 5 | import { Bucket } from '../util/Bucket'; 6 | import evaluateLookup from './evaluateLookup'; 7 | 8 | class Lookup extends Expression { 9 | private readonly _keySpecifier: '*' | Expression; 10 | private readonly _selector: Expression; 11 | 12 | constructor(selector: Expression, keySpecifier: '*' | Expression) { 13 | super(selector.specificity, [selector].concat(keySpecifier === '*' ? [] : [keySpecifier]), { 14 | canBeStaticallyEvaluated: selector.canBeStaticallyEvaluated, 15 | resultOrder: selector.expectedResultOrder, 16 | subtree: selector.subtree, 17 | }); 18 | 19 | this._selector = selector; 20 | this._keySpecifier = keySpecifier; 21 | } 22 | 23 | public evaluate(dynamicContext: DynamicContext, executionParameters: ExecutionParameters) { 24 | const sequence = this._selector.evaluateMaybeStatically( 25 | dynamicContext, 26 | executionParameters, 27 | ); 28 | return sequence.mapAll((items) => { 29 | return items.reduce((toReturn, item) => { 30 | return evaluateLookup( 31 | item, 32 | this._keySpecifier, 33 | toReturn, 34 | dynamicContext, 35 | executionParameters, 36 | ); 37 | }, new EmptySequence()); 38 | }); 39 | } 40 | 41 | public override getBucket(): Bucket | null { 42 | return this._selector.getBucket(); 43 | } 44 | } 45 | 46 | export default Lookup; 47 | -------------------------------------------------------------------------------- /src/expressions/postfix/UnaryLookup.ts: -------------------------------------------------------------------------------- 1 | import EmptySequence from '../dataTypes/Sequences/EmptySequence'; 2 | import { SequenceType } from '../dataTypes/Value'; 3 | import DynamicContext from '../DynamicContext'; 4 | import ExecutionParameters from '../ExecutionParameters'; 5 | import Expression from '../Expression'; 6 | import Specificity from '../Specificity'; 7 | import evaluateLookup from './evaluateLookup'; 8 | 9 | class UnaryLookup extends Expression { 10 | private readonly _keySpecifier: '*' | Expression; 11 | 12 | constructor(keySpecifier: '*' | Expression, type: SequenceType) { 13 | super( 14 | new Specificity({ 15 | [Specificity.EXTERNAL_KIND]: 1, 16 | }), 17 | keySpecifier === '*' ? [] : [keySpecifier], 18 | { canBeStaticallyEvaluated: false }, 19 | false, 20 | type, 21 | ); 22 | 23 | this._keySpecifier = keySpecifier; 24 | } 25 | 26 | public evaluate(dynamicContext: DynamicContext, executionParameters: ExecutionParameters) { 27 | return evaluateLookup( 28 | dynamicContext.contextItem, 29 | this._keySpecifier, 30 | new EmptySequence(), 31 | dynamicContext, 32 | executionParameters, 33 | ); 34 | } 35 | } 36 | 37 | export default UnaryLookup; 38 | -------------------------------------------------------------------------------- /src/expressions/staticallyKnownNamespaces.ts: -------------------------------------------------------------------------------- 1 | export const enum BUILT_IN_NAMESPACE_URIS { 2 | XMLNS_NAMESPACE_URI = 'http://www.w3.org/2000/xmlns/', 3 | XML_NAMESPACE_URI = 'http://www.w3.org/XML/1998/namespace', 4 | XMLSCHEMA_NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema', 5 | ARRAY_NAMESPACE_URI = 'http://www.w3.org/2005/xpath-functions/array', 6 | FUNCTIONS_NAMESPACE_URI = 'http://www.w3.org/2005/xpath-functions', 7 | LOCAL_NAMESPACE_URI = 'http://www.w3.org/2005/xquery-local-functions', 8 | MAP_NAMESPACE_URI = 'http://www.w3.org/2005/xpath-functions/map', 9 | MATH_NAMESPACE_URI = 'http://www.w3.org/2005/xpath-functions/math', 10 | FONTOXPATH_NAMESPACE_URI = 'http://fontoxml.com/fontoxpath', 11 | XQUERYX_UPDATING_NAMESPACE_URI = 'http://www.w3.org/2007/xquery-update-10', 12 | XQUERYX_NAMESPACE_URI = 'http://www.w3.org/2005/XQueryX', 13 | } 14 | 15 | export const staticallyKnownNamespaceByPrefix: { 16 | [prefix: string]: BUILT_IN_NAMESPACE_URIS | string; 17 | } = { 18 | ['xml']: BUILT_IN_NAMESPACE_URIS.XML_NAMESPACE_URI, 19 | ['xs']: BUILT_IN_NAMESPACE_URIS.XMLSCHEMA_NAMESPACE_URI, 20 | ['fn']: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI, 21 | ['map']: BUILT_IN_NAMESPACE_URIS.MAP_NAMESPACE_URI, 22 | ['array']: BUILT_IN_NAMESPACE_URIS.ARRAY_NAMESPACE_URI, 23 | ['math']: BUILT_IN_NAMESPACE_URIS.MATH_NAMESPACE_URI, 24 | ['fontoxpath']: BUILT_IN_NAMESPACE_URIS.FONTOXPATH_NAMESPACE_URI, 25 | ['local']: BUILT_IN_NAMESPACE_URIS.LOCAL_NAMESPACE_URI, 26 | }; 27 | 28 | export function registerStaticallyKnownNamespace(prefix: string, namespaceURI: string) { 29 | if (staticallyKnownNamespaceByPrefix[prefix]) { 30 | throw new Error('Prefix already registered: Do not register the same prefix twice.'); 31 | } 32 | staticallyKnownNamespaceByPrefix[prefix] = namespaceURI; 33 | } 34 | -------------------------------------------------------------------------------- /src/expressions/tests/KindTest.ts: -------------------------------------------------------------------------------- 1 | import { NODE_TYPES } from '../../domFacade/ConcreteNode'; 2 | import isSubtypeOf from '../dataTypes/isSubtypeOf'; 3 | import Value, { ValueType } from '../dataTypes/Value'; 4 | import DynamicContext from '../DynamicContext'; 5 | import ExecutionParameters from '../ExecutionParameters'; 6 | import {} from '../Expression'; 7 | import Specificity from '../Specificity'; 8 | import { Bucket } from '../util/Bucket'; 9 | import TestAbstractExpression from './TestAbstractExpression'; 10 | 11 | class KindTest extends TestAbstractExpression { 12 | private _nodeType: NODE_TYPES; 13 | constructor(nodeType: NODE_TYPES) { 14 | super( 15 | new Specificity({ 16 | [Specificity.NODETYPE_KIND]: 1, 17 | }), 18 | ); 19 | 20 | this._nodeType = nodeType; 21 | } 22 | 23 | public evaluateToBoolean( 24 | _dynamicContext: DynamicContext, 25 | node: Value, 26 | executionParameters: ExecutionParameters, 27 | ) { 28 | if (!isSubtypeOf(node.type, ValueType.NODE)) { 29 | return false; 30 | } 31 | const nodeType = executionParameters.domFacade.getNodeType(node.value); 32 | if (this._nodeType === 3 && nodeType === 4) { 33 | // CDATA_SECTION_NODES should be regarded as text nodes, and CDATA does not exist in the XPath Data Model 34 | return true; 35 | } 36 | return this._nodeType === nodeType; 37 | } 38 | public override getBucket(): Bucket { 39 | return `type-${this._nodeType}`; 40 | } 41 | } 42 | export default KindTest; 43 | -------------------------------------------------------------------------------- /src/expressions/tests/PITest.ts: -------------------------------------------------------------------------------- 1 | import isSubtypeOf from '../dataTypes/isSubtypeOf'; 2 | import Value, { ValueType } from '../dataTypes/Value'; 3 | import DynamicContext from '../DynamicContext'; 4 | import ExecutionParameters from '../ExecutionParameters'; 5 | import {} from '../Expression'; 6 | import Specificity from '../Specificity'; 7 | import { Bucket } from '../util/Bucket'; 8 | import TestAbstractExpression from './TestAbstractExpression'; 9 | 10 | class PITest extends TestAbstractExpression { 11 | private _target: string; 12 | 13 | constructor(target: string) { 14 | super( 15 | new Specificity({ 16 | [Specificity.NODENAME_KIND]: 1, 17 | }), 18 | ); 19 | 20 | this._target = target; 21 | } 22 | 23 | public evaluateToBoolean( 24 | _dynamicContext: DynamicContext, 25 | node: Value, 26 | executionParameters: ExecutionParameters, 27 | ) { 28 | // Assume singleton 29 | const isMatchingProcessingInstruction = 30 | isSubtypeOf(node.type, ValueType.PROCESSINGINSTRUCTION) && 31 | executionParameters.domFacade.getTarget(node.value) === this._target; 32 | return isMatchingProcessingInstruction; 33 | } 34 | 35 | public override getBucket(): Bucket { 36 | return 'type-7'; 37 | } 38 | } 39 | 40 | export default PITest; 41 | -------------------------------------------------------------------------------- /src/expressions/tests/TestAbstractExpression.ts: -------------------------------------------------------------------------------- 1 | import AtomicValue from '../dataTypes/AtomicValue'; 2 | import sequenceFactory from '../dataTypes/sequenceFactory'; 3 | import DynamicContext from '../DynamicContext'; 4 | import ExecutionParameters from '../ExecutionParameters'; 5 | import Expression from '../Expression'; 6 | import Specificity from '../Specificity'; 7 | 8 | abstract class TestAbstractExpression extends Expression { 9 | constructor(specificity: Specificity) { 10 | super(specificity, [], { canBeStaticallyEvaluated: false }); 11 | } 12 | 13 | public evaluate(dynamicContext: DynamicContext, executionParameters: ExecutionParameters) { 14 | return this.evaluateToBoolean( 15 | dynamicContext, 16 | dynamicContext.contextItem, 17 | executionParameters, 18 | ) 19 | ? sequenceFactory.singletonTrueSequence() 20 | : sequenceFactory.singletonFalseSequence(); 21 | } 22 | 23 | public abstract evaluateToBoolean( 24 | dynamicContext: DynamicContext, 25 | item: AtomicValue, 26 | executionParameters: ExecutionParameters, 27 | ): boolean; 28 | } 29 | 30 | export default TestAbstractExpression; 31 | -------------------------------------------------------------------------------- /src/expressions/tests/TypeTest.ts: -------------------------------------------------------------------------------- 1 | import isSubtypeOf from '../dataTypes/isSubtypeOf'; 2 | import Value, { stringToValueType } from '../dataTypes/Value'; 3 | import DynamicContext from '../DynamicContext'; 4 | import ExecutionParameters from '../ExecutionParameters'; 5 | import Specificity from '../Specificity'; 6 | import TestAbstractExpression from './TestAbstractExpression'; 7 | 8 | class TypeTest extends TestAbstractExpression { 9 | public _type: { localName: string; namespaceURI: string; prefix: string }; 10 | 11 | constructor(type: { localName: string; namespaceURI: string | null; prefix: string }) { 12 | super(new Specificity({})); 13 | this._type = type; 14 | } 15 | 16 | public evaluateToBoolean( 17 | _dynamicContext: DynamicContext, 18 | item: Value, 19 | _executionParameters: ExecutionParameters, 20 | ) { 21 | return isSubtypeOf( 22 | item.type, 23 | stringToValueType( 24 | this._type.prefix 25 | ? this._type.prefix + ':' + this._type.localName 26 | : this._type.localName, 27 | ), 28 | ); 29 | } 30 | } 31 | export default TypeTest; 32 | -------------------------------------------------------------------------------- /src/expressions/util/Random.ts: -------------------------------------------------------------------------------- 1 | // Seed a prng with the chosen seed. This is a linear congruential generator. The constants are 2 | // taken from Steele, GL, Vigna, S. Computationally easy, spectrally good multipliers for 3 | // congruential pseudorandom number generators. Softw Pract Exper. 2022; 52( 2): 443– 4 | // 458. doi:10.1002/spe.3030 5 | export default class Random { 6 | private static readonly _a = 0x72ed; 7 | private static readonly _c = 0; 8 | private static readonly _m = 2 ** 32; 9 | 10 | private readonly _defaultSeed: number; 11 | 12 | constructor(seed: number = Math.floor(Math.random() * Random._m)) { 13 | this._defaultSeed = Math.abs(seed % Random._m); 14 | } 15 | 16 | public getRandomNumber(seed: number | null) { 17 | const current = (Random._a * (seed ?? this._defaultSeed) + Random._c) % Random._m; 18 | 19 | return { 20 | currentInt: Math.floor(current), 21 | currentDecimal: current / Random._m, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/expressions/util/atomizeSequence.ts: -------------------------------------------------------------------------------- 1 | import { atomizeSingleValue } from '../dataTypes/atomize'; 2 | import ISequence from '../dataTypes/ISequence'; 3 | import sequenceFactory from '../dataTypes/sequenceFactory'; 4 | import ExecutionParameters from '../ExecutionParameters'; 5 | import { DONE_TOKEN } from './iterators'; 6 | 7 | export default function atomizeSequence( 8 | sequence: ISequence, 9 | executionParameters: ExecutionParameters, 10 | ): ISequence { 11 | let currentSequence: ISequence; 12 | 13 | return sequenceFactory.create({ 14 | next: (iterationHint) => { 15 | while (true) { 16 | if (!currentSequence) { 17 | const value = sequence.value.next(iterationHint); 18 | if (value.done) { 19 | return DONE_TOKEN; 20 | } 21 | 22 | currentSequence = atomizeSingleValue(value.value, executionParameters); 23 | } 24 | const atomizedValue = currentSequence.value.next(iterationHint); 25 | if (atomizedValue.done) { 26 | currentSequence = null; 27 | continue; 28 | } 29 | 30 | return atomizedValue; 31 | } 32 | }, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/expressions/util/concatSequences.ts: -------------------------------------------------------------------------------- 1 | import ISequence from '../dataTypes/ISequence'; 2 | import sequenceFactory from '../dataTypes/sequenceFactory'; 3 | import Value from '../dataTypes/Value'; 4 | import { DONE_TOKEN, IIterator, IterationHint } from './iterators'; 5 | 6 | export default function concatSequences(sequences: ISequence[]): ISequence { 7 | let i = 0; 8 | let iterator: IIterator = null; 9 | let isFirst = true; 10 | return sequenceFactory.create({ 11 | next: (hint: IterationHint) => { 12 | while (i < sequences.length) { 13 | if (!iterator) { 14 | iterator = sequences[i].value; 15 | isFirst = true; 16 | } 17 | const value = iterator.next(isFirst ? IterationHint.NONE : hint); 18 | isFirst = false; 19 | if (value.done) { 20 | i++; 21 | iterator = null; 22 | continue; 23 | } 24 | return value; 25 | } 26 | return DONE_TOKEN; 27 | }, 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/expressions/util/createChildGenerator.ts: -------------------------------------------------------------------------------- 1 | import { ChildNodePointer, NodePointer, ParentNodePointer } from '../../domClone/Pointer'; 2 | import { NODE_TYPES } from '../../domFacade/ConcreteNode'; 3 | import DomFacade from '../../domFacade/DomFacade'; 4 | import { Bucket } from './Bucket'; 5 | import { DONE_TOKEN, IIterator, ready } from './iterators'; 6 | 7 | export default function createChildGenerator( 8 | domFacade: DomFacade, 9 | pointer: NodePointer, 10 | bucket: Bucket | null, 11 | ): IIterator { 12 | const nodeType = domFacade.getNodeType(pointer); 13 | if (nodeType !== NODE_TYPES.ELEMENT_NODE && nodeType !== NODE_TYPES.DOCUMENT_NODE) { 14 | return { 15 | next: () => { 16 | return DONE_TOKEN; 17 | }, 18 | }; 19 | } 20 | 21 | let childNode = domFacade.getFirstChildPointer(pointer as ParentNodePointer, bucket); 22 | return { 23 | next() { 24 | if (!childNode) { 25 | return DONE_TOKEN; 26 | } 27 | const current = childNode; 28 | childNode = domFacade.getNextSiblingPointer(childNode, bucket); 29 | return ready(current); 30 | }, 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/expressions/util/createDoublyIterableSequence.ts: -------------------------------------------------------------------------------- 1 | import ISequence from '../dataTypes/ISequence'; 2 | import sequenceFactory from '../dataTypes/sequenceFactory'; 3 | import Value from '../dataTypes/Value'; 4 | import { IterationHint, IterationResult } from './iterators'; 5 | 6 | export default function createDoublyIterableSequence(sequence: ISequence): () => ISequence { 7 | const savedValues: IterationResult[] = []; 8 | const backingIterator = sequence.value; 9 | return () => { 10 | let i = 0; 11 | return sequenceFactory.create({ 12 | next: (_hint: IterationHint) => { 13 | if (savedValues[i] !== undefined) { 14 | return savedValues[i++]; 15 | } 16 | const val = backingIterator.next(IterationHint.NONE); 17 | if (val.done) { 18 | return val; 19 | } 20 | savedValues[i++] = val; 21 | return val; 22 | }, 23 | }); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/expressions/util/createSingleValueIterator.ts: -------------------------------------------------------------------------------- 1 | import { DONE_TOKEN, IIterator, ready } from './iterators'; 2 | 3 | export default function createSingleValueIterator(onlyValue: T): IIterator { 4 | let hasPassed = false; 5 | return { 6 | next: () => { 7 | if (hasPassed) { 8 | return DONE_TOKEN; 9 | } 10 | hasPassed = true; 11 | return ready(onlyValue); 12 | }, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/expressions/util/iterators.ts: -------------------------------------------------------------------------------- 1 | export class IterationResult { 2 | public done: boolean; 3 | public value: T | undefined | null; 4 | constructor(done: boolean, value: T | undefined) { 5 | this.done = done; 6 | this.value = value; 7 | } 8 | } 9 | 10 | export enum IterationHint { 11 | NONE = 0, 12 | SKIP_DESCENDANTS = 1 << 0, 13 | } 14 | 15 | export const DONE_TOKEN = new IterationResult(true, undefined); 16 | export function ready(value: T) { 17 | return new IterationResult(false, value); 18 | } 19 | 20 | export interface IIterator { 21 | next(hint: IterationHint): IterationResult; 22 | } 23 | -------------------------------------------------------------------------------- /src/expressions/util/sequenceEvery.ts: -------------------------------------------------------------------------------- 1 | import { falseBoolean, trueBoolean } from '../dataTypes/createAtomicValue'; 2 | import ISequence from '../dataTypes/ISequence'; 3 | import sequenceFactory from '../dataTypes/sequenceFactory'; 4 | import Value from '../dataTypes/Value'; 5 | import { DONE_TOKEN, IterationHint, ready } from './iterators'; 6 | 7 | export default function sequenceEvery( 8 | sequence: ISequence, 9 | typeTest: (value: Value) => ISequence, 10 | ): ISequence { 11 | const iterator = sequence.value; 12 | let typeTestResultIterator: ISequence | null = null; 13 | let done = false; 14 | return sequenceFactory.create({ 15 | next: (_hint: IterationHint) => { 16 | while (!done) { 17 | if (!typeTestResultIterator) { 18 | const value = iterator.next(IterationHint.NONE); 19 | if (value.done) { 20 | done = true; 21 | return ready(trueBoolean); 22 | } 23 | typeTestResultIterator = typeTest(value.value); 24 | } 25 | const ebv = typeTestResultIterator.getEffectiveBooleanValue(); 26 | typeTestResultIterator = null; 27 | if (ebv === false) { 28 | done = true; 29 | return ready(falseBoolean); 30 | } 31 | } 32 | return DONE_TOKEN; 33 | }, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/expressions/util/zipSingleton.ts: -------------------------------------------------------------------------------- 1 | import ISequence from '../dataTypes/ISequence'; 2 | import Value from '../dataTypes/Value'; 3 | 4 | type CallbackType = (values: Value[]) => ISequence; 5 | 6 | /** 7 | * Take a bunch of sequences, take their first values and call the callback with those values 8 | */ 9 | export default function zipSingleton(sequences: ISequence[], callback: CallbackType): ISequence { 10 | const firstValues = sequences.map((seq) => seq.first()); 11 | return callback(firstValues); 12 | } 13 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/IPendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { NodePointer } from '../../domClone/Pointer'; 2 | import ExecutionParameters from '../ExecutionParameters'; 3 | import { TransferablePendingUpdate } from './createPendingUpdateFromTransferable'; 4 | 5 | export type PendingUpdateType = 6 | | 'delete' 7 | | 'insertAfter' 8 | | 'insertAttributes' 9 | | 'insertBefore' 10 | | 'insertInto' 11 | | 'insertIntoAsFirst' 12 | | 'insertIntoAsLast' 13 | | 'put' 14 | | 'rename' 15 | | 'replaceElementContent' 16 | | 'replaceNode' 17 | | 'replaceValue'; 18 | 19 | export abstract class IPendingUpdate { 20 | public readonly target: NodePointer; 21 | public readonly type: PendingUpdateType; 22 | constructor(public pendingUpdateType: PendingUpdateType) { 23 | this.type = pendingUpdateType; 24 | } 25 | 26 | public abstract toTransferable( 27 | executionParameters: ExecutionParameters, 28 | ): TransferablePendingUpdate; 29 | } 30 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/UpdatingFunctionDefinitionType.ts: -------------------------------------------------------------------------------- 1 | import FunctionDefinitionType from '../functions/FunctionDefinitionType'; 2 | import UpdatingExpressionResult from '../UpdatingExpressionResult'; 3 | import { IIterator } from '../util/iterators'; 4 | 5 | type UpdatingFunctionDefinitionType = FunctionDefinitionType>; 6 | export default UpdatingFunctionDefinitionType; 7 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/UpdatingFunctionValue.ts: -------------------------------------------------------------------------------- 1 | import FunctionValue, { FunctionSignature } from '../dataTypes/FunctionValue'; 2 | import { ParameterType, SequenceType } from '../dataTypes/Value'; 3 | import UpdatingExpressionResult from '../UpdatingExpressionResult'; 4 | import { IIterator } from '../util/iterators'; 5 | 6 | export default class UpdatingFunctionValue extends FunctionValue< 7 | IIterator 8 | > { 9 | constructor(definition: { 10 | argumentTypes: ParameterType[]; 11 | arity: number; 12 | isAnonymous?: boolean; 13 | isUpdating: boolean; 14 | localName: string; 15 | namespaceURI: string; 16 | returnType: SequenceType; 17 | value: FunctionSignature>; 18 | }) { 19 | super(definition); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/DeletePendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { AttributeNodePointer, ChildNodePointer } from '../../../domClone/Pointer'; 2 | import realizeDom from '../../../domClone/realizeDom'; 3 | import ExecutionParameters from '../../../expressions/ExecutionParameters'; 4 | import { TransferablePendingUpdate } from '../createPendingUpdateFromTransferable'; 5 | import { IPendingUpdate } from '../IPendingUpdate'; 6 | export class DeletePendingUpdate extends IPendingUpdate { 7 | public readonly type: 'delete'; 8 | constructor(readonly target: AttributeNodePointer | ChildNodePointer) { 9 | super('delete'); 10 | } 11 | public toTransferable(executionParameters: ExecutionParameters): TransferablePendingUpdate { 12 | return { 13 | ['type']: this.type, 14 | ['target']: realizeDom(this.target, executionParameters, false), 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/InsertAfterPendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { ChildNodePointer } from '../../../domClone/Pointer'; 2 | import { InsertPendingUpdate } from './InsertPendingUpdate'; 3 | export class InsertAfterPendingUpdate extends InsertPendingUpdate { 4 | public readonly target: ChildNodePointer; 5 | public readonly type: 'insertAfter'; 6 | constructor(target: ChildNodePointer, content: ChildNodePointer[]) { 7 | super(target, content, 'insertAfter'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/InsertAttributesPendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { AttributeNodePointer, ElementNodePointer } from '../../../domClone/Pointer'; 2 | import realizeDom from '../../../domClone/realizeDom'; 3 | import ExecutionParameters from '../../../expressions/ExecutionParameters'; 4 | import { IPendingUpdate } from '../IPendingUpdate'; 5 | 6 | export class InsertAttributesPendingUpdate extends IPendingUpdate { 7 | public readonly type: 'insertAttributes'; 8 | constructor( 9 | readonly target: ElementNodePointer, 10 | readonly content: AttributeNodePointer[], 11 | ) { 12 | super('insertAttributes'); 13 | } 14 | public toTransferable(executionParameters: ExecutionParameters) { 15 | return { 16 | ['type']: this.type, 17 | ['target']: realizeDom(this.target, executionParameters, false), 18 | content: this.content.map((pointer) => realizeDom(pointer, executionParameters, true)), 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/InsertBeforePendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { ChildNodePointer, ParentNodePointer } from '../../../domClone/Pointer'; 2 | import { InsertPendingUpdate } from './InsertPendingUpdate'; 3 | export class InsertBeforePendingUpdate extends InsertPendingUpdate { 4 | public readonly target: ChildNodePointer; 5 | public readonly type: 'insertBefore'; 6 | constructor(target: ParentNodePointer, content: ChildNodePointer[]) { 7 | super(target, content, 'insertBefore'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/InsertIntoAsFirstPendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChildNodePointer, 3 | DocumentNodePointer, 4 | ElementNodePointer, 5 | } from '../../../domClone/Pointer'; 6 | import { InsertPendingUpdate } from './InsertPendingUpdate'; 7 | export class InsertIntoAsFirstPendingUpdate extends InsertPendingUpdate { 8 | public readonly target: ElementNodePointer | DocumentNodePointer; 9 | public readonly type: 'insertIntoAsFirst'; 10 | constructor(target: ElementNodePointer | DocumentNodePointer, content: ChildNodePointer[]) { 11 | super(target, content, 'insertIntoAsFirst'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/InsertIntoAsLastPendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChildNodePointer, 3 | DocumentNodePointer, 4 | ElementNodePointer, 5 | } from '../../../domClone/Pointer'; 6 | import { InsertPendingUpdate } from './InsertPendingUpdate'; 7 | export class InsertIntoAsLastPendingUpdate extends InsertPendingUpdate { 8 | public readonly target: ElementNodePointer | DocumentNodePointer; 9 | public readonly type: 'insertIntoAsLast'; 10 | constructor(target: ElementNodePointer | DocumentNodePointer, content: ChildNodePointer[]) { 11 | super(target, content, 'insertIntoAsLast'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/InsertIntoPendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AttributeNodePointer, 3 | ChildNodePointer, 4 | DocumentNodePointer, 5 | ElementNodePointer, 6 | } from '../../../domClone/Pointer'; 7 | import { InsertPendingUpdate } from './InsertPendingUpdate'; 8 | export class InsertIntoPendingUpdate extends InsertPendingUpdate { 9 | public readonly target: ElementNodePointer | DocumentNodePointer; 10 | public readonly type: 'insertInto'; 11 | constructor(target: ElementNodePointer, content: (AttributeNodePointer | ChildNodePointer)[]) { 12 | super(target, content, 'insertInto'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/InsertPendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { AttributeNodePointer, ChildNodePointer, NodePointer } from '../../../domClone/Pointer'; 2 | import realizeDom from '../../../domClone/realizeDom'; 3 | import ExecutionParameters from '../../../expressions/ExecutionParameters'; 4 | import { IPendingUpdate, PendingUpdateType } from '../IPendingUpdate'; 5 | export class InsertPendingUpdate extends IPendingUpdate { 6 | constructor( 7 | readonly target: NodePointer, 8 | readonly content: (ChildNodePointer | AttributeNodePointer)[], 9 | type: PendingUpdateType, 10 | ) { 11 | super(type); 12 | } 13 | public toTransferable(executionParameters: ExecutionParameters) { 14 | return { 15 | ['type']: this.type, 16 | ['target']: realizeDom(this.target, executionParameters, false), 17 | ['content']: this.content.map((pointer) => 18 | realizeDom(pointer, executionParameters, true), 19 | ), 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/RenamePendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { ElementNodePointer } from '../../../domClone/Pointer'; 2 | import realizeDom from '../../../domClone/realizeDom'; 3 | import ExecutionParameters from '../../../expressions/ExecutionParameters'; 4 | import QName from '../../dataTypes/valueTypes/QName'; 5 | import { IPendingUpdate } from '../IPendingUpdate'; 6 | export class RenamePendingUpdate extends IPendingUpdate { 7 | public newName: QName; 8 | public readonly type: 'rename'; 9 | constructor( 10 | readonly target: ElementNodePointer, 11 | newName: QName, 12 | ) { 13 | super('rename'); 14 | this.newName = newName.buildPrefixedName 15 | ? newName 16 | : new QName(newName.prefix, newName.namespaceURI, newName.localName); 17 | } 18 | public toTransferable(executionParameters: ExecutionParameters) { 19 | return { 20 | ['type']: this.type, 21 | ['target']: realizeDom(this.target, executionParameters, false), 22 | ['newName']: { 23 | ['prefix']: this.newName.prefix, 24 | ['namespaceURI']: this.newName.namespaceURI, 25 | ['localName']: this.newName.localName, 26 | }, 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/ReplaceElementContentPendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { ElementNodePointer, TextNodePointer } from '../../../domClone/Pointer'; 2 | import realizeDom from '../../../domClone/realizeDom'; 3 | import ExecutionParameters from '../../../expressions/ExecutionParameters'; 4 | import { IPendingUpdate } from '../IPendingUpdate'; 5 | 6 | export class ReplaceElementContentPendingUpdate extends IPendingUpdate { 7 | public readonly type: 'replaceElementContent'; 8 | constructor( 9 | readonly target: ElementNodePointer, 10 | readonly text: TextNodePointer | null, 11 | ) { 12 | super('replaceElementContent'); 13 | } 14 | public toTransferable(executionParameters: ExecutionParameters) { 15 | return { 16 | ['type']: this.type, 17 | ['target']: realizeDom(this.target, executionParameters, false), 18 | ['text']: this.text ? realizeDom(this.text, executionParameters, true) : null, 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/ReplaceNodePendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { AttributeNodePointer, ChildNodePointer } from '../../../domClone/Pointer'; 2 | import realizeDom from '../../../domClone/realizeDom'; 3 | import ExecutionParameters from '../../../expressions/ExecutionParameters'; 4 | import { IPendingUpdate } from '../IPendingUpdate'; 5 | export class ReplaceNodePendingUpdate extends IPendingUpdate { 6 | constructor( 7 | readonly target: AttributeNodePointer | ChildNodePointer, 8 | readonly replacement: (AttributeNodePointer | ChildNodePointer)[], 9 | ) { 10 | super('replaceNode'); 11 | } 12 | public toTransferable(executionParameters: ExecutionParameters) { 13 | return { 14 | ['type']: this.type, 15 | ['target']: realizeDom(this.target, executionParameters, false), 16 | ['replacement']: this.replacement.map((pointer) => 17 | realizeDom(pointer, executionParameters, true), 18 | ), 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/expressions/xquery-update/pendingUpdates/ReplaceValuePendingUpdate.ts: -------------------------------------------------------------------------------- 1 | import { AttributeNodePointer, ElementNodePointer } from '../../../domClone/Pointer'; 2 | import realizeDom from '../../../domClone/realizeDom'; 3 | import ExecutionParameters from '../../../expressions/ExecutionParameters'; 4 | import { IPendingUpdate } from '../IPendingUpdate'; 5 | export class ReplaceValuePendingUpdate extends IPendingUpdate { 6 | public readonly type: 'replaceValue'; 7 | constructor( 8 | readonly target: ElementNodePointer | AttributeNodePointer, 9 | readonly stringValue: string, 10 | ) { 11 | super('replaceValue'); 12 | } 13 | public toTransferable(executionParameters: ExecutionParameters) { 14 | return { 15 | ['type']: this.type, 16 | ['target']: realizeDom(this.target, executionParameters, false), 17 | ['string-value']: this.stringValue, 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/jsCodegen/emitLiterals.ts: -------------------------------------------------------------------------------- 1 | import { Bucket } from '../expressions/util/Bucket'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | import escapeJavaScriptString from './escapeJavaScriptString'; 4 | import { 5 | acceptAst, 6 | GeneratedCodeBaseType, 7 | PartialCompilationResult, 8 | } from './JavaScriptCompiledXPath'; 9 | 10 | /** 11 | * Create a JavaScript function that returns the string literal. 12 | * 13 | * https://www.w3.org/TR/xpath-31/#doc-xpath31-StringLiteral 14 | * 15 | * @param ast The string literal AST node 16 | * @param identifier The function wrapper identifier 17 | * @returns Wrapped string literal function 18 | */ 19 | export function emitStringConstantExpr(ast: IAST): [PartialCompilationResult, Bucket] { 20 | // Note: default the value to the emptyy string. The XQueryX roundtrip may omit them 21 | let text = (astHelper.getFirstChild(ast, 'value')[1] as string) || ''; 22 | text = escapeJavaScriptString(text); 23 | return [acceptAst(text, { type: GeneratedCodeBaseType.Value }, []), null]; 24 | } 25 | -------------------------------------------------------------------------------- /src/jsCodegen/emitOperand.ts: -------------------------------------------------------------------------------- 1 | import { Bucket } from '../expressions/util/Bucket'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | import { CodeGenContext } from './CodeGenContext'; 4 | import { PartialCompilationResult, rejectAst } from './JavaScriptCompiledXPath'; 5 | 6 | /** 7 | * Retrieves the first or second operand for an operator AST node and wraps 8 | * it in a function. 9 | * 10 | * @param ast Base AST node for which get either the first or second operand. 11 | * @param identifier Function identifier for the emitted code. 12 | * @param operandKind Indicates if it's the first or second operand for an operator. 13 | * @param staticContext Static context parameter to retrieve context-dependent information. 14 | * @returns JavaScript code of the operand. 15 | */ 16 | export function emitOperand( 17 | ast: IAST, 18 | operandKind: 'firstOperand' | 'secondOperand', 19 | contextItemExpr: PartialCompilationResult, 20 | context: CodeGenContext, 21 | ): [PartialCompilationResult, Bucket] { 22 | const exprAst = astHelper.followPath(ast, [operandKind, '*']); 23 | if (!exprAst) { 24 | return [rejectAst(`${operandKind} expression not found`), null]; 25 | } 26 | 27 | return context.emitBaseExpr(exprAst, contextItemExpr, context); 28 | } 29 | -------------------------------------------------------------------------------- /src/jsCodegen/escapeJavaScriptString.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sanitize an untrusted string used in generated JavaScript code as a string 3 | * literal. 4 | * 5 | * Always use this function to escape user-provided strings before including 6 | * them in JavaScript code that will be evaluated. Not doing so can open up 7 | * security vulnerabilities, such as cross-site scripting. JSON.stringify 8 | * ensures the untrusted string is a valid JavaScript string, according to [the 9 | * ECMAScript specification]{@link https://tc39.es/ecma262/#sec-json.stringify}. 10 | * 11 | * String.prototype.replace handles support for characters older JavaScript 12 | * engines [don't support]{@link 13 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#issue_with_plain_json.stringify_for_use_as_javascript}, 14 | * which ensures this function returns a valid string. 15 | * 16 | * @param untrustedString - User provided string. 17 | * 18 | * @returns An escaped and valid JavaScript string. 19 | */ 20 | function escapeJavaScriptString(untrustedString: string): string { 21 | return JSON.stringify(untrustedString) 22 | .replace(/\u2028/g, '\\u2028') // LINE SEPARATOR 23 | .replace(/\u2029/g, '\\u2029'); // PARAGRAPH SEPARATOR 24 | } 25 | 26 | export default escapeJavaScriptString; 27 | -------------------------------------------------------------------------------- /src/nodesFactory/INodesFactory.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '../types/Types'; 2 | import ISimpleNodesFactory from './ISimpleNodesFactory'; 3 | 4 | /** 5 | * Defines the factory methods used in XQuery. Basically equivalent to the Document interface, but 6 | * with the 'createDocument' factory method added. 7 | * 8 | * @public 9 | */ 10 | export default interface INodesFactory extends ISimpleNodesFactory { 11 | createDocument(): Document; 12 | } 13 | -------------------------------------------------------------------------------- /src/nodesFactory/ISimpleNodesFactory.ts: -------------------------------------------------------------------------------- 1 | import { Attr, CDATASection, Comment, Element, ProcessingInstruction, Text } from '../types/Types'; 2 | 3 | /** 4 | * Subset of the constructor methods present on Document. Can be used to create textnodes, elements, 5 | * attributes, CDataSecions, comments and processing instructions. 6 | * 7 | * @public 8 | */ 9 | export default interface ISimpleNodesFactory { 10 | createAttributeNS(namespaceURI: string, name: string): Attr; 11 | 12 | createCDATASection(contents: string): CDATASection; 13 | 14 | createComment(contents: string): Comment; 15 | 16 | createElementNS(namespaceURI: string, name: string): Element; 17 | 18 | createProcessingInstruction(target: string, data: string): ProcessingInstruction; 19 | 20 | createTextNode(contents: string): Text; 21 | } 22 | -------------------------------------------------------------------------------- /src/nodesFactory/wrapExternalNodesFactory.ts: -------------------------------------------------------------------------------- 1 | import INodesFactory from './INodesFactory'; 2 | 3 | class WrappingNodesFactory implements INodesFactory { 4 | private _externalNodesFactory: INodesFactory; 5 | 6 | constructor(externalNodesFactory: INodesFactory) { 7 | this._externalNodesFactory = externalNodesFactory; 8 | } 9 | 10 | public createAttributeNS(namespaceURI: string, name: string) { 11 | return this._externalNodesFactory['createAttributeNS'](namespaceURI, name); 12 | } 13 | 14 | public createCDATASection(contents: string) { 15 | return this._externalNodesFactory['createCDATASection'](contents); 16 | } 17 | 18 | public createComment(contents: string) { 19 | return this._externalNodesFactory['createComment'](contents); 20 | } 21 | 22 | public createDocument() { 23 | return this._externalNodesFactory['createDocument'](); 24 | } 25 | 26 | public createElementNS(namespaceURI: string, name: string) { 27 | return this._externalNodesFactory['createElementNS'](namespaceURI, name); 28 | } 29 | 30 | public createProcessingInstruction(target: string, data: string) { 31 | return this._externalNodesFactory['createProcessingInstruction'](target, data); 32 | } 33 | 34 | public createTextNode(contents: string) { 35 | return this._externalNodesFactory['createTextNode'](contents); 36 | } 37 | } 38 | 39 | export default function wrapExternalNodesFactory(externalNodesFactory: INodesFactory) { 40 | return new WrappingNodesFactory(externalNodesFactory); 41 | } 42 | -------------------------------------------------------------------------------- /src/parsing/evaluableExpressionToString.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteNode, NODE_TYPES } from '../domFacade/ConcreteNode'; 2 | import ExternalDomFacade from '../domFacade/ExternalDomFacade'; 3 | import { EvaluableExpression } from '../evaluateXPath'; 4 | 5 | export default function evaluableExpressionToString( 6 | evaluableExpression: EvaluableExpression, 7 | ): string { 8 | if (typeof evaluableExpression === 'string') { 9 | return evaluableExpression; 10 | } else { 11 | const domFacade = new ExternalDomFacade(); 12 | 13 | const childNodes = domFacade.getChildNodes(evaluableExpression); 14 | const commentNode: any = childNodes.find( 15 | (node: ConcreteNode) => node.nodeType === NODE_TYPES.COMMENT_NODE, 16 | ); 17 | return commentNode ? commentNode.data : 'some expression'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/parsing/normalizeEndOfLines.ts: -------------------------------------------------------------------------------- 1 | export default function normalizeEndOfLines(xpathString: string) { 2 | // Replace all character sequences of 0xD followed by 0xA and all 0xD not followed by 0xA with 0xA. 3 | return xpathString.replace(/(\x0D\x0A)|(\x0D(?!\x0A))/g, String.fromCharCode(0xa)); 4 | } 5 | -------------------------------------------------------------------------------- /src/parsing/parseExpression.ts: -------------------------------------------------------------------------------- 1 | import { Location } from '../expressions/debug/StackTraceGenerator'; 2 | import { StackTraceEntry } from '../expressions/debug/StackTraceEntry'; 3 | import { IAST } from './astHelper'; 4 | import { parseUsingPrsc } from './prscParser'; 5 | 6 | /** 7 | * Parse an XPath string to a selector. 8 | * 9 | * @param xPathString The string to parse 10 | * @param compilationOptions Whether the compiler should parse XQuery 11 | */ 12 | export default function parseExpression( 13 | xPathString: string, 14 | compilationOptions: { allowXQuery?: boolean; debug?: boolean }, 15 | ): IAST { 16 | const options = { 17 | xquery: !!compilationOptions.allowXQuery, 18 | outputDebugInfo: !!compilationOptions.debug, 19 | }; 20 | 21 | const parseResult = parseUsingPrsc(xPathString, options); 22 | if (parseResult.success === true) { 23 | return parseResult.value; 24 | } 25 | const lines = xPathString.substring(0, parseResult.offset).split('\n'); 26 | const line = lines[lines.length - 1]; 27 | // The offset is the last known position where the parser was OK, so the error is one over 28 | const column = line.length + 1; 29 | 30 | const positionS: Location = { offset: parseResult.offset, line: lines.length, column }; 31 | const positionE: Location = { 32 | offset: parseResult.offset + 1, 33 | line: lines.length, 34 | column: column + 1, 35 | }; 36 | throw new StackTraceEntry( 37 | { start: positionS, end: positionE }, 38 | '', 39 | '', 40 | new Error( 41 | `XPST0003: Failed to parse script. Expected ${[...new Set(parseResult.expected)]}`, 42 | ), 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/parsing/typesParser.ts: -------------------------------------------------------------------------------- 1 | import { map, optional, Parser, then } from 'prsc'; 2 | import { ASTAttributes, IAST } from './astHelper'; 3 | import { QNameAST } from './literalParser'; 4 | import { eqName } from './nameParser'; 5 | import { QUESTION_MARK } from './tokens'; 6 | 7 | export const typeName: Parser = eqName; 8 | 9 | export const simpleTypeName: Parser = typeName; 10 | 11 | export const singleType: Parser = then( 12 | simpleTypeName, 13 | optional(QUESTION_MARK), 14 | (type, opt) => 15 | opt !== null 16 | ? ['singleType', ['atomicType', ...type], ['optional']] 17 | : ['singleType', ['atomicType', ...type]], 18 | ); 19 | 20 | export const atomicOrUnionType: Parser = map(eqName, (x) => ['atomicType', ...x]); 21 | -------------------------------------------------------------------------------- /src/parsing/whitespaceParser.ts: -------------------------------------------------------------------------------- 1 | import { delimited, map, not, or, Parser, ParseResult, peek, plus, preceded, star } from 'prsc'; 2 | import { cached, regex } from './parsingFunctions'; 3 | import * as tokens from './tokens'; 4 | 5 | export const whitespaceCache = new Map>(); 6 | export const whitespacePlusCache = new Map>(); 7 | 8 | export const char: Parser = or([ 9 | regex(/[\t\n\r -\uD7FF\uE000\uFFFD]/), 10 | regex(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), 11 | ]); 12 | 13 | export const commentContents: Parser = preceded( 14 | peek( 15 | not(or([tokens.COMMENT_START, tokens.COMMENT_END]), [ 16 | 'comment contents cannot contain "(:" or ":)"', 17 | ]), 18 | ), 19 | char, 20 | ); 21 | 22 | function commentIndirect(input: string, offset: number) { 23 | return comment(input, offset); 24 | } 25 | 26 | export const comment: Parser = map( 27 | delimited( 28 | tokens.COMMENT_START, 29 | star(or([commentContents, commentIndirect])), 30 | tokens.COMMENT_END, 31 | true, 32 | ), 33 | (x) => x.join(''), 34 | ); 35 | 36 | export const whitespaceCharacter: Parser = or([tokens.WHITESPACE, comment]); 37 | 38 | export const explicitWhitespace: Parser = map(plus(tokens.WHITESPACE), (x) => x.join('')); 39 | 40 | export const whitespace: Parser = cached( 41 | map(star(whitespaceCharacter), (x) => x.join('')), 42 | whitespaceCache, 43 | ); 44 | 45 | export const whitespacePlus: Parser = cached( 46 | map(plus(whitespaceCharacter), (x) => x.join('')), 47 | whitespacePlusCache, 48 | ); 49 | -------------------------------------------------------------------------------- /src/precompileXPath.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Precompile an XPath selector asynchronously. 3 | * 4 | * @deprecated This code is deprecated. This is a no-op! 5 | * 6 | * @public 7 | * 8 | * @param xPathString - The xPath which should be pre-compiled 9 | * 10 | * @returns A promise which is resolved with the xpath string after compilation. 11 | */ 12 | export default function precompileXPath(xPathString: string): Promise { 13 | return Promise.resolve(xPathString); 14 | } 15 | -------------------------------------------------------------------------------- /src/typeInference/annotateArrayConstructor.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * Inserting the array type of multiplicity exactly one to the ast; 6 | * as the return type of the array constructor is array. 7 | * 8 | * @param ast the AST to be annotated. 9 | * @returns the inferred SequenceType 10 | */ 11 | export function annotateArrayConstructor(ast: IAST): SequenceType { 12 | const seqType = { 13 | type: ValueType.ARRAY, 14 | mult: SequenceMultiplicity.EXACTLY_ONE, 15 | }; 16 | 17 | astHelper.insertAttribute(ast, 'type', seqType); 18 | 19 | return seqType; 20 | } 21 | -------------------------------------------------------------------------------- /src/typeInference/annotateCastOperators.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SequenceMultiplicity, 3 | SequenceType, 4 | stringToValueType, 5 | ValueType, 6 | } from '../expressions/dataTypes/Value'; 7 | import astHelper, { IAST } from '../parsing/astHelper'; 8 | 9 | /** 10 | * Read the target type of the cast operator from the AST and 11 | * inserts it to as new attribute `type` to the AST. 12 | * 13 | * @param ast the AST to be annotated 14 | * @returns the inferred type 15 | */ 16 | export function annotateCastOperator(ast: IAST): SequenceType { 17 | const targetTypeString = getTargetTypeFromAST(ast); 18 | const targetValueType = stringToValueType(targetTypeString); 19 | const sequenceType = { type: targetValueType, mult: SequenceMultiplicity.EXACTLY_ONE }; 20 | 21 | if (sequenceType.type !== ValueType.ITEM) { 22 | astHelper.insertAttribute(ast, 'type', sequenceType); 23 | } 24 | return sequenceType; 25 | } 26 | 27 | /** 28 | * Inserts a boolean type to the AST, as castable operator returns boolean type. 29 | * 30 | * @param ast the AST to be annotated 31 | * @returns `SequenceType` of type boolean and multiplicity of `Exactly_ONE` 32 | */ 33 | export function annotateCastableOperator(ast: IAST): SequenceType { 34 | const sequenceType = { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE }; 35 | 36 | astHelper.insertAttribute(ast, 'type', sequenceType); 37 | return sequenceType; 38 | } 39 | 40 | /** 41 | * Helper function that reads the target type of the cast operator from AST. 42 | */ 43 | function getTargetTypeFromAST(ast: IAST): string { 44 | const typeInfoNode = astHelper.followPath(ast, ['singleType', 'atomicType']); 45 | const prefix = astHelper.getAttribute(typeInfoNode, 'prefix'); 46 | const targetType = typeInfoNode[2]; 47 | 48 | return prefix + ':' + targetType; 49 | } 50 | -------------------------------------------------------------------------------- /src/typeInference/annotateContextItemExpr.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * A context item expression evaluates to the context item. Hence the type that is returned is the one from the current context. 6 | * 7 | * @param ast the AST to be annotated. 8 | * @returns the type of the context item. 9 | */ 10 | export function annotateContextItemExpr(ast: IAST): SequenceType { 11 | // TODO: What type should be returned here? 12 | return { 13 | type: ValueType.ITEM, 14 | mult: SequenceMultiplicity.ZERO_OR_MORE, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/typeInference/annotateDynamicFunctionInvocationExpr.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * At this moment there is no way to infer the return type of this function as 6 | * this would require the query to be executed. Therefore, it would return item()*. 7 | * 8 | * @param ast The ast we need to check the type of. 9 | * @returns A SequenceType of item()*. 10 | */ 11 | export function annotateDynamicFunctionInvocationExpr(ast: IAST): SequenceType { 12 | return { 13 | type: ValueType.ITEM, 14 | mult: SequenceMultiplicity.ZERO_OR_MORE, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/typeInference/annotateIfThenElseExpr.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * Checks the type and multiplicity of else clause and then clause 6 | * and inserts the type to the ast as an attribute if they match. 7 | * 8 | * @param ast the AST to be annotated. 9 | * @param elseClause the elseClause, uses type data of this param to ast. 10 | * @param thenClause the thenClause. 11 | * @returns the type of the context item. 12 | */ 13 | export function annotateIfThenElseExpr( 14 | ast: IAST, 15 | elseClause: SequenceType, 16 | thenClause: SequenceType, 17 | ): SequenceType { 18 | if (!elseClause || !thenClause) { 19 | return { 20 | type: ValueType.ITEM, 21 | mult: SequenceMultiplicity.ZERO_OR_MORE, 22 | }; 23 | } 24 | if (elseClause.type === thenClause.type && elseClause.mult === thenClause.mult) { 25 | if (elseClause.type !== ValueType.ITEM) { 26 | astHelper.insertAttribute(ast, 'type', elseClause); 27 | } 28 | return elseClause; 29 | } 30 | return { 31 | type: ValueType.ITEM, 32 | mult: SequenceMultiplicity.ZERO_OR_MORE, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/typeInference/annotateInstanceOfExpr.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * Insert type boolean multiplicity exactly one the type to the ast 6 | * as an attribute, since instance of expression evaluates to a boolean. 7 | * 8 | * @param ast the AST to be annotated. 9 | * @returns the type of the context item. 10 | */ 11 | export function annotateInstanceOfExpr(ast: IAST): SequenceType { 12 | const seqType = { 13 | type: ValueType.XSBOOLEAN, 14 | mult: SequenceMultiplicity.EXACTLY_ONE, 15 | }; 16 | 17 | astHelper.insertAttribute(ast, 'type', seqType); 18 | 19 | return seqType; 20 | } 21 | -------------------------------------------------------------------------------- /src/typeInference/annotateLogicalOperator.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * Switch cases that take care of the operators under the logical operator category. 6 | * 7 | * @param ast The AST to annotate. 8 | */ 9 | export function annotateLogicalOperator(ast: IAST): SequenceType { 10 | switch (ast[0]) { 11 | case 'orOp': 12 | return annotateOrOperator(ast); 13 | case 'andOp': 14 | return annotateAndOperator(ast); 15 | } 16 | } 17 | 18 | /** 19 | * Inserts a boolean type to the AST, as or operator returns boolean type. 20 | * 21 | * @param ast the AST to be annotated. 22 | * @returns `SequenceType` of type boolean and multiplicity of `Exactly_ONE`. 23 | */ 24 | function annotateOrOperator(ast: IAST): SequenceType { 25 | const seqType = { 26 | type: ValueType.XSBOOLEAN, 27 | mult: SequenceMultiplicity.EXACTLY_ONE, 28 | }; 29 | 30 | astHelper.insertAttribute(ast, 'type', seqType); 31 | 32 | return seqType; 33 | } 34 | 35 | /** 36 | * Inserts a boolean type to the AST, as and operator returns boolean type. 37 | * 38 | * @param ast the AST to be annotated. 39 | * @returns `SequenceType` of type boolean and multiplicity of `Exactly_ONE`. 40 | */ 41 | function annotateAndOperator(ast: IAST): SequenceType { 42 | const seqType = { 43 | type: ValueType.XSBOOLEAN, 44 | mult: SequenceMultiplicity.EXACTLY_ONE, 45 | }; 46 | 47 | astHelper.insertAttribute(ast, 'type', seqType); 48 | 49 | return seqType; 50 | } 51 | -------------------------------------------------------------------------------- /src/typeInference/annotateMapConstructor.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | import { AnnotationContext } from './AnnotationContext'; 4 | 5 | /** 6 | * Inserting the map type of multiplicity exactly one to the ast; 7 | * as the return type of the map constructor is map. 8 | * 9 | * @param ast the AST to be annotated. 10 | * @returns the inferred SequenceType 11 | */ 12 | export function annotateMapConstructor(ast: IAST): SequenceType { 13 | const seqType = { 14 | type: ValueType.MAP, 15 | mult: SequenceMultiplicity.EXACTLY_ONE, 16 | }; 17 | 18 | astHelper.insertAttribute(ast, 'type', seqType); 19 | return seqType; 20 | } 21 | -------------------------------------------------------------------------------- /src/typeInference/annotateQuantifiedExpr.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * Annotate the ast by inserting boolean sequence type of exactly one multiplicity. 6 | * Quantified Expression evaluates if a statement satisfies the quantifier; 7 | * therefore its value is a boolean. 8 | * 9 | * @param ast the ast node to be annotated. 10 | * @returns the annotated sequence type. 11 | */ 12 | export function annotateQuantifiedExpr(ast: IAST): SequenceType { 13 | const seqType = { 14 | type: ValueType.XSBOOLEAN, 15 | mult: SequenceMultiplicity.EXACTLY_ONE, 16 | }; 17 | 18 | astHelper.insertAttribute(ast, 'type', seqType); 19 | 20 | return seqType; 21 | } 22 | -------------------------------------------------------------------------------- /src/typeInference/annotateRangeSequenceOperator.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * Inserts an integer sequence into the AST for the rangeSequenceExpr node. 6 | * 7 | * @param ast the AST to be annotated. 8 | * @returns an integer sequence. 9 | */ 10 | export function annotateRangeSequenceOperator(ast: IAST): SequenceType { 11 | const seqType = { 12 | type: ValueType.XSINTEGER, 13 | mult: SequenceMultiplicity.ONE_OR_MORE, 14 | }; 15 | 16 | astHelper.insertAttribute(ast, 'type', seqType); 17 | 18 | return seqType; 19 | } 20 | -------------------------------------------------------------------------------- /src/typeInference/annotateSequenceOperator.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | import { filterOnUniqueObjects } from './annotateFlworExpression'; 4 | 5 | /** 6 | * If every type of alle the elements are the same we annotate the ast with that type *. 7 | * else inserts an item()* type to the AST, as sequence operator can contain multiple different ITEM types. 8 | * If we have an empty sequence, we return a Node type. 9 | * 10 | * @param ast the AST to be annotated. 11 | * @returns `SequenceType` with multiplicity of `ZERO_OR_MORE`. 12 | */ 13 | export function annotateSequenceOperator( 14 | ast: IAST, 15 | length: number, 16 | elems: IAST[], 17 | types: SequenceType[], 18 | ): SequenceType { 19 | let seqType; 20 | 21 | if (length === 0) { 22 | // We have an empty sequence here 23 | seqType = { 24 | type: ValueType.NODE, 25 | mult: SequenceMultiplicity.ZERO_OR_MORE, 26 | }; 27 | } else if (length === 1) { 28 | seqType = types[0]; 29 | } else { 30 | const contatinsUndefinedOrNull = types.includes(undefined) || types.includes(null); 31 | if (contatinsUndefinedOrNull) { 32 | seqType = { 33 | type: ValueType.ITEM, 34 | mult: SequenceMultiplicity.ZERO_OR_MORE, 35 | }; 36 | } else { 37 | const uniqueTypes = filterOnUniqueObjects(types); 38 | seqType = 39 | uniqueTypes.length > 1 40 | ? { 41 | type: ValueType.ITEM, 42 | mult: SequenceMultiplicity.ZERO_OR_MORE, 43 | } 44 | : { 45 | type: uniqueTypes[0].type, 46 | mult: SequenceMultiplicity.ZERO_OR_MORE, 47 | }; 48 | } 49 | } 50 | 51 | if (seqType && seqType.type !== ValueType.ITEM) { 52 | astHelper.insertAttribute(ast, 'type', seqType); 53 | } 54 | 55 | return seqType; 56 | } 57 | -------------------------------------------------------------------------------- /src/typeInference/annotateSetOperators.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * Annotates the union, intersect and except operators with a sequence of nodes. 6 | * 7 | * @param ast the AST to be annotated. 8 | * @returns a SequenceType of type NODE and of multiplicity EXACTLY_ONE 9 | */ 10 | export function annotateSetOperator(ast: IAST): SequenceType { 11 | switch (ast[0]) { 12 | case 'unionOp': 13 | return annotateUnionOperator(ast); 14 | case 'intersectOp': 15 | return annotateIntersectOperator(ast); 16 | case 'exceptOp': 17 | return annotateExceptOperator(ast); 18 | } 19 | } 20 | 21 | function annotateUnionOperator(ast: IAST): SequenceType { 22 | const seqType = { 23 | type: ValueType.NODE, 24 | mult: SequenceMultiplicity.ZERO_OR_MORE, 25 | }; 26 | 27 | astHelper.insertAttribute(ast, 'type', seqType); 28 | return seqType; 29 | } 30 | 31 | function annotateIntersectOperator(ast: IAST): SequenceType { 32 | const seqType = { 33 | type: ValueType.NODE, 34 | mult: SequenceMultiplicity.ZERO_OR_MORE, 35 | }; 36 | 37 | astHelper.insertAttribute(ast, 'type', seqType); 38 | return seqType; 39 | } 40 | 41 | function annotateExceptOperator(ast: IAST): SequenceType { 42 | const seqType = { 43 | type: ValueType.NODE, 44 | mult: SequenceMultiplicity.ZERO_OR_MORE, 45 | }; 46 | 47 | astHelper.insertAttribute(ast, 'type', seqType); 48 | return seqType; 49 | } 50 | -------------------------------------------------------------------------------- /src/typeInference/annotateSimpleMapExpr.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | import { AnnotationContext } from './AnnotationContext'; 4 | 5 | /** 6 | * A simpleMapExpr is a .map() function, so it checks the type of the input, 7 | * and with the scope and the further annotations, then inferres the type. 8 | * 9 | * @param ast the AST to be annotated. 10 | * @returns the inferred SequenceType 11 | */ 12 | export function annotateSimpleMapExpr( 13 | ast: IAST, 14 | context: AnnotationContext, 15 | lastType: SequenceType, 16 | ): SequenceType { 17 | if (lastType !== undefined && lastType !== null) { 18 | const sequenceType: SequenceType = { 19 | type: lastType.type, 20 | mult: SequenceMultiplicity.ZERO_OR_MORE, 21 | }; 22 | if (sequenceType && sequenceType.type !== ValueType.ITEM) { 23 | astHelper.insertAttribute(ast, 'type', sequenceType); 24 | } 25 | return sequenceType; 26 | } else { 27 | return { type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_MORE }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/typeInference/annotateStringConcatenateOperator.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | /** 5 | * Concatenates two strings to each other, hence it always returns a string. 6 | * 7 | * @param ast the AST to be annotated. 8 | * @param context 9 | * @returns a SequenceType with type XSSTRING and multiplicity EXACTLY_ONE. 10 | */ 11 | export function annotateStringConcatenateOperator(ast: IAST): SequenceType { 12 | const seqType = { 13 | type: ValueType.XSSTRING, 14 | mult: SequenceMultiplicity.EXACTLY_ONE, 15 | }; 16 | 17 | astHelper.insertAttribute(ast, 'type', seqType); 18 | return seqType; 19 | } 20 | -------------------------------------------------------------------------------- /src/typeInference/annotateUnaryLookup.ts: -------------------------------------------------------------------------------- 1 | import { SequenceMultiplicity, SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | 4 | // TODO: Annotation not yet implemented. How to lookup the NCName and get the type to be returned. 5 | export function annotateUnaryLookup(ast: IAST, ncName: IAST): SequenceType { 6 | return { 7 | type: ValueType.ITEM, 8 | mult: SequenceMultiplicity.ZERO_OR_MORE, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/typeInference/annotateVarRef.ts: -------------------------------------------------------------------------------- 1 | import { SequenceType, ValueType } from '../expressions/dataTypes/Value'; 2 | import astHelper, { IAST } from '../parsing/astHelper'; 3 | import { AnnotationContext } from './AnnotationContext'; 4 | 5 | /** 6 | * The method to annotate the varRef node 7 | * 8 | * @param ast the ast containing the varRef 9 | * @param annotationContext the context in which its being annotated 10 | * @returns the type of the variable as SequenceType if it is contained in the context 11 | */ 12 | export function annotateVarRef(ast: IAST, annotationContext: AnnotationContext): SequenceType { 13 | const varName = astHelper.getQName(astHelper.getFirstChild(ast, 'name')); 14 | 15 | const varType: SequenceType = annotationContext.getVariable(varName.localName); 16 | if (varType && varType.type !== ValueType.ITEM) { 17 | astHelper.insertAttribute(ast, 'type', varType); 18 | } 19 | 20 | if (varName.namespaceURI === null) { 21 | const uri = annotationContext.resolveNamespace(varName.prefix); 22 | if (uri !== undefined) { 23 | astHelper.insertAttribute(ast, 'URI', uri); 24 | } 25 | } 26 | 27 | return varType; 28 | } 29 | -------------------------------------------------------------------------------- /src/types/Types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @public 3 | */ 4 | export type Node = { 5 | nodeType: number; 6 | }; 7 | 8 | /** 9 | * @public 10 | */ 11 | export type Attr = Node & { 12 | localName: string; 13 | name: string; 14 | namespaceURI: string | null; 15 | nodeName: string; 16 | prefix: string | null; 17 | value: string; 18 | }; 19 | 20 | /** 21 | * @public 22 | */ 23 | export type CharacterData = Node & { data: string }; 24 | 25 | /** 26 | * @public 27 | */ 28 | export type CDATASection = CharacterData; 29 | 30 | /** 31 | * @public 32 | */ 33 | export type Comment = CharacterData; 34 | 35 | /** 36 | * @public 37 | */ 38 | export type Document = Node & { 39 | implementation: { 40 | createDocument(namespaceURI: null, qualifiedNameStr: null, documentType: null): Document; 41 | }; 42 | createAttributeNS(namespaceURI: string, name: string): Attr; 43 | createCDATASection(contents: string): CDATASection; 44 | createComment(data: string): Comment; 45 | createElementNS(namespaceURI: string, qualifiedName: string): Element; 46 | createProcessingInstruction(target: string, data: string): ProcessingInstruction; 47 | createTextNode(data: string): Text; 48 | }; 49 | 50 | /** 51 | * @public 52 | */ 53 | export type Element = Node & { 54 | localName: string; 55 | namespaceURI: string | null; 56 | nodeName: string; 57 | prefix: string | null; 58 | }; 59 | 60 | /** 61 | * @public 62 | */ 63 | export type ProcessingInstruction = CharacterData & { 64 | nodeName: string; 65 | target: string; 66 | }; 67 | 68 | /** 69 | * @public 70 | */ 71 | export type Text = CharacterData; 72 | 73 | export type DocumentTypeNode = Node; 74 | -------------------------------------------------------------------------------- /src/types/createTypedValueFactory.ts: -------------------------------------------------------------------------------- 1 | import DomFacade from '../domFacade/DomFacade'; 2 | import ExternalDomFacade from '../domFacade/ExternalDomFacade'; 3 | import IDomFacade from '../domFacade/IDomFacade'; 4 | import { adaptJavaScriptValueToArrayOfXPathValues } from '../expressions/adaptJavaScriptValueToXPathValue'; 5 | import Value, { SequenceType, stringToSequenceType } from '../expressions/dataTypes/Value'; 6 | 7 | /** 8 | * Any type is allowed expect: functions, symbols, undefined, and null 9 | * 10 | * @public 11 | */ 12 | export type ValidValue = string | number | boolean | object | Date; 13 | 14 | /** 15 | * @public 16 | */ 17 | export type UntypedExternalValue = ValidValue | ValidValue[] | null; 18 | 19 | export const IS_XPATH_VALUE_SYMBOL = Symbol('IS_XPATH_VALUE_SYMBOL'); 20 | 21 | /** 22 | * A value converted to a specific type. When passed in other usage of fontoxpath calls it will 23 | * be handled as the type. 24 | */ 25 | export type TypedExternalValue = { 26 | [IS_XPATH_VALUE_SYMBOL]: true; 27 | convertedValue: Value[]; 28 | }; 29 | 30 | /** 31 | * Generates a factory to create a @see TypedExternalValue for the type @see typeName. 32 | * 33 | * @param typeName The type into which to convert the values. 34 | */ 35 | export default function createTypedValueFactory(sequenceTypeName: string) { 36 | return (value: UntypedExternalValue, domFacade: IDomFacade): TypedExternalValue => { 37 | const wrappedDomFacade: DomFacade = new DomFacade( 38 | domFacade === null ? new ExternalDomFacade() : domFacade, 39 | ); 40 | 41 | const convertedValue = adaptJavaScriptValueToArrayOfXPathValues( 42 | wrappedDomFacade, 43 | value, 44 | stringToSequenceType(sequenceTypeName), 45 | ); 46 | 47 | return { 48 | [IS_XPATH_VALUE_SYMBOL]: true, 49 | convertedValue, 50 | }; 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": ['../.eslintrc.js'], 3 | "parserOptions": { 4 | "project": ["test/tsconfig.json"], 5 | "sourceType": "module" 6 | }, 7 | "rules": { 8 | "no-new-func": "off", 9 | "@typescript-eslint/no-empty-function": "off", 10 | "no-console": "off", 11 | "@typescript-eslint/ban-types": "off", 12 | "import/no-extraneous-dependencies": "off" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /test/browsertests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPathToString, registerCustomXPathFunction } from 'fontoxpath'; 3 | import { parseXmlDocument } from 'slimdom'; 4 | 5 | describe('Browser tests', function () { 6 | describe('Custom functions union result order', function () { 7 | it('Returns the results in the same order across browsers', function () { 8 | const firstDocument = parseXmlDocument('1st Document'); 9 | const secondDocument = parseXmlDocument('2nd Document'); 10 | const thirdDocument = parseXmlDocument('3rd Document'); 11 | 12 | registerCustomXPathFunction('cf:firstDocument', [], 'item()', () => { 13 | return firstDocument.documentElement; 14 | }); 15 | 16 | registerCustomXPathFunction('cf:secondDocument', [], 'item()', () => { 17 | return secondDocument.documentElement; 18 | }); 19 | 20 | registerCustomXPathFunction('cf:thirdDocument', [], 'item()', () => { 21 | return thirdDocument.documentElement; 22 | }); 23 | 24 | chai.assert.equal( 25 | evaluateXPathToString( 26 | 'cf:firstDocument() | cf:secondDocument() | cf:thirdDocument()', 27 | ), 28 | '1st Document 2nd Document 3rd Document', 29 | ); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/helpers/evaluateXPathToAsyncSingleton.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPathToAsyncIterator } from 'fontoxpath'; 3 | export default async function evaluateXPathToAsyncSingleton(...args) { 4 | const iterator = evaluateXPathToAsyncIterator.apply(null, args); 5 | const first = await iterator.next(); 6 | if (first.done) { 7 | return null; 8 | } 9 | const second = await iterator.next(); 10 | chai.assert.isTrue(second.done, 'The XPath should resolve to a singleton, or nothing.'); 11 | return first.value; 12 | } 13 | -------------------------------------------------------------------------------- /test/helpers/getSkippedTests.ts: -------------------------------------------------------------------------------- 1 | import testFs from './testFs'; 2 | 3 | export function getSkippedTests(filename) { 4 | const skippedTests = testFs.readFileSync(filename).split(/\r?\n/); 5 | if (process.argv.indexOf('--regenerate') !== -1) { 6 | const skipIndex = 7 | skippedTests.indexOf( 8 | '=====================TESTS ABOVE HAVE BEEN MARKED MANUALLY=====================', 9 | ) + 1; 10 | skippedTests.splice(skipIndex); 11 | } 12 | return skippedTests; 13 | } 14 | -------------------------------------------------------------------------------- /test/helpers/testFs.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | if (!fs.promises) { 5 | (fs as any).promises = { 6 | readFile: fs.readFileSync, 7 | }; 8 | } 9 | 10 | function createAssetPath(assetPath) { 11 | return path.join('test', 'assets', assetPath); 12 | } 13 | 14 | /** 15 | * @class 16 | * Wrapper for fs for reading test assets 17 | */ 18 | export default new (class testFs { 19 | lstatSync(assetPath) { 20 | return fs.lstatSync(createAssetPath(assetPath)); 21 | } 22 | 23 | existsSync(assetPath) { 24 | return fs.existsSync(createAssetPath(assetPath)); 25 | } 26 | 27 | readdirSync(dirPath) { 28 | return fs.readdirSync(createAssetPath(dirPath)); 29 | } 30 | 31 | async readFile(filePath) { 32 | const overridePath = path.join('overrides', filePath); 33 | if (this.existsSync(overridePath)) { 34 | filePath = overridePath; 35 | } 36 | return fs.promises.readFile(createAssetPath(filePath), 'utf-8'); 37 | } 38 | 39 | readFileSync(filePath) { 40 | const overridePath = path.join('overrides', filePath); 41 | if (this.existsSync(overridePath)) { 42 | filePath = overridePath; 43 | } 44 | return fs.readFileSync(createAssetPath(filePath), 'utf-8'); 45 | } 46 | 47 | writeFileSync(filePath, content) { 48 | return fs.writeFileSync(createAssetPath(filePath), content); 49 | } 50 | })(); 51 | -------------------------------------------------------------------------------- /test/install-assets.ps1: -------------------------------------------------------------------------------- 1 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 2 | Invoke-WebRequest "https://github.com/LeoWoerteler/XQUTS/archive/master.zip" -Out ./test/assets/XQUTS.zip 3 | Expand-Archive ./test/assets/qt3tests/xqueryx.zip -DestinationPath ./test/assets/qt3tests/xqueryx-extracted -Force 4 | Move-Item ./test/assets/qt3tests/xqueryx-extracted/xqueryx ./test/assets/qt3tests/xqueryx 5 | Expand-Archive ./test/assets/XQUTS.zip -DestinationPath ./test/assets/XQUTS-extracted -Force 6 | Move-Item ./test/assets/XQUTS-extracted/XQUTS-master ./test/assets/XQUTS 7 | Remove-Item ./test/assets/XQUTS.zip 8 | Remove-Item ./test/assets/qt3tests/xqueryx-extracted 9 | -------------------------------------------------------------------------------- /test/install-assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | curl -L https://github.com/LeoWoerteler/XQUTS/archive/master.tar.gz | tar -xz -C ./test/assets/XQUTS --strip-components=1 3 | unzip -q test/assets/qt3tests/xqueryx.zip -d ./test/assets/qt3tests/ 4 | -------------------------------------------------------------------------------- /test/specs/expressions/dataTypes/documentOrderUtils.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { mergeSort } from 'fontoxpath/expressions/dataTypes/documentOrderUtils'; 3 | 4 | describe('Merge sort', () => { 5 | describe('Default comparison', () => { 6 | it('Orders numbers numerically', () => { 7 | chai.expect(mergeSort([3, 6, 5])).to.have.ordered.members([3, 5, 6]); 8 | }); 9 | 10 | it('Orders strings alphabetically', () => { 11 | chai.expect(mergeSort(['c', 'a', 'b'])).to.have.ordered.members(['a', 'b', 'c']); 12 | }); 13 | }); 14 | 15 | describe('Custom comparison', () => { 16 | it('Orders based on the custom comparator', () => { 17 | const reverseOrderComparator = (value1: number, value2: number) => 18 | value1 < value2 ? 0 : -1; 19 | chai.expect(mergeSort([3, 6, 5], reverseOrderComparator)).to.have.ordered.members([ 20 | 6, 5, 3, 21 | ]); 22 | }); 23 | it('Orders based on any value less than 0', () => { 24 | const reverseOrderComparator = (value1: number, value2: number) => 25 | value1 < value2 ? 0 : -5; 26 | chai.expect(mergeSort([3, 6, 5], reverseOrderComparator)).to.have.ordered.members([ 27 | 6, 5, 3, 28 | ]); 29 | }); 30 | 31 | it('Reverses the array if the comparer does not return less than 0', () => { 32 | const reverseComparator = () => 0; 33 | chai.expect(mergeSort([3, 6, 5], reverseComparator)).to.have.ordered.members([5, 6, 3]); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/specs/expressions/functions/functionRegistry.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { SequenceMultiplicity, ValueType } from 'fontoxpath/expressions/dataTypes/Value'; 3 | import functionRegistry from 'fontoxpath/expressions/functions/functionRegistry'; 4 | import registerCustomXPathFunction from 'fontoxpath/registerCustomXPathFunction'; 5 | 6 | describe('functionRegistry.getFunctionByArity', () => { 7 | before(() => { 8 | registerCustomXPathFunction( 9 | 'fontoxpath_test_prefix:functionName', 10 | [], 11 | 'xs:boolean', 12 | function () {}, 13 | ); 14 | 15 | registerCustomXPathFunction( 16 | 'fontoxpath_test_prefix:functionName', 17 | ['xs:boolean'], 18 | 'xs:boolean', 19 | function () {}, 20 | ); 21 | 22 | registerCustomXPathFunction( 23 | 'fontoxpath_test_prefix:otherFunctionName', 24 | [], 25 | 'xs:boolean', 26 | function () {}, 27 | ); 28 | }); 29 | 30 | it('return null if a custom function cannot be found', () => { 31 | chai.assert.isNull( 32 | functionRegistry.getFunctionByArity( 33 | 'fontoxpath_test_prefix:bla', 34 | 'functionLocalName', 35 | 0, 36 | ), 37 | ); 38 | }); 39 | 40 | it('return null if a custom function with a given arity cannot be found', () => { 41 | chai.assert.isNull( 42 | functionRegistry.getFunctionByArity( 43 | 'fontoxpath_test_prefix:functionName', 44 | 'functionLocalName', 45 | 3, 46 | ), 47 | ); 48 | }); 49 | 50 | it('return null if a built in function cannot be found', () => { 51 | chai.assert.isNull(functionRegistry.getFunctionByArity('bla', 'functionLocalName', 3)); 52 | }); 53 | 54 | it('return null if a built in function with a given arity cannot be found', () => { 55 | chai.assert.isNull(functionRegistry.getFunctionByArity('true', 'functionLocalName', 3)); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/specs/expressions/jsCodegen/compare.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPathToBoolean, ReturnType } from 'fontoxpath'; 3 | import * as slimdom from 'slimdom'; 4 | import jsonMlMapper from 'test-helpers/jsonMlMapper'; 5 | import evaluateXPathWithJsCodegen from '../../parsing/jsCodegen/evaluateXPathWithJsCodegen'; 6 | 7 | describe('compare tests', () => { 8 | let documentNode: slimdom.Document; 9 | beforeEach(() => { 10 | documentNode = new slimdom.Document(); 11 | jsonMlMapper.parse(['xml', { attr: 'true' }], documentNode); 12 | }); 13 | 14 | it('does not generate compare function for nodes', () => { 15 | const node = documentNode.documentElement; 16 | const query = '@attr << @attr'; 17 | 18 | chai.assert.throws( 19 | () => evaluateXPathWithJsCodegen(query, node, null, ReturnType.BOOLEAN), 20 | 'Unsupported compare type', 21 | ); 22 | 23 | chai.assert.equal(evaluateXPathToBoolean(query, node), true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/specs/expressions/postfix/Filter.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import Expression from 'fontoxpath/expressions/Expression'; 3 | import Filter from 'fontoxpath/expressions/postfix/Filter'; 4 | import Specificity from 'fontoxpath/expressions/Specificity'; 5 | import * as sinon from 'sinon'; 6 | 7 | describe('Filter', () => { 8 | let equalExpression; 9 | 10 | beforeEach(() => { 11 | equalExpression = { 12 | specificity: new Specificity({}), 13 | equals: sinon.stub().returns(true), 14 | }; 15 | }); 16 | 17 | describe('Filter.getBucket()', () => { 18 | it('returns the bucket of its selector', () => { 19 | const filter = new Filter( 20 | { 21 | getBucket: () => 'name-just-for-testing', 22 | specificity: new Specificity({}), 23 | } as unknown as Expression, 24 | equalExpression, 25 | ); 26 | chai.assert.equal(filter.getBucket(), 'name-just-for-testing'); 27 | }); 28 | }); 29 | 30 | describe('Filter.specificity', () => { 31 | it('returns the specificity of the selector plus the other part', () => { 32 | const filter = new Filter( 33 | { specificity: new Specificity({ external: 1 }) } as Expression, 34 | { specificity: new Specificity({ external: 1 }) } as Expression, 35 | ); 36 | chai.assert.equal( 37 | filter.specificity.compareTo(new Specificity({ external: 2 })), 38 | 0, 39 | 'should be of equal specificity', 40 | ); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/specs/expressions/util/concatSequences.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import sequenceFactory from 'fontoxpath/expressions/dataTypes/sequenceFactory'; 3 | import Value, { ValueType } from 'fontoxpath/expressions/dataTypes/Value'; 4 | import concatSequences from 'fontoxpath/expressions/util/concatSequences'; 5 | 6 | function value(val: any) { 7 | return new Value(ValueType.XSINTEGER, val); 8 | } 9 | 10 | describe('concatSequences', () => { 11 | it('concats sequences', () => { 12 | chai.assert.deepEqual( 13 | concatSequences([ 14 | sequenceFactory.create([value(1), value(2), value(3)]), 15 | sequenceFactory.create([value(4), value(5), value(6)]), 16 | ]).getAllValues(), 17 | [value(1), value(2), value(3), value(4), value(5), value(6)], 18 | ); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/specs/parsing/arrays/ArrayConstructor.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPathToArray, evaluateXPathToString } from 'fontoxpath'; 3 | import * as slimdom from 'slimdom'; 4 | import jsonMlMapper from 'test-helpers/jsonMlMapper'; 5 | 6 | let documentNode; 7 | beforeEach(() => { 8 | documentNode = new slimdom.Document(); 9 | }); 10 | 11 | describe('array constructor', () => { 12 | beforeEach(() => { 13 | jsonMlMapper.parse( 14 | ['someElement', { someAttribute: 'someValue' }, 'A piece of text'], 15 | documentNode, 16 | ); 17 | }); 18 | 19 | describe('curly', () => { 20 | it('can be parsed', () => 21 | chai.assert.isOk( 22 | evaluateXPathToArray('array {1, 2}', documentNode), 23 | 'It should be able to be parsed', 24 | )); 25 | it('unfolds passed sequences', () => 26 | chai.assert.deepEqual(evaluateXPathToArray('array {("a", "b"), "c"}', documentNode), [ 27 | 'a', 28 | 'b', 29 | 'c', 30 | ])); 31 | }); 32 | 33 | describe('square', () => { 34 | it('can be parsed', () => 35 | chai.assert.isOk( 36 | evaluateXPathToArray('[1, 2]', documentNode), 37 | 'It should be able to be parsed', 38 | )); 39 | it('does not unfold passed sequences', () => 40 | chai.assert.equal(evaluateXPathToString('[("a", "b"), "c"](1)', documentNode), 'a b')); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/specs/parsing/arrays/arrayPredicates.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPathToNumbers } from 'fontoxpath'; 3 | 4 | describe('Predicates and arrays', () => { 5 | it('can filter an array', () => 6 | chai.assert.deepEqual( 7 | evaluateXPathToNumbers('(array {1,2,3,4,5,6,7})?*[. mod 2 = 1]'), 8 | [1, 3, 5, 7], 9 | )); 10 | 11 | it('can filter an array with a map in it', () => 12 | chai.assert.deepEqual( 13 | evaluateXPathToNumbers( 14 | '((array {map{"num":1},map{"num":2},map{"num":3},map{"num":4},map{"num":5},map{"num":6},map{"num":7}})?*[?num mod 2 = 1])?*', 15 | ), 16 | [1, 3, 5, 7], 17 | )); 18 | 19 | it('can filter an array with a map in it and applies operators in correct order', () => 20 | chai.assert.deepEqual( 21 | evaluateXPathToNumbers( 22 | 'array {map{"num":1},map{"num":2},map{"num":3},map{"num":4},map{"num":5},map{"num":6},map{"num":7}}?*[?num mod 2 = 1]?*', 23 | ), 24 | [1, 3, 5, 7], 25 | )); 26 | }); 27 | -------------------------------------------------------------------------------- /test/specs/parsing/asyncXPath.xq: -------------------------------------------------------------------------------- 1 | let $baseUrl := 'https://raw.githubusercontent.com/LeoWoerteler/QT3TS/master/', 2 | $catalog := fontoxpath:fetch($baseUrl || 'catalog.xml'), 3 | $environmentsByName := $catalog/*:catalog/*:environment ! map:entry(@name => string(), .) => map:merge(), 4 | $testSets := $catalog/*:catalog/*:test-set/@file/string()!fontoxpath:fetch($baseUrl || .) => head() 5 | return for $test in $testSets/*:test-set/*:test-case return fontoxpath:evaluate($test/*:test => string(), map{}) 6 | -------------------------------------------------------------------------------- /test/specs/parsing/axes/SelfAxis.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPathToFirstNode, getBucketForSelector, getBucketsForNode } from 'fontoxpath'; 5 | 6 | let documentNode; 7 | beforeEach(() => { 8 | documentNode = new slimdom.Document(); 9 | }); 10 | 11 | describe('self', () => { 12 | it('parses self::', () => { 13 | const element = documentNode.createElement('someElement'); 14 | chai.assert.equal(evaluateXPathToFirstNode('self::someElement', element), element); 15 | }); 16 | 17 | it('returns the correct bucket', () => { 18 | const element = documentNode.createElement('someElement'); 19 | chai.assert.include( 20 | getBucketsForNode(element), 21 | getBucketForSelector('self::someElement'), 22 | 'The self::element selector should have the matching buckets', 23 | ); 24 | }); 25 | 26 | it('returns the correct intersecting bucket', () => { 27 | const element = documentNode.createElement('someElement'); 28 | chai.assert.include( 29 | getBucketsForNode(element), 30 | getBucketForSelector('self::*[self::someElement]'), 31 | 'The self::*[self::someElement] selector should have the matching buckets', 32 | ); 33 | }); 34 | 35 | it('throws the correct error if context is absent', () => { 36 | chai.assert.throws(() => evaluateXPathToFirstNode('self::*', null), 'XPDY0002'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/specs/parsing/comments/comments.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPathToBoolean } from 'fontoxpath'; 5 | 6 | let documentNode; 7 | beforeEach(() => { 8 | documentNode = new slimdom.Document(); 9 | }); 10 | 11 | describe('comments', () => { 12 | it('can parse comments', () => 13 | chai.assert.isTrue( 14 | evaluateXPathToBoolean('true() (: and false() :) or true()', documentNode), 15 | )); 16 | 17 | it('can parse nested comments', () => 18 | chai.assert.isTrue( 19 | evaluateXPathToBoolean( 20 | 'true() (: and false() (:and true():) :) or false', 21 | documentNode, 22 | ), 23 | )); 24 | }); 25 | -------------------------------------------------------------------------------- /test/specs/parsing/compareSpecificity.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { compareSpecificity, parseScript } from 'fontoxpath'; 3 | import { Document } from 'slimdom'; 4 | 5 | describe('compareSpecificity', () => { 6 | function assertSpecificity( 7 | selectorExpressionA: string, 8 | selectorExpressionB: string, 9 | expectedResult, 10 | ) { 11 | // Assert selectors as a string 12 | chai.assert.equal( 13 | compareSpecificity(selectorExpressionA, selectorExpressionB), 14 | expectedResult, 15 | ); 16 | 17 | // Assert selectors as an AST 18 | chai.assert.equal( 19 | compareSpecificity( 20 | parseScript(selectorExpressionA, {}, new Document()), 21 | parseScript(selectorExpressionB, {}, new Document()), 22 | ), 23 | expectedResult, 24 | ); 25 | } 26 | 27 | it('returns 0 for the same xpath', () => assertSpecificity('self::*', 'self::*', 0)); 28 | it('nodeType > universal', () => assertSpecificity('self::element()', 'self::node()', 1)); 29 | it('name > nodeType', () => assertSpecificity('self::name', 'self::element()', 1)); 30 | it('attributes > nodeName', () => assertSpecificity('@id', 'self::name', 1)); 31 | it('functions > attributes', () => assertSpecificity('id("123")', '@id', 1)); 32 | it('union is the maximum of its operands', () => 33 | assertSpecificity('self::name | id("123") | self::otherName | id("123")', 'self::name', 1)); 34 | }); 35 | -------------------------------------------------------------------------------- /test/specs/parsing/conditional/IfExpression.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPathToBoolean } from 'fontoxpath'; 5 | 6 | import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; 7 | 8 | describe('IfExpression', () => { 9 | let documentNode; 10 | beforeEach(() => { 11 | documentNode = new slimdom.Document(); 12 | }); 13 | 14 | it('returns the value of the then expression if the test resolves to true', () => 15 | chai.assert( 16 | evaluateXPathToBoolean( 17 | '(if (true()) then "then expression" else "else expression") eq "then expression"', 18 | documentNode, 19 | ), 20 | )); 21 | 22 | it('returns the value of the else expression if the test resolves to false', () => 23 | chai.assert( 24 | evaluateXPathToBoolean( 25 | '(if (false()) then "then expression" else "else expression") eq "else expression"', 26 | documentNode, 27 | ), 28 | )); 29 | }); 30 | -------------------------------------------------------------------------------- /test/specs/parsing/createSelectorFromXPath.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | import jsonMlMapper from 'test-helpers/jsonMlMapper'; 4 | 5 | import { evaluateXPathToNodes } from 'fontoxpath'; 6 | 7 | describe('createExpressionFromXPath', () => { 8 | let documentNode; 9 | beforeEach(() => { 10 | documentNode = new slimdom.Document(); 11 | }); 12 | 13 | it('matches hovercrafts full of eels', () => { 14 | jsonMlMapper.parse(['hovercraft', ['eel'], ['eel']], documentNode); 15 | chai.assert.deepEqual( 16 | evaluateXPathToNodes( 17 | 'self::hovercraft[eel and not(*[not(self::eel)])]', 18 | documentNode.documentElement, 19 | ), 20 | [documentNode.documentElement], 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/specs/parsing/createSelectorFromXPathAsync.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPathToNumber, precompileXPath } from 'fontoxpath'; 3 | import * as slimdom from 'slimdom'; 4 | 5 | describe('createExpressionFromXPathAsync', () => { 6 | let documentNode; 7 | beforeEach(() => { 8 | documentNode = new slimdom.Document(); 9 | }); 10 | 11 | it('can compile a selector asynchronously', () => { 12 | return precompileXPath('1 + 1').then(function (selector) { 13 | // Assume selector to be ok 14 | chai.expect(evaluateXPathToNumber(selector, documentNode)).to.equal(2); 15 | }); 16 | }).timeout(10000); 17 | 18 | it('can compile a new, unique selector asynchronously', () => { 19 | const now = Date.now(); 20 | return precompileXPath(`1 + ${now}`).then(function (selector) { 21 | // Assume selector to be ok 22 | chai.assert.equal(evaluateXPathToNumber(selector, documentNode), now + 1); 23 | }); 24 | }).timeout(10000); 25 | }); 26 | -------------------------------------------------------------------------------- /test/specs/parsing/deprecatedFeatures.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPathToBoolean } from 'fontoxpath'; 5 | 6 | describe('Deprecated features', () => { 7 | let documentNode; 8 | beforeEach(() => { 9 | documentNode = new slimdom.Document(); 10 | }); 11 | 12 | it('Does not accept functions as tests anymore', () => 13 | chai.assert.throws( 14 | () => evaluateXPathToBoolean('self::false()', documentNode), 15 | 'XPST0003', 16 | )); 17 | }); 18 | -------------------------------------------------------------------------------- /test/specs/parsing/flwor.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPath, evaluateXPathToString, evaluateXPathToStrings } from 'fontoxpath'; 5 | import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; 6 | 7 | let documentNode; 8 | beforeEach(() => { 9 | documentNode = new slimdom.Document(); 10 | }); 11 | 12 | describe('FLWOR', () => { 13 | it('runs basic flwor expression', () => 14 | chai.assert.equal( 15 | evaluateXPathToString( 16 | `for $i in (0,1,2) 17 | let $e := 'Hello' 18 | return $e`, 19 | null, 20 | null, 21 | null, 22 | { debug: true, language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 23 | ), 24 | 'Hello Hello Hello', 25 | )); 26 | 27 | it('runs flwor with where', () => 28 | chai.assert.equal( 29 | evaluateXPathToString( 30 | `for $i in (0,1,2) 31 | where $i = 1 32 | let $e := 'Hello' 33 | return $e`, 34 | null, 35 | null, 36 | null, 37 | { debug: true, language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 38 | ), 39 | 'Hello', 40 | )); 41 | 42 | it('runs flwor expressions with order by', () => { 43 | chai.assert.deepEqual( 44 | evaluateXPathToStrings( 45 | `for $a in ("B", "A", "C") 46 | order by $a 47 | return $a`, 48 | null, 49 | null, 50 | null, 51 | { debug: true, language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 52 | ), 53 | ['A', 'B', 'C'], 54 | ); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/specs/parsing/functions/builtinFunctions.context.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPathToBoolean, evaluateXPathToString } from 'fontoxpath'; 5 | 6 | let documentNode; 7 | beforeEach(() => { 8 | documentNode = new slimdom.Document(); 9 | }); 10 | 11 | describe('Context related functions', () => { 12 | describe('fn:current-dateTime', () => { 13 | it('returns the current dateTime', () => 14 | chai.assert.isOk(evaluateXPathToString('current-dateTime()', documentNode))); 15 | it('returns the same value during the execution of the query', () => 16 | chai.assert.isTrue(evaluateXPathToBoolean('current-dateTime() eq current-dateTime()'))); 17 | it('returns different values for different queries', async () => { 18 | const dateTime = evaluateXPathToString('current-dateTime()'); 19 | const dateTimeLater = await new Promise((resolve) => 20 | setTimeout(() => resolve(evaluateXPathToString('current-dateTime()')), 100), 21 | ); 22 | 23 | chai.assert.notEqual(dateTime, dateTimeLater); 24 | }); 25 | }); 26 | describe('fn:current-date', () => { 27 | it('returns the current date', () => 28 | chai.assert.isOk(evaluateXPathToString('current-date()', documentNode))); 29 | }); 30 | describe('fn:current-time', () => { 31 | it('returns the current time', () => 32 | chai.assert.isOk(evaluateXPathToString('current-time()', documentNode))); 33 | }); 34 | describe('fn:implicit-timezone', () => { 35 | it('returns the implicit timezone', () => 36 | chai.assert.isOk(evaluateXPathToString('implicit-timezone()', documentNode))); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/specs/parsing/functions/functions.boolean.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { domFacade, evaluateXPathToBoolean } from 'fontoxpath'; 5 | 6 | import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; 7 | 8 | let documentNode; 9 | beforeEach(() => { 10 | documentNode = new slimdom.Document(); 11 | }); 12 | 13 | describe('boolean functions', () => { 14 | describe('xs:boolean()', () => { 15 | it('accepts "true"', () => { 16 | chai.assert.equal( 17 | evaluateXPathToBoolean('xs:boolean("true")', documentNode, domFacade), 18 | true, 19 | ); 20 | }); 21 | }); 22 | describe('fn:boolean()', () => { 23 | it('accepts "true"', () => { 24 | chai.assert.equal( 25 | evaluateXPathToBoolean('fn:boolean("true")', documentNode, domFacade), 26 | true, 27 | ); 28 | }); 29 | }); 30 | describe('fn:not()', () => { 31 | it('accepts true', () => { 32 | chai.assert.isFalse(evaluateXPathToBoolean('not(true())', documentNode, domFacade)); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/specs/parsing/functions/functions.json.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { 5 | domFacade, 6 | evaluateXPathToArray, 7 | evaluateXPathToBoolean, 8 | evaluateXPathToMap, 9 | } from 'fontoxpath'; 10 | 11 | let documentNode; 12 | beforeEach(() => { 13 | documentNode = new slimdom.Document(); 14 | }); 15 | 16 | describe('functions over json', () => { 17 | it('can parse json objects', () => 18 | chai.assert.deepEqual( 19 | evaluateXPathToMap('parse-json("{""a"": 1}")', documentNode, domFacade), 20 | { a: 1 }, 21 | )); 22 | 23 | it('can parse json arrays', () => 24 | chai.assert.deepEqual( 25 | evaluateXPathToArray('parse-json("[1,2,3]")', documentNode, domFacade), 26 | [1, 2, 3], 27 | )); 28 | 29 | it('can parse json booleans', () => 30 | chai.assert.isTrue( 31 | evaluateXPathToBoolean('parse-json("true") eq true()', documentNode, domFacade), 32 | )); 33 | 34 | it('can parse json numbers', () => 35 | chai.assert.isTrue( 36 | evaluateXPathToBoolean('parse-json("1") eq 1e0', documentNode, domFacade), 37 | )); 38 | 39 | it('can parse json strings', () => 40 | chai.assert.isTrue( 41 | evaluateXPathToBoolean( 42 | 'parse-json("""A piece of text""") eq "A piece of text"', 43 | documentNode, 44 | domFacade, 45 | ), 46 | )); 47 | 48 | it('can parse json null', () => 49 | chai.assert.isTrue( 50 | evaluateXPathToBoolean('parse-json("null") => count() eq 0', documentNode, domFacade), 51 | )); 52 | 53 | it('returns an error for invalid json', () => 54 | chai.assert.throws( 55 | () => 56 | evaluateXPathToBoolean('parse-json("}{") => count() eq 0', documentNode, domFacade), 57 | 'FOJS0001', 58 | )); 59 | }); 60 | -------------------------------------------------------------------------------- /test/specs/parsing/getBucketsForNode.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { getBucketsForNode } from 'fontoxpath'; 3 | import * as slimdom from 'slimdom'; 4 | const doc = new slimdom.Document(); 5 | describe('getBucketsForNode', () => { 6 | it('returns the correct buckets for elements', () => { 7 | chai.assert.deepEqual(getBucketsForNode(doc.createElement('element')), [ 8 | 'type-1-or-type-2', 9 | 'type-1', 10 | 'name-element', 11 | ]); 12 | }); 13 | it('returns the correct buckets for text nodes', () => { 14 | chai.assert.deepEqual(getBucketsForNode(doc.createTextNode('A piece of text')), ['type-3']); 15 | chai.assert.deepEqual(getBucketsForNode(doc.createCDATASection('A piece of cdata')), [ 16 | 'type-3', 17 | ]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/specs/parsing/indexOf.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | 3 | import { evaluateXPathToNumber } from 'fontoxpath'; 4 | 5 | describe('indexOf', () => { 6 | it('determine a single index', () => 7 | chai.assert.equal(evaluateXPathToNumber('index-of(1, 1)'), 1)); 8 | 9 | it('determine a single index in two values', () => 10 | chai.assert.equal(evaluateXPathToNumber('index-of((1, 2), 2)'), 2)); 11 | 12 | it('determine a single index in a string set', () => 13 | chai.assert.equal(evaluateXPathToNumber('index-of(("Hey", "Hola", "Hi"), "Hola")'), 2)); 14 | }); 15 | -------------------------------------------------------------------------------- /test/specs/parsing/jsCodegen/blns.tests.ts: -------------------------------------------------------------------------------- 1 | import * as blns from 'blns'; 2 | import * as chai from 'chai'; 3 | import { evaluateXPath, ReturnType } from 'fontoxpath'; 4 | import * as slimdom from 'slimdom'; 5 | import jsonMlMapper from 'test-helpers/jsonMlMapper'; 6 | import evaluateXPathWithJsCodegen from './evaluateXPathWithJsCodegen'; 7 | 8 | describe('Big List of Naughty Strings tests for JSCodegen', () => { 9 | let documentNode: slimdom.Document; 10 | beforeEach(() => { 11 | documentNode = new slimdom.Document(); 12 | jsonMlMapper.parse(['xml', 'Hello'], documentNode); 13 | }); 14 | 15 | for (const badString of blns) { 16 | it(`BLNS equality: ${badString}`, () => { 17 | const literalQuery = '"' + badString + '"'; 18 | 19 | try { 20 | const normalRes = evaluateXPath( 21 | literalQuery, 22 | documentNode, 23 | null, 24 | null, 25 | ReturnType.STRING, 26 | {}, 27 | ); 28 | const jsCodegenRes = evaluateXPathWithJsCodegen( 29 | literalQuery, 30 | documentNode, 31 | null, 32 | ReturnType.STRING, 33 | {}, 34 | ); 35 | 36 | chai.assert.equal(jsCodegenRes, normalRes); 37 | } catch (errEval) { 38 | try { 39 | evaluateXPathWithJsCodegen( 40 | literalQuery, 41 | documentNode, 42 | null, 43 | ReturnType.STRING, 44 | {}, 45 | ); 46 | chai.assert.fail( 47 | `BLNS literal ${literalQuery} errored during evaluation but not during JS Codegen.`, 48 | ); 49 | } catch (errJs) { 50 | // Only comparing the string representations, since the stack traces will differ. 51 | chai.assert.equal(errEval.toString(), errJs.toString()); 52 | } 53 | } 54 | }); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /test/specs/parsing/jsCodegen/operators.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import jsonMlMapper from 'test-helpers/jsonMlMapper'; 5 | 6 | import { compileXPathToJavaScript, ReturnType } from 'fontoxpath'; 7 | import evaluateXPathWithJsCodegen from './evaluateXPathWithJsCodegen'; 8 | 9 | describe('operators', () => { 10 | let documentNode: slimdom.Document; 11 | 12 | beforeEach(() => { 13 | documentNode = new slimdom.Document(); 14 | jsonMlMapper.parse(['xml', ['title', 'Tips'], ['tips']], documentNode); 15 | }); 16 | 17 | it('compiles "or" when used as a base expression', () => { 18 | const elementNode: slimdom.Node = documentNode.firstChild; 19 | chai.assert.isTrue( 20 | evaluateXPathWithJsCodegen( 21 | 'self::p or self::xml', 22 | elementNode, 23 | null, 24 | ReturnType.BOOLEAN, 25 | ), 26 | ); 27 | }); 28 | 29 | it('compiles "and" when used as a base expression', () => { 30 | const elementNode: slimdom.Node = documentNode.firstChild; 31 | chai.assert.isTrue( 32 | evaluateXPathWithJsCodegen( 33 | 'self::xml and child::element(tips)', 34 | elementNode, 35 | null, 36 | ReturnType.BOOLEAN, 37 | ), 38 | ); 39 | }); 40 | 41 | it('rejects logical operator when lhs is not compilable', () => { 42 | chai.assert.isFalse( 43 | compileXPathToJavaScript('count((1,2,3)) and self::xml', ReturnType.BOOLEAN, {}) 44 | .isAstAccepted, 45 | ); 46 | }); 47 | 48 | it('rejects logical operator when rhs is not compilable', () => { 49 | chai.assert.isFalse( 50 | compileXPathToJavaScript('self::xml and count((1,2,3))', ReturnType.BOOLEAN, {}) 51 | .isAstAccepted, 52 | ); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/specs/parsing/jsCodegen/wildcard.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import jsonMlMapper from 'test-helpers/jsonMlMapper'; 5 | 6 | import { ReturnType } from 'fontoxpath'; 7 | import evaluateXPathWithJsCodegen from './evaluateXPathWithJsCodegen'; 8 | 9 | describe('wildcard', () => { 10 | let documentNode: slimdom.Document; 11 | 12 | beforeEach(() => { 13 | documentNode = new slimdom.Document(); 14 | jsonMlMapper.parse( 15 | [ 16 | 'xml', 17 | ['title', 'Tips'], 18 | [ 19 | 'tips', 20 | ['tip', 'Make it work'], 21 | ['tip', 'Make it right'], 22 | ['tip', 'Make it fast'], 23 | ], 24 | ], 25 | documentNode, 26 | ); 27 | }); 28 | 29 | it('selects elements (non-specified namespace)', () => { 30 | const results = evaluateXPathWithJsCodegen('/xml/*', documentNode, null, ReturnType.NODES); 31 | 32 | chai.assert.equal(results.length, 2); 33 | }); 34 | 35 | it('does not select text elements', () => { 36 | chai.assert.isFalse( 37 | evaluateXPathWithJsCodegen('/xml/tips/tip/*', documentNode, null, ReturnType.BOOLEAN), 38 | ); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/specs/parsing/operators/boolean/AndOperator.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPathToBoolean } from 'fontoxpath'; 3 | import * as slimdom from 'slimdom'; 4 | 5 | import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; 6 | 7 | describe('and operator', () => { 8 | it('can perform "1 and 1"', () => chai.assert.isTrue(evaluateXPathToBoolean('1 and 1'))); 9 | 10 | it('can parse an "and" selector', () => { 11 | chai.assert.isTrue(evaluateXPathToBoolean('true() and true()')); 12 | }); 13 | 14 | it('can not compute the ebv of a sequence with 2 items', () => { 15 | chai.assert.throws( 16 | () => evaluateXPathToBoolean('(false(), false()) and (false(), false())'), 17 | 'FORG0006', 18 | ); 19 | }); 20 | 21 | it('can turns non-empty nodes sequences into "true"', () => { 22 | chai.assert.isTrue(evaluateXPathToBoolean('true() and (.,.)', new slimdom.Document())); 23 | }); 24 | 25 | it('can optimize an and expression with buckets', () => { 26 | chai.assert.isFalse(evaluateXPathToBoolean('self::p and true()', new slimdom.Document())); 27 | }); 28 | 29 | it('can optimize an and expression with buckets, inside a not()', () => { 30 | chai.assert.isTrue( 31 | evaluateXPathToBoolean('not(self::p and true())', new slimdom.Document()), 32 | ); 33 | }); 34 | 35 | it('can parse a concatenation of ands', () => 36 | chai.assert.isFalse(evaluateXPathToBoolean('true() and true() and true() and false()'))); 37 | }); 38 | -------------------------------------------------------------------------------- /test/specs/parsing/operators/boolean/OrOperator.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; 4 | import jsonMlMapper from 'test-helpers/jsonMlMapper'; 5 | 6 | import { evaluateXPathToBoolean } from 'fontoxpath'; 7 | 8 | let documentNode; 9 | beforeEach(() => { 10 | documentNode = new slimdom.Document(); 11 | }); 12 | 13 | describe('or operator', () => { 14 | it('can parse an "or" selector', () => { 15 | chai.assert.isTrue(evaluateXPathToBoolean('false() or true()')); 16 | }); 17 | it('can parse an "or" selector returning false', () => { 18 | chai.assert.isFalse(evaluateXPathToBoolean('false() or false()')); 19 | }); 20 | 21 | it('can parse an "or" selector with different buckets', () => { 22 | jsonMlMapper.parse(['someParentElement', ['someElement']], documentNode); 23 | chai.assert.isTrue( 24 | evaluateXPathToBoolean( 25 | 'self::someElement or self::processing-instruction()', 26 | documentNode.documentElement.firstChild, 27 | ), 28 | ); 29 | }); 30 | 31 | it('can parse a concatenation of ors', () => 32 | chai.assert.isTrue( 33 | evaluateXPathToBoolean( 34 | 'false() or false() or false() or (: Note: the last true() will make te result true:) true()', 35 | ), 36 | )); 37 | 38 | it('allows not in combination with or', () => { 39 | jsonMlMapper.parse(['someOtherParentElement', ['someOtherChildElement']], documentNode); 40 | chai.assert.isTrue( 41 | evaluateXPathToBoolean( 42 | 'someChildElement or not(someOtherChild)', 43 | documentNode.documentElement, 44 | ), 45 | ); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/specs/parsing/operators/boolean/operatorPrecedence.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | import jsonMlMapper from 'test-helpers/jsonMlMapper'; 4 | 5 | import { evaluateXPathToBoolean } from 'fontoxpath'; 6 | 7 | let documentNode; 8 | beforeEach(() => { 9 | documentNode = new slimdom.Document(); 10 | }); 11 | 12 | describe('operators', () => { 13 | it('uses correct operator precedence', () => { 14 | jsonMlMapper.parse( 15 | [ 16 | 'someParentElement', 17 | ['someMiddleElement', { someAttribute: 'someValue' }, ['someOtherElement']], 18 | ], 19 | documentNode, 20 | ); 21 | chai.assert.isTrue( 22 | evaluateXPathToBoolean( 23 | "(child::someElement and ancestor::someParentElement) or @someAttribute='someValue'", 24 | documentNode.documentElement.firstChild, 25 | ), 26 | ); 27 | // The other way around 28 | chai.assert.isTrue( 29 | evaluateXPathToBoolean( 30 | "(child::someOtherElement and ancestor::someParentElement) or @someAttribute='someOtherValue'", 31 | documentNode.documentElement.firstChild, 32 | ), 33 | ); 34 | // Changes to testcase A: Operator order changed because of parentheses 35 | chai.assert.isFalse( 36 | evaluateXPathToBoolean( 37 | 'child::someElement and (ancestor::someParentElement or @someAttribute="someValue")', 38 | documentNode.documentElement.firstChild, 39 | ), 40 | ); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/specs/parsing/operators/sequenceOperator.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPathToBoolean, evaluateXPathToNumbers } from 'fontoxpath'; 5 | 6 | import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; 7 | 8 | describe('sequence', () => { 9 | it('creates a sequence', () => 10 | chai.assert.deepEqual(evaluateXPathToNumbers('(1,2,3)'), [1, 2, 3])); 11 | 12 | it('creates an empty sequence', () => chai.assert.deepEqual(evaluateXPathToNumbers('()'), [])); 13 | 14 | it('normalizes sequences', () => 15 | chai.assert.deepEqual(evaluateXPathToNumbers('(1,2,(3,4))'), [1, 2, 3, 4])); 16 | }); 17 | 18 | describe('range', () => { 19 | it('creates a sequence', () => 20 | chai.assert.deepEqual(evaluateXPathToNumbers('1 to 10'), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); 21 | 22 | it('creates an empty sequence when passed a > b', () => 23 | chai.assert.deepEqual(evaluateXPathToNumbers('10 to 1'), [])); 24 | 25 | it('creates an empty sequence when passed () to 10', () => 26 | chai.assert.deepEqual(evaluateXPathToNumbers('() to 10'), [])); 27 | 28 | it('creates a sequence of correct length', () => 29 | chai.assert.isTrue(evaluateXPathToBoolean('(1 to 10) => count() = 10'))); 30 | 31 | it('creates an empty sequence when passed 1 to ()', () => 32 | chai.assert.deepEqual(evaluateXPathToNumbers('1 to ()'), [])); 33 | }); 34 | -------------------------------------------------------------------------------- /test/specs/parsing/operators/stringConcat.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPathToString } from 'fontoxpath'; 5 | 6 | let documentNode; 7 | beforeEach(() => { 8 | documentNode = new slimdom.Document(); 9 | }); 10 | 11 | describe('stringConcat', () => { 12 | it('can concatenate strings', () => 13 | chai.assert.equal( 14 | evaluateXPathToString('"con" || "cat" || "enate"', documentNode), 15 | 'concatenate', 16 | )); 17 | 18 | it('can concatenate empty sequences', () => 19 | chai.assert.equal( 20 | evaluateXPathToString( 21 | '() || "con" || () || "cat" || () || "enate" || ()', 22 | documentNode, 23 | ), 24 | 'concatenate', 25 | )); 26 | }); 27 | -------------------------------------------------------------------------------- /test/specs/parsing/postfix/Filter.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPathToNumber, evaluateXPathToNumbers } from 'fontoxpath'; 3 | 4 | describe('Filter (predicate)', () => { 5 | it('parses', () => chai.assert.equal(evaluateXPathToNumber('(1,2,3)[. = 2]'), 2)); 6 | it('allows spaces', () => chai.assert.equal(evaluateXPathToNumber('(1,2,3) [. = 2]'), 2)); 7 | it('returns empty sequence when inputted empty sequence', () => 8 | chai.assert.isEmpty(evaluateXPathToNumbers('(1,2,3)[()]'))); 9 | it('returns the sequence when filtering with a string', () => 10 | chai.assert.deepEqual(evaluateXPathToNumbers('(1,2,3,4)["TAKE ME"]'), [1, 2, 3, 4])); 11 | it('returns the empty sequence when filtering with an empty string', () => 12 | chai.assert.isEmpty(evaluateXPathToNumbers('(1,2,3,4)[""]'))); 13 | }); 14 | -------------------------------------------------------------------------------- /test/specs/parsing/xquery-updating/DeleteExpression.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateUpdatingExpressionSync } from 'fontoxpath'; 5 | import assertUpdateList from './assertUpdateList'; 6 | 7 | let documentNode: slimdom.Document; 8 | beforeEach(() => { 9 | documentNode = new slimdom.Document(); 10 | }); 11 | 12 | describe('DeleteExpression', () => { 13 | it('merges pul from target expressions', () => { 14 | const element = documentNode.appendChild(documentNode.createElement('element')); 15 | const a = element.appendChild(documentNode.createElement('a')); 16 | 17 | const result = evaluateUpdatingExpressionSync( 18 | `delete node (/element, delete node /element/a)`, 19 | documentNode, 20 | null, 21 | {}, 22 | {}, 23 | ); 24 | 25 | chai.assert.deepEqual(result.xdmValue, []); 26 | assertUpdateList(result.pendingUpdateList, [ 27 | { 28 | type: 'delete', 29 | target: element, 30 | }, 31 | { 32 | type: 'delete', 33 | target: a, 34 | }, 35 | ]); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/specs/parsing/xquery-updating/assertUpdateList.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | export default function assertUpdateList(actual, expected) { 5 | chai.assert.equal(actual.length, expected.length, 'pendingUpdates.length'); 6 | for (let i = 0, l = expected.length; i < l; ++i) { 7 | chai.assert.equal(actual[i].type, expected[i].type); 8 | chai.assert.equal(actual[i].target, expected[i].target); 9 | 10 | switch (actual[i].type) { 11 | case 'rename': 12 | chai.assert.equal(actual[i].newName.prefix, expected[i].newName.prefix); 13 | chai.assert.equal(actual[i].newName.namespaceURI, expected[i].newName.namespaceURI); 14 | chai.assert.equal(actual[i].newName.localName, expected[i].newName.localName); 15 | break; 16 | case 'replaceNode': 17 | actual[i].replacement.forEach((replacement, j) => 18 | chai.assert.equal( 19 | new slimdom.XMLSerializer().serializeToString(replacement), 20 | expected[i].replacementXML[j], 21 | ), 22 | ); 23 | break; 24 | case 'replaceElementContent': 25 | chai.assert.equal(actual[i].text.data, expected[i].text); 26 | break; 27 | case 'replaceValue': 28 | chai.assert.equal(actual[i]['string-value'], expected[i].stringValue); 29 | break; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/specs/parsing/xquery/AttributeConstructor.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPath, evaluateXPathToFirstNode } from 'fontoxpath'; 5 | import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; 6 | 7 | let documentNode; 8 | beforeEach(() => { 9 | documentNode = new slimdom.Document(); 10 | }); 11 | 12 | describe('AttributeConstructor', () => { 13 | it('can create an attribute', () => { 14 | const attribute = evaluateXPathToFirstNode( 15 | 'attribute attr {"val"}', 16 | documentNode, 17 | undefined, 18 | {}, 19 | { language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 20 | ); 21 | 22 | chai.assert.equal(attribute.nodeType, 2); 23 | chai.assert.equal(attribute.name, 'attr'); 24 | chai.assert.equal(attribute.value, 'val'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/specs/parsing/xquery/DefaultElementNamespace.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { Document } from 'slimdom'; 3 | 4 | import { evaluateXPath, evaluateXPathToBoolean } from 'fontoxpath'; 5 | 6 | let documentNode: Document; 7 | beforeEach(() => { 8 | documentNode = new Document(); 9 | }); 10 | 11 | describe('DefaultElementNamespace', () => { 12 | it('Can create a default element namespace', () => { 13 | chai.assert.isTrue( 14 | evaluateXPathToBoolean( 15 | `declare default element namespace "http://example.com"; 16 | 17 | self::p 18 | `, 19 | documentNode.createElementNS('http://example.com', 'p'), 20 | undefined, 21 | {}, 22 | { language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 23 | ), 24 | ); 25 | 26 | chai.assert.isFalse( 27 | evaluateXPathToBoolean( 28 | `declare default element namespace "something-else"; 29 | 30 | self::p 31 | `, 32 | documentNode.createElementNS('http://example.com', 'p'), 33 | undefined, 34 | {}, 35 | { language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 36 | ), 37 | ); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/specs/parsing/xquery/PIConstructor.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as slimdom from 'slimdom'; 3 | 4 | import { evaluateXPath, evaluateXPathToFirstNode } from 'fontoxpath'; 5 | import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; 6 | 7 | let documentNode; 8 | beforeEach(() => { 9 | documentNode = new slimdom.Document(); 10 | }); 11 | 12 | describe('PIConstructor', () => { 13 | it('can create a PI', () => { 14 | chai.assert.equal( 15 | evaluateXPathToFirstNode( 16 | 'processing-instruction my-pi { "data" }', 17 | documentNode, 18 | undefined, 19 | {}, 20 | { language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 21 | ).nodeType, 22 | 7, 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/specs/parsing/xquery/TextConstructor.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { evaluateXPath, evaluateXPathToString } from 'fontoxpath'; 3 | 4 | import * as slimdom from 'slimdom'; 5 | 6 | let documentNode; 7 | beforeEach(() => { 8 | documentNode = new slimdom.Document(); 9 | }); 10 | 11 | describe('Computed Text Constructor ', () => { 12 | it('create a a computed text constructor', () => { 13 | chai.assert.equal( 14 | evaluateXPathToString( 15 | `text {"Hello"}`, 16 | documentNode, 17 | undefined, 18 | {}, 19 | { language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 20 | ), 21 | 'Hello', 22 | ); 23 | }); 24 | 25 | it('create a a computed text constructor expression', () => { 26 | chai.assert.equal( 27 | evaluateXPathToString( 28 | `text {" "}`, 29 | documentNode, 30 | undefined, 31 | {}, 32 | { language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 33 | ), 34 | '\n', 35 | ); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/specs/parsing/xquery/Typeswitch.tests.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | 3 | import { evaluateXPath, evaluateXPathToNumber, evaluateXPathToString } from 'fontoxpath'; 4 | 5 | describe('Typeswitch', () => { 6 | it('runs typeswitch and returns an integer', () => 7 | chai.assert.equal( 8 | evaluateXPathToNumber( 9 | `typeswitch((1,2)) 10 | case xs:integer return 1 11 | case xs:string+ return 42 12 | case xs:float | xs:string return 27 13 | case xs:integer* return 2828 14 | default return 2`, 15 | null, 16 | null, 17 | null, 18 | { debug: true, language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 19 | ), 20 | 2828, 21 | )); 22 | 23 | it('runs typeswitch and returns a string', () => 24 | chai.assert.equal( 25 | evaluateXPathToString( 26 | `typeswitch(("Hello", "Hi")) 27 | case xs:integer? return "Hey" 28 | case xs:string+ return "Good morning" 29 | case xs:float return "Good afternoon" 30 | case xs:integer* return "Good evening" 31 | default return "Bye"`, 32 | null, 33 | null, 34 | null, 35 | { debug: true, language: evaluateXPath.XQUERY_3_1_LANGUAGE }, 36 | ), 37 | 'Good morning', 38 | )); 39 | }); 40 | -------------------------------------------------------------------------------- /test/testhook.js: -------------------------------------------------------------------------------- 1 | const useDist = process.argv.includes('--dist'); 2 | const useJsCodegen = process.argv.includes('--jscodegen'); 3 | 4 | console.log( 5 | (useDist ? 'Running tests against dist build' : 'Running tests against development bundle') + 6 | (useJsCodegen ? ' using the JSCodegen backend where possible' : '') 7 | ); 8 | 9 | const tsConfig = require('./tsconfig.json'); 10 | 11 | const tsConfigPaths = require('tsconfig-paths'); 12 | 13 | if (useDist) { 14 | tsConfig.compilerOptions.paths.fontoxpath = ['../dist/fontoxpath.js']; 15 | } 16 | 17 | if (useJsCodegen) { 18 | tsConfig.compilerOptions.paths.fontoxpath = ['../test/testCodegenFacade.ts']; 19 | } 20 | 21 | // Make the import of 'xspattern' point to the node_modules version for unit tests 22 | tsConfig.compilerOptions.paths.xspattern = ['node_modules/xspattern/dist/xspattern.ts']; 23 | 24 | tsConfigPaths.register({ 25 | baseUrl: './test', 26 | paths: tsConfig.compilerOptions.paths, 27 | allowJs: true, 28 | include: '../dist/*', 29 | }); 30 | 31 | require('source-map-support/register'); 32 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "fontoxpath": ["../src/index.ts"], 6 | "fontoxpath/*": ["../src/*"], 7 | "test-helpers/*": ["./helpers/*"] 8 | }, 9 | "strict": false, 10 | "allowJs": true, 11 | "types": ["mocha", "node"], 12 | "moduleResolution": "node", 13 | "target": "es2020" 14 | }, 15 | "include": ["../src/**/*.ts", "./**/*.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /test/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": ["tslint:latest", "tslint-config-prettier"], 4 | "rules": { 5 | "array-type": [true, "array"], 6 | "class-name": true, 7 | "comment-format": [true, "check-space"], 8 | "interface-name": true, 9 | "interface-over-type-literal": false, 10 | "match-default-export-name": true, 11 | "no-implicit-dependencies": [true, "dev"], 12 | "no-bitwise": false, 13 | "no-implicit-dependencies": false, 14 | "no-string-literal": false, 15 | "no-submodule-imports": false, 16 | "no-unnecessary-qualifier": true, 17 | "no-unnecessary-type-assertion": true, 18 | "no-unused-expression": true, 19 | "no-var-keyword": true, 20 | "member-ordering": [ 21 | true, 22 | { 23 | "alphabetize": true, 24 | "order": "fields-first" 25 | } 26 | ], 27 | "ordered-imports": true, 28 | "prefer-for-of": false, 29 | "semicolon": [true, "always", "ignore-bound-class-methods"], 30 | "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "lib": ["es6", "es2017", "esnext.asynciterable"], 6 | "module": "commonjs", 7 | "noImplicitAny": true, 8 | "outDir": "./built", 9 | "strict": false, 10 | "target": "es2017" 11 | }, 12 | "include": ["./src/**/*"], 13 | "exclude": ["./**/*.d.ts"] 14 | } 15 | --------------------------------------------------------------------------------