befly 1.2.10 → 2.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.
@@ -1,400 +0,0 @@
1
- 'use strict';
2
-
3
- import { getAllMatches, isName } from './util.js';
4
-
5
- const defaultOptions = {
6
- allowBooleanAttributes: false, //A tag can have attributes without any value
7
- unpairedTags: []
8
- };
9
-
10
- //const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
11
- export function validate(xmlData, options) {
12
- options = Object.assign({}, defaultOptions, options);
13
-
14
- //xmlData = xmlData.replace(/(\r\n|\n|\r)/gm,"");//make it single line
15
- //xmlData = xmlData.replace(/(^\s*<\?xml.*?\?>)/g,"");//Remove XML starting tag
16
- //xmlData = xmlData.replace(/(<!DOCTYPE[\s\w\"\.\/\-\:]+(\[.*\])*\s*>)/g,"");//Remove DOCTYPE
17
- const tags = [];
18
- let tagFound = false;
19
-
20
- //indicates that the root tag has been closed (aka. depth 0 has been reached)
21
- let reachedRoot = false;
22
-
23
- if (xmlData[0] === '\ufeff') {
24
- // check for byte order mark (BOM)
25
- xmlData = xmlData.substr(1);
26
- }
27
-
28
- for (let i = 0; i < xmlData.length; i++) {
29
- if (xmlData[i] === '<' && xmlData[i + 1] === '?') {
30
- i += 2;
31
- i = readPI(xmlData, i);
32
- if (i.err) return i;
33
- } else if (xmlData[i] === '<') {
34
- //starting of tag
35
- //read until you reach to '>' avoiding any '>' in attribute value
36
- let tagStartPos = i;
37
- i++;
38
-
39
- if (xmlData[i] === '!') {
40
- i = readCommentAndCDATA(xmlData, i);
41
- continue;
42
- } else {
43
- let closingTag = false;
44
- if (xmlData[i] === '/') {
45
- //closing tag
46
- closingTag = true;
47
- i++;
48
- }
49
- //read tagname
50
- let tagName = '';
51
- for (; i < xmlData.length && xmlData[i] !== '>' && xmlData[i] !== ' ' && xmlData[i] !== '\t' && xmlData[i] !== '\n' && xmlData[i] !== '\r'; i++) {
52
- tagName += xmlData[i];
53
- }
54
- tagName = tagName.trim();
55
- //console.log(tagName);
56
-
57
- if (tagName[tagName.length - 1] === '/') {
58
- //self closing tag without attributes
59
- tagName = tagName.substring(0, tagName.length - 1);
60
- //continue;
61
- i--;
62
- }
63
- if (!validateTagName(tagName)) {
64
- let msg;
65
- if (tagName.trim().length === 0) {
66
- msg = "Invalid space after '<'.";
67
- } else {
68
- msg = "Tag '" + tagName + "' is an invalid name.";
69
- }
70
- return getErrorObject('InvalidTag', msg, getLineNumberForPosition(xmlData, i));
71
- }
72
-
73
- const result = readAttributeStr(xmlData, i);
74
- if (result === false) {
75
- return getErrorObject('InvalidAttr', "Attributes for '" + tagName + "' have open quote.", getLineNumberForPosition(xmlData, i));
76
- }
77
- let attrStr = result.value;
78
- i = result.index;
79
-
80
- if (attrStr[attrStr.length - 1] === '/') {
81
- //self closing tag
82
- const attrStrStart = i - attrStr.length;
83
- attrStr = attrStr.substring(0, attrStr.length - 1);
84
- const isValid = validateAttributeString(attrStr, options);
85
- if (isValid === true) {
86
- tagFound = true;
87
- //continue; //text may presents after self closing tag
88
- } else {
89
- //the result from the nested function returns the position of the error within the attribute
90
- //in order to get the 'true' error line, we need to calculate the position where the attribute begins (i - attrStr.length) and then add the position within the attribute
91
- //this gives us the absolute index in the entire xml, which we can use to find the line at last
92
- return getErrorObject(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData, attrStrStart + isValid.err.line));
93
- }
94
- } else if (closingTag) {
95
- if (!result.tagClosed) {
96
- return getErrorObject('InvalidTag', "Closing tag '" + tagName + "' doesn't have proper closing.", getLineNumberForPosition(xmlData, i));
97
- } else if (attrStr.trim().length > 0) {
98
- return getErrorObject('InvalidTag', "Closing tag '" + tagName + "' can't have attributes or invalid starting.", getLineNumberForPosition(xmlData, tagStartPos));
99
- } else if (tags.length === 0) {
100
- return getErrorObject('InvalidTag', "Closing tag '" + tagName + "' has not been opened.", getLineNumberForPosition(xmlData, tagStartPos));
101
- } else {
102
- const otg = tags.pop();
103
- if (tagName !== otg.tagName) {
104
- let openPos = getLineNumberForPosition(xmlData, otg.tagStartPos);
105
- return getErrorObject('InvalidTag', "Expected closing tag '" + otg.tagName + "' (opened in line " + openPos.line + ', col ' + openPos.col + ") instead of closing tag '" + tagName + "'.", getLineNumberForPosition(xmlData, tagStartPos));
106
- }
107
-
108
- //when there are no more tags, we reached the root level.
109
- if (tags.length == 0) {
110
- reachedRoot = true;
111
- }
112
- }
113
- } else {
114
- const isValid = validateAttributeString(attrStr, options);
115
- if (isValid !== true) {
116
- //the result from the nested function returns the position of the error within the attribute
117
- //in order to get the 'true' error line, we need to calculate the position where the attribute begins (i - attrStr.length) and then add the position within the attribute
118
- //this gives us the absolute index in the entire xml, which we can use to find the line at last
119
- return getErrorObject(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData, i - attrStr.length + isValid.err.line));
120
- }
121
-
122
- //if the root level has been reached before ...
123
- if (reachedRoot === true) {
124
- return getErrorObject('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, i));
125
- } else if (options.unpairedTags.indexOf(tagName) !== -1) {
126
- //don't push into stack
127
- } else {
128
- tags.push({ tagName, tagStartPos });
129
- }
130
- tagFound = true;
131
- }
132
-
133
- //skip tag text value
134
- //It may include comments and CDATA value
135
- for (i++; i < xmlData.length; i++) {
136
- if (xmlData[i] === '<') {
137
- if (xmlData[i + 1] === '!') {
138
- //comment or CADATA
139
- i++;
140
- i = readCommentAndCDATA(xmlData, i);
141
- continue;
142
- } else if (xmlData[i + 1] === '?') {
143
- i = readPI(xmlData, ++i);
144
- if (i.err) return i;
145
- } else {
146
- break;
147
- }
148
- } else if (xmlData[i] === '&') {
149
- const afterAmp = validateAmpersand(xmlData, i);
150
- if (afterAmp == -1) return getErrorObject('InvalidChar', "char '&' is not expected.", getLineNumberForPosition(xmlData, i));
151
- i = afterAmp;
152
- } else {
153
- if (reachedRoot === true && !isWhiteSpace(xmlData[i])) {
154
- return getErrorObject('InvalidXml', 'Extra text at the end', getLineNumberForPosition(xmlData, i));
155
- }
156
- }
157
- } //end of reading tag text value
158
- if (xmlData[i] === '<') {
159
- i--;
160
- }
161
- }
162
- } else {
163
- if (isWhiteSpace(xmlData[i])) {
164
- continue;
165
- }
166
- return getErrorObject('InvalidChar', "char '" + xmlData[i] + "' is not expected.", getLineNumberForPosition(xmlData, i));
167
- }
168
- }
169
-
170
- if (!tagFound) {
171
- return getErrorObject('InvalidXml', 'Start tag expected.', 1);
172
- } else if (tags.length == 1) {
173
- return getErrorObject('InvalidTag', "Unclosed tag '" + tags[0].tagName + "'.", getLineNumberForPosition(xmlData, tags[0].tagStartPos));
174
- } else if (tags.length > 0) {
175
- return getErrorObject(
176
- 'InvalidXml',
177
- "Invalid '" +
178
- JSON.stringify(
179
- tags.map((t) => t.tagName),
180
- null,
181
- 4
182
- ).replace(/\r?\n/g, '') +
183
- "' found.",
184
- { line: 1, col: 1 }
185
- );
186
- }
187
-
188
- return true;
189
- }
190
-
191
- function isWhiteSpace(char) {
192
- return char === ' ' || char === '\t' || char === '\n' || char === '\r';
193
- }
194
- /**
195
- * Read Processing insstructions and skip
196
- * @param {*} xmlData
197
- * @param {*} i
198
- */
199
- function readPI(xmlData, i) {
200
- const start = i;
201
- for (; i < xmlData.length; i++) {
202
- if (xmlData[i] == '?' || xmlData[i] == ' ') {
203
- //tagname
204
- const tagname = xmlData.substr(start, i - start);
205
- if (i > 5 && tagname === 'xml') {
206
- return getErrorObject('InvalidXml', 'XML declaration allowed only at the start of the document.', getLineNumberForPosition(xmlData, i));
207
- } else if (xmlData[i] == '?' && xmlData[i + 1] == '>') {
208
- //check if valid attribut string
209
- i++;
210
- break;
211
- } else {
212
- continue;
213
- }
214
- }
215
- }
216
- return i;
217
- }
218
-
219
- function readCommentAndCDATA(xmlData, i) {
220
- if (xmlData.length > i + 5 && xmlData[i + 1] === '-' && xmlData[i + 2] === '-') {
221
- //comment
222
- for (i += 3; i < xmlData.length; i++) {
223
- if (xmlData[i] === '-' && xmlData[i + 1] === '-' && xmlData[i + 2] === '>') {
224
- i += 2;
225
- break;
226
- }
227
- }
228
- } else if (xmlData.length > i + 8 && xmlData[i + 1] === 'D' && xmlData[i + 2] === 'O' && xmlData[i + 3] === 'C' && xmlData[i + 4] === 'T' && xmlData[i + 5] === 'Y' && xmlData[i + 6] === 'P' && xmlData[i + 7] === 'E') {
229
- let angleBracketsCount = 1;
230
- for (i += 8; i < xmlData.length; i++) {
231
- if (xmlData[i] === '<') {
232
- angleBracketsCount++;
233
- } else if (xmlData[i] === '>') {
234
- angleBracketsCount--;
235
- if (angleBracketsCount === 0) {
236
- break;
237
- }
238
- }
239
- }
240
- } else if (xmlData.length > i + 9 && xmlData[i + 1] === '[' && xmlData[i + 2] === 'C' && xmlData[i + 3] === 'D' && xmlData[i + 4] === 'A' && xmlData[i + 5] === 'T' && xmlData[i + 6] === 'A' && xmlData[i + 7] === '[') {
241
- for (i += 8; i < xmlData.length; i++) {
242
- if (xmlData[i] === ']' && xmlData[i + 1] === ']' && xmlData[i + 2] === '>') {
243
- i += 2;
244
- break;
245
- }
246
- }
247
- }
248
-
249
- return i;
250
- }
251
-
252
- const doubleQuote = '"';
253
- const singleQuote = "'";
254
-
255
- /**
256
- * Keep reading xmlData until '<' is found outside the attribute value.
257
- * @param {string} xmlData
258
- * @param {number} i
259
- */
260
- function readAttributeStr(xmlData, i) {
261
- let attrStr = '';
262
- let startChar = '';
263
- let tagClosed = false;
264
- for (; i < xmlData.length; i++) {
265
- if (xmlData[i] === doubleQuote || xmlData[i] === singleQuote) {
266
- if (startChar === '') {
267
- startChar = xmlData[i];
268
- } else if (startChar !== xmlData[i]) {
269
- //if vaue is enclosed with double quote then single quotes are allowed inside the value and vice versa
270
- } else {
271
- startChar = '';
272
- }
273
- } else if (xmlData[i] === '>') {
274
- if (startChar === '') {
275
- tagClosed = true;
276
- break;
277
- }
278
- }
279
- attrStr += xmlData[i];
280
- }
281
- if (startChar !== '') {
282
- return false;
283
- }
284
-
285
- return {
286
- value: attrStr,
287
- index: i,
288
- tagClosed: tagClosed
289
- };
290
- }
291
-
292
- /**
293
- * Select all the attributes whether valid or invalid.
294
- */
295
- const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g');
296
-
297
- //attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
298
-
299
- function validateAttributeString(attrStr, options) {
300
- //console.log("start:"+attrStr+":end");
301
-
302
- //if(attrStr.trim().length === 0) return true; //empty string
303
-
304
- const matches = getAllMatches(attrStr, validAttrStrRegxp);
305
- const attrNames = {};
306
-
307
- for (let i = 0; i < matches.length; i++) {
308
- if (matches[i][1].length === 0) {
309
- //nospace before attribute name: a="sd"b="saf"
310
- return getErrorObject('InvalidAttr', "Attribute '" + matches[i][2] + "' has no space in starting.", getPositionFromMatch(matches[i]));
311
- } else if (matches[i][3] !== undefined && matches[i][4] === undefined) {
312
- return getErrorObject('InvalidAttr', "Attribute '" + matches[i][2] + "' is without value.", getPositionFromMatch(matches[i]));
313
- } else if (matches[i][3] === undefined && !options.allowBooleanAttributes) {
314
- //independent attribute: ab
315
- return getErrorObject('InvalidAttr', "boolean attribute '" + matches[i][2] + "' is not allowed.", getPositionFromMatch(matches[i]));
316
- }
317
- /* else if(matches[i][6] === undefined){//attribute without value: ab=
318
- return { err: { code:"InvalidAttr",msg:"attribute " + matches[i][2] + " has no value assigned."}};
319
- } */
320
- const attrName = matches[i][2];
321
- if (!validateAttrName(attrName)) {
322
- return getErrorObject('InvalidAttr', "Attribute '" + attrName + "' is an invalid name.", getPositionFromMatch(matches[i]));
323
- }
324
- if (!attrNames.hasOwnProperty(attrName)) {
325
- //check for duplicate attribute.
326
- attrNames[attrName] = 1;
327
- } else {
328
- return getErrorObject('InvalidAttr', "Attribute '" + attrName + "' is repeated.", getPositionFromMatch(matches[i]));
329
- }
330
- }
331
-
332
- return true;
333
- }
334
-
335
- function validateNumberAmpersand(xmlData, i) {
336
- let re = /\d/;
337
- if (xmlData[i] === 'x') {
338
- i++;
339
- re = /[\da-fA-F]/;
340
- }
341
- for (; i < xmlData.length; i++) {
342
- if (xmlData[i] === ';') return i;
343
- if (!xmlData[i].match(re)) break;
344
- }
345
- return -1;
346
- }
347
-
348
- function validateAmpersand(xmlData, i) {
349
- // https://www.w3.org/TR/xml/#dt-charref
350
- i++;
351
- if (xmlData[i] === ';') return -1;
352
- if (xmlData[i] === '#') {
353
- i++;
354
- return validateNumberAmpersand(xmlData, i);
355
- }
356
- let count = 0;
357
- for (; i < xmlData.length; i++, count++) {
358
- if (xmlData[i].match(/\w/) && count < 20) continue;
359
- if (xmlData[i] === ';') break;
360
- return -1;
361
- }
362
- return i;
363
- }
364
-
365
- function getErrorObject(code, message, lineNumber) {
366
- return {
367
- err: {
368
- code: code,
369
- msg: message,
370
- line: lineNumber.line || lineNumber,
371
- col: lineNumber.col
372
- }
373
- };
374
- }
375
-
376
- function validateAttrName(attrName) {
377
- return isName(attrName);
378
- }
379
-
380
- // const startsWithXML = /^xml/i;
381
-
382
- function validateTagName(tagname) {
383
- return isName(tagname) /* && !tagname.match(startsWithXML) */;
384
- }
385
-
386
- //this function returns the line number for the character at the given index
387
- function getLineNumberForPosition(xmlData, index) {
388
- const lines = xmlData.substring(0, index).split(/\r?\n/);
389
- return {
390
- line: lines.length,
391
-
392
- // column number is last line's length + 1, because column numbering starts at 1:
393
- col: lines[lines.length - 1].length + 1
394
- };
395
- }
396
-
397
- //this function returns the position of the first character of match within attrStr
398
- function getPositionFromMatch(match) {
399
- return match.startIndex + match[1].length;
400
- }
@@ -1,40 +0,0 @@
1
- 'use strict';
2
-
3
- let METADATA_SYMBOL;
4
-
5
- if (typeof Symbol !== 'function') {
6
- METADATA_SYMBOL = '@@xmlMetadata';
7
- } else {
8
- METADATA_SYMBOL = Symbol('XML Node Metadata');
9
- }
10
-
11
- export default class XmlNode {
12
- constructor(tagname) {
13
- this.tagname = tagname;
14
- this.child = []; //nested tags, text, cdata, comments in order
15
- this[':@'] = {}; //attributes map
16
- }
17
- add(key, val) {
18
- // this.child.push( {name : key, val: val, isCdata: isCdata });
19
- if (key === '__proto__') key = '#__proto__';
20
- this.child.push({ [key]: val });
21
- }
22
- addChild(node, startIndex) {
23
- if (node.tagname === '__proto__') node.tagname = '#__proto__';
24
- if (node[':@'] && Object.keys(node[':@']).length > 0) {
25
- this.child.push({ [node.tagname]: node.child, [':@']: node[':@'] });
26
- } else {
27
- this.child.push({ [node.tagname]: node.child });
28
- }
29
- // if requested, add the startIndex
30
- if (startIndex !== undefined) {
31
- // Note: for now we just overwrite the metadata. If we had more complex metadata,
32
- // we might need to do an object append here: metadata = { ...metadata, startIndex }
33
- this.child[this.child.length - 1][METADATA_SYMBOL] = { startIndex };
34
- }
35
- }
36
- /** symbol used for metadata */
37
- static getMetaDataSymbol() {
38
- return METADATA_SYMBOL;
39
- }
40
- }