fast-xml-validator 1.4.2 → 1.4.3
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/lib/fxv.cjs +1 -1
- package/lib/fxv.d.cts +57 -27
- package/lib/fxv.min.js +1 -1
- package/lib/fxv.min.js.map +1 -1
- package/package.json +4 -2
- package/src/SyntaxValidator.js +27 -4
- package/src/fxv.d.ts +46 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-xml-validator",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "Validate XML for Syntax and Business rules without C/C++ based libraries",
|
|
5
5
|
"main": "./lib/fxv.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "c8 --reporter=lcov --reporter=text jasmine specs/*spec.js",
|
|
24
|
-
"bundle": "webpack --config webpack.cjs.config.js"
|
|
24
|
+
"bundle": "webpack --config webpack.cjs.config.js",
|
|
25
|
+
"codemap": "codebase-map scan && codebase-map format --format dsl --include \"src/**\" > .codebase-map.dsl"
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
27
28
|
"lib",
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
"@types/node": "20",
|
|
53
54
|
"babel-loader": "^8.2.2",
|
|
54
55
|
"c8": "^10.1.3",
|
|
56
|
+
"codebase-map": "^0.6.1",
|
|
55
57
|
"eslint": "^8.3.0",
|
|
56
58
|
"jasmine": "^5.6.0",
|
|
57
59
|
"typescript": "5",
|
package/src/SyntaxValidator.js
CHANGED
|
@@ -100,6 +100,24 @@ export default class SyntaxValidator {
|
|
|
100
100
|
docTypeSeen = true;
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
// Character data (text, entity references, CDATA) is only legal inside an
|
|
104
|
+
// element. At depth 0 it is "extra" data, whether before or after the root.
|
|
105
|
+
const checkTopLevelText = (pos) => {
|
|
106
|
+
if (tags.length === 0) {
|
|
107
|
+
throwError('InvalidXml', 'Extra text at the end', getLineNumberForPosition(xmlData, pos));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const checkCdataPlacement = (startBang) => {
|
|
112
|
+
if (tags.length === 0 && xmlData.startsWith('<![CDATA[', startBang)) {
|
|
113
|
+
throwError(
|
|
114
|
+
'InvalidXml',
|
|
115
|
+
'CDATA section is not allowed outside the root element.',
|
|
116
|
+
getLineNumberForPosition(xmlData, startBang)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
103
121
|
for (let i = 0; i < xmlData.length; i++) {
|
|
104
122
|
if (xmlData[i] === '<' && xmlData[i + 1] === '?') {
|
|
105
123
|
i += 2;
|
|
@@ -109,6 +127,7 @@ export default class SyntaxValidator {
|
|
|
109
127
|
i++;
|
|
110
128
|
|
|
111
129
|
if (xmlData[i] === '!') {
|
|
130
|
+
checkCdataPlacement(tagStartPos);
|
|
112
131
|
try {
|
|
113
132
|
checkDocTypePlacement(tagStartPos);
|
|
114
133
|
i = commentCdataReader.read(xmlData, i, docTypeValidator);
|
|
@@ -214,14 +233,16 @@ export default class SyntaxValidator {
|
|
|
214
233
|
throwError('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, i));
|
|
215
234
|
} else if (options.unpairedTags.indexOf(tagName) !== -1) {
|
|
216
235
|
skipMatcher.pop();
|
|
236
|
+
// An unpaired tag at depth 0 is a complete top-level element.
|
|
237
|
+
if (tags.length === 0) reachedRoot = true;
|
|
217
238
|
} else if (skip) {
|
|
218
239
|
// Consume the whole skipped subtree now, as opaque raw text,
|
|
219
|
-
// then
|
|
240
|
+
// then fall through to the text scan below so that whatever
|
|
241
|
+
// follows the skipped tag's closing '>' (text, entities,
|
|
242
|
+
// comments, the next tag) is handled like after any other tag.
|
|
220
243
|
i = RawSubtreeReader.consume(xmlData, tagName, i + 1);
|
|
221
244
|
skipMatcher.pop();
|
|
222
245
|
if (tags.length === 0) reachedRoot = true;
|
|
223
|
-
tagFound = true;
|
|
224
|
-
continue;
|
|
225
246
|
} else {
|
|
226
247
|
tags.push({ tagName, tagStartPos });
|
|
227
248
|
}
|
|
@@ -235,6 +256,7 @@ export default class SyntaxValidator {
|
|
|
235
256
|
if (xmlData[i] === '<') {
|
|
236
257
|
if (xmlData[i + 1] === '!') {
|
|
237
258
|
i++;
|
|
259
|
+
checkCdataPlacement(i - 1);
|
|
238
260
|
try {
|
|
239
261
|
checkDocTypePlacement(i - 1);
|
|
240
262
|
i = commentCdataReader.read(xmlData, i, docTypeValidator);
|
|
@@ -251,6 +273,7 @@ export default class SyntaxValidator {
|
|
|
251
273
|
break;
|
|
252
274
|
}
|
|
253
275
|
} else if (xmlData[i] === '&') {
|
|
276
|
+
checkTopLevelText(i);
|
|
254
277
|
const afterAmp = this._validateAmpersand(xmlData, i);
|
|
255
278
|
if (afterAmp === -1)
|
|
256
279
|
throwError('InvalidChar', "char '&' is not expected.", getLineNumberForPosition(xmlData, i));
|
|
@@ -261,7 +284,7 @@ export default class SyntaxValidator {
|
|
|
261
284
|
if (isIllegalControlCode(code)) {
|
|
262
285
|
throwError('IllegalCharacter', `Illegal control character ${ControlCharValidator.describe(code)} in document content.`, getLineNumberForPosition(xmlData, i));
|
|
263
286
|
}
|
|
264
|
-
if (
|
|
287
|
+
if (tags.length === 0 && !isWhiteSpace(xmlData[i])) {
|
|
265
288
|
throwError('InvalidXml', 'Extra text at the end', getLineNumberForPosition(xmlData, i));
|
|
266
289
|
}
|
|
267
290
|
if (checkTagValueSeq) {
|
package/src/fxv.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
|
|
2
1
|
export type docTypeOptions = {
|
|
3
2
|
/**
|
|
4
|
-
* maximum number of entity present in DOCTYPE
|
|
5
|
-
*
|
|
3
|
+
* maximum number of entity present in DOCTYPE
|
|
4
|
+
*
|
|
6
5
|
* Defaults to `Infinity`
|
|
7
6
|
*/
|
|
8
7
|
maxEntityCount?: number;
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
|
-
* maximum size of each entity present in DOCTYPE
|
|
12
|
-
*
|
|
10
|
+
* maximum size of each entity present in DOCTYPE
|
|
11
|
+
*
|
|
13
12
|
* Defaults to `Infinity`
|
|
14
13
|
*/
|
|
15
14
|
maxEntitySize?: number;
|
|
16
|
-
}
|
|
15
|
+
};
|
|
17
16
|
|
|
18
17
|
export type invalidCharSequenceOptions = {
|
|
19
18
|
/**
|
|
@@ -59,14 +58,14 @@ export type xmlDeclarationOptions = {
|
|
|
59
58
|
export type validationOptions = {
|
|
60
59
|
/**
|
|
61
60
|
* Whether to allow attributes without value
|
|
62
|
-
*
|
|
61
|
+
*
|
|
63
62
|
* Defaults to `false`
|
|
64
63
|
*/
|
|
65
64
|
allowBooleanAttributes?: boolean;
|
|
66
65
|
|
|
67
66
|
/**
|
|
68
67
|
* List of tags without closing tags
|
|
69
|
-
*
|
|
68
|
+
*
|
|
70
69
|
* Defaults to `[]`
|
|
71
70
|
*/
|
|
72
71
|
unpairedTags?: string[];
|
|
@@ -79,10 +78,13 @@ export type validationOptions = {
|
|
|
79
78
|
/** Controls handling of the `<?xml ... ?>` prolog. */
|
|
80
79
|
xmlDeclaraion?: xmlDeclarationOptions;
|
|
81
80
|
|
|
81
|
+
/** Correctly-spelled alias of `xmlDeclaraion`. */
|
|
82
|
+
xmlDeclaration?: xmlDeclarationOptions;
|
|
83
|
+
|
|
82
84
|
/**
|
|
83
85
|
* Allow more than one root-level element (as if wrapped in a virtual root).
|
|
84
86
|
*
|
|
85
|
-
* Defaults to `
|
|
87
|
+
* Defaults to `true`.
|
|
86
88
|
*/
|
|
87
89
|
multipleRoots?: boolean;
|
|
88
90
|
|
|
@@ -95,22 +97,46 @@ export type validationOptions = {
|
|
|
95
97
|
skipTags?: string[];
|
|
96
98
|
};
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Known error codes. The union stays open (`string & {}`) so new codes
|
|
102
|
+
* added in the future don't break consumers' exhaustive checks.
|
|
103
|
+
*/
|
|
104
|
+
export type ValidationErrorCode =
|
|
105
|
+
| 'InvalidXml'
|
|
106
|
+
| 'InvalidTag'
|
|
107
|
+
| 'InvalidAttr'
|
|
108
|
+
| 'InvalidChar'
|
|
109
|
+
| 'InvalidDocType'
|
|
110
|
+
| 'IllegalCharacter'
|
|
111
|
+
| 'UnquotedAttributeValue'
|
|
112
|
+
| (string & {});
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Thrown by `SyntaxValidator#validate` on the first well-formedness problem.
|
|
116
|
+
*/
|
|
117
|
+
export class ValidationError extends Error {
|
|
118
|
+
constructor(message: string, code: ValidationErrorCode, line: number, col: number);
|
|
119
|
+
code: ValidationErrorCode;
|
|
120
|
+
line: number;
|
|
121
|
+
col: number;
|
|
122
|
+
}
|
|
106
123
|
|
|
107
124
|
export class SyntaxValidator {
|
|
108
125
|
constructor(options?: validationOptions);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @returns `true` when the document is well-formed.
|
|
129
|
+
* @throws {ValidationError} on the first problem found.
|
|
130
|
+
*/
|
|
109
131
|
validate(xmlData: string): true;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @returns `true` when the document is well-formed.
|
|
135
|
+
* @throws {ValidationError} on the first problem found.
|
|
136
|
+
*/
|
|
110
137
|
static validate(xmlData: string, options?: validationOptions): true;
|
|
111
138
|
}
|
|
112
139
|
|
|
113
|
-
|
|
114
140
|
export interface BusinessRulesValidatorOptions {
|
|
115
141
|
unknownAllow?: boolean;
|
|
116
142
|
boolean?: string[];
|
|
@@ -122,7 +148,7 @@ export type ValidationFailure = {
|
|
|
122
148
|
value?: string;
|
|
123
149
|
actual?: number | string;
|
|
124
150
|
expected?: number | string;
|
|
125
|
-
}
|
|
151
|
+
};
|
|
126
152
|
|
|
127
153
|
export type CustomValidatorFn = (
|
|
128
154
|
value: string,
|
|
@@ -140,7 +166,7 @@ export class BusinessRulesValidator {
|
|
|
140
166
|
*
|
|
141
167
|
* @example
|
|
142
168
|
* ```ts
|
|
143
|
-
* const validator = new
|
|
169
|
+
* const validator = new BusinessRulesValidator(`
|
|
144
170
|
* <students>
|
|
145
171
|
* <student repeatable minOccurs="1">
|
|
146
172
|
* <email unique="true" nillable="false"></email>
|