xml-disassembler 1.10.12 → 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.cjs CHANGED
@@ -55,34 +55,49 @@ function mergeXmlElements(elements) {
55
55
  const rootKey = Object.keys(first).find((k) => k !== "?xml");
56
56
  const mergedContent = {};
57
57
  for (const element of elements) {
58
- const current = element[rootKey];
59
- for (const [childKey, value] of Object.entries(current)) {
60
- if (Array.isArray(value)) {
61
- mergedContent[childKey] = [...value];
62
- }
63
- else if (typeof value === "object") {
64
- if (Array.isArray(mergedContent[childKey])) {
65
- mergedContent[childKey].push(value);
66
- }
67
- else if (mergedContent[childKey]) {
68
- mergedContent[childKey] = [mergedContent[childKey], value];
69
- }
70
- else {
71
- mergedContent[childKey] = value;
72
- }
73
- }
74
- else {
75
- if (!mergedContent.hasOwnProperty(childKey)) {
76
- mergedContent[childKey] = value;
77
- }
78
- }
58
+ mergeElementContent(mergedContent, element[rootKey]);
59
+ }
60
+ return buildFinalXmlElement(first["?xml"], rootKey, mergedContent);
61
+ }
62
+ function mergeElementContent(target, source) {
63
+ for (const [key, value] of Object.entries(source)) {
64
+ if (Array.isArray(value)) {
65
+ mergeArrayValue(target, key, value);
66
+ }
67
+ else if (isMergeableObject(value)) {
68
+ mergeObjectValue(target, key, value);
69
+ }
70
+ else {
71
+ mergePrimitiveValue(target, key, value);
79
72
  }
80
73
  }
81
- const declaration = first["?xml"];
82
- const finalMerged = declaration
83
- ? { "?xml": declaration, [rootKey]: mergedContent }
84
- : { [rootKey]: mergedContent };
85
- return finalMerged;
74
+ }
75
+ function mergeArrayValue(target, key, value) {
76
+ target[key] = [...value];
77
+ }
78
+ function mergeObjectValue(target, key, value) {
79
+ if (Array.isArray(target[key])) {
80
+ target[key].push(value);
81
+ }
82
+ else if (target[key]) {
83
+ target[key] = [target[key], value];
84
+ }
85
+ else {
86
+ target[key] = value;
87
+ }
88
+ }
89
+ function mergePrimitiveValue(target, key, value) {
90
+ if (!Object.prototype.hasOwnProperty.call(target, key)) {
91
+ target[key] = value;
92
+ }
93
+ }
94
+ function isMergeableObject(value) {
95
+ return typeof value === "object" && value !== null;
96
+ }
97
+ function buildFinalXmlElement(declaration, rootKey, content) {
98
+ return declaration
99
+ ? { "?xml": declaration, [rootKey]: content }
100
+ : { [rootKey]: content };
86
101
  }
87
102
 
88
103
  const INDENT = " ";
@@ -99,25 +114,33 @@ const XML_PARSER_OPTION = {
99
114
  };
100
115
  const JSON_PARSER_OPTION = Object.assign(Object.assign({}, XML_PARSER_OPTION), { format: true, indentBy: INDENT, suppressBooleanAttributes: false, suppressEmptyNode: false });
101
116
 
117
+ function isEmptyTextNode(key, value) {
118
+ return key === "#text" && typeof value === "string" && value.trim() === "";
119
+ }
120
+ function cleanArray(arr) {
121
+ return arr
122
+ .map(stripWhitespaceTextNodes)
123
+ .filter((entry) => !(typeof entry === "object" && Object.keys(entry).length === 0));
124
+ }
125
+ function cleanObject(obj) {
126
+ const result = {};
127
+ for (const key in obj) {
128
+ const value = obj[key];
129
+ if (isEmptyTextNode(key, value))
130
+ continue;
131
+ const cleaned = stripWhitespaceTextNodes(value);
132
+ if (cleaned !== undefined) {
133
+ result[key] = cleaned;
134
+ }
135
+ }
136
+ return result;
137
+ }
102
138
  function stripWhitespaceTextNodes(node) {
103
139
  if (Array.isArray(node)) {
104
- return node.map(stripWhitespaceTextNodes).filter((entry) => {
105
- return !(typeof entry === "object" && Object.keys(entry).length === 0);
106
- });
140
+ return cleanArray(node);
107
141
  }
108
142
  else if (typeof node === "object" && node !== null) {
109
- const result = {};
110
- for (const key in node) {
111
- const value = node[key];
112
- if (key === "#text" && typeof value === "string" && value.trim() === "") {
113
- continue;
114
- }
115
- const cleaned = stripWhitespaceTextNodes(value);
116
- if (cleaned !== undefined) {
117
- result[key] = cleaned;
118
- }
119
- }
120
- return result;
143
+ return cleanObject(node);
121
144
  }
122
145
  else {
123
146
  return node;
@@ -141,90 +164,91 @@ function parseXML(filePath) {
141
164
  });
142
165
  }
143
166
 
167
+ const parsers = {
168
+ ".yaml": yaml.parse,
169
+ ".yml": yaml.parse,
170
+ ".json": JSON.parse,
171
+ ".json5": json5.parse,
172
+ ".toml": smolToml.parse,
173
+ ".ini": ini.parse,
174
+ };
144
175
  function parseToXmlObject(filePath) {
145
176
  return __awaiter(this, void 0, void 0, function* () {
146
177
  if (filePath.endsWith(".xml")) {
147
178
  return yield parseXML(filePath);
148
179
  }
180
+ const ext = Object.keys(parsers).find((ext) => filePath.endsWith(ext));
149
181
  const fileContent = yield promises$1.readFile(filePath, "utf-8");
150
- let parsed;
151
- if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
152
- parsed = yaml.parse(fileContent);
153
- }
154
- else if (filePath.endsWith(".json5")) {
155
- parsed = json5.parse(fileContent);
156
- }
157
- else if (filePath.endsWith(".json")) {
158
- parsed = JSON.parse(fileContent);
159
- }
160
- else if (filePath.endsWith(".toml")) {
161
- parsed = smolToml.parse(fileContent);
162
- }
163
- else if (filePath.endsWith(".ini")) {
164
- parsed = ini.parse(fileContent);
165
- }
166
- return parsed;
182
+ return parsers[ext](fileContent);
167
183
  });
168
184
  }
