UNPKG

21.7 kBJavaScriptView Raw
1 /*
2 new function() {
3 var parser = new EasySAXParser();
4
5 parser.ns('rss', { // or false
6 rss: 'http://purl.org/rss/1.0/',
7 atom: 'http://www.w3.org/2005/Atom',
8 xhtml: 'http://www.w3.org/1999/xhtml',
9 media: 'http://search.yahoo.com/mrss/'
10 });
11
12
13 parser.on('error', function(msg) {
14 //console.log(msg)
15 });
16
17 parser.on('startNode', function(elem, attr, uq, tagend, getStrNode) {
18 attr();
19 return;
20 if (tagend) {
21 console.log(' '+str)
22 } else {
23 console.log('+ '+str)
24 };
25 });
26
27 parser.on('endNode', function(elem, uq, tagstart, str) {
28 return;
29 if (!tagstart) console.log('- ' + str)
30 });
31
32 parser.on('textNode', function(s, uq) {
33 uq(s);
34 return
35 console.log(' '+s)
36 });
37
38 parser.on('cdata', function(data) {
39 });
40
41
42 parser.on('comment', function(text) {
43 //console.log('--'+text+'--')
44 });
45
46 //parser.on('question', function() {}); // <? ... ?>
47 //parser.on('attention', function() {}); // <!XXXXX zzzz="eeee">
48
49 console.time('easysax');
50 for(var z=1000;z--;) {
51 parser.parse(xml)
52 };
53 console.timeEnd('easysax');
54 };
55
56
57 */
58
59// << ------------------------------------------------------------------------ >> //
60
61
62if (typeof exports === 'object' /*&& this == exports*/) {
63 module.exports.EasySAXParser = EasySAXParser;
64};
65
66function EasySAXParser() {
67 'use strict';
68
69 if (!this) return null;
70
71 this.angularSyntax = false;
72
73 function nullFunc() {};
74
75 this.onTextNode = nullFunc;
76 this.onStartNode = nullFunc;
77 this.onEndNode = nullFunc;
78 this.onCDATA = nullFunc;
79 this.onError = nullFunc;
80 this.onComment = null;
81 this.onQuestion = null;
82 this.onAttention = null;
83 this.is_onComment = this.is_onQuestion = this.is_onAttention = false;
84
85 this.isNamespace = false;
86 this.useNS = null;
87 this.default_xmlns = null;
88 this.xmlns = null;
89 this.nsmatrix = {xmlns: this.xmlns};
90 this.hasSurmiseNS = false;
91 ;
92
93
94 this.attr_string = ''; // строка атрибутов
95 this.attr_posstart = 0; //
96 this.attr_res; // закешированный результат разбора атрибутов , null - разбор не проводился, object - хеш атрибутов, true - нет атрибутов, false - невалидный xml
97}
98
99EasySAXParser.prototype.on = function(name, cb) {
100 if (typeof cb !== 'function') {
101 if (cb !== null) return;
102 };
103
104 switch(name) {
105 case 'error': this.onError = cb || nullFunc; break;
106 case 'startNode': this.onStartNode = cb || nullFunc; break;
107 case 'endNode': this.onEndNode = cb || nullFunc; break;
108 case 'textNode': this.onTextNode = cb || nullFunc; break;
109 case 'cdata': this.onCDATA = cb || nullFunc; break;
110
111 case 'comment': this.onComment = cb; this.is_onComment = !!cb; break;
112 case 'question': this.onQuestion = cb; this.is_onQuestion = !!cb; break; // <? .... ?>
113 case 'attention': this.onAttention = cb; this.is_onAttention = !!cb; break; // <!XXXXX zzzz="eeee">
114 };
115};
116
117EasySAXParser.prototype.ns = function(root, ns) {
118 if (!root || typeof root !== 'string' || !ns) {
119 return;
120 };
121
122 var u, x = {}, ok, v, i;
123
124 for(i in ns) {
125 v = ns[i];
126 if (typeof v === 'string') {
127 if (root === v) ok = true;
128 x[i] = v;
129 };
130 };
131
132 if (ok) {
133 this.isNamespace = true;
134 this.default_xmlns = root;
135 this.useNS = x;
136 };
137};
138
139
140EasySAXParser.prototype.parse = function(xml) {
141 if (typeof xml !== 'string') {
142 return;
143 };
144
145 if (this.isNamespace) {
146 this.nsmatrix = {xmlns: this.default_xmlns};
147
148 parse(xml);
149
150 this.nsmatrix = false;
151
152 } else {
153 parse(xml);
154 };
155
156 this.attr_res = true;
157};
158
159// -----------------------------------------------------
160
161var xharsQuot={constructor: false, hasOwnProperty: false, isPrototypeOf: false, propertyIsEnumerable: false, toLocaleString: false, toString: false, valueOf: false
162 , quot: '"'
163 , QUOT: '"'
164 , amp: '&'
165 , AMP: '&'
166 , nbsp: '\u00A0'
167 , apos: '\''
168 , lt: '<'
169 , LT: '<'
170 , gt: '>'
171 , GT: '>'
172 , copy: '\u00A9'
173 , laquo: '\u00AB'
174 , raquo: '\u00BB'
175 , reg: '\u00AE'
176 , deg: '\u00B0'
177 , plusmn: '\u00B1'
178 , sup2: '\u00B2'
179 , sup3: '\u00B3'
180 , micro: '\u00B5'
181 , para: '\u00B6'
182};
183
184
185function rpEntities(s, d, x, z) {
186 if (z) {
187 return xharsQuot[z] || '\x01';
188 };
189
190 if (d) {
191 return String.fromCodePoint(d);
192 };
193
194 return String.fromCodePoint(parseInt(x, 16));
195};
196
197function unEntities(s, i) {
198 s = String(s);
199 if (s.length > 3 && s.indexOf('&') !== -1) {
200 if (s.indexOf('&gt;') !== -1) s = s.replace(/&gt;/g, '>');
201 if (s.indexOf('&lt;') !== -1) s = s.replace(/&lt;/g, '<');
202 if (s.indexOf('&quot;') !== -1) s = s.replace(/&quot;/g, '"');
203
204 if (s.indexOf('&') !== -1) {
205 s = s.replace(/&#(\d+);|&#x([0123456789abcdef]+);|&(\w+);/ig, rpEntities);
206 };
207 };
208
209 return s;
210};
211
212
213EasySAXParser.prototype.allowedAngularAttributeChars = function(w) {
214 if (!this.angularSyntax) {
215 return false;
216 } else {
217 return (
218 w === 40 || // (
219 w === 41 || // )
220 w === 91 || // [
221 w === 93 || // ]
222 w === 94 || // ^
223 w === 35 // #
224 );
225 }
226};
227
228 /*
229 парсит атрибуты по требованию. Важно! - функция не генерирует исключения.
230
231 если была ошибка разбора возврашается false
232 если атрибутов нет и разбор удачен то возврашается true
233 если есть атрибуты то возврашается обьект(хеш)
234 */
235
236EasySAXParser.prototype.getAttrs = function() {
237 if (this.attr_res !== null) {
238 return this.attr_res;
239 };
240
241 /*
242 if (xxtest !== u && attr_string.indexOf(xxtest) === -1) {
243 / *
244 // для ускорения
245 if (getAttrs('html').type == 'html') {
246 ...
247 };
248 * /
249 return true;
250 };
251 */
252
253 var u
254 , res = {}
255 , s = this.attr_string
256 , i = this.attr_posstart
257 , l = s.length
258 , attr_list = this.hasSurmiseNS ? [] : false
259 , name, value = ''
260 , ok = false
261 , noValueAttribute = false
262 , j, w, nn, n
263 , hasNewMatrix
264 , alias, newalias
265 ;
266
267 aa:
268 for(; i < l; i++) {
269 w = s.charCodeAt(i);
270
271 if (w===32 || (w<14 && w > 8) ) { // \f\n\r\t\v
272 continue
273 };
274
275 // Check for valid attribute start char
276 if ((w < 65 && !this.allowedAngularAttributeChars(w)) ||
277 w > 122 || (w > 90 && w < 97 && !this.allowedAngularAttributeChars(w)) ) { // ожидаем символ
278 return this.attr_res = false; // error. invalid char
279 };
280
281 for(j = i + 1; j < l; j++) { // проверяем все символы имени атрибута
282 w = s.charCodeAt(j);
283
284 if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w === 46 /* https://github.com/telerik/xPlatCore/issues/179 */) {
285 if (noValueAttribute) {
286 j--; //Started next attribute. Get back and break out of the loop.
287 break;
288 } else {
289 continue;
290 }
291 };
292
293 if (this.allowedAngularAttributeChars(w)) {
294 continue;
295 }
296
297 if (w === 32 || (w > 8 && w < 14) ) { // \f\n\r\t\v пробел
298 noValueAttribute = true;
299 continue;
300 } else if (w === 61) { // "=" == 61
301 noValueAttribute = false;
302 break;
303 } else {
304 //console.log('error 2');
305 if (!noValueAttribute)
306 return this.attr_res = false; // error. invalid char
307 };
308
309 break;
310 };
311
312 name = s.substring(i, j).trim();
313 ok = true;
314
315 if (name === 'xmlns:xmlns') {
316 //console.log('error 6')
317 return this.attr_res = false; // error. invalid name
318 };
319
320 w = s.charCodeAt(j+1);
321
322 while (w = s.charCodeAt(j+1)) {
323 if (w===32 || (w > 8 && w<14) ) { // \f\n\r\t\v пробел
324 j++;
325 } else {
326 break;
327 }
328 }
329
330 if (!noValueAttribute) {
331 if (w === 34) { // '"'
332 j = s.indexOf('"', i = j+2 );
333
334 } else {
335 if (w === 39) {
336 j = s.indexOf('\'', i = j+2 );
337
338 } else { // "'"
339 return this.attr_res = false; // error. invalid char
340 };
341 };
342 }
343
344 if (j === -1) {
345 //console.log('error 4')
346 return this.attr_res = false; // error. invalid char
347 };
348
349
350 if (j+1 < l && !noValueAttribute) {
351 w = s.charCodeAt(j+1);
352
353 if (w > 32 || w < 9 || (w < 32 && w > 13)) {
354 // error. invalid char
355 //console.log('error 5')
356 return this.attr_res = false;
357 };
358 };
359
360
361 if (noValueAttribute) {
362 value = '';
363 } else {
364 value = s.substring(i, j);
365 }
366
367 //i = j + 1; // след. семвол уже проверен потому проверять нужно следуюший
368 i = j; // след. семвол уже проверен потому проверять нужно следуюший
369
370 if (this.isNamespace) { //
371 if (this.hasSurmiseNS) {
372 // есть подозрение что в атрибутах присутствует xmlns
373
374 if (newalias = name === 'xmlns' ? 'xmlns' : name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:' && name.substr(6) ) {
375 alias = this.useNS[unEntities(value)];
376
377 if (alias) {
378 if (this.nsmatrix[newalias] !== alias) {
379 if (!hasNewMatrix) {
380 hasNewMatrix = true;
381 nn = {}; for (n in this.nsmatrix) nn[n] = this.nsmatrix[n];
382 this.nsmatrix = nn;
383 };
384
385 this.nsmatrix[newalias] = alias;
386 };
387 } else {
388 if (this.nsmatrix[newalias]) {
389 if (!hasNewMatrix) {
390 hasNewMatrix = true;
391 nn = {}; for (n in this.nsmatrix) nn[n] = this.nsmatrix[n];
392 this.nsmatrix = nn;
393 };
394
395 this.nsmatrix[newalias] = false;
396 };
397 };
398
399 res[name] = value;
400 continue;
401 };
402
403 attr_list.push(name, value);
404 continue;
405 };
406
407 w = name.length;
408 while(--w) {
409 if (name.charCodeAt(w) === 58) { // ':'
410 if (w = this.nsmatrix[name.substring(0, w)] ) {
411 res[w + name.substr(w)] = value;
412 };
413 continue aa;
414
415 // 'xml:base' ???
416 };
417 };
418 };
419
420 res[name] = value;
421 noValueAttribute = false;
422 };
423
424
425 if (!ok) {
426 return this.attr_res = true; // атрибутов нет, ошибок тоже нет
427 };
428
429
430 if (this.hasSurmiseNS) {
431 bb:
432
433 for (i = 0, l = attr_list.length; i < l; i++) {
434 name = attr_list[i++];
435
436 w = name.length;
437 while(--w) { // name.indexOf(':')
438 if (name.charCodeAt(w) === 58) { // ':'
439 if (w = this.nsmatrix[name.substring(0, w)]) {
440 res[w + name.substr(w)] = attr_list[i];
441 };
442 continue bb;
443 break;
444 };
445 };
446
447 res[name] = attr_list[i];
448 };
449 };
450
451 return this.attr_res = res;
452};
453
454
455// xml - string
456EasySAXParser.prototype.parse = function(xml) {
457 var u
458 , xml = String(xml)
459 , nodestack = []
460 , stacknsmatrix = []
461 //, string_node
462 , elem
463 , tagend = false
464 , tagstart = false
465 , j = 0, i = 0, k = 0, len
466 , x, y, q, w
467 , xmlns
468 , stopIndex = 0
469 , stop // используется при разборе "namespace" . если встретился неизвестное пространство то события не генерируются
470 , _nsmatrix
471 , ok
472 , pos = 0, ln = 0, lnStart = -2, lnEnd = -1
473 ;
474
475 len = xml.length;
476 function getStringNode() {
477 return xml.substring(i, j+1)
478 };
479 function findLineAndColumnFromPos() {
480 while (lnStart < lnEnd && lnEnd < pos) {
481 lnStart = lnEnd;
482 lnEnd = xml.indexOf("\n", lnEnd + 1);
483 ++ln;
484 }
485 return { line: ln, column: pos - lnStart };
486 }
487 function position(p) {
488 pos = p;
489 return findLineAndColumnFromPos;
490 }
491
492 while(j !== -1) {
493 stop = stopIndex > 0;
494
495 if (xml.charCodeAt(j) === 60) { // "<"
496 i = j;
497 } else {
498 i = xml.indexOf('<', j);
499 };
500
501 if (i === -1) { // конец разбора
502
503 if (nodestack.length) {
504 this.onError('end file', position(j));
505 return;
506 };
507
508 return;
509 };
510
511 if (j !== i && !stop) {
512 ok = this.onTextNode(xml.substring(j, i), unEntities, position(j));
513 if (ok === false) return;
514 };
515
516 w = xml.charCodeAt(i+1);
517
518 if (w === 33) { // "!"
519 w = xml.charCodeAt(i+2);
520 if (w === 91 && xml.substr(i+3, 6) === 'CDATA[') { // 91 == "["
521 j = xml.indexOf(']]>', i);
522 if (j === -1) {
523 this.onError('cdata', position(i));
524 return;
525 };
526
527 //x = xml.substring(i+9, j);
528 if (!stop) {
529 ok = this.onCDATA(xml.substring(i+9, j), false, position(i));
530 if (ok === false) return;
531 };
532
533 j += 3;
534 continue;
535 };
536
537 if (w === 45 && xml.charCodeAt(i+3) === 45) { // 45 == "-"
538 j = xml.indexOf('-->', i);
539 if (j === -1) {
540 this.onError('expected -->', position(i));
541 return;
542 };
543
544
545 if (this.is_onComment && !stop) {
546 ok = this.onComment(xml.substring(i+4, j), unEntities, position(i));
547 if (ok === false) return;
548 };
549
550 j += 3;
551 continue;
552 };
553
554 j = xml.indexOf('>', i+1);
555 if (j === -1) {
556 this.onError('expected ">"', position(i + 1));
557 return;
558 };
559
560 if (this.is_onAttention && !stop) {
561 ok = this.onAttention(xml.substring(i, j+1), unEntities, position(i));
562 if (ok === false) return;
563 };
564
565 j += 1;
566 continue;
567
568 } else {
569 if (w === 63) { // "?"
570 j = xml.indexOf('?>', i);
571 if (j === -1) { // error
572 this.onError('...?>', position(i));
573 return;
574 };
575
576 if (this.is_onQuestion) {
577 ok = this.onQuestion(xml.substring(i, j+2), position(i));
578 if (ok === false) return;
579 };
580
581 j += 2;
582 continue;
583 };
584 };
585
586 var inside=false;
587 for (k=i,j=-1;k<len;k++) {
588 var c = xml.charCodeAt(k);
589 if (!inside) {
590
591 if (c === 34) { // '"'
592 inside = c;
593 }
594 else if (c === 39) { // "'"
595 inside = c;
596 }
597 else if (c === 62) { // <
598 j = k; break;
599 }
600 } else {
601 if (c === inside) { inside = false; }
602 }
603 }
604
605 if (j == -1) { // error
606 this.onError('...>', position(i + 1));
607 return;
608 };
609
610 this.attr_res = true; // атрибутов нет
611
612 //if (xml.charCodeAt(i+1) === 47) { // </...
613 if (w === 47) { // </...
614 tagstart = false;
615 tagend = true;
616
617 // проверяем что должен быть закрыт тотже тег что и открывался
618 x = elem = nodestack.pop();
619 q = i + 2 + x.length;
620
621 //console.log()
622 if (xml.substring(i+2, q) !== x) {
623 this.onError('close tagname', position(i + 2));
624 return;
625 };
626
627 // проверим что в закрываюшем теге нет лишнего
628 for(; q < j; q++) {
629 w = xml.charCodeAt(q);
630
631 if (w===32 || (w > 8 && w<14) ) { // \f\n\r\t\v пробел
632 continue;
633 };
634
635 this.onError('close tag', position(i + 2));
636 return;
637 };
638
639 } else {
640 if (xml.charCodeAt(j-1) === 47) { // .../>
641 x = elem = xml.substring(i+1, j-1);
642
643 tagstart = true;
644 tagend = true;
645 } else {
646 x = elem = xml.substring(i+1, j);
647
648 tagstart = true;
649 tagend = false;
650 };
651
652 if ( !(w > 96 && w < 123 || w > 64 && w <91) ) {
653 this.onError('first char nodeName', position(i + 1));
654 return;
655 };
656
657 for(q = 1, y = x.length; q < y; q++) {
658 w = x.charCodeAt(q);
659
660 if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w === 46 /* https://github.com/telerik/xPlatCore/issues/179 */) {
661 continue;
662 };
663
664 if (w===32 || (w<14 && w > 8)) { // \f\n\r\t\v пробел
665 elem = x.substring(0, q)
666 this.attr_res = null; // возможно есть атирибуты
667 break;
668 };
669
670 this.onError('invalid nodeName', position(i + 1));
671 return;
672 };
673
674 if (!tagend) {
675 nodestack.push(elem);
676 };
677 };
678
679
680 if (this.isNamespace) {
681 if (stop) {
682 if (tagend) {
683 if (!tagstart) {
684 if (--stopIndex === 0) {
685 this.nsmatrix = stacknsmatrix.pop();
686 };
687 };
688
689 } else {
690 stopIndex += 1;
691 };
692
693
694 j += 1;
695 continue;
696 };
697
698 _nsmatrix = this.nsmatrix;
699
700 if (!tagend) {
701 stacknsmatrix.push(this.nsmatrix);
702
703 if (this.attr_res !== true) {
704 if (this.hasSurmiseNS = x.indexOf('xmlns', q) !== -1) {
705 this.attr_string = x;
706 this.attr_posstart = q;
707
708 this.getAttrs();
709
710 this.hasSurmiseNS = false;
711 };
712 };
713 };
714
715
716 w = elem.indexOf(':');
717 if (w !== -1) {
718 xmlns = this.nsmatrix[elem.substring(0, w)];
719 elem = elem.substr(w+1);
720
721 } else {
722 xmlns = this.nsmatrix.xmlns;
723 };
724
725 if (!xmlns) {
726 if (tagend) {
727 if (tagstart) {
728 this.nsmatrix = _nsmatrix;
729 } else {
730 this.nsmatrix = stacknsmatrix.pop();
731 };
732 } else {
733 stopIndex = 1; // первый элемент для которого не определено пространство имен
734 this.attr_res = true;
735 };
736
737 j += 1;
738 continue;
739 };
740
741 elem = xmlns + ':' + elem;
742 };
743
744 //string_node = xml.substring(i, j+1); // текст ноды как есть
745
746 if (tagstart) { // is_onStartNode
747 this.attr_string = x;
748 this.attr_posstart = q;
749
750 var that = this;
751 ok = this.onStartNode(elem, function() { return that.getAttrs() }, unEntities, tagend
752 , getStringNode, position(i)
753 );
754
755 if (ok === false) {
756 return;
757 };
758
759 this.attr_res = true;
760 };
761
762 if (tagend) {
763 ok = this.onEndNode(elem, unEntities, tagstart
764 , getStringNode, position(i)
765 );
766
767 if (ok === false) {
768 return;
769 };
770
771 if (this.isNamespace) {
772 if (tagstart) {
773 this.nsmatrix = _nsmatrix;
774 } else {
775 this.nsmatrix = stacknsmatrix.pop();
776 };
777 };
778 };
779
780 j += 1;
781 };
782};