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.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) {
@@ -289,13 +313,16 @@
289
313
  function findNestedFieldMatch(element, uniqueIdElements) {
290
314
  for (const key in element) {
291
315
  const child = element[key];
292
- if (typeof child === "object" && child !== null) {
293
- const result = parseUniqueIdElement(child, uniqueIdElements);
294
- if (result)
295
- return result;
296
- }
316
+ if (!isObject(child))
317
+ continue;
318
+ const result = parseUniqueIdElement(child, uniqueIdElements);
319
+ if (result)
320
+ return result;
297
321
  }
298
322
  }
323
+ function isObject(value) {
324
+ return typeof value === "object" && value !== null && !Array.isArray(value);
325
+ }
299
326
  function createShortHash(element) {
300
327
  const hash = node_crypto.createHash("sha256")
301
328
  .update(JSON.stringify(element))
@@ -407,10 +434,8 @@
407
434
  strategy,
408
435
  format,
409
436
  });
410
- if (!hasNestedElements && leafCount > 0) {
411
- logger.error(`The XML file ${filePath} only has leaf elements. This file will not be disassembled.`);
437
+ if (shouldAbortForLeafOnly(leafCount, hasNestedElements, filePath))
412
438
  return;
413
- }
414
439
  yield writeNestedGroups(nestedGroups, strategy, {
415
440
  disassembledPath,
416
441
  rootElementName,
@@ -418,25 +443,42 @@
418
443
  xmlDeclaration,
419
444
  format,
420
445
  });
421
- if (leafCount > 0) {
422
- const finalLeafContent = strategy === "grouped-by-tag"
423
- ? orderXmlElementKeys(leafContent, keyOrder)
424
- : leafContent;
425
- yield buildDisassembledFile({
426
- content: finalLeafContent,
446
+ yield writeLeafContentIfAny({
447
+ leafCount,
448
+ leafContent,
449
+ strategy,
450
+ keyOrder,
451
+ options: {
427
452
  disassembledPath,
428
453
  outputFileName: `${baseName}.${format}`,
429
454
  rootElementName,
430
455
  rootAttributes,
431
456
  xmlDeclaration,
432
457
  format,
433
- });
434
- }
458
+ },
459
+ });
435
460
  if (postPurge) {
436
461
  yield promises.unlink(filePath);
437
462
  }
438
463
  });
439
464
  }
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));
480
+ });
481
+ }
440
482
  function getRootInfo(parsedXml) {
441
483
  const rawDeclaration = parsedXml["?xml"];
442
484
  const xmlDeclaration = typeof rawDeclaration === "object" && rawDeclaration !== null
@@ -532,68 +574,83 @@
532
574
  logger.warn(`Unsupported strategy "${strategy}", defaulting to "unique-id".`);
533
575
  strategy = "unique-id";
534
576
  }
535
- const resolvedIgnorePath = node_path.resolve(ignorePath);
536
- if (node_fs.existsSync(resolvedIgnorePath)) {
537
- const content = yield promises.readFile(resolvedIgnorePath);
538
- this.ign.add(content.toString());
539
- }
577
+ yield this._loadIgnoreRules(ignorePath);
540
578
  const fileStat = yield promises.stat(filePath);
541
579
  const relativePath = this.posixPath(node_path.relative(process.cwd(), filePath));
542
580
  if (fileStat.isFile()) {
543
- const resolvedPath = node_path.resolve(filePath);
544
- if (!resolvedPath.endsWith(".xml")) {
545
- logger.error(`The file path provided is not an XML file: ${resolvedPath}`);
546
- return;
547
- }
548
- if (this.ign.ignores(relativePath)) {
549
- logger.warn(`File ignored by ${ignorePath}: ${resolvedPath}`);
550
- return;
551
- }
552
- const dirPath = node_path.dirname(resolvedPath);
553
- yield this.processFile({
554
- dirPath,
555
- strategy,
556
- filePath: resolvedPath,
581
+ yield this._handleFile(filePath, relativePath, {
557
582
  uniqueIdElements,
583
+ strategy,
558
584
  prePurge,
559
585
  postPurge,
560
586
  format,
561
587
  });
562
588
  }
563
589
  else if (fileStat.isDirectory()) {
564
- const subFiles = yield promises.readdir(filePath);
565
- for (const subFile of subFiles) {
566
- const subFilePath = node_path.join(filePath, subFile);
567
- const relativeSubFilePath = this.posixPath(node_path.relative(process.cwd(), subFilePath));
568
- if (subFilePath.endsWith(".xml") &&
569
- !this.ign.ignores(relativeSubFilePath)) {
570
- yield this.processFile({
571
- dirPath: filePath,
572
- strategy,
573
- filePath: subFilePath,
574
- uniqueIdElements,
575
- prePurge,
576
- postPurge,
577
- format,
578
- });
579
- }
580
- else if (this.ign.ignores(relativeSubFilePath)) {
581
- logger.warn(`File ignored by ${ignorePath}: ${subFilePath}`);
582
- }
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}`);
583
637
  }
584
638
  }
585
639
  });
586
640
  }
641
+ _isXmlFile(filePath) {
642
+ return filePath.endsWith(".xml");
643
+ }
587
644
  processFile(xmlAttributes) {
588
645
  return __awaiter(this, void 0, void 0, function* () {
589
646
  const { dirPath, strategy, filePath, uniqueIdElements, prePurge, postPurge, format, } = xmlAttributes;
590
647
  logger.debug(`Parsing file to disassemble: ${filePath}`);
591
648
  const fullName = node_path.basename(filePath, node_path.extname(filePath));
592
649
  const baseName = fullName.split(".")[0];
593
- let outputPath;
594
- outputPath = node_path.join(dirPath, baseName);
595
- if (prePurge && node_fs.existsSync(outputPath))
650
+ const outputPath = node_path.join(dirPath, baseName);
651
+ if (prePurge && node_fs.existsSync(outputPath)) {
596
652
  yield promises.rm(outputPath, { recursive: true });
653
+ }
597
654
  yield buildDisassembledFilesUnified({
598
655
  filePath,
599
656
  disassembledPath: outputPath,