@getodk/xforms-engine 0.12.0 → 0.14.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.
Files changed (95) hide show
  1. package/dist/client/BaseItem.d.ts +6 -0
  2. package/dist/client/GroupNode.d.ts +4 -4
  3. package/dist/client/MarkdownNode.d.ts +33 -0
  4. package/dist/client/RankNode.d.ts +2 -4
  5. package/dist/client/SelectNode.d.ts +2 -5
  6. package/dist/client/TextRange.d.ts +2 -11
  7. package/dist/client/hierarchy.d.ts +1 -2
  8. package/dist/client/index.d.ts +1 -1
  9. package/dist/client/node-types.d.ts +1 -1
  10. package/dist/index.js +10543 -332
  11. package/dist/index.js.map +1 -1
  12. package/dist/instance/RankControl.d.ts +3 -2
  13. package/dist/instance/SelectControl.d.ts +3 -2
  14. package/dist/instance/hierarchy.d.ts +5 -6
  15. package/dist/instance/markdown/MarkdownNode.d.ts +75 -0
  16. package/dist/instance/text/TextChunk.d.ts +0 -1
  17. package/dist/instance/text/TextRange.d.ts +2 -1
  18. package/dist/instance/text/markdownFormat.d.ts +3 -0
  19. package/dist/lib/reactivity/createItemCollection.d.ts +5 -7
  20. package/dist/parse/body/BodyDefinition.d.ts +2 -8
  21. package/dist/parse/body/GroupElementDefinition.d.ts +22 -0
  22. package/dist/parse/body/control/ItemsetDefinition.d.ts +4 -1
  23. package/dist/parse/expression/BindComputationExpression.d.ts +1 -1
  24. package/dist/parse/expression/ItemPropertyExpression.d.ts +6 -0
  25. package/dist/parse/expression/ItemsetNodesetExpression.d.ts +1 -2
  26. package/dist/parse/expression/TextChunkExpression.d.ts +9 -7
  27. package/dist/parse/expression/abstract/DependentExpression.d.ts +1 -15
  28. package/dist/parse/model/{SubtreeDefinition.d.ts → GroupDefinition.d.ts} +4 -3
  29. package/dist/parse/model/ModelDefinition.d.ts +6 -1
  30. package/dist/parse/model/NodeDefinition.d.ts +7 -8
  31. package/dist/parse/model/RepeatDefinition.d.ts +1 -1
  32. package/dist/parse/model/RootDefinition.d.ts +1 -1
  33. package/dist/parse/model/generateItextChunks.d.ts +5 -0
  34. package/dist/parse/text/LabelDefinition.d.ts +4 -5
  35. package/dist/parse/xpath/semantic-analysis.d.ts +1 -0
  36. package/dist/solid.js +10543 -332
  37. package/dist/solid.js.map +1 -1
  38. package/package.json +3 -2
  39. package/src/client/BaseItem.ts +7 -0
  40. package/src/client/GroupNode.ts +4 -10
  41. package/src/client/MarkdownNode.ts +53 -0
  42. package/src/client/RankNode.ts +2 -5
  43. package/src/client/SelectNode.ts +2 -6
  44. package/src/client/TextRange.ts +2 -11
  45. package/src/client/hierarchy.ts +0 -2
  46. package/src/client/index.ts +1 -1
  47. package/src/client/node-types.ts +0 -1
  48. package/src/instance/Group.ts +1 -1
  49. package/src/instance/RankControl.ts +8 -2
  50. package/src/instance/SelectControl.ts +8 -2
  51. package/src/instance/children/buildChildren.ts +1 -17
  52. package/src/instance/children/normalizeChildInitOptions.ts +44 -59
  53. package/src/instance/hierarchy.ts +0 -6
  54. package/src/instance/markdown/MarkdownNode.ts +115 -0
  55. package/src/instance/text/TextChunk.ts +0 -5
  56. package/src/instance/text/TextRange.ts +5 -3
  57. package/src/instance/text/markdownFormat.ts +214 -0
  58. package/src/integration/xpath/adapter/names.ts +0 -1
  59. package/src/lib/reactivity/createItemCollection.ts +25 -9
  60. package/src/lib/reactivity/text/createTextRange.ts +30 -30
  61. package/src/parse/body/BodyDefinition.ts +7 -34
  62. package/src/parse/body/GroupElementDefinition.ts +47 -0
  63. package/src/parse/body/control/ItemsetDefinition.ts +9 -2
  64. package/src/parse/expression/BindComputationExpression.ts +2 -7
  65. package/src/parse/expression/ItemPropertyExpression.ts +12 -0
  66. package/src/parse/expression/ItemsetNodesetExpression.ts +2 -3
  67. package/src/parse/expression/ItemsetValueExpression.ts +1 -1
  68. package/src/parse/expression/RepeatCountControlExpression.ts +4 -4
  69. package/src/parse/expression/TextChunkExpression.ts +25 -35
  70. package/src/parse/expression/abstract/DependentExpression.ts +2 -38
  71. package/src/parse/model/{SubtreeDefinition.ts → GroupDefinition.ts} +6 -8
  72. package/src/parse/model/ModelDefinition.ts +13 -0
  73. package/src/parse/model/NodeDefinition.ts +8 -9
  74. package/src/parse/model/RepeatDefinition.ts +2 -2
  75. package/src/parse/model/RootDefinition.ts +2 -2
  76. package/src/parse/model/generateItextChunks.ts +61 -0
  77. package/src/parse/text/ItemsetLabelDefinition.ts +4 -4
  78. package/src/parse/text/LabelDefinition.ts +4 -9
  79. package/src/parse/text/MessageDefinition.ts +4 -4
  80. package/src/parse/text/abstract/TextElementDefinition.ts +6 -7
  81. package/src/parse/xpath/semantic-analysis.ts +37 -8
  82. package/dist/client/SubtreeNode.d.ts +0 -56
  83. package/dist/instance/Subtree.d.ts +0 -38
  84. package/dist/instance/text/FormattedTextStub.d.ts +0 -1
  85. package/dist/parse/body/group/BaseGroupDefinition.d.ts +0 -40
  86. package/dist/parse/body/group/LogicalGroupDefinition.d.ts +0 -6
  87. package/dist/parse/body/group/PresentationGroupDefinition.d.ts +0 -11
  88. package/dist/parse/body/group/StructuralGroupDefinition.d.ts +0 -6
  89. package/src/client/SubtreeNode.ts +0 -61
  90. package/src/instance/Subtree.ts +0 -102
  91. package/src/instance/text/FormattedTextStub.ts +0 -8
  92. package/src/parse/body/group/BaseGroupDefinition.ts +0 -89
  93. package/src/parse/body/group/LogicalGroupDefinition.ts +0 -11
  94. package/src/parse/body/group/PresentationGroupDefinition.ts +0 -28
  95. package/src/parse/body/group/StructuralGroupDefinition.ts +0 -11
