xml-disassembler 1.10.11 → 1.10.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.umd.js CHANGED
@@ -45,34 +45,49 @@
45
45
  const rootKey = Object.keys(first).find((k) => k !== "?xml");
46
46
  const mergedContent = {};
47
47
  for (const element of elements) {
48
- const current = element[rootKey];
49
- for (const [childKey, value] of Object.entries(current)) {
50
- if (Array.isArray(value)) {
51
- mergedContent[childKey] = [...value];
52
- }
53
- else if (typeof value === "object") {
54
- if (Array.isArray(mergedContent[childKey])) {
55
- mergedContent[childKey].push(value);
56
- }
57
- else if (mergedContent[childKey]) {
58
- mergedContent[childKey] = [mergedContent[childKey], value];
59
- }
60
- else {
61
- mergedContent[childKey] = value;
62
- }
63
- }
64
- else {
65
- if (!mergedContent.hasOwnProperty(childKey)) {
66
- mergedContent[childKey] = value;
67
- }
68
- }
48
+ mergeElementContent(mergedContent, element[rootKey]);
49
+ }
50
+ return buildFinalXmlElement(first["?xml"], rootKey, mergedContent);
51
+ }
52
+ function mergeElementContent(target, source) {
53
+ for (const [key, value] of Object.entries(source)) {
54
+ if (Array.isArray(value)) {
55
+ mergeArrayValue(target, key, value);
56
+ }
57
+ else if (isMergeableObject(value)) {
58
+ mergeObjectValue(target, key, value);
59
+ }
60
+ else {
61
+ mergePrimitiveValue(target, key, value);
69
62
  }
70
63
  }
71
- const declaration = first["?xml"];
72
- const finalMerged = declaration
73
- ? { "?xml": declaration, [rootKey]: mergedContent }
74
- : { [rootKey]: mergedContent };
75
- return finalMerged;
64
+ }
65
+ function mergeArrayValue(target, key, value) {
66
+ target[key] = [...value];
67
+ }
68
+ function mergeObjectValue(target, key, value) {
69
+ if (Array.isArray(target[key])) {
70
+ target[key].push(value);
71
+ }
72
+ else if (target[key]) {
73
+ target[key] = [target[key], value];
74
+ }
75
+ else {
76
+ target[key] = value;
77
+ }
78
+ }
79
+ function mergePrimitiveValue(target, key, value) {
80
+ if (!Object.prototype.hasOwnProperty.call(target, key)) {
81
+ target[key] = value;
82
+ }
83
+ }
84
+ function isMergeableObject(value) {
85
+ return typeof value === "object" && value !== null;
86
+ }
87
+ function buildFinalXmlElement(declaration, rootKey, content) {
88
+ return declaration
89
+ ? { "?xml": declaration, [rootKey]: content }
90
+ : { [rootKey]: content };
76
91
  }
77
92
 
78
93
  const INDENT = " ";
@@ -89,25 +104,33 @@
89
104
  };
90
105
  const JSON_PARSER_OPTION = Object.assign(Object.assign({}, XML_PARSER_OPTION), { format: true, indentBy: INDENT, suppressBooleanAttributes: false, suppressEmptyNode: false });
91
106
 
107
+ function isEmptyTextNode(key, value) {
108
+ return key === "#text" && typeof value === "string" && value.trim() === "";
109
+ }
110
+ function cleanArray(arr) {
111
+ return arr
112
+ .map(stripWhitespaceTextNodes)
113
+ .filter((entry) => !(typeof entry === "object" && Object.keys(entry).length === 0));
114
+ }
115
+ function cleanObject(obj) {
116
+ const result = {};
117
+ for (const key in obj) {
118
+ const value = obj[key];
119
+ if (isEmptyTextNode(key, value))
120
+ continue;
121
+ const cleaned = stripWhitespaceTextNodes(value);
122
+ if (cleaned !== undefined) {
123
+ result[key] = cleaned;
124
+ }
125
+ }
126
+ return result;
127
+ }
92
128
  function stripWhitespaceTextNodes(node) {
93
129
  if (Array.isArray(node)) {
94
- return node.map(stripWhitespaceTextNodes).filter((entry) => {
95
- return !(typeof entry === "object" && Object.keys(entry).length === 0);
96
- });
130
+ return cleanArray(node);
97
131
  }
98
132
  else if (typeof node === "object" && node !== null) {
99
- const result = {};
100
- for (const key in node) {
101
- const value = node[key];
102
- if (key === "#text" && typeof value === "string" && value.trim() === "") {
103
- continue;
104
- }
105
- const cleaned = stripWhitespaceTextNodes(value);
106
- if (cleaned !== undefined) {
107
- result[key] = cleaned;
108
- }
109
- }
110
- return result;
133
+ return cleanObject(node);
111
134
  }
112
135
  else {
113
136
  return node;
@@ -131,90 +154,91 @@
131
154
  });
