fast-xml-validator 1.0.0
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/LICENSE +21 -0
- package/README.md +47 -0
- package/lib/fxv.cjs +1 -0
- package/lib/fxv.d.cts +129 -0
- package/lib/fxv.min.js +2 -0
- package/lib/fxv.min.js.map +1 -0
- package/package.json +70 -0
- package/src/DocTypeValidator.js +279 -0
- package/src/ValidationError.js +10 -0
- package/src/fxv.d.ts +115 -0
- package/src/fxv.js +12 -0
- package/src/util.js +61 -0
- package/src/validator.js +472 -0
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fast-xml-validator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Validate XML for Syntax and Business rules without C/C++ based libraries",
|
|
5
|
+
"main": "./lib/fxv.cjs",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"module": "./src/fxv.js",
|
|
9
|
+
"types": "./src/fxv.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./src/fxv.d.ts",
|
|
14
|
+
"default": "./src/fxv.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./lib/fxv.d.cts",
|
|
18
|
+
"default": "./lib/fxv.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "c8 --reporter=lcov --reporter=text jasmine specs/*spec.js",
|
|
24
|
+
"bundle": "webpack --config webpack.cjs.config.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"lib",
|
|
28
|
+
"src",
|
|
29
|
+
"CHANGELOG.md"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/NaturalIntelligence/fast-xml-validator.git"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"fast",
|
|
37
|
+
"xml",
|
|
38
|
+
"validator",
|
|
39
|
+
"validate",
|
|
40
|
+
"syntax",
|
|
41
|
+
"rules",
|
|
42
|
+
"business",
|
|
43
|
+
"schema"
|
|
44
|
+
],
|
|
45
|
+
"author": "Amit Gupta (https://solothought.com)",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@babel/core": "^7.13.10",
|
|
49
|
+
"@babel/plugin-transform-runtime": "^7.13.10",
|
|
50
|
+
"@babel/preset-env": "^7.13.10",
|
|
51
|
+
"@babel/register": "^7.13.8",
|
|
52
|
+
"@types/node": "20",
|
|
53
|
+
"babel-loader": "^8.2.2",
|
|
54
|
+
"c8": "^10.1.3",
|
|
55
|
+
"eslint": "^8.3.0",
|
|
56
|
+
"jasmine": "^5.6.0",
|
|
57
|
+
"typescript": "5",
|
|
58
|
+
"webpack": "^5.64.4",
|
|
59
|
+
"webpack-cli": "^4.9.1"
|
|
60
|
+
},
|
|
61
|
+
"funding": [
|
|
62
|
+
{
|
|
63
|
+
"type": "github",
|
|
64
|
+
"url": "https://github.com/sponsors/NaturalIntelligence"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"detailed-xml-validator": "^2.1.0"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { isName } from './util.js';
|
|
2
|
+
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
maxEntityCount: Infinity,
|
|
5
|
+
maxEntitySize: Infinity,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default class DocTypeValidator {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
11
|
+
this.suppressValidationErr = !options;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
validateDocType(xmlData, i) {
|
|
15
|
+
if (
|
|
16
|
+
xmlData[i + 3] !== 'O' ||
|
|
17
|
+
xmlData[i + 4] !== 'C' ||
|
|
18
|
+
xmlData[i + 5] !== 'T' ||
|
|
19
|
+
xmlData[i + 6] !== 'Y' ||
|
|
20
|
+
xmlData[i + 7] !== 'P' ||
|
|
21
|
+
xmlData[i + 8] !== 'E'
|
|
22
|
+
) {
|
|
23
|
+
throw new Error(`Invalid Tag instead of DOCTYPE`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
i += 9;
|
|
27
|
+
let angleBracketsCount = 1;
|
|
28
|
+
let hasBody = false;
|
|
29
|
+
let comment = false;
|
|
30
|
+
let entityCount = 0;
|
|
31
|
+
|
|
32
|
+
for (; i < xmlData.length; i++) {
|
|
33
|
+
if (xmlData[i] === '<' && !comment) {
|
|
34
|
+
if (hasBody && hasSeq(xmlData, '!ENTITY', i)) {
|
|
35
|
+
i += 7;
|
|
36
|
+
[entityCount, i] = this._validateEntityExp(xmlData, i + 1, entityCount);
|
|
37
|
+
// continue;
|
|
38
|
+
} else if (hasBody && hasSeq(xmlData, '!ELEMENT', i)) {
|
|
39
|
+
i += 8;
|
|
40
|
+
i = this._validateElementExp(xmlData, i + 1);
|
|
41
|
+
// continue;
|
|
42
|
+
} else if (hasBody && hasSeq(xmlData, '!ATTLIST', i)) {
|
|
43
|
+
i += 8;
|
|
44
|
+
i = this._validateAttlistExp(xmlData, i + 1);
|
|
45
|
+
continue;
|
|
46
|
+
} else if (hasBody && hasSeq(xmlData, '!NOTATION', i)) {
|
|
47
|
+
i += 9;
|
|
48
|
+
i = this._validateNotationExp(xmlData, i + 1);
|
|
49
|
+
// continue;
|
|
50
|
+
} else if (hasSeq(xmlData, '!--', i)) {
|
|
51
|
+
comment = true;
|
|
52
|
+
} else {
|
|
53
|
+
throw new Error(`Invalid DOCTYPE`);
|
|
54
|
+
}
|
|
55
|
+
angleBracketsCount++;
|
|
56
|
+
} else if (xmlData[i] === '>') {
|
|
57
|
+
if (comment) {
|
|
58
|
+
if (xmlData[i - 1] === '-' && xmlData[i - 2] === '-') {
|
|
59
|
+
comment = false;
|
|
60
|
+
angleBracketsCount--;
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
angleBracketsCount--;
|
|
64
|
+
}
|
|
65
|
+
if (angleBracketsCount === 0) break;
|
|
66
|
+
} else if (xmlData[i] === '[') {
|
|
67
|
+
hasBody = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (angleBracketsCount !== 0) {
|
|
72
|
+
throw new Error(`Unclosed DOCTYPE`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return i;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_validateEntityExp(xmlData, i, entityCount) {
|
|
79
|
+
i = skipWhitespace(xmlData, i);
|
|
80
|
+
|
|
81
|
+
// Read and validate entity name
|
|
82
|
+
const nameStart = i;
|
|
83
|
+
while (i < xmlData.length && !/\s/.test(xmlData[i]) && xmlData[i] !== '"' && xmlData[i] !== "'") {
|
|
84
|
+
i++;
|
|
85
|
+
}
|
|
86
|
+
const entityName = xmlData.substring(nameStart, i);
|
|
87
|
+
validateName(entityName);
|
|
88
|
+
|
|
89
|
+
i = skipWhitespace(xmlData, i);
|
|
90
|
+
|
|
91
|
+
// Check for unsupported constructs
|
|
92
|
+
if (!this.suppressValidationErr) {
|
|
93
|
+
if (xmlData.substring(i, i + 6).toUpperCase() === 'SYSTEM') {
|
|
94
|
+
throw new Error('External entities are not supported');
|
|
95
|
+
} else if (xmlData[i] === '%') {
|
|
96
|
+
throw new Error('Parameter entities are not supported');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Read and validate the entity value (validates quoting + size limit)
|
|
101
|
+
let entityValue;
|
|
102
|
+
[i, entityValue] = readQuotedValue(xmlData, i, 'entity');
|
|
103
|
+
|
|
104
|
+
const { maxEntitySize, maxEntityCount } = this.options;
|
|
105
|
+
|
|
106
|
+
if (entityValue.length > maxEntitySize) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Entity "${entityName}" size (${entityValue.length}) exceeds maximum allowed size (${maxEntitySize})`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Only count non-parameter entities (parameter entities contain '&')
|
|
113
|
+
if (entityValue.indexOf('&') === -1) {
|
|
114
|
+
entityCount++;
|
|
115
|
+
if (entityCount > maxEntityCount) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Entity count (${entityCount}) exceeds maximum allowed (${maxEntityCount})`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return [entityCount, i - 1];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_validateNotationExp(xmlData, i) {
|
|
126
|
+
i = skipWhitespace(xmlData, i);
|
|
127
|
+
|
|
128
|
+
// Read and validate notation name
|
|
129
|
+
const nameStart = i;
|
|
130
|
+
while (i < xmlData.length && !/\s/.test(xmlData[i])) i++;
|
|
131
|
+
const notationName = xmlData.substring(nameStart, i);
|
|
132
|
+
!this.suppressValidationErr && validateName(notationName);
|
|
133
|
+
|
|
134
|
+
i = skipWhitespace(xmlData, i);
|
|
135
|
+
|
|
136
|
+
const identifierType = xmlData.substring(i, i + 6).toUpperCase();
|
|
137
|
+
if (!this.suppressValidationErr && identifierType !== 'SYSTEM' && identifierType !== 'PUBLIC') {
|
|
138
|
+
throw new Error(`Expected SYSTEM or PUBLIC, found "${identifierType}"`);
|
|
139
|
+
}
|
|
140
|
+
i += identifierType.length;
|
|
141
|
+
i = skipWhitespace(xmlData, i);
|
|
142
|
+
|
|
143
|
+
if (identifierType === 'PUBLIC') {
|
|
144
|
+
[i] = readQuotedValue(xmlData, i, 'publicIdentifier');
|
|
145
|
+
i = skipWhitespace(xmlData, i);
|
|
146
|
+
if (xmlData[i] === '"' || xmlData[i] === "'") {
|
|
147
|
+
[i] = readQuotedValue(xmlData, i, 'systemIdentifier');
|
|
148
|
+
}
|
|
149
|
+
} else if (identifierType === 'SYSTEM') {
|
|
150
|
+
[i] = readQuotedValue(xmlData, i, 'systemIdentifier');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return --i;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
_validateElementExp(xmlData, i) {
|
|
157
|
+
i = skipWhitespace(xmlData, i);
|
|
158
|
+
|
|
159
|
+
// Read and validate element name
|
|
160
|
+
const nameStart = i;
|
|
161
|
+
while (i < xmlData.length && !/\s/.test(xmlData[i])) i++;
|
|
162
|
+
const elementName = xmlData.substring(nameStart, i);
|
|
163
|
+
if (!this.suppressValidationErr && !isName(elementName)) {
|
|
164
|
+
throw new Error(`Invalid element name: "${elementName}"`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
i = skipWhitespace(xmlData, i);
|
|
168
|
+
|
|
169
|
+
// Validate content model keyword or group
|
|
170
|
+
if (xmlData[i] === 'E' && hasSeq(xmlData, 'MPTY', i)) {
|
|
171
|
+
i += 4;
|
|
172
|
+
} else if (xmlData[i] === 'A' && hasSeq(xmlData, 'NY', i)) {
|
|
173
|
+
i += 2;
|
|
174
|
+
} else if (xmlData[i] === '(') {
|
|
175
|
+
i++;
|
|
176
|
+
while (i < xmlData.length && xmlData[i] !== ')') i++;
|
|
177
|
+
if (xmlData[i] !== ')') throw new Error('Unterminated content model');
|
|
178
|
+
} else if (!this.suppressValidationErr) {
|
|
179
|
+
throw new Error(`Invalid Element Expression, found "${xmlData[i]}"`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return i;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_validateAttlistExp(xmlData, i) {
|
|
186
|
+
i = skipWhitespace(xmlData, i);
|
|
187
|
+
|
|
188
|
+
// Read element name
|
|
189
|
+
const elemNameStart = i;
|
|
190
|
+
while (i < xmlData.length && !/\s/.test(xmlData[i])) i++;
|
|
191
|
+
const elementName = xmlData.substring(elemNameStart, i);
|
|
192
|
+
validateName(elementName);
|
|
193
|
+
|
|
194
|
+
i = skipWhitespace(xmlData, i);
|
|
195
|
+
|
|
196
|
+
// Read attribute name
|
|
197
|
+
const attrNameStart = i;
|
|
198
|
+
while (i < xmlData.length && !/\s/.test(xmlData[i])) i++;
|
|
199
|
+
const attributeName = xmlData.substring(attrNameStart, i);
|
|
200
|
+
validateName(attributeName);
|
|
201
|
+
|
|
202
|
+
i = skipWhitespace(xmlData, i);
|
|
203
|
+
|
|
204
|
+
// Read attribute type
|
|
205
|
+
if (xmlData.substring(i, i + 8).toUpperCase() === 'NOTATION') {
|
|
206
|
+
i += 8;
|
|
207
|
+
i = skipWhitespace(xmlData, i);
|
|
208
|
+
if (xmlData[i] !== '(') throw new Error(`Expected '(', found "${xmlData[i]}"`);
|
|
209
|
+
i++;
|
|
210
|
+
while (i < xmlData.length && xmlData[i] !== ')') {
|
|
211
|
+
const notationStart = i;
|
|
212
|
+
while (i < xmlData.length && xmlData[i] !== '|' && xmlData[i] !== ')') i++;
|
|
213
|
+
const notation = xmlData.substring(notationStart, i).trim();
|
|
214
|
+
validateName(notation);
|
|
215
|
+
if (xmlData[i] === '|') {
|
|
216
|
+
i++;
|
|
217
|
+
i = skipWhitespace(xmlData, i);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (xmlData[i] !== ')') throw new Error('Unterminated list of notations');
|
|
221
|
+
i++;
|
|
222
|
+
} else {
|
|
223
|
+
const typeStart = i;
|
|
224
|
+
while (i < xmlData.length && !/\s/.test(xmlData[i])) i++;
|
|
225
|
+
const attributeType = xmlData.substring(typeStart, i);
|
|
226
|
+
const validTypes = ['CDATA', 'ID', 'IDREF', 'IDREFS', 'ENTITY', 'ENTITIES', 'NMTOKEN', 'NMTOKENS'];
|
|
227
|
+
if (!this.suppressValidationErr && !validTypes.includes(attributeType.toUpperCase())) {
|
|
228
|
+
throw new Error(`Invalid attribute type: "${attributeType}"`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
i = skipWhitespace(xmlData, i);
|
|
233
|
+
|
|
234
|
+
// Read default value declaration
|
|
235
|
+
if (xmlData.substring(i, i + 9).toUpperCase() === '#REQUIRED') {
|
|
236
|
+
i += 9;
|
|
237
|
+
} else if (xmlData.substring(i, i + 8).toUpperCase() === '#IMPLIED') {
|
|
238
|
+
i += 8;
|
|
239
|
+
} else {
|
|
240
|
+
[i] = readQuotedValue(xmlData, i, 'ATTLIST default value');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return i;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
function skipWhitespace(data, index) {
|
|
250
|
+
while (index < data.length && /\s/.test(data[index])) index++;
|
|
251
|
+
return index;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function hasSeq(data, seq, i) {
|
|
255
|
+
for (let j = 0; j < seq.length; j++) {
|
|
256
|
+
if (seq[j] !== data[i + j + 1]) return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function validateName(name) {
|
|
262
|
+
if (!isName(name)) throw new Error(`Invalid entity name "${name}"`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Read a single- or double-quoted string and return [nextIndex, value].
|
|
267
|
+
*/
|
|
268
|
+
function readQuotedValue(xmlData, i, type) {
|
|
269
|
+
const startChar = xmlData[i];
|
|
270
|
+
if (startChar !== '"' && startChar !== "'") {
|
|
271
|
+
throw new Error(`Expected quoted string for ${type}, found "${startChar}"`);
|
|
272
|
+
}
|
|
273
|
+
i++;
|
|
274
|
+
const start = i;
|
|
275
|
+
while (i < xmlData.length && xmlData[i] !== startChar) i++;
|
|
276
|
+
if (xmlData[i] !== startChar) throw new Error(`Unterminated ${type} value`);
|
|
277
|
+
const value = xmlData.substring(start, i);
|
|
278
|
+
return [++i, value];
|
|
279
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default class ValidationError extends Error {
|
|
2
|
+
constructor(message, code, line, col) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = this.constructor.name;
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.line = line;
|
|
7
|
+
this.col = col;
|
|
8
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/fxv.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
|
|
2
|
+
export type docTypeOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* maximum number of entity present in DOCTYPE
|
|
5
|
+
*
|
|
6
|
+
* Defaults to `Infinity`
|
|
7
|
+
*/
|
|
8
|
+
maxEntityCount?: number;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* maximum size of each entity present in DOCTYPE
|
|
12
|
+
*
|
|
13
|
+
* Defaults to `Infinity`
|
|
14
|
+
*/
|
|
15
|
+
maxEntitySize?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type validationOptions = {
|
|
19
|
+
/**
|
|
20
|
+
* Whether to allow attributes without value
|
|
21
|
+
*
|
|
22
|
+
* Defaults to `false`
|
|
23
|
+
*/
|
|
24
|
+
allowBooleanAttributes?: boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* List of tags without closing tags
|
|
28
|
+
*
|
|
29
|
+
* Defaults to `[]`
|
|
30
|
+
*/
|
|
31
|
+
unpairedTags?: string[];
|
|
32
|
+
|
|
33
|
+
docType?: docTypeOptions;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ValidationError = {
|
|
37
|
+
err: {
|
|
38
|
+
code: string;
|
|
39
|
+
msg: string,
|
|
40
|
+
line: number,
|
|
41
|
+
col: number
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export class SyntaxValidator {
|
|
46
|
+
static validate(xmlData: string, options?: validationOptions): true | ValidationError;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
export interface BusinessRulesValidatorOptions {
|
|
51
|
+
unknownAllow?: boolean;
|
|
52
|
+
boolean?: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ValidationFailure = {
|
|
56
|
+
code: string;
|
|
57
|
+
path: string;
|
|
58
|
+
value?: string;
|
|
59
|
+
actual?: number | string;
|
|
60
|
+
expected?: number | string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type CustomValidatorFn = (
|
|
64
|
+
value: string,
|
|
65
|
+
path: string
|
|
66
|
+
) => ValidationFailure | Record<string, unknown> | undefined | null | void;
|
|
67
|
+
|
|
68
|
+
export class BusinessRulesValidator {
|
|
69
|
+
/**
|
|
70
|
+
* Create a new Validator with a rules XML string.
|
|
71
|
+
*
|
|
72
|
+
* @param rules - XML string whose attributes define validation constraints.
|
|
73
|
+
* @param options - Optional configuration.
|
|
74
|
+
*
|
|
75
|
+
* @throws {Error} If `rules` is empty, not a string, or contains XML syntax errors.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const validator = new Validator(`
|
|
80
|
+
* <students>
|
|
81
|
+
* <student repeatable minOccurs="1">
|
|
82
|
+
* <email unique="true" nillable="false"></email>
|
|
83
|
+
* <age type="integer" range="18..65"></age>
|
|
84
|
+
* </student>
|
|
85
|
+
* </students>
|
|
86
|
+
* `);
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
constructor(rules: string, options?: BusinessRulesValidatorOptions);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Register a custom validation function under a name.
|
|
93
|
+
* Reference it in rules XML with `checkBy="<name>"`.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* validator.register("isEmail", (value, path) => {
|
|
98
|
+
* if (!value.includes("@")) {
|
|
99
|
+
* return { code: "invalid-email", path, value };
|
|
100
|
+
* }
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
register(name: string, fn: CustomValidatorFn): void;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Validate an XML string against the rules.
|
|
108
|
+
*
|
|
109
|
+
* @param xmldata - XML string to validate.
|
|
110
|
+
* @returns Array of failure objects. Empty array means the document is valid.
|
|
111
|
+
*
|
|
112
|
+
* @throws {Error} If `xmldata` is empty, not a string, or contains XML syntax errors.
|
|
113
|
+
*/
|
|
114
|
+
validate(xmldata: string): ValidationFailure[];
|
|
115
|
+
}
|
package/src/fxv.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { validate as validateSyntax } from './validator.js';
|
|
4
|
+
import BusinessRulesValidator from 'detailed-xml-validator';
|
|
5
|
+
|
|
6
|
+
class SyntaxValidator {
|
|
7
|
+
static validate(xmlData, options) {
|
|
8
|
+
return validateSyntax(xmlData, options);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { SyntaxValidator, BusinessRulesValidator };
|
package/src/util.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const nameStartChar = ':A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD';
|
|
4
|
+
const nameChar = nameStartChar + '\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040';
|
|
5
|
+
export const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*';
|
|
6
|
+
const regexName = new RegExp('^' + nameRegexp + '$');
|
|
7
|
+
|
|
8
|
+
export function getAllMatches(string, regex) {
|
|
9
|
+
const matches = [];
|
|
10
|
+
let match = regex.exec(string);
|
|
11
|
+
while (match) {
|
|
12
|
+
const allmatches = [];
|
|
13
|
+
allmatches.startIndex = regex.lastIndex - match[0].length;
|
|
14
|
+
const len = match.length;
|
|
15
|
+
for (let index = 0; index < len; index++) {
|
|
16
|
+
allmatches.push(match[index]);
|
|
17
|
+
}
|
|
18
|
+
matches.push(allmatches);
|
|
19
|
+
match = regex.exec(string);
|
|
20
|
+
}
|
|
21
|
+
return matches;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const isName = function (string) {
|
|
25
|
+
const match = regexName.exec(string);
|
|
26
|
+
return !(match === null || typeof match === 'undefined');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isExist(v) {
|
|
30
|
+
return typeof v !== 'undefined';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isEmptyObject(obj) {
|
|
34
|
+
return Object.keys(obj).length === 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getValue(v) {
|
|
38
|
+
if (exports.isExist(v)) {
|
|
39
|
+
return v;
|
|
40
|
+
} else {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Dangerous property names that could lead to prototype pollution or security issues
|
|
47
|
+
*/
|
|
48
|
+
export const DANGEROUS_PROPERTY_NAMES = [
|
|
49
|
+
// '__proto__',
|
|
50
|
+
// 'constructor',
|
|
51
|
+
// 'prototype',
|
|
52
|
+
'hasOwnProperty',
|
|
53
|
+
'toString',
|
|
54
|
+
'valueOf',
|
|
55
|
+
'__defineGetter__',
|
|
56
|
+
'__defineSetter__',
|
|
57
|
+
'__lookupGetter__',
|
|
58
|
+
'__lookupSetter__'
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export const criticalProperties = ["__proto__", "constructor", "prototype"];
|