@@ -0,0 +1,47 @@
1
+ import { LabelDefinition } from '../text/LabelDefinition.ts';
2
+ import type { XFormDefinition } from '../XFormDefinition.ts';
3
+ import { parseNodesetReference } from '../xpath/reference-parsing.ts';
4
+ import type { StructureElementAppearanceDefinition } from './appearance/structureElementAppearanceParser.ts';
5
+ import { structureElementAppearanceParser } from './appearance/structureElementAppearanceParser.ts';
6
+ import {
7
+ type BodyElementDefinitionArray,
8
+ type BodyElementParentContext,
9
+ } from './BodyDefinition.ts';
10
+ import { BodyElementDefinition } from './BodyElementDefinition.ts';
11
+
12
+ /**
13
+ * As per the spec: https://getodk.github.io/xforms-spec/#groups
14
+ *
15
+ * A group combines elements together.
16
+ * The group can have a label, and if so is referred to as a "presentation group".
17
+ * The group can have a ref, and if so is referred to as a "logical group".
18
+ */
19
+ export class GroupElementDefinition extends BodyElementDefinition<'group'> {
20
+ override readonly category = 'structure';
21
+ override readonly type = 'group';
22
+
23
+ readonly children: BodyElementDefinitionArray;
24
+
25
+ override readonly reference: string | null;
26
+ readonly appearances: StructureElementAppearanceDefinition;
27
+ override readonly label: LabelDefinition | null;
28
+
29
+ static override isCompatible(localName: string): boolean {
30
+ return localName === 'group';
31
+ }
32
+
33
+ constructor(form: XFormDefinition, parent: BodyElementParentContext, element: Element) {
34
+ super(form, parent, element);
35
+
36
+ const childElements = Array.from(element.children).filter((child) => {
37
+ const childName = child.localName;
38
+
39
+ return childName !== 'label';
40
+ });
41
+
42
+ this.children = this.body.getChildElementDefinitions(form, this, element, childElements);
43
+ this.reference = parseNodesetReference(parent, element, 'ref');
44
+ this.appearances = structureElementAppearanceParser.parseFrom(element, 'appearance');
45
+ this.label = LabelDefinition.forGroup(form, this);
46
+ }
47
+ }
@@ -1,13 +1,16 @@
1
+ import type { StaticElement } from '../../../integration/xpath/static-dom/StaticElement.ts';
1
2
  import type { ItemsetElement } from '../../../lib/dom/query.ts';
2
3
  import { getValueElement } from '../../../lib/dom/query.ts';
4
+ import { DependentExpression } from '../../expression/abstract/DependentExpression.ts';
5
+ import { ItemPropertyExpression } from '../../expression/ItemPropertyExpression.ts';
3
6
  import { ItemsetNodesetExpression } from '../../expression/ItemsetNodesetExpression.ts';
