befly 0.1.26

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.
@@ -0,0 +1,597 @@
1
+ 'use strict';
2
+ ///@ts-check
3
+
4
+ import { getAllMatches, isExist } from './util.js';
5
+ import xmlNode from './xmlNode.js';
6
+ import readDocType from './DocTypeReader.js';
7
+ import toNumber from './strnum.js';
8
+ import getIgnoreAttributesFn from './ignoreAttributes.js';
9
+
10
+ // const regx =
11
+ // '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
12
+ // .replace(/NAME/g, util.nameRegexp);
13
+
14
+ //const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
15
+ //const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
16
+
17
+ export default class OrderedObjParser {
18
+ constructor(options) {
19
+ this.options = options;
20
+ this.currentNode = null;
21
+ this.tagsNodeStack = [];
22
+ this.docTypeEntities = {};
23
+ this.lastEntities = {
24
+ apos: { regex: /&(apos|#39|#x27);/g, val: "'" },
25
+ gt: { regex: /&(gt|#62|#x3E);/g, val: '>' },
26
+ lt: { regex: /&(lt|#60|#x3C);/g, val: '<' },
27
+ quot: { regex: /&(quot|#34|#x22);/g, val: '"' }
28
+ };
29
+ this.ampEntity = { regex: /&(amp|#38|#x26);/g, val: '&' };
30
+ this.htmlEntities = {
31
+ space: { regex: /&(nbsp|#160);/g, val: ' ' },
32
+ // "lt" : { regex: /&(lt|#60);/g, val: "<" },
33
+ // "gt" : { regex: /&(gt|#62);/g, val: ">" },
34
+ // "amp" : { regex: /&(amp|#38);/g, val: "&" },
35
+ // "quot" : { regex: /&(quot|#34);/g, val: "\"" },
36
+ // "apos" : { regex: /&(apos|#39);/g, val: "'" },
37
+ cent: { regex: /&(cent|#162);/g, val: '¢' },
38
+ pound: { regex: /&(pound|#163);/g, val: '£' },
39
+ yen: { regex: /&(yen|#165);/g, val: '¥' },
40
+ euro: { regex: /&(euro|#8364);/g, val: '€' },
41
+ copyright: { regex: /&(copy|#169);/g, val: '©' },
42
+ reg: { regex: /&(reg|#174);/g, val: '®' },
43
+ inr: { regex: /&(inr|#8377);/g, val: '₹' },
44
+ num_dec: { regex: /&#([0-9]{1,7});/g, val: (_, str) => String.fromCodePoint(Number.parseInt(str, 10)) },
45
+ num_hex: { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => String.fromCodePoint(Number.parseInt(str, 16)) }
46
+ };
47
+ this.addExternalEntities = addExternalEntities;
48
+ this.parseXml = parseXml;
49
+ this.parseTextData = parseTextData;
50
+ this.resolveNameSpace = resolveNameSpace;
51
+ this.buildAttributesMap = buildAttributesMap;
52
+ this.isItStopNode = isItStopNode;
53
+ this.replaceEntitiesValue = replaceEntitiesValue;
54
+ this.readStopNodeData = readStopNodeData;
55
+ this.saveTextToParentTag = saveTextToParentTag;
56
+ this.addChild = addChild;
57
+ this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes);
58
+ }
59
+ }
60
+
61
+ function addExternalEntities(externalEntities) {
62
+ const entKeys = Object.keys(externalEntities);
63
+ for (let i = 0; i < entKeys.length; i++) {
64
+ const ent = entKeys[i];
65
+ this.lastEntities[ent] = {
66
+ regex: new RegExp('&' + ent + ';', 'g'),
67
+ val: externalEntities[ent]
68
+ };
69
+ }
70
+ }
71
+
72
+ /**
73
+ * @param {string} val
74
+ * @param {string} tagName
75
+ * @param {string} jPath
76
+ * @param {boolean} dontTrim
77
+ * @param {boolean} hasAttributes
78
+ * @param {boolean} isLeafNode
79
+ * @param {boolean} escapeEntities
80
+ */
81
+ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, escapeEntities) {
82
+ if (val !== undefined) {
83
+ if (this.options.trimValues && !dontTrim) {
84
+ val = val.trim();
85
+ }
86
+ if (val.length > 0) {
87
+ if (!escapeEntities) val = this.replaceEntitiesValue(val);
88
+
89
+ const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
90
+ if (newval === null || newval === undefined) {
91
+ //don't parse
92
+ return val;
93
+ } else if (typeof newval !== typeof val || newval !== val) {
94
+ //overwrite
95
+ return newval;
96
+ } else if (this.options.trimValues) {
97
+ return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
98
+ } else {
99
+ const trimmedVal = val.trim();
100
+ if (trimmedVal === val) {
101
+ return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
102
+ } else {
103
+ return val;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ function resolveNameSpace(tagname) {
111
+ if (this.options.removeNSPrefix) {
112
+ const tags = tagname.split(':');
113
+ const prefix = tagname.charAt(0) === '/' ? '/' : '';
114
+ if (tags[0] === 'xmlns') {
115
+ return '';
116
+ }
117
+ if (tags.length === 2) {
118
+ tagname = prefix + tags[1];
119
+ }
120
+ }
121
+ return tagname;
122
+ }
123
+
124
+ //TODO: change regex to capture NS
125
+ //const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
126
+ const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
127
+
128
+ function buildAttributesMap(attrStr, jPath, tagName) {
129
+ if (this.options.ignoreAttributes !== true && typeof attrStr === 'string') {
130
+ // attrStr = attrStr.replace(/\r?\n/g, ' ');
131
+ //attrStr = attrStr || attrStr.trim();
132
+
133
+ const matches = getAllMatches(attrStr, attrsRegx);
134
+ const len = matches.length; //don't make it inline
135
+ const attrs = {};
136
+ for (let i = 0; i < len; i++) {
137
+ const attrName = this.resolveNameSpace(matches[i][1]);
138
+ if (this.ignoreAttributesFn(attrName, jPath)) {
139
+ continue;
140
+ }
141
+ let oldVal = matches[i][4];
142
+ let aName = this.options.attributeNamePrefix + attrName;
143
+ if (attrName.length) {
144
+ if (this.options.transformAttributeName) {
145
+ aName = this.options.transformAttributeName(aName);
146
+ }
147
+ if (aName === '__proto__') aName = '#__proto__';
148
+ if (oldVal !== undefined) {
149
+ if (this.options.trimValues) {
150
+ oldVal = oldVal.trim();
151
+ }
152
+ oldVal = this.replaceEntitiesValue(oldVal);
153
+ const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath);
154
+ if (newVal === null || newVal === undefined) {
155
+ //don't parse
156
+ attrs[aName] = oldVal;
157
+ } else if (typeof newVal !== typeof oldVal || newVal !== oldVal) {
158
+ //overwrite
159
+ attrs[aName] = newVal;
160
+ } else {
161
+ //parse
162
+ attrs[aName] = parseValue(oldVal, this.options.parseAttributeValue, this.options.numberParseOptions);
163
+ }
164
+ } else if (this.options.allowBooleanAttributes) {
165
+ attrs[aName] = true;
166
+ }
167
+ }
168
+ }
169
+ if (!Object.keys(attrs).length) {
170
+ return;
171
+ }
172
+ if (this.options.attributesGroupName) {
173
+ const attrCollection = {};
174
+ attrCollection[this.options.attributesGroupName] = attrs;
175
+ return attrCollection;
176
+ }
177
+ return attrs;
178
+ }
179
+ }
180
+
181
+ const parseXml = function (xmlData) {
182
+ xmlData = xmlData.replace(/\r\n?/g, '\n'); //TODO: remove this line
183
+ const xmlObj = new xmlNode('!xml');
184
+ let currentNode = xmlObj;
185
+ let textData = '';
186
+ let jPath = '';
187
+ for (let i = 0; i < xmlData.length; i++) {
188
+ //for each char in XML data
189
+ const ch = xmlData[i];
190
+ if (ch === '<') {
191
+ // const nextIndex = i+1;
192
+ // const _2ndChar = xmlData[nextIndex];
193
+ if (xmlData[i + 1] === '/') {
194
+ //Closing Tag
195
+ const closeIndex = findClosingIndex(xmlData, '>', i, 'Closing Tag is not closed.');
196
+ let tagName = xmlData.substring(i + 2, closeIndex).trim();
197
+
198
+ if (this.options.removeNSPrefix) {
199
+ const colonIndex = tagName.indexOf(':');
200
+ if (colonIndex !== -1) {
201
+ tagName = tagName.substr(colonIndex + 1);
202
+ }
203
+ }
204
+
205
+ if (this.options.transformTagName) {
206
+ tagName = this.options.transformTagName(tagName);
207
+ }
208
+
209
+ if (currentNode) {
210
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
211
+ }
212
+
213
+ //check if last tag of nested tag was unpaired tag
214
+ const lastTagName = jPath.substring(jPath.lastIndexOf('.') + 1);
215
+ if (tagName && this.options.unpairedTags.indexOf(tagName) !== -1) {
216
+ throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
217
+ }
218
+ let propIndex = 0;
219
+ if (lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1) {
220
+ propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.') - 1);
221
+ this.tagsNodeStack.pop();
222
+ } else {
223
+ propIndex = jPath.lastIndexOf('.');
224
+ }
225
+ jPath = jPath.substring(0, propIndex);
226
+
227
+ currentNode = this.tagsNodeStack.pop(); //avoid recursion, set the parent tag scope
228
+ textData = '';
229
+ i = closeIndex;
230
+ } else if (xmlData[i + 1] === '?') {
231
+ let tagData = readTagExp(xmlData, i, false, '?>');
232
+ if (!tagData) throw new Error('Pi Tag is not closed.');
233
+
234
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
235
+ if ((this.options.ignoreDeclaration && tagData.tagName === '?xml') || this.options.ignorePiTags) {
236
+ } else {
237
+ const childNode = new xmlNode(tagData.tagName);
238
+ childNode.add(this.options.textNodeName, '');
239
+
240
+ if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
241
+ childNode[':@'] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
242
+ }
243
+ this.addChild(currentNode, childNode, jPath, i);
244
+ }
245
+
246
+ i = tagData.closeIndex + 1;
247
+ } else if (xmlData.substr(i + 1, 3) === '!--') {
248
+ const endIndex = findClosingIndex(xmlData, '-->', i + 4, 'Comment is not closed.');
249
+ if (this.options.commentPropName) {
250
+ const comment = xmlData.substring(i + 4, endIndex - 2);
251
+
252
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
253
+
254
+ currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]);
255
+ }
256
+ i = endIndex;
257
+ } else if (xmlData.substr(i + 1, 2) === '!D') {
258
+ const result = readDocType(xmlData, i);
259
+ this.docTypeEntities = result.entities;
260
+ i = result.i;
261
+ } else if (xmlData.substr(i + 1, 2) === '![') {
262
+ const closeIndex = findClosingIndex(xmlData, ']]>', i, 'CDATA is not closed.') - 2;
263
+ const tagExp = xmlData.substring(i + 9, closeIndex);
264
+
265
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
266
+
267
+ let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
268
+ if (val == undefined) val = '';
269
+
270
+ //cdata should be set even if it is 0 length string
271
+ if (this.options.cdataPropName) {
272
+ currentNode.add(this.options.cdataPropName, [{ [this.options.textNodeName]: tagExp }]);
273
+ } else {
274
+ currentNode.add(this.options.textNodeName, val);
275
+ }
276
+
277
+ i = closeIndex + 2;
278
+ } else {
279
+ //Opening tag
280
+ let result = readTagExp(xmlData, i, this.options.removeNSPrefix);
281
+ let tagName = result.tagName;
282
+ const rawTagName = result.rawTagName;
283
+ let tagExp = result.tagExp;
284
+ let attrExpPresent = result.attrExpPresent;
285
+ let closeIndex = result.closeIndex;
286
+
287
+ if (this.options.transformTagName) {
288
+ tagName = this.options.transformTagName(tagName);
289
+ }
290
+
291
+ //save text as child node
292
+ if (currentNode && textData) {
293
+ if (currentNode.tagname !== '!xml') {
294
+ //when nested tag is found
295
+ textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
296
+ }
297
+ }
298
+
299
+ //check if last tag was unpaired tag
300
+ const lastTag = currentNode;
301
+ if (lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1) {
302
+ currentNode = this.tagsNodeStack.pop();
303
+ jPath = jPath.substring(0, jPath.lastIndexOf('.'));
304
+ }
305
+ if (tagName !== xmlObj.tagname) {
306
+ jPath += jPath ? '.' + tagName : tagName;
307
+ }
308
+ const startIndex = i;
309
+ if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
310
+ let tagContent = '';
311
+ //self-closing tag
312
+ if (tagExp.length > 0 && tagExp.lastIndexOf('/') === tagExp.length - 1) {
313
+ if (tagName[tagName.length - 1] === '/') {
314
+ //remove trailing '/'
315
+ tagName = tagName.substr(0, tagName.length - 1);
316
+ jPath = jPath.substr(0, jPath.length - 1);
317
+ tagExp = tagName;
318
+ } else {
319
+ tagExp = tagExp.substr(0, tagExp.length - 1);
320
+ }
321
+ i = result.closeIndex;
322
+ }
323
+ //unpaired tag
324
+ else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
325
+ i = result.closeIndex;
326
+ }
327
+ //normal tag
328
+ else {
329
+ //read until closing tag is found
330
+ const result = this.readStopNodeData(xmlData, rawTagName, closeIndex + 1);
331
+ if (!result) throw new Error(`Unexpected end of ${rawTagName}`);
332
+ i = result.i;
333
+ tagContent = result.tagContent;
334
+ }
335
+
336
+ const childNode = new xmlNode(tagName);
337
+
338
+ if (tagName !== tagExp && attrExpPresent) {
339
+ childNode[':@'] = this.buildAttributesMap(tagExp, jPath, tagName);
340
+ }
341
+ if (tagContent) {
342
+ tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
343
+ }
344
+
345
+ jPath = jPath.substr(0, jPath.lastIndexOf('.'));
346
+ childNode.add(this.options.textNodeName, tagContent);
347
+
348
+ this.addChild(currentNode, childNode, jPath, startIndex);
349
+ } else {
350
+ //selfClosing tag
351
+ if (tagExp.length > 0 && tagExp.lastIndexOf('/') === tagExp.length - 1) {
352
+ if (tagName[tagName.length - 1] === '/') {
353
+ //remove trailing '/'
354
+ tagName = tagName.substr(0, tagName.length - 1);
355
+ jPath = jPath.substr(0, jPath.length - 1);
356
+ tagExp = tagName;
357
+ } else {
358
+ tagExp = tagExp.substr(0, tagExp.length - 1);
359
+ }
360
+
361
+ if (this.options.transformTagName) {
362
+ tagName = this.options.transformTagName(tagName);
363
+ }
364
+
365
+ const childNode = new xmlNode(tagName);
366
+ if (tagName !== tagExp && attrExpPresent) {
367
+ childNode[':@'] = this.buildAttributesMap(tagExp, jPath, tagName);
368
+ }
369
+ this.addChild(currentNode, childNode, jPath, startIndex);
370
+ jPath = jPath.substr(0, jPath.lastIndexOf('.'));
371
+ }
372
+ //opening tag
373
+ else {
374
+ const childNode = new xmlNode(tagName);
375
+ this.tagsNodeStack.push(currentNode);
376
+
377
+ if (tagName !== tagExp && attrExpPresent) {
378
+ childNode[':@'] = this.buildAttributesMap(tagExp, jPath, tagName);
379
+ }
380
+ this.addChild(currentNode, childNode, jPath, startIndex);
381
+ currentNode = childNode;
382
+ }
383
+ textData = '';
384
+ i = closeIndex;
385
+ }
386
+ }
387
+ } else {
388
+ textData += xmlData[i];
389
+ }
390
+ }
391
+ return xmlObj.child;
392
+ };
393
+
394
+ function addChild(currentNode, childNode, jPath, startIndex) {
395
+ // unset startIndex if not requested
396
+ if (!this.options.captureMetaData) startIndex = undefined;
397
+ const result = this.options.updateTag(childNode.tagname, jPath, childNode[':@']);
398
+ if (result === false) {
399
+ } else if (typeof result === 'string') {
400
+ childNode.tagname = result;
401
+ currentNode.addChild(childNode, startIndex);
402
+ } else {
403
+ currentNode.addChild(childNode, startIndex);
404
+ }
405
+ }
406
+
407
+ const replaceEntitiesValue = function (val) {
408
+ if (this.options.processEntities) {
409
+ for (let entityName in this.docTypeEntities) {
410
+ const entity = this.docTypeEntities[entityName];
411
+ val = val.replace(entity.regx, entity.val);
412
+ }
413
+ for (let entityName in this.lastEntities) {
414
+ const entity = this.lastEntities[entityName];
415
+ val = val.replace(entity.regex, entity.val);
416
+ }
417
+ if (this.options.htmlEntities) {
418
+ for (let entityName in this.htmlEntities) {
419
+ const entity = this.htmlEntities[entityName];
420
+ val = val.replace(entity.regex, entity.val);
421
+ }
422
+ }
423
+ val = val.replace(this.ampEntity.regex, this.ampEntity.val);
424
+ }
425
+ return val;
426
+ };
427
+ function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
428
+ if (textData) {
429
+ //store previously collected data as textNode
430
+ if (isLeafNode === undefined) isLeafNode = currentNode.child.length === 0;
431
+
432
+ textData = this.parseTextData(textData, currentNode.tagname, jPath, false, currentNode[':@'] ? Object.keys(currentNode[':@']).length !== 0 : false, isLeafNode);
433
+
434
+ if (textData !== undefined && textData !== '') currentNode.add(this.options.textNodeName, textData);
435
+ textData = '';
436
+ }
437
+ return textData;
438
+ }
439
+
440
+ //TODO: use jPath to simplify the logic
441
+ /**
442
+ *
443
+ * @param {string[]} stopNodes
444
+ * @param {string} jPath
445
+ * @param {string} currentTagName
446
+ */
447
+ function isItStopNode(stopNodes, jPath, currentTagName) {
448
+ const allNodesExp = '*.' + currentTagName;
449
+ for (const stopNodePath in stopNodes) {
450
+ const stopNodeExp = stopNodes[stopNodePath];
451
+ if (allNodesExp === stopNodeExp || jPath === stopNodeExp) return true;
452
+ }
453
+ return false;
454
+ }
455
+
456
+ /**
457
+ * Returns the tag Expression and where it is ending handling single-double quotes situation
458
+ * @param {string} xmlData
459
+ * @param {number} i starting index
460
+ * @returns
461
+ */
462
+ function tagExpWithClosingIndex(xmlData, i, closingChar = '>') {
463
+ let attrBoundary;
464
+ let tagExp = '';
465
+ for (let index = i; index < xmlData.length; index++) {
466
+ let ch = xmlData[index];
467
+ if (attrBoundary) {
468
+ if (ch === attrBoundary) attrBoundary = ''; //reset
469
+ } else if (ch === '"' || ch === "'") {
470
+ attrBoundary = ch;
471
+ } else if (ch === closingChar[0]) {
472
+ if (closingChar[1]) {
473
+ if (xmlData[index + 1] === closingChar[1]) {
474
+ return {
475
+ data: tagExp,
476
+ index: index
477
+ };
478
+ }
479
+ } else {
480
+ return {
481
+ data: tagExp,
482
+ index: index
483
+ };
484
+ }
485
+ } else if (ch === '\t') {
486
+ ch = ' ';
487
+ }
488
+ tagExp += ch;
489
+ }
490
+ }
491
+
492
+ function findClosingIndex(xmlData, str, i, errMsg) {
493
+ const closingIndex = xmlData.indexOf(str, i);
494
+ if (closingIndex === -1) {
495
+ throw new Error(errMsg);
496
+ } else {
497
+ return closingIndex + str.length - 1;
498
+ }
499
+ }
500
+
501
+ function readTagExp(xmlData, i, removeNSPrefix, closingChar = '>') {
502
+ const result = tagExpWithClosingIndex(xmlData, i + 1, closingChar);
503
+ if (!result) return;
504
+ let tagExp = result.data;
505
+ const closeIndex = result.index;
506
+ const separatorIndex = tagExp.search(/\s/);
507
+ let tagName = tagExp;
508
+ let attrExpPresent = true;
509
+ if (separatorIndex !== -1) {
510
+ //separate tag name and attributes expression
511
+ tagName = tagExp.substring(0, separatorIndex);
512
+ tagExp = tagExp.substring(separatorIndex + 1).trimStart();
513
+ }
514
+
515
+ const rawTagName = tagName;
516
+ if (removeNSPrefix) {
517
+ const colonIndex = tagName.indexOf(':');
518
+ if (colonIndex !== -1) {
519
+ tagName = tagName.substr(colonIndex + 1);
520
+ attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
521
+ }
522
+ }
523
+
524
+ return {
525
+ tagName: tagName,
526
+ tagExp: tagExp,
527
+ closeIndex: closeIndex,
528
+ attrExpPresent: attrExpPresent,
529
+ rawTagName: rawTagName
530
+ };
531
+ }
532
+ /**
533
+ * find paired tag for a stop node
534
+ * @param {string} xmlData
535
+ * @param {string} tagName
536
+ * @param {number} i
537
+ */
538
+ function readStopNodeData(xmlData, tagName, i) {
539
+ const startIndex = i;
540
+ // Starting at 1 since we already have an open tag
541
+ let openTagCount = 1;
542
+
543
+ for (; i < xmlData.length; i++) {
544
+ if (xmlData[i] === '<') {
545
+ if (xmlData[i + 1] === '/') {
546
+ //close tag
547
+ const closeIndex = findClosingIndex(xmlData, '>', i, `${tagName} is not closed`);
548
+ let closeTagName = xmlData.substring(i + 2, closeIndex).trim();
549
+ if (closeTagName === tagName) {
550
+ openTagCount--;
551
+ if (openTagCount === 0) {
552
+ return {
553
+ tagContent: xmlData.substring(startIndex, i),
554
+ i: closeIndex
555
+ };
556
+ }
557
+ }
558
+ i = closeIndex;
559
+ } else if (xmlData[i + 1] === '?') {
560
+ const closeIndex = findClosingIndex(xmlData, '?>', i + 1, 'StopNode is not closed.');
561
+ i = closeIndex;
562
+ } else if (xmlData.substr(i + 1, 3) === '!--') {
563
+ const closeIndex = findClosingIndex(xmlData, '-->', i + 3, 'StopNode is not closed.');
564
+ i = closeIndex;
565
+ } else if (xmlData.substr(i + 1, 2) === '![') {
566
+ const closeIndex = findClosingIndex(xmlData, ']]>', i, 'StopNode is not closed.') - 2;
567
+ i = closeIndex;
568
+ } else {
569
+ const tagData = readTagExp(xmlData, i, '>');
570
+
571
+ if (tagData) {
572
+ const openTagName = tagData && tagData.tagName;
573
+ if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length - 1] !== '/') {
574
+ openTagCount++;
575
+ }
576
+ i = tagData.closeIndex;
577
+ }
578
+ }
579
+ }
580
+ } //end for loop
581
+ }
582
+
583
+ function parseValue(val, shouldParse, options) {
584
+ if (shouldParse && typeof val === 'string') {
585
+ //console.log(options)
586
+ const newval = val.trim();
587
+ if (newval === 'true') return true;
588
+ else if (newval === 'false') return false;
589
+ else return toNumber(val, options);
590
+ } else {
591
+ if (isExist(val)) {
592
+ return val;
593
+ } else {
594
+ return '';
595
+ }
596
+ }
597
+ }
@@ -0,0 +1,69 @@
1
+ import { buildOptions } from './OptionsBuilder.js';
2
+ import OrderedObjParser from './OrderedObjParser.js';
3
+ import prettify from './node2json.js';
4
+ import { validate } from './validator.js';
5
+ import XmlNode from './xmlNode.js';
6
+
7
+ export class XMLParser {
8
+ constructor(options) {
9
+ this.externalEntities = {};
10
+ this.options = buildOptions(options);
11
+ }
12
+ /**
13
+ * Parse XML dats to JS object
14
+ * @param {string|Buffer} xmlData
15
+ * @param {boolean|Object} validationOption
16
+ */
17
+ parse(xmlData, validationOption) {
18
+ if (typeof xmlData === 'string') {
19
+ } else if (xmlData.toString) {
20
+ xmlData = xmlData.toString();
21
+ } else {
22
+ throw new Error('XML data is accepted in String or Bytes[] form.');
23
+ }
24
+ if (validationOption) {
25
+ if (validationOption === true) validationOption = {}; //validate with default options
26
+
27
+ const result = validate(xmlData, validationOption);
28
+ if (result !== true) {
29
+ throw Error(`${result.err.msg}:${result.err.line}:${result.err.col}`);
30
+ }
31
+ }
32
+ const orderedObjParser = new OrderedObjParser(this.options);
33
+ orderedObjParser.addExternalEntities(this.externalEntities);
34
+ const orderedResult = orderedObjParser.parseXml(xmlData);
35
+ if (this.options.preserveOrder || orderedResult === undefined) return orderedResult;
36
+ else return prettify(orderedResult, this.options);
37
+ }
38
+
39
+ /**
40
+ * Add Entity which is not by default supported by this library
41
+ * @param {string} key
42
+ * @param {string} value
43
+ */
44
+ addEntity(key, value) {
45
+ if (value.indexOf('&') !== -1) {
46
+ throw new Error("Entity value can't have '&'");
47
+ } else if (key.indexOf('&') !== -1 || key.indexOf(';') !== -1) {
48
+ throw new Error("An entity must be set without '&' and ';'. Eg. use '#xD' for '&#xD;'");
49
+ } else if (value === '&') {
50
+ throw new Error("An entity with value '&' is not permitted");
51
+ } else {
52
+ this.externalEntities[key] = value;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Returns a Symbol that can be used to access the metadata
58
+ * property on a node.
59
+ *
60
+ * If Symbol is not available in the environment, an ordinary property is used
61
+ * and the name of the property is here returned.
62
+ *
63
+ * The XMLMetaData property is only present when `captureMetaData`
64
+ * is true in the options.
65
+ */
66
+ static getMetaDataSymbol() {
67
+ return XmlNode.getMetaDataSymbol();
68
+ }
69
+ }
@@ -0,0 +1,18 @@
1
+ export default function getIgnoreAttributesFn(ignoreAttributes) {
2
+ if (typeof ignoreAttributes === 'function') {
3
+ return ignoreAttributes;
4
+ }
5
+ if (Array.isArray(ignoreAttributes)) {
6
+ return (attrName) => {
7
+ for (const pattern of ignoreAttributes) {
8
+ if (typeof pattern === 'string' && attrName === pattern) {
9
+ return true;
10
+ }
11
+ if (pattern instanceof RegExp && pattern.test(attrName)) {
12
+ return true;
13
+ }
14
+ }
15
+ };
16
+ }
17
+ return () => false;
18
+ }