jtlt 0.18.0 → 0.19.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/CHANGES.md +4 -0
- package/README.md +75 -0
- package/demo/vendor/jamilih/dist/jml.mjs +898 -25
- package/dist/DOMJoiningTransformer.d.ts +73 -25
- package/dist/DOMJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONJoiningTransformer.d.ts +5 -3
- package/dist/JSONJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +59 -19
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/StringJoiningTransformer.d.ts +8 -3
- package/dist/StringJoiningTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/index.d.ts +104 -261
- package/dist/index.d.ts.map +1 -1
- package/dist/jsonTemplate.d.ts +37 -0
- package/dist/jsonTemplate.d.ts.map +1 -0
- package/docs/API.expanded.md +75 -1
- package/docs/API.md +20 -0
- package/docs/TO-DO.md +10 -4
- package/package.json +6 -6
- package/pnpm-workspace.yaml +4 -1
- package/src/DOMJoiningTransformer.js +37 -11
- package/src/JSONJoiningTransformer.js +94 -67
- package/src/JSONPathTransformer.js +40 -10
- package/src/JSONPathTransformerContext.js +217 -46
- package/src/StringJoiningTransformer.js +35 -9
- package/src/XPathTransformerContext.js +25 -1
- package/src/index.js +115 -4
- package/src/jsonTemplate.js +410 -0
|
@@ -99,6 +99,10 @@ const escapeRegexReplacement = (string) => {
|
|
|
99
99
|
* Priority resolver function
|
|
100
100
|
* @property {import('./index.js').JSONPathTemplateObject<T>[]|
|
|
101
101
|
* import('./index.js').JSONPathTemplateArray<T>[]} [templates]
|
|
102
|
+
* @property {'json'|'javascript'} [defaultTemplateFormat] Config-wide
|
|
103
|
+
* default for the jamilih validation strictness `compileJSONTemplate`
|
|
104
|
+
* applies to a declarative (Array) `template`, used when an entry has no
|
|
105
|
+
* `format` of its own
|
|
102
106
|
* @property {Record<string, unknown>} [params] Runtime parameter values
|
|
103
107
|
* (like an XSLT processor's stylesheet parameters); a `param()` with a
|
|
104
108
|
* matching name uses this value instead of its declared default
|
|
@@ -609,26 +613,40 @@ class JSONPathTransformerContext {
|
|
|
609
613
|
that._parent = parent;
|
|
610
614
|
that._parentProperty = (parentProperty ?? that._parentProperty);
|
|
611
615
|
|
|
612
|
-
// Set up parameter context for valueOf() access in templates
|
|
616
|
+
// Set up parameter context for valueOf() access in templates. `vars`
|
|
617
|
+
// is reset too — matching XSLT's `xsl:variable`/`xsl:param` scoping,
|
|
618
|
+
// a `variable()` set in the calling template must not leak into (or
|
|
619
|
+
// be leaked into by) a separately applied/matched template.
|
|
613
620
|
const prevTemplateParams = that._params;
|
|
621
|
+
const prevTemplateVars = that.vars;
|
|
614
622
|
that._params = {0: value, ...appliedParams};
|
|
615
|
-
|
|
623
|
+
that.vars = {};
|
|
624
|
+
|
|
625
|
+
// `template` is always a function here: a declarative (jamilih-shaped)
|
|
626
|
+
// Array is compiled to one up front, by JSONPathTransformer's
|
|
627
|
+
// constructor.
|
|
628
|
+
const {template: matchedTemplateFn} =
|
|
629
|
+
/**
|
|
630
|
+
* @type {import('./index.js').JSONPathTemplateObject<T> &
|
|
631
|
+
* {template: import('./index.js').
|
|
632
|
+
* TemplateFunction<T, "json", JSONPathTransformerContext<T>>}}
|
|
633
|
+
*/ (
|
|
634
|
+
templateObj
|
|
635
|
+
);
|
|
616
636
|
/**
|
|
617
637
|
* The template may return synchronously or return a Promise (e.g. from
|
|
618
638
|
* `await this.indexedDB(...)`), which is awaited unless `config.sync`.
|
|
619
639
|
* @type {any}
|
|
620
640
|
*/
|
|
621
|
-
const ret =
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
)
|
|
625
|
-
|
|
626
|
-
// `ContextExtensions` augmentation would otherwise reject `that`.
|
|
627
|
-
/** @type {any} */ (that), value, {mode, parent, parentProperty}
|
|
628
|
-
);
|
|
641
|
+
const ret = matchedTemplateFn.call(
|
|
642
|
+
// `this` carries runtime `extensions`; a consumer's
|
|
643
|
+
// `ContextExtensions` augmentation would otherwise reject `that`.
|
|
644
|
+
/** @type {any} */ (that), value, {mode, parent, parentProperty}
|
|
645
|
+
);
|
|
629
646
|
|
|
630
|
-
// Restore previous parameter context
|
|
647
|
+
// Restore previous parameter/variable context
|
|
631
648
|
that._params = prevTemplateParams;
|
|
649
|
+
that.vars = prevTemplateVars;
|
|
632
650
|
if (ret !== null && typeof ret !== 'undefined' &&
|
|
633
651
|
typeof ret.then === 'function') {
|
|
634
652
|
if (that._config.sync) {
|
|
@@ -643,6 +661,7 @@ class JSONPathTransformerContext {
|
|
|
643
661
|
// eslint-disable-next-line promise/prefer-await-to-then, consistent-return -- intentional dynamic sync/async
|
|
644
662
|
return ret.then((/** @type {any} */ resolvedRet) => {
|
|
645
663
|
that._params = prevTemplateParams;
|
|
664
|
+
that.vars = prevTemplateVars;
|
|
646
665
|
if (typeof resolvedRet !== 'undefined') {
|
|
647
666
|
const joiner = that._getJoiningTransformer();
|
|
648
667
|
/* c8 ignore start -- _openTagState only on
|
|
@@ -726,11 +745,16 @@ class JSONPathTransformerContext {
|
|
|
726
745
|
}
|
|
727
746
|
withParams ||= [];
|
|
728
747
|
|
|
729
|
-
// Store parameters in a temporary context for valueOf() access
|
|
748
|
+
// Store parameters in a temporary context for valueOf() access. `vars`
|
|
749
|
+
// is reset too, matching XSLT's `xsl:variable`/`xsl:param` scoping: a
|
|
750
|
+
// called template gets a fresh scope, not the caller's `variable()`
|
|
751
|
+
// values, and its own don't leak back once it returns.
|
|
730
752
|
const prevParams = this._params;
|
|
753
|
+
const prevVars = this.vars;
|
|
731
754
|
/** @type {Record<string, any>} */
|
|
732
755
|
const params = {};
|
|
733
756
|
this._params = params;
|
|
757
|
+
this.vars = {};
|
|
734
758
|
|
|
735
759
|
// Params staged via `this.withParam()` seed the set; explicit `withParam`
|
|
736
760
|
// entries below override any of the same name (`xsl:with-param`).
|
|
@@ -759,7 +783,16 @@ class JSONPathTransformerContext {
|
|
|
759
783
|
);
|
|
760
784
|
}
|
|
761
785
|
|
|
762
|
-
|
|
786
|
+
// `template` is always a function here: a declarative (jamilih-shaped)
|
|
787
|
+
// Array is compiled to one up front, by JSONPathTransformer's
|
|
788
|
+
// constructor.
|
|
789
|
+
const {template: namedTemplateFn} =
|
|
790
|
+
/**
|
|
791
|
+
* @type {import('./index.js').JSONPathTemplateObject<T> &
|
|
792
|
+
* {template: import('./index.js').
|
|
793
|
+
* TemplateFunction<T, "json", JSONPathTransformerContext<T>>}}
|
|
794
|
+
*/ (templateObj);
|
|
795
|
+
const result = namedTemplateFn.call(
|
|
763
796
|
// `this` carries runtime `extensions`; a consumer's `ContextExtensions`
|
|
764
797
|
// augmentation would otherwise reject the bare context.
|
|
765
798
|
/** @type {any} */ (this), this._contextObj, {}
|
|
@@ -768,8 +801,9 @@ class JSONPathTransformerContext {
|
|
|
768
801
|
/** @type {any} */ (results).append(result);
|
|
769
802
|
}
|
|
770
803
|
|
|
771
|
-
// Restore previous parameter context
|
|
804
|
+
// Restore previous parameter/variable context
|
|
772
805
|
this._params = prevParams;
|
|
806
|
+
this.vars = prevVars;
|
|
773
807
|
|
|
774
808
|
return this;
|
|
775
809
|
}
|
|
@@ -781,21 +815,38 @@ class JSONPathTransformerContext {
|
|
|
781
815
|
* @param {string} select - JSONPath selector
|
|
782
816
|
* @param {(this: JSONPathTransformerContext<T>,
|
|
783
817
|
* value: unknown
|
|
784
|
-
* ) => void} cb - Callback function
|
|
818
|
+
* ) => void} cb - Callback function; may be async, in which case
|
|
819
|
+
* `forEach()` itself returns a `Promise<this>` instead of `this` from
|
|
820
|
+
* the iteration where that first happens onward. (Typed as plain
|
|
821
|
+
* `void`, not `void|Promise<void>` — see the note on `SimpleCallback`
|
|
822
|
+
* in JSONJoiningTransformer.js.)
|
|
785
823
|
* @param {SortSpec<V>} [sort] - Sort spec
|
|
786
|
-
* @returns {this}
|
|
824
|
+
* @returns {this|Promise<this>}
|
|
787
825
|
*/
|
|
788
826
|
forEach (select, cb, sort) {
|
|
789
827
|
// eslint-disable-next-line unicorn/no-this-assignment -- Temporary
|
|
790
828
|
const that = this;
|
|
829
|
+
// A bare `$name` (matching `if()`/`valueOf()`/comparisons' own
|
|
830
|
+
// convention — no trailing path) resolves against the param/var scope
|
|
831
|
+
// and iterates its value directly: jsonpath-plus has no notion of a
|
|
832
|
+
// named root to continue a path from (`$name[*]` is not `$.name[*]`),
|
|
833
|
+
// and XPath's data model treats a non-array value as a length-1
|
|
834
|
+
// sequence, so a scalar/object here becomes a single iteration.
|
|
835
|
+
const paramRef = select.trim().match(/^\$(?<name>[\w\-]+)$/v);
|
|
836
|
+
const param = paramRef && paramRef.groups
|
|
837
|
+
? this._lookupParam(paramRef.groups.name)
|
|
838
|
+
: {has: false, value: undefined};
|
|
791
839
|
/** @type {{value: any}[]} */
|
|
792
|
-
const matches =
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
840
|
+
const matches = param.has
|
|
841
|
+
? (Array.isArray(param.value) ? param.value : [param.value]).
|
|
842
|
+
map((value) => ({value}))
|
|
843
|
+
: /** @type {any} */ (jsonpath)({
|
|
844
|
+
path: select,
|
|
845
|
+
json: this._contextObj,
|
|
846
|
+
preventEval: this._config.preventEval,
|
|
847
|
+
wrap: true,
|
|
848
|
+
resultType: 'all'
|
|
849
|
+
});
|
|
799
850
|
|
|
800
851
|
/**
|
|
801
852
|
* @param {string} expr
|
|
@@ -896,19 +947,70 @@ class JSONPathTransformerContext {
|
|
|
896
947
|
|
|
897
948
|
const comparator = feBuildComparator(sort);
|
|
898
949
|
const list = comparator ? [...matches].toSorted(comparator) : matches;
|
|
899
|
-
for (const m of list) {
|
|
900
|
-
// Set up parameter context for valueOf() access
|
|
950
|
+
for (const [idx, m] of list.entries()) {
|
|
951
|
+
// Set up parameter context for valueOf() access. `vars` gets a fresh
|
|
952
|
+
// scope per iteration too, matching `xsl:for-each`: a `variable()` set
|
|
953
|
+
// for one item must not leak into (or be seen by) the next.
|
|
901
954
|
const prevParams = that._params;
|
|
902
955
|
const prevContext = that._contextObj;
|
|
956
|
+
const prevVars = that.vars;
|
|
903
957
|
that._params = {0: m.value};
|
|
904
958
|
that._contextObj = m.value;
|
|
959
|
+
that.vars = {};
|
|
960
|
+
// `cb`'s declared return type is plain `void` (see the parameter's
|
|
961
|
+
// JSDoc) — cast here to duck-type the real value; see the note on
|
|
962
|
+
// `SimpleCallback` in JSONJoiningTransformer.js.
|
|
963
|
+
/** @type {any} */
|
|
964
|
+
let cbResult;
|
|
905
965
|
try {
|
|
906
|
-
cb.call(that, m.value);
|
|
907
|
-
}
|
|
908
|
-
// Restore previous parameter context
|
|
966
|
+
cbResult = cb.call(that, m.value);
|
|
967
|
+
} catch (err) {
|
|
909
968
|
that._params = prevParams;
|
|
910
969
|
that._contextObj = prevContext;
|
|
970
|
+
that.vars = prevVars;
|
|
971
|
+
throw err;
|
|
911
972
|
}
|
|
973
|
+
if (cbResult && typeof cbResult.then === 'function') {
|
|
974
|
+
// `cb` turned out to be async (e.g. it awaits a `$indexedDB`
|
|
975
|
+
// fetch): finish the remaining items sequentially, awaiting each,
|
|
976
|
+
// rather than the plain synchronous loop above — which is used for
|
|
977
|
+
// as long as every `cb` call keeps returning synchronously.
|
|
978
|
+
const remaining = list.slice(idx + 1);
|
|
979
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- see above
|
|
980
|
+
return cbResult.then(async () => {
|
|
981
|
+
that._params = prevParams;
|
|
982
|
+
that._contextObj = prevContext;
|
|
983
|
+
that.vars = prevVars;
|
|
984
|
+
for (const next of remaining) {
|
|
985
|
+
const pp = that._params;
|
|
986
|
+
const pc = that._contextObj;
|
|
987
|
+
const pv = that.vars;
|
|
988
|
+
that._params = {0: next.value};
|
|
989
|
+
that._contextObj = next.value;
|
|
990
|
+
that.vars = {};
|
|
991
|
+
try {
|
|
992
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential order
|
|
993
|
+
await cb.call(that, next.value);
|
|
994
|
+
// eslint-disable-next-line promise/always-return -- see above
|
|
995
|
+
} finally {
|
|
996
|
+
that._params = pp;
|
|
997
|
+
that._contextObj = pc;
|
|
998
|
+
that.vars = pv;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return that;
|
|
1002
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- see above
|
|
1003
|
+
}).catch((/** @type {any} */ err) => {
|
|
1004
|
+
that._params = prevParams;
|
|
1005
|
+
that._contextObj = prevContext;
|
|
1006
|
+
that.vars = prevVars;
|
|
1007
|
+
throw err;
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
// Restore previous parameter/variable context
|
|
1011
|
+
that._params = prevParams;
|
|
1012
|
+
that._contextObj = prevContext;
|
|
1013
|
+
that.vars = prevVars;
|
|
912
1014
|
}
|
|
913
1015
|
return this;
|
|
914
1016
|
}
|
|
@@ -1000,6 +1102,8 @@ class JSONPathTransformerContext {
|
|
|
1000
1102
|
const actualKey = key === null && keyStr === 'null' ? undefined : key;
|
|
1001
1103
|
const prevContext = this._contextObj;
|
|
1002
1104
|
const prevParams = this._params;
|
|
1105
|
+
const prevVars = this.vars;
|
|
1106
|
+
this.vars = {};
|
|
1003
1107
|
try {
|
|
1004
1108
|
this._contextObj = items;
|
|
1005
1109
|
// Provide currentGroup() and currentGroupingKey() via context
|
|
@@ -1009,6 +1113,7 @@ class JSONPathTransformerContext {
|
|
|
1009
1113
|
} finally {
|
|
1010
1114
|
this._contextObj = prevContext;
|
|
1011
1115
|
this._params = prevParams;
|
|
1116
|
+
this.vars = prevVars;
|
|
1012
1117
|
delete /** @type {any} */ (this)._currentGroup;
|
|
1013
1118
|
delete /** @type {any} */ (this)._currentGroupingKey;
|
|
1014
1119
|
}
|
|
@@ -1027,6 +1132,8 @@ class JSONPathTransformerContext {
|
|
|
1027
1132
|
if (currentGroup.length > 0) {
|
|
1028
1133
|
const prevContext = this._contextObj;
|
|
1029
1134
|
const prevParams = this._params;
|
|
1135
|
+
const prevVars = this.vars;
|
|
1136
|
+
this.vars = {};
|
|
1030
1137
|
try {
|
|
1031
1138
|
this._contextObj = currentGroup;
|
|
1032
1139
|
/** @type {any} */ (this)._currentGroup = currentGroup;
|
|
@@ -1041,6 +1148,7 @@ class JSONPathTransformerContext {
|
|
|
1041
1148
|
} finally {
|
|
1042
1149
|
this._contextObj = prevContext;
|
|
1043
1150
|
this._params = prevParams;
|
|
1151
|
+
this.vars = prevVars;
|
|
1044
1152
|
delete /** @type {any} */ (this)._currentGroup;
|
|
1045
1153
|
delete /** @type {any} */ (this)._currentGroupingKey;
|
|
1046
1154
|
}
|
|
@@ -1056,6 +1164,8 @@ class JSONPathTransformerContext {
|
|
|
1056
1164
|
if (currentGroup.length > 0) {
|
|
1057
1165
|
const prevContext = this._contextObj;
|
|
1058
1166
|
const prevParams = this._params;
|
|
1167
|
+
const prevVars = this.vars;
|
|
1168
|
+
this.vars = {};
|
|
1059
1169
|
try {
|
|
1060
1170
|
this._contextObj = currentGroup;
|
|
1061
1171
|
/** @type {any} */ (this)._currentGroup = currentGroup;
|
|
@@ -1070,6 +1180,7 @@ class JSONPathTransformerContext {
|
|
|
1070
1180
|
} finally {
|
|
1071
1181
|
this._contextObj = prevContext;
|
|
1072
1182
|
this._params = prevParams;
|
|
1183
|
+
this.vars = prevVars;
|
|
1073
1184
|
delete /** @type {any} */ (this)._currentGroup;
|
|
1074
1185
|
delete /** @type {any} */ (this)._currentGroupingKey;
|
|
1075
1186
|
}
|
|
@@ -1084,6 +1195,8 @@ class JSONPathTransformerContext {
|
|
|
1084
1195
|
if (startMatch && currentGroup.length > 0) {
|
|
1085
1196
|
const prevContext = this._contextObj;
|
|
1086
1197
|
const prevParams = this._params;
|
|
1198
|
+
const prevVars = this.vars;
|
|
1199
|
+
this.vars = {};
|
|
1087
1200
|
try {
|
|
1088
1201
|
this._contextObj = currentGroup;
|
|
1089
1202
|
/** @type {any} */ (this)._currentGroup = currentGroup;
|
|
@@ -1091,6 +1204,7 @@ class JSONPathTransformerContext {
|
|
|
1091
1204
|
} finally {
|
|
1092
1205
|
this._contextObj = prevContext;
|
|
1093
1206
|
this._params = prevParams;
|
|
1207
|
+
this.vars = prevVars;
|
|
1094
1208
|
delete /** @type {any} */ (this)._currentGroup;
|
|
1095
1209
|
}
|
|
1096
1210
|
currentGroup = [];
|
|
@@ -1102,6 +1216,8 @@ class JSONPathTransformerContext {
|
|
|
1102
1216
|
if (currentGroup.length > 0) {
|
|
1103
1217
|
const prevContext = this._contextObj;
|
|
1104
1218
|
const prevParams = this._params;
|
|
1219
|
+
const prevVars = this.vars;
|
|
1220
|
+
this.vars = {};
|
|
1105
1221
|
try {
|
|
1106
1222
|
this._contextObj = currentGroup;
|
|
1107
1223
|
/** @type {any} */ (this)._currentGroup = currentGroup;
|
|
@@ -1109,6 +1225,7 @@ class JSONPathTransformerContext {
|
|
|
1109
1225
|
} finally {
|
|
1110
1226
|
this._contextObj = prevContext;
|
|
1111
1227
|
this._params = prevParams;
|
|
1228
|
+
this.vars = prevVars;
|
|
1112
1229
|
delete /** @type {any} */ (this)._currentGroup;
|
|
1113
1230
|
}
|
|
1114
1231
|
}
|
|
@@ -1123,6 +1240,8 @@ class JSONPathTransformerContext {
|
|
|
1123
1240
|
if (endMatch) {
|
|
1124
1241
|
const prevContext = this._contextObj;
|
|
1125
1242
|
const prevParams = this._params;
|
|
1243
|
+
const prevVars = this.vars;
|
|
1244
|
+
this.vars = {};
|
|
1126
1245
|
try {
|
|
1127
1246
|
this._contextObj = currentGroup;
|
|
1128
1247
|
/** @type {any} */ (this)._currentGroup = currentGroup;
|
|
@@ -1130,6 +1249,7 @@ class JSONPathTransformerContext {
|
|
|
1130
1249
|
} finally {
|
|
1131
1250
|
this._contextObj = prevContext;
|
|
1132
1251
|
this._params = prevParams;
|
|
1252
|
+
this.vars = prevVars;
|
|
1133
1253
|
delete /** @type {any} */ (this)._currentGroup;
|
|
1134
1254
|
}
|
|
1135
1255
|
currentGroup = [];
|
|
@@ -1140,6 +1260,8 @@ class JSONPathTransformerContext {
|
|
|
1140
1260
|
if (currentGroup.length > 0) {
|
|
1141
1261
|
const prevContext = this._contextObj;
|
|
1142
1262
|
const prevParams = this._params;
|
|
1263
|
+
const prevVars = this.vars;
|
|
1264
|
+
this.vars = {};
|
|
1143
1265
|
try {
|
|
1144
1266
|
this._contextObj = currentGroup;
|
|
1145
1267
|
/** @type {any} */ (this)._currentGroup = currentGroup;
|
|
@@ -1147,6 +1269,7 @@ class JSONPathTransformerContext {
|
|
|
1147
1269
|
} finally {
|
|
1148
1270
|
this._contextObj = prevContext;
|
|
1149
1271
|
this._params = prevParams;
|
|
1272
|
+
this.vars = prevVars;
|
|
1150
1273
|
delete /** @type {any} */ (this)._currentGroup;
|
|
1151
1274
|
}
|
|
1152
1275
|
}
|
|
@@ -1645,12 +1768,18 @@ class JSONPathTransformerContext {
|
|
|
1645
1768
|
}
|
|
1646
1769
|
|
|
1647
1770
|
/**
|
|
1771
|
+
* Bind a variable, equivalent to `xsl:variable`. Accepts the same default/
|
|
1772
|
+
* value forms as `param()`/`withParam()`: a bare string (a JSONPath
|
|
1773
|
+
* expression), an explicit `{select}`, or a literal `{value}` — the last
|
|
1774
|
+
* for binding an already-computed value (e.g. `this.indexedDB(...)`'s
|
|
1775
|
+
* result) directly, with no selector round-trip.
|
|
1648
1776
|
* @param {string} name - Variable name
|
|
1649
|
-
* @param {string} select - JSONPath
|
|
1777
|
+
* @param {string|{select: string}|{value: unknown}} select - A JSONPath
|
|
1778
|
+
* expression string, an explicit `{select}`, or a literal `{value}`.
|
|
1650
1779
|
* @returns {this}
|
|
1651
1780
|
*/
|
|
1652
1781
|
variable (name, select) {
|
|
1653
|
-
this.vars[name] = this.
|
|
1782
|
+
this.vars[name] = this._resolveParam(this._paramSpec(select));
|
|
1654
1783
|
return this;
|
|
1655
1784
|
}
|
|
1656
1785
|
|
|
@@ -1675,9 +1804,12 @@ class JSONPathTransformerContext {
|
|
|
1675
1804
|
}
|
|
1676
1805
|
|
|
1677
1806
|
/**
|
|
1678
|
-
* Look up a parameter by name across the active with-param scope
|
|
1679
|
-
* runtime `config.params`, so
|
|
1680
|
-
*
|
|
1807
|
+
* Look up a parameter by name across the active with-param scope, any
|
|
1808
|
+
* `variable()`-set value, and the runtime `config.params`, so a bare
|
|
1809
|
+
* `$name` reference resolves the same way from `if()`/`choose()`/
|
|
1810
|
+
* comparisons/`valueOf()` regardless of which of those set it. Runtime
|
|
1811
|
+
* params act like XSLT global parameters (visible to every template and
|
|
1812
|
+
* expression); `vars` is more local, matching `xsl:variable` scoping.
|
|
1681
1813
|
* @param {string} name
|
|
1682
1814
|
* @returns {{has: boolean, value: any}}
|
|
1683
1815
|
* @private
|
|
@@ -1686,6 +1818,9 @@ class JSONPathTransformerContext {
|
|
|
1686
1818
|
if (this._params && Object.hasOwn(this._params, name)) {
|
|
1687
1819
|
return {has: true, value: this._params[name]};
|
|
1688
1820
|
}
|
|
1821
|
+
if (Object.hasOwn(this.vars, name)) {
|
|
1822
|
+
return {has: true, value: this.vars[name]};
|
|
1823
|
+
}
|
|
1689
1824
|
if (Object.hasOwn(this._runtimeParams, name)) {
|
|
1690
1825
|
return {has: true, value: this._runtimeParams[name]};
|
|
1691
1826
|
}
|
|
@@ -2328,13 +2463,17 @@ class JSONPathTransformerContext {
|
|
|
2328
2463
|
});
|
|
2329
2464
|
|
|
2330
2465
|
// Evaluate the JSONPath expression with bound variables
|
|
2331
|
-
// Temporarily set _params so expressions can access them
|
|
2466
|
+
// Temporarily set _params so expressions can access them; `vars` is
|
|
2467
|
+
// scoped fresh too, matching a called `xsl:function`'s own scope.
|
|
2332
2468
|
const oldParams = this._params;
|
|
2469
|
+
const oldVars = this.vars;
|
|
2333
2470
|
this._params = variables;
|
|
2471
|
+
this.vars = {};
|
|
2334
2472
|
try {
|
|
2335
2473
|
return this.get(sequence, false);
|
|
2336
2474
|
} finally {
|
|
2337
2475
|
this._params = oldParams;
|
|
2476
|
+
this.vars = oldVars;
|
|
2338
2477
|
}
|
|
2339
2478
|
}
|
|
2340
2479
|
: body;
|
|
@@ -2370,15 +2509,24 @@ class JSONPathTransformerContext {
|
|
|
2370
2509
|
* @param {any[]|
|
|
2371
2510
|
* ((this: JSONPathTransformerContext<T>) => void)} [children] -
|
|
2372
2511
|
* Child nodes or callback
|
|
2373
|
-
* @param {(this: JSONPathTransformerContext<T>) => void
|
|
2374
|
-
* Callback function
|
|
2512
|
+
* @param {(this: JSONPathTransformerContext<T>) => void
|
|
2513
|
+
* } [cb] - Callback function; may be async (e.g. to `await` a
|
|
2514
|
+
* `$indexedDB` fetch), in which case `element()` itself returns a
|
|
2515
|
+
* `Promise<this>` instead of `this` — check for `.then` (or `await`)
|
|
2516
|
+
* rather than assuming a synchronous return. (Typed as plain `void`,
|
|
2517
|
+
* not `void|Promise<void>` — see the note on `SimpleCallback` in
|
|
2518
|
+
* JSONJoiningTransformer.js.)
|
|
2375
2519
|
* @param {string[]} [useAttributeSets] - Attribute set names to apply
|
|
2376
|
-
* @returns {this}
|
|
2520
|
+
* @returns {this|Promise<this>}
|
|
2377
2521
|
*/
|
|
2378
2522
|
element (name, atts, children, cb, useAttributeSets) {
|
|
2379
|
-
/** @type {any} */ (this._getJoiningTransformer()).element(
|
|
2523
|
+
const ret = /** @type {any} */ (this._getJoiningTransformer()).element(
|
|
2380
2524
|
name, atts, children, cb, useAttributeSets
|
|
2381
2525
|
);
|
|
2526
|
+
if (ret && typeof ret.then === 'function') {
|
|
2527
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- Not async
|
|
2528
|
+
return ret.then(() => this);
|
|
2529
|
+
}
|
|
2382
2530
|
return this;
|
|
2383
2531
|
}
|
|
2384
2532
|
|
|
@@ -2569,13 +2717,23 @@ class JSONPathTransformerContext {
|
|
|
2569
2717
|
*
|
|
2570
2718
|
* @param {string} select - JSONPath selector expression
|
|
2571
2719
|
* @param {(this: JSONPathTransformerContext<T>)
|
|
2572
|
-
* => void} cb - Callback to invoke if condition is met
|
|
2573
|
-
*
|
|
2720
|
+
* => void} cb - Callback to invoke if condition is met; may be async,
|
|
2721
|
+
* in which case `if()` itself returns a `Promise<this>` instead of
|
|
2722
|
+
* `this`. (Typed as plain `void`, not `void|Promise<void>` — see the
|
|
2723
|
+
* note on `SimpleCallback` in JSONJoiningTransformer.js.)
|
|
2724
|
+
* @returns {this|Promise<this>}
|
|
2574
2725
|
*/
|
|
2575
2726
|
if (select, cb) {
|
|
2576
2727
|
const passes = this._passesIf(select);
|
|
2577
2728
|
if (passes && typeof cb === 'function') {
|
|
2578
|
-
cb
|
|
2729
|
+
// `cb`'s declared return type is plain `void` — cast here to
|
|
2730
|
+
// duck-type the real value; see the note on `SimpleCallback` in
|
|
2731
|
+
// JSONJoiningTransformer.js.
|
|
2732
|
+
const ret = /** @type {any} */ (cb.call(this));
|
|
2733
|
+
if (ret && typeof ret.then === 'function') {
|
|
2734
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- Not async
|
|
2735
|
+
return ret.then(() => this);
|
|
2736
|
+
}
|
|
2579
2737
|
}
|
|
2580
2738
|
return this;
|
|
2581
2739
|
}
|
|
@@ -2749,19 +2907,32 @@ class JSONPathTransformerContext {
|
|
|
2749
2907
|
* when the test does not pass (similar to xsl:choose/xsl:otherwise).
|
|
2750
2908
|
* @param {string} select JSONPath selector
|
|
2751
2909
|
* @param {(this: JSONPathTransformerContext<T>)
|
|
2752
|
-
* => void} whenCb Callback when condition passes
|
|
2910
|
+
* => void} whenCb Callback when condition passes; may be async, in
|
|
2911
|
+
* which case `choose()` itself returns a `Promise<this>` instead of
|
|
2912
|
+
* `this`. (Typed as plain `void`, not `void|Promise<void>` — see the
|
|
2913
|
+
* note on `SimpleCallback` in JSONJoiningTransformer.js.)
|
|
2753
2914
|
* @param {(this: JSONPathTransformerContext<T>)
|
|
2754
|
-
* => void} [otherwiseCb] Callback when condition fails
|
|
2755
|
-
*
|
|
2915
|
+
* => void} [otherwiseCb] Callback when condition fails; may likewise
|
|
2916
|
+
* be async.
|
|
2917
|
+
* @returns {this|Promise<this>}
|
|
2756
2918
|
*/
|
|
2757
2919
|
choose (select, whenCb, otherwiseCb) {
|
|
2758
2920
|
const passes = this._passesIf(select);
|
|
2921
|
+
// `whenCb`/`otherwiseCb`'s declared return type is plain `void` — cast
|
|
2922
|
+
// here to duck-type the real value; see the note on `SimpleCallback`
|
|
2923
|
+
// in JSONJoiningTransformer.js.
|
|
2924
|
+
/** @type {any} */
|
|
2925
|
+
let ret;
|
|
2759
2926
|
if (passes) {
|
|
2760
2927
|
if (typeof whenCb === 'function') {
|
|
2761
|
-
whenCb.call(this);
|
|
2928
|
+
ret = whenCb.call(this);
|
|
2762
2929
|
}
|
|
2763
2930
|
} else if (typeof otherwiseCb === 'function') {
|
|
2764
|
-
otherwiseCb.call(this);
|
|
2931
|
+
ret = otherwiseCb.call(this);
|
|
2932
|
+
}
|
|
2933
|
+
if (ret && typeof ret.then === 'function') {
|
|
2934
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- Not async
|
|
2935
|
+
return ret.then(() => this);
|
|
2765
2936
|
}
|
|
2766
2937
|
return this;
|
|
2767
2938
|
}
|
|
@@ -391,6 +391,12 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
391
391
|
if (_oldStrTemp !== undefined) {
|
|
392
392
|
this._strTemp = (this._strTemp || '') + str;
|
|
393
393
|
} else {
|
|
394
|
+
// Close any open parent start tag before appending content, matching
|
|
395
|
+
// text()'s behavior — string() is content too, not an attribute value.
|
|
396
|
+
if (this._openTagState) {
|
|
397
|
+
this.append('>');
|
|
398
|
+
this._openTagState = false;
|
|
399
|
+
}
|
|
394
400
|
// Append to the output (or current container via append()).
|
|
395
401
|
this.append(tmpStr + str);
|
|
396
402
|
}
|
|
@@ -497,9 +503,14 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
497
503
|
* @param {string|Element} elem - Element name or element object
|
|
498
504
|
* @param {ElementAttributes} [atts] - Element attributes
|
|
499
505
|
* @param {any[]} [childNodes] - Child nodes
|
|
500
|
-
* @param {(this: StringJoiningTransformer) => void} [cb] -
|
|
506
|
+
* @param {(this: StringJoiningTransformer) => void} [cb] -
|
|
507
|
+
* Callback function; may be async (e.g. to `await` a `$indexedDB`
|
|
508
|
+
* fetch), in which case `element()` itself returns a `Promise` instead
|
|
509
|
+
* of `this` — check for `.then` (or `await`) rather than assuming a
|
|
510
|
+
* synchronous return. (Typed as plain `void`, not `void|Promise<void>`
|
|
511
|
+
* — see the note on `SimpleCallback` in JSONJoiningTransformer.js.)
|
|
501
512
|
* @param {string[]} [useAttributeSets] - Attribute set names to apply
|
|
502
|
-
* @returns {StringJoiningTransformer}
|
|
513
|
+
* @returns {StringJoiningTransformer|Promise<StringJoiningTransformer>}
|
|
503
514
|
*/
|
|
504
515
|
element (elem, atts, childNodes, cb, useAttributeSets) {
|
|
505
516
|
// If a parent element's start tag is still open, close it before
|
|
@@ -635,17 +646,32 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
635
646
|
jml[method]({'#': childNodes})
|
|
636
647
|
));
|
|
637
648
|
}
|
|
638
|
-
cb
|
|
649
|
+
// `cb`'s declared return type is plain `void` (see the parameter's
|
|
650
|
+
// JSDoc) so the leniency that lets any actual return value through
|
|
651
|
+
// still applies to callers — cast here to duck-type the real value.
|
|
652
|
+
const cbResult = /** @type {any} */ (cb.call(this._context || this));
|
|
639
653
|
|
|
640
654
|
// Todo: Depending on an this._cfg.xmlElements option, allow for
|
|
641
655
|
// XML self-closing when empty or as per the tag, HTML
|
|
642
656
|
// self-closing tags (or polyglot-friendly self-closing)
|
|
643
|
-
|
|
644
|
-
this.
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
657
|
+
const finish = () => {
|
|
658
|
+
if (this._openTagState) {
|
|
659
|
+
this.append('>');
|
|
660
|
+
}
|
|
661
|
+
this.append('</' + elName + '>');
|
|
662
|
+
this._openTagState = oldTagState;
|
|
663
|
+
return this;
|
|
664
|
+
};
|
|
665
|
+
// `cb` may be an async function (e.g. one that awaits
|
|
666
|
+
// `this.indexedDB(...)`/`this.renderDefault()` before appending
|
|
667
|
+
// content) — mirroring how a root/matched/named template's own return
|
|
668
|
+
// value is already handled elsewhere, closing the tag only after that
|
|
669
|
+
// settles keeps output correctly ordered.
|
|
670
|
+
if (cbResult && typeof cbResult.then === 'function') {
|
|
671
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- Not async
|
|
672
|
+
return cbResult.then(() => finish());
|
|
673
|
+
}
|
|
674
|
+
return finish();
|
|
649
675
|
}
|
|
650
676
|
|
|
651
677
|
/**
|
|
@@ -619,12 +619,36 @@ class XPathTransformerContext {
|
|
|
619
619
|
const prevTemplateParams = this._params;
|
|
620
620
|
this._params = {0: node, ...appliedParams};
|
|
621
621
|
|
|
622
|
+
const {template: xpathTemplateFn} = templateObj;
|
|
623
|
+
/* c8 ignore start -- Todo: compile via compileJSONTemplate() once it
|
|
624
|
+
exists (see ~/idb-manager/JTLT-JSON-TEMPLATES-PROPOSAL.md) instead
|
|
625
|
+
of rejecting; no matched template can reach here with an Array
|
|
626
|
+
`template` until that lands (and the declarative subset is
|
|
627
|
+
JSONPath-only per that plan, so this may stay unreachable longer
|
|
628
|
+
than the JSONPath-side guards). */
|
|
629
|
+
if (Array.isArray(xpathTemplateFn)) {
|
|
630
|
+
throw new TypeError(
|
|
631
|
+
'JSON (jamilih-shaped) Array templates are not yet supported by ' +
|
|
632
|
+
'the XPath engine; compile with compileJSONTemplate() first.'
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
/* c8 ignore stop */
|
|
622
636
|
/**
|
|
623
637
|
* The template may return synchronously or return a Promise (e.g. from
|
|
624
638
|
* `await this.indexedDB(...)`), which is awaited unless `config.sync`.
|
|
625
639
|
* @type {any}
|
|
626
640
|
*/
|
|
627
|
-
const ret =
|
|
641
|
+
const ret = xpathTemplateFn.call(
|
|
642
|
+
// `this` carries runtime `extensions`; a consumer's
|
|
643
|
+
// `ContextExtensions` augmentation would otherwise reject it here
|
|
644
|
+
// (matching the cast already used at the other three call sites).
|
|
645
|
+
// `node` is cast too: `templateObj.template`'s static type is a
|
|
646
|
+
// union across a `dom`-typed `TemplateFunction` (expecting
|
|
647
|
+
// `DocumentFragment | Element`) and the mode-callback signature
|
|
648
|
+
// above (expecting plain `Node`) — the same node value satisfies
|
|
649
|
+
// whichever one actually runs.
|
|
650
|
+
/** @type {any} */ (this), /** @type {any} */ (node), {mode}
|
|
651
|
+
);
|
|
628
652
|
|
|
629
653
|
// Restore previous parameter context
|
|
630
654
|
this._params = prevTemplateParams;
|