@jinntec/fore 2.7.2 → 2.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/fore-dev.js +2240 -1936
- package/dist/fore.js +10949 -14540
- package/package.json +1 -1
- package/resources/fore.css +3 -0
- package/src/functions/fx-functionlib.js +138 -12
- package/src/functions/mylib.js +13 -0
- package/src/functions/registerFunction.js +15 -2
- package/src/fx-bind.js +1 -1
- package/src/fx-fore.js +309 -143
- package/src/fx-instance.js +2 -2
- package/src/fx-submission.js +18 -0
- package/src/modelitem.js +5 -5
- package/src/tools/deprecation.md +1 -0
- package/src/tools/fx-action-log.js +2 -2
- package/src/ui/abstract-control.js +49 -16
- package/src/xpath-util.js +3 -3
- package/src/DataObserver.js +0 -181
package/dist/fore-dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Version: 2.
|
|
1
|
+
/* Version: 2.9.0 - January 21, 2026 15:42:06 */
|
|
2
2
|
function t$2(t, s, r, i) {
|
|
3
3
|
const n = {
|
|
4
4
|
op: s,
|
|
@@ -16822,1978 +16822,1979 @@ class DependencyNotifyingDomFacade {
|
|
|
16822
16822
|
}
|
|
16823
16823
|
}
|
|
16824
16824
|
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
' <xsl:value-of select="normalize-space(.)"/>', ' </xsl:template>', ' <xsl:template match="node()|@*">', ' <xsl:copy>', ' <xsl:apply-templates select="node()|@*"/>', ' </xsl:copy>', ' </xsl:template>', '</xsl:stylesheet>'].join('\n'), 'application/xml');
|
|
16832
|
-
const xsltProcessor = new XSLTProcessor();
|
|
16833
|
-
xsltProcessor.importStylesheet(xsltDoc);
|
|
16834
|
-
const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
|
|
16835
|
-
const resultXml = new XMLSerializer().serializeToString(resultDoc);
|
|
16836
|
-
return resultXml;
|
|
16837
|
-
}
|
|
16838
|
-
|
|
16839
|
-
const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
16840
|
-
const createdNamespaceResolversByXPathQueryAndNode = new Map();
|
|
16841
|
-
|
|
16842
|
-
// A global registry of function names that are declared in Fore by a developer using the
|
|
16843
|
-
// `fx-function` element. These should be available without providing a prefix as well
|
|
16844
|
-
const globallyDeclaredFunctionLocalNames = [];
|
|
16845
|
-
function getCachedNamespaceResolver(xpath, node) {
|
|
16846
|
-
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
16847
|
-
return null;
|
|
16848
|
-
}
|
|
16849
|
-
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).get(node) || null;
|
|
16850
|
-
}
|
|
16851
|
-
function setCachedNamespaceResolver(xpath, node, resolver) {
|
|
16852
|
-
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
16853
|
-
return createdNamespaceResolversByXPathQueryAndNode.set(xpath, new Map());
|
|
16854
|
-
}
|
|
16855
|
-
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).set(node, resolver);
|
|
16856
|
-
}
|
|
16857
|
-
const xhtmlNamespaceResolver = prefix => {
|
|
16858
|
-
if (!prefix) {
|
|
16859
|
-
return 'http://www.w3.org/1999/xhtml';
|
|
16860
|
-
}
|
|
16861
|
-
return undefined;
|
|
16862
|
-
};
|
|
16825
|
+
class XPathUtil {
|
|
16826
|
+
/**
|
|
16827
|
+
* Recursively check AST for any dynamic expression components.
|
|
16828
|
+
*/
|
|
16829
|
+
static containsDynamicContent(astNode) {
|
|
16830
|
+
if (!astNode) return false;
|
|
16863
16831
|
|
|
16864
|
-
|
|
16865
|
-
|
|
16866
|
-
|
|
16867
|
-
* @param {string} id
|
|
16868
|
-
* @param {Node} sourceObject
|
|
16869
|
-
* @param {string} nodeName
|
|
16870
|
-
*
|
|
16871
|
-
* @returns {HTMLElement} The element with that ID, resolved with respect to repeats
|
|
16872
|
-
*/
|
|
16873
|
-
function resolveId(id, sourceObject, nodeName = null) {
|
|
16874
|
-
const query = 'outermost(ancestor-or-self::fx-fore[1]/(descendant::fx-fore|descendant::*[@id = $id]))[not(self::fx-fore)]';
|
|
16875
|
-
/*
|
|
16876
|
-
if (nodeName === 'fx-instance') {
|
|
16877
|
-
// Instance elements can only be in the `model` element
|
|
16878
|
-
// query = 'ancestor-or-self::fx-fore[1]/fx-model/fx-instance[@id = $id]';
|
|
16879
|
-
const fore = Fore.getFore(sourceObject);
|
|
16880
|
-
const instances = fore.getModel().instances;
|
|
16881
|
-
const targetInstance = instances.find(i => i.id === id);
|
|
16882
|
-
return targetInstance;
|
|
16883
|
-
return document.getElementById(id);
|
|
16884
|
-
}
|
|
16885
|
-
*/
|
|
16886
|
-
if (sourceObject.nodeType === Node.TEXT_NODE) {
|
|
16887
|
-
sourceObject = sourceObject.parentNode;
|
|
16888
|
-
}
|
|
16889
|
-
if (sourceObject.nodeType === Node.ATTRIBUTE_NODE) {
|
|
16890
|
-
sourceObject = sourceObject.ownerElement;
|
|
16891
|
-
}
|
|
16892
|
-
if (sourceObject.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
16893
|
-
sourceObject = sourceObject.parentNode.host;
|
|
16894
|
-
}
|
|
16895
|
-
const ownerForm = sourceObject.localName === 'fx-fore' ? sourceObject : sourceObject.closest('fx-fore');
|
|
16896
|
-
const elementsWithId = ownerForm.querySelectorAll(`[id='${id}']`);
|
|
16897
|
-
if (elementsWithId.length === 1) {
|
|
16898
|
-
// A single one is found. Assume no ID reuse.
|
|
16899
|
-
const targetObject = elementsWithId[0];
|
|
16900
|
-
if (nodeName && targetObject.localName !== nodeName) {
|
|
16901
|
-
return null;
|
|
16832
|
+
// Location paths, function calls, or variable refs are dynamic
|
|
16833
|
+
if (astNode.type === 'pathExpression' || astNode.type === 'functionCall' || astNode.type === 'variableReference') {
|
|
16834
|
+
return true;
|
|
16902
16835
|
}
|
|
16903
|
-
|
|
16904
|
-
|
|
16905
|
-
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
})) {
|
|
16916
|
-
// If the target element is not repeated, then the search for the target object is trivial since
|
|
16917
|
-
// there is only one associated with the target element that bears the matching ID. This is true
|
|
16918
|
-
// regardless of whether or not the source object is repeated. However, if the target element is
|
|
16919
|
-
// repeated, then additional information must be used to help select a target object from among
|
|
16920
|
-
// those associated with the identified target element.
|
|
16921
|
-
const targetObject = allMatchingTargetObjects[0];
|
|
16922
|
-
if (nodeName && targetObject.localName !== nodeName) {
|
|
16923
|
-
return null;
|
|
16836
|
+
|
|
16837
|
+
// Recursively check any child expressions
|
|
16838
|
+
for (const key in astNode) {
|
|
16839
|
+
if (astNode[key] && typeof astNode[key] === 'object') {
|
|
16840
|
+
if (Array.isArray(astNode[key])) {
|
|
16841
|
+
for (const item of astNode[key]) {
|
|
16842
|
+
if (XPathUtil.containsDynamicContent(item)) return true;
|
|
16843
|
+
}
|
|
16844
|
+
} else {
|
|
16845
|
+
if (XPathUtil.containsDynamicContent(astNode[key])) return true;
|
|
16846
|
+
}
|
|
16847
|
+
}
|
|
16924
16848
|
}
|
|
16925
|
-
return
|
|
16849
|
+
return false;
|
|
16926
16850
|
}
|
|
16927
16851
|
|
|
16928
|
-
|
|
16852
|
+
/**
|
|
16853
|
+
* creates DOM Nodes from an XPath locationpath expression. Support namespaced and un-namespaced
|
|
16854
|
+
* nodes.
|
|
16855
|
+
* E.g. 'foo/bar' creates an element 'foo' with an child element 'bar'
|
|
16856
|
+
* 'foo/@bar' creates a 'foo' element with an 'bar' attribute
|
|
16857
|
+
*
|
|
16858
|
+
* supports multiple steps
|
|
16859
|
+
*
|
|
16860
|
+
* @param xpath
|
|
16861
|
+
* @param doc {XMLDocument}
|
|
16862
|
+
* @param fore
|
|
16863
|
+
* @param namespaceResolver {function} optional namespace resolver function
|
|
16864
|
+
* @return {Node|Attr}
|
|
16865
|
+
*/
|
|
16866
|
+
static createNodesFromXPath(xpath, doc, fore, namespaceResolver = null) {
|
|
16867
|
+
const resolveNamespace = namespaceResolver || (() => undefined);
|
|
16868
|
+
if (!doc) {
|
|
16869
|
+
doc = document.implementation.createDocument(null, null, null); // Create a new XML document if not provided
|
|
16870
|
+
}
|
|
16929
16871
|
|
|
16930
|
-
|
|
16872
|
+
const parts = [];
|
|
16873
|
+
let scratch = '';
|
|
16874
|
+
let isInPredicate = false;
|
|
16875
|
+
for (const char of xpath.split('')) {
|
|
16876
|
+
if (!isInPredicate) {
|
|
16877
|
+
// We are not in a predicate, the slash will terminate our step.
|
|
16878
|
+
if (char === '/') {
|
|
16879
|
+
parts.push(scratch);
|
|
16880
|
+
scratch = '';
|
|
16881
|
+
continue;
|
|
16882
|
+
}
|
|
16883
|
+
scratch += char;
|
|
16884
|
+
if (char === '[') {
|
|
16885
|
+
isInPredicate = true;
|
|
16886
|
+
}
|
|
16887
|
+
continue;
|
|
16888
|
+
}
|
|
16889
|
+
// We are in a predicate! So the only interesting token is ']', which means we're out of one.
|
|
16890
|
+
scratch += char;
|
|
16891
|
+
if (char === ']') {
|
|
16892
|
+
isInPredicate = false;
|
|
16893
|
+
}
|
|
16894
|
+
}
|
|
16895
|
+
// Flush the last step
|
|
16896
|
+
parts.push(scratch);
|
|
16897
|
+
let rootNode = null;
|
|
16898
|
+
let currentNode = null;
|
|
16899
|
+
for (const part of parts) {
|
|
16900
|
+
if (!part) continue; // Skip empty parts (e.g., leading slashes)
|
|
16901
|
+
if (part === '.') {
|
|
16902
|
+
// A '.' does not introduce new elements
|
|
16903
|
+
continue;
|
|
16904
|
+
}
|
|
16931
16905
|
|
|
16932
|
-
|
|
16933
|
-
|
|
16934
|
-
|
|
16935
|
-
|
|
16936
|
-
|
|
16906
|
+
// Handle attributes
|
|
16907
|
+
if (part.startsWith('@')) {
|
|
16908
|
+
const attrName = part.slice(1); // Strip '@'
|
|
16909
|
+
if (!currentNode) {
|
|
16910
|
+
return doc.createAttribute(attrName, '');
|
|
16911
|
+
}
|
|
16912
|
+
currentNode.setAttribute(attrName, '');
|
|
16913
|
+
} else {
|
|
16914
|
+
// We are a predicate selector! Handle it
|
|
16915
|
+
// This regex matches strings like:
|
|
16916
|
+
// - listBibl
|
|
16917
|
+
// - tei:listBibl
|
|
16918
|
+
// - listBibl[@type="foo"]
|
|
16919
|
+
// - listBibl[@type="foo"][@class="bar"]
|
|
16920
|
+
// It will also match strings like
|
|
16921
|
+
// - listBibl[ancestor-or-self::foo]
|
|
16922
|
+
// which will be filtered out later.
|
|
16937
16923
|
|
|
16938
|
-
|
|
16939
|
-
|
|
16940
|
-
|
|
16941
|
-
// determines the set of run-time objects that contains the desired target object.
|
|
16942
|
-
for (const ancestorRepeatItem of evaluateXPathToNodes$1('ancestor::fx-repeatitem => reverse()', sourceObject, null, null, {
|
|
16943
|
-
namespaceResolver: xhtmlNamespaceResolver
|
|
16944
|
-
})) {
|
|
16945
|
-
const foundTargetObjects = allMatchingTargetObjects.filter(to => XPathUtil.contains(ancestorRepeatItem, to));
|
|
16946
|
-
switch (foundTargetObjects.length) {
|
|
16947
|
-
case 0:
|
|
16948
|
-
// Nothing found: ignore
|
|
16949
|
-
break;
|
|
16950
|
-
case 1:
|
|
16951
|
-
{
|
|
16952
|
-
// A single one is found: the target object is directly in a common repeat
|
|
16953
|
-
const targetObject = foundTargetObjects[0];
|
|
16954
|
-
if (nodeName && targetObject.localName !== nodeName) {
|
|
16955
|
-
return null;
|
|
16956
|
-
}
|
|
16957
|
-
return targetObject;
|
|
16924
|
+
const result = part.match(/^(?<name>[\w:-]+)(?<predicates>(\[[^]*\])*)$/);
|
|
16925
|
+
if (!result) {
|
|
16926
|
+
throw new Error(`No element could be made from the XPath step ${part}. It must be of these forms: 'localName', 'prefix:name', 'name[@attr="value"]' et cetera.`);
|
|
16958
16927
|
}
|
|
16959
|
-
|
|
16960
|
-
|
|
16961
|
-
|
|
16962
|
-
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
|
|
16966
|
-
|
|
16967
|
-
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
16928
|
+
const {
|
|
16929
|
+
name,
|
|
16930
|
+
predicates
|
|
16931
|
+
} = result.groups;
|
|
16932
|
+
// Handle namespaces if present
|
|
16933
|
+
const [prefix, localName] = name.includes(':') ? name.split(':') : [null, name];
|
|
16934
|
+
const namespace = resolveNamespace(prefix);
|
|
16935
|
+
const newElement = namespace ? doc.createElementNS(namespace, localName) : doc.createElement(localName);
|
|
16936
|
+
if (predicates) {
|
|
16937
|
+
const predicateExtractionRegex = /(\[@(?<name>[\w:-]*)\s?=\s?["'](?<value>[^"']*)['"]\])+/g;
|
|
16938
|
+
const parsedPredicates = predicates.matchAll(predicateExtractionRegex).map(match => ({
|
|
16939
|
+
attrName: match.groups.name,
|
|
16940
|
+
value: match.groups.value
|
|
16941
|
+
}));
|
|
16942
|
+
for (const {
|
|
16943
|
+
attrName,
|
|
16944
|
+
value
|
|
16945
|
+
} of parsedPredicates) {
|
|
16946
|
+
newElement.setAttribute(attrName, value);
|
|
16971
16947
|
}
|
|
16972
|
-
return targetObject;
|
|
16973
16948
|
}
|
|
16949
|
+
if (!rootNode) {
|
|
16950
|
+
rootNode = newElement; // Set as the root node
|
|
16951
|
+
} else {
|
|
16952
|
+
currentNode.appendChild(newElement);
|
|
16953
|
+
}
|
|
16954
|
+
currentNode = newElement;
|
|
16955
|
+
}
|
|
16956
|
+
}
|
|
16957
|
+
if (!rootNode) {
|
|
16958
|
+
throw new Error('Invalid XPath; no root element could be created.');
|
|
16974
16959
|
}
|
|
16960
|
+
return rootNode;
|
|
16975
16961
|
}
|
|
16976
|
-
// We found no target objects in common repeats. The id is unresolvable
|
|
16977
|
-
return null;
|
|
16978
|
-
}
|
|
16979
16962
|
|
|
16980
|
-
|
|
16981
|
-
|
|
16982
|
-
|
|
16983
|
-
|
|
16984
|
-
|
|
16985
|
-
|
|
16986
|
-
|
|
16963
|
+
/**
|
|
16964
|
+
* looks up namespace on ownerForm. Though not strictly in the sense of resolving namespaces in XML, the
|
|
16965
|
+
* fx-fore element is a convenient place to put namespace declarations for 2 reasons:
|
|
16966
|
+
* - this way namespaces are scoped to a Fore element
|
|
16967
|
+
* - as fx-fore is a web component we can add our xmlns attributes as we got no restrictions to attributes
|
|
16968
|
+
* though strictly speaking they are no xmlns declarations and just serve the purpose of namespace lookup.
|
|
16969
|
+
*
|
|
16970
|
+
* @param boundElement
|
|
16971
|
+
* @param prefix
|
|
16972
|
+
* @return {string}
|
|
16973
|
+
*/
|
|
16974
|
+
static lookupNamespace(ownerForm, prefix) {
|
|
16975
|
+
return ownerForm.getAttribute(`xmlns:${prefix}`);
|
|
16987
16976
|
}
|
|
16988
|
-
|
|
16989
|
-
|
|
16977
|
+
static querySelectorAll(querySelector, start) {
|
|
16978
|
+
const queue = [start];
|
|
16979
|
+
const found = [];
|
|
16980
|
+
while (queue.length) {
|
|
16981
|
+
const item = queue.shift();
|
|
16982
|
+
for (const child of Array.from(item.children).reverse()) {
|
|
16983
|
+
queue.unshift(child);
|
|
16984
|
+
}
|
|
16985
|
+
if (item.matches && item.matches('template')) {
|
|
16986
|
+
queue.unshift(item.content);
|
|
16987
|
+
}
|
|
16988
|
+
if (item.matches && item.matches(querySelector)) {
|
|
16989
|
+
found.push(item);
|
|
16990
|
+
}
|
|
16991
|
+
}
|
|
16992
|
+
return found;
|
|
16990
16993
|
}
|
|
16991
|
-
const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
|
|
16992
|
-
const instanceReferences = evaluateXPathToStrings$1(`descendant::xqx:functionCallExpr
|
|
16993
|
-
[xqx:functionName = "instance"]
|
|
16994
|
-
/xqx:arguments
|
|
16995
|
-
/xqx:stringConstantExpr
|
|
16996
|
-
/xqx:value`, xpathAST, null, {}, {
|
|
16997
|
-
namespaceResolver: prefix => prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined
|
|
16998
|
-
});
|
|
16999
|
-
instanceReferencesByQuery.set(xpathQuery, instanceReferences);
|
|
17000
|
-
return instanceReferences;
|
|
17001
|
-
}
|
|
17002
|
-
/**
|
|
17003
|
-
* @typedef {function(string):string} NamespaceResolver
|
|
17004
|
-
*/
|
|
17005
16994
|
|
|
17006
|
-
/**
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
|
|
17024
|
-
if (cachedResolver) {
|
|
17025
|
-
return cachedResolver;
|
|
17026
|
-
}
|
|
17027
|
-
let instanceReferences = findInstanceReferences(xpathQuery);
|
|
17028
|
-
if (instanceReferences.length === 0) {
|
|
17029
|
-
// No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
|
|
17030
|
-
const ancestorComponent = formElement.parentNode && formElement.parentNode.nodeType === formElement.ELEMENT_NODE && formElement.parentNode.closest('[ref]');
|
|
17031
|
-
if (ancestorComponent) {
|
|
17032
|
-
const resolver = createNamespaceResolver(ancestorComponent.getAttribute('ref'), ancestorComponent);
|
|
17033
|
-
setCachedNamespaceResolver(xpathQuery, formElement, resolver);
|
|
17034
|
-
return resolver;
|
|
16995
|
+
/**
|
|
16996
|
+
* Alternative to `contains` that respects shadowroots
|
|
16997
|
+
* @param {Node} ancestor
|
|
16998
|
+
* @param {Node} descendant
|
|
16999
|
+
* @returns {boolean}
|
|
17000
|
+
*/
|
|
17001
|
+
static contains(ancestor, descendant) {
|
|
17002
|
+
while (descendant) {
|
|
17003
|
+
if (descendant === ancestor) {
|
|
17004
|
+
return true;
|
|
17005
|
+
}
|
|
17006
|
+
if (descendant.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
17007
|
+
// We are passing a shadow root boundary
|
|
17008
|
+
descendant = descendant.host;
|
|
17009
|
+
} else {
|
|
17010
|
+
descendant = descendant.parentNode;
|
|
17011
|
+
}
|
|
17035
17012
|
}
|
|
17036
|
-
|
|
17037
|
-
instanceReferences = ['default'];
|
|
17013
|
+
return false;
|
|
17038
17014
|
}
|
|
17039
|
-
|
|
17040
|
-
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
|
|
17053
|
-
|
|
17054
|
-
|
|
17055
|
-
|
|
17056
|
-
|
|
17057
|
-
|
|
17058
|
-
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
|
|
17065
|
-
|
|
17066
|
-
|
|
17067
|
-
|
|
17068
|
-
|
|
17069
|
-
|
|
17070
|
-
return resolveNamespacePrefix;
|
|
17015
|
+
|
|
17016
|
+
/**
|
|
17017
|
+
* Alternative to `closest` that respects subcontrol boundaries
|
|
17018
|
+
*
|
|
17019
|
+
* @param {string} querySelector
|
|
17020
|
+
* @param {Node} start
|
|
17021
|
+
* @returns {HTMLElement}
|
|
17022
|
+
*/
|
|
17023
|
+
static getClosest(querySelector, start) {
|
|
17024
|
+
while (start && !start.matches || !start.matches(querySelector)) {
|
|
17025
|
+
if (start.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
17026
|
+
// We are passing a shadow root boundary
|
|
17027
|
+
start = start.host;
|
|
17028
|
+
continue;
|
|
17029
|
+
}
|
|
17030
|
+
if (start.nodeType === Node.ATTRIBUTE_NODE) {
|
|
17031
|
+
// We are passing an attribute
|
|
17032
|
+
start = start.ownerElement;
|
|
17033
|
+
continue;
|
|
17034
|
+
}
|
|
17035
|
+
if (start.nodeType === Node.TEXT_NODE) {
|
|
17036
|
+
start = start.parentNode;
|
|
17037
|
+
}
|
|
17038
|
+
if (start.matches('fx-fore')) {
|
|
17039
|
+
// Subform reached. Bail out
|
|
17040
|
+
return null;
|
|
17041
|
+
}
|
|
17042
|
+
start = start.parentNode;
|
|
17043
|
+
if (!start) {
|
|
17044
|
+
return null;
|
|
17045
|
+
}
|
|
17071
17046
|
}
|
|
17047
|
+
return start;
|
|
17072
17048
|
}
|
|
17073
|
-
/*
|
|
17074
|
-
if (instanceReferences.length > 1) {
|
|
17075
|
-
console.warn(
|
|
17076
|
-
`More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
|
|
17077
|
-
);
|
|
17078
|
-
}
|
|
17079
|
-
*/
|
|
17080
|
-
|
|
17081
|
-
const xpathDefaultNamespace = evaluateXPathToString$1('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) || '';
|
|
17082
17049
|
|
|
17083
17050
|
/**
|
|
17084
|
-
*
|
|
17051
|
+
* returns next bound element upwards in tree
|
|
17052
|
+
* @param {Node} start where to start the search
|
|
17053
|
+
* @returns {*|null}
|
|
17085
17054
|
*/
|
|
17086
|
-
|
|
17087
|
-
if (
|
|
17088
|
-
|
|
17055
|
+
static getParentBindingElement(start) {
|
|
17056
|
+
/* if (start.parentNode.host) {
|
|
17057
|
+
const { host } = start.parentNode;
|
|
17058
|
+
if (host.hasAttribute('ref')) {
|
|
17059
|
+
return host;
|
|
17060
|
+
}
|
|
17061
|
+
} else */
|
|
17062
|
+
if (start.parentNode && (start.parentNode.nodeType !== Node.DOCUMENT_NODE || start.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE)) {
|
|
17063
|
+
return this.getClosest('fx-control[ref],fx-upload[ref],fx-group[ref],fx-repeat[ref], fx-switch[ref],fx-repeatitem', start.parentNode);
|
|
17089
17064
|
}
|
|
17065
|
+
return null;
|
|
17066
|
+
}
|
|
17090
17067
|
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17068
|
+
/**
|
|
17069
|
+
* Checks whether the specified path expression is an absolute path.
|
|
17070
|
+
*
|
|
17071
|
+
* @param {string} path the path expression.
|
|
17072
|
+
* @returns {boolean} <code>true</code> if specified path expression is an absolute
|
|
17073
|
+
* path, otherwise <code>false</code>.
|
|
17074
|
+
*/
|
|
17075
|
+
static isAbsolutePath(path) {
|
|
17076
|
+
return path != null && (path.startsWith('/') || path.startsWith('instance(') || path.startsWith('$'));
|
|
17077
|
+
}
|
|
17094
17078
|
|
|
17095
|
-
|
|
17096
|
-
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
|
|
17100
|
-
return resolveNamespacePrefix;
|
|
17101
|
-
}
|
|
17102
|
-
function createNamespaceResolverForNode(query, contextNode, formElement) {
|
|
17103
|
-
if ((contextNode && contextNode.ownerDocument || contextNode) === window.document) {
|
|
17104
|
-
// Running a query on the HTML DOM. Don't bother resolving namespaces in any other way
|
|
17105
|
-
return xhtmlNamespaceResolver;
|
|
17079
|
+
/**
|
|
17080
|
+
* @param {string} ref
|
|
17081
|
+
*/
|
|
17082
|
+
static isSelfReference(ref) {
|
|
17083
|
+
return ref === '.' || ref === './text()' || ref === 'text()' || ref === '' || ref === null;
|
|
17106
17084
|
}
|
|
17107
|
-
return createNamespaceResolver(query, formElement);
|
|
17108
|
-
}
|
|
17109
17085
|
|
|
17110
|
-
/**
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
function
|
|
17116
|
-
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
case 'uri':
|
|
17138
|
-
case 'uri-fragment':
|
|
17139
|
-
case 'uri-host':
|
|
17140
|
-
case 'uri-param':
|
|
17141
|
-
case 'uri-path':
|
|
17142
|
-
case 'uri-relpath':
|
|
17143
|
-
case 'uri-port':
|
|
17144
|
-
case 'uri-query':
|
|
17145
|
-
case 'uri-scheme':
|
|
17146
|
-
case 'uri-scheme-specific-part':
|
|
17147
|
-
return {
|
|
17148
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17149
|
-
localName
|
|
17150
|
-
};
|
|
17151
|
-
default:
|
|
17152
|
-
if (prefix === '' && globallyDeclaredFunctionLocalNames.includes(localName)) {
|
|
17153
|
-
// The function has been declared without a prefix and is called here without a prefix.
|
|
17154
|
-
// Just make this work. It is the developer-friendly way
|
|
17155
|
-
return {
|
|
17156
|
-
namespaceURI: 'http://www.w3.org/2005/xquery-local-functions',
|
|
17157
|
-
localName
|
|
17158
|
-
};
|
|
17159
|
-
}
|
|
17160
|
-
if (prefix === 'fn' || prefix === '') {
|
|
17161
|
-
return {
|
|
17162
|
-
namespaceURI: 'http://www.w3.org/2005/xpath-functions',
|
|
17163
|
-
localName
|
|
17164
|
-
};
|
|
17086
|
+
/**
|
|
17087
|
+
* returns the instance id from a complete XPath using `instance()` function.
|
|
17088
|
+
*
|
|
17089
|
+
* Will return 'default' in case no ref is given at all or the `instance()` function is called without arg.
|
|
17090
|
+
*
|
|
17091
|
+
* Otherwise instance id is extracted from function and returned. If all fails null is returned.
|
|
17092
|
+
* @param {string} ref
|
|
17093
|
+
* @param {HTMLElement} boundElement The element related to this ref. Used to resolve variables
|
|
17094
|
+
* @returns {string}
|
|
17095
|
+
*/
|
|
17096
|
+
static getInstanceId(ref, boundElement) {
|
|
17097
|
+
if (!ref) {
|
|
17098
|
+
return 'default';
|
|
17099
|
+
}
|
|
17100
|
+
if (ref.startsWith('instance()')) {
|
|
17101
|
+
return 'default';
|
|
17102
|
+
}
|
|
17103
|
+
if (ref.startsWith('instance(')) {
|
|
17104
|
+
const result = ref.substring(ref.indexOf('(') + 1);
|
|
17105
|
+
return result.substring(1, result.indexOf(')') - 1);
|
|
17106
|
+
}
|
|
17107
|
+
if (ref.startsWith('$')) {
|
|
17108
|
+
// this variable might actually point to an instance
|
|
17109
|
+
const variableName = ref.match(/\$(?<variableName>[a-zA-Z0-9\-\_]+).*/)?.groups?.variableName;
|
|
17110
|
+
let closestActualFormElement = boundElement;
|
|
17111
|
+
while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
|
|
17112
|
+
closestActualFormElement = closestActualFormElement.nodeType === Node.ATTRIBUTE_NODE ? closestActualFormElement.ownerElement : closestActualFormElement.parentNode;
|
|
17165
17113
|
}
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
localName
|
|
17170
|
-
};
|
|
17114
|
+
const correspondingVariable = closestActualFormElement?.inScopeVariables?.get(variableName);
|
|
17115
|
+
if (!correspondingVariable) {
|
|
17116
|
+
return null;
|
|
17171
17117
|
}
|
|
17172
|
-
return
|
|
17118
|
+
return this.getInstanceId(correspondingVariable.valueQuery, correspondingVariable);
|
|
17119
|
+
}
|
|
17120
|
+
return null;
|
|
17173
17121
|
}
|
|
17174
|
-
}
|
|
17175
17122
|
|
|
17176
|
-
/**
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17123
|
+
/**
|
|
17124
|
+
* @param {HTMLElement} boundElement
|
|
17125
|
+
* @param {string} path
|
|
17126
|
+
* @returns {string}
|
|
17127
|
+
*/
|
|
17128
|
+
static resolveInstance(boundElement, path) {
|
|
17129
|
+
let instanceId = XPathUtil.getInstanceId(path, boundElement);
|
|
17130
|
+
if (!instanceId) {
|
|
17131
|
+
instanceId = XPathUtil.getInstanceId(boundElement.getAttribute('ref'), boundElement);
|
|
17132
|
+
}
|
|
17133
|
+
if (instanceId !== null) {
|
|
17134
|
+
return instanceId;
|
|
17135
|
+
}
|
|
17136
|
+
const parentBinding = XPathUtil.getParentBindingElement(boundElement);
|
|
17137
|
+
if (parentBinding) {
|
|
17138
|
+
return this.resolveInstance(parentBinding, path);
|
|
17139
|
+
}
|
|
17140
|
+
return 'default';
|
|
17188
17141
|
}
|
|
17189
|
-
|
|
17190
|
-
|
|
17142
|
+
|
|
17143
|
+
/**
|
|
17144
|
+
* @param {Node} node
|
|
17145
|
+
* @returns string
|
|
17146
|
+
*/
|
|
17147
|
+
/*
|
|
17148
|
+
static getDocPath(node) {
|
|
17149
|
+
const path = fx.evaluateXPathToString('path()', node);
|
|
17150
|
+
// Path is like `$default/x[1]/y[1]`
|
|
17151
|
+
const shortened = XPathUtil.shortenPath(path);
|
|
17152
|
+
return shortened.startsWith('/') ? `${shortened}` : `/${shortened}`;
|
|
17191
17153
|
}
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17154
|
+
*/
|
|
17155
|
+
|
|
17156
|
+
/**
|
|
17157
|
+
* @param {Node} node
|
|
17158
|
+
* @param {string} instanceId
|
|
17159
|
+
* @returns string
|
|
17160
|
+
*/
|
|
17161
|
+
static getPath(node, instanceId) {
|
|
17162
|
+
const path = evaluateXPathToString$1('path()', node);
|
|
17163
|
+
// Path is like `$default/x[1]/y[1]`
|
|
17164
|
+
const shortened = XPathUtil.shortenPath(path);
|
|
17165
|
+
return shortened.startsWith('/') ? `$${instanceId}${shortened}` : `$${instanceId}/${shortened}`;
|
|
17166
|
+
}
|
|
17167
|
+
|
|
17168
|
+
/**
|
|
17169
|
+
* @param {string} path
|
|
17170
|
+
* @returns string
|
|
17171
|
+
*/
|
|
17172
|
+
static shortenPath(path) {
|
|
17173
|
+
const tmp = path.replaceAll(/(Q{(.*?)\})/g, '');
|
|
17174
|
+
if (tmp === 'root()') return tmp;
|
|
17175
|
+
// cut off leading slash
|
|
17176
|
+
const tmp1 = tmp.substring(1, tmp.length);
|
|
17177
|
+
// ### cut-off root node ref
|
|
17178
|
+
return tmp1.substring(tmp1.indexOf('/'), tmp.length);
|
|
17179
|
+
}
|
|
17180
|
+
|
|
17181
|
+
/**
|
|
17182
|
+
* @param {string} dep
|
|
17183
|
+
* @returns {string}
|
|
17184
|
+
*/
|
|
17185
|
+
static getBasePath(dep) {
|
|
17186
|
+
const split = dep.split(':');
|
|
17187
|
+
return split[0];
|
|
17208
17188
|
}
|
|
17209
|
-
return variables;
|
|
17210
17189
|
}
|
|
17211
17190
|
|
|
17212
17191
|
/**
|
|
17213
|
-
*
|
|
17214
|
-
*
|
|
17192
|
+
* A simple dependency graph
|
|
17193
|
+
*
|
|
17194
|
+
* based on the work of https://github.com/jriecken/dependency-graph but working on ES6.
|
|
17195
|
+
*
|
|
17196
|
+
* Furthermore instead of the DepGraphCycleError a compute-exception event is dispatched.
|
|
17215
17197
|
*
|
|
17216
|
-
* @param {string} xpath The XPath to run
|
|
17217
|
-
* @param {Node} contextNode The start of the XPath
|
|
17218
|
-
* @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
|
|
17219
|
-
* @param {Object} variables Any variables to pass to the XPath
|
|
17220
|
-
* @param {Object} options Any options to pass to the XPath
|
|
17221
17198
|
*
|
|
17222
|
-
* @returns {any[]}
|
|
17223
17199
|
*/
|
|
17224
|
-
/*
|
|
17225
|
-
export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options={}, domFacade = null) {
|
|
17226
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17227
|
-
const variablesInScope = getVariablesInScope(formElement);
|
|
17228
|
-
|
|
17229
|
-
return fxEvaluateXPath(
|
|
17230
|
-
xpath,
|
|
17231
|
-
contextNode,
|
|
17232
|
-
domFacade,
|
|
17233
|
-
{...variablesInScope, ...variables},
|
|
17234
|
-
fxEvaluateXPath.ALL_RESULTS_TYPE,
|
|
17235
|
-
{
|
|
17236
|
-
debug: true,
|
|
17237
|
-
currentContext: {formElement, variables},
|
|
17238
|
-
moduleImports: {
|
|
17239
|
-
xf: XFORMS_NAMESPACE_URI,
|
|
17240
|
-
},
|
|
17241
|
-
functionNameResolver,
|
|
17242
|
-
namespaceResolver,
|
|
17243
|
-
language: options.language || evaluateXPath.XPATH_3_1
|
|
17244
|
-
},
|
|
17245
|
-
);
|
|
17246
|
-
}
|
|
17247
|
-
*/
|
|
17248
|
-
function evaluateXPath(xpath, contextNode, formElement, variables = {}, options = {}) {
|
|
17249
|
-
try {
|
|
17250
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17251
|
-
const variablesInScope = getVariablesInScope(formElement);
|
|
17252
|
-
const result = evaluateXPath$1(xpath, contextNode, null, {
|
|
17253
|
-
...variablesInScope,
|
|
17254
|
-
...variables
|
|
17255
|
-
}, evaluateXPath$1.ALL_RESULTS_TYPE, {
|
|
17256
|
-
xmlSerializer: new XMLSerializer(),
|
|
17257
|
-
debug: true,
|
|
17258
|
-
currentContext: {
|
|
17259
|
-
formElement,
|
|
17260
|
-
variables
|
|
17261
|
-
},
|
|
17262
|
-
moduleImports: {
|
|
17263
|
-
xf: XFORMS_NAMESPACE_URI
|
|
17264
|
-
},
|
|
17265
|
-
functionNameResolver,
|
|
17266
|
-
namespaceResolver,
|
|
17267
|
-
language: options.language || evaluateXPath$1.XPATH_3_1_LANGUAGE
|
|
17268
|
-
});
|
|
17269
|
-
// console.log('evaluateXPath',xpath, result);
|
|
17270
|
-
return result;
|
|
17271
|
-
} catch (e) {
|
|
17272
|
-
formElement.dispatchEvent(new CustomEvent('error', {
|
|
17273
|
-
composed: false,
|
|
17274
|
-
bubbles: true,
|
|
17275
|
-
detail: {
|
|
17276
|
-
origin: formElement,
|
|
17277
|
-
message: `Expression '${xpath}' failed: ${e}`,
|
|
17278
|
-
expr: xpath,
|
|
17279
|
-
level: 'Error'
|
|
17280
|
-
}
|
|
17281
|
-
}));
|
|
17282
17200
|
|
|
17283
|
-
/*
|
|
17284
|
-
formElement.dispatchEvent(
|
|
17285
|
-
new CustomEvent('error', {
|
|
17286
|
-
composed: false,
|
|
17287
|
-
bubbles: true,
|
|
17288
|
-
cancelable:true,
|
|
17289
|
-
detail: {
|
|
17290
|
-
origin: formElement,
|
|
17291
|
-
message: `Expression '${xpath}' failed`,
|
|
17292
|
-
expr:xpath,
|
|
17293
|
-
level:'Error'},
|
|
17294
|
-
}),
|
|
17295
|
-
);
|
|
17296
|
-
*/
|
|
17297
|
-
// Return 'nothing' in hope the rest of the page can forgive this
|
|
17298
|
-
return [];
|
|
17299
|
-
}
|
|
17300
|
-
}
|
|
17301
17201
|
/**
|
|
17302
|
-
*
|
|
17303
|
-
*
|
|
17304
|
-
* @param {string} xpath The XPath to run
|
|
17305
|
-
* @param {Node} contextNode The start of the XPath
|
|
17306
|
-
* @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
|
|
17307
|
-
* @returns {Node} The first node found in the XPath
|
|
17202
|
+
* Cycle error, including the path of the cycle.
|
|
17308
17203
|
*/
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
formElement
|
|
17320
|
-
},
|
|
17321
|
-
functionNameResolver,
|
|
17322
|
-
namespaceResolver,
|
|
17323
|
-
xmlSerializer: new XMLSerializer()
|
|
17324
|
-
});
|
|
17325
|
-
// console.log('evaluateXPathToFirstNode',xpath, result);
|
|
17326
|
-
return result;
|
|
17327
|
-
} catch (e) {
|
|
17328
|
-
formElement.dispatchEvent(new CustomEvent('error', {
|
|
17329
|
-
composed: false,
|
|
17330
|
-
bubbles: true,
|
|
17331
|
-
detail: {
|
|
17332
|
-
origin: formElement,
|
|
17333
|
-
message: `Expression '${xpath}' failed: ${e}`,
|
|
17334
|
-
expr: xpath,
|
|
17335
|
-
level: 'Error'
|
|
17336
|
-
}
|
|
17337
|
-
}));
|
|
17204
|
+
// const DepGraphCycleError = (exports.DepGraphCycleError = function (cyclePath) {
|
|
17205
|
+
|
|
17206
|
+
/*
|
|
17207
|
+
export function DepGraphCycleError(cyclePath) {
|
|
17208
|
+
const message = "Dependency Cycle Found: " + cyclePath.join(" -> ");
|
|
17209
|
+
const instance = new Error(message);
|
|
17210
|
+
instance.cyclePath = cyclePath;
|
|
17211
|
+
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
|
|
17212
|
+
if (Error.captureStackTrace) {
|
|
17213
|
+
Error.captureStackTrace(instance, DepGraphCycleError);
|
|
17338
17214
|
}
|
|
17339
|
-
return
|
|
17340
|
-
}
|
|
17215
|
+
return instance;
|
|
17216
|
+
};
|
|
17341
17217
|
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
* @return {Node[]} All nodes
|
|
17349
|
-
*/
|
|
17350
|
-
function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
17351
|
-
try {
|
|
17352
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17353
|
-
const variablesInScope = getVariablesInScope(formElement);
|
|
17354
|
-
const result = evaluateXPathToNodes$1(xpath, contextNode, null, variablesInScope, {
|
|
17355
|
-
currentContext: {
|
|
17356
|
-
formElement
|
|
17357
|
-
},
|
|
17358
|
-
functionNameResolver,
|
|
17359
|
-
moduleImports: {
|
|
17360
|
-
xf: XFORMS_NAMESPACE_URI
|
|
17361
|
-
},
|
|
17362
|
-
namespaceResolver,
|
|
17363
|
-
xmlSerializer: new XMLSerializer()
|
|
17364
|
-
});
|
|
17365
|
-
// console.log('evaluateXPathToNodes',xpath, result);
|
|
17366
|
-
return result;
|
|
17367
|
-
} catch (e) {
|
|
17368
|
-
formElement.dispatchEvent(new CustomEvent('error', {
|
|
17369
|
-
composed: false,
|
|
17370
|
-
bubbles: true,
|
|
17371
|
-
detail: {
|
|
17372
|
-
origin: formElement,
|
|
17373
|
-
message: `Expression '${xpath}' failed: ${e}`,
|
|
17374
|
-
expr: xpath,
|
|
17375
|
-
level: 'Error'
|
|
17376
|
-
}
|
|
17377
|
-
}));
|
|
17218
|
+
DepGraphCycleError.prototype = Object.create(Error.prototype, {
|
|
17219
|
+
constructor: {
|
|
17220
|
+
value: Error,
|
|
17221
|
+
enumerable: false,
|
|
17222
|
+
writable: true,
|
|
17223
|
+
configurable: true
|
|
17378
17224
|
}
|
|
17379
|
-
}
|
|
17225
|
+
});
|
|
17226
|
+
Object.setPrototypeOf(DepGraphCycleError, Error);
|
|
17227
|
+
*/
|
|
17380
17228
|
|
|
17381
17229
|
/**
|
|
17382
|
-
*
|
|
17230
|
+
* Helper for creating a Topological Sort using Depth-First-Search on a set of edges.
|
|
17383
17231
|
*
|
|
17384
|
-
*
|
|
17385
|
-
*
|
|
17386
|
-
*
|
|
17387
|
-
* @
|
|
17232
|
+
* Detects cycles and throws an Error if one is detected (unless the "circular"
|
|
17233
|
+
* parameter is "true" in which case it ignores them).
|
|
17234
|
+
*
|
|
17235
|
+
* @param edges The set of edges to DFS through
|
|
17236
|
+
* @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges)
|
|
17237
|
+
* @param result An array in which the results will be populated
|
|
17238
|
+
* @param circular A boolean to allow circular dependencies
|
|
17388
17239
|
*/
|
|
17389
|
-
function
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
|
|
17393
|
-
|
|
17394
|
-
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17240
|
+
function createDFS(edges, leavesOnly, result, circular) {
|
|
17241
|
+
const visited = {};
|
|
17242
|
+
// eslint-disable-next-line func-names
|
|
17243
|
+
return function (start) {
|
|
17244
|
+
// console.log('start ', start);
|
|
17245
|
+
if (visited[start]) {
|
|
17246
|
+
return;
|
|
17247
|
+
}
|
|
17248
|
+
const inCurrentPath = {};
|
|
17249
|
+
const currentPath = [];
|
|
17250
|
+
const todo = []; // used as a stack
|
|
17251
|
+
todo.push({
|
|
17252
|
+
node: start,
|
|
17253
|
+
processed: false
|
|
17403
17254
|
});
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17255
|
+
while (todo.length > 0) {
|
|
17256
|
+
const current = todo[todo.length - 1]; // peek at the todo stack
|
|
17257
|
+
const {
|
|
17258
|
+
processed
|
|
17259
|
+
} = current;
|
|
17260
|
+
const {
|
|
17261
|
+
node
|
|
17262
|
+
} = current;
|
|
17263
|
+
if (!processed) {
|
|
17264
|
+
// Haven't visited edges yet (visiting phase)
|
|
17265
|
+
if (visited[node]) {
|
|
17266
|
+
todo.pop();
|
|
17267
|
+
// eslint-disable-next-line no-continue
|
|
17268
|
+
continue;
|
|
17269
|
+
} else if (inCurrentPath[node]) {
|
|
17270
|
+
// It's not a DAG
|
|
17271
|
+
if (circular) {
|
|
17272
|
+
todo.pop();
|
|
17273
|
+
// If we're tolerating cycles, don't revisit the node
|
|
17274
|
+
// eslint-disable-next-line no-continue
|
|
17275
|
+
continue;
|
|
17276
|
+
}
|
|
17277
|
+
currentPath.push(node);
|
|
17278
|
+
window.dispatchEvent(new CustomEvent('compute-exception', {
|
|
17279
|
+
composed: false,
|
|
17280
|
+
bubbles: true,
|
|
17281
|
+
detail: {
|
|
17282
|
+
path: currentPath,
|
|
17283
|
+
message: 'cyclic graph'
|
|
17284
|
+
}
|
|
17285
|
+
}));
|
|
17286
|
+
// return;
|
|
17287
|
+
// console.log('‘circular path: ' + currentPath);
|
|
17288
|
+
// throw new DepGraphCycleError(currentPath);
|
|
17289
|
+
|
|
17290
|
+
// Stop all processing. This form is broken and we should not break the browser
|
|
17291
|
+
throw new Error(`Cyclic at ${currentPath}`);
|
|
17292
|
+
}
|
|
17293
|
+
inCurrentPath[node] = true;
|
|
17294
|
+
currentPath.push(node);
|
|
17295
|
+
const nodeEdges = edges[node];
|
|
17296
|
+
// (push edges onto the todo stack in reverse order to be order-compatible with the old DFS implementation)
|
|
17297
|
+
for (let i = nodeEdges.length - 1; i >= 0; i -= 1) {
|
|
17298
|
+
todo.push({
|
|
17299
|
+
node: nodeEdges[i],
|
|
17300
|
+
processed: false
|
|
17301
|
+
});
|
|
17302
|
+
}
|
|
17303
|
+
current.processed = true;
|
|
17304
|
+
} else {
|
|
17305
|
+
// Have visited edges (stack unrolling phase)
|
|
17306
|
+
todo.pop();
|
|
17307
|
+
currentPath.pop();
|
|
17308
|
+
inCurrentPath[node] = false;
|
|
17309
|
+
visited[node] = true;
|
|
17310
|
+
if (!leavesOnly || edges[node].length === 0) {
|
|
17311
|
+
result.push(node);
|
|
17312
|
+
}
|
|
17413
17313
|
}
|
|
17414
|
-
}
|
|
17415
|
-
}
|
|
17314
|
+
}
|
|
17315
|
+
};
|
|
17416
17316
|
}
|
|
17417
17317
|
|
|
17418
17318
|
/**
|
|
17419
|
-
*
|
|
17420
|
-
*
|
|
17421
|
-
* @param {string} xpath The XPath to run
|
|
17422
|
-
* @param {Node} contextNode The start of the XPath
|
|
17423
|
-
* @param {Node} formElement The form element associated to the XPath
|
|
17424
|
-
* @param {Node} formElement The element where the XPath is defined: used for namespace resolving
|
|
17425
|
-
* @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
|
|
17426
|
-
* access. This is used to determine dependencies between bind elements.
|
|
17427
|
-
* @return {string}
|
|
17319
|
+
* Simple Dependency Graph
|
|
17428
17320
|
*/
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
|
|
17438
|
-
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
} catch (e) {
|
|
17445
|
-
formElement.dispatchEvent(new CustomEvent('error', {
|
|
17446
|
-
composed: false,
|
|
17447
|
-
bubbles: true,
|
|
17448
|
-
detail: {
|
|
17449
|
-
origin: formElement,
|
|
17450
|
-
message: `Expression '${xpath}' failed: ${e}`,
|
|
17451
|
-
expr: xpath,
|
|
17452
|
-
level: 'Error'
|
|
17453
|
-
}
|
|
17454
|
-
}));
|
|
17455
|
-
}
|
|
17321
|
+
|
|
17322
|
+
/*
|
|
17323
|
+
var DepGraph = (exports.DepGraph = function DepGraph(opts) {
|
|
17324
|
+
this.nodes = {}; // Node -> Node/Data (treated like a Set)
|
|
17325
|
+
this.outgoingEdges = {}; // Node -> [Dependency Node]
|
|
17326
|
+
this.incomingEdges = {}; // Node -> [Dependant Node]
|
|
17327
|
+
this.circular = opts && !!opts.circular; // Allows circular deps
|
|
17328
|
+
});
|
|
17329
|
+
*/
|
|
17330
|
+
|
|
17331
|
+
function DepGraph(opts) {
|
|
17332
|
+
this.nodes = {}; // Node -> Node/Data (treated like a Set)
|
|
17333
|
+
this.outgoingEdges = {}; // Node -> [Dependency Node]
|
|
17334
|
+
this.incomingEdges = {}; // Node -> [Dependant Node]
|
|
17335
|
+
this.circular = opts && !!opts.circular; // Allows circular deps
|
|
17456
17336
|
}
|
|
17457
17337
|
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
},
|
|
17476
|
-
functionNameResolver,
|
|
17477
|
-
moduleImports: {
|
|
17478
|
-
xf: XFORMS_NAMESPACE_URI
|
|
17479
|
-
},
|
|
17480
|
-
namespaceResolver,
|
|
17481
|
-
xmlSerializer: new XMLSerializer()
|
|
17482
|
-
});
|
|
17483
|
-
} catch (e) {
|
|
17484
|
-
formElement.dispatchEvent(new CustomEvent('error', {
|
|
17485
|
-
composed: false,
|
|
17486
|
-
bubbles: true,
|
|
17487
|
-
detail: {
|
|
17488
|
-
origin: formElement,
|
|
17489
|
-
message: `Expression '${xpath}' failed: ${e}`,
|
|
17490
|
-
expr: xpath,
|
|
17491
|
-
level: 'Error'
|
|
17338
|
+
DepGraph.prototype = {
|
|
17339
|
+
/**
|
|
17340
|
+
* The number of nodes in the graph.
|
|
17341
|
+
*/
|
|
17342
|
+
size() {
|
|
17343
|
+
return Object.keys(this.nodes).length;
|
|
17344
|
+
},
|
|
17345
|
+
/**
|
|
17346
|
+
* Add a node to the dependency graph. If a node already exists, this method will do nothing.
|
|
17347
|
+
*/
|
|
17348
|
+
addNode(node, data) {
|
|
17349
|
+
if (!this.hasNode(node)) {
|
|
17350
|
+
// Checking the arguments length allows the user to add a node with undefined data
|
|
17351
|
+
if (arguments.length === 2) {
|
|
17352
|
+
this.nodes[node] = data;
|
|
17353
|
+
} else {
|
|
17354
|
+
this.nodes[node] = node;
|
|
17492
17355
|
}
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
}
|
|
17356
|
+
this.outgoingEdges[node] = [];
|
|
17357
|
+
this.incomingEdges[node] = [];
|
|
17358
|
+
}
|
|
17359
|
+
},
|
|
17360
|
+
/**
|
|
17361
|
+
* Remove a node from the dependency graph. If a node does not exist, this method will do nothing.
|
|
17362
|
+
*/
|
|
17363
|
+
removeNode(node) {
|
|
17364
|
+
if (this.hasNode(node)) {
|
|
17365
|
+
delete this.nodes[node];
|
|
17366
|
+
delete this.outgoingEdges[node];
|
|
17367
|
+
delete this.incomingEdges[node];
|
|
17368
|
+
// [this.incomingEdges, this.outgoingEdges].forEach(function (edgeList) {
|
|
17369
|
+
[this.incomingEdges, this.outgoingEdges].forEach(edgeList => {
|
|
17370
|
+
Object.keys(edgeList).forEach(key => {
|
|
17371
|
+
const idx = edgeList[key].indexOf(node);
|
|
17372
|
+
if (idx >= 0) {
|
|
17373
|
+
edgeList[key].splice(idx, 1);
|
|
17374
|
+
}
|
|
17375
|
+
}, this);
|
|
17376
|
+
});
|
|
17377
|
+
}
|
|
17378
|
+
},
|
|
17379
|
+
/**
|
|
17380
|
+
* Check if a node exists in the graph
|
|
17381
|
+
*/
|
|
17382
|
+
hasNode(node) {
|
|
17383
|
+
// return this.nodes.hasOwnProperty(node);
|
|
17496
17384
|
|
|
17497
|
-
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
|
|
17505
|
-
|
|
17506
|
-
|
|
17507
|
-
|
|
17508
|
-
|
|
17509
|
-
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
|
|
17515
|
-
}
|
|
17516
|
-
|
|
17517
|
-
|
|
17518
|
-
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17385
|
+
return Object.prototype.hasOwnProperty.call(this.nodes, node);
|
|
17386
|
+
},
|
|
17387
|
+
/**
|
|
17388
|
+
* Get the data associated with a node name
|
|
17389
|
+
*/
|
|
17390
|
+
getNodeData(node) {
|
|
17391
|
+
if (this.hasNode(node)) {
|
|
17392
|
+
return this.nodes[node];
|
|
17393
|
+
}
|
|
17394
|
+
throw new Error(`Node does not exist: ${node}`);
|
|
17395
|
+
},
|
|
17396
|
+
/**
|
|
17397
|
+
* Set the associated data for a given node name. If the node does not exist, this method will throw an error
|
|
17398
|
+
*/
|
|
17399
|
+
setNodeData(node, data) {
|
|
17400
|
+
if (this.hasNode(node)) {
|
|
17401
|
+
this.nodes[node] = data;
|
|
17402
|
+
} else {
|
|
17403
|
+
throw new Error(`Node does not exist: ${node}`);
|
|
17404
|
+
}
|
|
17405
|
+
},
|
|
17406
|
+
/**
|
|
17407
|
+
* Add a dependency between two nodes. If either of the nodes does not exist,
|
|
17408
|
+
* an Error will be thrown.
|
|
17409
|
+
*/
|
|
17410
|
+
addDependency(from, to) {
|
|
17411
|
+
if (!this.hasNode(from)) {
|
|
17412
|
+
throw new Error(`Node does not exist: ${from}`);
|
|
17413
|
+
}
|
|
17414
|
+
if (!this.hasNode(to)) {
|
|
17415
|
+
throw new Error(`Node does not exist: ${to}`);
|
|
17416
|
+
}
|
|
17417
|
+
if (this.outgoingEdges[from].indexOf(to) === -1) {
|
|
17418
|
+
this.outgoingEdges[from].push(to);
|
|
17419
|
+
}
|
|
17420
|
+
if (this.incomingEdges[to].indexOf(from) === -1) {
|
|
17421
|
+
this.incomingEdges[to].push(from);
|
|
17422
|
+
}
|
|
17423
|
+
return true;
|
|
17424
|
+
},
|
|
17425
|
+
/**
|
|
17426
|
+
* Remove a dependency between two nodes.
|
|
17427
|
+
*/
|
|
17428
|
+
removeDependency(from, to) {
|
|
17429
|
+
let idx;
|
|
17430
|
+
if (this.hasNode(from)) {
|
|
17431
|
+
idx = this.outgoingEdges[from].indexOf(to);
|
|
17432
|
+
if (idx >= 0) {
|
|
17433
|
+
this.outgoingEdges[from].splice(idx, 1);
|
|
17434
|
+
}
|
|
17435
|
+
}
|
|
17436
|
+
if (this.hasNode(to)) {
|
|
17437
|
+
idx = this.incomingEdges[to].indexOf(from);
|
|
17438
|
+
if (idx >= 0) {
|
|
17439
|
+
this.incomingEdges[to].splice(idx, 1);
|
|
17440
|
+
}
|
|
17441
|
+
}
|
|
17442
|
+
},
|
|
17443
|
+
/**
|
|
17444
|
+
* Return a clone of the dependency graph. If any custom data is attached
|
|
17445
|
+
* to the nodes, it will only be shallow copied.
|
|
17446
|
+
*/
|
|
17447
|
+
clone() {
|
|
17448
|
+
const source = this;
|
|
17449
|
+
const result = new DepGraph();
|
|
17450
|
+
const keys = Object.keys(source.nodes);
|
|
17451
|
+
keys.forEach(n => {
|
|
17452
|
+
result.nodes[n] = source.nodes[n];
|
|
17453
|
+
result.outgoingEdges[n] = source.outgoingEdges[n].slice(0);
|
|
17454
|
+
result.incomingEdges[n] = source.incomingEdges[n].slice(0);
|
|
17522
17455
|
});
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17528
|
-
|
|
17529
|
-
|
|
17530
|
-
|
|
17531
|
-
|
|
17456
|
+
return result;
|
|
17457
|
+
},
|
|
17458
|
+
/**
|
|
17459
|
+
* Get an array containing the direct dependencies of the specified node.
|
|
17460
|
+
*
|
|
17461
|
+
* Throws an Error if the specified node does not exist.
|
|
17462
|
+
*/
|
|
17463
|
+
directDependenciesOf(node) {
|
|
17464
|
+
if (this.hasNode(node)) {
|
|
17465
|
+
return this.outgoingEdges[node].slice(0);
|
|
17466
|
+
}
|
|
17467
|
+
throw new Error(`Node does not exist: ${node}`);
|
|
17468
|
+
},
|
|
17469
|
+
/**
|
|
17470
|
+
* Get an array containing the nodes that directly depend on the specified node.
|
|
17471
|
+
*
|
|
17472
|
+
* Throws an Error if the specified node does not exist.
|
|
17473
|
+
*/
|
|
17474
|
+
directDependantsOf(node) {
|
|
17475
|
+
if (this.hasNode(node)) {
|
|
17476
|
+
return this.incomingEdges[node].slice(0);
|
|
17477
|
+
}
|
|
17478
|
+
throw new Error(`Node does not exist: ${node}`);
|
|
17479
|
+
},
|
|
17480
|
+
/**
|
|
17481
|
+
* Get an array containing the nodes that the specified node depends on (transitively).
|
|
17482
|
+
*
|
|
17483
|
+
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
|
17484
|
+
*
|
|
17485
|
+
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned
|
|
17486
|
+
* in the array.
|
|
17487
|
+
*/
|
|
17488
|
+
dependenciesOf(node, leavesOnly) {
|
|
17489
|
+
if (this.hasNode(node)) {
|
|
17490
|
+
const result = [];
|
|
17491
|
+
const DFS = createDFS(this.outgoingEdges, leavesOnly, result, this.circular);
|
|
17492
|
+
DFS(node);
|
|
17493
|
+
const idx = result.indexOf(node);
|
|
17494
|
+
if (idx >= 0) {
|
|
17495
|
+
result.splice(idx, 1);
|
|
17532
17496
|
}
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
}
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
if (
|
|
17546
|
-
const
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
return nodeset[offset];
|
|
17553
|
-
}
|
|
17497
|
+
return result;
|
|
17498
|
+
}
|
|
17499
|
+
throw new Error(`Node does not exist: ${node}`);
|
|
17500
|
+
},
|
|
17501
|
+
/**
|
|
17502
|
+
* get an array containing the nodes that depend on the specified node (transitively).
|
|
17503
|
+
*
|
|
17504
|
+
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
|
17505
|
+
*
|
|
17506
|
+
* If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array.
|
|
17507
|
+
*/
|
|
17508
|
+
dependantsOf(node, leavesOnly) {
|
|
17509
|
+
if (this.hasNode(node)) {
|
|
17510
|
+
const result = [];
|
|
17511
|
+
const DFS = createDFS(this.incomingEdges, leavesOnly, result, this.circular);
|
|
17512
|
+
DFS(node);
|
|
17513
|
+
const idx = result.indexOf(node);
|
|
17514
|
+
if (idx >= 0) {
|
|
17515
|
+
result.splice(idx, 1);
|
|
17554
17516
|
}
|
|
17517
|
+
return result;
|
|
17518
|
+
}
|
|
17519
|
+
throw new Error(`Node does not exist: ${node}`);
|
|
17520
|
+
},
|
|
17521
|
+
/**
|
|
17522
|
+
* Get an array of nodes that have no dependants (i.e. nothing depends on them).
|
|
17523
|
+
*/
|
|
17524
|
+
entryNodes() {
|
|
17525
|
+
const self = this;
|
|
17526
|
+
return Object.keys(this.nodes).filter(node => self.incomingEdges[node].length === 0);
|
|
17527
|
+
},
|
|
17528
|
+
/**
|
|
17529
|
+
* Construct the overall processing order for the dependency graph.
|
|
17530
|
+
*
|
|
17531
|
+
* Throws an Error if the graph has a cycle.
|
|
17532
|
+
*
|
|
17533
|
+
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned.
|
|
17534
|
+
*/
|
|
17535
|
+
overallOrder(leavesOnly) {
|
|
17536
|
+
const self = this;
|
|
17537
|
+
const result = [];
|
|
17538
|
+
const keys = Object.keys(this.nodes);
|
|
17539
|
+
if (keys.length === 0) {
|
|
17540
|
+
return result; // Empty graph
|
|
17555
17541
|
}
|
|
17556
|
-
return instance.nodeset;
|
|
17557
|
-
}
|
|
17558
|
-
return caller.getInScopeContext();
|
|
17559
|
-
};
|
|
17560
|
-
|
|
17561
|
-
// todo: implement
|
|
17562
|
-
const currentFunction = (dynamicContext, string) => {
|
|
17563
|
-
dynamicContext.currentContext.formElement;
|
|
17564
|
-
return null;
|
|
17565
|
-
};
|
|
17566
|
-
const elementFunction = (dynamicContext, string) => {
|
|
17567
|
-
dynamicContext.currentContext.formElement;
|
|
17568
|
-
const newElement = document.createElement(string);
|
|
17569
|
-
return newElement;
|
|
17570
|
-
};
|
|
17571
|
-
|
|
17572
|
-
/**
|
|
17573
|
-
* @param id as string
|
|
17574
|
-
* @return instance data for given id serialized to string.
|
|
17575
|
-
*/
|
|
17576
|
-
registerCustomXPathFunction({
|
|
17577
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17578
|
-
localName: 'context'
|
|
17579
|
-
}, [], 'item()?', contextFunction);
|
|
17580
17542
|
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
17587
|
-
|
|
17588
|
-
}
|
|
17589
|
-
|
|
17590
|
-
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
localName: 'element'
|
|
17596
|
-
}, ['xs:string'], 'item()?', elementFunction);
|
|
17543
|
+
if (!this.circular) {
|
|
17544
|
+
// Look for cycles - we run the DFS starting at all the nodes in case there
|
|
17545
|
+
// are several disconnected subgraphs inside this dependency graph.
|
|
17546
|
+
const CycleDFS = createDFS(this.outgoingEdges, false, [], this.circular);
|
|
17547
|
+
keys.forEach(n => {
|
|
17548
|
+
CycleDFS(n);
|
|
17549
|
+
});
|
|
17550
|
+
}
|
|
17551
|
+
const DFS = createDFS(this.outgoingEdges, leavesOnly, result, this.circular);
|
|
17552
|
+
// Find all potential starting points (nodes with nothing depending on them) an
|
|
17553
|
+
// run a DFS starting at these points to get the order
|
|
17554
|
+
keys.filter(node => self.incomingEdges[node].length === 0).forEach(n => {
|
|
17555
|
+
DFS(n);
|
|
17556
|
+
});
|
|
17597
17557
|
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
17603
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17604
|
-
localName: 'log'
|
|
17605
|
-
}, ['xs:string?'], 'xs:string?', (dynamicContext, string) => {
|
|
17606
|
-
const {
|
|
17607
|
-
formElement
|
|
17608
|
-
} = dynamicContext.currentContext;
|
|
17609
|
-
const instance = resolveId(string, formElement, 'fx-instance');
|
|
17610
|
-
if (instance) {
|
|
17611
|
-
if (instance.getAttribute('type') === 'json') {
|
|
17612
|
-
console.warn('log() does not work for JSON yet');
|
|
17613
|
-
// return JSON.stringify(instance.getDefaultContext());
|
|
17614
|
-
} else {
|
|
17615
|
-
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
17616
|
-
return prettifyXml(def);
|
|
17558
|
+
// If we're allowing cycles - we need to run the DFS against any remaining
|
|
17559
|
+
// nodes that did not end up in the initial result (as they are part of a
|
|
17560
|
+
// subgraph that does not have a clear starting point)
|
|
17561
|
+
if (this.circular) {
|
|
17562
|
+
keys.filter(node => result.indexOf(node) === -1).forEach(n => DFS(n));
|
|
17617
17563
|
}
|
|
17564
|
+
return result;
|
|
17618
17565
|
}
|
|
17619
|
-
|
|
17620
|
-
});
|
|
17621
|
-
registerCustomXPathFunction({
|
|
17622
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17623
|
-
localName: 'fore-attr'
|
|
17624
|
-
}, ['xs:string?'], 'xs:string?', (dynamicContext, string) => {
|
|
17625
|
-
const {
|
|
17626
|
-
formElement
|
|
17627
|
-
} = dynamicContext.currentContext;
|
|
17628
|
-
let parent = formElement;
|
|
17629
|
-
if (formElement.nodeType === Node.TEXT_NODE) {
|
|
17630
|
-
parent = formElement.parentNode;
|
|
17631
|
-
}
|
|
17632
|
-
const foreElement = parent.closest('fx-fore');
|
|
17633
|
-
if (foreElement.hasAttribute(string)) {
|
|
17634
|
-
return foreElement.getAttribute(string);
|
|
17635
|
-
}
|
|
17636
|
-
return null;
|
|
17637
|
-
});
|
|
17638
|
-
registerCustomXPathFunction({
|
|
17639
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17640
|
-
localName: 'parse'
|
|
17641
|
-
}, ['xs:string?'], 'element()?', (_dynamicContext, string) => {
|
|
17642
|
-
const parser = new DOMParser();
|
|
17643
|
-
const out = parser.parseFromString(string, 'application/xml');
|
|
17644
|
-
console.log('parse', out);
|
|
17566
|
+
};
|
|
17645
17567
|
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
|
|
17649
|
-
if (instance) {
|
|
17650
|
-
if (instance.getAttribute('type') === 'json') {
|
|
17651
|
-
console.warn('log() does not work for JSON yet');
|
|
17652
|
-
// return JSON.stringify(instance.getDefaultContext());
|
|
17653
|
-
} else {
|
|
17654
|
-
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
17655
|
-
return Fore.prettifyXml(def);
|
|
17656
|
-
}
|
|
17657
|
-
}
|
|
17658
|
-
*/
|
|
17659
|
-
return out.firstElementChild;
|
|
17660
|
-
});
|
|
17661
|
-
function buildTree(tree, data) {
|
|
17662
|
-
if (!data) return;
|
|
17663
|
-
if (data.nodeType === Node.ELEMENT_NODE) {
|
|
17664
|
-
if (data.children) {
|
|
17665
|
-
const details = document.createElement('details');
|
|
17666
|
-
details.setAttribute('data-path', data.nodeName);
|
|
17667
|
-
const summary = document.createElement('summary');
|
|
17668
|
-
let display = ` <${data.nodeName}`;
|
|
17669
|
-
Array.from(data.attributes).forEach(attr => {
|
|
17670
|
-
display += ` ${attr.nodeName}="${attr.nodeValue}"`;
|
|
17671
|
-
});
|
|
17672
|
-
let contents;
|
|
17673
|
-
if (data.firstChild && data.firstChild.nodeType === Node.TEXT_NODE && data.firstChild.data.trim() !== '') {
|
|
17674
|
-
// console.log('whoooooooooopp');
|
|
17675
|
-
contents = data.firstChild.nodeValue;
|
|
17676
|
-
display += `>${contents}</${data.nodeName}>`;
|
|
17677
|
-
} else {
|
|
17678
|
-
display += '>';
|
|
17679
|
-
}
|
|
17680
|
-
summary.textContent = display;
|
|
17681
|
-
details.appendChild(summary);
|
|
17682
|
-
if (data.childElementCount !== 0) {
|
|
17683
|
-
details.setAttribute('open', 'open');
|
|
17684
|
-
} else {
|
|
17685
|
-
summary.setAttribute('style', 'list-style:none;');
|
|
17686
|
-
}
|
|
17687
|
-
tree.appendChild(details);
|
|
17688
|
-
Array.from(data.children).forEach(child => {
|
|
17689
|
-
// if(child.nodeType === Node.ELEMENT_NODE){
|
|
17690
|
-
// child.parentNode.appendChild(buildTree(child));
|
|
17691
|
-
buildTree(details, child);
|
|
17692
|
-
// }
|
|
17693
|
-
});
|
|
17694
|
-
}
|
|
17695
|
-
} /* else if(data.nodeType === Node.ATTRIBUTE_NODE){
|
|
17696
|
-
//create span for now
|
|
17697
|
-
// const span = document.createElement('span');
|
|
17698
|
-
// span.style.background = 'grey';
|
|
17699
|
-
// span.textContent = data.value;
|
|
17700
|
-
// tree.appendChild(span);
|
|
17701
|
-
tree.setAttribute(data.nodeName,data.value);
|
|
17702
|
-
}else {
|
|
17703
|
-
tree.textContent = data;
|
|
17704
|
-
} */
|
|
17568
|
+
// Create some aliases
|
|
17569
|
+
DepGraph.prototype.directDependentsOf = DepGraph.prototype.directDependantsOf;
|
|
17570
|
+
DepGraph.prototype.dependentsOf = DepGraph.prototype.dependantsOf;
|
|
17705
17571
|
|
|
17706
|
-
|
|
17572
|
+
function prettifyXml(source) {
|
|
17573
|
+
const xmlDoc = new DOMParser().parseFromString(source, 'application/xml');
|
|
17574
|
+
const xsltDoc = new DOMParser().parseFromString([
|
|
17575
|
+
// describes how we want to modify the XML - indent everything
|
|
17576
|
+
'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">', ' <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>', ' <xsl:strip-space elements="*"/>', ' <xsl:template match="text()">',
|
|
17577
|
+
// change to just text() to strip space in text nodes
|
|
17578
|
+
' <xsl:value-of select="normalize-space(.)"/>', ' </xsl:template>', ' <xsl:template match="node()|@*">', ' <xsl:copy>', ' <xsl:apply-templates select="node()|@*"/>', ' </xsl:copy>', ' </xsl:template>', '</xsl:stylesheet>'].join('\n'), 'application/xml');
|
|
17579
|
+
const xsltProcessor = new XSLTProcessor();
|
|
17580
|
+
xsltProcessor.importStylesheet(xsltDoc);
|
|
17581
|
+
const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
|
|
17582
|
+
const resultXml = new XMLSerializer().serializeToString(resultDoc);
|
|
17583
|
+
return resultXml;
|
|
17707
17584
|
}
|
|
17708
17585
|
|
|
17709
|
-
|
|
17710
|
-
|
|
17711
|
-
localName: 'logtree'
|
|
17712
|
-
}, ['xs:string?'], 'element()?', (dynamicContext, string) => {
|
|
17713
|
-
const {
|
|
17714
|
-
formElement
|
|
17715
|
-
} = dynamicContext.currentContext;
|
|
17716
|
-
const instance = resolveId(string, formElement, 'fx-instance');
|
|
17717
|
-
if (instance) {
|
|
17718
|
-
// const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
17719
|
-
// const def = JSON.stringify(instance.getDefaultContext());
|
|
17586
|
+
const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
17587
|
+
const createdNamespaceResolversByXPathQueryAndNode = new Map();
|
|
17720
17588
|
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17727
|
-
const logtree = form.querySelector('.logtree');
|
|
17728
|
-
if (logtree) {
|
|
17729
|
-
logtree.parentNode.removeChild(logtree);
|
|
17730
|
-
}
|
|
17731
|
-
const tree = buildTree(treeDiv, instance.getDefaultContext());
|
|
17732
|
-
if (tree) {
|
|
17733
|
-
form.appendChild(tree);
|
|
17734
|
-
}
|
|
17589
|
+
// A global registry of function names that are declared in Fore by a developer using the
|
|
17590
|
+
// `fx-function` element. These should be available without providing a prefix as well
|
|
17591
|
+
const globallyDeclaredFunctionLocalNames = [];
|
|
17592
|
+
function getCachedNamespaceResolver(xpath, node) {
|
|
17593
|
+
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
17594
|
+
return null;
|
|
17735
17595
|
}
|
|
17736
|
-
return null;
|
|
17737
|
-
}
|
|
17738
|
-
|
|
17739
|
-
|
|
17740
|
-
|
|
17741
|
-
|
|
17742
|
-
/**
|
|
17743
|
-
* @type {import('./fx-fore.js').FxFore}
|
|
17744
|
-
*/
|
|
17745
|
-
const formElement = evaluateXPathToFirstNode$1('ancestor-or-self::fx-fore[1]', dynamicContext.currentContext.formElement, null, null, {
|
|
17746
|
-
namespaceResolver: xhtmlNamespaceResolver
|
|
17747
|
-
});
|
|
17748
|
-
let lookup = null;
|
|
17749
|
-
if (string === null || string === 'default') {
|
|
17750
|
-
lookup = formElement.getModel().getDefaultInstance();
|
|
17751
|
-
} else {
|
|
17752
|
-
lookup = formElement.getModel().getInstance(string);
|
|
17753
|
-
if (!lookup) {
|
|
17754
|
-
document.querySelector('fx-fore').dispatchEvent(new CustomEvent('error', {
|
|
17755
|
-
composed: true,
|
|
17756
|
-
bubbles: true,
|
|
17757
|
-
detail: {
|
|
17758
|
-
origin: 'functions',
|
|
17759
|
-
message: `Instance not found '${string}'`,
|
|
17760
|
-
level: 'Error'
|
|
17761
|
-
}
|
|
17762
|
-
}));
|
|
17763
|
-
}
|
|
17596
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).get(node) || null;
|
|
17597
|
+
}
|
|
17598
|
+
function setCachedNamespaceResolver(xpath, node, resolver) {
|
|
17599
|
+
if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
|
|
17600
|
+
return createdNamespaceResolversByXPathQueryAndNode.set(xpath, new Map());
|
|
17764
17601
|
}
|
|
17765
|
-
|
|
17766
|
-
|
|
17767
|
-
|
|
17602
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).set(node, resolver);
|
|
17603
|
+
}
|
|
17604
|
+
const xhtmlNamespaceResolver = prefix => {
|
|
17605
|
+
if (!prefix) {
|
|
17606
|
+
return 'http://www.w3.org/1999/xhtml';
|
|
17768
17607
|
}
|
|
17769
|
-
return
|
|
17608
|
+
return undefined;
|
|
17770
17609
|
};
|
|
17771
|
-
registerCustomXPathFunction({
|
|
17772
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17773
|
-
localName: 'index'
|
|
17774
|
-
}, ['xs:string?'], 'xs:integer?', (dynamicContext, string) => {
|
|
17775
|
-
const {
|
|
17776
|
-
formElement
|
|
17777
|
-
} = dynamicContext.currentContext;
|
|
17778
|
-
if (string === null) {
|
|
17779
|
-
return 1;
|
|
17780
|
-
}
|
|
17781
|
-
const repeat = resolveId(string, formElement, 'fx-repeat');
|
|
17782
17610
|
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17611
|
+
/**
|
|
17612
|
+
* Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
|
|
17613
|
+
*
|
|
17614
|
+
* @param {string} id
|
|
17615
|
+
* @param {Node} sourceObject
|
|
17616
|
+
* @param {string} nodeName
|
|
17617
|
+
*
|
|
17618
|
+
* @returns {HTMLElement} The element with that ID, resolved with respect to repeats
|
|
17619
|
+
*/
|
|
17620
|
+
function resolveId(id, sourceObject, nodeName = null) {
|
|
17621
|
+
const query = 'outermost(ancestor-or-self::fx-fore[1]/(descendant::fx-fore|descendant::*[@id = $id]))[not(self::fx-fore)]';
|
|
17622
|
+
/*
|
|
17623
|
+
if (nodeName === 'fx-instance') {
|
|
17624
|
+
// Instance elements can only be in the `model` element
|
|
17625
|
+
// query = 'ancestor-or-self::fx-fore[1]/fx-model/fx-instance[@id = $id]';
|
|
17626
|
+
const fore = Fore.getFore(sourceObject);
|
|
17627
|
+
const instances = fore.getModel().instances;
|
|
17628
|
+
const targetInstance = instances.find(i => i.id === id);
|
|
17629
|
+
return targetInstance;
|
|
17630
|
+
return document.getElementById(id);
|
|
17786
17631
|
}
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
// Note that this is not to spec. The spec enforces elements to be returned from the
|
|
17791
|
-
// instance. However, we allow instances to actually be JSON!
|
|
17792
|
-
registerCustomXPathFunction({
|
|
17793
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17794
|
-
localName: 'instance'
|
|
17795
|
-
}, [], 'item()?', domFacade => instance(domFacade, null));
|
|
17796
|
-
registerCustomXPathFunction({
|
|
17797
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17798
|
-
localName: 'instance'
|
|
17799
|
-
}, ['xs:string?'], 'item()?', instance);
|
|
17800
|
-
const jsonToXml = (_dynamicContext, json) => {
|
|
17801
|
-
const escapeXml = str => str.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]/g, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
17802
|
-
const convert = (obj, parent) => {
|
|
17803
|
-
const type = typeof obj;
|
|
17804
|
-
if (type === 'number') {
|
|
17805
|
-
parent.setAttribute('type', 'number');
|
|
17806
|
-
parent.textContent = obj.toString();
|
|
17807
|
-
} else if (type === 'boolean') {
|
|
17808
|
-
parent.setAttribute('type', 'boolean');
|
|
17809
|
-
parent.textContent = obj.toString();
|
|
17810
|
-
} else if (obj === null) {
|
|
17811
|
-
const node = document.createElement('_');
|
|
17812
|
-
node.setAttribute('type', 'null');
|
|
17813
|
-
parent.appendChild(node);
|
|
17814
|
-
} else if (type === 'string') {
|
|
17815
|
-
parent.textContent = escapeXml(obj);
|
|
17816
|
-
} else if (Array.isArray(obj)) {
|
|
17817
|
-
parent.setAttribute('type', 'array');
|
|
17818
|
-
obj.forEach(item => {
|
|
17819
|
-
const node = document.createElement('_');
|
|
17820
|
-
convert(item, node);
|
|
17821
|
-
node.textContent = item;
|
|
17822
|
-
parent.appendChild(node);
|
|
17823
|
-
});
|
|
17824
|
-
} else if (type === 'object') {
|
|
17825
|
-
parent.setAttribute('type', 'object');
|
|
17826
|
-
Object.entries(obj).forEach(([key, value]) => {
|
|
17827
|
-
if (value) {
|
|
17828
|
-
const childNode = document.createElement(key.replace(/[^a-zA-Z0-9_]/g, '_'));
|
|
17829
|
-
convert(value, childNode);
|
|
17830
|
-
parent.appendChild(childNode);
|
|
17831
|
-
}
|
|
17832
|
-
});
|
|
17833
|
-
}
|
|
17834
|
-
};
|
|
17835
|
-
const root = document.createElement('json');
|
|
17836
|
-
if (Array.isArray(json)) {
|
|
17837
|
-
root.setAttribute('type', 'array');
|
|
17838
|
-
} else {
|
|
17839
|
-
root.setAttribute('type', 'object');
|
|
17632
|
+
*/
|
|
17633
|
+
if (sourceObject.nodeType === Node.TEXT_NODE) {
|
|
17634
|
+
sourceObject = sourceObject.parentNode;
|
|
17840
17635
|
}
|
|
17841
|
-
|
|
17842
|
-
|
|
17843
|
-
|
|
17844
|
-
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
|
|
17853
|
-
|
|
17854
|
-
if (isElementNode(node)) {
|
|
17855
|
-
const obj = {};
|
|
17856
|
-
if (node.hasAttributes()) {
|
|
17857
|
-
obj.type = node.getAttribute('type');
|
|
17858
|
-
}
|
|
17859
|
-
if (node.childNodes.length === 1 && isTextNode(node.firstChild)) {
|
|
17860
|
-
return node.textContent;
|
|
17861
|
-
}
|
|
17862
|
-
for (const child of node.childNodes) {
|
|
17863
|
-
const childName = child.nodeName;
|
|
17864
|
-
const childValue = parseNode(child);
|
|
17865
|
-
if (obj[childName]) {
|
|
17866
|
-
if (!Array.isArray(obj[childName])) {
|
|
17867
|
-
obj[childName] = [obj[childName]];
|
|
17868
|
-
}
|
|
17869
|
-
obj[childName].push(childValue);
|
|
17870
|
-
} else {
|
|
17871
|
-
obj[childName] = childValue;
|
|
17872
|
-
}
|
|
17873
|
-
}
|
|
17874
|
-
return obj;
|
|
17636
|
+
if (sourceObject.nodeType === Node.ATTRIBUTE_NODE) {
|
|
17637
|
+
sourceObject = sourceObject.ownerElement;
|
|
17638
|
+
}
|
|
17639
|
+
if (sourceObject.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
17640
|
+
sourceObject = sourceObject.parentNode.host;
|
|
17641
|
+
}
|
|
17642
|
+
const ownerForm = sourceObject.localName === 'fx-fore' ? sourceObject : sourceObject.closest('fx-fore');
|
|
17643
|
+
const elementsWithId = ownerForm.querySelectorAll(`[id='${id}']`);
|
|
17644
|
+
if (elementsWithId.length === 1) {
|
|
17645
|
+
// A single one is found. Assume no ID reuse.
|
|
17646
|
+
const targetObject = elementsWithId[0];
|
|
17647
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
17648
|
+
return null;
|
|
17875
17649
|
}
|
|
17876
|
-
|
|
17877
|
-
|
|
17650
|
+
return targetObject;
|
|
17651
|
+
}
|
|
17652
|
+
const allMatchingTargetObjects = evaluateXPathToNodes$1(query, sourceObject, null, {
|
|
17653
|
+
id
|
|
17654
|
+
}, {
|
|
17655
|
+
namespaceResolver: xhtmlNamespaceResolver
|
|
17656
|
+
});
|
|
17657
|
+
if (allMatchingTargetObjects.length === 0) {
|
|
17658
|
+
return null;
|
|
17659
|
+
}
|
|
17660
|
+
if (allMatchingTargetObjects.length === 1 && evaluateXPathToBoolean$1('(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore', allMatchingTargetObjects[0], null, null, {
|
|
17661
|
+
namespaceResolver: xhtmlNamespaceResolver
|
|
17662
|
+
})) {
|
|
17663
|
+
// If the target element is not repeated, then the search for the target object is trivial since
|
|
17664
|
+
// there is only one associated with the target element that bears the matching ID. This is true
|
|
17665
|
+
// regardless of whether or not the source object is repeated. However, if the target element is
|
|
17666
|
+
// repeated, then additional information must be used to help select a target object from among
|
|
17667
|
+
// those associated with the identified target element.
|
|
17668
|
+
const targetObject = allMatchingTargetObjects[0];
|
|
17669
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
17670
|
+
return null;
|
|
17878
17671
|
}
|
|
17879
|
-
return
|
|
17880
|
-
}
|
|
17881
|
-
const parser = new DOMParser();
|
|
17882
|
-
const xmlDoc = parser.parseFromString(xml, 'application/xml');
|
|
17883
|
-
const root = xmlDoc.documentElement;
|
|
17884
|
-
return parseNode(root);
|
|
17885
|
-
};
|
|
17886
|
-
registerCustomXPathFunction({
|
|
17887
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17888
|
-
localName: 'xmltoJson'
|
|
17889
|
-
}, ['item()?'], 'item()?', xmlToJson);
|
|
17672
|
+
return targetObject;
|
|
17673
|
+
}
|
|
17890
17674
|
|
|
17891
|
-
|
|
17892
|
-
// Example usage:
|
|
17893
|
-
const xml = '<json type="object"><given>Mark</given><family>Smith</family></json>';
|
|
17894
|
-
console.log(xmlToJson(xml));
|
|
17895
|
-
*/
|
|
17675
|
+
// SPEC:
|
|
17896
17676
|
|
|
17897
|
-
|
|
17898
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17899
|
-
localName: 'depends'
|
|
17900
|
-
}, ['node()*'], 'item()?', (_dynamicContext, nodes) =>
|
|
17901
|
-
// console.log('depends on : ', nodes[0]);
|
|
17902
|
-
nodes[0]);
|
|
17903
|
-
registerCustomXPathFunction({
|
|
17904
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17905
|
-
localName: 'event'
|
|
17906
|
-
}, ['xs:string?'], 'item()?', (dynamicContext, arg) => {
|
|
17907
|
-
if (!arg) return null;
|
|
17908
|
-
for (let ancestor = dynamicContext.currentContext.formElement; ancestor; ancestor = ancestor.parentNode) {
|
|
17909
|
-
if (!ancestor.currentEvent) {
|
|
17910
|
-
continue;
|
|
17911
|
-
}
|
|
17677
|
+
// 12.2.1 References to Elements within a repeat Element
|
|
17912
17678
|
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
}
|
|
17679
|
+
// When the target element that is identified by the IDREF of a source object has one or more
|
|
17680
|
+
// repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
|
|
17681
|
+
// subsets, those in common with the source element and those that are not in common. Any ancestor
|
|
17682
|
+
// repeat elements of the target element not in common with the source element are descendants of
|
|
17683
|
+
// the repeat elements that the source and target element have in common, if any.
|
|
17919
17684
|
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
|
|
17685
|
+
// For the repeat elements that are in common, the desired target object exists in the same set of
|
|
17686
|
+
// run-time objects that contains the source object. Then, for each ancestor repeat of the target
|
|
17687
|
+
// element that is not in common with the source element, the current index of the repeat
|
|
17688
|
+
// determines the set of run-time objects that contains the desired target object.
|
|
17689
|
+
for (const ancestorRepeatItem of evaluateXPathToNodes$1('ancestor::fx-repeatitem => reverse()', sourceObject, null, null, {
|
|
17690
|
+
namespaceResolver: xhtmlNamespaceResolver
|
|
17691
|
+
})) {
|
|
17692
|
+
const foundTargetObjects = allMatchingTargetObjects.filter(to => XPathUtil.contains(ancestorRepeatItem, to));
|
|
17693
|
+
switch (foundTargetObjects.length) {
|
|
17694
|
+
case 0:
|
|
17695
|
+
// Nothing found: ignore
|
|
17696
|
+
break;
|
|
17697
|
+
case 1:
|
|
17698
|
+
{
|
|
17699
|
+
// A single one is found: the target object is directly in a common repeat
|
|
17700
|
+
const targetObject = foundTargetObjects[0];
|
|
17701
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
17702
|
+
return null;
|
|
17703
|
+
}
|
|
17704
|
+
return targetObject;
|
|
17705
|
+
}
|
|
17706
|
+
default:
|
|
17707
|
+
{
|
|
17708
|
+
// Multiple target objects are found: they are in a repeat that is not common with the
|
|
17709
|
+
// source object We found a target object in a common repeat! We now need to find the one
|
|
17710
|
+
// that is in the repeatitem identified at the current index
|
|
17711
|
+
const targetObject = foundTargetObjects.find(to => evaluateXPathToNodes$1('every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]', to, null, {}));
|
|
17712
|
+
if (!targetObject) {
|
|
17713
|
+
// Nothing valid found for whatever reason. This might be something dynamic?
|
|
17714
|
+
return null;
|
|
17715
|
+
}
|
|
17716
|
+
if (nodeName && targetObject.localName !== nodeName) {
|
|
17717
|
+
return null;
|
|
17718
|
+
}
|
|
17719
|
+
return targetObject;
|
|
17720
|
+
}
|
|
17923
17721
|
}
|
|
17924
|
-
return ancestor.currentEvent[arg] || null;
|
|
17925
17722
|
}
|
|
17723
|
+
// We found no target objects in common repeats. The id is unresolvable
|
|
17926
17724
|
return null;
|
|
17927
|
-
});
|
|
17928
|
-
function _propertyLookup(obj, path) {
|
|
17929
|
-
const parts = path.split('.');
|
|
17930
|
-
if (parts.length == 1) {
|
|
17931
|
-
return obj[parts[0]];
|
|
17932
|
-
}
|
|
17933
|
-
return _propertyLookup(obj[parts[0]], parts.slice(1).join('.'));
|
|
17934
17725
|
}
|
|
17935
17726
|
|
|
17936
|
-
//
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
}
|
|
17958
|
-
`)
|
|
17959
|
-
// At some point:
|
|
17960
|
-
const contextNode = null;
|
|
17961
|
-
const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
|
|
17962
|
-
|
|
17963
|
-
console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
|
|
17964
|
-
|
|
17965
|
-
executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
|
|
17966
|
-
*/
|
|
17967
|
-
|
|
17727
|
+
// Make namespace resolving use the `instance` element that is related to here
|
|
17728
|
+
const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
|
|
17729
|
+
const instanceReferencesByQuery = new Map();
|
|
17730
|
+
function findInstanceReferences(xpathQuery) {
|
|
17731
|
+
if (!xpathQuery.includes('instance')) {
|
|
17732
|
+
// No call to the instance function anyway: short-circuit and prevent AST processing
|
|
17733
|
+
return [];
|
|
17734
|
+
}
|
|
17735
|
+
if (instanceReferencesByQuery.has(xpathQuery)) {
|
|
17736
|
+
return instanceReferencesByQuery.get(xpathQuery);
|
|
17737
|
+
}
|
|
17738
|
+
const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
|
|
17739
|
+
const instanceReferences = evaluateXPathToStrings$1(`descendant::xqx:functionCallExpr
|
|
17740
|
+
[xqx:functionName = "instance"]
|
|
17741
|
+
/xqx:arguments
|
|
17742
|
+
/xqx:stringConstantExpr
|
|
17743
|
+
/xqx:value`, xpathAST, null, {}, {
|
|
17744
|
+
namespaceResolver: prefix => prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined
|
|
17745
|
+
});
|
|
17746
|
+
instanceReferencesByQuery.set(xpathQuery, instanceReferences);
|
|
17747
|
+
return instanceReferences;
|
|
17748
|
+
}
|
|
17968
17749
|
/**
|
|
17969
|
-
* @
|
|
17970
|
-
* @return {string}
|
|
17750
|
+
* @typedef {function(string):string} NamespaceResolver
|
|
17971
17751
|
*/
|
|
17972
|
-
registerCustomXPathFunction({
|
|
17973
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17974
|
-
localName: 'base64encode'
|
|
17975
|
-
}, ['xs:string?'], 'xs:string?', (_dynamicContext, string) => btoa(string));
|
|
17976
|
-
registerCustomXPathFunction({
|
|
17977
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17978
|
-
localName: 'local-date'
|
|
17979
|
-
}, [], 'xs:string?', (_dynamicContext, _string) => new Date().toLocaleDateString());
|
|
17980
|
-
registerCustomXPathFunction({
|
|
17981
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17982
|
-
localName: 'local-dateTime'
|
|
17983
|
-
}, [], 'xs:string?', (_dynamicContext, _string) => new Date().toLocaleString());
|
|
17984
|
-
registerCustomXPathFunction({
|
|
17985
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17986
|
-
localName: 'uri'
|
|
17987
|
-
}, [], 'xs:string?', (_dynamicContext, _string) => window.location.href);
|
|
17988
|
-
registerCustomXPathFunction({
|
|
17989
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17990
|
-
localName: 'uri-fragment'
|
|
17991
|
-
}, [], 'xs:string?', (_dynamicContext, _arg) => window.location.hash);
|
|
17992
|
-
registerCustomXPathFunction({
|
|
17993
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17994
|
-
localName: 'uri-host'
|
|
17995
|
-
}, [], 'xs:string?', (_dynamicContext, _arg) => window.location.host);
|
|
17996
|
-
registerCustomXPathFunction({
|
|
17997
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17998
|
-
localName: 'uri-query'
|
|
17999
|
-
}, [], 'xs:string?', (_dynamicContext, _arg) => window.location.search);
|
|
18000
|
-
registerCustomXPathFunction({
|
|
18001
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18002
|
-
localName: 'uri-relpath'
|
|
18003
|
-
}, [], 'xs:string?', (_dynamicContext, _arg) => {
|
|
18004
|
-
const path = new URL(window.location.href).pathname;
|
|
18005
|
-
return path.substring(0, path.lastIndexOf('/') + 1);
|
|
18006
|
-
});
|
|
18007
|
-
registerCustomXPathFunction({
|
|
18008
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18009
|
-
localName: 'uri-path'
|
|
18010
|
-
}, [], 'xs:string?', (_dynamicContext, _arg) => new URL(window.location.href).pathname);
|
|
18011
|
-
registerCustomXPathFunction({
|
|
18012
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18013
|
-
localName: 'uri-port'
|
|
18014
|
-
}, [], 'xs:string?', (_dynamicContext, _arg) => window.location.port);
|
|
18015
|
-
registerCustomXPathFunction({
|
|
18016
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18017
|
-
localName: 'uri-param'
|
|
18018
|
-
}, ['xs:string?'], 'xs:string?', (_dynamicContext, arg) => {
|
|
18019
|
-
if (!arg) return null;
|
|
18020
|
-
const {
|
|
18021
|
-
search
|
|
18022
|
-
} = window.location;
|
|
18023
|
-
const urlparams = new URLSearchParams(search);
|
|
18024
|
-
const param = urlparams.get(arg);
|
|
18025
|
-
return param || '';
|
|
18026
|
-
});
|
|
18027
|
-
registerCustomXPathFunction({
|
|
18028
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18029
|
-
localName: 'uri-scheme'
|
|
18030
|
-
}, [], 'xs:string?', (_dynamicContext, _arg) => new URL(window.location.href).protocol);
|
|
18031
|
-
registerCustomXPathFunction({
|
|
18032
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18033
|
-
localName: 'uri-scheme-specific-part'
|
|
18034
|
-
}, [], 'xs:string?', (_dynamicContext, _arg) => {
|
|
18035
|
-
const uri = window.location.href;
|
|
18036
|
-
return uri.substring(uri.indexOf(':') + 1, uri.length);
|
|
18037
|
-
});
|
|
18038
17752
|
|
|
18039
17753
|
/**
|
|
18040
|
-
* @
|
|
18041
|
-
*
|
|
17754
|
+
* @function
|
|
17755
|
+
* Resolve a namespace. Needs a namespace prefix and the element that is most closely related to the
|
|
17756
|
+
* XPath in which the namespace is being resolved. The prefix will be resolved by using the
|
|
17757
|
+
* ancestry of said element.
|
|
17758
|
+
*
|
|
17759
|
+
* It has two ways of doing so:
|
|
17760
|
+
*
|
|
17761
|
+
* - If the prefix is defined in an `xmlns:XXX="YYY"` namespace declaration, it will return 'YYY'.
|
|
17762
|
+
* - If the prefix is the empty prefix and there is an `xpath-default-namespace="YYY"` attribute in
|
|
17763
|
+
* - the * ancestry, that attribute will be used and 'YYY' will be returned
|
|
17764
|
+
*
|
|
17765
|
+
* @param {string} xpathQuery
|
|
17766
|
+
* @param {HTMLElement} formElement
|
|
17767
|
+
* @returns {NamespaceResolver} The namespace resolver for this context
|
|
18042
17768
|
*/
|
|
18043
|
-
|
|
18044
|
-
|
|
18045
|
-
|
|
18046
|
-
|
|
18047
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
18056
|
-
static containsDynamicContent(astNode) {
|
|
18057
|
-
if (!astNode) return false;
|
|
18058
|
-
|
|
18059
|
-
// Location paths, function calls, or variable refs are dynamic
|
|
18060
|
-
if (astNode.type === 'pathExpression' || astNode.type === 'functionCall' || astNode.type === 'variableReference') {
|
|
18061
|
-
return true;
|
|
17769
|
+
function createNamespaceResolver(xpathQuery, formElement) {
|
|
17770
|
+
const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
|
|
17771
|
+
if (cachedResolver) {
|
|
17772
|
+
return cachedResolver;
|
|
17773
|
+
}
|
|
17774
|
+
let instanceReferences = findInstanceReferences(xpathQuery);
|
|
17775
|
+
if (instanceReferences.length === 0) {
|
|
17776
|
+
// No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
|
|
17777
|
+
const ancestorComponent = formElement.parentNode && formElement.parentNode.nodeType === formElement.ELEMENT_NODE && formElement.parentNode.closest('[ref]');
|
|
17778
|
+
if (ancestorComponent) {
|
|
17779
|
+
const resolver = createNamespaceResolver(ancestorComponent.getAttribute('ref'), ancestorComponent);
|
|
17780
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolver);
|
|
17781
|
+
return resolver;
|
|
18062
17782
|
}
|
|
18063
|
-
|
|
18064
|
-
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
|
|
18069
|
-
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
|
|
17783
|
+
// Nothing found: let's just assume we're supposed to use the `default` instance
|
|
17784
|
+
instanceReferences = ['default'];
|
|
17785
|
+
}
|
|
17786
|
+
if (instanceReferences.length === 1) {
|
|
17787
|
+
// console.log(`resolving ${xpathQuery} with ${instanceReferences[0]}`);
|
|
17788
|
+
let instance;
|
|
17789
|
+
if (instanceReferences[0] === 'default') {
|
|
17790
|
+
/**
|
|
17791
|
+
* @type {HTMLElement}
|
|
17792
|
+
*/
|
|
17793
|
+
const actualForeElement = evaluateXPathToFirstNode$1('ancestor-or-self::fx-fore[1]', formElement, null, null, {
|
|
17794
|
+
namespaceResolver: xhtmlNamespaceResolver
|
|
17795
|
+
});
|
|
17796
|
+
instance = actualForeElement && actualForeElement.querySelector('fx-instance');
|
|
17797
|
+
} else {
|
|
17798
|
+
instance = resolveId(instanceReferences[0], formElement, 'fx-instance');
|
|
17799
|
+
}
|
|
17800
|
+
if (instance && instance.hasAttribute('xpath-default-namespace')) {
|
|
17801
|
+
const xpathDefaultNamespace = instance.getAttribute('xpath-default-namespace');
|
|
17802
|
+
/*
|
|
17803
|
+
console.log(
|
|
17804
|
+
`Resolving the xpath ${xpathQuery} with the default namespace set to ${xpathDefaultNamespace}`,
|
|
17805
|
+
);
|
|
17806
|
+
*/
|
|
17807
|
+
/**
|
|
17808
|
+
* @type {NamespaceResolver}
|
|
17809
|
+
*/
|
|
17810
|
+
const resolveNamespacePrefix = prefix => {
|
|
17811
|
+
if (!prefix) {
|
|
17812
|
+
return xpathDefaultNamespace;
|
|
18073
17813
|
}
|
|
18074
|
-
|
|
17814
|
+
return undefined;
|
|
17815
|
+
};
|
|
17816
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
17817
|
+
return resolveNamespacePrefix;
|
|
18075
17818
|
}
|
|
18076
|
-
return false;
|
|
18077
17819
|
}
|
|
17820
|
+
/*
|
|
17821
|
+
if (instanceReferences.length > 1) {
|
|
17822
|
+
console.warn(
|
|
17823
|
+
`More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
|
|
17824
|
+
);
|
|
17825
|
+
}
|
|
17826
|
+
*/
|
|
17827
|
+
|
|
17828
|
+
const xpathDefaultNamespace = evaluateXPathToString$1('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) || '';
|
|
18078
17829
|
|
|
18079
17830
|
/**
|
|
18080
|
-
*
|
|
18081
|
-
* nodes.
|
|
18082
|
-
* E.g. 'foo/bar' creates an element 'foo' with an child element 'bar'
|
|
18083
|
-
* 'foo/@bar' creates a 'foo' element with an 'bar' attribute
|
|
18084
|
-
*
|
|
18085
|
-
* supports multiple steps
|
|
18086
|
-
*
|
|
18087
|
-
* @param xpath
|
|
18088
|
-
* @param doc {XMLDocument}
|
|
18089
|
-
* @param fore
|
|
18090
|
-
* @return {Node|Attr}
|
|
17831
|
+
* @type {NamespaceResolver}
|
|
18091
17832
|
*/
|
|
18092
|
-
|
|
18093
|
-
|
|
18094
|
-
|
|
18095
|
-
doc = document.implementation.createDocument(null, null, null); // Create a new XML document if not provided
|
|
18096
|
-
}
|
|
18097
|
-
|
|
18098
|
-
const parts = [];
|
|
18099
|
-
let scratch = '';
|
|
18100
|
-
let isInPredicate = false;
|
|
18101
|
-
for (const char of xpath.split('')) {
|
|
18102
|
-
if (!isInPredicate) {
|
|
18103
|
-
// We are not in a predicate, the slash will terminate our step.
|
|
18104
|
-
if (char === '/') {
|
|
18105
|
-
parts.push(scratch);
|
|
18106
|
-
scratch = '';
|
|
18107
|
-
continue;
|
|
18108
|
-
}
|
|
18109
|
-
scratch += char;
|
|
18110
|
-
if (char === '[') {
|
|
18111
|
-
isInPredicate = true;
|
|
18112
|
-
}
|
|
18113
|
-
continue;
|
|
18114
|
-
}
|
|
18115
|
-
// We are in a predicate! So the only interesting token is ']', which means we're out of one.
|
|
18116
|
-
scratch += char;
|
|
18117
|
-
if (char === ']') {
|
|
18118
|
-
isInPredicate = false;
|
|
18119
|
-
}
|
|
17833
|
+
const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
|
|
17834
|
+
if (prefix === '') {
|
|
17835
|
+
return xpathDefaultNamespace;
|
|
18120
17836
|
}
|
|
18121
|
-
// Flush the last step
|
|
18122
|
-
parts.push(scratch);
|
|
18123
|
-
let rootNode = null;
|
|
18124
|
-
let currentNode = null;
|
|
18125
|
-
for (const part of parts) {
|
|
18126
|
-
if (!part) continue; // Skip empty parts (e.g., leading slashes)
|
|
18127
|
-
if (part === '.') {
|
|
18128
|
-
// A '.' does not introduce new elements
|
|
18129
|
-
continue;
|
|
18130
|
-
}
|
|
18131
17837
|
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
if (!currentNode) {
|
|
18136
|
-
return doc.createAttribute(attrName, '');
|
|
18137
|
-
}
|
|
18138
|
-
currentNode.setAttribute(attrName, '');
|
|
18139
|
-
} else {
|
|
18140
|
-
// We are a predicate selector! Handle it
|
|
18141
|
-
// This regex matches strings like:
|
|
18142
|
-
// - listBibl
|
|
18143
|
-
// - tei:listBibl
|
|
18144
|
-
// - listBibl[@type="foo"]
|
|
18145
|
-
// - listBibl[@type="foo"][@class="bar"]
|
|
18146
|
-
// It will also match strings like
|
|
18147
|
-
// - listBibl[ancestor-or-self::foo]
|
|
18148
|
-
// which will be filtered out later.
|
|
17838
|
+
// Note: ideally we should use Node#lookupNamespaceURI. However, the nodes we are passed are
|
|
17839
|
+
// XML. The best we can do is emulate the `xmlns:xxx` namespace declarations by regarding them as
|
|
17840
|
+
// attributes. Which they technically ARE NOT!
|
|
18149
17841
|
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
|
|
18154
|
-
|
|
18155
|
-
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
18161
|
-
const newElement = namespace ? doc.createElementNS(namespace, localName) : doc.createElement(localName);
|
|
18162
|
-
if (predicates) {
|
|
18163
|
-
const predicateExtractionRegex = /(\[@(?<name>[\w:-]*)\s?=\s?["'](?<value>[^"']*)['"]\])+/g;
|
|
18164
|
-
const parsedPredicates = predicates.matchAll(predicateExtractionRegex).map(match => ({
|
|
18165
|
-
attrName: match.groups.name,
|
|
18166
|
-
value: match.groups.value
|
|
18167
|
-
}));
|
|
18168
|
-
for (const {
|
|
18169
|
-
attrName,
|
|
18170
|
-
value
|
|
18171
|
-
} of parsedPredicates) {
|
|
18172
|
-
newElement.setAttribute(attrName, value);
|
|
18173
|
-
}
|
|
18174
|
-
}
|
|
18175
|
-
if (!rootNode) {
|
|
18176
|
-
rootNode = newElement; // Set as the root node
|
|
18177
|
-
} else {
|
|
18178
|
-
currentNode.appendChild(newElement);
|
|
18179
|
-
}
|
|
18180
|
-
currentNode = newElement;
|
|
18181
|
-
}
|
|
18182
|
-
}
|
|
18183
|
-
if (!rootNode) {
|
|
18184
|
-
throw new Error('Invalid XPath; no root element could be created.');
|
|
18185
|
-
}
|
|
18186
|
-
return rootNode;
|
|
17842
|
+
return evaluateXPathToString$1('ancestor-or-self::*/@*[name() = "xmlns:" || $prefix][last()]', formElement, null, {
|
|
17843
|
+
prefix
|
|
17844
|
+
});
|
|
17845
|
+
};
|
|
17846
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
17847
|
+
return resolveNamespacePrefix;
|
|
17848
|
+
}
|
|
17849
|
+
function createNamespaceResolverForNode(query, contextNode, formElement) {
|
|
17850
|
+
if ((contextNode && contextNode.ownerDocument || contextNode) === window.document) {
|
|
17851
|
+
// Running a query on the HTML DOM. Don't bother resolving namespaces in any other way
|
|
17852
|
+
return xhtmlNamespaceResolver;
|
|
18187
17853
|
}
|
|
17854
|
+
return createNamespaceResolver(query, formElement);
|
|
17855
|
+
}
|
|
18188
17856
|
|
|
18189
|
-
|
|
18190
|
-
|
|
18191
|
-
|
|
18192
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
|
|
18197
|
-
|
|
18198
|
-
|
|
18199
|
-
|
|
18200
|
-
|
|
18201
|
-
|
|
18202
|
-
|
|
18203
|
-
|
|
18204
|
-
|
|
18205
|
-
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
17857
|
+
/**
|
|
17858
|
+
* Implementation of the functionNameResolver passed to FontoXPath to
|
|
17859
|
+
* redirect function resolving for unprefixed functions to either the fn or the xf namespace
|
|
17860
|
+
*/
|
|
17861
|
+
// eslint-disable-next-line no-unused-vars
|
|
17862
|
+
function functionNameResolver({
|
|
17863
|
+
prefix,
|
|
17864
|
+
localName
|
|
17865
|
+
}, _arity) {
|
|
17866
|
+
switch (localName) {
|
|
17867
|
+
// TODO: put the full XForms library functions set here
|
|
17868
|
+
case 'context':
|
|
17869
|
+
case 'base64encode':
|
|
17870
|
+
case 'boolean-from-string':
|
|
17871
|
+
case 'current':
|
|
17872
|
+
case 'depends':
|
|
17873
|
+
case 'event':
|
|
17874
|
+
case 'fore-attr':
|
|
17875
|
+
case 'index':
|
|
17876
|
+
case 'instance':
|
|
17877
|
+
case 'json2xml':
|
|
17878
|
+
case 'xml2Json':
|
|
17879
|
+
case 'log':
|
|
17880
|
+
case 'parse':
|
|
17881
|
+
case 'local-date':
|
|
17882
|
+
case 'local-dateTime':
|
|
17883
|
+
case 'logtree':
|
|
17884
|
+
case 'uri':
|
|
17885
|
+
case 'uri-fragment':
|
|
17886
|
+
case 'uri-host':
|
|
17887
|
+
case 'uri-param':
|
|
17888
|
+
case 'uri-path':
|
|
17889
|
+
case 'uri-relpath':
|
|
17890
|
+
case 'uri-port':
|
|
17891
|
+
case 'uri-query':
|
|
17892
|
+
case 'uri-scheme':
|
|
17893
|
+
case 'uri-scheme-specific-part':
|
|
17894
|
+
return {
|
|
17895
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17896
|
+
localName
|
|
17897
|
+
};
|
|
17898
|
+
default:
|
|
17899
|
+
if (prefix === '' && globallyDeclaredFunctionLocalNames.includes(localName)) {
|
|
17900
|
+
// The function has been declared without a prefix and is called here without a prefix.
|
|
17901
|
+
// Just make this work. It is the developer-friendly way
|
|
17902
|
+
return {
|
|
17903
|
+
namespaceURI: 'http://www.w3.org/2005/xquery-local-functions',
|
|
17904
|
+
localName
|
|
17905
|
+
};
|
|
18210
17906
|
}
|
|
18211
|
-
if (
|
|
18212
|
-
|
|
17907
|
+
if (prefix === 'fn' || prefix === '') {
|
|
17908
|
+
return {
|
|
17909
|
+
namespaceURI: 'http://www.w3.org/2005/xpath-functions',
|
|
17910
|
+
localName
|
|
17911
|
+
};
|
|
18213
17912
|
}
|
|
18214
|
-
if (
|
|
18215
|
-
|
|
17913
|
+
if (prefix === 'local') {
|
|
17914
|
+
return {
|
|
17915
|
+
namespaceURI: 'http://www.w3.org/2005/xquery-local-functions',
|
|
17916
|
+
localName
|
|
17917
|
+
};
|
|
18216
17918
|
}
|
|
18217
|
-
|
|
18218
|
-
return found;
|
|
17919
|
+
return null;
|
|
18219
17920
|
}
|
|
17921
|
+
}
|
|
18220
17922
|
|
|
18221
|
-
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
|
|
18226
|
-
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
// We are passing a shadow root boundary
|
|
18234
|
-
descendant = descendant.host;
|
|
18235
|
-
} else {
|
|
18236
|
-
descendant = descendant.parentNode;
|
|
18237
|
-
}
|
|
18238
|
-
}
|
|
18239
|
-
return false;
|
|
17923
|
+
/**
|
|
17924
|
+
* Get the variables in scope of the form element. These are the values of the variables that
|
|
17925
|
+
* logically precede the formElement that declares the XPath
|
|
17926
|
+
*
|
|
17927
|
+
* @param {Node} formElement The element that declares the XPath
|
|
17928
|
+
*
|
|
17929
|
+
* @returns {Object} A key-value mapping of the variables
|
|
17930
|
+
*/
|
|
17931
|
+
function getVariablesInScope(formElement) {
|
|
17932
|
+
let closestActualFormElement = formElement;
|
|
17933
|
+
while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
|
|
17934
|
+
closestActualFormElement = closestActualFormElement.nodeType === Node.ATTRIBUTE_NODE ? closestActualFormElement.ownerElement : closestActualFormElement.parentNode;
|
|
18240
17935
|
}
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
|
|
18248
|
-
|
|
18249
|
-
static getClosest(querySelector, start) {
|
|
18250
|
-
while (start && !start.matches || !start.matches(querySelector)) {
|
|
18251
|
-
if (start.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
18252
|
-
// We are passing a shadow root boundary
|
|
18253
|
-
start = start.host;
|
|
18254
|
-
continue;
|
|
18255
|
-
}
|
|
18256
|
-
if (start.nodeType === Node.ATTRIBUTE_NODE) {
|
|
18257
|
-
// We are passing an attribute
|
|
18258
|
-
start = start.ownerElement;
|
|
17936
|
+
if (!closestActualFormElement) {
|
|
17937
|
+
return {};
|
|
17938
|
+
}
|
|
17939
|
+
const variables = {};
|
|
17940
|
+
if (closestActualFormElement.inScopeVariables) {
|
|
17941
|
+
for (const key of closestActualFormElement.inScopeVariables.keys()) {
|
|
17942
|
+
const varElementOrValue = closestActualFormElement.inScopeVariables.get(key);
|
|
17943
|
+
if (!varElementOrValue) {
|
|
18259
17944
|
continue;
|
|
18260
17945
|
}
|
|
18261
|
-
if (
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
start = start.parentNode;
|
|
18269
|
-
if (!start) {
|
|
18270
|
-
return null;
|
|
17946
|
+
if (varElementOrValue.nodeType) {
|
|
17947
|
+
// We are a var element, set the value to the value computed there
|
|
17948
|
+
variables[key] = varElementOrValue.value;
|
|
17949
|
+
// variables[key] = varElementOrValue.inScopeVariables.get(key);
|
|
17950
|
+
} else {
|
|
17951
|
+
// We are a direct value. This is used to leak in event variables
|
|
17952
|
+
variables[key] = varElementOrValue;
|
|
18271
17953
|
}
|
|
18272
17954
|
}
|
|
18273
|
-
return start;
|
|
18274
17955
|
}
|
|
17956
|
+
return variables;
|
|
17957
|
+
}
|
|
18275
17958
|
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
|
|
18280
|
-
|
|
18281
|
-
|
|
18282
|
-
|
|
18283
|
-
|
|
18284
|
-
|
|
18285
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
18289
|
-
|
|
18290
|
-
|
|
18291
|
-
|
|
18292
|
-
}
|
|
17959
|
+
/**
|
|
17960
|
+
* Evaluate an XPath to _any_ type. When possible, prefer to use any other function to ensure the
|
|
17961
|
+
* type of the output is more predictable.
|
|
17962
|
+
*
|
|
17963
|
+
* @param {string} xpath The XPath to run
|
|
17964
|
+
* @param {Node} contextNode The start of the XPath
|
|
17965
|
+
* @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
|
|
17966
|
+
* @param {Object} variables Any variables to pass to the XPath
|
|
17967
|
+
* @param {Object} options Any options to pass to the XPath
|
|
17968
|
+
*
|
|
17969
|
+
* @returns {any[]}
|
|
17970
|
+
*/
|
|
17971
|
+
/*
|
|
17972
|
+
export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options={}, domFacade = null) {
|
|
17973
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17974
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
18293
17975
|
|
|
18294
|
-
|
|
18295
|
-
|
|
18296
|
-
|
|
18297
|
-
|
|
18298
|
-
|
|
18299
|
-
|
|
18300
|
-
|
|
18301
|
-
|
|
18302
|
-
|
|
18303
|
-
|
|
17976
|
+
return fxEvaluateXPath(
|
|
17977
|
+
xpath,
|
|
17978
|
+
contextNode,
|
|
17979
|
+
domFacade,
|
|
17980
|
+
{...variablesInScope, ...variables},
|
|
17981
|
+
fxEvaluateXPath.ALL_RESULTS_TYPE,
|
|
17982
|
+
{
|
|
17983
|
+
debug: true,
|
|
17984
|
+
currentContext: {formElement, variables},
|
|
17985
|
+
moduleImports: {
|
|
17986
|
+
xf: XFORMS_NAMESPACE_URI,
|
|
17987
|
+
},
|
|
17988
|
+
functionNameResolver,
|
|
17989
|
+
namespaceResolver,
|
|
17990
|
+
language: options.language || evaluateXPath.XPATH_3_1
|
|
17991
|
+
},
|
|
17992
|
+
);
|
|
17993
|
+
}
|
|
17994
|
+
*/
|
|
17995
|
+
function evaluateXPath(xpath, contextNode, formElement, variables = {}, options = {}) {
|
|
17996
|
+
try {
|
|
17997
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17998
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
17999
|
+
const result = evaluateXPath$1(xpath, contextNode, null, {
|
|
18000
|
+
...variablesInScope,
|
|
18001
|
+
...variables
|
|
18002
|
+
}, evaluateXPath$1.ALL_RESULTS_TYPE, {
|
|
18003
|
+
xmlSerializer: new XMLSerializer(),
|
|
18004
|
+
debug: true,
|
|
18005
|
+
currentContext: {
|
|
18006
|
+
formElement,
|
|
18007
|
+
variables
|
|
18008
|
+
},
|
|
18009
|
+
moduleImports: {
|
|
18010
|
+
xf: XFORMS_NAMESPACE_URI
|
|
18011
|
+
},
|
|
18012
|
+
functionNameResolver,
|
|
18013
|
+
namespaceResolver,
|
|
18014
|
+
language: options.language || evaluateXPath$1.XPATH_3_1_LANGUAGE
|
|
18015
|
+
});
|
|
18016
|
+
// console.log('evaluateXPath',xpath, result);
|
|
18017
|
+
return result;
|
|
18018
|
+
} catch (e) {
|
|
18019
|
+
formElement.dispatchEvent(new CustomEvent('error', {
|
|
18020
|
+
composed: false,
|
|
18021
|
+
bubbles: true,
|
|
18022
|
+
detail: {
|
|
18023
|
+
origin: formElement,
|
|
18024
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
18025
|
+
expr: xpath,
|
|
18026
|
+
level: 'Error'
|
|
18027
|
+
}
|
|
18028
|
+
}));
|
|
18304
18029
|
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
|
|
18030
|
+
/*
|
|
18031
|
+
formElement.dispatchEvent(
|
|
18032
|
+
new CustomEvent('error', {
|
|
18033
|
+
composed: false,
|
|
18034
|
+
bubbles: true,
|
|
18035
|
+
cancelable:true,
|
|
18036
|
+
detail: {
|
|
18037
|
+
origin: formElement,
|
|
18038
|
+
message: `Expression '${xpath}' failed`,
|
|
18039
|
+
expr:xpath,
|
|
18040
|
+
level:'Error'},
|
|
18041
|
+
}),
|
|
18042
|
+
);
|
|
18043
|
+
*/
|
|
18044
|
+
// Return 'nothing' in hope the rest of the page can forgive this
|
|
18045
|
+
return [];
|
|
18310
18046
|
}
|
|
18311
|
-
|
|
18312
|
-
|
|
18313
|
-
|
|
18314
|
-
|
|
18315
|
-
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
18321
|
-
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
|
|
18325
|
-
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
18331
|
-
|
|
18332
|
-
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18047
|
+
}
|
|
18048
|
+
/**
|
|
18049
|
+
* Evaluate an XPath to the first Node
|
|
18050
|
+
*
|
|
18051
|
+
* @param {string} xpath The XPath to run
|
|
18052
|
+
* @param {Node} contextNode The start of the XPath
|
|
18053
|
+
* @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
|
|
18054
|
+
* @returns {Node} The first node found in the XPath
|
|
18055
|
+
*/
|
|
18056
|
+
function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
18057
|
+
try {
|
|
18058
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
18059
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
18060
|
+
const result = evaluateXPathToFirstNode$1(xpath, contextNode, null, variablesInScope, {
|
|
18061
|
+
defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
|
|
18062
|
+
moduleImports: {
|
|
18063
|
+
xf: XFORMS_NAMESPACE_URI
|
|
18064
|
+
},
|
|
18065
|
+
currentContext: {
|
|
18066
|
+
formElement
|
|
18067
|
+
},
|
|
18068
|
+
functionNameResolver,
|
|
18069
|
+
namespaceResolver,
|
|
18070
|
+
xmlSerializer: new XMLSerializer()
|
|
18071
|
+
});
|
|
18072
|
+
// console.log('evaluateXPathToFirstNode',xpath, result);
|
|
18073
|
+
return result;
|
|
18074
|
+
} catch (e) {
|
|
18075
|
+
formElement.dispatchEvent(new CustomEvent('error', {
|
|
18076
|
+
composed: false,
|
|
18077
|
+
bubbles: true,
|
|
18078
|
+
detail: {
|
|
18079
|
+
origin: formElement,
|
|
18080
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
18081
|
+
expr: xpath,
|
|
18082
|
+
level: 'Error'
|
|
18343
18083
|
}
|
|
18344
|
-
|
|
18345
|
-
}
|
|
18346
|
-
return null;
|
|
18347
|
-
}
|
|
18348
|
-
|
|
18349
|
-
/**
|
|
18350
|
-
* @param {HTMLElement} boundElement
|
|
18351
|
-
* @param {string} path
|
|
18352
|
-
* @returns {string}
|
|
18353
|
-
*/
|
|
18354
|
-
static resolveInstance(boundElement, path) {
|
|
18355
|
-
let instanceId = XPathUtil.getInstanceId(path, boundElement);
|
|
18356
|
-
if (!instanceId) {
|
|
18357
|
-
instanceId = XPathUtil.getInstanceId(boundElement.getAttribute('ref'), boundElement);
|
|
18358
|
-
}
|
|
18359
|
-
if (instanceId !== null) {
|
|
18360
|
-
return instanceId;
|
|
18361
|
-
}
|
|
18362
|
-
const parentBinding = XPathUtil.getParentBindingElement(boundElement);
|
|
18363
|
-
if (parentBinding) {
|
|
18364
|
-
return this.resolveInstance(parentBinding, path);
|
|
18365
|
-
}
|
|
18366
|
-
return 'default';
|
|
18084
|
+
}));
|
|
18367
18085
|
}
|
|
18086
|
+
return null;
|
|
18087
|
+
}
|
|
18368
18088
|
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
|
|
18374
|
-
|
|
18375
|
-
|
|
18376
|
-
|
|
18377
|
-
|
|
18378
|
-
|
|
18089
|
+
/**
|
|
18090
|
+
* Evaluate an XPath to all nodes
|
|
18091
|
+
*
|
|
18092
|
+
* @param {string} xpath The XPath to run
|
|
18093
|
+
* @param {Node} contextNode The start of the XPath
|
|
18094
|
+
* @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
|
|
18095
|
+
* @return {Node[]} All nodes
|
|
18096
|
+
*/
|
|
18097
|
+
function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
18098
|
+
try {
|
|
18099
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
18100
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
18101
|
+
const result = evaluateXPathToNodes$1(xpath, contextNode, null, variablesInScope, {
|
|
18102
|
+
currentContext: {
|
|
18103
|
+
formElement
|
|
18104
|
+
},
|
|
18105
|
+
functionNameResolver,
|
|
18106
|
+
moduleImports: {
|
|
18107
|
+
xf: XFORMS_NAMESPACE_URI
|
|
18108
|
+
},
|
|
18109
|
+
namespaceResolver,
|
|
18110
|
+
xmlSerializer: new XMLSerializer()
|
|
18111
|
+
});
|
|
18112
|
+
// console.log('evaluateXPathToNodes',xpath, result);
|
|
18113
|
+
return result;
|
|
18114
|
+
} catch (e) {
|
|
18115
|
+
formElement.dispatchEvent(new CustomEvent('error', {
|
|
18116
|
+
composed: false,
|
|
18117
|
+
bubbles: true,
|
|
18118
|
+
detail: {
|
|
18119
|
+
origin: formElement,
|
|
18120
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
18121
|
+
expr: xpath,
|
|
18122
|
+
level: 'Error'
|
|
18123
|
+
}
|
|
18124
|
+
}));
|
|
18379
18125
|
}
|
|
18380
|
-
|
|
18126
|
+
}
|
|
18381
18127
|
|
|
18382
|
-
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18386
|
-
|
|
18387
|
-
|
|
18388
|
-
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18128
|
+
/**
|
|
18129
|
+
* Evaluate an XPath to a boolean
|
|
18130
|
+
*
|
|
18131
|
+
* @param {string} xpath The XPath to run
|
|
18132
|
+
* @param {Node} contextNode The start of the XPath
|
|
18133
|
+
* @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
|
|
18134
|
+
* @return {boolean}
|
|
18135
|
+
*/
|
|
18136
|
+
function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
18137
|
+
try {
|
|
18138
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
18139
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
18140
|
+
return evaluateXPathToBoolean$1(xpath, contextNode, null, variablesInScope, {
|
|
18141
|
+
currentContext: {
|
|
18142
|
+
formElement
|
|
18143
|
+
},
|
|
18144
|
+
functionNameResolver,
|
|
18145
|
+
moduleImports: {
|
|
18146
|
+
xf: XFORMS_NAMESPACE_URI
|
|
18147
|
+
},
|
|
18148
|
+
namespaceResolver,
|
|
18149
|
+
xmlSerializer: new XMLSerializer()
|
|
18150
|
+
});
|
|
18151
|
+
} catch (e) {
|
|
18152
|
+
formElement.dispatchEvent(new CustomEvent('error', {
|
|
18153
|
+
composed: false,
|
|
18154
|
+
bubbles: true,
|
|
18155
|
+
detail: {
|
|
18156
|
+
origin: formElement,
|
|
18157
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
18158
|
+
expr: xpath,
|
|
18159
|
+
level: 'Error'
|
|
18160
|
+
}
|
|
18161
|
+
}));
|
|
18392
18162
|
}
|
|
18163
|
+
}
|
|
18393
18164
|
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
18398
|
-
|
|
18399
|
-
|
|
18400
|
-
|
|
18401
|
-
|
|
18402
|
-
|
|
18403
|
-
|
|
18404
|
-
|
|
18165
|
+
/**
|
|
18166
|
+
* Evaluate an XPath to a string
|
|
18167
|
+
*
|
|
18168
|
+
* @param {string} xpath The XPath to run
|
|
18169
|
+
* @param {Node} contextNode The start of the XPath
|
|
18170
|
+
* @param {Node} formElement The form element associated to the XPath
|
|
18171
|
+
* @param {Node} formElement The element where the XPath is defined: used for namespace resolving
|
|
18172
|
+
* @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
|
|
18173
|
+
* access. This is used to determine dependencies between bind elements.
|
|
18174
|
+
* @return {string}
|
|
18175
|
+
*/
|
|
18176
|
+
function evaluateXPathToString(xpath, contextNode, formElement, domFacade = null) {
|
|
18177
|
+
try {
|
|
18178
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
18179
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
18180
|
+
return evaluateXPathToString$1(xpath, contextNode, domFacade, variablesInScope, {
|
|
18181
|
+
currentContext: {
|
|
18182
|
+
formElement
|
|
18183
|
+
},
|
|
18184
|
+
functionNameResolver,
|
|
18185
|
+
moduleImports: {
|
|
18186
|
+
xf: XFORMS_NAMESPACE_URI
|
|
18187
|
+
},
|
|
18188
|
+
namespaceResolver,
|
|
18189
|
+
xmlSerializer: new XMLSerializer()
|
|
18190
|
+
});
|
|
18191
|
+
} catch (e) {
|
|
18192
|
+
formElement.dispatchEvent(new CustomEvent('error', {
|
|
18193
|
+
composed: false,
|
|
18194
|
+
bubbles: true,
|
|
18195
|
+
detail: {
|
|
18196
|
+
origin: formElement,
|
|
18197
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
18198
|
+
expr: xpath,
|
|
18199
|
+
level: 'Error'
|
|
18200
|
+
}
|
|
18201
|
+
}));
|
|
18405
18202
|
}
|
|
18203
|
+
}
|
|
18406
18204
|
|
|
18407
|
-
|
|
18408
|
-
|
|
18409
|
-
|
|
18410
|
-
|
|
18411
|
-
|
|
18412
|
-
|
|
18413
|
-
|
|
18205
|
+
/**
|
|
18206
|
+
* Evaluate an XPath to a set of strings
|
|
18207
|
+
*
|
|
18208
|
+
* @param {string} xpath The XPath to run
|
|
18209
|
+
* @param {Node} contextNode The start of the XPath
|
|
18210
|
+
* @param {Node} formElement The form element associated to the XPath
|
|
18211
|
+
* @param {Node} formElement The element where the XPath is defined: used for namespace resolving
|
|
18212
|
+
* @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
|
|
18213
|
+
* access. This is used to determine dependencies between bind elements.
|
|
18214
|
+
* @return {string[]}
|
|
18215
|
+
*/
|
|
18216
|
+
function evaluateXPathToStrings(xpath, contextNode, formElement, domFacade = null) {
|
|
18217
|
+
try {
|
|
18218
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
18219
|
+
return evaluateXPathToStrings$1(xpath, contextNode, domFacade, {}, {
|
|
18220
|
+
currentContext: {
|
|
18221
|
+
formElement
|
|
18222
|
+
},
|
|
18223
|
+
functionNameResolver,
|
|
18224
|
+
moduleImports: {
|
|
18225
|
+
xf: XFORMS_NAMESPACE_URI
|
|
18226
|
+
},
|
|
18227
|
+
namespaceResolver,
|
|
18228
|
+
xmlSerializer: new XMLSerializer()
|
|
18229
|
+
});
|
|
18230
|
+
} catch (e) {
|
|
18231
|
+
formElement.dispatchEvent(new CustomEvent('error', {
|
|
18232
|
+
composed: false,
|
|
18233
|
+
bubbles: true,
|
|
18234
|
+
detail: {
|
|
18235
|
+
origin: formElement,
|
|
18236
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
18237
|
+
expr: xpath,
|
|
18238
|
+
level: 'Error'
|
|
18239
|
+
}
|
|
18240
|
+
}));
|
|
18414
18241
|
}
|
|
18415
18242
|
}
|
|
18416
18243
|
|
|
18417
18244
|
/**
|
|
18418
|
-
*
|
|
18419
|
-
*
|
|
18420
|
-
* based on the work of https://github.com/jriecken/dependency-graph but working on ES6.
|
|
18421
|
-
*
|
|
18422
|
-
* Furthermore instead of the DepGraphCycleError a compute-exception event is dispatched.
|
|
18423
|
-
*
|
|
18245
|
+
* Evaluate an XPath to a number
|
|
18424
18246
|
*
|
|
18247
|
+
* @param {string} xpath The XPath to run
|
|
18248
|
+
* @param {Node} contextNode The start of the XPath
|
|
18249
|
+
* @param {Node} formElement The form element associated to the XPath
|
|
18250
|
+
* @param {Node} formElement The element where the XPath is defined: used for namespace resolving
|
|
18251
|
+
* @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
|
|
18252
|
+
* access. This is used to determine dependencies between bind elements.
|
|
18253
|
+
* @return {number}
|
|
18425
18254
|
*/
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
|
|
18432
|
-
|
|
18433
|
-
|
|
18434
|
-
|
|
18435
|
-
|
|
18436
|
-
|
|
18437
|
-
|
|
18438
|
-
|
|
18439
|
-
|
|
18255
|
+
function evaluateXPathToNumber(xpath, contextNode, formElement, domFacade = null) {
|
|
18256
|
+
try {
|
|
18257
|
+
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
18258
|
+
const variablesInScope = getVariablesInScope(formElement);
|
|
18259
|
+
return evaluateXPathToNumber$1(xpath, contextNode, domFacade, variablesInScope, {
|
|
18260
|
+
currentContext: {
|
|
18261
|
+
formElement
|
|
18262
|
+
},
|
|
18263
|
+
functionNameResolver,
|
|
18264
|
+
moduleImports: {
|
|
18265
|
+
xf: XFORMS_NAMESPACE_URI
|
|
18266
|
+
},
|
|
18267
|
+
namespaceResolver,
|
|
18268
|
+
xmlSerializer: new XMLSerializer()
|
|
18269
|
+
});
|
|
18270
|
+
} catch (e) {
|
|
18271
|
+
formElement.dispatchEvent(new CustomEvent('error', {
|
|
18272
|
+
composed: false,
|
|
18273
|
+
bubbles: true,
|
|
18274
|
+
detail: {
|
|
18275
|
+
origin: formElement,
|
|
18276
|
+
message: `Expression '${xpath}' failed: ${e}`,
|
|
18277
|
+
expr: xpath,
|
|
18278
|
+
level: 'Error'
|
|
18279
|
+
}
|
|
18280
|
+
}));
|
|
18440
18281
|
}
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
configurable: true
|
|
18282
|
+
}
|
|
18283
|
+
const contextFunction = (dynamicContext, string) => {
|
|
18284
|
+
const caller = dynamicContext.currentContext.formElement;
|
|
18285
|
+
let instance = null;
|
|
18286
|
+
if (string) {
|
|
18287
|
+
instance = resolveId(string, caller);
|
|
18288
|
+
} else {
|
|
18289
|
+
instance = XPathUtil.getParentBindingElement(caller);
|
|
18450
18290
|
}
|
|
18451
|
-
|
|
18452
|
-
|
|
18453
|
-
*/
|
|
18454
|
-
|
|
18455
|
-
/**
|
|
18456
|
-
* Helper for creating a Topological Sort using Depth-First-Search on a set of edges.
|
|
18457
|
-
*
|
|
18458
|
-
* Detects cycles and throws an Error if one is detected (unless the "circular"
|
|
18459
|
-
* parameter is "true" in which case it ignores them).
|
|
18460
|
-
*
|
|
18461
|
-
* @param edges The set of edges to DFS through
|
|
18462
|
-
* @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges)
|
|
18463
|
-
* @param result An array in which the results will be populated
|
|
18464
|
-
* @param circular A boolean to allow circular dependencies
|
|
18465
|
-
*/
|
|
18466
|
-
function createDFS(edges, leavesOnly, result, circular) {
|
|
18467
|
-
const visited = {};
|
|
18468
|
-
// eslint-disable-next-line func-names
|
|
18469
|
-
return function (start) {
|
|
18470
|
-
// console.log('start ', start);
|
|
18471
|
-
if (visited[start]) {
|
|
18472
|
-
return;
|
|
18473
|
-
}
|
|
18474
|
-
const inCurrentPath = {};
|
|
18475
|
-
const currentPath = [];
|
|
18476
|
-
const todo = []; // used as a stack
|
|
18477
|
-
todo.push({
|
|
18478
|
-
node: start,
|
|
18479
|
-
processed: false
|
|
18480
|
-
});
|
|
18481
|
-
while (todo.length > 0) {
|
|
18482
|
-
const current = todo[todo.length - 1]; // peek at the todo stack
|
|
18483
|
-
const {
|
|
18484
|
-
processed
|
|
18485
|
-
} = current;
|
|
18291
|
+
if (instance) {
|
|
18292
|
+
if (instance.nodeName === 'FX-REPEAT') {
|
|
18486
18293
|
const {
|
|
18487
|
-
|
|
18488
|
-
} =
|
|
18489
|
-
|
|
18490
|
-
|
|
18491
|
-
|
|
18492
|
-
|
|
18493
|
-
// eslint-disable-next-line no-continue
|
|
18494
|
-
continue;
|
|
18495
|
-
} else if (inCurrentPath[node]) {
|
|
18496
|
-
// It's not a DAG
|
|
18497
|
-
if (circular) {
|
|
18498
|
-
todo.pop();
|
|
18499
|
-
// If we're tolerating cycles, don't revisit the node
|
|
18500
|
-
// eslint-disable-next-line no-continue
|
|
18501
|
-
continue;
|
|
18502
|
-
}
|
|
18503
|
-
currentPath.push(node);
|
|
18504
|
-
window.dispatchEvent(new CustomEvent('compute-exception', {
|
|
18505
|
-
composed: false,
|
|
18506
|
-
bubbles: true,
|
|
18507
|
-
detail: {
|
|
18508
|
-
path: currentPath,
|
|
18509
|
-
message: 'cyclic graph'
|
|
18510
|
-
}
|
|
18511
|
-
}));
|
|
18512
|
-
// return;
|
|
18513
|
-
// console.log('‘circular path: ' + currentPath);
|
|
18514
|
-
// throw new DepGraphCycleError(currentPath);
|
|
18515
|
-
|
|
18516
|
-
// Stop all processing. This form is broken and we should not break the browser
|
|
18517
|
-
throw new Error(`Cyclic at ${currentPath}`);
|
|
18518
|
-
}
|
|
18519
|
-
inCurrentPath[node] = true;
|
|
18520
|
-
currentPath.push(node);
|
|
18521
|
-
const nodeEdges = edges[node];
|
|
18522
|
-
// (push edges onto the todo stack in reverse order to be order-compatible with the old DFS implementation)
|
|
18523
|
-
for (let i = nodeEdges.length - 1; i >= 0; i -= 1) {
|
|
18524
|
-
todo.push({
|
|
18525
|
-
node: nodeEdges[i],
|
|
18526
|
-
processed: false
|
|
18527
|
-
});
|
|
18528
|
-
}
|
|
18529
|
-
current.processed = true;
|
|
18530
|
-
} else {
|
|
18531
|
-
// Have visited edges (stack unrolling phase)
|
|
18532
|
-
todo.pop();
|
|
18533
|
-
currentPath.pop();
|
|
18534
|
-
inCurrentPath[node] = false;
|
|
18535
|
-
visited[node] = true;
|
|
18536
|
-
if (!leavesOnly || edges[node].length === 0) {
|
|
18537
|
-
result.push(node);
|
|
18294
|
+
nodeset
|
|
18295
|
+
} = instance;
|
|
18296
|
+
for (let parent = caller; parent; parent = parent.parentNode) {
|
|
18297
|
+
if (parent.parentNode === instance) {
|
|
18298
|
+
const offset = Array.from(parent.parentNode.children).indexOf(parent);
|
|
18299
|
+
return nodeset[offset];
|
|
18538
18300
|
}
|
|
18539
18301
|
}
|
|
18540
18302
|
}
|
|
18541
|
-
|
|
18542
|
-
}
|
|
18303
|
+
return instance.nodeset;
|
|
18304
|
+
}
|
|
18305
|
+
return caller.getInScopeContext();
|
|
18306
|
+
};
|
|
18307
|
+
|
|
18308
|
+
// todo: implement
|
|
18309
|
+
const currentFunction = (dynamicContext, string) => {
|
|
18310
|
+
dynamicContext.currentContext.formElement;
|
|
18311
|
+
return null;
|
|
18312
|
+
};
|
|
18313
|
+
const elementFunction = (dynamicContext, string) => {
|
|
18314
|
+
dynamicContext.currentContext.formElement;
|
|
18315
|
+
const newElement = document.createElement(string);
|
|
18316
|
+
return newElement;
|
|
18317
|
+
};
|
|
18543
18318
|
|
|
18544
18319
|
/**
|
|
18545
|
-
*
|
|
18320
|
+
* @param id as string
|
|
18321
|
+
* @return instance data for given id serialized to string.
|
|
18546
18322
|
*/
|
|
18323
|
+
registerCustomXPathFunction({
|
|
18324
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18325
|
+
localName: 'context'
|
|
18326
|
+
}, [], 'item()?', contextFunction);
|
|
18547
18327
|
|
|
18548
|
-
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18552
|
-
|
|
18553
|
-
|
|
18554
|
-
|
|
18555
|
-
|
|
18328
|
+
/**
|
|
18329
|
+
* @param id as string
|
|
18330
|
+
* @return instance data for given id serialized to string.
|
|
18331
|
+
*/
|
|
18332
|
+
registerCustomXPathFunction({
|
|
18333
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18334
|
+
localName: 'context'
|
|
18335
|
+
}, ['xs:string'], 'item()?', contextFunction);
|
|
18336
|
+
registerCustomXPathFunction({
|
|
18337
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18338
|
+
localName: 'current'
|
|
18339
|
+
}, ['xs:string'], 'item()?', currentFunction);
|
|
18340
|
+
registerCustomXPathFunction({
|
|
18341
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18342
|
+
localName: 'element'
|
|
18343
|
+
}, ['xs:string'], 'item()?', elementFunction);
|
|
18556
18344
|
|
|
18557
|
-
|
|
18558
|
-
|
|
18559
|
-
|
|
18560
|
-
|
|
18561
|
-
|
|
18562
|
-
|
|
18345
|
+
/**
|
|
18346
|
+
* @param id as string
|
|
18347
|
+
* @return instance data for given id serialized to string.
|
|
18348
|
+
*/
|
|
18349
|
+
registerCustomXPathFunction({
|
|
18350
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18351
|
+
localName: 'log'
|
|
18352
|
+
}, ['xs:string?'], 'xs:string?', (dynamicContext, string) => {
|
|
18353
|
+
const {
|
|
18354
|
+
formElement
|
|
18355
|
+
} = dynamicContext.currentContext;
|
|
18356
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
18357
|
+
if (instance) {
|
|
18358
|
+
if (instance.getAttribute('type') === 'json') {
|
|
18359
|
+
console.warn('log() does not work for JSON yet');
|
|
18360
|
+
// return JSON.stringify(instance.getDefaultContext());
|
|
18361
|
+
} else {
|
|
18362
|
+
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
18363
|
+
return prettifyXml(def);
|
|
18364
|
+
}
|
|
18365
|
+
}
|
|
18366
|
+
return null;
|
|
18367
|
+
});
|
|
18368
|
+
registerCustomXPathFunction({
|
|
18369
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18370
|
+
localName: 'fore-attr'
|
|
18371
|
+
}, ['xs:string?'], 'xs:string?', (dynamicContext, string) => {
|
|
18372
|
+
const {
|
|
18373
|
+
formElement
|
|
18374
|
+
} = dynamicContext.currentContext;
|
|
18375
|
+
let parent = formElement;
|
|
18376
|
+
if (formElement.nodeType === Node.TEXT_NODE) {
|
|
18377
|
+
parent = formElement.parentNode;
|
|
18378
|
+
}
|
|
18379
|
+
const foreElement = parent.closest('fx-fore');
|
|
18380
|
+
if (foreElement.hasAttribute(string)) {
|
|
18381
|
+
return foreElement.getAttribute(string);
|
|
18382
|
+
}
|
|
18383
|
+
return null;
|
|
18384
|
+
});
|
|
18385
|
+
registerCustomXPathFunction({
|
|
18386
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18387
|
+
localName: 'parse'
|
|
18388
|
+
}, ['xs:string?'], 'element()?', (_dynamicContext, string) => {
|
|
18389
|
+
const parser = new DOMParser();
|
|
18390
|
+
const out = parser.parseFromString(string, 'application/xml');
|
|
18391
|
+
console.log('parse', out);
|
|
18563
18392
|
|
|
18564
|
-
|
|
18565
|
-
|
|
18566
|
-
|
|
18567
|
-
|
|
18568
|
-
|
|
18569
|
-
|
|
18570
|
-
|
|
18571
|
-
|
|
18572
|
-
|
|
18573
|
-
|
|
18574
|
-
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18393
|
+
/*
|
|
18394
|
+
const {formElement} = dynamicContext.currentContext;
|
|
18395
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
18396
|
+
if (instance) {
|
|
18397
|
+
if (instance.getAttribute('type') === 'json') {
|
|
18398
|
+
console.warn('log() does not work for JSON yet');
|
|
18399
|
+
// return JSON.stringify(instance.getDefaultContext());
|
|
18400
|
+
} else {
|
|
18401
|
+
const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
18402
|
+
return Fore.prettifyXml(def);
|
|
18403
|
+
}
|
|
18404
|
+
}
|
|
18405
|
+
*/
|
|
18406
|
+
return out.firstElementChild;
|
|
18407
|
+
});
|
|
18408
|
+
function buildTree(tree, data) {
|
|
18409
|
+
if (!data) return;
|
|
18410
|
+
if (data.nodeType === Node.ELEMENT_NODE) {
|
|
18411
|
+
if (data.children) {
|
|
18412
|
+
const details = document.createElement('details');
|
|
18413
|
+
details.setAttribute('data-path', data.nodeName);
|
|
18414
|
+
const summary = document.createElement('summary');
|
|
18415
|
+
let display = ` <${data.nodeName}`;
|
|
18416
|
+
Array.from(data.attributes).forEach(attr => {
|
|
18417
|
+
display += ` ${attr.nodeName}="${attr.nodeValue}"`;
|
|
18418
|
+
});
|
|
18419
|
+
let contents;
|
|
18420
|
+
if (data.firstChild && data.firstChild.nodeType === Node.TEXT_NODE && data.firstChild.data.trim() !== '') {
|
|
18421
|
+
// console.log('whoooooooooopp');
|
|
18422
|
+
contents = data.firstChild.nodeValue;
|
|
18423
|
+
display += `>${contents}</${data.nodeName}>`;
|
|
18579
18424
|
} else {
|
|
18580
|
-
|
|
18425
|
+
display += '>';
|
|
18581
18426
|
}
|
|
18582
|
-
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
|
|
18594
|
-
|
|
18595
|
-
[this.incomingEdges, this.outgoingEdges].forEach(edgeList => {
|
|
18596
|
-
Object.keys(edgeList).forEach(key => {
|
|
18597
|
-
const idx = edgeList[key].indexOf(node);
|
|
18598
|
-
if (idx >= 0) {
|
|
18599
|
-
edgeList[key].splice(idx, 1);
|
|
18600
|
-
}
|
|
18601
|
-
}, this);
|
|
18427
|
+
summary.textContent = display;
|
|
18428
|
+
details.appendChild(summary);
|
|
18429
|
+
if (data.childElementCount !== 0) {
|
|
18430
|
+
details.setAttribute('open', 'open');
|
|
18431
|
+
} else {
|
|
18432
|
+
summary.setAttribute('style', 'list-style:none;');
|
|
18433
|
+
}
|
|
18434
|
+
tree.appendChild(details);
|
|
18435
|
+
Array.from(data.children).forEach(child => {
|
|
18436
|
+
// if(child.nodeType === Node.ELEMENT_NODE){
|
|
18437
|
+
// child.parentNode.appendChild(buildTree(child));
|
|
18438
|
+
buildTree(details, child);
|
|
18439
|
+
// }
|
|
18602
18440
|
});
|
|
18603
18441
|
}
|
|
18604
|
-
}
|
|
18605
|
-
|
|
18606
|
-
|
|
18607
|
-
|
|
18608
|
-
|
|
18609
|
-
|
|
18442
|
+
} /* else if(data.nodeType === Node.ATTRIBUTE_NODE){
|
|
18443
|
+
//create span for now
|
|
18444
|
+
// const span = document.createElement('span');
|
|
18445
|
+
// span.style.background = 'grey';
|
|
18446
|
+
// span.textContent = data.value;
|
|
18447
|
+
// tree.appendChild(span);
|
|
18448
|
+
tree.setAttribute(data.nodeName,data.value);
|
|
18449
|
+
}else {
|
|
18450
|
+
tree.textContent = data;
|
|
18451
|
+
} */
|
|
18610
18452
|
|
|
18611
|
-
|
|
18612
|
-
|
|
18613
|
-
|
|
18614
|
-
|
|
18615
|
-
|
|
18616
|
-
|
|
18617
|
-
|
|
18618
|
-
|
|
18453
|
+
// return tree;
|
|
18454
|
+
}
|
|
18455
|
+
|
|
18456
|
+
registerCustomXPathFunction({
|
|
18457
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18458
|
+
localName: 'logtree'
|
|
18459
|
+
}, ['xs:string?'], 'element()?', (dynamicContext, string) => {
|
|
18460
|
+
const {
|
|
18461
|
+
formElement
|
|
18462
|
+
} = dynamicContext.currentContext;
|
|
18463
|
+
const instance = resolveId(string, formElement, 'fx-instance');
|
|
18464
|
+
if (instance) {
|
|
18465
|
+
// const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
|
|
18466
|
+
// const def = JSON.stringify(instance.getDefaultContext());
|
|
18467
|
+
|
|
18468
|
+
const treeDiv = document.createElement('div');
|
|
18469
|
+
treeDiv.setAttribute('class', 'logtree');
|
|
18470
|
+
// const datatree = buildTree(tree,instance.getDefaultContext());
|
|
18471
|
+
// return tree.appendChild(datatree);
|
|
18472
|
+
// return buildTree(root,instance.getDefaultContext());;
|
|
18473
|
+
const form = dynamicContext.currentContext.formElement;
|
|
18474
|
+
const logtree = form.querySelector('.logtree');
|
|
18475
|
+
if (logtree) {
|
|
18476
|
+
logtree.parentNode.removeChild(logtree);
|
|
18619
18477
|
}
|
|
18620
|
-
|
|
18621
|
-
|
|
18622
|
-
|
|
18623
|
-
* Set the associated data for a given node name. If the node does not exist, this method will throw an error
|
|
18624
|
-
*/
|
|
18625
|
-
setNodeData(node, data) {
|
|
18626
|
-
if (this.hasNode(node)) {
|
|
18627
|
-
this.nodes[node] = data;
|
|
18628
|
-
} else {
|
|
18629
|
-
throw new Error(`Node does not exist: ${node}`);
|
|
18478
|
+
const tree = buildTree(treeDiv, instance.getDefaultContext());
|
|
18479
|
+
if (tree) {
|
|
18480
|
+
form.appendChild(tree);
|
|
18630
18481
|
}
|
|
18631
|
-
}
|
|
18482
|
+
}
|
|
18483
|
+
return null;
|
|
18484
|
+
});
|
|
18485
|
+
const instance = (dynamicContext, string) => {
|
|
18486
|
+
// Spec: https://www.w3.org/TR/xforms-xpath/#The_XForms_Function_Library#The_instance.28.29_Function
|
|
18487
|
+
// TODO: handle no string passed (null will be passed instead)
|
|
18488
|
+
|
|
18632
18489
|
/**
|
|
18633
|
-
*
|
|
18634
|
-
* an Error will be thrown.
|
|
18490
|
+
* @type {import('./fx-fore.js').FxFore}
|
|
18635
18491
|
*/
|
|
18636
|
-
|
|
18637
|
-
|
|
18638
|
-
|
|
18639
|
-
|
|
18640
|
-
|
|
18641
|
-
|
|
18642
|
-
|
|
18643
|
-
|
|
18644
|
-
|
|
18492
|
+
const formElement = evaluateXPathToFirstNode$1('ancestor-or-self::fx-fore[1]', dynamicContext.currentContext.formElement, null, null, {
|
|
18493
|
+
namespaceResolver: xhtmlNamespaceResolver
|
|
18494
|
+
});
|
|
18495
|
+
let lookup = null;
|
|
18496
|
+
if (string === null || string === 'default') {
|
|
18497
|
+
lookup = formElement.getModel().getDefaultInstance();
|
|
18498
|
+
} else {
|
|
18499
|
+
lookup = formElement.getModel().getInstance(string);
|
|
18500
|
+
if (!lookup) {
|
|
18501
|
+
document.querySelector('fx-fore').dispatchEvent(new CustomEvent('error', {
|
|
18502
|
+
composed: true,
|
|
18503
|
+
bubbles: true,
|
|
18504
|
+
detail: {
|
|
18505
|
+
origin: 'functions',
|
|
18506
|
+
message: `Instance not found '${string}'`,
|
|
18507
|
+
level: 'Error'
|
|
18508
|
+
}
|
|
18509
|
+
}));
|
|
18645
18510
|
}
|
|
18646
|
-
|
|
18647
|
-
|
|
18511
|
+
}
|
|
18512
|
+
const context = lookup.getDefaultContext();
|
|
18513
|
+
if (!context) {
|
|
18514
|
+
return null;
|
|
18515
|
+
}
|
|
18516
|
+
return context;
|
|
18517
|
+
};
|
|
18518
|
+
registerCustomXPathFunction({
|
|
18519
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18520
|
+
localName: 'index'
|
|
18521
|
+
}, ['xs:string?'], 'xs:integer?', (dynamicContext, string) => {
|
|
18522
|
+
const {
|
|
18523
|
+
formElement
|
|
18524
|
+
} = dynamicContext.currentContext;
|
|
18525
|
+
if (string === null) {
|
|
18526
|
+
return 1;
|
|
18527
|
+
}
|
|
18528
|
+
const repeat = resolveId(string, formElement, 'fx-repeat');
|
|
18529
|
+
|
|
18530
|
+
// const def = instance.getInstanceData();
|
|
18531
|
+
if (repeat) {
|
|
18532
|
+
return repeat.getAttribute('index');
|
|
18533
|
+
}
|
|
18534
|
+
return Number(1);
|
|
18535
|
+
});
|
|
18536
|
+
|
|
18537
|
+
// Note that this is not to spec. The spec enforces elements to be returned from the
|
|
18538
|
+
// instance. However, we allow instances to actually be JSON!
|
|
18539
|
+
registerCustomXPathFunction({
|
|
18540
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18541
|
+
localName: 'instance'
|
|
18542
|
+
}, [], 'item()?', domFacade => instance(domFacade, null));
|
|
18543
|
+
registerCustomXPathFunction({
|
|
18544
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18545
|
+
localName: 'instance'
|
|
18546
|
+
}, ['xs:string?'], 'item()?', instance);
|
|
18547
|
+
const jsonToXml = (_dynamicContext, json) => {
|
|
18548
|
+
const escapeXml = str => str.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]/g, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
18549
|
+
const convert = (obj, parent) => {
|
|
18550
|
+
const type = typeof obj;
|
|
18551
|
+
if (type === 'number') {
|
|
18552
|
+
parent.setAttribute('type', 'number');
|
|
18553
|
+
parent.textContent = obj.toString();
|
|
18554
|
+
} else if (type === 'boolean') {
|
|
18555
|
+
parent.setAttribute('type', 'boolean');
|
|
18556
|
+
parent.textContent = obj.toString();
|
|
18557
|
+
} else if (obj === null) {
|
|
18558
|
+
const node = document.createElement('_');
|
|
18559
|
+
node.setAttribute('type', 'null');
|
|
18560
|
+
parent.appendChild(node);
|
|
18561
|
+
} else if (type === 'string') {
|
|
18562
|
+
parent.textContent = escapeXml(obj);
|
|
18563
|
+
} else if (Array.isArray(obj)) {
|
|
18564
|
+
parent.setAttribute('type', 'array');
|
|
18565
|
+
obj.forEach(item => {
|
|
18566
|
+
const node = document.createElement('_');
|
|
18567
|
+
convert(item, node);
|
|
18568
|
+
node.textContent = item;
|
|
18569
|
+
parent.appendChild(node);
|
|
18570
|
+
});
|
|
18571
|
+
} else if (type === 'object') {
|
|
18572
|
+
parent.setAttribute('type', 'object');
|
|
18573
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
18574
|
+
if (value) {
|
|
18575
|
+
const childNode = document.createElement(key.replace(/[^a-zA-Z0-9_]/g, '_'));
|
|
18576
|
+
convert(value, childNode);
|
|
18577
|
+
parent.appendChild(childNode);
|
|
18578
|
+
}
|
|
18579
|
+
});
|
|
18648
18580
|
}
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18658
|
-
|
|
18659
|
-
|
|
18581
|
+
};
|
|
18582
|
+
const root = document.createElement('json');
|
|
18583
|
+
if (Array.isArray(json)) {
|
|
18584
|
+
root.setAttribute('type', 'array');
|
|
18585
|
+
} else {
|
|
18586
|
+
root.setAttribute('type', 'object');
|
|
18587
|
+
}
|
|
18588
|
+
convert(json, root);
|
|
18589
|
+
// return root.outerHTML;
|
|
18590
|
+
console.log('xml', root);
|
|
18591
|
+
return root;
|
|
18592
|
+
};
|
|
18593
|
+
registerCustomXPathFunction({
|
|
18594
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18595
|
+
localName: 'json2xml'
|
|
18596
|
+
}, ['item()?'], 'item()?', jsonToXml);
|
|
18597
|
+
const xmlToJson = (_dynamicContext, xml) => {
|
|
18598
|
+
const isElementNode = node => node.nodeType === Node.ELEMENT_NODE;
|
|
18599
|
+
const isTextNode = node => node.nodeType === Node.TEXT_NODE;
|
|
18600
|
+
const parseNode = node => {
|
|
18601
|
+
if (isElementNode(node)) {
|
|
18602
|
+
const obj = {};
|
|
18603
|
+
if (node.hasAttributes()) {
|
|
18604
|
+
obj.type = node.getAttribute('type');
|
|
18660
18605
|
}
|
|
18661
|
-
|
|
18662
|
-
|
|
18663
|
-
idx = this.incomingEdges[to].indexOf(from);
|
|
18664
|
-
if (idx >= 0) {
|
|
18665
|
-
this.incomingEdges[to].splice(idx, 1);
|
|
18606
|
+
if (node.childNodes.length === 1 && isTextNode(node.firstChild)) {
|
|
18607
|
+
return node.textContent;
|
|
18666
18608
|
}
|
|
18667
|
-
|
|
18668
|
-
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
|
|
18675
|
-
|
|
18676
|
-
|
|
18677
|
-
|
|
18678
|
-
result.nodes[n] = source.nodes[n];
|
|
18679
|
-
result.outgoingEdges[n] = source.outgoingEdges[n].slice(0);
|
|
18680
|
-
result.incomingEdges[n] = source.incomingEdges[n].slice(0);
|
|
18681
|
-
});
|
|
18682
|
-
return result;
|
|
18683
|
-
},
|
|
18684
|
-
/**
|
|
18685
|
-
* Get an array containing the direct dependencies of the specified node.
|
|
18686
|
-
*
|
|
18687
|
-
* Throws an Error if the specified node does not exist.
|
|
18688
|
-
*/
|
|
18689
|
-
directDependenciesOf(node) {
|
|
18690
|
-
if (this.hasNode(node)) {
|
|
18691
|
-
return this.outgoingEdges[node].slice(0);
|
|
18692
|
-
}
|
|
18693
|
-
throw new Error(`Node does not exist: ${node}`);
|
|
18694
|
-
},
|
|
18695
|
-
/**
|
|
18696
|
-
* Get an array containing the nodes that directly depend on the specified node.
|
|
18697
|
-
*
|
|
18698
|
-
* Throws an Error if the specified node does not exist.
|
|
18699
|
-
*/
|
|
18700
|
-
directDependantsOf(node) {
|
|
18701
|
-
if (this.hasNode(node)) {
|
|
18702
|
-
return this.incomingEdges[node].slice(0);
|
|
18703
|
-
}
|
|
18704
|
-
throw new Error(`Node does not exist: ${node}`);
|
|
18705
|
-
},
|
|
18706
|
-
/**
|
|
18707
|
-
* Get an array containing the nodes that the specified node depends on (transitively).
|
|
18708
|
-
*
|
|
18709
|
-
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
|
18710
|
-
*
|
|
18711
|
-
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned
|
|
18712
|
-
* in the array.
|
|
18713
|
-
*/
|
|
18714
|
-
dependenciesOf(node, leavesOnly) {
|
|
18715
|
-
if (this.hasNode(node)) {
|
|
18716
|
-
const result = [];
|
|
18717
|
-
const DFS = createDFS(this.outgoingEdges, leavesOnly, result, this.circular);
|
|
18718
|
-
DFS(node);
|
|
18719
|
-
const idx = result.indexOf(node);
|
|
18720
|
-
if (idx >= 0) {
|
|
18721
|
-
result.splice(idx, 1);
|
|
18609
|
+
for (const child of node.childNodes) {
|
|
18610
|
+
const childName = child.nodeName;
|
|
18611
|
+
const childValue = parseNode(child);
|
|
18612
|
+
if (obj[childName]) {
|
|
18613
|
+
if (!Array.isArray(obj[childName])) {
|
|
18614
|
+
obj[childName] = [obj[childName]];
|
|
18615
|
+
}
|
|
18616
|
+
obj[childName].push(childValue);
|
|
18617
|
+
} else {
|
|
18618
|
+
obj[childName] = childValue;
|
|
18619
|
+
}
|
|
18722
18620
|
}
|
|
18723
|
-
return
|
|
18621
|
+
return obj;
|
|
18724
18622
|
}
|
|
18725
|
-
|
|
18726
|
-
|
|
18727
|
-
/**
|
|
18728
|
-
* get an array containing the nodes that depend on the specified node (transitively).
|
|
18729
|
-
*
|
|
18730
|
-
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
|
18731
|
-
*
|
|
18732
|
-
* If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array.
|
|
18733
|
-
*/
|
|
18734
|
-
dependantsOf(node, leavesOnly) {
|
|
18735
|
-
if (this.hasNode(node)) {
|
|
18736
|
-
const result = [];
|
|
18737
|
-
const DFS = createDFS(this.incomingEdges, leavesOnly, result, this.circular);
|
|
18738
|
-
DFS(node);
|
|
18739
|
-
const idx = result.indexOf(node);
|
|
18740
|
-
if (idx >= 0) {
|
|
18741
|
-
result.splice(idx, 1);
|
|
18742
|
-
}
|
|
18743
|
-
return result;
|
|
18623
|
+
if (isTextNode(node)) {
|
|
18624
|
+
return node.textContent;
|
|
18744
18625
|
}
|
|
18745
|
-
|
|
18746
|
-
}
|
|
18747
|
-
|
|
18748
|
-
|
|
18749
|
-
|
|
18750
|
-
|
|
18751
|
-
|
|
18752
|
-
|
|
18753
|
-
|
|
18754
|
-
|
|
18755
|
-
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
18765
|
-
|
|
18766
|
-
|
|
18626
|
+
return undefined;
|
|
18627
|
+
};
|
|
18628
|
+
const parser = new DOMParser();
|
|
18629
|
+
const xmlDoc = parser.parseFromString(xml, 'application/xml');
|
|
18630
|
+
const root = xmlDoc.documentElement;
|
|
18631
|
+
return parseNode(root);
|
|
18632
|
+
};
|
|
18633
|
+
registerCustomXPathFunction({
|
|
18634
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18635
|
+
localName: 'xmltoJson'
|
|
18636
|
+
}, ['item()?'], 'item()?', xmlToJson);
|
|
18637
|
+
|
|
18638
|
+
/*
|
|
18639
|
+
// Example usage:
|
|
18640
|
+
const xml = '<json type="object"><given>Mark</given><family>Smith</family></json>';
|
|
18641
|
+
console.log(xmlToJson(xml));
|
|
18642
|
+
*/
|
|
18643
|
+
|
|
18644
|
+
registerCustomXPathFunction({
|
|
18645
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18646
|
+
localName: 'depends'
|
|
18647
|
+
}, ['node()*'], 'item()?', (_dynamicContext, nodes) =>
|
|
18648
|
+
// console.log('depends on : ', nodes[0]);
|
|
18649
|
+
nodes[0]);
|
|
18650
|
+
registerCustomXPathFunction({
|
|
18651
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18652
|
+
localName: 'event'
|
|
18653
|
+
}, ['xs:string?'], 'item()?', (dynamicContext, arg) => {
|
|
18654
|
+
if (!arg) return null;
|
|
18655
|
+
for (let ancestor = dynamicContext.currentContext.formElement; ancestor; ancestor = ancestor.parentNode) {
|
|
18656
|
+
if (!ancestor.currentEvent) {
|
|
18657
|
+
continue;
|
|
18767
18658
|
}
|
|
18768
18659
|
|
|
18769
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
CycleDFS(n);
|
|
18775
|
-
});
|
|
18660
|
+
// We have a current event. read the property either from detail, or from the event
|
|
18661
|
+
// itself.
|
|
18662
|
+
// Check detail for custom events! This is how that is passed along
|
|
18663
|
+
if (ancestor.currentEvent.detail && typeof ancestor.currentEvent.detail === 'object' && arg in ancestor.currentEvent.detail) {
|
|
18664
|
+
return ancestor.currentEvent.detail[arg];
|
|
18776
18665
|
}
|
|
18777
|
-
const DFS = createDFS(this.outgoingEdges, leavesOnly, result, this.circular);
|
|
18778
|
-
// Find all potential starting points (nodes with nothing depending on them) an
|
|
18779
|
-
// run a DFS starting at these points to get the order
|
|
18780
|
-
keys.filter(node => self.incomingEdges[node].length === 0).forEach(n => {
|
|
18781
|
-
DFS(n);
|
|
18782
|
-
});
|
|
18783
18666
|
|
|
18784
|
-
//
|
|
18785
|
-
|
|
18786
|
-
|
|
18787
|
-
if (this.circular) {
|
|
18788
|
-
keys.filter(node => result.indexOf(node) === -1).forEach(n => DFS(n));
|
|
18667
|
+
// arg might be `code`, so currentEvent.code should work
|
|
18668
|
+
if (arg.includes('.')) {
|
|
18669
|
+
return _propertyLookup(ancestor.currentEvent, arg);
|
|
18789
18670
|
}
|
|
18790
|
-
return
|
|
18671
|
+
return ancestor.currentEvent[arg] || null;
|
|
18672
|
+
}
|
|
18673
|
+
return null;
|
|
18674
|
+
});
|
|
18675
|
+
function _propertyLookup(obj, path) {
|
|
18676
|
+
const parts = path.split('.');
|
|
18677
|
+
if (parts.length == 1) {
|
|
18678
|
+
return obj[parts[0]];
|
|
18791
18679
|
}
|
|
18680
|
+
return _propertyLookup(obj[parts[0]], parts.slice(1).join('.'));
|
|
18681
|
+
}
|
|
18682
|
+
|
|
18683
|
+
// Implement the XForms standard functions here.
|
|
18684
|
+
registerXQueryModule(`
|
|
18685
|
+
module namespace xf="${XFORMS_NAMESPACE_URI}";
|
|
18686
|
+
|
|
18687
|
+
declare %public function xf:boolean-from-string($str as xs:string) as xs:boolean {
|
|
18688
|
+
lower-case($str) = "true" or $str = "1"
|
|
18689
|
+
};
|
|
18690
|
+
`);
|
|
18691
|
+
|
|
18692
|
+
// How to run XQUERY:
|
|
18693
|
+
/**
|
|
18694
|
+
registerXQueryModule(`
|
|
18695
|
+
module namespace my-custom-namespace = "my-custom-uri";
|
|
18696
|
+
(:~
|
|
18697
|
+
Insert attribute somewhere
|
|
18698
|
+
~:)
|
|
18699
|
+
declare %public %updating function my-custom-namespace:do-something ($ele as element()) as xs:boolean {
|
|
18700
|
+
if ($ele/@done) then false() else
|
|
18701
|
+
(insert node
|
|
18702
|
+
attribute done {"true"}
|
|
18703
|
+
into $ele, true())
|
|
18792
18704
|
};
|
|
18705
|
+
`)
|
|
18706
|
+
// At some point:
|
|
18707
|
+
const contextNode = null;
|
|
18708
|
+
const pendingUpdatesAndXdmValue = evaluateUpdatingExpressionSync('ns:do-something(.)', contextNode, null, null, {moduleImports: {'ns': 'my-custom-uri'}})
|
|
18793
18709
|
|
|
18794
|
-
//
|
|
18795
|
-
|
|
18796
|
-
|
|
18710
|
+
console.log(pendingUpdatesAndXdmValue.xdmValue); // this is true or false, see function
|
|
18711
|
+
|
|
18712
|
+
executePendingUpdateList(pendingUpdatesAndXdmValue.pendingUpdateList, null, null, null);
|
|
18713
|
+
*/
|
|
18714
|
+
|
|
18715
|
+
/**
|
|
18716
|
+
* @param input as string
|
|
18717
|
+
* @return {string}
|
|
18718
|
+
*/
|
|
18719
|
+
registerCustomXPathFunction({
|
|
18720
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18721
|
+
localName: 'base64encode'
|
|
18722
|
+
}, ['xs:string?'], 'xs:string?', (_dynamicContext, string) => btoa(string));
|
|
18723
|
+
registerCustomXPathFunction({
|
|
18724
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18725
|
+
localName: 'local-date'
|
|
18726
|
+
}, [], 'xs:string?', (_dynamicContext, _string) => new Date().toLocaleDateString());
|
|
18727
|
+
registerCustomXPathFunction({
|
|
18728
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18729
|
+
localName: 'local-dateTime'
|
|
18730
|
+
}, [], 'xs:string?', (_dynamicContext, _string) => new Date().toLocaleString());
|
|
18731
|
+
registerCustomXPathFunction({
|
|
18732
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18733
|
+
localName: 'uri'
|
|
18734
|
+
}, [], 'xs:string?', (_dynamicContext, _string) => window.location.href);
|
|
18735
|
+
registerCustomXPathFunction({
|
|
18736
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18737
|
+
localName: 'uri-fragment'
|
|
18738
|
+
}, [], 'xs:string?', (_dynamicContext, _arg) => window.location.hash);
|
|
18739
|
+
registerCustomXPathFunction({
|
|
18740
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18741
|
+
localName: 'uri-host'
|
|
18742
|
+
}, [], 'xs:string?', (_dynamicContext, _arg) => window.location.host);
|
|
18743
|
+
registerCustomXPathFunction({
|
|
18744
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18745
|
+
localName: 'uri-query'
|
|
18746
|
+
}, [], 'xs:string?', (_dynamicContext, _arg) => window.location.search);
|
|
18747
|
+
registerCustomXPathFunction({
|
|
18748
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18749
|
+
localName: 'uri-relpath'
|
|
18750
|
+
}, [], 'xs:string?', (_dynamicContext, _arg) => {
|
|
18751
|
+
const path = new URL(window.location.href).pathname;
|
|
18752
|
+
return path.substring(0, path.lastIndexOf('/') + 1);
|
|
18753
|
+
});
|
|
18754
|
+
registerCustomXPathFunction({
|
|
18755
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18756
|
+
localName: 'uri-path'
|
|
18757
|
+
}, [], 'xs:string?', (_dynamicContext, _arg) => new URL(window.location.href).pathname);
|
|
18758
|
+
registerCustomXPathFunction({
|
|
18759
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18760
|
+
localName: 'uri-port'
|
|
18761
|
+
}, [], 'xs:string?', (_dynamicContext, _arg) => window.location.port);
|
|
18762
|
+
registerCustomXPathFunction({
|
|
18763
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18764
|
+
localName: 'uri-param'
|
|
18765
|
+
}, ['xs:string?'], 'xs:string?', (_dynamicContext, arg) => {
|
|
18766
|
+
if (!arg) return null;
|
|
18767
|
+
const {
|
|
18768
|
+
search
|
|
18769
|
+
} = window.location;
|
|
18770
|
+
const urlparams = new URLSearchParams(search);
|
|
18771
|
+
const param = urlparams.get(arg);
|
|
18772
|
+
return param || '';
|
|
18773
|
+
});
|
|
18774
|
+
registerCustomXPathFunction({
|
|
18775
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18776
|
+
localName: 'uri-scheme'
|
|
18777
|
+
}, [], 'xs:string?', (_dynamicContext, _arg) => new URL(window.location.href).protocol);
|
|
18778
|
+
registerCustomXPathFunction({
|
|
18779
|
+
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
18780
|
+
localName: 'uri-scheme-specific-part'
|
|
18781
|
+
}, [], 'xs:string?', (_dynamicContext, _arg) => {
|
|
18782
|
+
const uri = window.location.href;
|
|
18783
|
+
return uri.substring(uri.indexOf(':') + 1, uri.length);
|
|
18784
|
+
});
|
|
18785
|
+
|
|
18786
|
+
/**
|
|
18787
|
+
* @param {Node} node
|
|
18788
|
+
* @returns string
|
|
18789
|
+
*/
|
|
18790
|
+
/*
|
|
18791
|
+
export static getDocPath(node) {
|
|
18792
|
+
const path = fx.evaluateXPathToString('path()', node);
|
|
18793
|
+
// Path is like `$default/x[1]/y[1]`
|
|
18794
|
+
const shortened = XPathUtil.shortenPath(path);
|
|
18795
|
+
return shortened.startsWith('/') ? `${shortened}` : `/${shortened}`;
|
|
18796
|
+
}
|
|
18797
|
+
*/
|
|
18797
18798
|
|
|
18798
18799
|
/**
|
|
18799
18800
|
* @param {Node} node
|
|
@@ -19669,10 +19670,10 @@ class FxInstance extends HTMLElement {
|
|
|
19669
19670
|
// Note: use the getter here: it might provide us with stubbed data if anything async is racing,
|
|
19670
19671
|
// such as an @src attribute
|
|
19671
19672
|
const instanceData = this.getInstanceData();
|
|
19672
|
-
if (this.type === 'xml') {
|
|
19673
|
+
if (this.type === 'xml' || this.type === 'html') {
|
|
19673
19674
|
return instanceData.firstElementChild;
|
|
19674
19675
|
}
|
|
19675
|
-
return instanceData;
|
|
19676
|
+
return this.instanceData;
|
|
19676
19677
|
}
|
|
19677
19678
|
|
|
19678
19679
|
/**
|
|
@@ -19902,13 +19903,13 @@ class ModelItem {
|
|
|
19902
19903
|
set value(newVal) {
|
|
19903
19904
|
if (!this.node) return;
|
|
19904
19905
|
const oldVal = this.value;
|
|
19905
|
-
if (newVal?.nodeType === Node.DOCUMENT_NODE) {
|
|
19906
|
+
if (newVal?.nodeType && newVal.nodeType === Node.DOCUMENT_NODE) {
|
|
19906
19907
|
this.node.replaceWith(newVal.firstElementChild);
|
|
19907
|
-
this.node
|
|
19908
|
-
} else if (newVal?.nodeType === Node.ELEMENT_NODE) {
|
|
19908
|
+
// this.node.appendChild(newVal.firstElementChild);
|
|
19909
|
+
} else if (newVal?.nodeType && newVal.nodeType === Node.ELEMENT_NODE) {
|
|
19909
19910
|
this.node.replaceWith(newVal);
|
|
19910
|
-
this.node
|
|
19911
|
-
} else if (this.node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
19911
|
+
// this.node.appendChild(newVal);
|
|
19912
|
+
} else if (newVal?.nodeType && this.node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
19912
19913
|
this.node.nodeValue = newVal;
|
|
19913
19914
|
} else {
|
|
19914
19915
|
this.node.textContent = newVal;
|
|
@@ -21210,7 +21211,7 @@ class FxBind extends ForeElementMixin {
|
|
|
21210
21211
|
}
|
|
21211
21212
|
|
|
21212
21213
|
// ✅ only the repeat item gets the _<opNum> suffix; children do not.
|
|
21213
|
-
const basePath =
|
|
21214
|
+
const basePath = getPath(node, instanceId);
|
|
21214
21215
|
const path = opNum ? `${basePath}_${opNum}` : basePath;
|
|
21215
21216
|
|
|
21216
21217
|
// const path = XPathUtil.getPath(node, instanceId);
|
|
@@ -23722,6 +23723,24 @@ const dirtyStates = {
|
|
|
23722
23723
|
* @ts-check
|
|
23723
23724
|
*/
|
|
23724
23725
|
class FxFore extends HTMLElement {
|
|
23726
|
+
// Records init gate events that have already happened for a given target (document/window/element).
|
|
23727
|
+
// This prevents “missed gate” situations when an fx-fore is replaced (e.g. via src loading)
|
|
23728
|
+
// after the init event already fired.
|
|
23729
|
+
static _hasSeenInitEvent(target, eventName) {
|
|
23730
|
+
const set = FxFore._initEventState.get(target);
|
|
23731
|
+
return !!(set && set.has(eventName));
|
|
23732
|
+
}
|
|
23733
|
+
static _markInitEventSeen(target, eventName) {
|
|
23734
|
+
let set = FxFore._initEventState.get(target);
|
|
23735
|
+
if (!set) {
|
|
23736
|
+
set = new Set();
|
|
23737
|
+
FxFore._initEventState.set(target, set);
|
|
23738
|
+
}
|
|
23739
|
+
set.add(eventName);
|
|
23740
|
+
}
|
|
23741
|
+
static get observedAttributes() {
|
|
23742
|
+
return ['src', 'selector'];
|
|
23743
|
+
}
|
|
23725
23744
|
static get properties() {
|
|
23726
23745
|
return {
|
|
23727
23746
|
/**
|
|
@@ -23791,14 +23810,12 @@ class FxFore extends HTMLElement {
|
|
|
23791
23810
|
// avoid double init
|
|
23792
23811
|
if (this.inited) return;
|
|
23793
23812
|
|
|
23794
|
-
// 2) Wait for
|
|
23795
|
-
|
|
23796
|
-
|
|
23797
|
-
|
|
23798
|
-
|
|
23799
|
-
|
|
23800
|
-
return;
|
|
23801
|
-
}
|
|
23813
|
+
// 2) Wait for init gates (init-on / init-on-target / wait-for)
|
|
23814
|
+
try {
|
|
23815
|
+
await this._waitForInitGates();
|
|
23816
|
+
} catch (e) {
|
|
23817
|
+
console.warn('init gating failed', e);
|
|
23818
|
+
return;
|
|
23802
23819
|
}
|
|
23803
23820
|
|
|
23804
23821
|
// 3) Bail if we got disconnected/replaced while waiting
|
|
@@ -23807,10 +23824,21 @@ class FxFore extends HTMLElement {
|
|
|
23807
23824
|
this.ignoredNodes = Array.from(this.querySelectorAll(this.ignoreExpressions));
|
|
23808
23825
|
}
|
|
23809
23826
|
|
|
23827
|
+
// 4) Safely read assigned content
|
|
23828
|
+
const getAssignedElements = () => {
|
|
23829
|
+
if (typeof slotEl.assignedElements === 'function') {
|
|
23830
|
+
return slotEl.assignedElements({
|
|
23831
|
+
flatten: true
|
|
23832
|
+
});
|
|
23833
|
+
}
|
|
23834
|
+
// Fallback for odd engines/polyfills
|
|
23835
|
+
return (slotEl.assignedNodes({
|
|
23836
|
+
flatten: true
|
|
23837
|
+
}) || []).filter(n => n.nodeType === Node.ELEMENT_NODE);
|
|
23838
|
+
};
|
|
23839
|
+
|
|
23810
23840
|
// SAFE: slotEl is the actual event source, not a fresh query
|
|
23811
|
-
const children =
|
|
23812
|
-
flatten: true
|
|
23813
|
-
});
|
|
23841
|
+
const children = getAssignedElements();
|
|
23814
23842
|
let modelElement = children.find(modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL');
|
|
23815
23843
|
if (!modelElement) {
|
|
23816
23844
|
const generatedModel = document.createElement('fx-model');
|
|
@@ -23831,19 +23859,27 @@ class FxFore extends HTMLElement {
|
|
|
23831
23859
|
registerVariables(child);
|
|
23832
23860
|
}
|
|
23833
23861
|
})(this);
|
|
23862
|
+
|
|
23863
|
+
// Ensure all function libraries are loaded/registered before model construction,
|
|
23864
|
+
// so binds/calculate/XPath evaluations can safely call them.
|
|
23865
|
+
const libs = Array.from(this.querySelectorAll('fx-functionlib'));
|
|
23866
|
+
await Promise.all(libs.map(l => l.readyPromise || Promise.resolve()));
|
|
23834
23867
|
await modelElement.modelConstruct();
|
|
23835
23868
|
this._handleModelConstructDone();
|
|
23836
23869
|
}
|
|
23837
23870
|
this._createRepeatsFromAttributes();
|
|
23838
23871
|
this.inited = true;
|
|
23839
23872
|
};
|
|
23840
|
-
this.version = 'Version: 2.
|
|
23873
|
+
this.version = 'Version: 2.9.0 - built on January 21, 2026 15:42:06';
|
|
23841
23874
|
|
|
23842
23875
|
/**
|
|
23843
23876
|
* @type {import('./fx-model.js').FxModel}
|
|
23844
23877
|
*/
|
|
23845
23878
|
this.model = null;
|
|
23846
23879
|
this.inited = false;
|
|
23880
|
+
this._initGatesPromise = null;
|
|
23881
|
+
this._warnedWaitForDeprecation = false;
|
|
23882
|
+
this._srcLoadPromise = null;
|
|
23847
23883
|
// this.addEventListener('model-construct-done', this._handleModelConstructDone);
|
|
23848
23884
|
// todo: refactoring - these should rather go into connectedcallback
|
|
23849
23885
|
this.addEventListener('message', this._displayMessage);
|
|
@@ -23985,94 +24021,217 @@ class FxFore extends HTMLElement {
|
|
|
23985
24021
|
}
|
|
23986
24022
|
|
|
23987
24023
|
/**
|
|
23988
|
-
*
|
|
23989
|
-
*
|
|
24024
|
+
* Parse a list of target specs.
|
|
24025
|
+
*
|
|
24026
|
+
* We accept both comma- and whitespace-separated lists (for backward compatibility with `wait-for`).
|
|
24027
|
+
* Each token can be:
|
|
24028
|
+
* - "self" (default)
|
|
24029
|
+
* - "closest" (closest fx-fore)
|
|
24030
|
+
* - "document"
|
|
24031
|
+
* - "window"
|
|
24032
|
+
* - a CSS selector (no whitespace)
|
|
23990
24033
|
*/
|
|
23991
|
-
|
|
23992
|
-
const raw = this.getAttribute('wait-for');
|
|
24034
|
+
_parseTargetList(raw) {
|
|
23993
24035
|
if (!raw) return [];
|
|
23994
|
-
|
|
24036
|
+
return raw.split(/[\s,]+/).map(s => s.trim()).filter(Boolean);
|
|
24037
|
+
}
|
|
24038
|
+
_findBySelector(sel) {
|
|
23995
24039
|
const roots = [this.getRootNode?.() ?? document, document];
|
|
23996
|
-
const
|
|
23997
|
-
|
|
23998
|
-
|
|
23999
|
-
|
|
24000
|
-
el = this.closest('fx-fore');
|
|
24001
|
-
} else {
|
|
24002
|
-
for (const r of roots) {
|
|
24003
|
-
if (r && 'querySelector' in r) {
|
|
24004
|
-
el = r.querySelector(sel);
|
|
24005
|
-
if (el) break;
|
|
24006
|
-
}
|
|
24007
|
-
}
|
|
24040
|
+
for (const r of roots) {
|
|
24041
|
+
if (r && 'querySelector' in r) {
|
|
24042
|
+
const el = r.querySelector(sel);
|
|
24043
|
+
if (el) return el;
|
|
24008
24044
|
}
|
|
24009
|
-
if (el) out.push(el);
|
|
24010
24045
|
}
|
|
24011
|
-
return
|
|
24046
|
+
return null;
|
|
24047
|
+
}
|
|
24048
|
+
_isReadyTarget(el) {
|
|
24049
|
+
return !!(el && (el.ready === true || el.classList && el.classList.contains('fx-ready') || typeof el.hasAttribute === 'function' && el.hasAttribute('ready')));
|
|
24012
24050
|
}
|
|
24013
24051
|
|
|
24014
24052
|
/**
|
|
24015
|
-
*
|
|
24016
|
-
*
|
|
24053
|
+
* Collect all init gates derived from attributes.
|
|
24054
|
+
*
|
|
24055
|
+
* - `wait-for` (DEPRECATED) becomes: init-on="ready" + init-on-target=<list>
|
|
24056
|
+
* - `init-on` / `init-on-target` define a generic event gate
|
|
24017
24057
|
*/
|
|
24018
|
-
|
|
24019
|
-
const
|
|
24020
|
-
|
|
24021
|
-
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
24027
|
-
|
|
24028
|
-
|
|
24058
|
+
_collectInitGates() {
|
|
24059
|
+
const gates = [];
|
|
24060
|
+
const waitForRaw = this.getAttribute('wait-for');
|
|
24061
|
+
if (waitForRaw) {
|
|
24062
|
+
if (!this._warnedWaitForDeprecation) {
|
|
24063
|
+
console.warn('[fx-fore] The "wait-for" attribute is deprecated. Use init-on="ready" init-on-target="..." instead.');
|
|
24064
|
+
this._warnedWaitForDeprecation = true;
|
|
24065
|
+
}
|
|
24066
|
+
const deps = this._parseTargetList(waitForRaw);
|
|
24067
|
+
for (const dep of deps) {
|
|
24068
|
+
gates.push({
|
|
24069
|
+
event: 'ready',
|
|
24070
|
+
targetSpec: dep
|
|
24071
|
+
});
|
|
24029
24072
|
}
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
const
|
|
24033
|
-
|
|
24034
|
-
|
|
24035
|
-
|
|
24073
|
+
}
|
|
24074
|
+
const initOn = this.getAttribute('init-on');
|
|
24075
|
+
const initOnTargetRaw = this.getAttribute('init-on-target');
|
|
24076
|
+
if (initOn || initOnTargetRaw) {
|
|
24077
|
+
const eventName = initOn || 'ready';
|
|
24078
|
+
const targets = initOnTargetRaw ? this._parseTargetList(initOnTargetRaw) : ['self'];
|
|
24079
|
+
for (const t of targets) {
|
|
24080
|
+
gates.push({
|
|
24081
|
+
event: eventName,
|
|
24082
|
+
targetSpec: t
|
|
24083
|
+
});
|
|
24036
24084
|
}
|
|
24037
|
-
|
|
24038
|
-
|
|
24039
|
-
|
|
24040
|
-
|
|
24041
|
-
|
|
24042
|
-
|
|
24085
|
+
}
|
|
24086
|
+
return gates;
|
|
24087
|
+
}
|
|
24088
|
+
_waitForEvent(target, eventName, isSatisfiedFn = null) {
|
|
24089
|
+
// If a caller provides an explicit satisfaction check, honor it first.
|
|
24090
|
+
if (typeof isSatisfiedFn === 'function' && isSatisfiedFn(target)) {
|
|
24091
|
+
FxFore._markInitEventSeen(target, eventName);
|
|
24092
|
+
return Promise.resolve();
|
|
24093
|
+
}
|
|
24043
24094
|
|
|
24044
|
-
|
|
24045
|
-
|
|
24046
|
-
|
|
24047
|
-
|
|
24048
|
-
|
|
24049
|
-
|
|
24050
|
-
|
|
24051
|
-
|
|
24052
|
-
|
|
24053
|
-
|
|
24054
|
-
|
|
24055
|
-
|
|
24056
|
-
|
|
24095
|
+
// Sticky gate: if this event already happened on this target, don't wait again.
|
|
24096
|
+
if (FxFore._hasSeenInitEvent(target, eventName)) {
|
|
24097
|
+
return Promise.resolve();
|
|
24098
|
+
}
|
|
24099
|
+
return new Promise(resolve => {
|
|
24100
|
+
const ac = new AbortController();
|
|
24101
|
+
const on = () => {
|
|
24102
|
+
FxFore._markInitEventSeen(target, eventName);
|
|
24103
|
+
ac.abort();
|
|
24104
|
+
resolve();
|
|
24105
|
+
};
|
|
24106
|
+
target.addEventListener(eventName, on, {
|
|
24107
|
+
once: true,
|
|
24108
|
+
signal: ac.signal
|
|
24109
|
+
});
|
|
24110
|
+
});
|
|
24111
|
+
}
|
|
24112
|
+
_waitForMatchingEvent(eventName, matchesEventFn, recheckFn = null) {
|
|
24113
|
+
if (typeof recheckFn === 'function' && recheckFn()) {
|
|
24114
|
+
return Promise.resolve();
|
|
24115
|
+
}
|
|
24116
|
+
return new Promise(resolve => {
|
|
24117
|
+
const root = document;
|
|
24118
|
+
const cleanupAll = () => {
|
|
24119
|
+
root.removeEventListener(eventName, onEvent, true);
|
|
24120
|
+
if (mo) mo.disconnect();
|
|
24057
24121
|
};
|
|
24058
|
-
const
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
// also re-check on DOM changes in case a ready fore is inserted without firing (paranoia)
|
|
24063
|
-
const mo = new MutationObserver(() => {
|
|
24064
|
-
if (isReadyNow(sel)) {
|
|
24065
|
-
mo.disconnect();
|
|
24066
|
-
cleanup();
|
|
24122
|
+
const onEvent = ev => {
|
|
24123
|
+
if (matchesEventFn(ev)) {
|
|
24124
|
+
cleanupAll();
|
|
24067
24125
|
resolve();
|
|
24068
24126
|
}
|
|
24069
|
-
}
|
|
24070
|
-
|
|
24071
|
-
|
|
24072
|
-
|
|
24073
|
-
|
|
24127
|
+
};
|
|
24128
|
+
root.addEventListener(eventName, onEvent, true);
|
|
24129
|
+
|
|
24130
|
+
// Only used for `ready` (or any other gate that provides a recheck function)
|
|
24131
|
+
let mo = null;
|
|
24132
|
+
if (typeof recheckFn === 'function') {
|
|
24133
|
+
mo = new MutationObserver(() => {
|
|
24134
|
+
if (recheckFn()) {
|
|
24135
|
+
cleanupAll();
|
|
24136
|
+
resolve();
|
|
24137
|
+
}
|
|
24138
|
+
});
|
|
24139
|
+
mo.observe(document.documentElement, {
|
|
24140
|
+
childList: true,
|
|
24141
|
+
subtree: true
|
|
24142
|
+
});
|
|
24143
|
+
}
|
|
24074
24144
|
});
|
|
24075
|
-
|
|
24145
|
+
}
|
|
24146
|
+
_waitForInitGate({
|
|
24147
|
+
event,
|
|
24148
|
+
targetSpec
|
|
24149
|
+
}) {
|
|
24150
|
+
// Direct targets
|
|
24151
|
+
if (targetSpec === 'self') {
|
|
24152
|
+
const satisfied = event === 'ready' ? t => this._isReadyTarget(t) : null;
|
|
24153
|
+
return this._waitForEvent(this, event, satisfied);
|
|
24154
|
+
}
|
|
24155
|
+
if (targetSpec === 'document') {
|
|
24156
|
+
return this._waitForEvent(document, event);
|
|
24157
|
+
}
|
|
24158
|
+
if (targetSpec === 'window') {
|
|
24159
|
+
return this._waitForEvent(window, event);
|
|
24160
|
+
}
|
|
24161
|
+
|
|
24162
|
+
// Special: closest fx-fore
|
|
24163
|
+
if (targetSpec === 'closest') {
|
|
24164
|
+
const recheckFn = event === 'ready' ? () => this._isReadyTarget(this.closest('fx-fore')) : null;
|
|
24165
|
+
const matchesFn = ev => {
|
|
24166
|
+
const t = ev.target;
|
|
24167
|
+
return t?.tagName === 'FX-FORE' && t.contains(this);
|
|
24168
|
+
};
|
|
24169
|
+
return this._waitForMatchingEvent(event, matchesFn, recheckFn);
|
|
24170
|
+
}
|
|
24171
|
+
|
|
24172
|
+
// Selector targets
|
|
24173
|
+
const selector = targetSpec;
|
|
24174
|
+
const recheckFn = event === 'ready' ? () => this._isReadyTarget(this._findBySelector(selector)) : null;
|
|
24175
|
+
if (typeof recheckFn === 'function' && recheckFn()) {
|
|
24176
|
+
return Promise.resolve();
|
|
24177
|
+
}
|
|
24178
|
+
const matchesFn = ev => {
|
|
24179
|
+
// Prefer composedPath() so events coming from inside shadow DOM still match
|
|
24180
|
+
const path = typeof ev.composedPath === 'function' ? ev.composedPath() : [];
|
|
24181
|
+
for (const n of path) {
|
|
24182
|
+
if (n && n.matches && n.matches(selector)) return true;
|
|
24183
|
+
}
|
|
24184
|
+
const t = ev.target;
|
|
24185
|
+
return !!(t && t.closest && t.closest(selector));
|
|
24186
|
+
};
|
|
24187
|
+
return this._waitForMatchingEvent(event, matchesFn, recheckFn);
|
|
24188
|
+
}
|
|
24189
|
+
|
|
24190
|
+
/**
|
|
24191
|
+
* Wait until all configured init gates are satisfied.
|
|
24192
|
+
* This is the single consolidation point for init gating.
|
|
24193
|
+
*/
|
|
24194
|
+
_waitForInitGates() {
|
|
24195
|
+
if (this._initGatesPromise) return this._initGatesPromise;
|
|
24196
|
+
const gates = this._collectInitGates();
|
|
24197
|
+
if (!gates.length) {
|
|
24198
|
+
this._initGatesPromise = Promise.resolve();
|
|
24199
|
+
return this._initGatesPromise;
|
|
24200
|
+
}
|
|
24201
|
+
this._initGatesPromise = Promise.all(gates.map(g => this._waitForInitGate(g))).then(() => undefined);
|
|
24202
|
+
return this._initGatesPromise;
|
|
24203
|
+
}
|
|
24204
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
24205
|
+
if (oldValue === newValue) return;
|
|
24206
|
+
if (name === 'src') {
|
|
24207
|
+
this.src = newValue;
|
|
24208
|
+
if (!newValue) {
|
|
24209
|
+
// Reset so a later src assignment can load again
|
|
24210
|
+
this._srcLoadPromise = null;
|
|
24211
|
+
return;
|
|
24212
|
+
}
|
|
24213
|
+
if (this.isConnected) {
|
|
24214
|
+
this._maybeLoadFromSrc();
|
|
24215
|
+
}
|
|
24216
|
+
return;
|
|
24217
|
+
}
|
|
24218
|
+
if (name === 'selector') {
|
|
24219
|
+
// Selector changes should affect a pending src-load
|
|
24220
|
+
if (this.isConnected && this.src && !this._srcLoadPromise) {
|
|
24221
|
+
this._maybeLoadFromSrc();
|
|
24222
|
+
}
|
|
24223
|
+
}
|
|
24224
|
+
}
|
|
24225
|
+
_maybeLoadFromSrc() {
|
|
24226
|
+
if (!this.src) return null;
|
|
24227
|
+
if (this._srcLoadPromise) return this._srcLoadPromise;
|
|
24228
|
+
this._srcLoadPromise = (async () => {
|
|
24229
|
+
await this._waitForInitGates();
|
|
24230
|
+
if (!this.isConnected) return;
|
|
24231
|
+
const selector = this.getAttribute('selector') || 'fx-fore';
|
|
24232
|
+
await Fore.loadForeFromSrc(this, this.src, selector);
|
|
24233
|
+
})();
|
|
24234
|
+
return this._srcLoadPromise;
|
|
24076
24235
|
}
|
|
24077
24236
|
connectedCallback() {
|
|
24078
24237
|
const modelElement = Array.from(this.children).find(modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL');
|
|
@@ -24110,7 +24269,7 @@ class FxFore extends HTMLElement {
|
|
|
24110
24269
|
}
|
|
24111
24270
|
this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
|
|
24112
24271
|
if (this.src) {
|
|
24113
|
-
this.
|
|
24272
|
+
this._maybeLoadFromSrc();
|
|
24114
24273
|
return;
|
|
24115
24274
|
}
|
|
24116
24275
|
this._injectDevtools();
|
|
@@ -24186,15 +24345,7 @@ class FxFore extends HTMLElement {
|
|
|
24186
24345
|
* @private
|
|
24187
24346
|
*/
|
|
24188
24347
|
async _loadFromSrc() {
|
|
24189
|
-
|
|
24190
|
-
if (this.hasAttribute('wait-for')) {
|
|
24191
|
-
await this._whenDependenciesReady();
|
|
24192
|
-
}
|
|
24193
|
-
if (this.hasAttribute('selector')) {
|
|
24194
|
-
await Fore.loadForeFromSrc(this, this.src, this.getAttribute('selector'));
|
|
24195
|
-
} else {
|
|
24196
|
-
await Fore.loadForeFromSrc(this, this.src, 'fx-fore');
|
|
24197
|
-
}
|
|
24348
|
+
return this._maybeLoadFromSrc();
|
|
24198
24349
|
}
|
|
24199
24350
|
|
|
24200
24351
|
/**
|
|
@@ -24489,7 +24640,7 @@ class FxFore extends HTMLElement {
|
|
|
24489
24640
|
}
|
|
24490
24641
|
// Templates are special: they use the namespace configuration from the place where they are
|
|
24491
24642
|
// being defined
|
|
24492
|
-
const instanceId = XPathUtil.getInstanceId(naked);
|
|
24643
|
+
const instanceId = XPathUtil.getInstanceId(naked, node);
|
|
24493
24644
|
|
|
24494
24645
|
// If there is an instance referred
|
|
24495
24646
|
const inst = instanceId ? this.getModel().getInstance(instanceId) : this.getModel().getDefaultInstance();
|
|
@@ -24969,11 +25120,13 @@ class FxFore extends HTMLElement {
|
|
|
24969
25120
|
let newElement;
|
|
24970
25121
|
if (ref.includes('/')) {
|
|
24971
25122
|
// multi-step ref expressions
|
|
24972
|
-
|
|
25123
|
+
const namespaceResolver = createNamespaceResolver(ref, this);
|
|
25124
|
+
newElement = XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
|
|
24973
25125
|
// console.log('new subtree', newElement);
|
|
24974
25126
|
return newElement;
|
|
24975
25127
|
} else {
|
|
24976
|
-
|
|
25128
|
+
const namespaceResolver = createNamespaceResolver(ref, this);
|
|
25129
|
+
return XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
|
|
24977
25130
|
}
|
|
24978
25131
|
}
|
|
24979
25132
|
_handleDragStart(event) {
|
|
@@ -25132,6 +25285,7 @@ class FxFore extends HTMLElement {
|
|
|
25132
25285
|
}
|
|
25133
25286
|
FxFore.outermostHandler = null;
|
|
25134
25287
|
FxFore.draggedItem = null;
|
|
25288
|
+
FxFore._initEventState = new WeakMap();
|
|
25135
25289
|
if (!customElements.get('fx-fore')) {
|
|
25136
25290
|
customElements.define('fx-fore', FxFore);
|
|
25137
25291
|
}
|
|
@@ -25582,8 +25736,27 @@ class FxSubmission extends ForeElementMixin {
|
|
|
25582
25736
|
_handleResponse(data, resolvedUrl, contentType) {
|
|
25583
25737
|
// console.log('_handleResponse ', data);
|
|
25584
25738
|
|
|
25585
|
-
|
|
25739
|
+
this._getTargetInstance();
|
|
25586
25740
|
if (this.replace === 'instance') {
|
|
25741
|
+
const targetInstance = this._getTargetInstance();
|
|
25742
|
+
|
|
25743
|
+
// ### contentType handling
|
|
25744
|
+
|
|
25745
|
+
if (contentType.includes('html')) {
|
|
25746
|
+
let effectiveData;
|
|
25747
|
+
if (data.nodeType) {
|
|
25748
|
+
effectiveData = data;
|
|
25749
|
+
}
|
|
25750
|
+
// ## try parsing
|
|
25751
|
+
try {
|
|
25752
|
+
effectiveData = new DOMParser().parseFromString(data, 'text/html');
|
|
25753
|
+
} catch {
|
|
25754
|
+
Fore.dispatch(this, 'error', {
|
|
25755
|
+
message: 'could not parse data as HTML'
|
|
25756
|
+
});
|
|
25757
|
+
}
|
|
25758
|
+
targetInstance.instanceData = effectiveData;
|
|
25759
|
+
}
|
|
25587
25760
|
if (targetInstance) {
|
|
25588
25761
|
if (this.targetref) {
|
|
25589
25762
|
const [theTarget] = evaluateXPath(this.targetref, targetInstance.instanceData.firstElementChild, this);
|
|
@@ -26070,6 +26243,9 @@ class AbstractControl extends UIElement {
|
|
|
26070
26243
|
}
|
|
26071
26244
|
}
|
|
26072
26245
|
_toggleValid(valid) {
|
|
26246
|
+
// Used by required handling (and potentially other callers).
|
|
26247
|
+
// It must also fire validity events and sync aria-invalid.
|
|
26248
|
+
const wasInvalid = this.hasAttribute('invalid');
|
|
26073
26249
|
if (valid) {
|
|
26074
26250
|
this.removeAttribute('invalid');
|
|
26075
26251
|
this.setAttribute('valid', '');
|
|
@@ -26077,6 +26253,23 @@ class AbstractControl extends UIElement {
|
|
|
26077
26253
|
this.removeAttribute('valid');
|
|
26078
26254
|
this.setAttribute('invalid', '');
|
|
26079
26255
|
}
|
|
26256
|
+
this._syncAriaInvalid();
|
|
26257
|
+
const isInvalid = this.hasAttribute('invalid');
|
|
26258
|
+
// Only dispatch when the state actually changed
|
|
26259
|
+
if (wasInvalid !== isInvalid) {
|
|
26260
|
+
this._dispatchEvent(isInvalid ? 'invalid' : 'valid');
|
|
26261
|
+
}
|
|
26262
|
+
}
|
|
26263
|
+
_syncAriaInvalid() {
|
|
26264
|
+
// Keep widget aria-invalid in sync with the *control* state, regardless of
|
|
26265
|
+
// whether invalidity comes from constraint, required emptiness, etc.
|
|
26266
|
+
try {
|
|
26267
|
+
const w = this.getWidget?.() || this.widget;
|
|
26268
|
+
if (!w) return;
|
|
26269
|
+
w.setAttribute('aria-invalid', this.hasAttribute('invalid') ? 'true' : 'false');
|
|
26270
|
+
} catch (e) {
|
|
26271
|
+
// ignore: widget might not exist yet
|
|
26272
|
+
}
|
|
26080
26273
|
}
|
|
26081
26274
|
handleReadonly() {
|
|
26082
26275
|
// console.log('mip readonly', this.modelItem.isReadonly);
|
|
@@ -26104,8 +26297,8 @@ class AbstractControl extends UIElement {
|
|
|
26104
26297
|
// if (alert) alert.style.display = 'none';
|
|
26105
26298
|
this._dispatchEvent('valid');
|
|
26106
26299
|
this.setAttribute('valid', '');
|
|
26107
|
-
this.getWidget().setAttribute('aria-invalid', 'false');
|
|
26108
26300
|
this.removeAttribute('invalid');
|
|
26301
|
+
this.getWidget().setAttribute('aria-invalid', 'false');
|
|
26109
26302
|
} else {
|
|
26110
26303
|
this.setAttribute('invalid', '');
|
|
26111
26304
|
this.getWidget().setAttribute('aria-invalid', 'true');
|
|
@@ -26137,33 +26330,40 @@ class AbstractControl extends UIElement {
|
|
|
26137
26330
|
this._dispatchEvent('invalid');
|
|
26138
26331
|
}
|
|
26139
26332
|
}
|
|
26333
|
+
|
|
26334
|
+
// Ensure aria-invalid matches the current control state even if
|
|
26335
|
+
// we didn't enter the state-change branch above.
|
|
26336
|
+
this._syncAriaInvalid();
|
|
26140
26337
|
}
|
|
26141
26338
|
handleRelevant() {
|
|
26142
|
-
//
|
|
26339
|
+
// IMPORTANT: don't clear relevant/nonrelevant BEFORE comparing states.
|
|
26340
|
+
// Otherwise isEnabled() (based on attributes) always reads as "enabled"
|
|
26341
|
+
// and we can never detect a transition back to relevant.
|
|
26143
26342
|
const item = this.modelItem.node;
|
|
26144
|
-
this.
|
|
26145
|
-
|
|
26343
|
+
const wasEnabled = this.isEnabled();
|
|
26344
|
+
|
|
26345
|
+
// Determine new enabled state
|
|
26346
|
+
let newEnabled = !!this.modelItem.relevant;
|
|
26347
|
+
|
|
26348
|
+
// If a nodeset resolves to an empty array, treat the control as nonrelevant
|
|
26146
26349
|
if (Array.isArray(item) && item.length === 0) {
|
|
26147
|
-
|
|
26350
|
+
newEnabled = false;
|
|
26351
|
+
}
|
|
26352
|
+
|
|
26353
|
+
// Apply attributes
|
|
26354
|
+
if (newEnabled) {
|
|
26355
|
+
this.setAttribute('relevant', '');
|
|
26356
|
+
this.removeAttribute('nonrelevant');
|
|
26357
|
+
} else {
|
|
26148
26358
|
this.setAttribute('nonrelevant', '');
|
|
26149
|
-
|
|
26150
|
-
return;
|
|
26359
|
+
this.removeAttribute('relevant');
|
|
26151
26360
|
}
|
|
26152
|
-
|
|
26153
|
-
|
|
26154
|
-
|
|
26155
|
-
|
|
26156
|
-
this.setAttribute('relevant', '');
|
|
26157
|
-
// this.style.display = this.display;
|
|
26158
|
-
} else {
|
|
26159
|
-
this._dispatchEvent('nonrelevant');
|
|
26160
|
-
// this._fadeOut(this);
|
|
26161
|
-
this.setAttribute('nonrelevant', '');
|
|
26162
|
-
// this.style.display = 'none';
|
|
26163
|
-
}
|
|
26361
|
+
|
|
26362
|
+
// Dispatch only on actual change
|
|
26363
|
+
if (wasEnabled !== newEnabled) {
|
|
26364
|
+
this._dispatchEvent(newEnabled ? 'relevant' : 'nonrelevant');
|
|
26164
26365
|
}
|
|
26165
26366
|
}
|
|
26166
|
-
|
|
26167
26367
|
isRequired() {
|
|
26168
26368
|
return this.hasAttribute('required');
|
|
26169
26369
|
}
|
|
@@ -30212,7 +30412,7 @@ class FxActionLog extends HTMLElement {
|
|
|
30212
30412
|
*/
|
|
30213
30413
|
_logDetails(e) {
|
|
30214
30414
|
const eventType = e.type;
|
|
30215
|
-
const path =
|
|
30415
|
+
const path = getPath(e.target, '');
|
|
30216
30416
|
// console.log('>>>> _logDetails', path);
|
|
30217
30417
|
const cut = path.substring(path.indexOf('/fx-fore'), path.length);
|
|
30218
30418
|
const xpath = `/${cut}`;
|
|
@@ -36035,6 +36235,14 @@ function registerFunction(functionObject, formElement) {
|
|
|
36035
36235
|
switch (type) {
|
|
36036
36236
|
case 'text/javascript':
|
|
36037
36237
|
{
|
|
36238
|
+
// NEW: if a real JS function is provided (module libs), register it directly.
|
|
36239
|
+
if (typeof functionObject.implementation === 'function') {
|
|
36240
|
+
const impl = functionObject.implementation;
|
|
36241
|
+
registerCustomXPathFunction(functionIdentifier, paramParts.map(paramPart => paramPart.variableType), returnType || 'item()*', (domFacade, ...values) => impl.apply(formElement.getInScopeContext(), [...values, formElement.getOwnerForm()]));
|
|
36242
|
+
break;
|
|
36243
|
+
}
|
|
36244
|
+
|
|
36245
|
+
// Existing behavior: compile from functionBody
|
|
36038
36246
|
// eslint-disable-next-line no-new-func
|
|
36039
36247
|
const fun = new Function('_domFacade', ...paramParts.map(paramPart => paramPart.variableName), 'form', functionObject.functionBody);
|
|
36040
36248
|
registerCustomXPathFunction(functionIdentifier, paramParts.map(paramPart => paramPart.variableType), returnType || 'item()*', (...args) => fun.apply(formElement.getInScopeContext(), [...args, formElement.getOwnerForm()]));
|
|
@@ -36085,10 +36293,35 @@ if (!customElements.get('fx-function')) {
|
|
|
36085
36293
|
customElements.define('fx-function', FxFunction);
|
|
36086
36294
|
}
|
|
36087
36295
|
|
|
36088
|
-
|
|
36089
|
-
|
|
36090
|
-
|
|
36091
|
-
|
|
36296
|
+
const LOCAL_FUNCTIONS_NS = 'http://www.w3.org/2005/xquery-local-functions';
|
|
36297
|
+
|
|
36298
|
+
// Global per-page cache to prevent registering the same library multiple times.
|
|
36299
|
+
// Keyed by resolved URL + prefix + mode.
|
|
36300
|
+
const _functionLibLoadCache = new Map();
|
|
36301
|
+
function looksLikeModuleSrc(src) {
|
|
36302
|
+
return /\.m?js($|\?)/i.test(src);
|
|
36303
|
+
}
|
|
36304
|
+
function applyPrefixToSignature(signature, prefix) {
|
|
36305
|
+
if (!signature || !prefix) return signature;
|
|
36306
|
+
const s = signature.trim();
|
|
36307
|
+
const paren = s.indexOf('(');
|
|
36308
|
+
if (paren < 0) return s;
|
|
36309
|
+
const namePart = s.slice(0, paren).trim();
|
|
36310
|
+
const rest = s.slice(paren);
|
|
36311
|
+
const localName = namePart.includes(':') ? namePart.split(':').pop().trim() : namePart;
|
|
36312
|
+
if (!localName) return s;
|
|
36313
|
+
return `${prefix}:${localName}${rest}`;
|
|
36314
|
+
}
|
|
36315
|
+
function normalizeModuleExportToList(mod, src) {
|
|
36316
|
+
const lib = mod.functions ?? mod.fxFunctions;
|
|
36317
|
+
if (!lib) {
|
|
36318
|
+
console.error(`fx-functionlib: Module ${src} must export a named \`functions\` (or \`fxFunctions\`).`);
|
|
36319
|
+
return [];
|
|
36320
|
+
}
|
|
36321
|
+
if (Array.isArray(lib)) return lib;
|
|
36322
|
+
if (typeof lib === 'object') return Object.values(lib);
|
|
36323
|
+
return [];
|
|
36324
|
+
}
|
|
36092
36325
|
class FxFunctionlib extends ForeElementMixin {
|
|
36093
36326
|
constructor() {
|
|
36094
36327
|
super();
|
|
@@ -36106,27 +36339,98 @@ class FxFunctionlib extends ForeElementMixin {
|
|
|
36106
36339
|
async connectedCallback() {
|
|
36107
36340
|
this.style.display = 'none';
|
|
36108
36341
|
const src = this.getAttribute('src');
|
|
36109
|
-
|
|
36342
|
+
if (!src) {
|
|
36343
|
+
console.error('fx-functionlib: Missing required @src.');
|
|
36344
|
+
this._resolveLoading(undefined);
|
|
36345
|
+
return;
|
|
36346
|
+
}
|
|
36347
|
+
const prefix = (this.getAttribute('prefix') || '').trim();
|
|
36348
|
+
const typeAttr = (this.getAttribute('type') || '').trim().toLowerCase();
|
|
36349
|
+
const isModule = typeAttr === 'module' || !typeAttr && looksLikeModuleSrc(src);
|
|
36350
|
+
const resolvedUrl = new URL(src, this.baseURI).href;
|
|
36351
|
+
if (prefix) this._ensurePrefixDeclared(prefix);
|
|
36352
|
+
const mode = isModule ? 'module' : 'html';
|
|
36353
|
+
const cacheKey = `${mode}|${resolvedUrl}|${prefix}`;
|
|
36354
|
+
const existing = _functionLibLoadCache.get(cacheKey);
|
|
36355
|
+
if (existing) {
|
|
36356
|
+
try {
|
|
36357
|
+
await existing;
|
|
36358
|
+
} finally {
|
|
36359
|
+
this._resolveLoading(undefined);
|
|
36360
|
+
}
|
|
36361
|
+
return;
|
|
36362
|
+
}
|
|
36363
|
+
const loadPromise = (async () => {
|
|
36364
|
+
if (isModule) {
|
|
36365
|
+
await this._loadModuleLibrary(resolvedUrl, src, prefix);
|
|
36366
|
+
} else {
|
|
36367
|
+
await this._loadHtmlLibrary(resolvedUrl, src, prefix);
|
|
36368
|
+
}
|
|
36369
|
+
})();
|
|
36370
|
+
_functionLibLoadCache.set(cacheKey, loadPromise);
|
|
36371
|
+
try {
|
|
36372
|
+
await loadPromise;
|
|
36373
|
+
} catch (e) {
|
|
36374
|
+
_functionLibLoadCache.delete(cacheKey);
|
|
36375
|
+
console.error(`fx-functionlib: Loading function library at ${src} failed.`, e);
|
|
36376
|
+
} finally {
|
|
36377
|
+
this._resolveLoading(undefined);
|
|
36378
|
+
}
|
|
36379
|
+
}
|
|
36380
|
+
_ensurePrefixDeclared(prefix) {
|
|
36381
|
+
const ownerForm = typeof this.getOwnerForm === 'function' && this.getOwnerForm() || this.closest('fx-fore');
|
|
36382
|
+
if (!ownerForm) return;
|
|
36383
|
+
const attrName = `xmlns:${prefix}`;
|
|
36384
|
+
if (!ownerForm.getAttribute(attrName)) {
|
|
36385
|
+
ownerForm.setAttribute(attrName, LOCAL_FUNCTIONS_NS);
|
|
36386
|
+
}
|
|
36387
|
+
}
|
|
36388
|
+
_register(functionObject, prefix) {
|
|
36389
|
+
if (!functionObject || typeof functionObject.signature !== 'string') return;
|
|
36390
|
+
|
|
36391
|
+
// If prefix is given: register ONLY the prefixed signature (no unprefixed alias).
|
|
36392
|
+
const sig = prefix ? applyPrefixToSignature(functionObject.signature, prefix) : functionObject.signature;
|
|
36393
|
+
registerFunction({
|
|
36394
|
+
...functionObject,
|
|
36395
|
+
signature: sig
|
|
36396
|
+
}, this);
|
|
36397
|
+
}
|
|
36398
|
+
async _loadModuleLibrary(resolvedUrl, src, prefix) {
|
|
36399
|
+
const mod = await import( /* @vite-ignore */resolvedUrl);
|
|
36400
|
+
const items = normalizeModuleExportToList(mod, src);
|
|
36401
|
+
for (const item of items) {
|
|
36402
|
+
if (typeof item === 'function') {
|
|
36403
|
+
const {
|
|
36404
|
+
signature
|
|
36405
|
+
} = item;
|
|
36406
|
+
if (typeof signature !== 'string' || !signature.trim()) continue;
|
|
36407
|
+
this._register({
|
|
36408
|
+
type: 'text/javascript',
|
|
36409
|
+
signature: signature.trim(),
|
|
36410
|
+
implementation: item
|
|
36411
|
+
}, prefix);
|
|
36412
|
+
} else if (item && typeof item === 'object' && typeof item.signature === 'string') {
|
|
36413
|
+
this._register(item, prefix);
|
|
36414
|
+
}
|
|
36415
|
+
}
|
|
36416
|
+
}
|
|
36417
|
+
async _loadHtmlLibrary(resolvedUrl, src, prefix) {
|
|
36418
|
+
const result = await fetch(resolvedUrl);
|
|
36110
36419
|
if (!result.ok) {
|
|
36111
36420
|
console.error(`Loading function library at ${src} failed.`);
|
|
36421
|
+
return;
|
|
36112
36422
|
}
|
|
36113
36423
|
const body = await result.text();
|
|
36114
36424
|
const document = new DOMParser().parseFromString(body, 'text/html');
|
|
36115
|
-
|
|
36116
|
-
/**
|
|
36117
|
-
* @type {HTMLElement[]}
|
|
36118
|
-
*/
|
|
36119
36425
|
const functions = Array.from(document.querySelectorAll('fx-function'));
|
|
36120
|
-
// TODO: also recurse into new function libraries here?
|
|
36121
36426
|
for (const func of functions) {
|
|
36122
36427
|
const functionObject = {
|
|
36123
36428
|
type: func.getAttribute('type'),
|
|
36124
36429
|
signature: func.getAttribute('signature'),
|
|
36125
36430
|
functionBody: func.innerText
|
|
36126
36431
|
};
|
|
36127
|
-
|
|
36432
|
+
this._register(functionObject, prefix);
|
|
36128
36433
|
}
|
|
36129
|
-
this._resolveLoading(undefined);
|
|
36130
36434
|
}
|
|
36131
36435
|
}
|
|
36132
36436
|
if (!customElements.get('fx-functionlib')) {
|