Skip to content

Commit 1236cd2

Browse files
committed
[XPath] Strict Error Handling for Casts and Functions
- add xs:decimal and properly validate input strings to all number parsers - correctly implement differences between xs:boolean and fn:boolean (EBV semantics) 16406 72.9% successes 0 0.0% skipped 3839 17.1% failures 2269 10.1% errors
1 parent 35a7b04 commit 1236cd2

File tree

16 files changed

+244
-91
lines changed

16 files changed

+244
-91
lines changed

lib/src/xpath/evaluation/types.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const basicTypes = <XPathType<Object>>[
2626
xsBoolean,
2727
xsByte,
2828
xsDateTime,
29+
xsDecimal,
2930
xsDouble,
3031
xsDuration,
3132
xsEmptySequence,

lib/src/xpath/expressions/predicate.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'package:meta/meta.dart';
22

33
import '../evaluation/context.dart';
44
import '../evaluation/expression.dart';
5-
import '../types/boolean.dart';
65
import '../types/number.dart';
76
import '../types/sequence.dart';
87

@@ -15,9 +14,7 @@ class Predicate {
1514
bool matches(XPathContext context) {
1615
final value = expression(context);
1716
final item = value.singleOrNull;
18-
return item is num
19-
? xsInteger.cast(item) == context.position
20-
: xsBoolean.cast(value);
17+
return item is num ? xsInteger.cast(item) == context.position : value.ebv;
2118
}
2219
}
2320

lib/src/xpath/expressions/statement.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import '../evaluation/context.dart';
22
import '../evaluation/expression.dart';
3-
import '../types/boolean.dart';
43
import '../types/sequence.dart';
54

65
typedef XPathBinding = ({String name, XPathExpression expression});
@@ -70,7 +69,7 @@ class SomeExpression implements XPathExpression {
7069
}
7170
return false;
7271
} else {
73-
return xsBoolean.cast(body(currentContext));
72+
return body(currentContext).ebv;
7473
}
7574
}
7675

@@ -102,7 +101,7 @@ class EveryExpression implements XPathExpression {
102101
}
103102
return true;
104103
} else {
105-
return xsBoolean.cast(body(currentContext));
104+
return body(currentContext).ebv;
106105
}
107106
}
108107

@@ -120,7 +119,7 @@ class IfExpression implements XPathExpression {
120119
final XPathExpression falseExpression;
121120

122121
@override
123-
XPathSequence call(XPathContext context) => xsBoolean.cast(condition(context))
122+
XPathSequence call(XPathContext context) => condition(context).ebv
124123
? trueExpression(context)
125124
: falseExpression(context);
126125
}

lib/src/xpath/functions/array.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import '../exceptions/evaluation_exception.dart';
66
import '../operators/comparison.dart';
77
import '../types/any.dart';
88
import '../types/array.dart';
9-
import '../types/boolean.dart';
109
import '../types/function.dart';
1110
import '../types/number.dart';
1211
import '../types/sequence.dart';
@@ -316,9 +315,7 @@ XPathSequence _fnArrayFilter(
316315
final result = <Object>[];
317316
for (final item in array) {
318317
final value = predicate(context, [xsSequence.cast(item)]);
319-
if (xsBoolean.cast(value)) {
320-
result.add(item);
321-
}
318+
if (value.ebv) result.add(item);
322319
}
323320
return XPathSequence.single(result);
324321
}

lib/src/xpath/functions/boolean.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import '../definitions/cardinality.dart';
66
import '../definitions/function.dart';
77
import '../evaluation/context.dart';
88
import '../types/any.dart';
9-
import '../types/boolean.dart';
109
import '../types/node.dart';
1110
import '../types/sequence.dart';
1211
import '../types/string.dart';
@@ -25,7 +24,7 @@ const fnBoolean = XPathFunctionDefinition(
2524
);
2625

2726
XPathSequence _fnBoolean(XPathContext context, XPathSequence arg) =>
28-
XPathSequence.single(xsBoolean.cast(arg));
27+
XPathSequence.single(arg.ebv);
2928

