@adobe/acc-js-sdk 1.1.61 → 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.
Files changed (62) hide show
  1. package/.cursor/commands/opsx-apply.md +152 -0
  2. package/.cursor/commands/opsx-archive.md +157 -0
  3. package/.cursor/commands/opsx-explore.md +173 -0
  4. package/.cursor/commands/opsx-propose.md +106 -0
  5. package/.cursor/skills/openspec-apply-change/SKILL.md +156 -0
  6. package/.cursor/skills/openspec-archive-change/SKILL.md +114 -0
  7. package/.cursor/skills/openspec-explore/SKILL.md +288 -0
  8. package/.cursor/skills/openspec-propose/SKILL.md +110 -0
  9. package/.eslintrc.js +2 -2
  10. package/.github/prompts/opsx-apply.prompt.md +149 -0
  11. package/.github/prompts/opsx-archive.prompt.md +154 -0
  12. package/.github/prompts/opsx-explore.prompt.md +170 -0
  13. package/.github/prompts/opsx-propose.prompt.md +103 -0
  14. package/.github/skills/openspec-apply-change/SKILL.md +156 -0
  15. package/.github/skills/openspec-archive-change/SKILL.md +114 -0
  16. package/.github/skills/openspec-explore/SKILL.md +288 -0
  17. package/.github/skills/openspec-propose/SKILL.md +110 -0
  18. package/.github/workflows/codeql-analysis.yml +5 -4
  19. package/.github/workflows/npm-publish.yml +3 -3
  20. package/AGENTS.md +117 -0
  21. package/CLAUDE.md +2 -0
  22. package/MIGRATION.md +10 -0
  23. package/README.md +6 -2
  24. package/ai-docs/coding-rules.md +95 -0
  25. package/ai-docs/tech-stack.md +43 -0
  26. package/babel.config.js +5 -0
  27. package/docs/changeLog.html +34 -0
  28. package/docs/checkList.html +2 -2
  29. package/docs/connectionParameters.html +5 -0
  30. package/docs/quickstart.html +2 -1
  31. package/docs/release.html +1 -1
  32. package/openspec/config.yaml +20 -0
  33. package/package-lock.json +7437 -3924
  34. package/package.json +9 -7
  35. package/src/AGENTS.md +98 -0
  36. package/src/CLAUDE.md +2 -0
  37. package/src/application.js +637 -637
  38. package/src/cache.js +133 -133
  39. package/src/cacheRefresher.js +190 -190
  40. package/src/campaign.js +532 -532
  41. package/src/client.js +1539 -1532
  42. package/src/crypto.js +52 -52
  43. package/src/domUtil.js +346 -346
  44. package/src/entityAccessor.js +61 -61
  45. package/src/index.js +83 -83
  46. package/src/methodCache.js +69 -69
  47. package/src/optionCache.js +26 -26
  48. package/src/soap.js +321 -322
  49. package/src/testUtil.js +13 -13
  50. package/src/transport.js +70 -70
  51. package/src/util.js +147 -147
  52. package/src/web/bundler.js +5 -5
  53. package/src/xtkCaster.js +258 -258
  54. package/src/xtkEntityCache.js +34 -34
  55. package/src/xtkJob.js +185 -185
  56. package/test/AGENTS.md +37 -0
  57. package/test/CLAUDE.md +2 -0
  58. package/test/cacheRefresher.test.js +7 -0
  59. package/test/client.test.js +123 -81
  60. package/test/jest.config.js +6 -0
  61. package/test/observability.test.js +6 -1
  62. 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
- var jsdom = function(text) {
34
- var parser = new DOMParser();
35
- var dom = parser.parseFromString(text, "application/xml");
36
- this.window = {
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
- this.serialize = function() {
38
+ };
39
+ this.serialize = function() {
40
40
  return new XMLSerializer().serializeToString(dom);
41
+ };
41
42
  };
42
- };
43
43
 
44
- JSDOM = jsdom;
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
- const result = [];
69
- for (const i of json)
70
- result.push(_toBadgerFish(i));
71
- return result;
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
- return new BadgerFishObject(json);
74
+ return new BadgerFishObject(json);
75
75
  return json;
76
- }
76
+ }
77
77
 
78
- class BadgerFishObject {
78
+ class BadgerFishObject {
79
79
  constructor(json) {
80
- for (const p in json) {
81
- this[p] = _toBadgerFish(json[p]);
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
- this.message = message;
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
- const dom = new JSDOM(xmlString, {contentType: "text/xml"});
114
- const doc = dom.window.document;
115
- doc.__jsdom__ = dom;
116
- return doc;
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
- return this.parse(`<${name}/>`);
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
- 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 + "&quot;";
141
- else if (c === '&') escaped = escaped + "&amp;";
142
- else if (c === '\'') escaped = escaped + "&apos;";
143
- else if (c === '<') escaped = escaped + "&lt;";
144
- else if (c === '>') escaped = escaped + "&gt;";
145
- else escaped = escaped + c;
146
- }
147
- return escaped;
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 + "&quot;";
141
+ else if (c === '&') escaped = escaped + "&amp;";
142
+ else if (c === '\'') escaped = escaped + "&apos;";
143
+ else if (c === '<') escaped = escaped + "&lt;";
144
+ else if (c === '>') escaped = escaped + "&gt;";
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
- 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;
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
- 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;
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
- 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;
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
- 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;
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
- const attr = node.getAttribute(name);
228
- return XtkCaster.asString(attr);
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
- const attr = node.getAttribute(name);
240
- return XtkCaster.asByte(attr);
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
- const attr = node.getAttribute(name);
252
- return XtkCaster.asBoolean(attr);
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
- const attr = node.getAttribute(name);
264
- return XtkCaster.asShort(attr);
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
- const attr = node.getAttribute(name);
276
- return XtkCaster.asLong(attr);
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
- 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;
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
- 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
- }
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
- 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);
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
- 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;
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
- 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;
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, 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;
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
- 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;
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
- // Proceed with text nodes in SimpleJson format.
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
- // 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;
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
- 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
- /**
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
- if (pathElement == null || pathElement == undefined || pathElement.trim() == "")
575
- throw new DomException(`Invalid empty xpath element`);
576
- this._pathElement = pathElement;
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
- return this._pathElement == ".";
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
- return this._pathElement == "..";
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
- return this._pathElement;
600
+ return this._pathElement;
601
601
  }
602
602
 
603
603
  toString() {
604
- return this.asString();
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
- path = (path || "").trim();
621
- if (path && path.startsWith("[") && path.endsWith("]"))
622
- path = path.substring(1, path.length - 1).trim();
623
- this._path = path;
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
- return this._path;
631
+ return this._path;
632
632
  }
633
633
 
634
634
  toString() {
635
- return this.asString();
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
- return this._path.length == 0;
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
- return this._path.length > 0 && this._path[0] == '/';
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
- return this._path == ".";
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
- return this._path == "/";
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
- 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);
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
- return elements;
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
- if (!this.isAbsolute()) return this;
695
- return new XPath(this._path.substring(1));
694
+ if (!this.isAbsolute()) return this;
695
+ return new XPath(this._path.substring(1));
696
696
  }
697
- }
697
+ }
698
698
 
699
- // Public expots
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
  })();