4
7
  import { ItemsetValueExpression } from '../../expression/ItemsetValueExpression.ts';
5
8
  import { ItemsetLabelDefinition } from '../../text/ItemsetLabelDefinition.ts';
6
9
  import type { XFormDefinition } from '../../XFormDefinition.ts';
7
10
  import { parseNodesetReference } from '../../xpath/reference-parsing.ts';
8
11
  import { BodyElementDefinition } from '../BodyElementDefinition.ts';
9
- import type { AnySelectControlDefinition } from './SelectControlDefinition.ts';
10
12
  import { RankControlDefinition } from './RankControlDefinition.ts';
13
+ import type { AnySelectControlDefinition } from './SelectControlDefinition.ts';
11
14
 
12
15
  export class ItemsetDefinition extends BodyElementDefinition<'itemset'> {
13
16
  override readonly category = 'support';
@@ -28,7 +31,7 @@ export class ItemsetDefinition extends BodyElementDefinition<'itemset'> {
28
31
 
29
32
  const nodesetExpression = parseNodesetReference(parent, element, 'nodeset');
30
33
 
31
- this.nodes = new ItemsetNodesetExpression(this, nodesetExpression);
34
+ this.nodes = new ItemsetNodesetExpression(nodesetExpression);
32
35
  this.reference = nodesetExpression;
33
36
 
34
37
  const valueElement = getValueElement(element);
@@ -52,4 +55,8 @@ export class ItemsetDefinition extends BodyElementDefinition<'itemset'> {
52
55
  this.value = new ItemsetValueExpression(this, valueExpression);
53
56
  this.label = ItemsetLabelDefinition.from(form, this);
54
57
  }
58
+
59
+ getPropertiesExpressions(propertiesNodes: StaticElement[]): Array<DependentExpression<'string'>> {
60
+ return ItemPropertyExpression.from(propertiesNodes);
61
+ }
55
62
  }
@@ -47,18 +47,15 @@ export class BindComputationExpression<
47
47
  return null as BindComputationFactoryResult<Type>;
48
48
  }
49
49
 
50
- return new this(bind, computation, expression);
50
+ return new this(computation, expression);
51
51
  }
52
52
 
53
53
  readonly isDefaultExpression: boolean;
54
54
 
55
55
  protected constructor(
56
- bind: BindDefinition,
57
56
  readonly computation: Computation,
58
57
  expression: string | null
59
58
  ) {
60
- const ignoreContextReference = computation === 'constraint';
61
-
62
59
  let isDefaultExpression: boolean;
63
60
  let resolvedExpression: string;
64
61
 
@@ -75,9 +72,7 @@ export class BindComputationExpression<
75
72
  resolvedExpression = expression;
76
73
  }
77
74
 
78
- super(bind, bindComputationResultTypes[computation], resolvedExpression, {
79
- ignoreContextReference,
80
- });
75
+ super(bindComputationResultTypes[computation], resolvedExpression);
81
76
 
82
77
  this.isDefaultExpression = isDefaultExpression;
83
78
  }
@@ -0,0 +1,12 @@
1
+ import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
2
+ import { DependentExpression } from './abstract/DependentExpression.ts';
3
+
4
+ export class ItemPropertyExpression extends DependentExpression<'string'> {
5
+ static from(propertiesNodes: StaticElement[]) {
6
+ return propertiesNodes.map((node: StaticElement) => new this(node.qualifiedName.localName));
7
+ }
8
+
9
+ constructor(propertyName: string) {
10
+ super('string', propertyName);
11
+ }
12
+ }
@@ -1,8 +1,7 @@
1
- import type { ItemsetDefinition } from '../body/control/ItemsetDefinition.ts';
2
1
  import { DependentExpression } from './abstract/DependentExpression.ts';
3
2
 
4
3
  export class ItemsetNodesetExpression extends DependentExpression<'nodes'> {
5
- constructor(itemset: ItemsetDefinition, nodesetExpression: string) {
6
- super(itemset.parent, 'nodes', nodesetExpression);
4
+ constructor(nodesetExpression: string) {
5
+ super('nodes', nodesetExpression);
7
6
  }
8
7
  }
@@ -6,6 +6,6 @@ export class ItemsetValueExpression extends DependentExpression<'string'> {
6
6
  readonly itemset: ItemsetDefinition,
7
7
  expression: string
8
8
  ) {
9
- super(itemset, 'string', expression);
9
+ super('string', expression);
10
10
  }
11
11
  }
@@ -23,7 +23,7 @@ export class RepeatCountControlExpression extends DependentExpression<'number'>
23
23
  const { countExpression, noAddRemoveExpression } = bodyElement;
24
24
 
