@getodk/xforms-engine 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/InputNode.d.ts +4 -2
- package/dist/client/NoteNode.d.ts +4 -2
- package/dist/index.js +14239 -28220
- package/dist/index.js.map +1 -1
- package/dist/integration/xpath/adapter/traversal.d.ts +2 -2
- package/dist/lib/codecs/DateValueCodec.d.ts +7 -0
- package/dist/lib/codecs/getSharedValueCodec.d.ts +3 -2
- package/dist/parse/text/abstract/TextElementDefinition.d.ts +1 -1
- package/dist/solid.js +14239 -28220
- package/dist/solid.js.map +1 -1
- package/package.json +4 -3
- package/src/client/InputNode.ts +4 -0
- package/src/client/NoteNode.ts +4 -0
- package/src/integration/xpath/adapter/traversal.ts +4 -2
- package/src/lib/codecs/DateValueCodec.ts +94 -0
- package/src/lib/codecs/getSharedValueCodec.ts +5 -3
- package/src/parse/text/abstract/TextElementDefinition.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getodk/xforms-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "XForms engine for ODK Web Forms",
|
|
6
6
|
"type": "module",
|
|
@@ -56,12 +56,13 @@
|
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"bin-packer": "1.7.0",
|
|
58
58
|
"papaparse": "^5.5.2",
|
|
59
|
-
"solid-js": "^1.9.5"
|
|
59
|
+
"solid-js": "^1.9.5",
|
|
60
|
+
"temporal-polyfill": "^0.3.0"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@babel/core": "^7.26.10",
|
|
63
64
|
"@getodk/tree-sitter-xpath": "0.1.3",
|
|
64
|
-
"@getodk/xpath": "0.
|
|
65
|
+
"@getodk/xpath": "0.5.0",
|
|
65
66
|
"@playwright/test": "^1.49.1",
|
|
66
67
|
"@types/papaparse": "^5.3.15",
|
|
67
68
|
"@vitest/browser": "^3.0.9",
|
package/src/client/InputNode.ts
CHANGED
|
@@ -84,11 +84,13 @@ export interface InputNode<V extends ValueType = ValueType>
|
|
|
84
84
|
export type StringInputValue = InputValue<'string'>;
|
|
85
85
|
export type IntInputValue = InputValue<'int'>;
|
|
86
86
|
export type DecimalInputValue = InputValue<'decimal'>;
|
|
87
|
+
export type DateInputValue = InputValue<'date'>;
|
|
87
88
|
export type GeopointInputValue = InputValue<'geopoint'>;
|
|
88
89
|
|
|
89
90
|
export type StringInputNode = InputNode<'string'>;
|
|
90
91
|
export type IntInputNode = InputNode<'int'>;
|
|
91
92
|
export type DecimalInputNode = InputNode<'decimal'>;
|
|
93
|
+
export type DateInputNode = InputNode<'date'>;
|
|
92
94
|
export type GeopointInputNode = InputNode<'geopoint'>;
|
|
93
95
|
|
|
94
96
|
// prettier-ignore
|
|
@@ -97,6 +99,7 @@ type SupportedInputValueType =
|
|
|
97
99
|
| 'string'
|
|
98
100
|
| 'int'
|
|
99
101
|
| 'decimal'
|
|
102
|
+
| 'date'
|
|
100
103
|
| 'geopoint';
|
|
101
104
|
|
|
102
105
|
type TemporaryStringValueType = Exclude<ValueType, SupportedInputValueType>;
|
|
@@ -109,5 +112,6 @@ export type AnyInputNode =
|
|
|
109
112
|
| StringInputNode
|
|
110
113
|
| IntInputNode
|
|
111
114
|
| DecimalInputNode
|
|
115
|
+
| DateInputNode
|
|
112
116
|
| GeopointInputNode
|
|
113
117
|
| TemporaryStringValueInputNode;
|
package/src/client/NoteNode.ts
CHANGED
|
@@ -81,11 +81,13 @@ export interface NoteNode<V extends ValueType = ValueType> extends BaseValueNode
|
|
|
81
81
|
export type StringNoteValue = NoteValue<'string'>;
|
|
82
82
|
export type IntNoteValue = NoteValue<'int'>;
|
|
83
83
|
export type DecimalNoteValue = NoteValue<'decimal'>;
|
|
84
|
+
export type DateNoteValue = NoteValue<'date'>;
|
|
84
85
|
export type GeopointNoteValue = NoteValue<'geopoint'>;
|
|
85
86
|
|
|
86
87
|
export type StringNoteNode = NoteNode<'string'>;
|
|
87
88
|
export type IntNoteNode = NoteNode<'int'>;
|
|
88
89
|
export type DecimalNoteNode = NoteNode<'decimal'>;
|
|
90
|
+
export type DateNoteNode = NoteNode<'date'>;
|
|
89
91
|
export type GeopointNoteNode = NoteNode<'geopoint'>;
|
|
90
92
|
|
|
91
93
|
// prettier-ignore
|
|
@@ -94,6 +96,7 @@ type SupportedNoteValueType =
|
|
|
94
96
|
| 'string'
|
|
95
97
|
| 'int'
|
|
96
98
|
| 'decimal'
|
|
99
|
+
| 'date'
|
|
97
100
|
| 'geopoint';
|
|
98
101
|
|
|
99
102
|
type TemporaryStringValueType = Exclude<ValueType, SupportedNoteValueType>;
|
|
@@ -106,5 +109,6 @@ export type AnyNoteNode =
|
|
|
106
109
|
| StringNoteNode
|
|
107
110
|
| IntNoteNode
|
|
108
111
|
| DecimalNoteNode
|
|
112
|
+
| DateNoteNode
|
|
109
113
|
| GeopointNoteNode
|
|
110
114
|
| TemporaryStringValueNoteNode;
|
|
@@ -18,7 +18,9 @@ export const getContainingEngineXPathDocument = (node: EngineXPathNode): EngineX
|
|
|
18
18
|
return node.rootDocument;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
export const getEngineXPathAttributes = (
|
|
21
|
+
export const getEngineXPathAttributes = (
|
|
22
|
+
node: EngineXPathNode
|
|
23
|
+
): readonly EngineXPathAttribute[] => {
|
|
22
24
|
if (node.nodeType === 'static-element') {
|
|
23
25
|
return node.attributes;
|
|
24
26
|
}
|
|
@@ -39,7 +41,7 @@ export const getEngineXPathAttributes = (node: EngineXPathNode): Iterable<Engine
|
|
|
39
41
|
* it throw? It might be nice to be alerted if the assumptions in point 2 above
|
|
40
42
|
* are somehow wrong (or become wrong).
|
|
41
43
|
*/
|
|
42
|
-
export const getNamespaceDeclarations = ():
|
|
44
|
+
export const getNamespaceDeclarations = (): readonly [] => [];
|
|
43
45
|
|
|
44
46
|
export const getParentNode = (node: EngineXPathNode): EngineXPathParentNode | null => {
|
|
45
47
|
if (node.nodeType === 'repeat-instance') {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ISO_DATE_OR_DATE_TIME_NO_OFFSET_PATTERN } from '@getodk/common/constants/datetime.ts';
|
|
2
|
+
import { Temporal } from 'temporal-polyfill';
|
|
3
|
+
import { type CodecDecoder, type CodecEncoder, ValueCodec } from './ValueCodec.ts';
|
|
4
|
+
|
|
5
|
+
export type DatetimeRuntimeValue = Temporal.PlainDate | null;
|
|
6
|
+
|
|
7
|
+
export type DatetimeInputValue =
|
|
8
|
+
| Date
|
|
9
|
+
| Temporal.PlainDate
|
|
10
|
+
| Temporal.PlainDateTime
|
|
11
|
+
| Temporal.ZonedDateTime
|
|
12
|
+
| string
|
|
13
|
+
| null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parses a string in the format 'YYYY-MM-DD' or 'YYYY-MM-DDTHH:MM:SS' (no offset)
|
|
17
|
+
* into a Temporal.PlainDate.
|
|
18
|
+
* TODO: Datetimes with a valid timezone offset are treated as errors.
|
|
19
|
+
* User research is needed to determine whether the date should honor
|
|
20
|
+
* the timezone or be truncated to the yyyy-mm-dd format only.
|
|
21
|
+
*
|
|
22
|
+
* @param value - The string to parse.
|
|
23
|
+
* @returns A {@link DatetimeRuntimeValue}
|
|
24
|
+
*/
|
|
25
|
+
const parseString = (value: string): DatetimeRuntimeValue => {
|
|
26
|
+
if (
|
|
27
|
+
value == null ||
|
|
28
|
+
typeof value !== 'string' ||
|
|
29
|
+
!ISO_DATE_OR_DATE_TIME_NO_OFFSET_PATTERN.test(value)
|
|
30
|
+
) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const dateOnly = value.split('T')[0]!;
|
|
36
|
+
return Temporal.PlainDate.from(dateOnly);
|
|
37
|
+
} catch {
|
|
38
|
+
// TODO: should we throw when codec cannot interpret the value?
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Converts a date-like value ({@link DatetimeInputValue}) to a 'YYYY-MM-DD' string.
|
|
45
|
+
* TODO: Datetimes with a valid timezone offset are treated as errors.
|
|
46
|
+
* User research is needed to determine whether the date should honor
|
|
47
|
+
* the timezone or be truncated to the yyyy-mm-dd format only.
|
|
48
|
+
*
|
|
49
|
+
* @param value - The value to convert.
|
|
50
|
+
* @returns A date string or empty string if invalid.
|
|
51
|
+
*/
|
|
52
|
+
const toDateString = (value: DatetimeInputValue): string => {
|
|
53
|
+
if (value == null || value instanceof Temporal.ZonedDateTime) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
if (value instanceof Temporal.PlainDate) {
|
|
59
|
+
return value.toString();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (value instanceof Temporal.PlainDateTime) {
|
|
63
|
+
return value.toPlainDate().toString();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (value instanceof Date) {
|
|
67
|
+
return Temporal.PlainDate.from({
|
|
68
|
+
year: value.getFullYear(),
|
|
69
|
+
month: value.getMonth() + 1,
|
|
70
|
+
day: value.getDate(),
|
|
71
|
+
}).toString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const parsed = parseString(String(value));
|
|
75
|
+
return parsed?.toString() ?? '';
|
|
76
|
+
} catch {
|
|
77
|
+
// TODO: should we throw when codec cannot interpret the value?
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export class DateValueCodec extends ValueCodec<'date', DatetimeRuntimeValue, DatetimeInputValue> {
|
|
83
|
+
constructor() {
|
|
84
|
+
const encodeValue: CodecEncoder<DatetimeInputValue> = (value) => {
|
|
85
|
+
return toDateString(value);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const decodeValue: CodecDecoder<DatetimeRuntimeValue> = (value: string) => {
|
|
89
|
+
return parseString(value);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
super('date', encodeValue, decodeValue);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ValueType } from '../../client/ValueType.ts';
|
|
2
|
+
import type { DatetimeInputValue, DatetimeRuntimeValue } from './DateValueCodec.ts';
|
|
3
|
+
import { DateValueCodec } from './DateValueCodec.ts';
|
|
2
4
|
import {
|
|
3
5
|
DecimalValueCodec,
|
|
4
6
|
type DecimalInputValue,
|
|
@@ -16,7 +18,7 @@ interface RuntimeValuesByType {
|
|
|
16
18
|
readonly int: IntRuntimeValue;
|
|
17
19
|
readonly decimal: DecimalRuntimeValue;
|
|
18
20
|
readonly boolean: string;
|
|
19
|
-
readonly date:
|
|
21
|
+
readonly date: DatetimeRuntimeValue;
|
|
20
22
|
readonly time: string;
|
|
21
23
|
readonly dateTime: string;
|
|
22
24
|
readonly geopoint: GeopointRuntimeValue;
|
|
@@ -34,7 +36,7 @@ interface RuntimeInputValuesByType {
|
|
|
34
36
|
readonly int: IntInputValue;
|
|
35
37
|
readonly decimal: DecimalInputValue;
|
|
36
38
|
readonly boolean: string;
|
|
37
|
-
readonly date:
|
|
39
|
+
readonly date: DatetimeInputValue;
|
|
38
40
|
readonly time: string;
|
|
39
41
|
readonly dateTime: string;
|
|
40
42
|
readonly geopoint: GeopointInputValue;
|
|
@@ -63,7 +65,7 @@ export const sharedValueCodecs: SharedValueCodecs = {
|
|
|
63
65
|
int: new IntValueCodec(),
|
|
64
66
|
decimal: new DecimalValueCodec(),
|
|
65
67
|
boolean: new ValueTypePlaceholderCodec('boolean'),
|
|
66
|
-
date: new
|
|
68
|
+
date: new DateValueCodec(),
|
|
67
69
|
time: new ValueTypePlaceholderCodec('time'),
|
|
68
70
|
dateTime: new ValueTypePlaceholderCodec('dateTime'),
|
|
69
71
|
geopoint: new GeopointValueCodec(),
|