index.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. let assert = require('assert').strict;
  2. let handlerCompany = function(type, ...args) {
  3. if (typeof this.handlers['on' + type] === 'function') {
  4. this.handlers['on' + type](...args);
  5. }
  6. };
  7. class WXMLParser {
  8. constructor(handlers) {
  9. this.handlers = handlers || {};
  10. }
  11. write(source) {
  12. this.inputs = source;
  13. this.len = source.length || 0;
  14. this.pos = 0;
  15. this.parseNodes();
  16. }
  17. getNextChar() {
  18. return this.inputs[this.pos];
  19. }
  20. getNextString(len) {
  21. return this.inputs.substr(this.pos, len)
  22. }
  23. startWiths(str) {
  24. return this.inputs.substr(this.pos, str.length) === str;
  25. }
  26. isEOF() {
  27. return this.pos >= this.len;
  28. }
  29. // consume
  30. consumeChar() {
  31. return this.inputs[this.pos++];
  32. }
  33. consumeCharIgnoreWhitespace() {
  34. const text = this.consumeWhitespace()
  35. // if (text) {
  36. // handlerCompany.call(this, 'text', text);
  37. // }
  38. return this.inputs[this.pos++];
  39. }
  40. consumeWhile(matchFunc, len) {
  41. let result = '';
  42. while (!this.isEOF() && matchFunc(len ? this.getNextString(len) : this.getNextChar())) {
  43. result += this.consumeChar();
  44. }
  45. return result;
  46. }
  47. consumeWhitespace() {
  48. return this.consumeWhile((char) => /\s/.test(char));
  49. }
  50. // parse
  51. parseNodes() {
  52. while (!this.isEOF() && !this.startWiths('</')) {
  53. this.parseNode();
  54. }
  55. }
  56. parseNode() {
  57. let nextChar = this.getNextChar();
  58. switch (nextChar) {
  59. case '{':
  60. this.parseTemplate();
  61. break;
  62. case '<':
  63. if (this.startWiths('<!--')) {
  64. this.parseComment();
  65. return;
  66. }
  67. // open tag
  68. this.parseElement();
  69. break;
  70. default:
  71. this.parseText();
  72. }
  73. }
  74. parseTemplate() {
  75. assert.ok(this.consumeChar() === '{');
  76. assert.ok(this.consumeChar() === '{');
  77. let template = this.consumeWhile((char) => char !== '}');
  78. handlerCompany.call(this, 'template', template);
  79. assert.ok(this.consumeChar() === '}');
  80. assert.ok(this.consumeChar() === '}');
  81. }
  82. parseText() {
  83. let text = this.consumeWhile((char) => /[^<{]/.test(char));
  84. handlerCompany.call(this, 'text', text);
  85. return text;
  86. }
  87. parseComment() {
  88. assert.ok(this.consumeChar() === '<');
  89. assert.ok(this.consumeChar() === '!');
  90. assert.ok(this.consumeChar() === '-');
  91. assert.ok(this.consumeChar() === '-');
  92. let comment = this.consumeWhile((char) => char !== '-' || !this.startWiths('-->'));
  93. handlerCompany.call(this, 'comment', comment);
  94. assert.ok(this.consumeChar() === '-');
  95. assert.ok(this.consumeChar() === '-');
  96. assert.ok(this.consumeChar() === '>');
  97. return comment;
  98. }
  99. parseElement() {
  100. // open tag
  101. assert.ok(this.consumeChar() === '<');
  102. let tagName = this.parseTagName();
  103. let attrs = this.parseAttrs();
  104. let isSelfClosing = false;
  105. this.consumeWhitespace();
  106. if (this.getNextChar() === '/') {
  107. // selfClosing
  108. isSelfClosing = true;
  109. }
  110. handlerCompany.call(this, 'opentag', tagName, attrs, isSelfClosing);
  111. this.consumeWhile((char) => char !== '>');
  112. assert.ok(this.consumeChar() === '>');
  113. if (isSelfClosing) {
  114. // handlerCompany.call(this, 'closetag', tagName, true);
  115. return;
  116. }
  117. if (tagName === 'wxs') {
  118. const wxs = this.consumeWhile(str => str !== '</wxs', 5);
  119. handlerCompany.call(this, 'wxs', wxs);
  120. } else {
  121. this.parseNodes();
  122. }
  123. assert.ok(this.consumeCharIgnoreWhitespace() === '<');
  124. assert.ok(this.consumeCharIgnoreWhitespace() === '/');
  125. let closeTagName = this.parseTagName();
  126. handlerCompany.call(this, 'closetag', closeTagName, false);
  127. assert.ok(this.consumeCharIgnoreWhitespace() === '>');
  128. }
  129. parseTagName() {
  130. return this.consumeWhile((char) => /[\w-]/.test(char));
  131. }
  132. parseAttrs() {
  133. this.consumeWhitespace();
  134. let attrs = {};
  135. while (/[^/>]/.test(this.getNextChar())) {
  136. let key = this.consumeWhile((char) => /[^=/>\s]/.test(char));
  137. this.consumeWhitespace();
  138. if (this.getNextChar() !== '=') {
  139. attrs[key] = '';
  140. continue;
  141. }
  142. assert.ok(this.consumeChar() === '=');
  143. this.consumeWhitespace();
  144. let quoteMark = this.consumeChar(); // single or double quote marks
  145. assert.ok(/['"]/.test(quoteMark));
  146. let val = this.consumeWhile((char) => char !== quoteMark);
  147. assert.ok(this.consumeChar() === quoteMark);
  148. attrs[key] = val;
  149. this.consumeWhitespace();
  150. }
  151. return attrs;
  152. }
  153. }
  154. module.exports = WXMLParser;