3029
/// https://www.w3.org/TR/xpath-functions-31/#func-not
3130
const fnNot = XPathFunctionDefinition(
@@ -41,7 +40,7 @@ const fnNot = XPathFunctionDefinition(
4140
);
4241

4342
XPathSequence _fnNot(XPathContext context, XPathSequence arg) =>
44-
XPathSequence.single(!xsBoolean.cast(arg));
43+
XPathSequence.single(!arg.ebv);
4544

4645
/// https://www.w3.org/TR/xpath-functions-31/#func-true
4746
const fnTrue = XPathFunctionDefinition(

lib/src/xpath/functions/constructors.dart

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,20 @@ XPathSequence _xsStringConstructor(XPathContext context, [Object? value]) {
3333
/// https://www.w3.org/TR/xpath-functions-31/#func-boolean
3434
const xsBooleanConstructor = XPathFunctionDefinition(
3535
name: XmlName.qualified('xs:boolean'),
36-
requiredArguments: [
36+
optionalArguments: [
3737
XPathArgumentDefinition(
3838
name: 'value',
39-
type: xsSequence,
40-
cardinality: XPathCardinality.zeroOrMore,
39+
type: xsAny,
40+
cardinality: XPathCardinality.zeroOrOne,
4141
),
4242
],
4343
function: _xsBooleanConstructor,
4444
);
4545

46-
XPathSequence _xsBooleanConstructor(
47-
XPathContext context,
48-
XPathSequence value,
49-
) => XPathSequence.single(xsBoolean.cast(value));
46+
XPathSequence _xsBooleanConstructor(XPathContext context, [Object? value]) {
47+
if (value == null) return XPathSequence.empty;
48+
return XPathSequence.single(xsBoolean.cast(value));
49+
}
5050

5151
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
5252
const xsIntegerConstructor = XPathFunctionDefinition(
@@ -78,7 +78,7 @@ const xsDecimalConstructor = XPathFunctionDefinition(
7878
);
7979

8080
XPathSequence _xsDecimalConstructor(XPathContext context, Object value) =>
81-
XPathSequence.single(xsNumeric.cast(value));
81+
XPathSequence.single(xsDecimal.cast(value));
8282

8383
/// https://www.w3.org/TR/xpath-functions-31/#func-double
8484
const xsDoubleConstructor = XPathFunctionDefinition(
@@ -138,9 +138,12 @@ const xsByteConstructor = XPathFunctionDefinition(
138138
cardinality: XPathCardinality.exactlyOne,
139139
),
140140
],
141-
function: _xsIntegerConstructor,
141+
function: _xsByteConstructor,
142142
);
143143

144+
XPathSequence _xsByteConstructor(XPathContext context, Object value) =>
145+
XPathSequence.single(xsByte.cast(value));
146+
144147
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
145148
const xsIntConstructor = XPathFunctionDefinition(
146149
name: XmlName.qualified('xs:int'),
@@ -151,9 +154,12 @@ const xsIntConstructor = XPathFunctionDefinition(
151154
cardinality: XPathCardinality.exactlyOne,
152155
),
153156
],
154-
function: _xsIntegerConstructor,
157+
function: _xsIntConstructor,
155158
);
156159

160+
XPathSequence _xsIntConstructor(XPathContext context, Object value) =>
161+
XPathSequence.single(xsInt.cast(value));
162+
157163
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
158164
const xsLongConstructor = XPathFunctionDefinition(
159165
name: XmlName.qualified('xs:long'),
@@ -164,9 +170,12 @@ const xsLongConstructor = XPathFunctionDefinition(
164170
cardinality: XPathCardinality.exactlyOne,
165171
),
166172
],
167-
function: _xsIntegerConstructor,
173+
function: _xsLongConstructor,
168174
);
169175

176+
XPathSequence _xsLongConstructor(XPathContext context, Object value) =>
177+
XPathSequence.single(xsLong.cast(value));
178+
170179
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
171180
const xsNegativeIntegerConstructor = XPathFunctionDefinition(
172181
name: XmlName.qualified('xs:negativeInteger'),
@@ -177,9 +186,14 @@ const xsNegativeIntegerConstructor = XPathFunctionDefinition(
177186
cardinality: XPathCardinality.exactlyOne,
178187
),
179188
],
180-
function: _xsIntegerConstructor,
189+
function: _xsNegativeIntegerConstructor,
181190
);
182191

192+
XPathSequence _xsNegativeIntegerConstructor(
193+
XPathContext context,
194+
Object value,
195+
) => XPathSequence.single(xsNegativeInteger.cast(value));
196+
183197
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
184198
const xsNonNegativeIntegerConstructor = XPathFunctionDefinition(
185199
name: XmlName.qualified('xs:nonNegativeInteger'),
@@ -190,9 +204,14 @@ const xsNonNegativeIntegerConstructor = XPathFunctionDefinition(
190204
cardinality: XPathCardinality.exactlyOne,
191205
),
192206
],
193-
function: _xsIntegerConstructor,
207+
function: _xsNonNegativeIntegerConstructor,
194208
);
195209

