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