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.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) {
@@ -297,13 +321,16 @@ function findDirectFieldMatch(element, fieldNames) {
297
321
  function findNestedFieldMatch(element, uniqueIdElements) {
298
322
  for (const key in element) {
299
323
  const child = element[key];
300
- if (typeof child === "object" && child !== null) {
301
- const result = parseUniqueIdElement(child, uniqueIdElements);
302
- if (result)
303
- return result;
304
- }
324
+ if (!isObject(child))
325
+ continue;
326
+ const result = parseUniqueIdElement(child, uniqueIdElements);
327
+ if (result)
328
+ return result;
305
329
  }
306
330
  }
331
+ function isObject(value) {
332
+ return typeof value === "object" && value !== null && !Array.isArray(value);
333
+ }
307
334
  function createShortHash(element) {
308
335
  const hash = createHash("sha256")
309
336
  .update(JSON.stringify(element))
@@ -415,10 +442,8 @@ function buildDisassembledFilesUnified(_a) {
415
442
  strategy,
416
443
  format,
417
444
  });
418
- if (!hasNestedElements && leafCount > 0) {
419
- logger.error(`The XML file ${filePath} only has leaf elements. This file will not be disassembled.`);
445
+ if (shouldAbortForLeafOnly(leafCount, hasNestedElements, filePath))
420
446
  return;
421
- }
422
447
  yield writeNestedGroups(nestedGroups, strategy, {
423
448
  disassembledPath,
424
449
  rootElementName,
@@ -426,25 +451,42 @@ function buildDisassembledFilesUnified(_a) {
426
451
  xmlDeclaration,
427
452
  format,
428
453
  });
429
- if (leafCount > 0) {
430
- const finalLeafContent = strategy === "grouped-by-tag"
431
- ? orderXmlElementKeys(leafContent, keyOrder)
432
- : leafContent;
433
- yield buildDisassembledFile({
434
- content: finalLeafContent,
454
+ yield writeLeafContentIfAny({
455
+ leafCount,
456
+ leafContent,
457
+ strategy,
458
+ keyOrder,
459
+ options: {
435
460
  disassembledPath,
436
461
  outputFileName: `${baseName}.${format}`,
437
462
  rootElementName,
438
463
  rootAttributes,
439
464
  xmlDeclaration,
440
465
  format,
441
- });
442
- }
466
+ },
467
+ });
443
468
  if (postPurge) {
444
469
  yield unlink(filePath);
445
470
  }
446
471
  });
447
472
  }
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));
488
+ });
489
+ }
448
490
  function getRootInfo(parsedXml) {
449
491
  const rawDeclaration = parsedXml["?xml"];
450
492
  const xmlDeclaration = typeof rawDeclaration === "object" && rawDeclaration !== null
@@ -540,68 +582,83 @@ class DisassembleXMLFileHandler {
540
582
  logger.warn(`Unsupported strategy "${strategy}", defaulting to "unique-id".`);
541
583
  strategy = "unique-id";
542
584
  }
543
- const resolvedIgnorePath = resolve(ignorePath);
544
- if (existsSync(resolvedIgnorePath)) {
545
- const content = yield readFile(resolvedIgnorePath);
546
- this.ign.add(content.toString());
547
- }
585
+ yield this._loadIgnoreRules(ignorePath);
548
586
  const fileStat = yield stat(filePath);
549
587
  const relativePath = this.posixPath(relative(process.cwd(), filePath));
550
588
  if (fileStat.isFile()) {
551
- const resolvedPath = resolve(filePath);
552
- if (!resolvedPath.endsWith(".xml")) {
553
- logger.error(`The file path provided is not an XML file: ${resolvedPath}`);
554
- return;
555
- }
556
- if (this.ign.ignores(relativePath)) {
557
- logger.warn(`File ignored by ${ignorePath}: ${resolvedPath}`);
558
- return;
559
- }
560
- const dirPath = dirname$1(resolvedPath);
561
- yield this.processFile({
562
- dirPath,
563
- strategy,
564
- filePath: resolvedPath,
589
+ yield this._handleFile(filePath, relativePath, {
565
590
  uniqueIdElements,
591
+ strategy,
566
592
  prePurge,
567
593
  postPurge,
568
594
  format,
569
595
  });
570
596
  }
571
597
  else if (fileStat.isDirectory()) {
572
- const subFiles = yield readdir(filePath);
573
- for (const subFile of subFiles) {
574
- const subFilePath = join$1(filePath, subFile);
575
- const relativeSubFilePath = this.posixPath(relative(process.cwd(), subFilePath));
576
- if (subFilePath.endsWith(".xml") &&
577
- !this.ign.ignores(relativeSubFilePath)) {
578
- yield this.processFile({
579
- dirPath: filePath,
580
- strategy,
581
- filePath: subFilePath,
582
- uniqueIdElements,
583
- prePurge,
584
- postPurge,
585
- format,
586
- });
587
- }
588
- else if (this.ign.ignores(relativeSubFilePath)) {
589
- logger.warn(`File ignored by ${ignorePath}: ${subFilePath}`);
590
- }
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}`);
591
645
  }
592
646
  }
593
647
  });
594
648
  }
649
+ _isXmlFile(filePath) {
650
+ return filePath.endsWith(".xml");
651
+ }
595
652
  processFile(xmlAttributes) {
596
653
  return __awaiter(this, void 0, void 0, function* () {
597
654
  const { dirPath, strategy, filePath, uniqueIdElements, prePurge, postPurge, format, } = xmlAttributes;
598
655
  logger.debug(`Parsing file to disassemble: ${filePath}`);
599
656
  const fullName = basename$1(filePath, extname(filePath));
600
657
  const baseName = fullName.split(".")[0];
601
- let outputPath;
602
- outputPath = join$1(dirPath, baseName);
603
- if (prePurge && existsSync(outputPath))
658
+ const outputPath = join$1(dirPath, baseName);
659
+ if (prePurge && existsSync(outputPath)) {
604
660
  yield rm(outputPath, { recursive: true });
661
+ }
605
662
  yield buildDisassembledFilesUnified({
606
663
  filePath,
607
664
  disassembledPath: outputPath,