210+
XPathSequence _xsNonNegativeIntegerConstructor(
211+
XPathContext context,
212+
Object value,
213+
) => XPathSequence.single(xsNonNegativeInteger.cast(value));
214+
196215
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
197216
const xsNonPositiveIntegerConstructor = XPathFunctionDefinition(
198217
name: XmlName.qualified('xs:nonPositiveInteger'),
@@ -203,9 +222,14 @@ const xsNonPositiveIntegerConstructor = XPathFunctionDefinition(
203222
cardinality: XPathCardinality.exactlyOne,
204223
),
205224
],
206-
function: _xsIntegerConstructor,
225+
function: _xsNonPositiveIntegerConstructor,
207226
);
208227

228+
XPathSequence _xsNonPositiveIntegerConstructor(
229+
XPathContext context,
230+
Object value,
231+
) => XPathSequence.single(xsNonPositiveInteger.cast(value));
232+
209233
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
210234
const xsPositiveIntegerConstructor = XPathFunctionDefinition(
211235
name: XmlName.qualified('xs:positiveInteger'),
@@ -216,9 +240,14 @@ const xsPositiveIntegerConstructor = XPathFunctionDefinition(
216240
cardinality: XPathCardinality.exactlyOne,
217241
),
218242
],
219-
function: _xsIntegerConstructor,
243+
function: _xsPositiveIntegerConstructor,
220244
);
221245

246+
XPathSequence _xsPositiveIntegerConstructor(
247+
XPathContext context,
248+
Object value,
249+
) => XPathSequence.single(xsPositiveInteger.cast(value));
250+
222251
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
223252
const xsShortConstructor = XPathFunctionDefinition(
224253
name: XmlName.qualified('xs:short'),
@@ -229,9 +258,12 @@ const xsShortConstructor = XPathFunctionDefinition(
229258
cardinality: XPathCardinality.exactlyOne,
230259
),
231260
],
232-
function: _xsIntegerConstructor,
261+
function: _xsShortConstructor,
233262
);
234263

264+
XPathSequence _xsShortConstructor(XPathContext context, Object value) =>
265+
XPathSequence.single(xsShort.cast(value));
266+
235267
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
236268
const xsUnsignedByteConstructor = XPathFunctionDefinition(
237269
name: XmlName.qualified('xs:unsignedByte'),
@@ -242,9 +274,12 @@ const xsUnsignedByteConstructor = XPathFunctionDefinition(
242274
cardinality: XPathCardinality.exactlyOne,
243275
),
244276
],
245-
function: _xsIntegerConstructor,
277+
function: _xsUnsignedByteConstructor,
246278
);
247279

280+
XPathSequence _xsUnsignedByteConstructor(XPathContext context, Object value) =>
281+
XPathSequence.single(xsUnsignedByte.cast(value));
282+
248283
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
249284
const xsUnsignedIntConstructor = XPathFunctionDefinition(
250285
name: XmlName.qualified('xs:unsignedInt'),
@@ -255,9 +290,12 @@ const xsUnsignedIntConstructor = XPathFunctionDefinition(
255290
cardinality: XPathCardinality.exactlyOne,
256291
),
257292
],
258-
function: _xsIntegerConstructor,
293+
function: _xsUnsignedIntConstructor,
259294
);
260295

296+
XPathSequence _xsUnsignedIntConstructor(XPathContext context, Object value) =>
297+
XPathSequence.single(xsUnsignedInt.cast(value));
298+
261299
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
262300
const xsUnsignedLongConstructor = XPathFunctionDefinition(
263301
name: XmlName.qualified('xs:unsignedLong'),
@@ -268,9 +306,12 @@ const xsUnsignedLongConstructor = XPathFunctionDefinition(
268306
cardinality: XPathCardinality.exactlyOne,
269307
),
270308
],
271-
function: _xsIntegerConstructor,
309+
function: _xsUnsignedLongConstructor,
272310
);
273311

312+
XPathSequence _xsUnsignedLongConstructor(XPathContext context, Object value) =>
313+
XPathSequence.single(xsUnsignedLong.cast(value));
314+
274315
/// https://www.w3.org/TR/xpath-functions-31/#func-integer
275316
const xsUnsignedShortConstructor = XPathFunctionDefinition(
276317
name: XmlName.qualified('xs:unsignedShort'),
@@ -281,9 +322,12 @@ const xsUnsignedShortConstructor = XPathFunctionDefinition(
281322
cardinality: XPathCardinality.exactlyOne,
282323
),
283324
],
284-
function: _xsIntegerConstructor,
325+
function: _xsUnsignedShortConstructor,
285326
);
286327

