@adobe/acc-js-sdk 1.1.62 → 1.2.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/.cursor/commands/opsx-apply.md +152 -0
- package/.cursor/commands/opsx-archive.md +157 -0
- package/.cursor/commands/opsx-explore.md +173 -0
- package/.cursor/commands/opsx-propose.md +106 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +156 -0
- package/.cursor/skills/openspec-archive-change/SKILL.md +114 -0
- package/.cursor/skills/openspec-explore/SKILL.md +288 -0
- package/.cursor/skills/openspec-propose/SKILL.md +110 -0
- package/.eslintrc.js +2 -2
- package/.github/prompts/opsx-apply.prompt.md +149 -0
- package/.github/prompts/opsx-archive.prompt.md +154 -0
- package/.github/prompts/opsx-explore.prompt.md +170 -0
- package/.github/prompts/opsx-propose.prompt.md +103 -0
- package/.github/skills/openspec-apply-change/SKILL.md +156 -0
- package/.github/skills/openspec-archive-change/SKILL.md +114 -0
- package/.github/skills/openspec-explore/SKILL.md +288 -0
- package/.github/skills/openspec-propose/SKILL.md +110 -0
- package/.github/workflows/codeql-analysis.yml +5 -4
- package/.github/workflows/npm-publish.yml +3 -3
- package/AGENTS.md +117 -0
- package/CLAUDE.md +2 -0
- package/MIGRATION.md +10 -0
- package/README.md +6 -2
- package/ai-docs/coding-rules.md +95 -0
- package/ai-docs/tech-stack.md +43 -0
- package/babel.config.js +5 -0
- package/docs/changeLog.html +28 -2
- package/docs/checkList.html +2 -2
- package/docs/quickstart.html +2 -1
- package/docs/release.html +1 -1
- package/openspec/config.yaml +20 -0
- package/package-lock.json +6055 -4036
- package/package.json +9 -7
- package/src/AGENTS.md +98 -0
- package/src/CLAUDE.md +2 -0
- package/src/application.js +637 -637
- package/src/cache.js +133 -133
- package/src/cacheRefresher.js +190 -190
- package/src/campaign.js +532 -532
- package/src/client.js +1539 -1537
- package/src/crypto.js +52 -52
- package/src/domUtil.js +346 -346
- package/src/entityAccessor.js +61 -61
- package/src/index.js +83 -83
- package/src/methodCache.js +69 -69
- package/src/optionCache.js +26 -26
- package/src/soap.js +321 -322
- package/src/testUtil.js +13 -13
- package/src/transport.js +70 -70
- package/src/util.js +147 -147
- package/src/web/bundler.js +5 -5
- package/src/xtkCaster.js +258 -258
- package/src/xtkEntityCache.js +34 -34
- package/src/xtkJob.js +185 -185
- package/test/AGENTS.md +37 -0
- package/test/CLAUDE.md +2 -0
- package/test/cacheRefresher.test.js +7 -0
- package/test/client.test.js +90 -78
- package/test/jest.config.js +6 -0
- package/test/observability.test.js +6 -1
- package/test/xtkJob.test.js +2 -2
package/src/domUtil.js
CHANGED
|
@@ -10,92 +10,92 @@ OF ANY KIND, either express or implied. See the License for the specific languag
|
|
|
10
10
|
governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
(function() {
|
|
13
|
-
"use strict";
|
|
13
|
+
"use strict";
|
|
14
14
|
|
|
15
|
-
const XtkCaster = require('./xtkCaster.js').XtkCaster;
|
|
16
|
-
const { Util } = require("./util.js");
|
|
15
|
+
const XtkCaster = require('./xtkCaster.js').XtkCaster;
|
|
16
|
+
const { Util } = require("./util.js");
|
|
17
17
|
|
|
18
|
-
var JSDOM;
|
|
18
|
+
var JSDOM;
|
|
19
19
|
|
|
20
|
-
/* istanbul ignore else */
|
|
21
|
-
if (!Util.isBrowser()) {
|
|
20
|
+
/* istanbul ignore else */
|
|
21
|
+
if (!Util.isBrowser()) {
|
|
22
22
|
JSDOM = require("jsdom").JSDOM;
|
|
23
|
-
}
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
/**********************************************************************************
|
|
25
|
+
/**********************************************************************************
|
|
26
26
|
*
|
|
27
27
|
* Browser-side implementation of jsdom node module. As the DOM is already
|
|
28
28
|
* available in the browser, this is very light weight
|
|
29
29
|
*
|
|
30
30
|
*********************************************************************************/
|
|
31
|
-
else {
|
|
31
|
+
else {
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
var jsdom = function(text) {
|
|
34
|
+
var parser = new DOMParser();
|
|
35
|
+
var dom = parser.parseFromString(text, "application/xml");
|
|
36
|
+
this.window = {
|
|
37
37
|
document: dom
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
};
|
|
39
|
+
this.serialize = function() {
|
|
40
40
|
return new XMLSerializer().serializeToString(dom);
|
|
41
|
+
};
|
|
41
42
|
};
|
|
42
|
-
};
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
}
|
|
44
|
+
JSDOM = jsdom;
|
|
45
|
+
}
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
/**********************************************************************************
|
|
48
|
+
/**********************************************************************************
|
|
49
49
|
*
|
|
50
50
|
* DOM Utilities
|
|
51
51
|
*
|
|
52
52
|
*********************************************************************************/
|
|
53
53
|
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
55
|
* @namespace XML
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
/**
|
|
59
|
+
/**
|
|
60
60
|
* A dedicated class of object literals of class BadgerFish
|
|
61
61
|
* Used to distinguish between the 2 JSON representations: BadgerFish & SimpleJson
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
|
-
function _toBadgerFish(json) {
|
|
64
|
+
function _toBadgerFish(json) {
|
|
65
65
|
if (!json) return json;
|
|
66
66
|
|
|
67
67
|
if (Util.isArray(json)) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
const result = [];
|
|
69
|
+
for (const i of json)
|
|
70
|
+
result.push(_toBadgerFish(i));
|
|
71
|
+
return result;
|
|
72
72
|
}
|
|
73
73
|
if (typeof json == "object")
|
|
74
|
-
|
|
74
|
+
return new BadgerFishObject(json);
|
|
75
75
|
return json;
|
|
76
|
-
}
|
|
76
|
+
}
|
|
77
77
|
|
|
78
|
-
class BadgerFishObject {
|
|
78
|
+
class BadgerFishObject {
|
|
79
79
|
constructor(json) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
for (const p in json) {
|
|
81
|
+
this[p] = _toBadgerFish(json[p]);
|
|
82
|
+
}
|
|
83
83
|
}
|
|
84
|
-
}
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
class DomException {
|
|
86
|
+
class DomException {
|
|
87
87
|
constructor(message) {
|
|
88
|
-
|
|
88
|
+
this.message = message;
|
|
89
89
|
}
|
|
90
|
-
}
|
|
90
|
+
}
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
/**
|
|
93
|
+
/**
|
|
94
94
|
* @memberof XML
|
|
95
95
|
* @class
|
|
96
96
|
* @constructor
|
|
97
97
|
*/
|
|
98
|
-
class DomUtil {
|
|
98
|
+
class DomUtil {
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
101
|
* Helpers for common manipulation of DOM documents. Al functions are static, it is not necessary to create new instances of this object
|
|
@@ -110,10 +110,10 @@ class DomUtil {
|
|
|
110
110
|
* @returns {Document} the DOM document to the parsed string
|
|
111
111
|
*/
|
|
112
112
|
static parse(xmlString) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
const dom = new JSDOM(xmlString, {contentType: "text/xml"});
|
|
114
|
+
const doc = dom.window.document;
|
|
115
|
+
doc.__jsdom__ = dom;
|
|
116
|
+
return doc;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
@@ -123,7 +123,7 @@ class DomUtil {
|
|
|
123
123
|
* @returns a DOM Document
|
|
124
124
|
*/
|
|
125
125
|
static newDocument(name) {
|
|
126
|
-
|
|
126
|
+
return this.parse(`<${name}/>`);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
@@ -133,18 +133,18 @@ class DomUtil {
|
|
|
133
133
|
* @returns {string} the escaped string
|
|
134
134
|
*/
|
|
135
135
|
static escapeXmlString(text) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
if (text === null || text === undefined) return text;
|
|
137
|
+
var escaped = "";
|
|
138
|
+
for (var i=0; i<text.length; i++) {
|
|
139
|
+
var c = text[i];
|
|
140
|
+
if (c === '"') escaped = escaped + """;
|
|
141
|
+
else if (c === '&') escaped = escaped + "&";
|
|
142
|
+
else if (c === '\'') escaped = escaped + "'";
|
|
143
|
+
else if (c === '<') escaped = escaped + "<";
|
|
144
|
+
else if (c === '>') escaped = escaped + ">";
|
|
145
|
+
else escaped = escaped + c;
|
|
146
|
+
}
|
|
147
|
+
return escaped;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
@@ -156,16 +156,16 @@ class DomUtil {
|
|
|
156
156
|
* @returns the first child element with given name, or null if not found
|
|
157
157
|
*/
|
|
158
158
|
static findElement(element, tagName, throws) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
159
|
+
if (element === null || element === undefined) return null;
|
|
160
|
+
var child = element.firstChild;
|
|
161
|
+
while (child) {
|
|
162
|
+
if (child.nodeType === 1 && child.nodeName == tagName)
|
|
163
|
+
return child;
|
|
164
|
+
child = child.nextSibling;
|
|
165
|
+
}
|
|
166
|
+
if (throws)
|
|
167
|
+
throw new DomException(`Node ${tagName} not found`);
|
|
168
|
+
return null;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/**
|
|
@@ -175,11 +175,11 @@ class DomUtil {
|
|
|
175
175
|
* @param {string} optionalNodeName is an optional tag name. If set, the first child with a matching name will be returned
|
|
176
176
|
*/
|
|
177
177
|
static getFirstChildElement(node, optionalNodeName) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
if (node === null || node === undefined) return null;
|
|
179
|
+
var child = node.firstChild;
|
|
180
|
+
while (child && (child.nodeType != 1 || (optionalNodeName !== undefined && child.nodeName !== optionalNodeName)))
|
|
181
|
+
child = child.nextSibling;
|
|
182
|
+
return child;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/**
|
|
@@ -190,15 +190,15 @@ class DomUtil {
|
|
|
190
190
|
* @returns {string} the text content
|
|
191
191
|
*/
|
|
192
192
|
static elementValue(node) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
193
|
+
var text = "";
|
|
194
|
+
if (node === null || node === undefined) return text;
|
|
195
|
+
var child = node.firstChild;
|
|
196
|
+
while (child) {
|
|
197
|
+
if (child.nodeType === 3 || child.nodeType === 4) // text or CDATA
|
|
198
|
+
text = text + child.nodeValue;
|
|
199
|
+
child = child.nextSibling;
|
|
200
|
+
}
|
|
201
|
+
return text;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
/**
|
|
@@ -209,11 +209,11 @@ class DomUtil {
|
|
|
209
209
|
* @returns the next sibling element with the given tag name
|
|
210
210
|
*/
|
|
211
211
|
static getNextSiblingElement(node, optionalNodeName) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
if (node === null || node === undefined) return null;
|
|
213
|
+
var sibling = node.nextSibling;
|
|
214
|
+
while (sibling && (sibling.nodeType != 1 || (optionalNodeName !== undefined && sibling.nodeName !== optionalNodeName)))
|
|
215
|
+
sibling = sibling.nextSibling;
|
|
216
|
+
return sibling;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
/**
|
|
@@ -224,8 +224,8 @@ class DomUtil {
|
|
|
224
224
|
* @returns {string} the attribute value, or an empty string if the attribute is not set
|
|
225
225
|
*/
|
|
226
226
|
static getAttributeAsString(node, name) {
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
const attr = node.getAttribute(name);
|
|
228
|
+
return XtkCaster.asString(attr);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/**
|
|
@@ -236,8 +236,8 @@ class DomUtil {
|
|
|
236
236
|
* @returns {number} the attribute value casted to a byte number, or 0 if the attribute is not set
|
|
237
237
|
*/
|
|
238
238
|
static getAttributeAsByte(node, name) {
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
const attr = node.getAttribute(name);
|
|
240
|
+
return XtkCaster.asByte(attr);
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/**
|
|
@@ -248,8 +248,8 @@ class DomUtil {
|
|
|
248
248
|
* @returns {boolean} the attribute value casted to a boolean, or false if the attribute is not set
|
|
249
249
|
*/
|
|
250
250
|
static getAttributeAsBoolean(node, name) {
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
const attr = node.getAttribute(name);
|
|
252
|
+
return XtkCaster.asBoolean(attr);
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
/**
|
|
@@ -260,8 +260,8 @@ class DomUtil {
|
|
|
260
260
|
* @returns {number} the attribute value casted to a short number, or 0 if the attribute is not set
|
|
261
261
|
*/
|
|
262
262
|
static getAttributeAsShort(node, name) {
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
const attr = node.getAttribute(name);
|
|
264
|
+
return XtkCaster.asShort(attr);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
/**
|
|
@@ -272,8 +272,8 @@ class DomUtil {
|
|
|
272
272
|
* @returns {number} the attribute value casted to a long number, or 0 if the attribute is not set
|
|
273
273
|
*/
|
|
274
274
|
static getAttributeAsLong(node, name) {
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
const attr = node.getAttribute(name);
|
|
276
|
+
return XtkCaster.asLong(attr);
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
/**
|
|
@@ -283,15 +283,15 @@ class DomUtil {
|
|
|
283
283
|
* @returns {string} the serialized XML string
|
|
284
284
|
*/
|
|
285
285
|
static toXMLString(node) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
286
|
+
var s = "";
|
|
287
|
+
if (node) {
|
|
288
|
+
if (node.__jsdom__)
|
|
289
|
+
return node.__jsdom__.serialize();
|
|
290
|
+
if (node.nodeType == 9) // documentElement
|
|
291
|
+
node = node.documentElement;
|
|
292
|
+
s = node.outerHTML;
|
|
293
|
+
}
|
|
294
|
+
return s;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
/**
|
|
@@ -305,58 +305,58 @@ class DomUtil {
|
|
|
305
305
|
* @param {string} flavor the JSON flavor: "SimpleJson" or "BadgerFish"
|
|
306
306
|
*/
|
|
307
307
|
static _fromJSON(doc, xmlRoot, jsonRoot, flavor) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
308
|
+
for(var att in jsonRoot) {
|
|
309
|
+
const value = jsonRoot[att];
|
|
310
|
+
if (value === null || value === undefined)
|
|
311
|
+
continue;
|
|
312
|
+
const t = typeof value;
|
|
313
|
+
var isAtt = att[0] == '@';
|
|
314
|
+
var attFirstIndex = 1;
|
|
315
|
+
|
|
316
|
+
if (flavor == "SimpleJson") {
|
|
317
|
+
if ((t == "string" || t == "number" || t == "boolean") && att[0] != '$' && att[0] != '@') {
|
|
318
|
+
isAtt = true;
|
|
319
|
+
attFirstIndex = 0;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
322
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
else if (t == "object") {
|
|
352
|
-
const xmlElement = doc.createElement(att);
|
|
353
|
-
this._fromJSON(doc, xmlElement, value, flavor);
|
|
354
|
-
xmlRoot.appendChild(xmlElement);
|
|
355
|
-
}
|
|
356
|
-
else
|
|
357
|
-
throw new DomException(`Cannot cast JSON to XML: element '${att}' type '${t}' is unknown or not supported yet`);
|
|
323
|
+
if (isAtt) {
|
|
324
|
+
att = att.substr(attFirstIndex);
|
|
325
|
+
if (t == "string")
|
|
326
|
+
xmlRoot.setAttribute(att, XtkCaster.asString(value));
|
|
327
|
+
else if (t == "number")
|
|
328
|
+
xmlRoot.setAttribute(att, XtkCaster.asString(XtkCaster.asNumber(value)));
|
|
329
|
+
else if (t == "boolean")
|
|
330
|
+
xmlRoot.setAttribute(att, XtkCaster.asString(XtkCaster.asBoolean(value)));
|
|
331
|
+
else
|
|
332
|
+
throw new DomException(`Cannot cast JSON to XML: attribute '${att}' type '${t}' is unknown or not supported yet`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
if (att == "$") {
|
|
336
|
+
xmlRoot.textContent = value;
|
|
337
|
+
}
|
|
338
|
+
else if (flavor == "SimpleJson" && att[0] == '$') {
|
|
339
|
+
att = att.substr(1);
|
|
340
|
+
const xmlElement = doc.createElement(att);
|
|
341
|
+
xmlElement.textContent = value;
|
|
342
|
+
xmlRoot.appendChild(xmlElement);
|
|
343
|
+
}
|
|
344
|
+
else if (Util.isArray(value)) {
|
|
345
|
+
for (var i=0; i<value.length; i++) {
|
|
346
|
+
const xmlElement = doc.createElement(att);
|
|
347
|
+
this._fromJSON(doc, xmlElement, value[i], flavor);
|
|
348
|
+
xmlRoot.appendChild(xmlElement);
|
|
358
349
|
}
|
|
350
|
+
}
|
|
351
|
+
else if (t == "object") {
|
|
352
|
+
const xmlElement = doc.createElement(att);
|
|
353
|
+
this._fromJSON(doc, xmlElement, value, flavor);
|
|
354
|
+
xmlRoot.appendChild(xmlElement);
|
|
355
|
+
}
|
|
356
|
+
else
|
|
357
|
+
throw new DomException(`Cannot cast JSON to XML: element '${att}' type '${t}' is unknown or not supported yet`);
|
|
359
358
|
}
|
|
359
|
+
}
|
|
360
360
|
|
|
361
361
|
}
|
|
362
362
|
|
|
@@ -369,34 +369,34 @@ class DomUtil {
|
|
|
369
369
|
* @returns {Document} An XML document corresponding to the converted obejct literal
|
|
370
370
|
*/
|
|
371
371
|
static fromJSON(docName, json, flavor) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
372
|
+
flavor = flavor || "SimpleJson";
|
|
373
|
+
if (flavor != "SimpleJson" && flavor != "BadgerFish")
|
|
374
|
+
throw new DomException(`Invalid JSON flavor '${flavor}'. Should be 'SimpleJson' or 'BadgerFish'`);
|
|
375
|
+
if (!docName)
|
|
376
|
+
throw new DomException(`Cannot transform entity of flavor '${flavor}' to xml because no XML root name was given`);
|
|
377
|
+
const doc = this.newDocument(docName);
|
|
378
|
+
const root = doc.documentElement;
|
|
379
|
+
this._fromJSON(doc, root, json, flavor);
|
|
380
|
+
return doc;
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
// Get the text of a node. Will return the text if the xml node contains
|
|
384
384
|
// only text and cdata nodes. Otherwise will return null
|
|
385
385
|
static _getTextIfTextNode(xml) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
386
|
+
let child = xml.firstChild;
|
|
387
|
+
if (!child) return null; // no children
|
|
388
|
+
if (xml.hasAttributes()) return null; // no attributes
|
|
389
|
+
let text = "";
|
|
390
|
+
while (child) {
|
|
391
|
+
// if child node is not text or cdata, it means we have a mix
|
|
392
|
+
// of text children and non-text children => we do not consider
|
|
393
|
+
// the xml node to be a text-only node
|
|
394
|
+
if (child.nodeType !== 3 && child.nodeType !== 4)
|
|
395
|
+
return null;
|
|
396
|
+
text = text + child.nodeValue;
|
|
397
|
+
child = child.nextSibling;
|
|
398
|
+
}
|
|
399
|
+
return text;
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
/**
|
|
@@ -412,106 +412,106 @@ class DomUtil {
|
|
|
412
412
|
* instead of "$name: value" syntax. This is necessary to process collections and arrays of elements which only
|
|
413
413
|
* contain text nodes.
|
|
414
414
|
*/
|
|
415
|
-
static _toJSON(xml, json, flavor, parentJson,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
child = child.nextSibling;
|
|
415
|
+
static _toJSON(xml, json, flavor, parentJson, _forceTextAs$) {
|
|
416
|
+
|
|
417
|
+
// Heuristic to determine if element is an object or an array
|
|
418
|
+
const isCollection = xml.tagName.length > 11 && xml.tagName.substr(xml.tagName.length-11) == '-collection';
|
|
419
|
+
|
|
420
|
+
// Figure out which elements are arrays. Keep a count of elements for each tag name.
|
|
421
|
+
// When count >1 we consider this tag name to be an array
|
|
422
|
+
var hasChildElements = false; // Will be set if there is at least one child element
|
|
423
|
+
const countByTag = {};
|
|
424
|
+
var child = xml.firstChild;
|
|
425
|
+
while (child) {
|
|
426
|
+
if (child.nodeType == 1) {
|
|
427
|
+
const childName = child.nodeName;
|
|
428
|
+
if (countByTag[childName] === undefined) countByTag[childName] = 1;
|
|
429
|
+
else countByTag[childName] = countByTag[childName] + 1;
|
|
430
|
+
hasChildElements = true;
|
|
433
431
|
}
|
|
432
|
+
child = child.nextSibling;
|
|
433
|
+
}
|
|
434
434
|
|
|
435
|
+
child = xml.firstChild;
|
|
436
|
+
while (child) {
|
|
437
|
+
if (child.nodeType == 1) {
|
|
438
|
+
const childName = child.nodeName;
|
|
439
|
+
const isArray = isCollection || countByTag[childName] > 1;
|
|
440
|
+
if (isArray && !json[childName]) json[childName] = [];
|
|
441
|
+
// In SimpleJson representation, ensure we have proper transformation
|
|
442
|
+
// of text and CDATA nodes. For instance, the following
|
|
443
|
+
// <workflow><desc>Hello</desc></workflow>
|
|
444
|
+
// should be transformed into { "$desc": "Hello" }
|
|
445
|
+
// Note that an empty element such as
|
|
446
|
+
// <workflow><desc></desc></workflow>
|
|
447
|
+
// will be transformed into { "desc": {} }
|
|
448
|
+
// because there is an ambiguity and, unless we have information
|
|
449
|
+
// from the schema, we cannot know if <desc></desc> should be
|
|
450
|
+
// transformed into "$desc": "" or into "desc": {}
|
|
451
|
+
const text = this._getTextIfTextNode(child);
|
|
452
|
+
if (!isArray && text !== null && flavor == "SimpleJson") {
|
|
453
|
+
json[`$${childName}`] = text;
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
const jsonChild = flavor == "BadgerFish" ? new BadgerFishObject() : {};
|
|
457
|
+
this._toJSON(child, jsonChild, flavor, json, isArray);
|
|
458
|
+
if (isArray)
|
|
459
|
+
json[childName].push(jsonChild);
|
|
460
|
+
else
|
|
461
|
+
json[childName] = jsonChild;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
else if (child.nodeType === 3 || child.nodeType === 4) { // text and CDATA
|
|
465
|
+
if (flavor == "BadgerFish") {
|
|
466
|
+
const text = child.nodeValue;
|
|
467
|
+
if (json.$ === undefined)
|
|
468
|
+
json.$ = text;
|
|
469
|
+
else
|
|
470
|
+
json.$ = json.$ + text;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
child = child.nextSibling;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Proceed with text nodes in SimpleJson format.
|
|
477
|
+
if (flavor === "SimpleJson") {
|
|
478
|
+
var text = "";
|
|
435
479
|
child = xml.firstChild;
|
|
436
480
|
while (child) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const text = this._getTextIfTextNode(child);
|
|
452
|
-
if (!isArray && text !== null && flavor == "SimpleJson") {
|
|
453
|
-
json[`$${childName}`] = text;
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
const jsonChild = flavor == "BadgerFish" ? new BadgerFishObject() : {};
|
|
457
|
-
this._toJSON(child, jsonChild, flavor, json, isArray);
|
|
458
|
-
if (isArray)
|
|
459
|
-
json[childName].push(jsonChild);
|
|
460
|
-
else
|
|
461
|
-
json[childName] = jsonChild;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
else if (child.nodeType === 3 || child.nodeType === 4) { // text and CDATA
|
|
465
|
-
if (flavor == "BadgerFish") {
|
|
466
|
-
const text = child.nodeValue;
|
|
467
|
-
if (json.$ === undefined)
|
|
468
|
-
json.$ = text;
|
|
469
|
-
else
|
|
470
|
-
json.$ = json.$ + text;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
child = child.nextSibling;
|
|
481
|
+
if (child.nodeType === 3) { // text
|
|
482
|
+
var nodeText = child.nodeValue;
|
|
483
|
+
// Whitespace trimming rule: always trim for the root node, and trim for non-root nodes
|
|
484
|
+
// which actually have children elements
|
|
485
|
+
// Never trim CDATA nodes (nodeType 4)
|
|
486
|
+
if (!parentJson || hasChildElements)
|
|
487
|
+
nodeText = nodeText.trim();
|
|
488
|
+
if (nodeText) text = text + nodeText;
|
|
489
|
+
}
|
|
490
|
+
else if (child.nodeType === 4) { // CDATA
|
|
491
|
+
const cdataText = child.nodeValue;
|
|
492
|
+
if (cdataText) text = text + cdataText;
|
|
493
|
+
}
|
|
494
|
+
child = child.nextSibling;
|
|
474
495
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (flavor === "SimpleJson") {
|
|
478
|
-
var text = "";
|
|
479
|
-
child = xml.firstChild;
|
|
480
|
-
while (child) {
|
|
481
|
-
if (child.nodeType === 3) { // text
|
|
482
|
-
var nodeText = child.nodeValue;
|
|
483
|
-
// Whitespace trimming rule: always trim for the root node, and trim for non-root nodes
|
|
484
|
-
// which actually have children elements
|
|
485
|
-
// Never trim CDATA nodes (nodeType 4)
|
|
486
|
-
if (!parentJson || hasChildElements)
|
|
487
|
-
nodeText = nodeText.trim();
|
|
488
|
-
if (nodeText) text = text + nodeText;
|
|
489
|
-
}
|
|
490
|
-
else if (child.nodeType === 4) { // CDATA
|
|
491
|
-
const cdataText = child.nodeValue;
|
|
492
|
-
if (cdataText) text = text + cdataText;
|
|
493
|
-
}
|
|
494
|
-
child = child.nextSibling;
|
|
495
|
-
}
|
|
496
|
-
if (text) {
|
|
497
|
-
json.$ = text;
|
|
498
|
-
}
|
|
496
|
+
if (text) {
|
|
497
|
+
json.$ = text;
|
|
499
498
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Finally proceed with attributes. They are processed last so that we get a chance to prefix
|
|
502
|
+
// the attribute name with "@" in SimpleJson format if there's already an element with the
|
|
503
|
+
// same name
|
|
504
|
+
if (xml.hasAttributes()) {
|
|
505
|
+
const attributes = xml.attributes;
|
|
506
|
+
for (var i=0; i<attributes.length; i++) {
|
|
507
|
+
const att = attributes[i];
|
|
508
|
+
var attName = (flavor == "BadgerFish" ? "@" : "") + att.name;
|
|
509
|
+
if (json[attName] !== undefined && flavor === "SimpleJson")
|
|
510
|
+
// There's already an element with the same name as the attribute
|
|
511
|
+
attName = "@" + attName;
|
|
512
|
+
json[attName] = att.value;
|
|
514
513
|
}
|
|
514
|
+
}
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
/**
|
|
@@ -522,44 +522,44 @@ class DomUtil {
|
|
|
522
522
|
* @returns {Object} an object literal corresponding to the XML element or document
|
|
523
523
|
*/
|
|
524
524
|
static toJSON(xml, flavor) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
// ========================================================================================
|
|
541
|
-
// XPath
|
|
542
|
-
// This is currently an internal helper structure
|
|
543
|
-
//
|
|
544
|
-
// A XPath can be either of:
|
|
545
|
-
// - null, undefined or empty, which represents a relative path to self
|
|
546
|
-
// - "." a relative path to self
|
|
547
|
-
// - ".." a relative path to the parent node
|
|
548
|
-
// - "/" a absolute path to the top level node (the schema node)
|
|
549
|
-
// - A "/" separate list of path elements, optionally starting with "/" to indicate an absolute xpath
|
|
550
|
-
//
|
|
551
|
-
// XPathElement
|
|
552
|
-
// This is currently an internal helper structure
|
|
553
|
-
//
|
|
554
|
-
// An XPathElement represents an element in a XPath. It can never be null, undefined or empty
|
|
555
|
-
// (this should not be possible by construction as XPath elements are created from the XPath
|
|
556
|
-
// object)
|
|
557
|
-
// - A attribute name, starting with "@"
|
|
558
|
-
// - An element name
|
|
559
|
-
//
|
|
560
|
-
// ========================================================================================
|
|
561
|
-
|
|
562
|
-
/**
|
|
525
|
+
if (xml === null || xml === undefined) return xml;
|
|
526
|
+
flavor = flavor || "SimpleJson";
|
|
527
|
+
if (flavor != "SimpleJson" && flavor != "BadgerFish")
|
|
528
|
+
throw new DomException(`Invalid JSON flavor '${flavor}'. Should be 'SimpleJson' or 'BadgerFish'`);
|
|
529
|
+
if (xml.nodeType == 9)
|
|
530
|
+
xml = xml.documentElement;
|
|
531
|
+
var json = flavor == "BadgerFish" ? new BadgerFishObject() : {};
|
|
532
|
+
this._toJSON(xml, json, flavor);
|
|
533
|
+
return json;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
// ========================================================================================
|
|
541
|
+
// XPath
|
|
542
|
+
// This is currently an internal helper structure
|
|
543
|
+
//
|
|
544
|
+
// A XPath can be either of:
|
|
545
|
+
// - null, undefined or empty, which represents a relative path to self
|
|
546
|
+
// - "." a relative path to self
|
|
547
|
+
// - ".." a relative path to the parent node
|
|
548
|
+
// - "/" a absolute path to the top level node (the schema node)
|
|
549
|
+
// - A "/" separate list of path elements, optionally starting with "/" to indicate an absolute xpath
|
|
550
|
+
//
|
|
551
|
+
// XPathElement
|
|
552
|
+
// This is currently an internal helper structure
|
|
553
|
+
//
|
|
554
|
+
// An XPathElement represents an element in a XPath. It can never be null, undefined or empty
|
|
555
|
+
// (this should not be possible by construction as XPath elements are created from the XPath
|
|
556
|
+
// object)
|
|
557
|
+
// - A attribute name, starting with "@"
|
|
558
|
+
// - An element name
|
|
559
|
+
//
|
|
560
|
+
// ========================================================================================
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
563
|
* Represents an element of a XPath
|
|
564
564
|
*
|
|
565
565
|
* @private
|
|
@@ -568,12 +568,12 @@ class DomUtil {
|
|
|
568
568
|
* @param {string} pathElement the element as a string
|
|
569
569
|
* @memberof XML
|
|
570
570
|
*/
|
|
571
|
-
class XPathElement {
|
|
571
|
+
class XPathElement {
|
|
572
572
|
|
|
573
573
|
constructor(pathElement) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
574
|
+
if (pathElement == null || pathElement == undefined || pathElement.trim() == "")
|
|
575
|
+
throw new DomException(`Invalid empty xpath element`);
|
|
576
|
+
this._pathElement = pathElement;
|
|
577
577
|
}
|
|
578
578
|
|
|
579
579
|
/**
|
|
@@ -581,7 +581,7 @@ class XPathElement {
|
|
|
581
581
|
* @returns {boolean} a boolean indicating if the element represents the current node
|
|
582
582
|
*/
|
|
583
583
|
isSelf() {
|
|
584
|
-
|
|
584
|
+
return this._pathElement == ".";
|
|
585
585
|
}
|
|
586
586
|
|
|
587
587
|
/**
|
|
@@ -589,7 +589,7 @@ class XPathElement {
|
|
|
589
589
|
* @returns {boolean} a boolean indicating if the element represents the parent node
|
|
590
590
|
*/
|
|
591
591
|
isParent() {
|
|
592
|
-
|
|
592
|
+
return this._pathElement == "..";
|
|
593
593
|
}
|
|
594
594
|
|
|
595
595
|
/**
|
|
@@ -597,15 +597,15 @@ class XPathElement {
|
|
|
597
597
|
* @returns {string} the XPath element as a string
|
|
598
598
|
*/
|
|
599
599
|
asString() {
|
|
600
|
-
|
|
600
|
+
return this._pathElement;
|
|
601
601
|
}
|
|
602
602
|
|
|
603
603
|
toString() {
|
|
604
|
-
|
|
604
|
+
return this.asString();
|
|
605
605
|
}
|
|
606
|
-
}
|
|
606
|
+
}
|
|
607
607
|
|
|
608
|
-
/**
|
|
608
|
+
/**
|
|
609
609
|
* Represents a XPath
|
|
610
610
|
*
|
|
611
611
|
* @private
|
|
@@ -614,13 +614,13 @@ class XPathElement {
|
|
|
614
614
|
* @param {string} path the XPath, as a string
|
|
615
615
|
* @memberof XML
|
|
616
616
|
*/
|
|
617
|
-
class XPath {
|
|
617
|
+
class XPath {
|
|
618
618
|
|
|
619
619
|
constructor(path) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
620
|
+
path = (path || "").trim();
|
|
621
|
+
if (path && path.startsWith("[") && path.endsWith("]"))
|
|
622
|
+
path = path.substring(1, path.length - 1).trim();
|
|
623
|
+
this._path = path;
|
|
624
624
|
}
|
|
625
625
|
|
|
626
626
|
/**
|
|
@@ -628,11 +628,11 @@ class XPath {
|
|
|
628
628
|
* @returns {string} the XPath as a string
|
|
629
629
|
*/
|
|
630
630
|
asString() {
|
|
631
|
-
|
|
631
|
+
return this._path;
|
|
632
632
|
}
|
|
633
633
|
|
|
634
634
|
toString() {
|
|
635
|
-
|
|
635
|
+
return this.asString();
|
|
636
636
|
}
|
|
637
637
|
|
|
638
638
|
/**
|
|
@@ -640,7 +640,7 @@ class XPath {
|
|
|
640
640
|
* @returns {boolean} a boolean indicating whether the XPath is empty or not
|
|
641
641
|
*/
|
|
642
642
|
isEmpty() {
|
|
643
|
-
|
|
643
|
+
return this._path.length == 0;
|
|
644
644
|
}
|
|
645
645
|
|
|
646
646
|
/**
|
|
@@ -648,7 +648,7 @@ class XPath {
|
|
|
648
648
|
* @returns {boolean} a boolean indicating whether the XPath is an absolute XPath or not
|
|
649
649
|
*/
|
|
650
650
|
isAbsolute() {
|
|
651
|
-
|
|
651
|
+
return this._path.length > 0 && this._path[0] == '/';
|
|
652
652
|
}
|
|
653
653
|
|
|
654
654
|
/**
|
|
@@ -656,7 +656,7 @@ class XPath {
|
|
|
656
656
|
* @returns {boolean} a boolean indicating whether the XPath is the current node
|
|
657
657
|
*/
|
|
658
658
|
isSelf() {
|
|
659
|
-
|
|
659
|
+
return this._path == ".";
|
|
660
660
|
}
|
|
661
661
|
|
|
662
662
|
/**
|
|
@@ -664,7 +664,7 @@ class XPath {
|
|
|
664
664
|
* @returns {boolean} a boolean indicating whether the XPath is the root node
|
|
665
665
|
*/
|
|
666
666
|
isRootPath() {
|
|
667
|
-
|
|
667
|
+
return this._path == "/";
|
|
668
668
|
}
|
|
669
669
|
|
|
670
670
|
/**
|
|
@@ -672,17 +672,17 @@ class XPath {
|
|
|
672
672
|
* @returns {XPathElement[]} an array of XPath elements, which may be empty. Will never be null or undefined.
|
|
673
673
|
*/
|
|
674
674
|
getElements() {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
675
|
+
const elements = [];
|
|
676
|
+
const first = this.isAbsolute() ? 1 : 0;
|
|
677
|
+
const path = this._path.substr(first);
|
|
678
|
+
if (path != "") {
|
|
679
|
+
const tokens = path.split('/');
|
|
680
|
+
for (var i=0; i<tokens.length; i++) {
|
|
681
|
+
const element = new XPathElement(tokens[i]);
|
|
682
|
+
elements.push(element);
|
|
684
683
|
}
|
|
685
|
-
|
|
684
|
+
}
|
|
685
|
+
return elements;
|
|
686
686
|
}
|
|
687
687
|
|
|
688
688
|
/**
|
|
@@ -691,16 +691,16 @@ class XPath {
|
|
|
691
691
|
* @returns {XPath} the relative XPaht
|
|
692
692
|
*/
|
|
693
693
|
getRelativePath() {
|
|
694
|
-
|
|
695
|
-
|
|
694
|
+
if (!this.isAbsolute()) return this;
|
|
695
|
+
return new XPath(this._path.substring(1));
|
|
696
696
|
}
|
|
697
|
-
}
|
|
697
|
+
}
|
|
698
698
|
|
|
699
|
-
// Public
|
|
700
|
-
exports.DomUtil = DomUtil;
|
|
701
|
-
exports.DomException = DomException;
|
|
702
|
-
exports.BadgerFishObject = BadgerFishObject;
|
|
703
|
-
exports.XPath = XPath;
|
|
704
|
-
exports.XPathElement = XPathElement;
|
|
699
|
+
// Public exports
|
|
700
|
+
exports.DomUtil = DomUtil;
|
|
701
|
+
exports.DomException = DomException;
|
|
702
|
+
exports.BadgerFishObject = BadgerFishObject;
|
|
703
|
+
exports.XPath = XPath;
|
|
704
|
+
exports.XPathElement = XPathElement;
|
|
705
705
|
|
|
706
706
|
})();
|