@jinntec/fore 2.7.2 → 2.8.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 +2137 -1942
- package/dist/fore.js +10837 -14535
- package/package.json +1 -1
- package/resources/fore.css +3 -0
- package/src/fx-bind.js +1 -1
- package/src/fx-fore.js +298 -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.8.0 - December 19, 2025 12:59:03 */
|
|
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
|
+
}
|
|
16974
16956
|
}
|
|
16957
|
+
if (!rootNode) {
|
|
16958
|
+
throw new Error('Invalid XPath; no root element could be created.');
|
|
16959
|
+
}
|
|
16960
|
+
return rootNode;
|
|
16975
16961
|
}
|
|
16976
|
-
// We found no target objects in common repeats. The id is unresolvable
|
|
16977
|
-
return null;
|
|
16978
|
-
}
|
|
16979
|
-
|
|
16980
|
-
// Make namespace resolving use the `instance` element that is related to here
|
|
16981
|
-
const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
|
|
16982
|
-
const instanceReferencesByQuery = new Map();
|
|
16983
|
-
function findInstanceReferences(xpathQuery) {
|
|
16984
|
-
if (!xpathQuery.includes('instance')) {
|
|
16985
|
-
// No call to the instance function anyway: short-circuit and prevent AST processing
|
|
16986
|
-
return [];
|
|
16987
|
-
}
|
|
16988
|
-
if (instanceReferencesByQuery.has(xpathQuery)) {
|
|
16989
|
-
return instanceReferencesByQuery.get(xpathQuery);
|
|
16990
|
-
}
|
|
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
16962
|
|
|
17006
|
-
/**
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
* @param {HTMLElement} formElement
|
|
17020
|
-
* @returns {NamespaceResolver} The namespace resolver for this context
|
|
17021
|
-
*/
|
|
17022
|
-
function createNamespaceResolver(xpathQuery, formElement) {
|
|
17023
|
-
const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
|
|
17024
|
-
if (cachedResolver) {
|
|
17025
|
-
return cachedResolver;
|
|
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}`);
|
|
17026
16976
|
}
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
const
|
|
17033
|
-
|
|
17034
|
-
|
|
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
|
+
}
|
|
17035
16991
|
}
|
|
17036
|
-
|
|
17037
|
-
instanceReferences = ['default'];
|
|
16992
|
+
return found;
|
|
17038
16993
|
}
|
|
17039
|
-
|
|
17040
|
-
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
|
|
17053
|
-
|
|
17054
|
-
|
|
17055
|
-
|
|
17056
|
-
|
|
17057
|
-
`Resolving the xpath ${xpathQuery} with the default namespace set to ${xpathDefaultNamespace}`,
|
|
17058
|
-
);
|
|
17059
|
-
*/
|
|
17060
|
-
/**
|
|
17061
|
-
* @type {NamespaceResolver}
|
|
17062
|
-
*/
|
|
17063
|
-
const resolveNamespacePrefix = prefix => {
|
|
17064
|
-
if (!prefix) {
|
|
17065
|
-
return xpathDefaultNamespace;
|
|
17066
|
-
}
|
|
17067
|
-
return undefined;
|
|
17068
|
-
};
|
|
17069
|
-
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
17070
|
-
return resolveNamespacePrefix;
|
|
16994
|
+
|
|
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
|
+
}
|
|
17071
17012
|
}
|
|
17013
|
+
return false;
|
|
17072
17014
|
}
|
|
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
17015
|
|
|
17081
|
-
|
|
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
|
+
}
|
|
17046
|
+
}
|
|
17047
|
+
return start;
|
|
17048
|
+
}
|
|
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
|
-
* type of the output is more predictable.
|
|
17192
|
+
* A simple dependency graph
|
|
17215
17193
|
*
|
|
17216
|
-
*
|
|
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
|
|
17194
|
+
* based on the work of https://github.com/jriecken/dependency-graph but working on ES6.
|
|
17221
17195
|
*
|
|
17222
|
-
*
|
|
17196
|
+
* Furthermore instead of the DepGraphCycleError a compute-exception event is dispatched.
|
|
17197
|
+
*
|
|
17198
|
+
*
|
|
17199
|
+
*/
|
|
17200
|
+
|
|
17201
|
+
/**
|
|
17202
|
+
* Cycle error, including the path of the cycle.
|
|
17223
17203
|
*/
|
|
17204
|
+
// const DepGraphCycleError = (exports.DepGraphCycleError = function (cyclePath) {
|
|
17205
|
+
|
|
17224
17206
|
/*
|
|
17225
|
-
export function
|
|
17226
|
-
|
|
17227
|
-
|
|
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);
|
|
17214
|
+
}
|
|
17215
|
+
return instance;
|
|
17216
|
+
};
|
|
17228
17217
|
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
|
|
17235
|
-
|
|
17236
|
-
|
|
17237
|
-
|
|
17238
|
-
moduleImports: {
|
|
17239
|
-
xf: XFORMS_NAMESPACE_URI,
|
|
17240
|
-
},
|
|
17241
|
-
functionNameResolver,
|
|
17242
|
-
namespaceResolver,
|
|
17243
|
-
language: options.language || evaluateXPath.XPATH_3_1
|
|
17244
|
-
},
|
|
17245
|
-
);
|
|
17246
|
-
}
|
|
17218
|
+
DepGraphCycleError.prototype = Object.create(Error.prototype, {
|
|
17219
|
+
constructor: {
|
|
17220
|
+
value: Error,
|
|
17221
|
+
enumerable: false,
|
|
17222
|
+
writable: true,
|
|
17223
|
+
configurable: true
|
|
17224
|
+
}
|
|
17225
|
+
});
|
|
17226
|
+
Object.setPrototypeOf(DepGraphCycleError, Error);
|
|
17247
17227
|
*/
|
|
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
17228
|
|
|
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
17229
|
/**
|
|
17302
|
-
*
|
|
17230
|
+
* Helper for creating a Topological Sort using Depth-First-Search on a set of edges.
|
|
17303
17231
|
*
|
|
17304
|
-
*
|
|
17305
|
-
*
|
|
17306
|
-
* @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
|
|
17307
|
-
* @returns {Node} The first node found in the XPath
|
|
17308
|
-
*/
|
|
17309
|
-
function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
17310
|
-
try {
|
|
17311
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17312
|
-
const variablesInScope = getVariablesInScope(formElement);
|
|
17313
|
-
const result = evaluateXPathToFirstNode$1(xpath, contextNode, null, variablesInScope, {
|
|
17314
|
-
defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
|
|
17315
|
-
moduleImports: {
|
|
17316
|
-
xf: XFORMS_NAMESPACE_URI
|
|
17317
|
-
},
|
|
17318
|
-
currentContext: {
|
|
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
|
-
}));
|
|
17338
|
-
}
|
|
17339
|
-
return null;
|
|
17340
|
-
}
|
|
17341
|
-
|
|
17342
|
-
/**
|
|
17343
|
-
* Evaluate an XPath to all nodes
|
|
17232
|
+
* Detects cycles and throws an Error if one is detected (unless the "circular"
|
|
17233
|
+
* parameter is "true" in which case it ignores them).
|
|
17344
17234
|
*
|
|
17345
|
-
* @param
|
|
17346
|
-
* @param
|
|
17347
|
-
* @param
|
|
17348
|
-
* @
|
|
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
|
|
17349
17239
|
*/
|
|
17350
|
-
function
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17356
|
-
|
|
17357
|
-
|
|
17358
|
-
|
|
17359
|
-
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
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
|
|
17364
17254
|
});
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
}
|
|
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);
|
|
17380
17289
|
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
|
|
17393
|
-
|
|
17394
|
-
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
} catch (e) {
|
|
17405
|
-
formElement.dispatchEvent(new CustomEvent('error', {
|
|
17406
|
-
composed: false,
|
|
17407
|
-
bubbles: true,
|
|
17408
|
-
detail: {
|
|
17409
|
-
origin: formElement,
|
|
17410
|
-
message: `Expression '${xpath}' failed: ${e}`,
|
|
17411
|
-
expr: xpath,
|
|
17412
|
-
level: 'Error'
|
|
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
|
-
function evaluateXPathToString(xpath, contextNode, formElement, domFacade = null) {
|
|
17430
|
-
try {
|
|
17431
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17432
|
-
const variablesInScope = getVariablesInScope(formElement);
|
|
17433
|
-
return evaluateXPathToString$1(xpath, contextNode, domFacade, variablesInScope, {
|
|
17434
|
-
currentContext: {
|
|
17435
|
-
formElement
|
|
17436
|
-
},
|
|
17437
|
-
functionNameResolver,
|
|
17438
|
-
moduleImports: {
|
|
17439
|
-
xf: XFORMS_NAMESPACE_URI
|
|
17440
|
-
},
|
|
17441
|
-
namespaceResolver,
|
|
17442
|
-
xmlSerializer: new XMLSerializer()
|
|
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
|
-
}
|
|
17456
|
-
}
|
|
17457
17321
|
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
* @param {Node} formElement The element where the XPath is defined: used for namespace resolving
|
|
17465
|
-
* @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
|
|
17466
|
-
* access. This is used to determine dependencies between bind elements.
|
|
17467
|
-
* @return {string[]}
|
|
17468
|
-
*/
|
|
17469
|
-
function evaluateXPathToStrings(xpath, contextNode, formElement, domFacade = null) {
|
|
17470
|
-
try {
|
|
17471
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17472
|
-
return evaluateXPathToStrings$1(xpath, contextNode, domFacade, {}, {
|
|
17473
|
-
currentContext: {
|
|
17474
|
-
formElement
|
|
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'
|
|
17492
|
-
}
|
|
17493
|
-
}));
|
|
17494
|
-
}
|
|
17495
|
-
}
|
|
17496
|
-
|
|
17497
|
-
/**
|
|
17498
|
-
* Evaluate an XPath to a number
|
|
17499
|
-
*
|
|
17500
|
-
* @param {string} xpath The XPath to run
|
|
17501
|
-
* @param {Node} contextNode The start of the XPath
|
|
17502
|
-
* @param {Node} formElement The form element associated to the XPath
|
|
17503
|
-
* @param {Node} formElement The element where the XPath is defined: used for namespace resolving
|
|
17504
|
-
* @param {import('fontoxpath').IDomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
|
|
17505
|
-
* access. This is used to determine dependencies between bind elements.
|
|
17506
|
-
* @return {number}
|
|
17507
|
-
*/
|
|
17508
|
-
function evaluateXPathToNumber(xpath, contextNode, formElement, domFacade = null) {
|
|
17509
|
-
try {
|
|
17510
|
-
const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
|
|
17511
|
-
const variablesInScope = getVariablesInScope(formElement);
|
|
17512
|
-
return evaluateXPathToNumber$1(xpath, contextNode, domFacade, variablesInScope, {
|
|
17513
|
-
currentContext: {
|
|
17514
|
-
formElement
|
|
17515
|
-
},
|
|
17516
|
-
functionNameResolver,
|
|
17517
|
-
moduleImports: {
|
|
17518
|
-
xf: XFORMS_NAMESPACE_URI
|
|
17519
|
-
},
|
|
17520
|
-
namespaceResolver,
|
|
17521
|
-
xmlSerializer: new XMLSerializer()
|
|
17522
|
-
});
|
|
17523
|
-
} catch (e) {
|
|
17524
|
-
formElement.dispatchEvent(new CustomEvent('error', {
|
|
17525
|
-
composed: false,
|
|
17526
|
-
bubbles: true,
|
|
17527
|
-
detail: {
|
|
17528
|
-
origin: formElement,
|
|
17529
|
-
message: `Expression '${xpath}' failed: ${e}`,
|
|
17530
|
-
expr: xpath,
|
|
17531
|
-
level: 'Error'
|
|
17532
|
-
}
|
|
17533
|
-
}));
|
|
17534
|
-
}
|
|
17535
|
-
}
|
|
17536
|
-
const contextFunction = (dynamicContext, string) => {
|
|
17537
|
-
const caller = dynamicContext.currentContext.formElement;
|
|
17538
|
-
let instance = null;
|
|
17539
|
-
if (string) {
|
|
17540
|
-
instance = resolveId(string, caller);
|
|
17541
|
-
} else {
|
|
17542
|
-
instance = XPathUtil.getParentBindingElement(caller);
|
|
17543
|
-
}
|
|
17544
|
-
if (instance) {
|
|
17545
|
-
if (instance.nodeName === 'FX-REPEAT') {
|
|
17546
|
-
const {
|
|
17547
|
-
nodeset
|
|
17548
|
-
} = instance;
|
|
17549
|
-
for (let parent = caller; parent; parent = parent.parentNode) {
|
|
17550
|
-
if (parent.parentNode === instance) {
|
|
17551
|
-
const offset = Array.from(parent.parentNode.children).indexOf(parent);
|
|
17552
|
-
return nodeset[offset];
|
|
17553
|
-
}
|
|
17554
|
-
}
|
|
17555
|
-
}
|
|
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
|
-
|
|
17581
|
-
/**
|
|
17582
|
-
* @param id as string
|
|
17583
|
-
* @return instance data for given id serialized to string.
|
|
17584
|
-
*/
|
|
17585
|
-
registerCustomXPathFunction({
|
|
17586
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17587
|
-
localName: 'context'
|
|
17588
|
-
}, ['xs:string'], 'item()?', contextFunction);
|
|
17589
|
-
registerCustomXPathFunction({
|
|
17590
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17591
|
-
localName: 'current'
|
|
17592
|
-
}, ['xs:string'], 'item()?', currentFunction);
|
|
17593
|
-
registerCustomXPathFunction({
|
|
17594
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17595
|
-
localName: 'element'
|
|
17596
|
-
}, ['xs:string'], 'item()?', elementFunction);
|
|
17597
|
-
|
|
17598
|
-
/**
|
|
17599
|
-
* @param id as string
|
|
17600
|
-
* @return instance data for given id serialized to string.
|
|
17601
|
-
*/
|
|
17602
|
-
registerCustomXPathFunction({
|
|
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);
|
|
17617
|
-
}
|
|
17618
|
-
}
|
|
17619
|
-
return null;
|
|
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);
|
|
17645
|
-
|
|
17646
|
-
/*
|
|
17647
|
-
const {formElement} = dynamicContext.currentContext;
|
|
17648
|
-
const instance = resolveId(string, formElement, 'fx-instance');
|
|
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;
|
|
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
|
|
17660
17328
|
});
|
|
17661
|
-
|
|
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
|
-
} */
|
|
17329
|
+
*/
|
|
17705
17330
|
|
|
17706
|
-
|
|
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
|
|
17707
17336
|
}
|
|
17708
17337
|
|
|
17709
|
-
|
|
17710
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
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());
|
|
17720
|
-
|
|
17721
|
-
const treeDiv = document.createElement('div');
|
|
17722
|
-
treeDiv.setAttribute('class', 'logtree');
|
|
17723
|
-
// const datatree = buildTree(tree,instance.getDefaultContext());
|
|
17724
|
-
// return tree.appendChild(datatree);
|
|
17725
|
-
// return buildTree(root,instance.getDefaultContext());;
|
|
17726
|
-
const form = dynamicContext.currentContext.formElement;
|
|
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
|
-
}
|
|
17735
|
-
}
|
|
17736
|
-
return null;
|
|
17737
|
-
});
|
|
17738
|
-
const instance = (dynamicContext, string) => {
|
|
17739
|
-
// Spec: https://www.w3.org/TR/xforms-xpath/#The_XForms_Function_Library#The_instance.28.29_Function
|
|
17740
|
-
// TODO: handle no string passed (null will be passed instead)
|
|
17741
|
-
|
|
17338
|
+
DepGraph.prototype = {
|
|
17742
17339
|
/**
|
|
17743
|
-
*
|
|
17340
|
+
* The number of nodes in the graph.
|
|
17744
17341
|
*/
|
|
17745
|
-
|
|
17746
|
-
|
|
17747
|
-
}
|
|
17748
|
-
|
|
17749
|
-
|
|
17750
|
-
|
|
17751
|
-
|
|
17752
|
-
|
|
17753
|
-
|
|
17754
|
-
|
|
17755
|
-
|
|
17756
|
-
|
|
17757
|
-
|
|
17758
|
-
origin: 'functions',
|
|
17759
|
-
message: `Instance not found '${string}'`,
|
|
17760
|
-
level: 'Error'
|
|
17761
|
-
}
|
|
17762
|
-
}));
|
|
17763
|
-
}
|
|
17764
|
-
}
|
|
17765
|
-
const context = lookup.getDefaultContext();
|
|
17766
|
-
if (!context) {
|
|
17767
|
-
return null;
|
|
17768
|
-
}
|
|
17769
|
-
return context;
|
|
17770
|
-
};
|
|
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
|
-
|
|
17783
|
-
// const def = instance.getInstanceData();
|
|
17784
|
-
if (repeat) {
|
|
17785
|
-
return repeat.getAttribute('index');
|
|
17786
|
-
}
|
|
17787
|
-
return Number(1);
|
|
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');
|
|
17840
|
-
}
|
|
17841
|
-
convert(json, root);
|
|
17842
|
-
// return root.outerHTML;
|
|
17843
|
-
console.log('xml', root);
|
|
17844
|
-
return root;
|
|
17845
|
-
};
|
|
17846
|
-
registerCustomXPathFunction({
|
|
17847
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17848
|
-
localName: 'json2xml'
|
|
17849
|
-
}, ['item()?'], 'item()?', jsonToXml);
|
|
17850
|
-
const xmlToJson = (_dynamicContext, xml) => {
|
|
17851
|
-
const isElementNode = node => node.nodeType === Node.ELEMENT_NODE;
|
|
17852
|
-
const isTextNode = node => node.nodeType === Node.TEXT_NODE;
|
|
17853
|
-
const parseNode = node => {
|
|
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
|
-
}
|
|
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;
|
|
17873
17355
|
}
|
|
17874
|
-
|
|
17356
|
+
this.outgoingEdges[node] = [];
|
|
17357
|
+
this.incomingEdges[node] = [];
|
|
17875
17358
|
}
|
|
17876
|
-
|
|
17877
|
-
|
|
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
|
+
});
|
|
17878
17377
|
}
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
17882
|
-
|
|
17883
|
-
|
|
17884
|
-
|
|
17885
|
-
};
|
|
17886
|
-
registerCustomXPathFunction({
|
|
17887
|
-
namespaceURI: XFORMS_NAMESPACE_URI,
|
|
17888
|
-
localName: 'xmltoJson'
|
|
17889
|
-
}, ['item()?'], 'item()?', xmlToJson);
|
|
17890
|
-
|
|
17891
|
-
/*
|
|
17892
|
-
// Example usage:
|
|
17893
|
-
const xml = '<json type="object"><given>Mark</given><family>Smith</family></json>';
|
|
17894
|
-
console.log(xmlToJson(xml));
|
|
17895
|
-
*/
|
|
17378
|
+
},
|
|
17379
|
+
/**
|
|
17380
|
+
* Check if a node exists in the graph
|
|
17381
|
+
*/
|
|
17382
|
+
hasNode(node) {
|
|
17383
|
+
// return this.nodes.hasOwnProperty(node);
|
|
17896
17384
|
|
|
17897
|
-
|
|
17898
|
-
|
|
17899
|
-
|
|
17900
|
-
|
|
17901
|
-
|
|
17902
|
-
|
|
17903
|
-
|
|
17904
|
-
|
|
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;
|
|
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];
|
|
17911
17393
|
}
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
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}`);
|
|
17918
17404
|
}
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
|
|
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}`);
|
|
17923
17413
|
}
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
|
|
17927
|
-
|
|
17928
|
-
|
|
17929
|
-
|
|
17930
|
-
|
|
17931
|
-
|
|
17932
|
-
|
|
17933
|
-
|
|
17934
|
-
}
|
|
17935
|
-
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
/**
|
|
17969
|
-
* @param input as string
|
|
17970
|
-
* @return {string}
|
|
17971
|
-
*/
|
|
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
|
-
|
|
18039
|
-
/**
|
|
18040
|
-
* @param {Node} node
|
|
18041
|
-
* @returns string
|
|
18042
|
-
*/
|
|
18043
|
-
/*
|
|
18044
|
-
export static getDocPath(node) {
|
|
18045
|
-
const path = fx.evaluateXPathToString('path()', node);
|
|
18046
|
-
// Path is like `$default/x[1]/y[1]`
|
|
18047
|
-
const shortened = XPathUtil.shortenPath(path);
|
|
18048
|
-
return shortened.startsWith('/') ? `${shortened}` : `/${shortened}`;
|
|
18049
|
-
}
|
|
18050
|
-
*/
|
|
18051
|
-
|
|
18052
|
-
class XPathUtil {
|
|
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);
|
|
17455
|
+
});
|
|
17456
|
+
return result;
|
|
17457
|
+
},
|
|
18053
17458
|
/**
|
|
18054
|
-
*
|
|
17459
|
+
* Get an array containing the direct dependencies of the specified node.
|
|
17460
|
+
*
|
|
17461
|
+
* Throws an Error if the specified node does not exist.
|
|
18055
17462
|
*/
|
|
18056
|
-
|
|
18057
|
-
if (
|
|
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;
|
|
17463
|
+
directDependenciesOf(node) {
|
|
17464
|
+
if (this.hasNode(node)) {
|
|
17465
|
+
return this.outgoingEdges[node].slice(0);
|
|
18062
17466
|
}
|
|
18063
|
-
|
|
18064
|
-
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
|
|
18069
|
-
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
|
|
18073
|
-
|
|
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);
|
|
18074
17496
|
}
|
|
17497
|
+
return result;
|
|
18075
17498
|
}
|
|
18076
|
-
|
|
18077
|
-
}
|
|
18078
|
-
|
|
17499
|
+
throw new Error(`Node does not exist: ${node}`);
|
|
17500
|
+
},
|
|
18079
17501
|
/**
|
|
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
|
|
17502
|
+
* get an array containing the nodes that depend on the specified node (transitively).
|
|
18084
17503
|
*
|
|
18085
|
-
*
|
|
17504
|
+
* Throws an Error if the graph has a cycle, or the specified node does not exist.
|
|
18086
17505
|
*
|
|
18087
|
-
*
|
|
18088
|
-
* @param doc {XMLDocument}
|
|
18089
|
-
* @param fore
|
|
18090
|
-
* @return {Node|Attr}
|
|
17506
|
+
* If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array.
|
|
18091
17507
|
*/
|
|
18092
|
-
|
|
18093
|
-
|
|
18094
|
-
|
|
18095
|
-
|
|
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);
|
|
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
|
|
18096
17541
|
}
|
|
18097
17542
|
|
|
18098
|
-
|
|
18099
|
-
|
|
18100
|
-
|
|
18101
|
-
|
|
18102
|
-
|
|
18103
|
-
|
|
18104
|
-
|
|
18105
|
-
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
|
|
18110
|
-
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
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
|
+
});
|
|
17557
|
+
|
|
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));
|
|
17563
|
+
}
|
|
17564
|
+
return result;
|
|
17565
|
+
}
|
|
17566
|
+
};
|
|
17567
|
+
|
|
17568
|
+
// Create some aliases
|
|
17569
|
+
DepGraph.prototype.directDependentsOf = DepGraph.prototype.directDependantsOf;
|
|
17570
|
+
DepGraph.prototype.dependentsOf = DepGraph.prototype.dependantsOf;
|
|
17571
|
+
|
|
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;
|
|
17584
|
+
}
|
|
17585
|
+
|
|
17586
|
+
const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
17587
|
+
const createdNamespaceResolversByXPathQueryAndNode = new Map();
|
|
17588
|
+
|
|
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;
|
|
17595
|
+
}
|
|
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());
|
|
17601
|
+
}
|
|
17602
|
+
return createdNamespaceResolversByXPathQueryAndNode.get(xpath).set(node, resolver);
|
|
17603
|
+
}
|
|
17604
|
+
const xhtmlNamespaceResolver = prefix => {
|
|
17605
|
+
if (!prefix) {
|
|
17606
|
+
return 'http://www.w3.org/1999/xhtml';
|
|
17607
|
+
}
|
|
17608
|
+
return undefined;
|
|
17609
|
+
};
|
|
17610
|
+
|
|
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);
|
|
17631
|
+
}
|
|
17632
|
+
*/
|
|
17633
|
+
if (sourceObject.nodeType === Node.TEXT_NODE) {
|
|
17634
|
+
sourceObject = sourceObject.parentNode;
|
|
17635
|
+
}
|
|
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;
|
|
17649
|
+
}
|
|
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;
|
|
18120
17671
|
}
|
|
18121
|
-
|
|
18122
|
-
|
|
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
|
-
}
|
|
17672
|
+
return targetObject;
|
|
17673
|
+
}
|
|
18131
17674
|
|
|
18132
|
-
|
|
18133
|
-
if (part.startsWith('@')) {
|
|
18134
|
-
const attrName = part.slice(1); // Strip '@'
|
|
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.
|
|
17675
|
+
// SPEC:
|
|
18149
17676
|
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
|
|
18154
|
-
|
|
18155
|
-
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
18161
|
-
|
|
18162
|
-
|
|
18163
|
-
|
|
18164
|
-
|
|
18165
|
-
|
|
18166
|
-
|
|
18167
|
-
|
|
18168
|
-
|
|
18169
|
-
|
|
18170
|
-
|
|
18171
|
-
|
|
18172
|
-
|
|
17677
|
+
// 12.2.1 References to Elements within a repeat Element
|
|
17678
|
+
|
|
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.
|
|
17684
|
+
|
|
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;
|
|
18173
17703
|
}
|
|
17704
|
+
return targetObject;
|
|
18174
17705
|
}
|
|
18175
|
-
|
|
18176
|
-
|
|
18177
|
-
|
|
18178
|
-
|
|
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;
|
|
18179
17720
|
}
|
|
18180
|
-
currentNode = newElement;
|
|
18181
|
-
}
|
|
18182
|
-
}
|
|
18183
|
-
if (!rootNode) {
|
|
18184
|
-
throw new Error('Invalid XPath; no root element could be created.');
|
|
18185
17721
|
}
|
|
18186
|
-
return rootNode;
|
|
18187
17722
|
}
|
|
17723
|
+
// We found no target objects in common repeats. The id is unresolvable
|
|
17724
|
+
return null;
|
|
17725
|
+
}
|
|
18188
17726
|
|
|
18189
|
-
|
|
18190
|
-
|
|
18191
|
-
|
|
18192
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
* @param boundElement
|
|
18197
|
-
* @param prefix
|
|
18198
|
-
* @return {string}
|
|
18199
|
-
*/
|
|
18200
|
-
static lookupNamespace(ownerForm, prefix) {
|
|
18201
|
-
return ownerForm.getAttribute(`xmlns:${prefix}`);
|
|
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 [];
|
|
18202
17734
|
}
|
|
18203
|
-
|
|
18204
|
-
|
|
18205
|
-
const found = [];
|
|
18206
|
-
while (queue.length) {
|
|
18207
|
-
const item = queue.shift();
|
|
18208
|
-
for (const child of Array.from(item.children).reverse()) {
|
|
18209
|
-
queue.unshift(child);
|
|
18210
|
-
}
|
|
18211
|
-
if (item.matches && item.matches('template')) {
|
|
18212
|
-
queue.unshift(item.content);
|
|
18213
|
-
}
|
|
18214
|
-
if (item.matches && item.matches(querySelector)) {
|
|
18215
|
-
found.push(item);
|
|
18216
|
-
}
|
|
18217
|
-
}
|
|
18218
|
-
return found;
|
|
17735
|
+
if (instanceReferencesByQuery.has(xpathQuery)) {
|
|
17736
|
+
return instanceReferencesByQuery.get(xpathQuery);
|
|
18219
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
|
+
}
|
|
17749
|
+
/**
|
|
17750
|
+
* @typedef {function(string):string} NamespaceResolver
|
|
17751
|
+
*/
|
|
18220
17752
|
|
|
18221
|
-
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
|
|
18226
|
-
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
|
|
18234
|
-
|
|
18235
|
-
|
|
18236
|
-
|
|
18237
|
-
|
|
17753
|
+
/**
|
|
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
|
|
17768
|
+
*/
|
|
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;
|
|
18238
17782
|
}
|
|
18239
|
-
|
|
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;
|
|
17813
|
+
}
|
|
17814
|
+
return undefined;
|
|
17815
|
+
};
|
|
17816
|
+
setCachedNamespaceResolver(xpathQuery, formElement, resolveNamespacePrefix);
|
|
17817
|
+
return resolveNamespacePrefix;
|
|
17818
|
+
}
|
|
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
|
+
);
|
|
18240
17825
|
}
|
|
17826
|
+
*/
|
|
17827
|
+
|
|
17828
|
+
const xpathDefaultNamespace = evaluateXPathToString$1('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) || '';
|
|
18241
17829
|
|
|
18242
17830
|
/**
|
|
18243
|
-
*
|
|
18244
|
-
*
|
|
18245
|
-
* @param {string} querySelector
|
|
18246
|
-
* @param {Node} start
|
|
18247
|
-
* @returns {HTMLElement}
|
|
17831
|
+
* @type {NamespaceResolver}
|
|
18248
17832
|
*/
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
17833
|
+
const resolveNamespacePrefix = function resolveNamespacePrefix(prefix) {
|
|
17834
|
+
if (prefix === '') {
|
|
17835
|
+
return xpathDefaultNamespace;
|
|
17836
|
+
}
|
|
17837
|
+
|
|
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!
|
|
17841
|
+
|
|
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;
|
|
17853
|
+
}
|
|
17854
|
+
return createNamespaceResolver(query, formElement);
|
|
17855
|
+
}
|
|
17856
|
+
|
|
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
|
+
};
|
|
18263
17906
|
}
|
|
18264
|
-
if (
|
|
18265
|
-
|
|
18266
|
-
|
|
17907
|
+
if (prefix === 'fn' || prefix === '') {
|
|
17908
|
+
return {
|
|
17909
|
+
namespaceURI: 'http://www.w3.org/2005/xpath-functions',
|
|
17910
|
+
localName
|
|
17911
|
+
};
|
|
18267
17912
|
}
|
|
18268
|
-
|
|
18269
|
-
|
|
18270
|
-
|
|
17913
|
+
if (prefix === 'local') {
|
|
17914
|
+
return {
|
|
17915
|
+
namespaceURI: 'http://www.w3.org/2005/xquery-local-functions',
|
|
17916
|
+
localName
|
|
17917
|
+
};
|
|
18271
17918
|
}
|
|
18272
|
-
|
|
18273
|
-
return start;
|
|
17919
|
+
return null;
|
|
18274
17920
|
}
|
|
17921
|
+
}
|
|
18275
17922
|
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
|
|
18280
|
-
|
|
18281
|
-
|
|
18282
|
-
|
|
18283
|
-
|
|
18284
|
-
|
|
18285
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
if (start.parentNode && (start.parentNode.nodeType !== Node.DOCUMENT_NODE || start.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE)) {
|
|
18289
|
-
return this.getClosest('fx-control[ref],fx-upload[ref],fx-group[ref],fx-repeat[ref], fx-switch[ref],fx-repeatitem', start.parentNode);
|
|
18290
|
-
}
|
|
18291
|
-
return null;
|
|
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;
|
|
18292
17935
|
}
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
* Checks whether the specified path expression is an absolute path.
|
|
18296
|
-
*
|
|
18297
|
-
* @param {string} path the path expression.
|
|
18298
|
-
* @returns {boolean} <code>true</code> if specified path expression is an absolute
|
|
18299
|
-
* path, otherwise <code>false</code>.
|
|
18300
|
-
*/
|
|
18301
|
-
static isAbsolutePath(path) {
|
|
18302
|
-
return path != null && (path.startsWith('/') || path.startsWith('instance(') || path.startsWith('$'));
|
|
17936
|
+
if (!closestActualFormElement) {
|
|
17937
|
+
return {};
|
|
18303
17938
|
}
|
|
18304
|
-
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
|
|
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) {
|
|
17944
|
+
continue;
|
|
17945
|
+
}
|
|
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;
|
|
17953
|
+
}
|
|
17954
|
+
}
|
|
18310
17955
|
}
|
|
17956
|
+
return variables;
|
|
17957
|
+
}
|
|
18311
17958
|
|
|
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
|
-
|
|
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);
|
|
17975
|
+
|
|
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'
|
|
18343
18027
|
}
|
|
18344
|
-
|
|
18345
|
-
}
|
|
18346
|
-
return null;
|
|
18347
|
-
}
|
|
18028
|
+
}));
|
|
18348
18029
|
|
|
18349
|
-
|
|
18350
|
-
|
|
18351
|
-
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
18356
|
-
|
|
18357
|
-
|
|
18358
|
-
|
|
18359
|
-
|
|
18360
|
-
|
|
18361
|
-
|
|
18362
|
-
|
|
18363
|
-
|
|
18364
|
-
|
|
18365
|
-
}
|
|
18366
|
-
return 'default';
|
|
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 [];
|
|
18367
18046
|
}
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
|
|
18374
|
-
|
|
18375
|
-
|
|
18376
|
-
|
|
18377
|
-
|
|
18378
|
-
|
|
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'
|
|
18083
|
+
}
|
|
18084
|
+
}));
|
|
18379
18085
|
}
|
|
18380
|
-
|
|
18086
|
+
return null;
|
|
18087
|
+
}
|
|
18381
18088
|
|
|
18382
|
-
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18386
|
-
|
|
18387
|
-
|
|
18388
|
-
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
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
|
+
}));
|
|
18392
18125
|
}
|
|
18126
|
+
}
|
|
18393
18127
|
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
18398
|
-
|
|
18399
|
-
|
|
18400
|
-
|
|
18401
|
-
|
|
18402
|
-
|
|
18403
|
-
|
|
18404
|
-
|
|
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
|
+
}));
|
|
18405
18162
|
}
|
|
18163
|
+
}
|
|
18406
18164
|
|
|
18407
|
-
|
|
18408
|
-
|
|
18409
|
-
|
|
18410
|
-
|
|
18411
|
-
|
|
18412
|
-
|
|
18413
|
-
|
|
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
|
+
}));
|
|
18414
18202
|
}
|
|
18415
18203
|
}
|
|
18416
18204
|
|
|
18417
18205
|
/**
|
|
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
|
-
*
|
|
18206
|
+
* Evaluate an XPath to a set of strings
|
|
18424
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[]}
|
|
18425
18215
|
*/
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
|
|
18432
|
-
|
|
18433
|
-
|
|
18434
|
-
|
|
18435
|
-
|
|
18436
|
-
|
|
18437
|
-
|
|
18438
|
-
|
|
18439
|
-
|
|
18440
|
-
}
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
|
|
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
|
+
}));
|
|
18450
18241
|
}
|
|
18451
|
-
}
|
|
18452
|
-
Object.setPrototypeOf(DepGraphCycleError, Error);
|
|
18453
|
-
*/
|
|
18242
|
+
}
|
|
18454
18243
|
|
|
18455
18244
|
/**
|
|
18456
|
-
*
|
|
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).
|
|
18245
|
+
* Evaluate an XPath to a number
|
|
18460
18246
|
*
|
|
18461
|
-
* @param
|
|
18462
|
-
* @param
|
|
18463
|
-
* @param
|
|
18464
|
-
* @param
|
|
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}
|
|
18465
18254
|
*/
|
|
18466
|
-
function
|
|
18467
|
-
|
|
18468
|
-
|
|
18469
|
-
|
|
18470
|
-
|
|
18471
|
-
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
18476
|
-
|
|
18477
|
-
|
|
18478
|
-
|
|
18479
|
-
|
|
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()
|
|
18480
18269
|
});
|
|
18481
|
-
|
|
18482
|
-
|
|
18483
|
-
|
|
18484
|
-
|
|
18485
|
-
|
|
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
|
+
}));
|
|
18281
|
+
}
|
|
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);
|
|
18290
|
+
}
|
|
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
|
-
|
|
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);
|
|
18344
|
+
|
|
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;
|
|
18554
18367
|
});
|
|
18555
|
-
|
|
18556
|
-
|
|
18557
|
-
|
|
18558
|
-
|
|
18559
|
-
|
|
18560
|
-
|
|
18561
|
-
|
|
18562
|
-
|
|
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
|
-
|
|
18619
|
-
|
|
18620
|
-
|
|
18621
|
-
|
|
18622
|
-
|
|
18623
|
-
|
|
18624
|
-
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
18629
|
-
|
|
18630
|
-
|
|
18631
|
-
|
|
18632
|
-
|
|
18633
|
-
|
|
18634
|
-
|
|
18635
|
-
*/
|
|
18636
|
-
addDependency(from, to) {
|
|
18637
|
-
if (!this.hasNode(from)) {
|
|
18638
|
-
throw new Error(`Node does not exist: ${from}`);
|
|
18639
|
-
}
|
|
18640
|
-
if (!this.hasNode(to)) {
|
|
18641
|
-
throw new Error(`Node does not exist: ${to}`);
|
|
18642
|
-
}
|
|
18643
|
-
if (this.outgoingEdges[from].indexOf(to) === -1) {
|
|
18644
|
-
this.outgoingEdges[from].push(to);
|
|
18645
|
-
}
|
|
18646
|
-
if (this.incomingEdges[to].indexOf(from) === -1) {
|
|
18647
|
-
this.incomingEdges[to].push(from);
|
|
18648
|
-
}
|
|
18649
|
-
return true;
|
|
18650
|
-
},
|
|
18651
|
-
/**
|
|
18652
|
-
* Remove a dependency between two nodes.
|
|
18653
|
-
*/
|
|
18654
|
-
removeDependency(from, to) {
|
|
18655
|
-
let idx;
|
|
18656
|
-
if (this.hasNode(from)) {
|
|
18657
|
-
idx = this.outgoingEdges[from].indexOf(to);
|
|
18658
|
-
if (idx >= 0) {
|
|
18659
|
-
this.outgoingEdges[from].splice(idx, 1);
|
|
18660
|
-
}
|
|
18661
|
-
}
|
|
18662
|
-
if (this.hasNode(to)) {
|
|
18663
|
-
idx = this.incomingEdges[to].indexOf(from);
|
|
18664
|
-
if (idx >= 0) {
|
|
18665
|
-
this.incomingEdges[to].splice(idx, 1);
|
|
18666
|
-
}
|
|
18667
|
-
}
|
|
18668
|
-
},
|
|
18669
|
-
/**
|
|
18670
|
-
* Return a clone of the dependency graph. If any custom data is attached
|
|
18671
|
-
* to the nodes, it will only be shallow copied.
|
|
18672
|
-
*/
|
|
18673
|
-
clone() {
|
|
18674
|
-
const source = this;
|
|
18675
|
-
const result = new DepGraph();
|
|
18676
|
-
const keys = Object.keys(source.nodes);
|
|
18677
|
-
keys.forEach(n => {
|
|
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);
|
|
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);
|
|
18692
18477
|
}
|
|
18693
|
-
|
|
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);
|
|
18478
|
+
const tree = buildTree(treeDiv, instance.getDefaultContext());
|
|
18479
|
+
if (tree) {
|
|
18480
|
+
form.appendChild(tree);
|
|
18703
18481
|
}
|
|
18704
|
-
|
|
18705
|
-
|
|
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
|
+
|
|
18706
18489
|
/**
|
|
18707
|
-
*
|
|
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.
|
|
18490
|
+
* @type {import('./fx-fore.js').FxFore}
|
|
18713
18491
|
*/
|
|
18714
|
-
|
|
18715
|
-
|
|
18716
|
-
|
|
18717
|
-
|
|
18718
|
-
|
|
18719
|
-
|
|
18720
|
-
|
|
18721
|
-
|
|
18722
|
-
|
|
18723
|
-
|
|
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
|
+
}));
|
|
18724
18510
|
}
|
|
18725
|
-
|
|
18726
|
-
|
|
18727
|
-
|
|
18728
|
-
|
|
18729
|
-
|
|
18730
|
-
|
|
18731
|
-
|
|
18732
|
-
|
|
18733
|
-
|
|
18734
|
-
|
|
18735
|
-
|
|
18736
|
-
|
|
18737
|
-
|
|
18738
|
-
|
|
18739
|
-
|
|
18740
|
-
|
|
18741
|
-
|
|
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
|
+
});
|
|
18580
|
+
}
|
|
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');
|
|
18742
18605
|
}
|
|
18743
|
-
|
|
18606
|
+
if (node.childNodes.length === 1 && isTextNode(node.firstChild)) {
|
|
18607
|
+
return node.textContent;
|
|
18608
|
+
}
|
|
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
|
+
}
|
|
18620
|
+
}
|
|
18621
|
+
return obj;
|
|
18744
18622
|
}
|
|
18745
|
-
|
|
18746
|
-
|
|
18747
|
-
/**
|
|
18748
|
-
* Get an array of nodes that have no dependants (i.e. nothing depends on them).
|
|
18749
|
-
*/
|
|
18750
|
-
entryNodes() {
|
|
18751
|
-
const self = this;
|
|
18752
|
-
return Object.keys(this.nodes).filter(node => self.incomingEdges[node].length === 0);
|
|
18753
|
-
},
|
|
18754
|
-
/**
|
|
18755
|
-
* Construct the overall processing order for the dependency graph.
|
|
18756
|
-
*
|
|
18757
|
-
* Throws an Error if the graph has a cycle.
|
|
18758
|
-
*
|
|
18759
|
-
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned.
|
|
18760
|
-
*/
|
|
18761
|
-
overallOrder(leavesOnly) {
|
|
18762
|
-
const self = this;
|
|
18763
|
-
const result = [];
|
|
18764
|
-
const keys = Object.keys(this.nodes);
|
|
18765
|
-
if (keys.length === 0) {
|
|
18766
|
-
return result; // Empty graph
|
|
18623
|
+
if (isTextNode(node)) {
|
|
18624
|
+
return node.textContent;
|
|
18767
18625
|
}
|
|
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);
|
|
18768
18637
|
|
|
18769
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
|
|
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;
|
|
18776
18658
|
}
|
|
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
18659
|
|
|
18784
|
-
//
|
|
18785
|
-
//
|
|
18786
|
-
//
|
|
18787
|
-
if (
|
|
18788
|
-
|
|
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];
|
|
18789
18665
|
}
|
|
18790
|
-
|
|
18666
|
+
|
|
18667
|
+
// arg might be `code`, so currentEvent.code should work
|
|
18668
|
+
if (arg.includes('.')) {
|
|
18669
|
+
return _propertyLookup(ancestor.currentEvent, arg);
|
|
18670
|
+
}
|
|
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');
|
|
@@ -23837,13 +23865,16 @@ class FxFore extends HTMLElement {
|
|
|
23837
23865
|
this._createRepeatsFromAttributes();
|
|
23838
23866
|
this.inited = true;
|
|
23839
23867
|
};
|
|
23840
|
-
this.version = 'Version: 2.
|
|
23868
|
+
this.version = 'Version: 2.8.0 - built on December 19, 2025 12:59:03';
|
|
23841
23869
|
|
|
23842
23870
|
/**
|
|
23843
23871
|
* @type {import('./fx-model.js').FxModel}
|
|
23844
23872
|
*/
|
|
23845
23873
|
this.model = null;
|
|
23846
23874
|
this.inited = false;
|
|
23875
|
+
this._initGatesPromise = null;
|
|
23876
|
+
this._warnedWaitForDeprecation = false;
|
|
23877
|
+
this._srcLoadPromise = null;
|
|
23847
23878
|
// this.addEventListener('model-construct-done', this._handleModelConstructDone);
|
|
23848
23879
|
// todo: refactoring - these should rather go into connectedcallback
|
|
23849
23880
|
this.addEventListener('message', this._displayMessage);
|
|
@@ -23985,94 +24016,217 @@ class FxFore extends HTMLElement {
|
|
|
23985
24016
|
}
|
|
23986
24017
|
|
|
23987
24018
|
/**
|
|
23988
|
-
*
|
|
23989
|
-
*
|
|
24019
|
+
* Parse a list of target specs.
|
|
24020
|
+
*
|
|
24021
|
+
* We accept both comma- and whitespace-separated lists (for backward compatibility with `wait-for`).
|
|
24022
|
+
* Each token can be:
|
|
24023
|
+
* - "self" (default)
|
|
24024
|
+
* - "closest" (closest fx-fore)
|
|
24025
|
+
* - "document"
|
|
24026
|
+
* - "window"
|
|
24027
|
+
* - a CSS selector (no whitespace)
|
|
23990
24028
|
*/
|
|
23991
|
-
|
|
23992
|
-
const raw = this.getAttribute('wait-for');
|
|
24029
|
+
_parseTargetList(raw) {
|
|
23993
24030
|
if (!raw) return [];
|
|
23994
|
-
|
|
24031
|
+
return raw.split(/[\s,]+/).map(s => s.trim()).filter(Boolean);
|
|
24032
|
+
}
|
|
24033
|
+
_findBySelector(sel) {
|
|
23995
24034
|
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
|
-
}
|
|
24035
|
+
for (const r of roots) {
|
|
24036
|
+
if (r && 'querySelector' in r) {
|
|
24037
|
+
const el = r.querySelector(sel);
|
|
24038
|
+
if (el) return el;
|
|
24008
24039
|
}
|
|
24009
|
-
if (el) out.push(el);
|
|
24010
24040
|
}
|
|
24011
|
-
return
|
|
24041
|
+
return null;
|
|
24042
|
+
}
|
|
24043
|
+
_isReadyTarget(el) {
|
|
24044
|
+
return !!(el && (el.ready === true || el.classList && el.classList.contains('fx-ready') || typeof el.hasAttribute === 'function' && el.hasAttribute('ready')));
|
|
24012
24045
|
}
|
|
24013
24046
|
|
|
24014
24047
|
/**
|
|
24015
|
-
*
|
|
24016
|
-
*
|
|
24048
|
+
* Collect all init gates derived from attributes.
|
|
24049
|
+
*
|
|
24050
|
+
* - `wait-for` (DEPRECATED) becomes: init-on="ready" + init-on-target=<list>
|
|
24051
|
+
* - `init-on` / `init-on-target` define a generic event gate
|
|
24017
24052
|
*/
|
|
24018
|
-
|
|
24019
|
-
const
|
|
24020
|
-
|
|
24021
|
-
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
24027
|
-
|
|
24028
|
-
|
|
24053
|
+
_collectInitGates() {
|
|
24054
|
+
const gates = [];
|
|
24055
|
+
const waitForRaw = this.getAttribute('wait-for');
|
|
24056
|
+
if (waitForRaw) {
|
|
24057
|
+
if (!this._warnedWaitForDeprecation) {
|
|
24058
|
+
console.warn('[fx-fore] The "wait-for" attribute is deprecated. Use init-on="ready" init-on-target="..." instead.');
|
|
24059
|
+
this._warnedWaitForDeprecation = true;
|
|
24060
|
+
}
|
|
24061
|
+
const deps = this._parseTargetList(waitForRaw);
|
|
24062
|
+
for (const dep of deps) {
|
|
24063
|
+
gates.push({
|
|
24064
|
+
event: 'ready',
|
|
24065
|
+
targetSpec: dep
|
|
24066
|
+
});
|
|
24029
24067
|
}
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
const
|
|
24033
|
-
|
|
24034
|
-
|
|
24035
|
-
|
|
24068
|
+
}
|
|
24069
|
+
const initOn = this.getAttribute('init-on');
|
|
24070
|
+
const initOnTargetRaw = this.getAttribute('init-on-target');
|
|
24071
|
+
if (initOn || initOnTargetRaw) {
|
|
24072
|
+
const eventName = initOn || 'ready';
|
|
24073
|
+
const targets = initOnTargetRaw ? this._parseTargetList(initOnTargetRaw) : ['self'];
|
|
24074
|
+
for (const t of targets) {
|
|
24075
|
+
gates.push({
|
|
24076
|
+
event: eventName,
|
|
24077
|
+
targetSpec: t
|
|
24078
|
+
});
|
|
24036
24079
|
}
|
|
24037
|
-
|
|
24038
|
-
|
|
24039
|
-
|
|
24040
|
-
|
|
24041
|
-
|
|
24042
|
-
|
|
24080
|
+
}
|
|
24081
|
+
return gates;
|
|
24082
|
+
}
|
|
24083
|
+
_waitForEvent(target, eventName, isSatisfiedFn = null) {
|
|
24084
|
+
// If a caller provides an explicit satisfaction check, honor it first.
|
|
24085
|
+
if (typeof isSatisfiedFn === 'function' && isSatisfiedFn(target)) {
|
|
24086
|
+
FxFore._markInitEventSeen(target, eventName);
|
|
24087
|
+
return Promise.resolve();
|
|
24088
|
+
}
|
|
24043
24089
|
|
|
24044
|
-
|
|
24045
|
-
|
|
24046
|
-
|
|
24047
|
-
|
|
24048
|
-
|
|
24049
|
-
|
|
24050
|
-
|
|
24051
|
-
|
|
24052
|
-
|
|
24053
|
-
|
|
24054
|
-
|
|
24055
|
-
|
|
24056
|
-
|
|
24090
|
+
// Sticky gate: if this event already happened on this target, don't wait again.
|
|
24091
|
+
if (FxFore._hasSeenInitEvent(target, eventName)) {
|
|
24092
|
+
return Promise.resolve();
|
|
24093
|
+
}
|
|
24094
|
+
return new Promise(resolve => {
|
|
24095
|
+
const ac = new AbortController();
|
|
24096
|
+
const on = () => {
|
|
24097
|
+
FxFore._markInitEventSeen(target, eventName);
|
|
24098
|
+
ac.abort();
|
|
24099
|
+
resolve();
|
|
24100
|
+
};
|
|
24101
|
+
target.addEventListener(eventName, on, {
|
|
24102
|
+
once: true,
|
|
24103
|
+
signal: ac.signal
|
|
24104
|
+
});
|
|
24105
|
+
});
|
|
24106
|
+
}
|
|
24107
|
+
_waitForMatchingEvent(eventName, matchesEventFn, recheckFn = null) {
|
|
24108
|
+
if (typeof recheckFn === 'function' && recheckFn()) {
|
|
24109
|
+
return Promise.resolve();
|
|
24110
|
+
}
|
|
24111
|
+
return new Promise(resolve => {
|
|
24112
|
+
const root = document;
|
|
24113
|
+
const cleanupAll = () => {
|
|
24114
|
+
root.removeEventListener(eventName, onEvent, true);
|
|
24115
|
+
if (mo) mo.disconnect();
|
|
24057
24116
|
};
|
|
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();
|
|
24117
|
+
const onEvent = ev => {
|
|
24118
|
+
if (matchesEventFn(ev)) {
|
|
24119
|
+
cleanupAll();
|
|
24067
24120
|
resolve();
|
|
24068
24121
|
}
|
|
24069
|
-
}
|
|
24070
|
-
|
|
24071
|
-
|
|
24072
|
-
|
|
24073
|
-
|
|
24122
|
+
};
|
|
24123
|
+
root.addEventListener(eventName, onEvent, true);
|
|
24124
|
+
|
|
24125
|
+
// Only used for `ready` (or any other gate that provides a recheck function)
|
|
24126
|
+
let mo = null;
|
|
24127
|
+
if (typeof recheckFn === 'function') {
|
|
24128
|
+
mo = new MutationObserver(() => {
|
|
24129
|
+
if (recheckFn()) {
|
|
24130
|
+
cleanupAll();
|
|
24131
|
+
resolve();
|
|
24132
|
+
}
|
|
24133
|
+
});
|
|
24134
|
+
mo.observe(document.documentElement, {
|
|
24135
|
+
childList: true,
|
|
24136
|
+
subtree: true
|
|
24137
|
+
});
|
|
24138
|
+
}
|
|
24074
24139
|
});
|
|
24075
|
-
|
|
24140
|
+
}
|
|
24141
|
+
_waitForInitGate({
|
|
24142
|
+
event,
|
|
24143
|
+
targetSpec
|
|
24144
|
+
}) {
|
|
24145
|
+
// Direct targets
|
|
24146
|
+
if (targetSpec === 'self') {
|
|
24147
|
+
const satisfied = event === 'ready' ? t => this._isReadyTarget(t) : null;
|
|
24148
|
+
return this._waitForEvent(this, event, satisfied);
|
|
24149
|
+
}
|
|
24150
|
+
if (targetSpec === 'document') {
|
|
24151
|
+
return this._waitForEvent(document, event);
|
|
24152
|
+
}
|
|
24153
|
+
if (targetSpec === 'window') {
|
|
24154
|
+
return this._waitForEvent(window, event);
|
|
24155
|
+
}
|
|
24156
|
+
|
|
24157
|
+
// Special: closest fx-fore
|
|
24158
|
+
if (targetSpec === 'closest') {
|
|
24159
|
+
const recheckFn = event === 'ready' ? () => this._isReadyTarget(this.closest('fx-fore')) : null;
|
|
24160
|
+
const matchesFn = ev => {
|
|
24161
|
+
const t = ev.target;
|
|
24162
|
+
return t?.tagName === 'FX-FORE' && t.contains(this);
|
|
24163
|
+
};
|
|
24164
|
+
return this._waitForMatchingEvent(event, matchesFn, recheckFn);
|
|
24165
|
+
}
|
|
24166
|
+
|
|
24167
|
+
// Selector targets
|
|
24168
|
+
const selector = targetSpec;
|
|
24169
|
+
const recheckFn = event === 'ready' ? () => this._isReadyTarget(this._findBySelector(selector)) : null;
|
|
24170
|
+
if (typeof recheckFn === 'function' && recheckFn()) {
|
|
24171
|
+
return Promise.resolve();
|
|
24172
|
+
}
|
|
24173
|
+
const matchesFn = ev => {
|
|
24174
|
+
// Prefer composedPath() so events coming from inside shadow DOM still match
|
|
24175
|
+
const path = typeof ev.composedPath === 'function' ? ev.composedPath() : [];
|
|
24176
|
+
for (const n of path) {
|
|
24177
|
+
if (n && n.matches && n.matches(selector)) return true;
|
|
24178
|
+
}
|
|
24179
|
+
const t = ev.target;
|
|
24180
|
+
return !!(t && t.closest && t.closest(selector));
|
|
24181
|
+
};
|
|
24182
|
+
return this._waitForMatchingEvent(event, matchesFn, recheckFn);
|
|
24183
|
+
}
|
|
24184
|
+
|
|
24185
|
+
/**
|
|
24186
|
+
* Wait until all configured init gates are satisfied.
|
|
24187
|
+
* This is the single consolidation point for init gating.
|
|
24188
|
+
*/
|
|
24189
|
+
_waitForInitGates() {
|
|
24190
|
+
if (this._initGatesPromise) return this._initGatesPromise;
|
|
24191
|
+
const gates = this._collectInitGates();
|
|
24192
|
+
if (!gates.length) {
|
|
24193
|
+
this._initGatesPromise = Promise.resolve();
|
|
24194
|
+
return this._initGatesPromise;
|
|
24195
|
+
}
|
|
24196
|
+
this._initGatesPromise = Promise.all(gates.map(g => this._waitForInitGate(g))).then(() => undefined);
|
|
24197
|
+
return this._initGatesPromise;
|
|
24198
|
+
}
|
|
24199
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
24200
|
+
if (oldValue === newValue) return;
|
|
24201
|
+
if (name === 'src') {
|
|
24202
|
+
this.src = newValue;
|
|
24203
|
+
if (!newValue) {
|
|
24204
|
+
// Reset so a later src assignment can load again
|
|
24205
|
+
this._srcLoadPromise = null;
|
|
24206
|
+
return;
|
|
24207
|
+
}
|
|
24208
|
+
if (this.isConnected) {
|
|
24209
|
+
this._maybeLoadFromSrc();
|
|
24210
|
+
}
|
|
24211
|
+
return;
|
|
24212
|
+
}
|
|
24213
|
+
if (name === 'selector') {
|
|
24214
|
+
// Selector changes should affect a pending src-load
|
|
24215
|
+
if (this.isConnected && this.src && !this._srcLoadPromise) {
|
|
24216
|
+
this._maybeLoadFromSrc();
|
|
24217
|
+
}
|
|
24218
|
+
}
|
|
24219
|
+
}
|
|
24220
|
+
_maybeLoadFromSrc() {
|
|
24221
|
+
if (!this.src) return null;
|
|
24222
|
+
if (this._srcLoadPromise) return this._srcLoadPromise;
|
|
24223
|
+
this._srcLoadPromise = (async () => {
|
|
24224
|
+
await this._waitForInitGates();
|
|
24225
|
+
if (!this.isConnected) return;
|
|
24226
|
+
const selector = this.getAttribute('selector') || 'fx-fore';
|
|
24227
|
+
await Fore.loadForeFromSrc(this, this.src, selector);
|
|
24228
|
+
})();
|
|
24229
|
+
return this._srcLoadPromise;
|
|
24076
24230
|
}
|
|
24077
24231
|
connectedCallback() {
|
|
24078
24232
|
const modelElement = Array.from(this.children).find(modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL');
|
|
@@ -24110,7 +24264,7 @@ class FxFore extends HTMLElement {
|
|
|
24110
24264
|
}
|
|
24111
24265
|
this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
|
|
24112
24266
|
if (this.src) {
|
|
24113
|
-
this.
|
|
24267
|
+
this._maybeLoadFromSrc();
|
|
24114
24268
|
return;
|
|
24115
24269
|
}
|
|
24116
24270
|
this._injectDevtools();
|
|
@@ -24186,15 +24340,7 @@ class FxFore extends HTMLElement {
|
|
|
24186
24340
|
* @private
|
|
24187
24341
|
*/
|
|
24188
24342
|
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
|
-
}
|
|
24343
|
+
return this._maybeLoadFromSrc();
|
|
24198
24344
|
}
|
|
24199
24345
|
|
|
24200
24346
|
/**
|
|
@@ -24489,7 +24635,7 @@ class FxFore extends HTMLElement {
|
|
|
24489
24635
|
}
|
|
24490
24636
|
// Templates are special: they use the namespace configuration from the place where they are
|
|
24491
24637
|
// being defined
|
|
24492
|
-
const instanceId = XPathUtil.getInstanceId(naked);
|
|
24638
|
+
const instanceId = XPathUtil.getInstanceId(naked, node);
|
|
24493
24639
|
|
|
24494
24640
|
// If there is an instance referred
|
|
24495
24641
|
const inst = instanceId ? this.getModel().getInstance(instanceId) : this.getModel().getDefaultInstance();
|
|
@@ -24969,11 +25115,13 @@ class FxFore extends HTMLElement {
|
|
|
24969
25115
|
let newElement;
|
|
24970
25116
|
if (ref.includes('/')) {
|
|
24971
25117
|
// multi-step ref expressions
|
|
24972
|
-
|
|
25118
|
+
const namespaceResolver = createNamespaceResolver(ref, this);
|
|
25119
|
+
newElement = XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
|
|
24973
25120
|
// console.log('new subtree', newElement);
|
|
24974
25121
|
return newElement;
|
|
24975
25122
|
} else {
|
|
24976
|
-
|
|
25123
|
+
const namespaceResolver = createNamespaceResolver(ref, this);
|
|
25124
|
+
return XPathUtil.createNodesFromXPath(ref, referenceNode.ownerDocument, this, namespaceResolver);
|
|
24977
25125
|
}
|
|
24978
25126
|
}
|
|
24979
25127
|
_handleDragStart(event) {
|
|
@@ -25132,6 +25280,7 @@ class FxFore extends HTMLElement {
|
|
|
25132
25280
|
}
|
|
25133
25281
|
FxFore.outermostHandler = null;
|
|
25134
25282
|
FxFore.draggedItem = null;
|
|
25283
|
+
FxFore._initEventState = new WeakMap();
|
|
25135
25284
|
if (!customElements.get('fx-fore')) {
|
|
25136
25285
|
customElements.define('fx-fore', FxFore);
|
|
25137
25286
|
}
|
|
@@ -25582,8 +25731,27 @@ class FxSubmission extends ForeElementMixin {
|
|
|
25582
25731
|
_handleResponse(data, resolvedUrl, contentType) {
|
|
25583
25732
|
// console.log('_handleResponse ', data);
|
|
25584
25733
|
|
|
25585
|
-
|
|
25734
|
+
this._getTargetInstance();
|
|
25586
25735
|
if (this.replace === 'instance') {
|
|
25736
|
+
const targetInstance = this._getTargetInstance();
|
|
25737
|
+
|
|
25738
|
+
// ### contentType handling
|
|
25739
|
+
|
|
25740
|
+
if (contentType.includes('html')) {
|
|
25741
|
+
let effectiveData;
|
|
25742
|
+
if (data.nodeType) {
|
|
25743
|
+
effectiveData = data;
|
|
25744
|
+
}
|
|
25745
|
+
// ## try parsing
|
|
25746
|
+
try {
|
|
25747
|
+
effectiveData = new DOMParser().parseFromString(data, 'text/html');
|
|
25748
|
+
} catch {
|
|
25749
|
+
Fore.dispatch(this, 'error', {
|
|
25750
|
+
message: 'could not parse data as HTML'
|
|
25751
|
+
});
|
|
25752
|
+
}
|
|
25753
|
+
targetInstance.instanceData = effectiveData;
|
|
25754
|
+
}
|
|
25587
25755
|
if (targetInstance) {
|
|
25588
25756
|
if (this.targetref) {
|
|
25589
25757
|
const [theTarget] = evaluateXPath(this.targetref, targetInstance.instanceData.firstElementChild, this);
|
|
@@ -26070,6 +26238,9 @@ class AbstractControl extends UIElement {
|
|
|
26070
26238
|
}
|
|
26071
26239
|
}
|
|
26072
26240
|
_toggleValid(valid) {
|
|
26241
|
+
// Used by required handling (and potentially other callers).
|
|
26242
|
+
// It must also fire validity events and sync aria-invalid.
|
|
26243
|
+
const wasInvalid = this.hasAttribute('invalid');
|
|
26073
26244
|
if (valid) {
|
|
26074
26245
|
this.removeAttribute('invalid');
|
|
26075
26246
|
this.setAttribute('valid', '');
|
|
@@ -26077,6 +26248,23 @@ class AbstractControl extends UIElement {
|
|
|
26077
26248
|
this.removeAttribute('valid');
|
|
26078
26249
|
this.setAttribute('invalid', '');
|
|
26079
26250
|
}
|
|
26251
|
+
this._syncAriaInvalid();
|
|
26252
|
+
const isInvalid = this.hasAttribute('invalid');
|
|
26253
|
+
// Only dispatch when the state actually changed
|
|
26254
|
+
if (wasInvalid !== isInvalid) {
|
|
26255
|
+
this._dispatchEvent(isInvalid ? 'invalid' : 'valid');
|
|
26256
|
+
}
|
|
26257
|
+
}
|
|
26258
|
+
_syncAriaInvalid() {
|
|
26259
|
+
// Keep widget aria-invalid in sync with the *control* state, regardless of
|
|
26260
|
+
// whether invalidity comes from constraint, required emptiness, etc.
|
|
26261
|
+
try {
|
|
26262
|
+
const w = this.getWidget?.() || this.widget;
|
|
26263
|
+
if (!w) return;
|
|
26264
|
+
w.setAttribute('aria-invalid', this.hasAttribute('invalid') ? 'true' : 'false');
|
|
26265
|
+
} catch (e) {
|
|
26266
|
+
// ignore: widget might not exist yet
|
|
26267
|
+
}
|
|
26080
26268
|
}
|
|
26081
26269
|
handleReadonly() {
|
|
26082
26270
|
// console.log('mip readonly', this.modelItem.isReadonly);
|
|
@@ -26104,8 +26292,8 @@ class AbstractControl extends UIElement {
|
|
|
26104
26292
|
// if (alert) alert.style.display = 'none';
|
|
26105
26293
|
this._dispatchEvent('valid');
|
|
26106
26294
|
this.setAttribute('valid', '');
|
|
26107
|
-
this.getWidget().setAttribute('aria-invalid', 'false');
|
|
26108
26295
|
this.removeAttribute('invalid');
|
|
26296
|
+
this.getWidget().setAttribute('aria-invalid', 'false');
|
|
26109
26297
|
} else {
|
|
26110
26298
|
this.setAttribute('invalid', '');
|
|
26111
26299
|
this.getWidget().setAttribute('aria-invalid', 'true');
|
|
@@ -26137,33 +26325,40 @@ class AbstractControl extends UIElement {
|
|
|
26137
26325
|
this._dispatchEvent('invalid');
|
|
26138
26326
|
}
|
|
26139
26327
|
}
|
|
26328
|
+
|
|
26329
|
+
// Ensure aria-invalid matches the current control state even if
|
|
26330
|
+
// we didn't enter the state-change branch above.
|
|
26331
|
+
this._syncAriaInvalid();
|
|
26140
26332
|
}
|
|
26141
26333
|
handleRelevant() {
|
|
26142
|
-
//
|
|
26334
|
+
// IMPORTANT: don't clear relevant/nonrelevant BEFORE comparing states.
|
|
26335
|
+
// Otherwise isEnabled() (based on attributes) always reads as "enabled"
|
|
26336
|
+
// and we can never detect a transition back to relevant.
|
|
26143
26337
|
const item = this.modelItem.node;
|
|
26144
|
-
this.
|
|
26145
|
-
|
|
26338
|
+
const wasEnabled = this.isEnabled();
|
|
26339
|
+
|
|
26340
|
+
// Determine new enabled state
|
|
26341
|
+
let newEnabled = !!this.modelItem.relevant;
|
|
26342
|
+
|
|
26343
|
+
// If a nodeset resolves to an empty array, treat the control as nonrelevant
|
|
26146
26344
|
if (Array.isArray(item) && item.length === 0) {
|
|
26147
|
-
|
|
26345
|
+
newEnabled = false;
|
|
26346
|
+
}
|
|
26347
|
+
|
|
26348
|
+
// Apply attributes
|
|
26349
|
+
if (newEnabled) {
|
|
26350
|
+
this.setAttribute('relevant', '');
|
|
26351
|
+
this.removeAttribute('nonrelevant');
|
|
26352
|
+
} else {
|
|
26148
26353
|
this.setAttribute('nonrelevant', '');
|
|
26149
|
-
|
|
26150
|
-
return;
|
|
26354
|
+
this.removeAttribute('relevant');
|
|
26151
26355
|
}
|
|
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
|
-
}
|
|
26356
|
+
|
|
26357
|
+
// Dispatch only on actual change
|
|
26358
|
+
if (wasEnabled !== newEnabled) {
|
|
26359
|
+
this._dispatchEvent(newEnabled ? 'relevant' : 'nonrelevant');
|
|
26164
26360
|
}
|
|
26165
26361
|
}
|
|
26166
|
-
|
|
26167
26362
|
isRequired() {
|
|
26168
26363
|
return this.hasAttribute('required');
|
|
26169
26364
|
}
|
|
@@ -30212,7 +30407,7 @@ class FxActionLog extends HTMLElement {
|
|
|
30212
30407
|
*/
|
|
30213
30408
|
_logDetails(e) {
|
|
30214
30409
|
const eventType = e.type;
|
|
30215
|
-
const path =
|
|
30410
|
+
const path = getPath(e.target, '');
|
|
30216
30411
|
// console.log('>>>> _logDetails', path);
|
|
30217
30412
|
const cut = path.substring(path.indexOf('/fx-fore'), path.length);
|
|
30218
30413
|
const xpath = `/${cut}`;
|