Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions demo/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ async function runNormalXPath(script: string, asXQuery: boolean) {
debug: true,
disableCache: true,
language: asXQuery
? fontoxpath.evaluateXPath.XQUERY_3_1_LANGUAGE
: fontoxpath.evaluateXPath.XPATH_3_1_LANGUAGE,
? fontoxpath.evaluateXPath.XQUERY_4_0_LANGUAGE
: fontoxpath.evaluateXPath.XPATH_4_0_LANGUAGE,
logger: {
trace: (m) => {
traceOutput.textContent = m;
Expand Down Expand Up @@ -227,9 +227,9 @@ async function runXPathWithJsCodegen(xpath: string, asXQuery: boolean) {
getReturnTypeFromChoice(codegenReturnTypeChoice.value),
{
language: asXQuery
? fontoxpath.evaluateXPath.XQUERY_3_1_LANGUAGE
: fontoxpath.evaluateXPath.XPATH_3_1_LANGUAGE,
}
? fontoxpath.evaluateXPath.XQUERY_4_0_LANGUAGE
: fontoxpath.evaluateXPath.XPATH_4_0_LANGUAGE,
},
);

if (compiledXPathResult.isAstAccepted === true) {
Expand Down Expand Up @@ -267,7 +267,7 @@ async function rerunXPath() {
const ast = fontoxpath.parseScript<Element>(
xpath,
{
language: fontoxpath.evaluateXPath.XQUERY_3_1_LANGUAGE,
language: fontoxpath.evaluateXPath.XQUERY_4_0_LANGUAGE,
debug: true,
},
document
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
"build": "node ./build.js",
"ci-qt3tests": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha --paths --project test/qt3tests.ts",
"ci-qt3testsxqueryx": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha --paths --project test/qt3testsXQueryX.ts",
"ci-test": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha \"test/specs/**/*.ts\" --parallel",
"ci-test-jscodegen": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha \"test/specs/**/*.ts\" --parallel -- --jscodegen",
"ci-test": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha \"test/specs/**/*.ts\"",
"ci-test-jscodegen": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha \"test/specs/**/*.ts\" -- --jscodegen",
"ci-xqutstests": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha test/xqutsTests.ts",
"ci-xqutstestsxqueryx": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha --paths --parallel test/xqutsTestsXQueryX.ts",
"ci-xqutstestsxqueryx": "cross-env TS_NODE_PROJECT=test/tsconfig.json nyc --no-clean --require ts-node/register --require tsconfig-paths/register mocha --paths test/xqutsTestsXQueryX.ts",
"coverage": "nyc report --reporter=text-lcov",
"integrationtests": "ts-mocha --require test/testhook.js \"test/specs/parsing/**/*.ts\" --parallel",
"integrationtests": "ts-mocha --require test/testhook.js \"test/specs/parsing/**/*.ts\"",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this intentional or removed for debugging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. the parallel hides easy to fix errors behind confusing errors. It's CI, so a bit slower is fine

"prepare": "npm run build",
"qt3performance": "ts-node --project test/tsconfig.json -r tsconfig-paths/register test/qt3testsBenchmark.ts",
"qt3tests": "ts-mocha --paths --project test/tsconfig.json test/qt3tests.ts",
Expand Down
3 changes: 3 additions & 0 deletions src/evaluateUpdatingExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { Language, Logger, XMLSerializer } from './types/Options';
* nodesFactory - Reference to a nodes factory object.
* returnType - The type that the evaluation function will return.
* xmlSerializer - An XML serializer that can serialize nodes. Used when the `fn:serialize` function is called with a node.
* language - Whether to allow experimental XQuery 4 features in the script
*/
export type UpdatingOptions = {
debug?: boolean;
Expand All @@ -41,6 +42,7 @@ export type UpdatingOptions = {
nodesFactory?: INodesFactory;
returnType?: ReturnType;
xmlSerializer?: XMLSerializer;
language?: Language.XQUERY_UPDATE_3_1_LANGUAGE | Language.XQUERY_UPDATE_4_0_LANGUAGE;
};

/**
Expand Down Expand Up @@ -82,6 +84,7 @@ export default async function evaluateUpdatingExpression(
allowXQuery: true,
debug: !!options['debug'],
disableCache: !!options['disableCache'],
version: options.language === Language.XQUERY_UPDATE_4_0_LANGUAGE ? 4 : 3.1,
},
);
dynamicContext = context.dynamicContext;
Expand Down
2 changes: 2 additions & 0 deletions src/evaluateUpdatingExpressionSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import UpdatingExpressionResult from './expressions/UpdatingExpressionResult';
import { IterationHint, IterationResult } from './expressions/util/iterators';
import { IReturnTypes, ReturnType } from './parsing/convertXDMReturnValue';
import { performStaticCompilationOnModules } from './parsing/globalModuleCache';
import { Language } from './types/Options';
import { Node } from './types/Types';

/**
Expand Down Expand Up @@ -57,6 +58,7 @@ export default function evaluateUpdatingExpressionSync<
allowXQuery: true,
debug: !!options['debug'],
disableCache: !!options['disableCache'],
version: options.language === Language.XQUERY_UPDATE_4_0_LANGUAGE ? 4 : 3.1,
},
);
dynamicContext = context.dynamicContext;
Expand Down
8 changes: 8 additions & 0 deletions src/evaluateXPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ const evaluateXPath = <TNode extends Node, TReturnType extends keyof IReturnType
options['language'] === Language.XQUERY_UPDATE_3_1_LANGUAGE,
debug: !!options['debug'],
disableCache: !!options['disableCache'],
version:
options['language'] === Language.XPATH_4_0_LANGUAGE ||
options['language'] === Language.XQUERY_4_0_LANGUAGE ||
options['language'] === Language.XQUERY_UPDATE_4_0_LANGUAGE
? 4
: 3.1,
},
);
dynamicContext = context.dynamicContext;
Expand Down Expand Up @@ -264,6 +270,8 @@ Object.assign(evaluateXPath, {
['XPATH_3_1_LANGUAGE']: Language.XPATH_3_1_LANGUAGE,
['XQUERY_3_1_LANGUAGE']: Language.XQUERY_3_1_LANGUAGE,
['XQUERY_UPDATE_3_1_LANGUAGE']: Language.XQUERY_UPDATE_3_1_LANGUAGE,
['XPATH_4_0_LANGUAGE']: Language.XPATH_4_0_LANGUAGE,
['XQUERY_4_0_LANGUAGE']: Language.XQUERY_4_0_LANGUAGE,
});

export default evaluateXPath as EvaluateXPath;
1 change: 1 addition & 0 deletions src/evaluationUtils/buildEvaluationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export default function buildEvaluationContext(
allowXQuery: boolean;
debug: boolean;
disableCache: boolean;
version: 3.1 | 4;
},
): {
dynamicContext: DynamicContext;
Expand Down
1 change: 1 addition & 0 deletions src/expressions/functions/builtInFunctions_fontoxpath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ function buildResultIterator(
allowXQuery: true,
debug: executionParameters.debug,
disableCache: executionParameters.disableCache,
version: 3.1,
},
(prefix) => staticContext.resolveNamespace(prefix),
// Set up temporary bindings for the given variables
Expand Down
13 changes: 5 additions & 8 deletions src/expressions/operators/boolean/OrOperator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import DynamicContext from '../../DynamicContext';
import ExecutionParameters from '../../ExecutionParameters';
import Expression from '../../Expression';
import Specificity from '../../Specificity';
import { Bucket } from '../../util/Bucket';
import { Bucket, unionBucket } from '../../util/Bucket';
import { DONE_TOKEN, ready } from '../../util/iterators';

class OrOperator extends Expression {
Expand All @@ -35,21 +35,18 @@ class OrOperator extends Expression {
type,
);

// If all subExpressions define the same bucket: use that one, else, use no bucket.
let bucket: Bucket | null;
for (let i = 0; i < expressions.length; ++i) {
const subTestBucket = expressions[i].getBucket();
if (bucket === undefined) {
bucket = expressions[i].getBucket();
bucket = subTestBucket;
}
if (bucket === null) {
// Not applicable buckets
// No applicable buckets
break;
}

if (bucket !== expressions[i].getBucket()) {
bucket = null;
break;
}
bucket = unionBucket(bucket, subTestBucket);
}
this._bucket = bucket;

Expand Down
64 changes: 64 additions & 0 deletions src/expressions/tests/UnionNodeTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Value from '../dataTypes/Value';
import DynamicContext from '../DynamicContext';
import ExecutionParameters from '../ExecutionParameters';
import Specificity from '../Specificity';
import StaticContext from '../StaticContext';
import { Bucket, unionBucket } from '../util/Bucket';
import TestAbstractExpression from './TestAbstractExpression';

/**
* Union node test, used to process `descendant::(a|b|c)` selectors
*/
class UnionNodeTest extends TestAbstractExpression {
private _subTests: TestAbstractExpression[];
private _bucket: Bucket;
constructor(subTests: TestAbstractExpression[]) {
const maxSpecificity = subTests.reduce<Specificity>((currentMaxSpecificity, selector) => {
if (currentMaxSpecificity.compareTo(selector.specificity) > 0) {
return currentMaxSpecificity;
}
return selector.specificity;
}, new Specificity({}));

super(maxSpecificity);

// If all subExpressions define the same bucket: use that one, else, use no bucket.
let bucket: Bucket | null;
for (let i = 0; i < subTests.length; ++i) {
const subTestBucket = subTests[i].getBucket();
if (bucket === undefined) {
bucket = subTestBucket;
}
if (bucket === null) {
// Not applicable buckets
break;
}

bucket = unionBucket(bucket, subTestBucket);
}

this._bucket = bucket;
this._subTests = subTests;
}

public evaluateToBoolean(
dynamicContext: DynamicContext,
value: Value,
executionParameters: ExecutionParameters,
) {
return this._subTests.some((test) => {
const result = test.evaluateToBoolean(dynamicContext, value, executionParameters);
return result;
});
}

public override getBucket(): Bucket {
return this._bucket;
}

public performStaticEvaluation(staticContext: StaticContext) {
this._subTests.forEach((test) => test.performStaticEvaluation(staticContext));
}
}

export default UnionNodeTest;
59 changes: 59 additions & 0 deletions src/expressions/util/Bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ const subBucketsByBucket: Map<Bucket, Bucket[]> = new Map([
['type-2', ['name']],
]);

// Some buckets exclude others. For the purppose of determining their unions, this lists supertypes pet bucket.
const superBucketsByBucket: Map<Bucket, Bucket> = new Map([
['name', 'type-1-or-type-2'],
['type-1', 'type-1-or-type-2'],
['type-2', 'type-1-or-type-2'],
]);

/**
* Determine the intersection between the two passed buckets. The intersection is the 'strongest'
* bucket of the two. This may return `empty` if there is no such intersection.
Expand Down Expand Up @@ -78,3 +85,55 @@ export function intersectBuckets(bucket1: Bucket | null, bucket2: Bucket | null)
// Expression will never match any nodes
return 'empty';
}

/**
* Determine the union between the two passed buckets. The union is the common denominator between buckets
*
* Example: `null` ∪ `name-div` = `null`
* Example: `name-p` ∪ `name-div` = `type-1-or-type-2`
* Example: `type-1` ∪ `name-p` = `type-1`
* Example: `type-1` ∪ `empty` = `type-1`
* Example: `type-1` ∪ `type-7` = `null`
*
* @param bucket1 - The first bucket to check
* @param bucket2 - The second bucket to check
*
* @returns The union of the two buckets.
*/
export function unionBucket(bucket1: Bucket | null, bucket2: Bucket | null): Bucket | null {
// null bucket applies to everything
if (bucket1 === null || bucket2 === null) {
return null;
}
if (bucket1 === 'empty') {
return bucket2;
}
if (bucket2 === 'empty') {
return bucket1;
}
// Same bucket is same
if (bucket1 === bucket2) {
return bucket1;
}
// Find the more specific one, given that the buckets are not equal
const type1 = bucket1.startsWith('name-') ? 'name' : bucket1;
const type2 = bucket2.startsWith('name-') ? 'name' : bucket2;

const supertypes1 = superBucketsByBucket.get(type1);
if (supertypes1 !== undefined && supertypes1 === type2) {
// bucket 2 includes bucket 1
return supertypes1;
}
const supertypes2 = superBucketsByBucket.get(type2);
if (supertypes2 !== undefined && supertypes2 === type1) {
// bucket 1 includes bucket 2
return supertypes2;
}

if (supertypes1 === supertypes2) {
return supertypes1;
}

// Expression match a lot. More than can be captured in buckets
return null;
}
47 changes: 47 additions & 0 deletions src/expressions/xpath-40/Otherwise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import ISequence from '../dataTypes/ISequence';
import sequenceFactory from '../dataTypes/sequenceFactory';
import { SequenceType, ValueType } from '../dataTypes/Value';
import DynamicContext from '../DynamicContext';
import ExecutionParameters from '../ExecutionParameters';
import Expression, { RESULT_ORDERINGS } from '../Expression';
import PossiblyUpdatingExpression, { SequenceCallbacks } from '../PossiblyUpdatingExpression';
import Specificity from '../Specificity';

/**
* The 'otherwise' expression: returns the first operand that's not empty
*/
class Otherwise extends PossiblyUpdatingExpression {
constructor(expressions: Expression[], type: SequenceType) {
const maxSpecificity = expressions.reduce((maxSpecificitySoFar, expression) => {
if (maxSpecificitySoFar.compareTo(expression.specificity) > 0) {
return maxSpecificitySoFar;
}
return expression.specificity;
}, new Specificity({}));
super(
maxSpecificity,
expressions,
{
canBeStaticallyEvaluated: expressions.every(
(expression) => expression.canBeStaticallyEvaluated,
),
},
type,
);
}

public override performFunctionalEvaluation(
dynamicContext: DynamicContext,
_executionParameters: ExecutionParameters,
sequenceCallbacks: SequenceCallbacks,
): ISequence {
for (const cb of sequenceCallbacks) {
const sequence = cb(dynamicContext);
if (!sequence.isEmpty()) {
return sequence;
}
}
return sequenceFactory.empty();
}
}
export default Otherwise;
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function parseXPath(xpathExpression: EvaluableExpression): Expression {

const ast =
typeof xpathExpression === 'string'
? parseExpression(xpathExpression, { allowXQuery: false })
? parseExpression(xpathExpression, { allowXQuery: false, version: 4 })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this depend on the options?

In that case we'd probably also need separate caches for the 3.1 vs 4 expressions.

: // AST is an element: convert to jsonml
convertXmlToAst(xpathExpression);

Expand Down
11 changes: 9 additions & 2 deletions src/jsCodegen/compileXPathToJavaScript.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { version } from 'chai';
import { EvaluableExpression } from '../evaluateXPath';
import {
createDefaultFunctionNameResolver,
Expand All @@ -11,7 +12,7 @@ import astHelper from '../parsing/astHelper';
import { ReturnType } from '../parsing/convertXDMReturnValue';
import convertXmlToAst from '../parsing/convertXmlToAst';
import normalizeEndOfLines from '../parsing/normalizeEndOfLines';
import parseExpression from '../parsing/parseExpression';
import parseExpression, { CompilationOptions } from '../parsing/parseExpression';
import annotateAst from '../typeInference/annotateAST';
import { AnnotationContext } from '../typeInference/AnnotationContext';
import { Language, Options } from '../types/Options';
Expand Down Expand Up @@ -42,13 +43,19 @@ function compileXPathToJavaScript(
let ast;
if (typeof selector === 'string') {
const expressionString = normalizeEndOfLines(selector);
const parserOptions = {
const parserOptions: CompilationOptions = {
allowXQuery:
options['language'] === Language.XQUERY_3_1_LANGUAGE ||
options['language'] === Language.XQUERY_UPDATE_3_1_LANGUAGE,
// Debugging inserts xs:stackTrace in the AST, but this is not supported
// yet by the js-codegen backend.
debug: false,
version:
options['language'] === Language.XPATH_4_0_LANGUAGE ||
options['language'] === Language.XQUERY_4_0_LANGUAGE ||
options['language'] === Language.XQUERY_UPDATE_4_0_LANGUAGE
? 4
: 3.1,
};
try {
ast = parseExpression(expressionString, parserOptions);
Expand Down
Loading
Loading