|
5 | 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
6 | 6 | */
|
7 | 7 | import { basename, join } from 'path';
|
8 |
| -import { parse } from 'fast-xml-parser'; |
| 8 | +import { parse, validate } from 'fast-xml-parser'; |
9 | 9 | import { get, getString, JsonMap } from '@salesforce/ts-types';
|
10 | 10 | import { baseName, normalizeToArray, parseMetadataXml, trimUntil } from '../utils';
|
11 | 11 | import { DEFAULT_PACKAGE_ROOT_SFDX } from '../common';
|
12 | 12 | import { SfdxFileFormat } from '../convert';
|
13 | 13 | import { MetadataType } from '../registry';
|
14 |
| -import { TypeInferenceError } from '../errors'; |
| 14 | +import { LibraryError, TypeInferenceError } from '../errors'; |
15 | 15 | import { DestructiveChangesType } from '../collections';
|
16 | 16 | import { filePathsFromMetadataComponent } from '../utils/filePathGenerator';
|
17 | 17 | import { MetadataComponent, VirtualDirectory } from './types';
|
@@ -116,17 +116,17 @@ export class SourceComponent implements MetadataComponent {
|
116 | 116 | public async parseXml<T = JsonMap>(xmlFilePath?: string): Promise<T> {
|
117 | 117 | const xml = xmlFilePath ?? this.xml;
|
118 | 118 | if (xml) {
|
119 |
| - const contents = await this.tree.readFile(xml); |
120 |
| - return this.parse<T>(contents.toString()); |
| 119 | + const contents = (await this.tree.readFile(xml)).toString(); |
| 120 | + return this.parseAndValidateXML(contents, xml); |
121 | 121 | }
|
122 | 122 | return {} as T;
|
123 | 123 | }
|
124 | 124 |
|
125 | 125 | public parseXmlSync<T = JsonMap>(xmlFilePath?: string): T {
|
126 | 126 | const xml = xmlFilePath ?? this.xml;
|
127 | 127 | if (xml) {
|
128 |
| - const contents = this.tree.readFileSync(xml); |
129 |
| - return this.parse<T>(contents.toString()); |
| 128 | + const contents = this.tree.readFileSync(xml).toString(); |
| 129 | + return this.parseAndValidateXML(contents, xml); |
130 | 130 | }
|
131 | 131 | return {} as T;
|
132 | 132 | }
|
@@ -232,6 +232,24 @@ export class SourceComponent implements MetadataComponent {
|
232 | 232 | }
|
233 | 233 | }
|
234 | 234 |
|
| 235 | + private parseAndValidateXML<T = JsonMap>(contents: string, path: string): T { |
| 236 | + try { |
| 237 | + return this.parse<T>(contents); |
| 238 | + } catch (e) { |
| 239 | + // only attempt validating once there's an error to avoid the performance hit of validating every file |
| 240 | + const validation = validate(contents); |
| 241 | + if (validation !== true) { |
| 242 | + throw new LibraryError('invalid_xml_parsing', [ |
| 243 | + path, |
| 244 | + validation.err.msg, |
| 245 | + validation.err.line.toString(), |
| 246 | + validation.err.code, |
| 247 | + ]); |
| 248 | + } |
| 249 | + throw e; |
| 250 | + } |
| 251 | + } |
| 252 | + |
235 | 253 | private getDecomposedChildren(dirPath: string): SourceComponent[] {
|
236 | 254 | const children: SourceComponent[] = [];
|
237 | 255 | for (const fsPath of this.walk(dirPath)) {
|
|
0 commit comments