xml-disassembler 1.10.2 → 1.10.4

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
@@ -51,34 +51,6 @@
51
51
  };
52
52
  const JSON_PARSER_OPTION = Object.assign(Object.assign({}, XML_PARSER_OPTION), { format: true, indentBy: INDENT, suppressBooleanAttributes: false, suppressEmptyNode: false });
53
53
 
54
- function buildReassembledFile(combinedXmlContents, reassembledPath, xmlElement, xmlRootElementHeader, xmlDeclarationStr) {
55
- return __awaiter(this, void 0, void 0, function* () {
56
- let finalXmlContent = combinedXmlContents.join("\n");
57
- const escapedXmlDeclaration = xmlDeclarationStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
58
- const xmlDeclarationLineRegex = new RegExp(`^\\s*${escapedXmlDeclaration}\\s*$`, "gm");
59
- finalXmlContent = finalXmlContent.replace(xmlDeclarationLineRegex, "");
60
- finalXmlContent = finalXmlContent.replace(new RegExp(`<${xmlElement}\\s*[^>]*>`, "g"), "");
61
- finalXmlContent = finalXmlContent.replace(new RegExp(`</${xmlElement}>`, "g"), "");
62
- finalXmlContent = finalXmlContent.replace(/<!\[CDATA\[\s*([\s\S]*?)\s*]]>/g, function (_, cdataContent) {
63
- const trimmedContent = cdataContent.trim();
64
- const lines = trimmedContent.split("\n");
65
- const indentedLines = lines.map(function (line) {
66
- return line.replace(/^\s*/, "");
67
- });
68
- return ("<![CDATA[\n" + INDENT + indentedLines.join("\n" + INDENT) + "\n]]>");
69
- });
70
- finalXmlContent = finalXmlContent.replace(/(\n\s*){2,}/g, `\n${INDENT}`);
71
- const closeTag = `</${xmlElement}>`;
72
- yield promises.writeFile(reassembledPath, `${xmlDeclarationStr}\n${xmlRootElementHeader}${finalXmlContent}${closeTag}`);
73
- logger.debug(`Created reassembled file: ${reassembledPath}`);
74
- });
75
- }
76
-
77
- function buildXMLString(element) {
78
- const xmlBuilder = new fastXmlParser.XMLBuilder(JSON_PARSER_OPTION);
79
- return xmlBuilder.build(element).trimEnd();
80
- }
81
-
82
54
  function stripWhitespaceTextNodes(node) {
83
55
  if (Array.isArray(node)) {
84
56
  return node.map(stripWhitespaceTextNodes).filter((entry) => {
@@ -132,45 +104,10 @@
132
104
  });
133
105
  }
134
106
 
135
- function buildRootElementHeader(rootElement, rootElementName) {
136
- let rootElementHeader = `<${rootElementName}`;
137
- for (const [attrKey, attrValue] of Object.entries(rootElement)) {
138
- if (attrKey.startsWith("@")) {
139
- const cleanAttrKey = attrKey.slice(2);
140
- rootElementHeader += ` ${cleanAttrKey}="${String(attrValue)}"`;
141
- }
142
- }
143
- rootElementHeader += ">";
144
- return rootElementHeader;
145
- }
146
-
147
- function buildXMLDeclaration(parsedXml) {
148
- let xmlDeclarationStr = XML_DEFAULT_DECLARATION;
149
- if (parsedXml["?xml"]) {
150
- const xmlDeclaration = parsedXml["?xml"];
151
- const attributes = Object.entries(xmlDeclaration)
152
- .map(([key, value]) => `${key.replace("@_", "")}="${value}"`)
153
- .join(" ");
154
- xmlDeclarationStr = `<?xml ${attributes}?>`;
155
- }
156
- return xmlDeclarationStr;
157
- }
158
-
159
- function parseRootElement(xmlParsed) {
160
- return __awaiter(this, void 0, void 0, function* () {
161
- const xmlDeclarationStr = buildXMLDeclaration(xmlParsed);
162
- const rootElementName = Object.keys(xmlParsed)[1];
163
- const rootElement = xmlParsed[rootElementName];
164
- const rootElementHeader = buildRootElementHeader(rootElement, rootElementName);
165
- return [rootElementName, rootElementHeader, xmlDeclarationStr];
166
- });
167
- }
168
-
169
107
  class ReassembleXMLFileHandler {
170
108
  processFilesInDirectory(dirPath) {
171
109
  return __awaiter(this, void 0, void 0, function* () {
172
- const combinedXmlContents = [];
173
- let rootResult = undefined;
110
+ const parsedXmlObjects = [];
174
111
  const files = yield promises.readdir(dirPath);
175
112
  files.sort((fileA, fileB) => {
176
113
  const fullNameA = fileA.split(".")[0].toLowerCase();
@@ -185,47 +122,44 @@
185
122
  const parsedObject = yield this.parseToXmlObject(filePath);
186
123
  if (parsedObject === undefined)
187
124
  continue;
188
- const rootResultFromFile = yield parseRootElement(parsedObject);
189
- rootResult = rootResultFromFile;
190
- const combinedXmlString = buildXMLString(parsedObject);
191
- combinedXmlContents.push(combinedXmlString);
125
+ parsedXmlObjects.push(parsedObject);
192
126
  }
193
127
  }
194
128
  else if (fileStat.isDirectory()) {
195
- const [subCombinedXmlContents, subRootResult] = yield this.processFilesInDirectory(filePath);
196
- combinedXmlContents.push(...subCombinedXmlContents);
197
- rootResult = subRootResult;
129
+ const subParsedObjects = yield this.processFilesInDirectory(filePath);
130
+ parsedXmlObjects.push(...subParsedObjects);
198
131
  }
199
132
  }
200
- return [combinedXmlContents, rootResult];
133
+ return parsedXmlObjects;
201
134
  });
202
135
  }
203
136
  reassemble(xmlAttributes) {
204
137
  return __awaiter(this, void 0, void 0, function* () {
205
138
  const { filePath, fileExtension, postPurge = false } = xmlAttributes;
206
- let combinedXmlContents = [];
207
139
  const fileStat = yield promises.stat(filePath);
208
140
  if (!fileStat.isDirectory()) {
209
141
  logger.error(`The provided path to reassemble is not a directory: ${filePath}`);
210
142
  return;
211
143
  }
212
144
  logger.debug(`Parsing directory to reassemble: ${filePath}`);
213
- const [subCombinedXmlContents, rootResult] = yield this.processFilesInDirectory(filePath);
214
- combinedXmlContents = subCombinedXmlContents;
145
+ const parsedXmlObjects = yield this.processFilesInDirectory(filePath);
146
+ if (!parsedXmlObjects.length) {
147
+ logger.error(`No files under ${filePath} were parsed successfully. A reassembled XML file was not created.`);
148
+ return;
149
+ }
150
+ const { xml: mergedXml, declaration } = mergeXmlElements(parsedXmlObjects);
151
+ const xmlDeclarationStr = createXmlDeclaration(declaration);
152
+ const xmlContent = buildXMLString(mergedXml);
153
+ const finalXml = xmlDeclarationStr + xmlContent;
215
154
  const parentDirectory = posix.dirname(filePath);
216
155
  const subdirectoryBasename = posix.basename(filePath);
217
156
  const fileName = fileExtension
218
157
  ? `${subdirectoryBasename}.${fileExtension}`
219
158
  : `${subdirectoryBasename}.xml`;
220
159
  const outputPath = posix.join(parentDirectory, fileName);
221
- if (rootResult !== undefined) {
222
- const [rootElementName, rootElementHeader, xmlDeclarationStr] = rootResult;
223
- yield buildReassembledFile(combinedXmlContents, outputPath, rootElementName, rootElementHeader, xmlDeclarationStr);
224
- if (postPurge)
225
- yield promises.rm(filePath, { recursive: true });
226
- }
227
- else {
228
- logger.error(`No files under ${filePath} were parsed successfully. A reassembled XML file was not created.`);
160
+ yield promises.writeFile(outputPath, finalXml, "utf-8");
161
+ if (postPurge) {
162
+ yield promises.rm(filePath, { recursive: true });
229
163
  }
230
164
  });
231
165
  }
@@ -255,6 +189,51 @@
255
189
  });