25
25
  if (countExpression != null) {
26
- return new this(bodyElement, countExpression);
26
+ return new this(countExpression);
27
27
  }
28
28
 
29
29
  if (noAddRemoveExpression != null && isConstantTruthyExpression(noAddRemoveExpression)) {
@@ -32,13 +32,13 @@ export class RepeatCountControlExpression extends DependentExpression<'number'>
32
32
  // repeat's template.
33
33
  const fixedCountExpression = String(Math.max(initialCount, 1));
34
34
 
35
- return new this(bodyElement, fixedCountExpression);
35
+ return new this(fixedCountExpression);
36
36
  }
37
37
 
38
38
  return null;
39
39
  }
40
40
 
41
- private constructor(context: RepeatElementDefinition, expression: string) {
42
- super(context, 'number', expression);
41
+ private constructor(expression: string) {
42
+ super('number', expression);
43
43
  }
44
44
  }
@@ -1,11 +1,14 @@
1
+ import type {
2
+ JRResourceURLString,
3
+ ResourceType,
4
+ } from '@getodk/common/jr-resources/JRResourceURL.ts';
1
5
  import type { KnownAttributeLocalNamedElement } from '@getodk/common/types/dom.ts';
2
6
  import type { TextChunkSource } from '../../client/TextRange.ts';
3
- import type { AnyTextRangeDefinition } from '../text/abstract/TextRangeDefinition.ts';
4
- import { isTranslationExpression } from '../xpath/semantic-analysis.ts';
7
+ import { getTranslationExpression } from '../xpath/semantic-analysis.ts';
5
8
  import { DependentExpression } from './abstract/DependentExpression.ts';
6
9
 
7
10
  interface TextChunkExpressionOptions {
8
- readonly isTranslated?: true;
11
+ readonly type?: ResourceType;
9
12
  }
10
13
 
11
14
  interface OutputElement extends KnownAttributeLocalNamedElement<'output', 'value'> {}
@@ -18,59 +21,46 @@ export class TextChunkExpression<T extends 'nodes' | 'string'> extends Dependent
18
21
  readonly source: TextChunkSource;
19
22
  // Set for the literal source, blank otherwise
20
23
  readonly stringValue: string;
24
+ readonly resourceType: ResourceType | null;
21
25
 
22
26
  constructor(
23
- context: AnyTextRangeDefinition,
24
27
  resultType: T,
25
28
  expression: string,
26
29
  source: TextChunkSource,
27
- options: TextChunkExpressionOptions = {},
28
- literalValue = ''
30
+ literalValue = '',
31
+ options: TextChunkExpressionOptions = {}
29
32
  ) {
30
- super(context, resultType, expression, {
31
- semanticDependencies: {
32
- translations: options.isTranslated,
33
- },
34
- ignoreContextReference: true,
35
- });
33
+ super(resultType, expression);
36
34
 
35
+ this.resourceType = options.type ?? null;
37
36
  this.source = source;
38
37
  this.stringValue = literalValue;
39
38
  }
40
39
 