169
185
 
170
186
  class ReassembleXMLFileHandler {
187
+ reassemble(xmlAttributes) {
188
+ return __awaiter(this, void 0, void 0, function* () {
189
+ const { filePath, fileExtension, postPurge = false } = xmlAttributes;
190
+ if (!(yield this._validateDirectory(filePath)))
191
+ return;
192
+ logger.debug(`Parsing directory to reassemble: ${filePath}`);
193
+ const parsedXmlObjects = yield this.processFilesInDirectory(filePath);
194
+ if (!parsedXmlObjects.length) {
195
+ this._logEmptyParseError(filePath);
196
+ return;
197
+ }
198
+ const mergedXml = mergeXmlElements(parsedXmlObjects);
199
+ const finalXml = buildXMLString(mergedXml);
200
+ const outputPath = this._getOutputPath(filePath, fileExtension);
201
+ yield promises.writeFile(outputPath, finalXml, "utf-8");
202
+ if (postPurge)
203
+ yield promises.rm(filePath, { recursive: true });
204
+ });
205
+ }
171
206
  processFilesInDirectory(dirPath) {
172
207
  return __awaiter(this, void 0, void 0, function* () {
173
208
  const parsedXmlObjects = [];
174
209
  const files = yield promises.readdir(dirPath);
175
- files.sort((fileA, fileB) => {
176
- const fullNameA = fileA.split(".")[0].toLowerCase();
177
- const fullNameB = fileB.split(".")[0].toLowerCase();
178
- return fullNameA.localeCompare(fullNameB);
179
- });
180
- for (const file of files) {
210
+ const sortedFiles = this._sortFilesByBaseName(files);
211
+ for (const file of sortedFiles) {
181
212
  const filePath = posix.join(dirPath, file);
182
213
  const fileStat = yield promises.stat(filePath);
183
- if (fileStat.isFile()) {
184
- if (/\.(xml|json|json5|ya?ml|toml|ini)$/.test(file)) {
185
- const parsedObject = yield parseToXmlObject(filePath);
186
- if (parsedObject === undefined)
187
- continue;
188
- parsedXmlObjects.push(parsedObject);
189
- }
214
+ if (fileStat.isFile() && this._isParsableFile(file)) {
215
+ const parsed = yield parseToXmlObject(filePath);
216
+ if (parsed)
217
+ parsedXmlObjects.push(parsed);
190
218
  }
191
219
  else if (fileStat.isDirectory()) {
192
- const subParsedObjects = yield this.processFilesInDirectory(filePath);
193
- parsedXmlObjects.push(...subParsedObjects);
220
+ const subParsed = yield this.processFilesInDirectory(filePath);
221
+ parsedXmlObjects.push(...subParsed);
194
222
  }
195
223
  }
196
224
  return parsedXmlObjects;
197
225
  });
198
226
  }
199
- reassemble(xmlAttributes) {
227
+ _sortFilesByBaseName(files) {
228
+ return files.sort((a, b) => a.split(".")[0].localeCompare(b.split(".")[0]));
229
+ }
230
+ _isParsableFile(fileName) {
231
+ return /\.(xml|json|json5|ya?ml|toml|ini)$/i.test(fileName);
232
+ }
233
+ _validateDirectory(path) {
200
234
  return __awaiter(this, void 0, void 0, function* () {
201
- const { filePath, fileExtension, postPurge = false } = xmlAttributes;
202
- const fileStat = yield promises.stat(filePath);
203
- if (!fileStat.isDirectory()) {
204
- logger.error(`The provided path to reassemble is not a directory: ${filePath}`);
205
- return;
206
- }
207
- logger.debug(`Parsing directory to reassemble: ${filePath}`);
208
- const parsedXmlObjects = yield this.processFilesInDirectory(filePath);
209
- if (!parsedXmlObjects.length) {
210
- logger.error(`No files under ${filePath} were parsed successfully. A reassembled XML file was not created.`);
211
- return;
212
- }
213
- const mergedXml = mergeXmlElements(parsedXmlObjects);
214
- const xmlContent = buildXMLString(mergedXml);
215
- const finalXml = xmlContent;
216
- const parentDirectory = posix.dirname(filePath);
217
- const subdirectoryBasename = posix.basename(filePath);
218
- const fileName = fileExtension
219
- ? `${subdirectoryBasename}.${fileExtension}`
220
- : `${subdirectoryBasename}.xml`;
221
- const outputPath = posix.join(parentDirectory, fileName);
222
- yield promises.writeFile(outputPath, finalXml, "utf-8");
223
- if (postPurge) {
224
- yield promises.rm(filePath, { recursive: true });
235
+ const stats = yield promises.stat(path);
236
+ if (!stats.isDirectory()) {
237
+ logger.error(`The provided path to reassemble is not a directory: ${path}`);
238
+ return false;
225
239
  }
240
+ return true;
226
241
  });
227
242
  }
243
+ _logEmptyParseError(path) {
244
+ logger.error(`No files under ${path} were parsed successfully. A reassembled XML file was not created.`);
245
+ }
246
+ _getOutputPath(dirPath, extension) {
247
+ const parentDir = posix.dirname(dirPath);
248
+ const baseName = posix.basename(dirPath);
249
+ const fileName = `${baseName}.${extension !== null && extension !== void 0 ? extension : "xml"}`;
250
+ return posix.join(parentDir, fileName);
251
+ }
228
252
  }
229
253
 
230
254
  function buildXMLString(element) {
@@ -299,13 +323,16 @@ function findDirectFieldMatch(element, fieldNames) {
299
323
  function findNestedFieldMatch(element, uniqueIdElements) {
300
324
  for (const key in element) {
301
325
  const child = element[key];
302
- if (typeof child === "object" && child !== null) {
303
- const result = parseUniqueIdElement(child, uniqueIdElements);
304
- if (result)
305
- return result;
306
- }
326
+ if (!isObject(child))
327
+ continue;
328
+ const result = parseUniqueIdElement(child, uniqueIdElements);
329
+ if (result)
330
+ return result;
307
331
  }
308
332
  }
333
+ function isObject(value) {
334
+ return typeof value === "object" && value !== null && !Array.isArray(value);
335
+ }
309
336
  function createShortHash(element) {
310
337
  const hash = node_crypto.createHash("sha256")
311
338
  .update(JSON.stringify(element))
@@ -417,10 +444,8 @@ function buildDisassembledFilesUnified(_a) {
417
444
  strategy,
418
445
  format,
419
446
  });
420
- if (!hasNestedElements && leafCount > 0) {
421
- logger.error(`The XML file ${filePath} only has leaf elements. This file will not be disassembled.`);
447
+ if (shouldAbortForLeafOnly(leafCount, hasNestedElements, filePath))
422
448
  return;
423
- }
424
449
  yield writeNestedGroups(nestedGroups, strategy, {
425
450
  disassembledPath,
426
451
  rootElementName,
@@ -428,25 +453,42 @@ function buildDisassembledFilesUnified(_a) {
428
453
  xmlDeclaration,
429
454
  format,
430
455
  });
431
- if (leafCount > 0) {
432
- const finalLeafContent = strategy === "grouped-by-tag"
433
- ? orderXmlElementKeys(leafContent, keyOrder)
434
- : leafContent;
435
- yield buildDisassembledFile({
436
- content: finalLeafContent,
456
+ yield writeLeafContentIfAny({
457
+ leafCount,
458
+ leafContent,
459
+ strategy,
460
+ keyOrder,
461
+ options: {
437
462
  disassembledPath,
438
463
  outputFileName: `${baseName}.${format}`,
439
464
  rootElementName,
440
465
  rootAttributes,
441
466
  xmlDeclaration,
442
467
  format,
443
- });
444
- }
468
+ },
469
+ });
445
470
  if (postPurge) {
446
471
  yield promises.unlink(filePath);
447
472
  }
448
473
  });
449
474
  }
475
+ function shouldAbortForLeafOnly(leafCount, hasNestedElements, filePath) {
476
+ if (!hasNestedElements && leafCount > 0) {
477
+ logger.error(`The XML file ${filePath} only has leaf elements. This file will not be disassembled.`);
478
+ return true;
479
+ }
480
+ return false;
481
+ }
482
+ function writeLeafContentIfAny(_a) {
483
+ return __awaiter(this, arguments, void 0, function* ({ leafCount, leafContent, strategy, keyOrder, options, }) {
484
+ if (leafCount === 0)
485
+ return;
486
+ const finalLeafContent = strategy === "grouped-by-tag"
487
+ ? orderXmlElementKeys(leafContent, keyOrder)
488
+ : leafContent;
489
+ yield buildDisassembledFile(Object.assign({ content: finalLeafContent }, options));
490
+ });
491
+ }
450
492
  function getRootInfo(parsedXml) {
451
493
  const rawDeclaration = parsedXml["?xml"];
452
494
  const xmlDeclaration = typeof rawDeclaration === "object" && rawDeclaration !== null
@@ -542,68 +584,83 @@ class DisassembleXMLFileHandler {
542
584
  logger.warn(`Unsupported strategy "${strategy}", defaulting to "unique-id".`);
543
585
  strategy = "unique-id";
544
586
  }
545
- const resolvedIgnorePath = node_path.resolve(ignorePath);
546
- if (node_fs.existsSync(resolvedIgnorePath)) {
547
- const content = yield promises.readFile(resolvedIgnorePath);
548
- this.ign.add(content.toString());
549
- }
587
+ yield this._loadIgnoreRules(ignorePath);
550
588
  const fileStat = yield promises.stat(filePath);
551
589
  const relativePath = this.posixPath(node_path.relative(process.cwd(), filePath));
552
590
  if (fileStat.isFile()) {
553
- const resolvedPath = node_path.resolve(filePath);
554
- if (!resolvedPath.endsWith(".xml")) {
555
- logger.error(`The file path provided is not an XML file: ${resolvedPath}`);
556
- return;
557
- }
558
- if (this.ign.ignores(relativePath)) {
559
- logger.warn(`File ignored by ${ignorePath}: ${resolvedPath}`);
560
- return;
561
- }
562
- const dirPath = node_path.dirname(resolvedPath);
563
- yield this.processFile({
564
- dirPath,
565
- strategy,
566
- filePath: resolvedPath,
591
+ yield this._handleFile(filePath, relativePath, {
567
592
  uniqueIdElements,
593
+ strategy,
568
594
  prePurge,
569
595
  postPurge,
570
596
  format,
571
597
  });
572
598
  }
573
599
  else if (fileStat.isDirectory()) {
574
- const subFiles = yield promises.readdir(filePath);
575
- for (const subFile of subFiles) {
576
- const subFilePath = node_path.join(filePath, subFile);
577
- const relativeSubFilePath = this.posixPath(node_path.relative(process.cwd(), subFilePath));
578
- if (subFilePath.endsWith(".xml") &&
579
- !this.ign.ignores(relativeSubFilePath)) {
580
- yield this.processFile({
581
- dirPath: filePath,
582
- strategy,
583
- filePath: subFilePath,
584
- uniqueIdElements,
585
- prePurge,
586
- postPurge,
587
- format,
588
- });
589
- }
590
- else if (this.ign.ignores(relativeSubFilePath)) {
591
- logger.warn(`File ignored by ${ignorePath}: ${subFilePath}`);
592
- }
600
+ yield this._handleDirectory(filePath, {
601
+ uniqueIdElements,
602
+ strategy,
603
+ prePurge,
604
+ postPurge,
605
+ format,
606
+ ignorePath,
607
+ });
608
+ }
609
+ });
610
+ }
611
+ _loadIgnoreRules(ignorePath) {
612
+ return __awaiter(this, void 0, void 0, function* () {
613
+ const resolvedIgnorePath = node_path.resolve(ignorePath);
614
+ if (node_fs.existsSync(resolvedIgnorePath)) {
615
+ const content = yield promises.readFile(resolvedIgnorePath);
616
+ this.ign.add(content.toString());
617
+ }
618
+ });
619
+ }
620
+ _handleFile(filePath, relativePath, options) {
621
+ return __awaiter(this, void 0, void 0, function* () {
622
+ const resolvedPath = node_path.resolve(filePath);
623
+ if (!this._isXmlFile(resolvedPath)) {
624
+ logger.error(`The file path provided is not an XML file: ${resolvedPath}`);
625
+ return;
626
+ }
627
+ if (this.ign.ignores(relativePath)) {
628
+ logger.warn(`File ignored by ignore rules: ${resolvedPath}`);
629
+ return;
630
+ }
631
+ const dirPath = node_path.dirname(resolvedPath);
632
+ yield this.processFile(Object.assign(Object.assign({}, options), { dirPath, filePath: resolvedPath }));
633
+ });
634
+ }
635
+ _handleDirectory(dirPath, options) {
636
+ return __awaiter(this, void 0, void 0, function* () {
637
+ const subFiles = yield promises.readdir(dirPath);
638
+ for (const subFile of subFiles) {
639
+ const subFilePath = node_path.join(dirPath, subFile);
640
+ const relativeSubFilePath = this.posixPath(node_path.relative(process.cwd(), subFilePath));
641
+ if (this._isXmlFile(subFilePath) &&
642
+ !this.ign.ignores(relativeSubFilePath)) {
643
+ yield this.processFile(Object.assign(Object.assign({}, options), { dirPath, filePath: subFilePath }));
644
+ }
645
+ else if (this.ign.ignores(relativeSubFilePath)) {
646
+ logger.warn(`File ignored by ignore rules: ${subFilePath}`);
593
647
  }
594
648
  }
595
649
  });
596
650
  }
651
+ _isXmlFile(filePath) {
652
+ return filePath.endsWith(".xml");
653
+ }
597
654
  processFile(xmlAttributes) {
598
655
  return __awaiter(this, void 0, void 0, function* () {
599
656
  const { dirPath, strategy, filePath, uniqueIdElements, prePurge, postPurge, format, } = xmlAttributes;
600
657
  logger.debug(`Parsing file to disassemble: ${filePath}`);
601
658
  const fullName = node_path.basename(filePath, node_path.extname(filePath));
602
659
  const baseName = fullName.split(".")[0];
603
- let outputPath;
604
- outputPath = node_path.join(dirPath, baseName);
605
- if (prePurge && node_fs.existsSync(outputPath))
660
+ const outputPath = node_path.join(dirPath, baseName);
661
+ if (prePurge && node_fs.existsSync(outputPath)) {
606
662
  yield promises.rm(outputPath, { recursive: true });
663
+ }
607
664
  yield buildDisassembledFilesUnified({
608
665
  filePath,
609
666
  disassembledPath: outputPath,