256
190
  }
257
191
  }
192
+ function mergeXmlElements(elements) {
193
+ if (elements.length === 0)
194
+ throw new Error("No elements to merge.");
195
+ const first = elements[0];
196
+ const declaration = first['?xml'];
197
+ const rootKey = Object.keys(first).find((k) => k !== '?xml');
198
+ if (!rootKey) {
199
+ throw new Error("No root element found in the provided XML elements.");
200
+ }
201
+ const mergedContent = {};
202
+ for (const element of elements) {
203
+ const current = element[rootKey];
204
+ for (const [childKey, value] of Object.entries(current)) {
205
+ if (Array.isArray(value)) {
206
+ mergedContent[childKey] = mergedContent[childKey]
207
+ ? mergedContent[childKey].concat(value)
208
+ : [...value];
209
+ }
210
+ else if (typeof value === "object") {
211
+ mergedContent[childKey] = mergedContent[childKey]
212
+ ? [].concat(mergedContent[childKey], value)
213
+ : [value];
214
+ }
215
+ else {
216
+ if (!mergedContent.hasOwnProperty(childKey)) {
217
+ mergedContent[childKey] = value;
218
+ }
219
+ }
220
+ }
221
+ }
222
+ return {
223
+ xml: { [rootKey]: mergedContent },
224
+ declaration,
225
+ };
226
+ }
227
+ function createXmlDeclaration(declaration) {
228
+ let declarationStr = `${XML_DEFAULT_DECLARATION}\n`;
229
+ if (declaration) {
230
+ const attributes = Object.entries(declaration)
231
+ .map(([key, value]) => `${key.replace("@_", "")}="${value}"`)
232
+ .join(" ");
233
+ declarationStr = `<?xml ${attributes}?>\n`;
234
+ }
235
+ return declarationStr;
236
+ }
258
237
 