328+
XPathSequence _xsUnsignedShortConstructor(XPathContext context, Object value) =>
329+
XPathSequence.single(xsUnsignedShort.cast(value));
330+
287331
/// https://www.w3.org/TR/xpath-functions-31/#func-date
288332
const xsDateConstructor = XPathFunctionDefinition(
289333
name: XmlName.qualified('xs:date'),

lib/src/xpath/functions/higher_order.dart

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import '../exceptions/evaluation_exception.dart';
66
import '../operators/comparison.dart';
77
import '../types/any.dart';
88
import '../types/array.dart';
9-
import '../types/boolean.dart';
109
import '../types/function.dart';
1110
import '../types/number.dart';
1211
import '../types/qname.dart';
@@ -69,12 +68,8 @@ Iterable<Object> _fnFilterSync(
6968
XPathFunction predicate,
7069
) sync* {
7170
for (final item in seq) {
72-
final result = xsBoolean.cast(
73-
predicate(context, [XPathSequence.single(item)]),
74-
);
75-
if (result) {
76-
yield item;
77-
}
71+
final result = predicate(context, [XPathSequence.single(item)]);
72+
if (result.ebv) yield item;
7873
}
7974
}
8075

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
import '../types/boolean.dart';
21
import '../types/sequence.dart';
32

43
/// https://www.w3.org/TR/xpath-functions-31/#func-boolean-equal
54
XPathSequence opBooleanEqual(XPathSequence left, XPathSequence right) =>
6-
XPathSequence.single(xsBoolean.cast(left) == xsBoolean.cast(right));
5+
XPathSequence.single(left.ebv == right.ebv);
76

87
/// https://www.w3.org/TR/xpath-functions-31/#func-boolean-less-than
98
XPathSequence opBooleanLessThan(XPathSequence left, XPathSequence right) =>
10-
XPathSequence.single(
11-
(xsBoolean.cast(left) ? 1 : 0) < (xsBoolean.cast(right) ? 1 : 0),
12-
);
9+
XPathSequence.single((left.ebv ? 1 : 0) < (right.ebv ? 1 : 0));
1310

1411
/// https://www.w3.org/TR/xpath-functions-31/#func-boolean-greater-than
1512
XPathSequence opBooleanGreaterThan(XPathSequence left, XPathSequence right) =>
16-
XPathSequence.single(
17-
(xsBoolean.cast(left) ? 1 : 0) > (xsBoolean.cast(right) ? 1 : 0),
18-
);
13+
XPathSequence.single((left.ebv ? 1 : 0) > (right.ebv ? 1 : 0));

lib/src/xpath/operators/comparison.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ int compare(Object a, Object b) {
7878
} else if (xsString.matches(a) && xsString.matches(b)) {
7979
return xsString.cast(a).compareTo(xsString.cast(b));
8080
} else if (xsBoolean.matches(a) && xsBoolean.matches(b)) {
81-
final ba = xsBoolean.cast(a);
82-
final bb = xsBoolean.cast(b);
81+
final ba = a as bool;
82+
final bb = b as bool;
8383
return ba == bb
8484
? 0
8585
: ba

lib/src/xpath/operators/general.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import '../../xml/nodes/node.dart';
2-
import '../types/boolean.dart';
32
import '../types/sequence.dart';
43
import '../types/string.dart';
54

65
/// https://www.w3.org/TR/xpath-31/#id-logical-expressions
76
XPathSequence opAnd(XPathSequence left, XPathSequence right) =>
8-
XPathSequence.single(xsBoolean.cast(left) && xsBoolean.cast(right));
7+
XPathSequence.single(left.ebv && right.ebv);
98

109
/// https://www.w3.org/TR/xpath-31/#id-logical-expressions
1110
XPathSequence opOr(XPathSequence left, XPathSequence right) =>
12-
XPathSequence.single(xsBoolean.cast(left) || xsBoolean.cast(right));
11+
XPathSequence.single(left.ebv || right.ebv);
1312

1413
/// https://www.w3.org/TR/xpath-31/#id-general-comparisons
1514
XPathSequence opGeneralEqual(XPathSequence left, XPathSequence right) =>

0 commit comments

Comments
 (0)