132
155
  }
133
156
 
157
+ const parsers = {
158
+ ".yaml": yaml.parse,
159
+ ".yml": yaml.parse,
160
+ ".json": JSON.parse,
161
+ ".json5": json5.parse,
162
+ ".toml": smolToml.parse,
163
+ ".ini": ini.parse,
164
+ };
134
165
  function parseToXmlObject(filePath) {
135
166
  return __awaiter(this, void 0, void 0, function* () {
136
167
  if (filePath.endsWith(".xml")) {
137
168
  return yield parseXML(filePath);
138
169
  }
170
+ const ext = Object.keys(parsers).find((ext) => filePath.endsWith(ext));
139
171
  const fileContent = yield promises$1.readFile(filePath, "utf-8");
140
- let parsed;
141
- if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
142
- parsed = yaml.parse(fileContent);
143
- }
144
- else if (filePath.endsWith(".json5")) {
145
- parsed = json5.parse(fileContent);
146
- }
147
- else if (filePath.endsWith(".json")) {
148
- parsed = JSON.parse(fileContent);
149
- }
150
- else if (filePath.endsWith(".toml")) {
151
- parsed = smolToml.parse(fileContent);
152
- }
153
- else if (filePath.endsWith(".ini")) {
154
- parsed = ini.parse(fileContent);
155
- }
156
- return parsed;
172
+ return parsers[ext](fileContent);
157
173
  });
158
174
  }
159
175
 
