@jinntec/fore 1.0.0-4 → 1.1.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/README.md +26 -38
- package/dist/fore-dev.js +43 -0
- package/dist/fore-dev.js.map +1 -0
- package/dist/fore.js +37 -0
- package/dist/fore.js.map +1 -0
- package/index.js +4 -0
- package/package.json +42 -42
- package/resources/fore.css +186 -0
- package/resources/toastify.css +87 -0
- package/resources/{fore-styles.css → vars.css} +0 -292
- package/src/DependencyNotifyingDomFacade.js +10 -16
- package/src/ForeElementMixin.js +26 -19
- package/src/actions/abstract-action.js +30 -9
- package/src/actions/fx-action.js +6 -4
- package/src/actions/fx-append.js +8 -17
- package/src/actions/fx-confirm.js +5 -3
- package/src/actions/fx-delete.js +6 -3
- package/src/actions/fx-dispatch.js +9 -8
- package/src/actions/fx-hide.js +10 -6
- package/src/actions/fx-insert.js +49 -39
- package/src/actions/fx-message.js +3 -1
- package/src/actions/fx-refresh.js +15 -1
- package/src/actions/fx-replace.js +73 -0
- package/src/actions/fx-return.js +42 -0
- package/src/actions/fx-send.js +3 -1
- package/src/actions/fx-setfocus.js +37 -0
- package/src/actions/fx-setvalue.js +58 -51
- package/src/actions/fx-show.js +12 -4
- package/src/actions/fx-toggle.js +15 -10
- package/src/actions/fx-update.js +3 -1
- package/src/dep_graph.js +4 -2
- package/src/drawdown.js +67 -82
- package/src/fore.js +158 -12
- package/src/functions/fx-function.js +17 -3
- package/src/fx-bind.js +42 -202
- package/src/fx-fore.js +599 -567
- package/src/fx-header.js +3 -1
- package/src/fx-instance.js +9 -1
- package/src/fx-model.js +59 -23
- package/src/fx-submission.js +106 -48
- package/src/fx-var.js +7 -4
- package/src/getInScopeContext.js +37 -11
- package/src/modelitem.js +4 -4
- package/src/relevance.js +64 -0
- package/src/ui/abstract-control.js +64 -37
- package/src/ui/fx-alert.js +7 -1
- package/src/ui/fx-case.js +4 -3
- package/src/ui/fx-container.js +7 -1
- package/src/ui/fx-control.js +309 -34
- package/src/ui/fx-dialog.js +54 -40
- package/src/ui/fx-group.js +3 -1
- package/src/ui/fx-hint.js +4 -1
- package/src/ui/fx-inspector.js +120 -17
- package/src/ui/fx-items.js +8 -8
- package/src/ui/fx-output.js +16 -5
- package/src/ui/fx-repeat.js +33 -41
- package/src/ui/fx-repeatitem.js +10 -4
- package/src/ui/fx-switch.js +5 -3
- package/src/ui/fx-trigger.js +3 -1
- package/src/xpath-evaluation.js +621 -576
- package/src/xpath-util.js +15 -8
- package/dist/fore-all.js +0 -140
- package/dist/fore-debug.js +0 -140
- package/src/.DS_Store +0 -0
- package/src/actions/.DS_Store +0 -0
- package/src/ui/.DS_Store +0 -0
package/src/xpath-evaluation.js
CHANGED
|
@@ -1,67 +1,151 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
evaluateXPath as fxEvaluateXPath,
|
|
3
|
+
evaluateXPathToBoolean as fxEvaluateXPathToBoolean,
|
|
4
|
+
evaluateXPathToFirstNode as fxEvaluateXPathToFirstNode,
|
|
5
|
+
evaluateXPathToNodes as fxEvaluateXPathToNodes,
|
|
6
|
+
evaluateXPathToNumber as fxEvaluateXPathToNumber,
|
|
7
|
+
evaluateXPathToString as fxEvaluateXPathToString,
|
|
8
|
+
evaluateXPathToStrings as fxEvaluateXPathToStrings,
|
|
9
|
+
parseScript,
|
|
10
|
+
registerCustomXPathFunction,
|
|
11
|
+
registerXQueryModule,
|
|
12
12
|
} from 'fontoxpath';
|
|
13
|
-
import {
|
|
13
|
+
import {Fore} from './fore.js';
|
|
14
|
+
|
|
15
|
+
import {XPathUtil} from './xpath-util.js';
|
|
14
16
|
|
|
15
17
|
const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
16
18
|
|
|
17
19
|
const createdNamespaceResolversByXPathQueryAndNode = new Map();
|
|
18
20
|
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
[
|
|
23
|
-
// describes how we want to modify the XML - indent everything
|
|
24
|
-
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
|
|
25
|
-
' <xsl:strip-space elements="*"/>',
|
|
26
|
-
' <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
|
|
27
|
-
' <xsl:value-of select="normalize-space(.)"/>',
|
|
28
|
-
' </xsl:template>',
|
|
29
|
-
' <xsl:template match="node()|@*">',
|
|
30
|
-
' <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
|
|
31
|
-
' </xsl:template>',
|
|
32
|
-
' <xsl:output indent="yes"/>',
|
|
33
|
-
'</xsl:stylesheet>',
|
|
34
|
-
].join('\n'),
|
|
35
|
-
'application/xml',
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const xsltProcessor = new XSLTProcessor();
|
|
39
|
-
xsltProcessor.importStylesheet(xsltDoc);
|
|
40
|
-
const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
|
|
41
|
-
const resultXml = new XMLSerializer().serializeToString(resultDoc);
|
|
42
|
-
return resultXml;
|
|
43
|
-
}
|
|
21
|
+
// A global registry of function names that are declared in Fore by a developer using the
|
|
22
|
+
// `fx-function` element. These should be available without providing a prefix as well
|
|
23
|
+
export const globallyDeclaredFunctionLocalNames = [];
|
|
44
24
|
|
|
45
25
|
function getCachedNamespaceResolver(xpath, node) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
26
|
+
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).get(node) || null;
|
|
50
30
|
}
|
|
31
|
+
|
|
51
32
|
function setCachedNamespaceResolver(xpath, node, resolver) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
33
|
+
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
34
|
+
return createdNamespaceResolversByXPathQueryAndNode.set(xpath, new Map());
|
|
35
|
+
}
|
|
36
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).set(node, resolver);
|
|
56
37
|
}
|
|
57
38
|
|
|
58
39
|
const xhtmlNamespaceResolver = prefix => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
if (!prefix) {
|
|
41
|
+
return 'http://www.w3.org/1999/xhtml';
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
63
44
|
};
|
|
64
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
|
|
48
|
+
*/
|
|
49
|
+
export function resolveId(id, sourceObject, nodeName = null) {
|
|
50
|
+
const allMatchingTargetObjects = fxEvaluateXPathToNodes(
|
|
51
|
+
'outermost(ancestor-or-self::fx-fore[1]/(descendant::fx-fore|descendant::*[@id = $id]))[not(self::fx-fore)]',
|
|
52
|
+
sourceObject,
|
|
53
|
+
null,
|
|
54
|
+
{id},
|
|
55
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (allMatchingTargetObjects.length === 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
allMatchingTargetObjects.length === 1 &&
|
|
64
|
+
fxEvaluateXPathToBoolean(
|
|
65
|
+
'(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore',
|
|
66
|
+
allMatchingTargetObjects[0],
|
|
67
|
+
null,
|
|
68
|
+
null,
|
|
69
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
70
|
+
)
|
|
71
|
+
) {
|
|
72
|
+
// If the target element is not repeated, then the search for the target object is trivial since
|
|
73
|
+
// there is only one associated with the target element that bears the matching ID. This is true
|
|
74
|
+
// regardless of whether or not the source object is repeated. However, if the target element is
|
|
75
|
+
// repeated, then additional information must be used to help select a target object from among
|
|
76
|
+
// those associated with the identified target element.
|
|
77
|
+
const targetObject = allMatchingTargetObjects[0];
|
|
78
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return targetObject;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// SPEC:
|
|
85
|
+
|
|
86
|
+
// 12.2.1 References to Elements within a repeat Element
|
|
87
|
+
|
|
88
|
+
// When the target element that is identified by the IDREF of a source object has one or more
|
|
89
|
+
// repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
|
|
90
|
+
// subsets, those in common with the source element and those that are not in common. Any ancestor
|
|
91
|
+
// repeat elements of the target element not in common with the source element are descendants of
|
|
92
|
+
// the repeat elements that the source and target element have in common, if any.
|
|
93
|
+
|
|
94
|
+
// For the repeat elements that are in common, the desired target object exists in the same set of
|
|
95
|
+
// run-time objects that contains the source object. Then, for each ancestor repeat of the target
|
|
96
|
+
// element that is not in common with the source element, the current index of the repeat
|
|
97
|
+
// determines the set of run-time objects that contains the desired target object.
|
|
98
|
+
for (const ancestorRepeatItem of fxEvaluateXPathToNodes(
|
|
99
|
+
'ancestor::fx-repeatitem => reverse()',
|
|
100
|
+
sourceObject,
|
|
101
|
+
null,
|
|
102
|
+
null,
|
|
103
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
104
|
+
)) {
|
|
105
|
+
const foundTargetObjects = allMatchingTargetObjects.filter(to =>
|
|
106
|
+
ancestorRepeatItem.contains(to),
|
|
107
|
+
);
|
|
108
|
+
switch (foundTargetObjects.length) {
|
|
109
|
+
case 0:
|
|
110
|
+
// Nothing found: ignore
|
|
111
|
+
break;
|
|
112
|
+
case 1: {
|
|
113
|
+
// A single one is found: the target object is directly in a common repeat
|
|
114
|
+
const targetObject = foundTargetObjects[0];
|
|
115
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return targetObject;
|
|
119
|
+
}
|
|
120
|
+
default: {
|
|
121
|
+
// Multiple target objects are found: they are in a repeat that is not common with the source object
|
|
122
|
+
// We found a target object in a common repeat! We now need to find the one that is in the repeatitem identified at the current index
|
|
123
|
+
const targetObject = foundTargetObjects.find(to =>
|
|
124
|
+
fxEvaluateXPathToNodes(
|
|
125
|
+
'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
|
|
126
|
+
to,
|
|
127
|
+
null,
|
|
128
|
+
{},
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
if (!targetObject) {
|
|
132
|
+
// Nothing valid found for whatever reason. This might be something dynamic?
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return targetObject;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// We found no target objects in common repeats. The id is unresolvable
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Make namespace resolving use the `instance` element that is related to here
|
|
147
|
+
const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
|
|
148
|
+
|
|
65
149
|
/**
|
|
66
150
|
* Resolve a namespace. Needs a namespace prefix and the element that is most closely related to the
|
|
67
151
|
* XPath in which the namespace is being resolved. The prefix will be resolved by using the
|
|
@@ -77,144 +161,148 @@ const xhtmlNamespaceResolver = prefix => {
|
|
|
77
161
|
* @param {string} prefix The prefix to resolve
|
|
78
162
|
*/
|
|
79
163
|
function createNamespaceResolver(xpathQuery, formElement) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Make namespace resolving use the `instance` element that is related to here
|
|
86
|
-
const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
|
|
164
|
+
const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
|
|
165
|
+
if (cachedResolver) {
|
|
166
|
+
return cachedResolver;
|
|
167
|
+
}
|
|
87
168
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
169
|
+
const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
|
|
170
|
+
let instanceReferences = fxEvaluateXPathToStrings(
|
|
171
|
+
`descendant::xqx:functionCallExpr
|
|
91
172
|
[xqx:functionName = "instance"]
|
|
92
173
|
/xqx:arguments
|
|
93
174
|
/xqx:stringConstantExpr
|
|
94
175
|
/xqx:value`,
|
|
95
|
-
|
|
96
|
-
null,
|
|
97
|
-
{},
|
|
98
|
-
{
|
|
99
|
-
namespaceResolver: prefix =>
|
|
100
|
-
prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
if (instanceReferences.length === 0) {
|
|
104
|
-
// No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
|
|
105
|
-
const ancestorComponent = fxEvaluateXPathToFirstNode('ancestor::*[@ref][1]', formElement);
|
|
106
|
-
if (ancestorComponent) {
|
|
107
|
-
const resolver = createNamespaceResolver(
|
|
108
|
-
ancestorComponent.getAttribute('ref'),
|
|
109
|
-
ancestorComponent,
|
|
110
|
-
);
|
|
111
|
-
setCachedNamespaceResolver(xpathQuery, formElement, resolver);
|
|
112
|
-
return resolver;
|
|
113
|
-
}
|
|
114
|
-
// Nothing found: let's just assume we're supposed to use the `default` instance
|
|
115
|
-
instanceReferences = ['default'];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (instanceReferences.length === 1) {
|
|
119
|
-
// console.log(`resolving ${xpathQuery} with ${instanceReferences[0]}`);
|
|
120
|
-
let instance;
|
|
121
|
-
if (instanceReferences[0] === 'default') {
|
|
122
|
-
const actualForeElement = fxEvaluateXPathToFirstNode(
|
|
123
|
-
'ancestor-or-self::fx-fore',
|
|
124
|
-
formElement,
|
|
176
|
+
xpathAST,
|
|
125
177
|
null,
|
|
126
|
-
|
|
127
|
-
{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
178
|
+
{},
|
|
179
|
+
{
|
|
180
|
+
namespaceResolver: prefix =>
|
|
181
|
+
prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
if (instanceReferences.length === 0) {
|
|
185
|
+
// No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
|
|
186
|
+
const ancestorComponent = fxEvaluateXPathToFirstNode('ancestor::*[@ref][1]', formElement);
|
|
187
|
+
if (ancestorComponent) {
|
|
188
|
+
const resolver = createNamespaceResolver(
|
|
189
|
+
ancestorComponent.getAttribute('ref'),
|
|
190
|
+
ancestorComponent,
|
|
191
|
+
);
|
|
192
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolver);
|
|
193
|
+
return resolver;
|
|
194
|
+
}
|
|
195
|
+
// Nothing found: let's just assume we're supposed to use the `default` instance
|
|
196
|
+
instanceReferences = ['default'];
|
|
133
197
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
198
|
+
|
|
199
|
+
if (instanceReferences.length === 1) {
|
|
200
|
+
// console.log(`resolving ${xpathQuery} with ${instanceReferences[0]}`);
|
|
201
|
+
let instance;
|
|
202
|
+
if (instanceReferences[0] === 'default') {
|
|
203
|
+
const actualForeElement = fxEvaluateXPathToFirstNode(
|
|
204
|
+
'ancestor-or-self::fx-fore[1]',
|
|
205
|
+
formElement,
|
|
206
|
+
null,
|
|
207
|
+
null,
|
|
208
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
instance = actualForeElement && actualForeElement.querySelector('fx-instance');
|
|
212
|
+
} else {
|
|
213
|
+
instance = resolveId(instanceReferences[0], formElement, 'fx-instance');
|
|
214
|
+
}
|
|
215
|
+
if (instance && instance.hasAttribute('xpath-default-namespace')) {
|
|
216
|
+
const xpathDefaultNamespace = instance.getAttribute('xpath-default-namespace');
|
|
217
|
+
/*
|
|
218
|
+
console.log(
|
|
219
|
+
`Resolving the xpath ${xpathQuery} with the default namespace set to ${xpathDefaultNamespace}`,
|
|
220
|
+
);
|
|
221
|
+
*/
|
|
222
|
+
const resolveNamespacePrefix = prefix => {
|
|
223
|
+
if (!prefix) {
|
|
224
|
+
return xpathDefaultNamespace;
|
|
225
|
+
}
|
|
226
|
+
return undefined;
|
|
227
|
+
};
|
|
228
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
229
|
+
return resolveNamespacePrefix;
|
|
144
230
|
}
|
|
145
|
-
return undefined;
|
|
146
|
-
};
|
|
147
|
-
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
148
|
-
return resolveNamespacePrefix;
|
|
149
231
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
232
|
+
if (instanceReferences.length > 1) {
|
|
233
|
+
console.warn(
|
|
234
|
+
`More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
156
237
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
238
|
+
const xpathDefaultNamespace =
|
|
239
|
+
fxEvaluateXPathToString('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) ||
|
|
240
|
+
'';
|
|
160
241
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
242
|
+
const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
|
|
243
|
+
if (prefix === '') {
|
|
244
|
+
return xpathDefaultNamespace;
|
|
245
|
+
}
|
|
165
246
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
247
|
+
// Note: ideally we should use Node#lookupNamespaceURI. However, the nodes we are passed are
|
|
248
|
+
// XML. The best we can do is emulate the `xmlns:xxx` namespace declarations by regarding them as
|
|
249
|
+
// attributes. Which they technically ARE NOT!
|
|
169
250
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
251
|
+
return fxEvaluateXPathToString(
|
|
252
|
+
'ancestor-or-self::*/@*[name() = "xmlns:" || $prefix][last()]',
|
|
253
|
+
formElement,
|
|
254
|
+
null,
|
|
255
|
+
{prefix},
|
|
256
|
+
);
|
|
257
|
+
};
|
|
177
258
|
|
|
178
|
-
|
|
179
|
-
|
|
259
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
260
|
+
return resolveNamespacePrefix;
|
|
180
261
|
}
|
|
181
262
|
|
|
182
263
|
function createNamespaceResolverForNode(query, contextNode, formElement) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
264
|
+
if (((contextNode && contextNode.ownerDocument) || contextNode) === window.document) {
|
|
265
|
+
// Running a query on the HTML DOM. Don't bother resolving namespaces in any other way
|
|
266
|
+
return xhtmlNamespaceResolver;
|
|
267
|
+
}
|
|
268
|
+
return createNamespaceResolver(query, formElement);
|
|
188
269
|
}
|
|
189
270
|
|
|
271
|
+
|
|
190
272
|
/**
|
|
191
273
|
* Implementation of the functionNameResolver passed to FontoXPath to
|
|
192
274
|
* redirect function resolving for unprefixed functions to either the fn or the xf namespace
|
|
193
275
|
*/
|
|
194
276
|
// eslint-disable-next-line no-unused-vars
|
|
195
|
-
function functionNameResolver({
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
277
|
+
function functionNameResolver({prefix, localName}, _arity) {
|
|
278
|
+
switch (localName) {
|
|
279
|
+
// TODO: put the full XForms library functions set here
|
|
280
|
+
case 'context':
|
|
281
|
+
case 'base64encode':
|
|
282
|
+
case 'boolean-from-string':
|
|
283
|
+
case 'current':
|
|
284
|
+
case 'depends':
|
|
285
|
+
case 'event':
|
|
286
|
+
case 'index':
|
|
287
|
+
case 'instance':
|
|
288
|
+
case 'log':
|
|
289
|
+
case 'parse':
|
|
290
|
+
case 'logtree':
|
|
291
|
+
return {namespaceURI: XFORMS_NAMESPACE_URI, localName};
|
|
292
|
+
default:
|
|
293
|
+
if (prefix === '' && globallyDeclaredFunctionLocalNames.includes(localName)) {
|
|
294
|
+
// The function has been declared without a prefix and is called here without a prefix.
|
|
295
|
+
// Just make this work. It is the developer-friendly way
|
|
296
|
+
return {namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName};
|
|
297
|
+
}
|
|
298
|
+
if (prefix === 'fn' || prefix === '') {
|
|
299
|
+
return {namespaceURI: 'http://www.w3.org/2005/xpath-functions', localName};
|
|
300
|
+
}
|
|
301
|
+
if (prefix === 'local') {
|
|
302
|
+
return {namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName};
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
218
306
|
}
|
|
219
307
|
|
|
220
308
|
/**
|
|
@@ -226,21 +314,23 @@ function functionNameResolver({ prefix, localName }, _arity) {
|
|
|
226
314
|
* @return {Object} A key-value mapping of the variables
|
|
227
315
|
*/
|
|
228
316
|
function getVariablesInScope(formElement) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
317
|
+
let closestActualFormElement = formElement;
|
|
318
|
+
while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
|
|
319
|
+
closestActualFormElement = closestActualFormElement.parentNode;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!closestActualFormElement) {
|
|
323
|
+
return {};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const variables = {};
|
|
327
|
+
if (closestActualFormElement.inScopeVariables) {
|
|
328
|
+
for (const key of closestActualFormElement.inScopeVariables.keys()) {
|
|
329
|
+
const varElement = closestActualFormElement.inScopeVariables.get(key);
|
|
330
|
+
variables[key] = varElement.value;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return variables;
|
|
244
334
|
}
|
|
245
335
|
|
|
246
336
|
/**
|
|
@@ -251,25 +341,26 @@ function getVariablesInScope(formElement) {
|
|
|
251
341
|
* @param {Node} contextNode The start of the XPath
|
|
252
342
|
* @param {{parentNode}|ForeElementMixin} formElement The form element associated to the XPath
|
|
253
343
|
*/
|
|
254
|
-
export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
|
|
255
|
-
|
|
256
|
-
|
|
344
|
+
export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options={}) {
|
|
345
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
346
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
257
347
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
348
|
+
return fxEvaluateXPath(
|
|
349
|
+
xpath,
|
|
350
|
+
contextNode,
|
|
351
|
+
null,
|
|
352
|
+
{...variablesInScope, ...variables},
|
|
353
|
+
fxEvaluateXPath.ALL_RESULTS_TYPE,
|
|
354
|
+
{
|
|
355
|
+
currentContext: {formElement, variables},
|
|
356
|
+
moduleImports: {
|
|
357
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
358
|
+
},
|
|
359
|
+
functionNameResolver,
|
|
360
|
+
namespaceResolver,
|
|
361
|
+
language: options.language || evaluateXPath.XPATH_3_1
|
|
362
|
+
},
|
|
363
|
+
);
|
|
273
364
|
}
|
|
274
365
|
|
|
275
366
|
/**
|
|
@@ -281,16 +372,17 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
|
|
|
281
372
|
* @return {Node} The first node found by the XPath
|
|
282
373
|
*/
|
|
283
374
|
export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
375
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
376
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
377
|
+
return fxEvaluateXPathToFirstNode(xpath, contextNode, null, variablesInScope, {
|
|
378
|
+
defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
|
|
379
|
+
moduleImports: {
|
|
380
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
381
|
+
},
|
|
382
|
+
currentContext: {formElement},
|
|
383
|
+
functionNameResolver,
|
|
384
|
+
namespaceResolver,
|
|
385
|
+
});
|
|
294
386
|
}
|
|
295
387
|
|
|
296
388
|
/**
|
|
@@ -302,17 +394,17 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
|
302
394
|
* @return {Node[]} All nodes
|
|
303
395
|
*/
|
|
304
396
|
export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
397
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
398
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
399
|
+
|
|
400
|
+
return fxEvaluateXPathToNodes(xpath, contextNode, null, variablesInScope, {
|
|
401
|
+
currentContext: {formElement},
|
|
402
|
+
functionNameResolver,
|
|
403
|
+
moduleImports: {
|
|
404
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
405
|
+
},
|
|
406
|
+
namespaceResolver,
|
|
407
|
+
});
|
|
316
408
|
}
|
|
317
409
|
|
|
318
410
|
/**
|
|
@@ -324,17 +416,17 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
|
324
416
|
* @return {boolean}
|
|
325
417
|
*/
|
|
326
418
|
export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
419
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
420
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
421
|
+
|
|
422
|
+
return fxEvaluateXPathToBoolean(xpath, contextNode, null, variablesInScope, {
|
|
423
|
+
currentContext: {formElement},
|
|
424
|
+
functionNameResolver,
|
|
425
|
+
moduleImports: {
|
|
426
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
427
|
+
},
|
|
428
|
+
namespaceResolver,
|
|
429
|
+
});
|
|
338
430
|
}
|
|
339
431
|
|
|
340
432
|
/**
|
|
@@ -349,23 +441,23 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
|
349
441
|
* @return {string}
|
|
350
442
|
*/
|
|
351
443
|
export function evaluateXPathToString(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
444
|
+
xpath,
|
|
445
|
+
contextNode,
|
|
446
|
+
formElement,
|
|
447
|
+
domFacade = null,
|
|
448
|
+
namespaceReferenceNode = formElement,
|
|
357
449
|
) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
450
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
451
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
452
|
+
|
|
453
|
+
return fxEvaluateXPathToString(xpath, contextNode, domFacade, variablesInScope, {
|
|
454
|
+
currentContext: {formElement},
|
|
455
|
+
functionNameResolver,
|
|
456
|
+
moduleImports: {
|
|
457
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
458
|
+
},
|
|
459
|
+
namespaceResolver,
|
|
460
|
+
});
|
|
369
461
|
}
|
|
370
462
|
|
|
371
463
|
/**
|
|
@@ -380,28 +472,28 @@ export function evaluateXPathToString(
|
|
|
380
472
|
* @return {string}
|
|
381
473
|
*/
|
|
382
474
|
export function evaluateXPathToStrings(
|
|
383
|
-
xpath,
|
|
384
|
-
contextNode,
|
|
385
|
-
formElement,
|
|
386
|
-
domFacade = null,
|
|
387
|
-
namespaceReferenceNode = formElement,
|
|
388
|
-
) {
|
|
389
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
390
|
-
return fxEvaluateXPathToStrings(
|
|
391
475
|
xpath,
|
|
392
476
|
contextNode,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
477
|
+
formElement,
|
|
478
|
+
domFacade = null,
|
|
479
|
+
namespaceReferenceNode = formElement,
|
|
480
|
+
) {
|
|
481
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
482
|
+
return fxEvaluateXPathToStrings(
|
|
483
|
+
xpath,
|
|
484
|
+
contextNode,
|
|
485
|
+
domFacade,
|
|
486
|
+
{},
|
|
487
|
+
|
|
488
|
+
{
|
|
489
|
+
currentContext: {formElement},
|
|
490
|
+
functionNameResolver,
|
|
491
|
+
moduleImports: {
|
|
492
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
493
|
+
},
|
|
494
|
+
namespaceResolver,
|
|
495
|
+
},
|
|
496
|
+
);
|
|
405
497
|
}
|
|
406
498
|
|
|
407
499
|
/**
|
|
@@ -416,148 +508,54 @@ export function evaluateXPathToStrings(
|
|
|
416
508
|
* @return {Number}
|
|
417
509
|
*/
|
|
418
510
|
export function evaluateXPathToNumber(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
511
|
+
xpath,
|
|
512
|
+
contextNode,
|
|
513
|
+
formElement,
|
|
514
|
+
domFacade = null,
|
|
515
|
+
namespaceReferenceNode = formElement,
|
|
424
516
|
) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
|
|
440
|
-
*/
|
|
441
|
-
export function resolveId(id, sourceObject, nodeName = null) {
|
|
442
|
-
const allMatchingTargetObjects = fxEvaluateXPathToNodes(
|
|
443
|
-
'outermost(ancestor-or-self::fx-fore[1]/(descendant::xf-fore|descendant::*[@id = $id]))[not(self::fx-fore)]',
|
|
444
|
-
sourceObject,
|
|
445
|
-
null,
|
|
446
|
-
{ id },
|
|
447
|
-
{ namespaceResolver: xhtmlNamespaceResolver },
|
|
448
|
-
);
|
|
449
|
-
|
|
450
|
-
if (allMatchingTargetObjects.length === 0) {
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (
|
|
455
|
-
allMatchingTargetObjects.length === 1 &&
|
|
456
|
-
fxEvaluateXPathToBoolean(
|
|
457
|
-
'(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore',
|
|
458
|
-
allMatchingTargetObjects[0],
|
|
459
|
-
null,
|
|
460
|
-
null,
|
|
461
|
-
{ namespaceResolver: xhtmlNamespaceResolver },
|
|
462
|
-
)
|
|
463
|
-
) {
|
|
464
|
-
// If the target element is not repeated, then the search for the target object is trivial since
|
|
465
|
-
// there is only one associated with the target element that bears the matching ID. This is true
|
|
466
|
-
// regardless of whether or not the source object is repeated. However, if the target element is
|
|
467
|
-
// repeated, then additional information must be used to help select a target object from among
|
|
468
|
-
// those associated with the identified target element.
|
|
469
|
-
const targetObject = allMatchingTargetObjects[0];
|
|
470
|
-
if (nodeName && targetObject.localName !== nodeName) {
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
473
|
-
return targetObject;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// SPEC:
|
|
477
|
-
|
|
478
|
-
// 12.2.1 References to Elements within a repeat Element
|
|
479
|
-
|
|
480
|
-
// When the target element that is identified by the IDREF of a source object has one or more
|
|
481
|
-
// repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
|
|
482
|
-
// subsets, those in common with the source element and those that are not in common. Any ancestor
|
|
483
|
-
// repeat elements of the target element not in common with the source element are descendants of
|
|
484
|
-
// the repeat elements that the source and target element have in common, if any.
|
|
485
|
-
|
|
486
|
-
// For the repeat elements that are in common, the desired target object exists in the same set of
|
|
487
|
-
// run-time objects that contains the source object. Then, for each ancestor repeat of the target
|
|
488
|
-
// element that is not in common with the source element, the current index of the repeat
|
|
489
|
-
// determines the set of run-time objects that contains the desired target object.
|
|
490
|
-
for (const ancestorRepeatItem of fxEvaluateXPathToNodes(
|
|
491
|
-
'ancestor::fx-repeatitem => reverse()',
|
|
492
|
-
sourceObject,
|
|
493
|
-
null,
|
|
494
|
-
null,
|
|
495
|
-
{ namespaceResolver: xhtmlNamespaceResolver },
|
|
496
|
-
)) {
|
|
497
|
-
const foundTargetObjects = allMatchingTargetObjects.filter(to =>
|
|
498
|
-
ancestorRepeatItem.contains(to),
|
|
499
|
-
);
|
|
500
|
-
switch (foundTargetObjects.length) {
|
|
501
|
-
case 0:
|
|
502
|
-
// Nothing found: ignore
|
|
503
|
-
break;
|
|
504
|
-
case 1: {
|
|
505
|
-
// A single one is found: the target object is directly in a common repeat
|
|
506
|
-
const targetObject = foundTargetObjects[0];
|
|
507
|
-
if (nodeName && targetObject.localName !== nodeName) {
|
|
508
|
-
return null;
|
|
509
|
-
}
|
|
510
|
-
return targetObject;
|
|
511
|
-
}
|
|
512
|
-
default: {
|
|
513
|
-
// Multiple target objects are found: they are in a repeat that is not common with the source object
|
|
514
|
-
// We found a target object in a common repeat! We now need to find the one that is in the repeatitem identified at the current index
|
|
515
|
-
const targetObject = foundTargetObjects.find(to =>
|
|
516
|
-
fxEvaluateXPathToNodes(
|
|
517
|
-
'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
|
|
518
|
-
to,
|
|
519
|
-
null,
|
|
520
|
-
{},
|
|
521
|
-
),
|
|
522
|
-
);
|
|
523
|
-
if (!targetObject) {
|
|
524
|
-
// Nothing valid found for whatever reason. This might be something dynamic?
|
|
525
|
-
return null;
|
|
526
|
-
}
|
|
527
|
-
if (nodeName && targetObject.localName !== nodeName) {
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
return targetObject;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
// We found no target objects in common repeats. The id is unresolvable
|
|
535
|
-
return null;
|
|
517
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
518
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
519
|
+
|
|
520
|
+
return fxEvaluateXPathToNumber(xpath, contextNode, domFacade, variablesInScope, {
|
|
521
|
+
currentContext: {formElement},
|
|
522
|
+
functionNameResolver,
|
|
523
|
+
moduleImports: {
|
|
524
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
525
|
+
},
|
|
526
|
+
namespaceResolver,
|
|
527
|
+
});
|
|
536
528
|
}
|
|
537
529
|
|
|
538
530
|
const contextFunction = (dynamicContext, string) => {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
531
|
+
const caller = dynamicContext.currentContext.formElement;
|
|
532
|
+
if (string) {
|
|
533
|
+
const instance = resolveId(string, caller);
|
|
534
|
+
if (instance) {
|
|
535
|
+
if (instance.nodeName === 'FX-REPEAT') {
|
|
536
|
+
const {nodeset} = instance;
|
|
537
|
+
for (let parent = caller; parent; parent = parent.parentNode) {
|
|
538
|
+
if (parent.parentNode === instance) {
|
|
539
|
+
const offset = Array.from(parent.parentNode.children).indexOf(parent);
|
|
540
|
+
return nodeset[offset];
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return instance.nodeset;
|
|
550
545
|
}
|
|
551
|
-
}
|
|
552
|
-
return instance.nodeset;
|
|
553
546
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
// const p = dynamicContext.domFacade.getParentElement();
|
|
547
|
+
const parent = XPathUtil.getParentBindingElement(caller);
|
|
548
|
+
// const p = caller.nodeName;
|
|
549
|
+
// const p = dynamicContext.domFacade.getParentElement();
|
|
558
550
|
|
|
559
|
-
|
|
560
|
-
|
|
551
|
+
if (parent) return parent.nodeset;
|
|
552
|
+
return caller.getInScopeContext();
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// todo: implement
|
|
556
|
+
const currentFunction = (dynamicContext, string) => {
|
|
557
|
+
const caller = dynamicContext.currentContext.formElement;
|
|
558
|
+
return null;
|
|
561
559
|
};
|
|
562
560
|
|
|
563
561
|
/**
|
|
@@ -565,10 +563,10 @@ const contextFunction = (dynamicContext, string) => {
|
|
|
565
563
|
* @return instance data for given id serialized to string.
|
|
566
564
|
*/
|
|
567
565
|
registerCustomXPathFunction(
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
566
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context'},
|
|
567
|
+
[],
|
|
568
|
+
'item()?',
|
|
569
|
+
contextFunction,
|
|
572
570
|
);
|
|
573
571
|
|
|
574
572
|
/**
|
|
@@ -576,10 +574,17 @@ registerCustomXPathFunction(
|
|
|
576
574
|
* @return instance data for given id serialized to string.
|
|
577
575
|
*/
|
|
578
576
|
registerCustomXPathFunction(
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
577
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context'},
|
|
578
|
+
['xs:string'],
|
|
579
|
+
'item()?',
|
|
580
|
+
contextFunction,
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
registerCustomXPathFunction(
|
|
584
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'current'},
|
|
585
|
+
['xs:string'],
|
|
586
|
+
'item()?',
|
|
587
|
+
currentFunction,
|
|
583
588
|
);
|
|
584
589
|
|
|
585
590
|
/**
|
|
@@ -587,68 +592,94 @@ registerCustomXPathFunction(
|
|
|
587
592
|
* @return instance data for given id serialized to string.
|
|
588
593
|
*/
|
|
589
594
|
registerCustomXPathFunction(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
595
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'log'},
|
|
596
|
+
['xs:string?'],
|
|
597
|
+
'xs:string?',
|
|
598
|
+
(dynamicContext, string) => {
|
|
599
|
+
const {formElement} = dynamicContext.currentContext;
|
|
600
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
601
|
+
if (instance) {
|
|
602
|
+
if (instance.getAttribute('type') === 'json') {
|
|
603
|
+
console.warn('log() does not work for JSON yet');
|
|
604
|
+
// return JSON.stringify(instance.getDefaultContext());
|
|
605
|
+
} else {
|
|
606
|
+
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
607
|
+
return Fore.prettifyXml(def);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
},
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
registerCustomXPathFunction(
|
|
615
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'parse'},
|
|
616
|
+
['xs:string?'],
|
|
617
|
+
'element()?',
|
|
618
|
+
(dynamicContext, string) => {
|
|
619
|
+
const parser = new DOMParser();
|
|
620
|
+
const out = parser.parseFromString(string, "application/xml");
|
|
621
|
+
console.log('parse', out);
|
|
622
|
+
|
|
623
|
+
/*
|
|
624
|
+
const {formElement} = dynamicContext.currentContext;
|
|
625
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
626
|
+
if (instance) {
|
|
627
|
+
if (instance.getAttribute('type') === 'json') {
|
|
628
|
+
console.warn('log() does not work for JSON yet');
|
|
629
|
+
// return JSON.stringify(instance.getDefaultContext());
|
|
630
|
+
} else {
|
|
631
|
+
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
632
|
+
return Fore.prettifyXml(def);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
*/
|
|
636
|
+
return out.firstElementChild;
|
|
637
|
+
},
|
|
607
638
|
);
|
|
608
639
|
|
|
609
640
|
function buildTree(tree, data) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
641
|
+
if (!data) return;
|
|
642
|
+
if (data.nodeType === Node.ELEMENT_NODE) {
|
|
643
|
+
if (data.children) {
|
|
644
|
+
const details = document.createElement('details');
|
|
645
|
+
details.setAttribute('data-path', data.nodeName);
|
|
646
|
+
const summary = document.createElement('summary');
|
|
647
|
+
|
|
648
|
+
let display = ` <${data.nodeName}`;
|
|
649
|
+
Array.from(data.attributes).forEach(attr => {
|
|
650
|
+
display += ` ${attr.nodeName}="${attr.nodeValue}"`;
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
let contents;
|
|
654
|
+
if (
|
|
655
|
+
data.firstChild &&
|
|
656
|
+
data.firstChild.nodeType === Node.TEXT_NODE &&
|
|
657
|
+
data.firstChild.data.trim() !== ''
|
|
658
|
+
) {
|
|
659
|
+
// console.log('whoooooooooopp');
|
|
660
|
+
contents = data.firstChild.nodeValue;
|
|
661
|
+
display += `>${contents}</${data.nodeName}>`;
|
|
662
|
+
} else {
|
|
663
|
+
display += '>';
|
|
664
|
+
}
|
|
665
|
+
summary.textContent = display;
|
|
666
|
+
|
|
667
|
+
details.appendChild(summary);
|
|
668
|
+
if (data.childElementCount !== 0) {
|
|
669
|
+
details.setAttribute('open', 'open');
|
|
670
|
+
} else {
|
|
671
|
+
summary.setAttribute('style', 'list-style:none;');
|
|
672
|
+
}
|
|
673
|
+
tree.appendChild(details);
|
|
674
|
+
|
|
675
|
+
Array.from(data.children).forEach(child => {
|
|
676
|
+
// if(child.nodeType === Node.ELEMENT_NODE){
|
|
677
|
+
// child.parentNode.appendChild(buildTree(child));
|
|
678
|
+
buildTree(details, child);
|
|
679
|
+
// }
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
} /* else if(data.nodeType === Node.ATTRIBUTE_NODE){
|
|
652
683
|
//create span for now
|
|
653
684
|
// const span = document.createElement('span');
|
|
654
685
|
// span.style.background = 'grey';
|
|
@@ -659,125 +690,139 @@ function buildTree(tree, data) {
|
|
|
659
690
|
tree.textContent = data;
|
|
660
691
|
} */
|
|
661
692
|
|
|
662
|
-
|
|
693
|
+
// return tree;
|
|
663
694
|
}
|
|
664
695
|
|
|
665
696
|
registerCustomXPathFunction(
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
697
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'logtree'},
|
|
698
|
+
['xs:string?'],
|
|
699
|
+
'element()?',
|
|
700
|
+
(dynamicContext, string) => {
|
|
701
|
+
const {formElement} = dynamicContext.currentContext;
|
|
702
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
703
|
+
|
|
704
|
+
if (instance) {
|
|
705
|
+
// const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
706
|
+
// const def = JSON.stringify(instance.getDefaultContext());
|
|
707
|
+
|
|
708
|
+
const treeDiv = document.createElement('div');
|
|
709
|
+
treeDiv.setAttribute('class', 'logtree');
|
|
710
|
+
// const datatree = buildTree(tree,instance.getDefaultContext());
|
|
711
|
+
// return tree.appendChild(datatree);
|
|
712
|
+
// return buildTree(root,instance.getDefaultContext());;
|
|
713
|
+
const form = dynamicContext.currentContext.formElement;
|
|
714
|
+
const logtree = form.querySelector('.logtree');
|
|
715
|
+
if (logtree) {
|
|
716
|
+
logtree.parentNode.removeChild(logtree);
|
|
717
|
+
}
|
|
718
|
+
const tree = buildTree(treeDiv, instance.getDefaultContext());
|
|
719
|
+
if (tree) {
|
|
720
|
+
form.appendChild(tree);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return null;
|
|
724
|
+
},
|
|
694
725
|
);
|
|
695
726
|
|
|
696
727
|
const instance = (dynamicContext, string) => {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const formElement = fxEvaluateXPathToFirstNode(
|
|
701
|
-
'ancestor-or-self::fx-fore',
|
|
702
|
-
dynamicContext.currentContext.formElement,
|
|
703
|
-
null,
|
|
704
|
-
null,
|
|
705
|
-
{ namespaceResolver: xhtmlNamespaceResolver },
|
|
706
|
-
);
|
|
707
|
-
|
|
708
|
-
// console.log('fnInstance dynamicContext: ', dynamicContext);
|
|
709
|
-
// console.log('fnInstance string: ', string);
|
|
710
|
-
|
|
711
|
-
const inst = string
|
|
712
|
-
? resolveId(string, formElement, 'fx-instance')
|
|
713
|
-
: formElement.querySelector(`fx-instance`);
|
|
714
|
-
|
|
715
|
-
// const def = instance.getInstanceData();
|
|
716
|
-
if (inst) {
|
|
717
|
-
const def = inst.getDefaultContext();
|
|
718
|
-
// console.log('target instance root node: ', def);
|
|
719
|
-
|
|
720
|
-
return def;
|
|
721
|
-
}
|
|
722
|
-
return null;
|
|
723
|
-
};
|
|
728
|
+
// Spec: https://www.w3.org/TR/xforms-xpath/#The_XForms_Function_Library#The_instance.28.29_Function
|
|
729
|
+
// TODO: handle no string passed (null will be passed instead)
|
|
724
730
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
731
|
+
const formElement = fxEvaluateXPathToFirstNode(
|
|
732
|
+
'ancestor-or-self::fx-fore[1]',
|
|
733
|
+
dynamicContext.currentContext.formElement,
|
|
734
|
+
null,
|
|
735
|
+
null,
|
|
736
|
+
{namespaceResolver: xhtmlNamespaceResolver},
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
// console.log('fnInstance dynamicContext: ', dynamicContext);
|
|
740
|
+
// console.log('fnInstance string: ', string);
|
|
741
|
+
|
|
742
|
+
const inst = string
|
|
743
|
+
? resolveId(string, formElement, 'fx-instance')
|
|
744
|
+
: formElement.querySelector(`fx-instance`);
|
|
735
745
|
|
|
736
746
|
// const def = instance.getInstanceData();
|
|
737
|
-
if (
|
|
738
|
-
|
|
747
|
+
if (inst) {
|
|
748
|
+
const def = inst.getDefaultContext();
|
|
749
|
+
// console.log('target instance root node: ', def);
|
|
750
|
+
|
|
751
|
+
return def;
|
|
739
752
|
}
|
|
740
|
-
return
|
|
741
|
-
|
|
753
|
+
return null;
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
registerCustomXPathFunction(
|
|
757
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'index'},
|
|
758
|
+
['xs:string?'],
|
|
759
|
+
'xs:integer?',
|
|
760
|
+
(dynamicContext, string) => {
|
|
761
|
+
const {formElement} = dynamicContext.currentContext;
|
|
762
|
+
if (string === null) {
|
|
763
|
+
return 1;
|
|
764
|
+
}
|
|
765
|
+
const repeat = resolveId(string, formElement, 'fx-repeat');
|
|
766
|
+
|
|
767
|
+
// const def = instance.getInstanceData();
|
|
768
|
+
if (repeat) {
|
|
769
|
+
return repeat.getAttribute('index');
|
|
770
|
+
}
|
|
771
|
+
return Number(1);
|
|
772
|
+
},
|
|
742
773
|
);
|
|
743
774
|
|
|
744
775
|
// Note that this is not to spec. The spec enforces elements to be returned from the
|
|
745
776
|
// instance. However, we allow instances to actually be JSON!
|
|
746
777
|
registerCustomXPathFunction(
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
778
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance'},
|
|
779
|
+
[],
|
|
780
|
+
'item()?',
|
|
781
|
+
domFacade => instance(domFacade, null),
|
|
751
782
|
);
|
|
752
783
|
|
|
753
784
|
registerCustomXPathFunction(
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
785
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'instance'},
|
|
786
|
+
['xs:string?'],
|
|
787
|
+
'item()?',
|
|
788
|
+
instance,
|
|
758
789
|
);
|
|
759
790
|
|
|
760
791
|
registerCustomXPathFunction(
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
792
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'depends'},
|
|
793
|
+
['node()*'],
|
|
794
|
+
'item()?',
|
|
795
|
+
(dynamicContext, nodes) =>
|
|
796
|
+
// console.log('depends on : ', nodes[0]);
|
|
797
|
+
nodes[0],
|
|
767
798
|
);
|
|
768
799
|
|
|
769
800
|
registerCustomXPathFunction(
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
801
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'event'},
|
|
802
|
+
['xs:string?'],
|
|
803
|
+
'item()?',
|
|
804
|
+
(dynamicContext, arg) => {
|
|
805
|
+
if (!arg) return [];
|
|
806
|
+
|
|
807
|
+
if (dynamicContext.currentContext.variables) {
|
|
808
|
+
const payload = dynamicContext.currentContext.variables[arg];
|
|
809
|
+
if (payload.nodeType) {
|
|
810
|
+
console.log('got some node as js object');
|
|
811
|
+
}
|
|
812
|
+
if (payload) {
|
|
813
|
+
return dynamicContext.currentContext.variables[arg];
|
|
814
|
+
}
|
|
815
|
+
}
|
|
778
816
|
|
|
779
|
-
|
|
780
|
-
|
|
817
|
+
if (dynamicContext.currentContext.formElement.inScopeVariables) {
|
|
818
|
+
console.log('event()', dynamicContext.currentContext.formElement.inScopeVariables);
|
|
819
|
+
console.log('event()', dynamicContext.currentContext.formElement.inScopeVariables.get(arg));
|
|
820
|
+
// dynamicContext.currentContext.variables = dynamicContext.currentContext.formElement.inScopeVariables;
|
|
821
|
+
return dynamicContext.currentContext.formElement.inScopeVariables.get(arg);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
return [];
|
|
825
|
+
},
|
|
781
826
|
);
|
|
782
827
|
|
|
783
828
|
// Implement the XForms standard functions here.
|
|
@@ -791,34 +836,34 @@ registerXQueryModule(`
|
|
|
791
836
|
|
|
792
837
|
// How to run XQUERY:
|
|
793
838
|
/**
|
|
794
|
-
registerXQueryModule(`
|
|
795
|
-
module namespace my-custom-namespace = "my-custom-uri";
|
|
796
|
-
(:~
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
declare %public %updating function my-custom-namespace:do-something ($ele as element()) as xs:boolean {
|
|
839
|
+
registerXQueryModule(`
|
|
840
|
+
module namespace my-custom-namespace = "my-custom-uri";
|
|
841
|
+
(:~
|
|
842
|
+
Insert attribute somewhere
|
|
843
|
+
~:)
|
|
844
|
+
declare %public %updating function my-custom-namespace:do-something ($ele as element()) as xs:boolean {
|
|
800
845
|
if ($ele/@done) then false() else
|
|
801
846
|
(insert node
|
|
802
847
|
attribute done {"true"}
|
|
803
848
|
into $ele, true())
|
|
804
849
|
};
|
|
805
|
-
`)
|
|
806
|
-
// At some point:
|
|
807
|
-
const contextNode = null;
|
|
808
|
-
const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
|
|
850
|
+
`)
|
|
851
|
+
// At some point:
|
|
852
|
+
const contextNode = null;
|
|
853
|
+
const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
|
|
809
854
|
|
|
810
|
-
console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
|
|
855
|
+
console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
|
|
811
856
|
|
|
812
|
-
executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
|
|
813
|
-
*/
|
|
857
|
+
executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
|
|
858
|
+
*/
|
|
814
859
|
|
|
815
860
|
/**
|
|
816
861
|
* @param input as string
|
|
817
862
|
* @return {string}
|
|
818
863
|
*/
|
|
819
864
|
registerCustomXPathFunction(
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
865
|
+
{namespaceURI: XFORMS_NAMESPACE_URI, localName: 'base64encode'},
|
|
866
|
+
['xs:string?'],
|
|
867
|
+
'xs:string?',
|
|
868
|
+
(dynamicContext, string) => btoa(string),
|
|
824
869
|
);
|