41
- static fromLiteral(
42
- context: AnyTextRangeDefinition,
43
- stringValue: string
44
- ): TextChunkExpression<'string'> {
45
- return new TextChunkExpression(context, 'string', 'null', 'literal', {}, stringValue);
40
+ static fromLiteral(stringValue: string): TextChunkExpression<'string'> {
41
+ return new TextChunkExpression('string', 'null', 'literal', stringValue);
46
42
  }
47
43
 
48
- static fromReference(
49
- context: AnyTextRangeDefinition,
50
- ref: string
51
- ): TextChunkExpression<'string'> {
52
- return new TextChunkExpression(context, 'string', ref, 'reference');
44
+ static fromReference(ref: string): TextChunkExpression<'string'> {
45
+ return new TextChunkExpression('string', ref, 'reference');
53
46
  }
54
47
 
55
- static fromOutput(
56
- context: AnyTextRangeDefinition,
57
- element: Element
58
- ): TextChunkExpression<'string'> | null {
48
+ static fromOutput(element: Element): TextChunkExpression<'string'> | null {
59
49
  if (!isOutputElement(element)) {
60
50
  return null;
61
51
  }
62
52
 
63
- return new TextChunkExpression(context, 'string', element.getAttribute('value'), 'output');
53
+ return new TextChunkExpression('string', element.getAttribute('value'), 'output');
64
54
  }
65
55
 
66
- static fromTranslation(
67
- context: AnyTextRangeDefinition,
68
- maybeExpression: string
69
- ): TextChunkExpression<'nodes'> | null {
70
- if (isTranslationExpression(maybeExpression)) {
71
- return new TextChunkExpression(context, 'nodes', maybeExpression, 'translation', {
72
- isTranslated: true,
73
- });
56
+ static fromResource(url: JRResourceURLString, type: ResourceType): TextChunkExpression<'string'> {
57
+ return new TextChunkExpression('string', 'null', 'literal', url, { type });
58
+ }
59
+
60
+ static fromTranslation(maybeExpression: string): TextChunkExpression<'nodes'> | null {
61
+ const translationExpression = getTranslationExpression(maybeExpression);
62
+ if (translationExpression) {
63
+ return new TextChunkExpression('nodes', translationExpression, 'translation');
74
64
  }
75
65
 
76
66
  return null;
@@ -3,12 +3,7 @@ import type {
3
3
  ConstantExpression,
4
4
  ConstantTruthyExpression,
5
5
  } from '../../xpath/semantic-analysis.ts';
6
- import {
7
- isConstantExpression,
8
- isConstantTruthyExpression,
9
- isTranslationExpression,
10
- } from '../../xpath/semantic-analysis.ts';
11
- import type { DependencyContext } from './DependencyContext.ts';
6
+ import { isConstantExpression, isConstantTruthyExpression } from '../../xpath/semantic-analysis.ts';
12
7
 
13
8
  const evaluatorMethodsByResultType = {
14
9
  boolean: 'evaluateBoolean',
@@ -28,22 +23,6 @@ export type DependentExpressionResult<Type extends DependentExpressionResultType
28
23
  EngineXPathEvaluator[DependentExpressionEvaluatorMethod<Type>]
29
24
  >;
30
25
 
31
- interface SemanticDependencyOptions {
32
- /**
33
- * @default false
34
- */
35
- readonly translations?: boolean | undefined;
36
- }
37
-
38
- interface DependentExpressionOptions {
39
- /**
40
- * @default false
41
- */
42
- readonly ignoreContextReference?: boolean;
43
-
44
- readonly semanticDependencies?: SemanticDependencyOptions;
45
- }
46
-
47
26
  export interface ConstantDependentExpression<Type extends DependentExpressionResultType>
48
27
  extends DependentExpression<Type> {
49
28
  readonly expression: ConstantExpression;
@@ -60,10 +39,8 @@ export abstract class DependentExpression<Type extends DependentExpressionResult
60
39
  readonly constantTruthyExpression: ConstantTruthyExpression | null;
61
40
 
62
41
  constructor(
63
- context: DependencyContext,
64
42
  readonly resultType: Type,
65
- readonly expression: string,
66
- options: DependentExpressionOptions = {}
43
+ readonly expression: string
67
44
  ) {
68
45
  if (resultType === 'boolean' && isConstantTruthyExpression(expression)) {
69
46
  this.constantTruthyExpression = expression;
@@ -77,19 +54,6 @@ export abstract class DependentExpression<Type extends DependentExpressionResult
77
54
  }
78
55
 
79
56
  this.evaluatorMethod = evaluatorMethodsByResultType[resultType];
80
-
81
- const {
82
- semanticDependencies = {
83
- translations: false,
84
- },
85
- } = options;
86
-
87
- const isTranslated = semanticDependencies.translations && isTranslationExpression(expression);
88
-
89
- if (isTranslated) {
90
- this.isTranslated = true;
91
- context.isTranslated = true;
92
- }
93
57
  }
94
58
 
95
59
  isConstantExpression(): this is ConstantDependentExpression<Type> {
@@ -1,19 +1,17 @@
1
1
  import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
2
2
  import { NamespaceDeclarationMap } from '../../lib/names/NamespaceDeclarationMap.ts';
3
3
  import { QualifiedName } from '../../lib/names/QualifiedName.ts';
4
- import type {
5
- AnyBodyElementDefinition,
6
- AnyGroupElementDefinition,
7
- } from '../body/BodyDefinition.ts';
4
+ import type { AnyBodyElementDefinition } from '../body/BodyDefinition.ts';
5
+ import type { GroupElementDefinition } from '../body/GroupElementDefinition.ts';
8
6
  import type { BindDefinition } from './BindDefinition.ts';
9
7
  import { DescendentNodeDefinition } from './DescendentNodeDefinition.ts';
10
8
  import type { ChildNodeDefinition, ParentNodeDefinition } from './NodeDefinition.ts';
11
9
 
12
- export class SubtreeDefinition extends DescendentNodeDefinition<
13
- 'subtree',
14
- AnyGroupElementDefinition | null
10
+ export class GroupDefinition extends DescendentNodeDefinition<
11
+ 'group',
12
+ GroupElementDefinition | null
15
13
  > {
16
- readonly type = 'subtree';
14
+ readonly type = 'group';
17
15
 
18
16
  readonly namespaceDeclarations: NamespaceDeclarationMap;
19
17
  readonly qualifiedName: QualifiedName;
@@ -1,7 +1,10 @@
1
+ import type { ActiveLanguage } from '../../client/FormLanguage.ts';
1
2
  import { ErrorProductionDesignPendingError } from '../../error/ErrorProductionDesignPendingError.ts';
2
3
  import type { StaticDocument } from '../../integration/xpath/static-dom/StaticDocument.ts';
4
+ import { TextChunkExpression } from '../expression/TextChunkExpression.ts';
3
5
  import { parseStaticDocumentFromDOMSubtree } from '../shared/parseStaticDocumentFromDOMSubtree.ts';
4
6
  import type { XFormDefinition } from '../XFormDefinition.ts';
7
+ import { generateItextChunks, type ChunkExpressionsByItextId } from './generateItextChunks.ts';
5
8
  import { ItextTranslationsDefinition } from './ItextTranslationsDefinition.ts';
6
9
  import { ModelBindMap } from './ModelBindMap.ts';
7
10
  import type { AnyNodeDefinition } from './NodeDefinition.ts';
@@ -16,6 +19,7 @@ export class ModelDefinition {
16
19
  readonly nodes: NodeDefinitionMap;
17
20
  readonly instance: StaticDocument;
18
21
  readonly itextTranslations: ItextTranslationsDefinition;
22
+ readonly itextChunks: Map<string, ChunkExpressionsByItextId>;
19
23
 
20
24
  constructor(readonly form: XFormDefinition) {
21
25
  const submission = new SubmissionDefinition(form.xformDOM);
@@ -27,6 +31,7 @@ export class ModelDefinition {
27
31
  this.root = new RootDefinition(form, this, submission, form.body.classes);
28
32
  this.nodes = nodeDefinitionMap(this.root);
29
33
  this.itextTranslations = ItextTranslationsDefinition.from(form.xformDOM);
34
+ this.itextChunks = generateItextChunks(form.xformDOM.itextTranslationElements);
30
35
  }
31
36
 
32
37
  getNodeDefinition(nodeset: string): AnyNodeDefinition {
@@ -54,4 +59,12 @@ export class ModelDefinition {
54
59
 
55
60
  return rest;
56
61
  }
62
+
63
+ getTranslationChunks(
64
+ itextId: string,
65
+ activeLanguage: ActiveLanguage
66
+ ): ReadonlyArray<TextChunkExpression<'string'>> {
67
+ const languageMap = this.itextChunks.get(activeLanguage.language);
68
+ return languageMap?.get(itextId) ?? [];
69
+ }
57
70
  }
@@ -7,10 +7,10 @@ import type { QualifiedName } from '../../lib/names/QualifiedName.ts';
7
7
  import type { AnyBodyElementDefinition } from '../body/BodyDefinition.ts';
8
8
  import type { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts';
9
9
  import type { BindDefinition } from './BindDefinition.ts';
10
+ import type { GroupDefinition } from './GroupDefinition.ts';
10
11
  import type { LeafNodeDefinition } from './LeafNodeDefinition.ts';
11
12
  import type { AnyRepeatDefinition } from './RepeatDefinition.ts';
12
13
  import type { RootDefinition } from './RootDefinition.ts';
13
- import type { SubtreeDefinition } from './SubtreeDefinition.ts';
14
14
 
15
15
  /**
16
16
  * Corresponds to a model instance root node, i.e.:
@@ -33,10 +33,9 @@ export type RepeatType = 'repeat';
33
33
 
34
34
  /**
35
35
  * Corresponds to a model instance subtree which **does not** correspond to a
36
- * <repeat> in the form definition. This will typically correspond to a <group>,
37
- * but this is not strictly necessary per spec (hence the distinct name).
36
+ * <repeat> in the form definition.
38
37
  */
39
- export type SubtreeNodeType = 'subtree';
38
+ export type GroupNodeType = 'group';
40
39
 
41
40
  /**
42
41
  * Corresponds to a model instance leaf node, i.e. one of:
@@ -51,7 +50,7 @@ export type NodeDefinitionType =
51
50
  // eslint-disable-next-line @typescript-eslint/sort-type-constituents
52
51
  | RootNodeType
53
52
  | RepeatType
54
- | SubtreeNodeType
53
+ | GroupNodeType
55
54
  | LeafNodeType;
56
55
 
57
56
  // prettier-ignore
@@ -59,13 +58,13 @@ export type ParentNodeDefinition =
59
58
  // eslint-disable-next-line @typescript-eslint/sort-type-constituents
60
59
  | RootDefinition
61
60
  | AnyRepeatDefinition
62
- | SubtreeDefinition;
61
+ | GroupDefinition;
63
62
 
64
63
  // prettier-ignore
65
64
  export type ChildNodeDefinition =
66
65
  | AnyRepeatDefinition
67
- | LeafNodeDefinition
68
- | SubtreeDefinition;
66
+ | GroupDefinition
67
+ | LeafNodeDefinition;
69
68
 
70
69
  export abstract class NodeDefinition<Type extends NodeDefinitionType>
71
70
  implements NamedSubtreeDefinition
@@ -92,5 +91,5 @@ export type AnyNodeDefinition =
92
91
  // eslint-disable-next-line @typescript-eslint/sort-type-constituents
93
92
  | RootDefinition
94
93
  | AnyRepeatDefinition
95
- | SubtreeDefinition
94
+ | GroupDefinition
96
95
  | LeafNodeDefinition;
@@ -21,9 +21,9 @@ import type { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts
21
21
  import { RepeatCountControlExpression } from '../expression/RepeatCountControlExpression.ts';
22
22
  import type { BindDefinition } from './BindDefinition.ts';
23
23
  import { DescendentNodeDefinition } from './DescendentNodeDefinition.ts';
24
+ import type { GroupDefinition } from './GroupDefinition.ts';
24
25
  import type { ChildNodeDefinition, ParentNodeDefinition } from './NodeDefinition.ts';
25
26
  import type { RootDefinition } from './RootDefinition.ts';
26
- import type { SubtreeDefinition } from './SubtreeDefinition.ts';
27
27
 
28
28
  interface JavaRosaNamespaceURI extends NamespaceURL {
29
29
  readonly href: JAVAROSA_NAMESPACE_URI;
@@ -284,7 +284,7 @@ export interface UncontrolledRepeatDefinition extends RepeatDefinition {
284
284
  * "repeat", as defined by a form, where those concepts include:
285
285
  *
286
286
  * - A {@link RepeatElementDefinition}—corresponding to a `<repeat>` {@link https://getodk.github.io/xforms-spec/#body-elements | body element}—which is associated with the nodeset referencing the "repeat template" and
287
- * all "repeat instances" (see below points describing both concepts in more detail). The presence of such a body element determines whether to produce a repeat definition (rather than e.g. a {@link SubtreeDefinition}).
287
+ * all "repeat instances" (see below points describing both concepts in more detail). The presence of such a body element determines whether to produce a repeat definition (rather than e.g. a {@link GroupDefinition}).
288
288
  *
289
289
  * - A "repeat template", defined by a form either
290
290
  * explicitly,
@@ -3,6 +3,7 @@ import { NamespaceDeclarationMap } from '../../lib/names/NamespaceDeclarationMap
3
3
  import { QualifiedName } from '../../lib/names/QualifiedName.ts';
4
4
  import type { BodyClassList } from '../body/BodyDefinition.ts';
5
5
  import type { XFormDefinition } from '../XFormDefinition.ts';
6
+ import { GroupDefinition } from './GroupDefinition.ts';
6
7
  import { LeafNodeDefinition } from './LeafNodeDefinition.ts';
7
8
  import type { ModelDefinition } from './ModelDefinition.ts';
8
9
  import type { ChildNodeDefinition, ParentNodeDefinition } from './NodeDefinition.ts';
@@ -12,7 +13,6 @@ import { RangeNodeDefinition } from './RangeNodeDefinition.ts';
12
13
  import { RepeatDefinition } from './RepeatDefinition.ts';
13
14
  import { RootAttributeMap } from './RootAttributeMap.ts';
14
15
  import type { SubmissionDefinition } from './SubmissionDefinition.ts';
15
- import { SubtreeDefinition } from './SubtreeDefinition.ts';
16
16
 
17
17
  export class RootDefinition extends NodeDefinition<'root'> {
18
18
  readonly type = 'root';
@@ -107,7 +107,7 @@ export class RootDefinition extends NodeDefinition<'root'> {
107
107
  );
108
108
  }
109
109
 
110
- return new SubtreeDefinition(parent, bind, bodyElement, element);
110
+ return new GroupDefinition(parent, bind, bodyElement, element);
111
111
  });
112
112
  }
113
113
 
@@ -0,0 +1,61 @@
1
+ import {
2
+ isResourceType,
3
+ type JRResourceURLString,
4
+ type ResourceType,
5
+ } from '@getodk/common/jr-resources/JRResourceURL.ts';
6
+ import { isElementNode, isTextNode } from '@getodk/common/lib/dom/predicates.ts';
7
+ import { TextChunkExpression } from '../expression/TextChunkExpression.ts';
8
+ import type { DOMItextTranslationElement } from '../XFormDOM.ts';
9
+
10
+ const generateChunk = (node: Node): TextChunkExpression<'string'> | null => {
11
+ if (isElementNode(node)) {
12
+ return TextChunkExpression.fromOutput(node);
13
+ }
14
+ if (isTextNode(node)) {
15
+ const formAttribute = node.parentElement!.getAttribute('form') as ResourceType;
16
+ if (isResourceType(formAttribute)) {
17
+ return TextChunkExpression.fromResource(node.data as JRResourceURLString, formAttribute);
18
+ }
19
+ return TextChunkExpression.fromLiteral(node.data);
20
+ }
21
+ return null;
22
+ };
23
+
24
+ const generateChunksForValues = (valueElement: ChildNode): Array<TextChunkExpression<'string'>> => {
25
+ return Array.from(valueElement.childNodes)
26
+ .map((node) => generateChunk(node))
27
+ .filter((chunk) => chunk !== null);
28
+ };
29
+
30
+ const generateChunksForTranslation = (
31
+ textElement: Element
32
+ ): Array<TextChunkExpression<'string'>> => {
33
+ return Array.from(textElement.childNodes).flatMap((valueElement) =>
34
+ generateChunksForValues(valueElement)
35
+ );
36
+ };
37
+
38
+ const generateChunksForLanguage = (
39
+ translationElement: DOMItextTranslationElement
40
+ ): Map<string, ReadonlyArray<TextChunkExpression<'string'>>> => {
41
+ return new Map(
42
+ Array.from(translationElement.children).map((textElement) => {
43
+ const itextId = textElement.getAttribute('id');
44
+ return [itextId!, generateChunksForTranslation(textElement)] as const;
45
+ })
46
+ );
47
+ };
48
+
49
+ export interface ChunkExpressionsByItextId
50
+ extends Map<string, ReadonlyArray<TextChunkExpression<'string'>>> {}
51
+
52
+ export const generateItextChunks = (
53
+ translationElements: readonly DOMItextTranslationElement[]
54
+ ): Map<string, ChunkExpressionsByItextId> => {
55
+ return new Map(
56
+ translationElements.map((translationElement) => {
57
+ const lang = translationElement.getAttribute('lang');
58
+ return [lang, generateChunksForLanguage(translationElement)] as const;
59
+ })
60
+ );
61
+ };
@@ -30,11 +30,11 @@ export class ItemsetLabelDefinition extends TextRangeDefinition<'item-label'> {
30
30
  throw new Error('<itemset><label> missing ref attribute');
31
31
  }
32
32
 
33
- const expression = TextChunkExpression.fromTranslation(this, refExpression);
34
- if (expression != null) {
35
- this.chunks = [expression];
33
+ const translationChunk = TextChunkExpression.fromTranslation(refExpression);
34
+ if (translationChunk) {
35
+ this.chunks = [translationChunk];
36
36
  } else {
37
- this.chunks = [TextChunkExpression.fromReference(this, refExpression)];
37
+ this.chunks = [TextChunkExpression.fromReference(refExpression)];
38
38
  }
39
39
  }
40
40
  }
@@ -1,16 +1,15 @@
1
1
  import type { LocalNamedElement } from '@getodk/common/types/dom.ts';
2
2
  import { getLabelElement, getRepeatGroupLabelElement } from '../../lib/dom/query.ts';
3
3
  import type { XFormDefinition } from '../../parse/XFormDefinition.ts';
4
- import type { AnyGroupElementDefinition } from '../../parse/body/BodyDefinition.ts';
5
- import type { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts';
6
4
  import type { AnyControlDefinition } from '../body/control/ControlDefinition.ts';
7
- import type { BaseGroupDefinition } from '../body/group/BaseGroupDefinition.ts';
5
+ import type { GroupElementDefinition } from '../body/GroupElementDefinition.ts';
6
+ import type { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts';
8
7
  import { TextElementDefinition } from './abstract/TextElementDefinition.ts';
9
8
 
10
9
  // prettier-ignore
11
10
  export type LabelOwner =
12
11
  | AnyControlDefinition
13
- | AnyGroupElementDefinition
12
+ | GroupElementDefinition
14
13
  | RepeatElementDefinition;
15
14
 
16
15
  interface LabelElement extends LocalNamedElement<'label'> {}
@@ -39,11 +38,7 @@ export class LabelDefinition extends TextElementDefinition<'label'> {
39
38
  return new this(form, repeat, repeatGroupLabel);
40
39
  }
41
40
 
42
- static forGroup(
43
- form: XFormDefinition,
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- group: BaseGroupDefinition<any>
46
- ): LabelDefinition | null {
41
+ static forGroup(form: XFormDefinition, group: GroupElementDefinition): LabelDefinition | null {
47
42
  const labelElement = getLabelElement(group.element);
48
43
 
49
44
  if (labelElement == null) {
@@ -29,11 +29,11 @@ export class MessageDefinition<
29
29
  ) {
30
30
  super(bind.form, bind, null);
31
31
 
32
- const expression = TextChunkExpression.fromTranslation(this, message);
33
- if (expression != null) {
34
- this.chunks = [expression];
32
+ const translationChunk = TextChunkExpression.fromTranslation(message);
33
+ if (translationChunk) {
34
+ this.chunks = [translationChunk];
35
35
  } else {
36
- this.chunks = [TextChunkExpression.fromLiteral(this, message)];
36
+ this.chunks = [TextChunkExpression.fromLiteral(message)];
37
37
  }
38
38
  }
39
39
  }