160
176
  class ReassembleXMLFileHandler {
177
+ reassemble(xmlAttributes) {
178
+ return __awaiter(this, void 0, void 0, function* () {
179
+ const { filePath, fileExtension, postPurge = false } = xmlAttributes;
180
+ if (!(yield this._validateDirectory(filePath)))
181
+ return;
182
+ logger.debug(`Parsing directory to reassemble: ${filePath}`);
183
+ const parsedXmlObjects = yield this.processFilesInDirectory(filePath);
184
+ if (!parsedXmlObjects.length) {
185
+ this._logEmptyParseError(filePath);
186
+ return;
187
+ }
188
+ const mergedXml = mergeXmlElements(parsedXmlObjects);
189
+ const finalXml = buildXMLString(mergedXml);
190
+ const outputPath = this._getOutputPath(filePath, fileExtension);
191
+ yield promises.writeFile(outputPath, finalXml, "utf-8");
192
+ if (postPurge)
193
+ yield promises.rm(filePath, { recursive: true });
194
+ });
195
+ }
161
196
  processFilesInDirectory(dirPath) {
162
197
  return __awaiter(this, void 0, void 0, function* () {
163
198
  const parsedXmlObjects = [];
164
199
  const files = yield promises.readdir(dirPath);
165
- files.sort((fileA, fileB) => {
166
- const fullNameA = fileA.split(".")[0].toLowerCase();
167
- const fullNameB = fileB.split(".")[0].toLowerCase();
168
- return fullNameA.localeCompare(fullNameB);
169
- });
170
- for (const file of files) {
200
+ const sortedFiles = this._sortFilesByBaseName(files);
201
+ for (const file of sortedFiles) {
171
202
  const filePath = posix.join(dirPath, file);
172
203
  const fileStat = yield promises.stat(filePath);
173
- if (fileStat.isFile()) {
174
- if (/\.(xml|json|json5|ya?ml|toml|ini)$/.test(file)) {
175
- const parsedObject = yield parseToXmlObject(filePath);
176
- if (parsedObject === undefined)
177
- continue;
178
- parsedXmlObjects.push(parsedObject);
179
- }
204
+ if (fileStat.isFile() && this._isParsableFile(file)) {
205
+ const parsed = yield parseToXmlObject(filePath);
206
+ if (parsed)
207
+ parsedXmlObjects.push(parsed);
180
208
  }
181
209
  else if (fileStat.isDirectory()) {
182
- const subParsedObjects = yield this.processFilesInDirectory(filePath);
183
- parsedXmlObjects.push(...subParsedObjects);
210
+ const subParsed = yield this.processFilesInDirectory(filePath);
211
+ parsedXmlObjects.push(...subParsed);
184
212
  }
185
213
  }
186
214
  return parsedXmlObjects;
187
215
  });
188
216
  }
189
- reassemble(xmlAttributes) {
217
+ _sortFilesByBaseName(files) {
218
+ return files.sort((a, b) => a.split(".")[0].localeCompare(b.split(".")[0]));
219
+ }
220
+ _isParsableFile(fileName) {
221
+ return /\.(xml|json|json5|ya?ml|toml|ini)$/i.test(fileName);
222
+ }
223
+ _validateDirectory(path) {
190
224
  return __awaiter(this, void 0, void 0, function* () {
191
- const { filePath, fileExtension, postPurge = false } = xmlAttributes;
192
- const fileStat = yield promises.stat(filePath);
193
- if (!fileStat.isDirectory()) {
194
- logger.error(`The provided path to reassemble is not a directory: ${filePath}`);
195
- return;
196
- }
197
- logger.debug(`Parsing directory to reassemble: ${filePath}`);
198
- const parsedXmlObjects = yield this.processFilesInDirectory(filePath);
199
- if (!parsedXmlObjects.length) {
200
- logger.error(`No files under ${filePath} were parsed successfully. A reassembled XML file was not created.`);
201
- return;
202
- }
203
- const mergedXml = mergeXmlElements(parsedXmlObjects);
204
- const xmlContent = buildXMLString(mergedXml);
205
- const finalXml = xmlContent;
206
- const parentDirectory = posix.dirname(filePath);
207
- const subdirectoryBasename = posix.basename(filePath);
208
- const fileName = fileExtension
209
- ? `${subdirectoryBasename}.${fileExtension}`
210
- : `${subdirectoryBasename}.xml`;
211
- const outputPath = posix.join(parentDirectory, fileName);
212
- yield promises.writeFile(outputPath, finalXml, "utf-8");
213
- if (postPurge) {
214
- yield promises.rm(filePath, { recursive: true });
225
+ const stats = yield promises.stat(path);
226
+ if (!stats.isDirectory()) {
227
+ logger.error(`The provided path to reassemble is not a directory: ${path}`);
228
+ return false;
215
229
  }
230
+ return true;
216
231
  });
217
232
  }
233
+ _logEmptyParseError(path) {
234
+ logger.error(`No files under ${path} were parsed successfully. A reassembled XML file was not created.`);
235
+ }
236
+ _getOutputPath(dirPath, extension) {
237
+ const parentDir = posix.dirname(dirPath);
238
+ const baseName = posix.basename(dirPath);
239
+ const fileName = `${baseName}.${extension !== null && extension !== void 0 ? extension : "xml"}`;
240
+ return posix.join(parentDir, fileName);
241
+ }
218
242
  }
219
243
 
220
244
  function buildXMLString(element) {
@@ -271,32 +295,39 @@
271
295
  }
272
296
 
273
297
  function parseUniqueIdElement(element, uniqueIdElements) {
274
- if (uniqueIdElements === undefined) {
298
+ var _a, _b;
299
+ if (!uniqueIdElements) {
275
300
  return createShortHash(element);
276
301
  }
277
- const uniqueIdElementsArray = uniqueIdElements.split(",");
278
- for (const fieldName of uniqueIdElementsArray) {
279
- if (element[fieldName] !== undefined) {
280
- if (typeof element[fieldName] === "string") {
281
- return element[fieldName];
282
- }
302
+ const id = (_b = (_a = findDirectFieldMatch(element, uniqueIdElements.split(","))) !== null && _a !== void 0 ? _a : findNestedFieldMatch(element, uniqueIdElements)) !== null && _b !== void 0 ? _b : createShortHash(element);
303
+ return id;
304
+ }
305
+ function findDirectFieldMatch(element, fieldNames) {
306
+ for (const name of fieldNames) {
307
+ const value = element[name];
308
+ if (typeof value === "string") {
309
+ return value;
283
310
  }
284
311
  }
312
+ }
313
+ function findNestedFieldMatch(element, uniqueIdElements) {
285
314
  for (const key in element) {
286
- if (typeof element[key] === "object" && element[key] !== null) {
287
- const childFieldName = parseUniqueIdElement(element[key], uniqueIdElements);
288
- if (childFieldName !== undefined) {
289
- return childFieldName;
290
- }
291
- }
315
+ const child = element[key];
316
+ if (!isObject(child))
317
+ continue;
318
+ const result = parseUniqueIdElement(child, uniqueIdElements);
319
+ if (result)
320
+ return result;
292
321
  }
293
- return createShortHash(element);
322
+ }
323
+ function isObject(value) {
324
+ return typeof value === "object" && value !== null && !Array.isArray(value);
294
325
  }
295
326
  function createShortHash(element) {
296
- const hash = node_crypto.createHash("sha256");
297
- hash.update(JSON.stringify(element));
298
- const fullHash = hash.digest("hex");
299
- return fullHash.slice(0, 8);
327
+ const hash = node_crypto.createHash("sha256")
328
+ .update(JSON.stringify(element))
329
+ .digest("hex");
330
+ return hash.slice(0, 8);
300
331
  }
301
332
 
302
333
  function buildDisassembledFile(_a) {
@@ -328,135 +359,135 @@
328
359
  });
329
360
  }
330
361
 
331
- function parseElement$1(params) {
332
- return __awaiter(this, void 0, void 0, function* () {
333
- const { element, disassembledPath, uniqueIdElements, rootElementName, rootAttributes, key, leafCount, hasNestedElements, format, xmlDeclaration, } = params;
334
- if (typeof element === "object" && element !== null) {
335
- yield buildDisassembledFile({
336
- content: element,
337
- disassembledPath,
338
- subdirectory: key,
339
- wrapKey: key,
340
- rootElementName,
341
- rootAttributes,
342
- xmlDeclaration,
343
- format,
344
- uniqueIdElements,
345
- });
346
- return [{}, leafCount, true];
347
- }
348
- const leafContent = {
349
- [key]: [element],
350
- };
351
- return [leafContent, leafCount + 1, hasNestedElements];
352
- });
353
- }
354
-
355
- function extractRootAttributes(rootElement) {
356
- const attributesOnly = {};
357
- for (const [attrKey, attrValue] of Object.entries(rootElement)) {
358
- if (attrKey.startsWith("@")) {
359
- attributesOnly[attrKey] = attrValue;
362
+ function extractRootAttributes(element) {
363
+ const attributes = {};
364
+ for (const [key, value] of Object.entries(element)) {
365
+ if (key.startsWith("@") && typeof value === "string") {
366
+ attributes[key] = value;
360
367
  }
361
368
  }
362
- return attributesOnly;
369
+ return attributes;
363
370
  }
364
371
 
365
- function buildDisassembledFiles$1(filePath, disassembledPath, uniqueIdElements, baseName, postPurge, format) {
372
+ function parseElementUnified(params) {
366
373
  return __awaiter(this, void 0, void 0, function* () {
367
- var _a;
368
- const parsedXml = yield parseXML(filePath);
369
- if (parsedXml === undefined)
370
- return;
371
- const rawDeclaration = parsedXml["?xml"];
372
- const xmlDeclaration = typeof rawDeclaration === "object" && rawDeclaration !== null
373
- ? rawDeclaration
374
- : undefined;
375
- const rootElementName = Object.keys(parsedXml).find((k) => k !== "?xml");
376
- const rootElement = parsedXml[rootElementName];
377
- const rootAttributes = extractRootAttributes(rootElement);
378
- let leafContent = {};
379
- let leafCount = 0;
380
- let hasNestedElements = false;
381
- for (const key of Object.keys(rootElement).filter((k) => !k.startsWith("@"))) {
382
- const elements = Array.isArray(rootElement[key])
383
- ? rootElement[key]
384
- : [rootElement[key]];
385
- for (const element of elements) {
386
- const [parsedLeafContent, updatedLeafCount, updatedHasNestedElements] = yield parseElement$1({
387
- element,
374
+ const { element, disassembledPath, uniqueIdElements, rootElementName, rootAttributes, key, leafCount, hasNestedElements, format, xmlDeclaration, strategy, } = params;
375
+ const isArray = Array.isArray(element);
376
+ const isNestedObject = typeof element === "object" &&
377
+ element !== null &&
378
+ Object.keys(element).some((k) => !k.startsWith("#"));
379
+ const isNested = isArray || isNestedObject;
380
+ if (isNested) {
381
+ if (strategy === "grouped-by-tag") {
382
+ return {
383
+ leafContent: {},
384
+ leafCount,
385
+ hasNestedElements: true,
386
+ nestedGroups: { [key]: [element] },
387
+ };
388
+ }
389
+ else {
390
+ yield buildDisassembledFile({
391
+ content: element,
388
392
  disassembledPath,
389
- uniqueIdElements,
393
+ subdirectory: key,
394
+ wrapKey: key,
390
395
  rootElementName,
391
396
  rootAttributes,
392
- key,
393
- leafCount,
394
- hasNestedElements,
395
- format,
396
397
  xmlDeclaration,
398
+ format,
399
+ uniqueIdElements,
397
400
  });
398
- const newContent = parsedLeafContent[key];
399
- if (newContent !== undefined) {
400
- leafContent[key] = [
401
- ...((_a = leafContent[key]) !== null && _a !== void 0 ? _a : []),
402
- ...newContent,
403
- ];
404
- }
405
- leafCount = updatedLeafCount;
406
- hasNestedElements = updatedHasNestedElements;
401
+ return {
402
+ leafContent: {},
403
+ leafCount,
404
+ hasNestedElements: true,
405
+ };
407
406
  }
408
407
  }
409
- if (!hasNestedElements) {
410
- logger.error(`The XML file ${filePath} only has leaf elements. This file will not be disassembled.`);
408
+ return {
409
+ leafContent: {
410
+ [key]: [element],
411
+ },
412
+ leafCount: leafCount + 1,
413
+ hasNestedElements,
414
+ };
415
+ });
416
+ }
417
+
418
+ function buildDisassembledFilesUnified(_a) {
419
+ return __awaiter(this, arguments, void 0, function* ({ filePath, disassembledPath, baseName, postPurge, format, uniqueIdElements, strategy, }) {
420
+ const parsedXml = yield parseXML(filePath);
421
+ if (!parsedXml)
411
422
  return;
412
- }
413
- if (leafCount > 0) {
414
- yield buildDisassembledFile({
415
- content: leafContent,
423
+ const { rootElementName, rootElement, xmlDeclaration } = getRootInfo(parsedXml);
424
+ const rootAttributes = extractRootAttributes(rootElement);
425
+ const keyOrder = Object.keys(rootElement).filter((k) => !k.startsWith("@"));
426
+ const { leafContent, nestedGroups, leafCount, hasNestedElements } = yield disassembleElementKeys({
427
+ rootElement,
428
+ keyOrder,
429
+ disassembledPath,
430
+ rootElementName,
431
+ rootAttributes,
432
+ xmlDeclaration,
433
+ uniqueIdElements,
434
+ strategy,
435
+ format,
436
+ });
437
+ if (shouldAbortForLeafOnly(leafCount, hasNestedElements, filePath))
438
+ return;
439
+ yield writeNestedGroups(nestedGroups, strategy, {
440
+ disassembledPath,
441
+ rootElementName,
442
+ rootAttributes,
443
+ xmlDeclaration,
444
+ format,
445
+ });
446
+ yield writeLeafContentIfAny({
447
+ leafCount,
448
+ leafContent,
449
+ strategy,
450
+ keyOrder,
451
+ options: {
416
452
  disassembledPath,
417
453
  outputFileName: `${baseName}.${format}`,
418
454
  rootElementName,
419
455
  rootAttributes,
420
456
  xmlDeclaration,
421
457
  format,
422
- });
423
- }
458
+ },
459
+ });
424
460
  if (postPurge) {
425
461
  yield promises.unlink(filePath);
426
462
  }
427
463
  });
428
464
  }
429
-
430
- function parseElement(params) {
431
- return __awaiter(this, void 0, void 0, function* () {
432
- const { element, key, hasNestedElements } = params;
433
- const nestedGroups = {};
434
- const isArray = Array.isArray(element);
435
- const isNestedObject = typeof element === "object" &&
436
- element !== null &&
437
- Object.keys(element).some((k) => !k.startsWith("#"));
438
- const isNested = isArray || isNestedObject;
439
- if (isNested) {
440
- nestedGroups[key] = [element];
441
- return {
442
- leafContent: {},
443
- leafCount: params.leafCount,
444
- hasNestedElements: true,
445
- nestedGroups,
446
- };
447
- }
448
- const leafContent = {
449
- [key]: [element],
450
- };
451
- return {
452
- leafContent,
453
- leafCount: params.leafCount + 1,
454
- hasNestedElements,
455
- nestedGroups,
456
- };
465
+ function shouldAbortForLeafOnly(leafCount, hasNestedElements, filePath) {
466
+ if (!hasNestedElements && leafCount > 0) {
467
+ logger.error(`The XML file ${filePath} only has leaf elements. This file will not be disassembled.`);
468
+ return true;
469
+ }
470
+ return false;
471
+ }
472
+ function writeLeafContentIfAny(_a) {
473
+ return __awaiter(this, arguments, void 0, function* ({ leafCount, leafContent, strategy, keyOrder, options, }) {
474
+ if (leafCount === 0)
475
+ return;
476
+ const finalLeafContent = strategy === "grouped-by-tag"
477
+ ? orderXmlElementKeys(leafContent, keyOrder)
478
+ : leafContent;
479
+ yield buildDisassembledFile(Object.assign({ content: finalLeafContent }, options));
457
480
  });
458
481
  }
459
-
482
+ function getRootInfo(parsedXml) {
483
+ const rawDeclaration = parsedXml["?xml"];
484
+ const xmlDeclaration = typeof rawDeclaration === "object" && rawDeclaration !== null
485
+ ? rawDeclaration
486
+ : undefined;
487
+ const rootElementName = Object.keys(parsedXml).find((k) => k !== "?xml");
488
+ const rootElement = parsedXml[rootElementName];
489
+ return { rootElementName, rootElement, xmlDeclaration };
490
+ }
460
491
  function orderXmlElementKeys(content, keyOrder) {
461
492
  const ordered = {};
462
493
  for (const key of keyOrder) {
@@ -466,84 +497,69 @@
466
497
  }
467
498
  return ordered;
468
499
  }
469
- function buildDisassembledFiles(filePath, disassembledPath, baseName, postPurge, format) {
470
- return __awaiter(this, void 0, void 0, function* () {
471
- var _a;
472
- const parsedXml = yield parseXML(filePath);
473
- if (parsedXml === undefined)
474
- return;
475
- const rawDeclaration = parsedXml["?xml"];
476
- const xmlDeclaration = typeof rawDeclaration === "object" && rawDeclaration !== null
477
- ? rawDeclaration
478
- : undefined;
479
- const rootElementName = Object.keys(parsedXml).find((k) => k !== "?xml");
480
- const rootElement = parsedXml[rootElementName];
481
- const rootAttributes = extractRootAttributes(rootElement);
500
+ function disassembleElementKeys(_a) {
501
+ return __awaiter(this, arguments, void 0, function* ({ rootElement, keyOrder, disassembledPath, rootElementName, rootAttributes, xmlDeclaration, uniqueIdElements, strategy, format, }) {
502
+ var _b, _c;
482
503
  let leafContent = {};
504
+ let nestedGroups = {};
483
505
  let leafCount = 0;
484
506
  let hasNestedElements = false;
485
- const nestedGroups = {};
486
- const keyOrder = Object.keys(rootElement).filter((k) => !k.startsWith("@"));
487
507
  for (const key of keyOrder) {
488
508
  const elements = Array.isArray(rootElement[key])
489
509
  ? rootElement[key]
490
510
  : [rootElement[key]];
491
511
  for (const element of elements) {
492
- const result = yield parseElement({
512
+ const result = yield parseElementUnified({
493
513
  element,
514
+ disassembledPath,
515
+ uniqueIdElements,
516
+ rootElementName,
517
+ rootAttributes,
494
518
  key,
495
519
  leafCount,
496
- hasNestedElements});
497
- if (Object.keys(result.leafContent).length > 0) {
498
- const newContent = result.leafContent[key];
499
- if (newContent !== undefined) {
500
- leafContent[key] = [
501
- ...((_a = leafContent[key]) !== null && _a !== void 0 ? _a : []),
502
- ...newContent,
520
+ hasNestedElements,
521
+ format,
522
+ xmlDeclaration,
523
+ strategy,
524
+ });
525
+ if (result.leafContent[key]) {
526
+ leafContent[key] = [
527
+ ...((_b = leafContent[key]) !== null && _b !== void 0 ? _b : []),
528
+ ...result.leafContent[key],
529
+ ];
530
+ }
531
+ if (strategy === "grouped-by-tag" && result.nestedGroups) {
532
+ for (const tag in result.nestedGroups) {
533
+ nestedGroups[tag] = [
534
+ ...((_c = nestedGroups[tag]) !== null && _c !== void 0 ? _c : []),
535
+ ...result.nestedGroups[tag],
503
536
  ];
504
537
  }
505
538
  }
506
539
  leafCount = result.leafCount;
507
540
  hasNestedElements = result.hasNestedElements;
508
- for (const tag in result.nestedGroups) {
509
- if (!nestedGroups[tag])
510
- nestedGroups[tag] = [];
511
- nestedGroups[tag].push(...result.nestedGroups[tag]);
512
- }
513
541
  }
514
542
  }
515
- if (!hasNestedElements && leafCount > 0) {
516
- logger.error(`The XML file ${filePath} only has leaf elements. This file will not be disassembled.`);
543
+ return { leafContent, nestedGroups, leafCount, hasNestedElements };
544
+ });
545
+ }
546
+ function writeNestedGroups(nestedGroups, strategy, options) {
547
+ return __awaiter(this, void 0, void 0, function* () {
548
+ if (strategy !== "grouped-by-tag")
517
549
  return;
518
- }
519
550
  for (const tag in nestedGroups) {
520
551
  yield buildDisassembledFile({
521
552
  content: nestedGroups[tag],
522
- disassembledPath,
523
- outputFileName: `${tag}.${format}`,
553
+ disassembledPath: options.disassembledPath,
554
+ outputFileName: `${tag}.${options.format}`,
524
555
  wrapKey: tag,
525
556
  isGroupedArray: true,
526
- rootElementName,
527
- rootAttributes,
528
- xmlDeclaration,
529
- format,
557
+ rootElementName: options.rootElementName,
558
+ rootAttributes: options.rootAttributes,
559
+ xmlDeclaration: options.xmlDeclaration,
560
+ format: options.format,
530
561
  });
531
562
  }
532
- if (leafCount > 0) {
533
- const orderedLeafContent = orderXmlElementKeys(leafContent, keyOrder);
534
- yield buildDisassembledFile({
535
- content: orderedLeafContent,
536
- disassembledPath,
537
- outputFileName: `${baseName}.${format}`,
538
- rootElementName,
539
- rootAttributes,
540
- xmlDeclaration,
541
- format,
542
- });
543
- }
544
- if (postPurge) {
545
- yield promises.unlink(filePath);
546
- }
547
563
  });
548
564
  }
549
565
 
@@ -553,75 +569,97 @@
553
569
  }
554
570
  disassemble(xmlAttributes) {
555
571
  return __awaiter(this, void 0, void 0, function* () {
556
- const { filePath, uniqueIdElements, strategy = "unique-id", prePurge = false, postPurge = false, ignorePath = ".xmldisassemblerignore", format = "xml", } = xmlAttributes;
557
- const resolvedIgnorePath = node_path.resolve(ignorePath);
558
- if (node_fs.existsSync(resolvedIgnorePath)) {
559
- const content = yield promises.readFile(resolvedIgnorePath);
560
- this.ign.add(content.toString());
572
+ let { filePath, uniqueIdElements, strategy = "unique-id", prePurge = false, postPurge = false, ignorePath = ".xmldisassemblerignore", format = "xml", } = xmlAttributes;
573
+ if (!["unique-id", "grouped-by-tag"].includes(strategy)) {
574
+ logger.warn(`Unsupported strategy "${strategy}", defaulting to "unique-id".`);
575
+ strategy = "unique-id";
561
576
  }
577
+ yield this._loadIgnoreRules(ignorePath);
562
578
  const fileStat = yield promises.stat(filePath);
563
579
  const relativePath = this.posixPath(node_path.relative(process.cwd(), filePath));
564
580
  if (fileStat.isFile()) {
565
- const resolvedPath = node_path.resolve(filePath);
566
- if (!resolvedPath.endsWith(".xml")) {
567
- logger.error(`The file path provided is not an XML file: ${resolvedPath}`);
568
- return;
569
- }
570
- if (this.ign.ignores(relativePath)) {
571
- logger.warn(`File ignored by ${ignorePath}: ${resolvedPath}`);
572
- return;
573
- }
574
- const dirPath = node_path.dirname(resolvedPath);
575
- yield this.processFile({
576
- dirPath,
577
- strategy,
578
- filePath: resolvedPath,
581
+ yield this._handleFile(filePath, relativePath, {
579
582
  uniqueIdElements,
583
+ strategy,
580
584
  prePurge,
581
585
  postPurge,
582
586
  format,
583
587
  });
584
588
  }
585
589
  else if (fileStat.isDirectory()) {
586
- const subFiles = yield promises.readdir(filePath);
587
- for (const subFile of subFiles) {
588
- const subFilePath = node_path.join(filePath, subFile);
589
- const relativeSubFilePath = this.posixPath(node_path.relative(process.cwd(), subFilePath));
590
- if (subFilePath.endsWith(".xml") &&
591
- !this.ign.ignores(relativeSubFilePath)) {
592
- yield this.processFile({
593
- dirPath: filePath,
594
- strategy,
595
- filePath: subFilePath,
596
- uniqueIdElements,
597
- prePurge,
598
- postPurge,
599
- format,
600
- });
601
- }
602
- else if (this.ign.ignores(relativeSubFilePath)) {
603
- logger.warn(`File ignored by ${ignorePath}: ${subFilePath}`);
604
- }
590
+ yield this._handleDirectory(filePath, {
591
+ uniqueIdElements,
592
+ strategy,
593
+ prePurge,
594
+ postPurge,
595
+ format,
596
+ ignorePath,
597
+ });
598
+ }
599
+ });
600
+ }
601
+ _loadIgnoreRules(ignorePath) {
602
+ return __awaiter(this, void 0, void 0, function* () {
603
+ const resolvedIgnorePath = node_path.resolve(ignorePath);
604
+ if (node_fs.existsSync(resolvedIgnorePath)) {
605
+ const content = yield promises.readFile(resolvedIgnorePath);
606
+ this.ign.add(content.toString());
607
+ }
608
+ });
609
+ }
610
+ _handleFile(filePath, relativePath, options) {
611
+ return __awaiter(this, void 0, void 0, function* () {
612
+ const resolvedPath = node_path.resolve(filePath);
613
+ if (!this._isXmlFile(resolvedPath)) {
614
+ logger.error(`The file path provided is not an XML file: ${resolvedPath}`);
615
+ return;
616
+ }
617
+ if (this.ign.ignores(relativePath)) {
618
+ logger.warn(`File ignored by ignore rules: ${resolvedPath}`);
619
+ return;
620
+ }
621
+ const dirPath = node_path.dirname(resolvedPath);
622
+ yield this.processFile(Object.assign(Object.assign({}, options), { dirPath, filePath: resolvedPath }));
623
+ });
624
+ }
625
+ _handleDirectory(dirPath, options) {
626
+ return __awaiter(this, void 0, void 0, function* () {
627
+ const subFiles = yield promises.readdir(dirPath);
628
+ for (const subFile of subFiles) {
629
+ const subFilePath = node_path.join(dirPath, subFile);
630
+ const relativeSubFilePath = this.posixPath(node_path.relative(process.cwd(), subFilePath));
631
+ if (this._isXmlFile(subFilePath) &&
632
+ !this.ign.ignores(relativeSubFilePath)) {
633
+ yield this.processFile(Object.assign(Object.assign({}, options), { dirPath, filePath: subFilePath }));
634
+ }
635
+ else if (this.ign.ignores(relativeSubFilePath)) {
636
+ logger.warn(`File ignored by ignore rules: ${subFilePath}`);
605
637
  }
606
638
  }
607
639
  });
608
640
  }
641
+ _isXmlFile(filePath) {
642
+ return filePath.endsWith(".xml");
643
+ }
609
644
  processFile(xmlAttributes) {
610
645
  return __awaiter(this, void 0, void 0, function* () {
611
646
  const { dirPath, strategy, filePath, uniqueIdElements, prePurge, postPurge, format, } = xmlAttributes;
612
647
  logger.debug(`Parsing file to disassemble: ${filePath}`);
613
648
  const fullName = node_path.basename(filePath, node_path.extname(filePath));
614
649
  const baseName = fullName.split(".")[0];
615
- let outputPath;
616
- outputPath = node_path.join(dirPath, baseName);
617
- if (prePurge && node_fs.existsSync(outputPath))
650
+ const outputPath = node_path.join(dirPath, baseName);
651
+ if (prePurge && node_fs.existsSync(outputPath)) {
618
652
  yield promises.rm(outputPath, { recursive: true });
619
- if (strategy === "grouped-by-tag") {
620
- yield buildDisassembledFiles(filePath, outputPath, fullName, postPurge, format);
621
- }
622
- else {
623
- yield buildDisassembledFiles$1(filePath, outputPath, uniqueIdElements, fullName, postPurge, format);
624
653
  }
654
+ yield buildDisassembledFilesUnified({
655
+ filePath,
656
+ disassembledPath: outputPath,
657
+ uniqueIdElements,
658
+ baseName: fullName,
659
+ postPurge,
660
+ format,
661
+ strategy,
662
+ });
625
663
  });
626
664
  }
627
665
  posixPath(path) {