259
238
  function parseUniqueIdElement(element, uniqueIdElements) {
260
239
  if (uniqueIdElements === undefined) {
@@ -285,6 +264,11 @@
285
264
  return fullHash.slice(0, 8);
286
265
  }
287
266
 
267
+ function buildXMLString(element) {
268
+ const xmlBuilder = new fastXmlParser.XMLBuilder(JSON_PARSER_OPTION);
269
+ return xmlBuilder.build(element).trimEnd();
270
+ }
271
+
288
272
  function transformToYaml(xmlPath) {
289
273
  return __awaiter(this, void 0, void 0, function* () {
290
274
  const parsedXml = yield parseXML(xmlPath);
@@ -371,26 +355,27 @@
371
355
 
372
356
  function parseElement$1(params) {
373
357
  return __awaiter(this, void 0, void 0, function* () {
374
- const { element, disassembledPath, uniqueIdElements, rootElementName, rootAttributes, key, indent, leafContent, leafCount, hasNestedElements, xmlDeclarationStr, format, } = params;
375
- if (typeof element === "object") {
358
+ const { element, disassembledPath, uniqueIdElements, rootElementName, rootAttributes, key, leafCount, hasNestedElements, xmlDeclarationStr, format, } = params;
359
+ if (typeof element === "object" && element !== null) {
376
360
  yield buildNestedFile(element, disassembledPath, uniqueIdElements, rootElementName, rootAttributes, key, xmlDeclarationStr, format);
377
- return [leafContent, leafCount, true];
378
- }
379
- else {
380
- const updatedLeafContent = `${leafContent}${indent}<${key}>${String(element)}</${key}>\n`;
381
- return [updatedLeafContent, leafCount + 1, hasNestedElements];
361
+ return [{}, leafCount, true];
382
362
  }
363
+ const leafContent = {
364
+ [key]: element,
365
+ };
366
+ return [leafContent, leafCount + 1, hasNestedElements];
383
367
  });
384
368
  }
385
369
 
386
- function buildLeafFile(leafContent, disassembledPath, baseName, rootElementName, rootElementHeader, xmlDeclarationStr, format) {
370
+ function buildLeafFile(leafContent, disassembledPath, baseName, rootElementName, rootAttributes, xmlDeclarationStr, format) {
387
371
  return __awaiter(this, void 0, void 0, function* () {
388
- let leafFile = `${xmlDeclarationStr}\n`;
389
- leafFile += `${rootElementHeader}\n`;
390
- leafFile += leafContent;
391
- leafFile += `</${rootElementName}>`;
392
372
  const leafOutputPath = posix.join(disassembledPath, `${baseName}.xml`);
393
- yield promises.writeFile(leafOutputPath, leafFile);
373
+ yield promises.mkdir(disassembledPath, { recursive: true });
374
+ const wrappedXml = {
375
+ [rootElementName]: Object.assign(Object.assign({}, rootAttributes), leafContent),
376
+ };
377
+ const serialized = `${xmlDeclarationStr}\n${buildXMLString(wrappedXml)}`;
378
+ yield promises.writeFile(leafOutputPath, serialized);
394
379
  logger.debug(`Created disassembled file: ${leafOutputPath}`);
395
380
  const transformer = getTransformer(format);
396
381
  if (transformer) {
@@ -400,6 +385,18 @@
400
385
  });
401
386
  }
402
387
 
388
+ function buildXMLDeclaration(parsedXml) {
389
+ let xmlDeclarationStr = XML_DEFAULT_DECLARATION;
390
+ if (parsedXml["?xml"]) {
391
+ const xmlDeclaration = parsedXml["?xml"];
392
+ const attributes = Object.entries(xmlDeclaration)
393
+ .map(([key, value]) => `${key.replace("@_", "")}="${value}"`)
394
+ .join(" ");
395
+ xmlDeclarationStr = `<?xml ${attributes}?>`;
396
+ }
397
+ return xmlDeclarationStr;
398
+ }
399
+
403
400
  function extractRootAttributes(rootElement) {
404
401
  const attributesOnly = {};
405
402
  for (const [attrKey, attrValue] of Object.entries(rootElement)) {
@@ -410,7 +407,7 @@
410
407
  return attributesOnly;
411
408
  }
412
409
 
413
- function buildDisassembledFiles$1(filePath, disassembledPath, uniqueIdElements, baseName, indent, postPurge, format) {
410
+ function buildDisassembledFiles$1(filePath, disassembledPath, uniqueIdElements, baseName, postPurge, format) {
414
411
  return __awaiter(this, void 0, void 0, function* () {
415
412
  const parsedXml = yield parseXML(filePath);
416
413
  if (parsedXml === undefined)
@@ -418,49 +415,40 @@
418
415
  const rootElementName = Object.keys(parsedXml)[1];
419
416
  const xmlDeclarationStr = buildXMLDeclaration(parsedXml);
420
417
  const rootElement = parsedXml[rootElementName];
421
- const rootElementHeader = buildRootElementHeader(rootElement, rootElementName);
422
418
  const rootAttributes = extractRootAttributes(rootElement);
423
- let leafContent = "";
419
+ let leafContent = {};
424
420
  let leafCount = 0;
425
421
  let hasNestedElements = false;
426
- for (const key of Object.keys(rootElement).filter((key) => !key.startsWith("@"))) {
427
- if (Array.isArray(rootElement[key])) {
428
- for (const element of rootElement[key]) {
429
- const [updatedLeafContent, updatedLeafCount, updatedHasNestedElements] = yield parseElement$1({
430
- element,
431
- disassembledPath,
432
- uniqueIdElements,
433
- rootElementName,
434
- rootAttributes,
435
- key,
436
- indent,
437
- leafContent,
438
- leafCount,
439
- hasNestedElements,
440
- xmlDeclarationStr,
441
- format,
442
- });
443
- leafContent = updatedLeafContent;
444
- leafCount = updatedLeafCount;
445
- hasNestedElements = updatedHasNestedElements;
446
- }
447
- }
448
- else {
449
- const [updatedLeafContent, updatedLeafCount, updatedHasNestedElements] = yield parseElement$1({
450
- element: rootElement[key],
422
+ for (const key of Object.keys(rootElement).filter((k) => !k.startsWith("@"))) {
423
+ const elements = Array.isArray(rootElement[key])
424
+ ? rootElement[key]
425
+ : [rootElement[key]];
426
+ for (const element of elements) {
427
+ const [parsedLeafContent, updatedLeafCount, updatedHasNestedElements] = yield parseElement$1({
428
+ element,
451
429
  disassembledPath,
452
430
  uniqueIdElements,
453
431
  rootElementName,
454
432
  rootAttributes,
455
433
  key,
456
- indent,
457
- leafContent,
458
434
  leafCount,
459
435
  hasNestedElements,
460
436
  xmlDeclarationStr,
461
437
  format,
462
438
  });
463
- leafContent = updatedLeafContent;
439
+ const newContent = parsedLeafContent[key];
440
+ if (newContent !== undefined) {
441
+ const existing = leafContent[key];
442
+ const existingArray = Array.isArray(existing)
443
+ ? existing
444
+ : existing !== undefined
445
+ ? [existing]
446
+ : [];
447
+ const incomingArray = Array.isArray(newContent)
448
+ ? newContent
449
+ : [newContent];
450
+ leafContent[key] = [...existingArray, ...incomingArray];
451
+ }
464
452
  leafCount = updatedLeafCount;
465
453
  hasNestedElements = updatedHasNestedElements;
466
454
  }
@@ -470,38 +458,41 @@
470
458
  return;
471
459
  }
472
460
  if (leafCount > 0) {
473
- yield buildLeafFile(leafContent, disassembledPath, baseName, rootElementName, rootElementHeader, xmlDeclarationStr, format);
461
+ yield buildLeafFile(leafContent, disassembledPath, baseName, rootElementName, rootAttributes, xmlDeclarationStr, format);
474
462
  }
475
463
  if (postPurge) {
476
- promises.unlink(filePath);
464
+ yield promises.unlink(filePath);
477
465
  }
478
466
  });
479
467
  }
480
468
 
481
469
  function parseElement(params) {
482
470
  return __awaiter(this, void 0, void 0, function* () {
483
- const { element, key, indent, leafContent, leafCount, hasNestedElements } = params;
471
+ const { element, key, hasNestedElements } = params;
484
472
  const nestedGroups = {};
485
- if (typeof element === "object") {
486
- if (!nestedGroups[key])
487
- nestedGroups[key] = [];
488
- nestedGroups[key].push(element);
473
+ const isArray = Array.isArray(element);
474
+ const isObjectWithMultipleFields = typeof element === "object" &&
475
+ element !== null &&
476
+ Object.keys(element).length > 1;
477
+ const isNested = isArray || isObjectWithMultipleFields;
478
+ if (isNested) {
479
+ nestedGroups[key] = [element];
489
480
  return {
490
- leafContent,
491
- leafCount,
481
+ leafContent: {},
482
+ leafCount: params.leafCount,
492
483
  hasNestedElements: true,
493
484
  nestedGroups,
494
485
  };
495
486
  }
496
- else {
497
- const updatedLeafContent = `${leafContent}${indent}<${key}>${String(element)}</${key}>\n`;
498
- return {
499
- leafContent: updatedLeafContent,
500
- leafCount: leafCount + 1,
501
- hasNestedElements,
502
- nestedGroups,
503
- };
504
- }
487
+ const leafContent = {
488
+ [key]: element,
489
+ };
490
+ return {
491
+ leafContent,
492
+ leafCount: params.leafCount + 1,
493
+ hasNestedElements,
494
+ nestedGroups,
495
+ };
505
496
  });
506
497
  }
507
498
 
@@ -523,21 +514,31 @@
523
514
  });
524
515
  }
525
516
 
526
- function buildDisassembledFiles(filePath, disassembledPath, baseName, indent, postPurge, format) {
517
+ function orderXmlElementKeys(content, keyOrder) {
518
+ const ordered = {};
519
+ for (const key of keyOrder) {
520
+ if (content[key] !== undefined) {
521
+ ordered[key] = content[key];
522
+ }
523
+ }
524
+ return ordered;
525
+ }
526
+ function buildDisassembledFiles(filePath, disassembledPath, baseName, postPurge, format) {
527
527
  return __awaiter(this, void 0, void 0, function* () {
528
+ var _a;
528
529
  const parsedXml = yield parseXML(filePath);
529
530
  if (parsedXml === undefined)
530
531
  return;
531
532
  const rootElementName = Object.keys(parsedXml)[1];
532
533
  const xmlDeclarationStr = buildXMLDeclaration(parsedXml);
533
534
  const rootElement = parsedXml[rootElementName];
534
- const rootElementHeader = buildRootElementHeader(rootElement, rootElementName);
535
535
  const rootAttributes = extractRootAttributes(rootElement);
536
- let leafContent = "";
536
+ let leafContent = {};
537
537
  let leafCount = 0;
538
538
  let hasNestedElements = false;
539
539
  const nestedGroups = {};
540
- for (const key of Object.keys(rootElement).filter((key) => !key.startsWith("@"))) {
540
+ const keyOrder = Object.keys(parsedXml[rootElementName]).filter((k) => !k.startsWith("@"));
541
+ for (const key of keyOrder) {
541
542
  const elements = Array.isArray(rootElement[key])
542
543
  ? rootElement[key]
543
544
  : [rootElement[key]];
@@ -545,21 +546,33 @@
545
546
  const result = yield parseElement({
546
547
  element,
547
548
  key,
548
- indent,
549
- leafContent,
550
549
  leafCount,
551
550
  hasNestedElements});
552
- leafContent = result.leafContent;
551
+ if (Object.keys(result.leafContent).length > 0) {
552
+ const newContent = result.leafContent[key];
553
+ if (newContent !== undefined) {
554
+ const existing = leafContent[key];
555
+ const existingArray = Array.isArray(existing)
556
+ ? existing
557
+ : existing !== undefined
558
+ ? [existing]
559
+ : [];
560
+ const incomingArray = Array.isArray(newContent)
561
+ ? newContent
562
+ : [newContent];
563
+ leafContent[key] = [...existingArray, ...incomingArray];
564
+ }
565
+ }
553
566
  leafCount = result.leafCount;
554
567
  hasNestedElements = result.hasNestedElements;
555
568
  for (const tag in result.nestedGroups) {
556
569
  if (!nestedGroups[tag])
557
570
  nestedGroups[tag] = [];
558
- nestedGroups[tag].push(...result.nestedGroups[tag]);
571
+ nestedGroups[tag].push(...((_a = result.nestedGroups[tag]) !== null && _a !== void 0 ? _a : []));
559
572
  }
560
573
  }
561
574
  }
562
- if (!hasNestedElements) {
575
+ if (!hasNestedElements && leafCount > 0) {
563
576
  logger.error(`The XML file ${filePath} only has leaf elements. This file will not be disassembled.`);
564
577
  return;
565
578
  }
@@ -567,7 +580,8 @@
567
580
  yield buildGroupedNestedFile(tag, nestedGroups[tag], disassembledPath, rootElementName, rootAttributes, xmlDeclarationStr, format);
568
581
  }
569
582
  if (leafCount > 0) {
570
- yield buildLeafFile(leafContent, disassembledPath, baseName, rootElementName, rootElementHeader, xmlDeclarationStr, format);
583
+ const orderedLeafContent = orderXmlElementKeys(leafContent, keyOrder);
584
+ yield buildLeafFile(orderedLeafContent, disassembledPath, baseName, rootElementName, rootAttributes, xmlDeclarationStr, format);
571
585
  }
572
586
  if (postPurge) {
573
587
  yield promises.unlink(filePath);
@@ -645,10 +659,10 @@
645
659
  if (prePurge && node_fs.existsSync(outputPath))
646
660
  yield promises.rm(outputPath, { recursive: true });
647
661
  if (strategy === "grouped-by-tag") {
648
- yield buildDisassembledFiles(filePath, outputPath, fullName, INDENT, postPurge, format);
662
+ yield buildDisassembledFiles(filePath, outputPath, fullName, postPurge, format);
649
663
  }
650
664
  else {
651
- yield buildDisassembledFiles$1(filePath, outputPath, uniqueIdElements, fullName, INDENT, postPurge, format);
665
+ yield buildDisassembledFiles$1(filePath, outputPath, uniqueIdElements, fullName, postPurge, format);
652
666
  }
653
667
  });
654
668
  }