MpHtmlParser.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. // +----------------------------------------------------------------------
  2. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  3. // +----------------------------------------------------------------------
  4. // | Copyright (c) 2016~2024 https://www.crmeb.com All rights reserved.
  5. // +----------------------------------------------------------------------
  6. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  7. // +----------------------------------------------------------------------
  8. // | Author: CRMEB Team <admin@crmeb.com>
  9. // +----------------------------------------------------------------------
  10. /*
  11. 将 html 解析为适用于小程序 rich-text 的 DOM 结构
  12. github:https://github.com/jin-yufeng/Parser
  13. docs:https://jin-yufeng.github.io/Parser
  14. author:JinYufeng
  15. update:2020/04/13
  16. */
  17. var cfg = require('./config.js'),
  18. blankChar = cfg.blankChar,
  19. CssHandler = require('./CssHandler.js'),
  20. {
  21. screenWidth,
  22. system
  23. } = wx.getSystemInfoSync();
  24. // #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO
  25. var entities = {
  26. lt: '<',
  27. gt: '>',
  28. amp: '&',
  29. quot: '"',
  30. apos: "'",
  31. nbsp: '\xA0',
  32. ensp: '\u2002',
  33. emsp: '\u2003',
  34. ndash: '–',
  35. mdash: '—',
  36. middot: '·',
  37. lsquo: '‘',
  38. rsquo: '’',
  39. ldquo: '“',
  40. rdquo: '”',
  41. bull: '•',
  42. hellip: '…',
  43. permil: '‰',
  44. copy: '©',
  45. reg: '®',
  46. trade: '™',
  47. times: '×',
  48. divide: '÷',
  49. cent: '¢',
  50. pound: '£',
  51. yen: '¥',
  52. euro: '€',
  53. sect: '§'
  54. };
  55. // #endif
  56. var emoji; // emoji 补丁包 https://jin-yufeng.github.io/Parser/#/instructions?id=emoji
  57. class MpHtmlParser {
  58. constructor(data, options = {}) {
  59. this.attrs = {};
  60. this.compress = options.compress;
  61. this.CssHandler = new CssHandler(options.tagStyle, screenWidth);
  62. this.data = data;
  63. this.domain = options.domain;
  64. this.DOM = [];
  65. this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
  66. this.protocol = this.domain && this.domain.includes('://') ? this.domain.split('://')[0] : '';
  67. this.state = this.Text;
  68. this.STACK = [];
  69. this.useAnchor = options.useAnchor;
  70. this.xml = options.xml;
  71. }
  72. parse() {
  73. if (emoji) this.data = emoji.parseEmoji(this.data);
  74. for (var c; c = this.data[this.i]; this.i++)
  75. this.state(c);
  76. if (this.state == this.Text) this.setText();
  77. while (this.STACK.length) this.popNode(this.STACK.pop());
  78. // #ifdef MP-BAIDU || MP-TOUTIAO
  79. // 将顶层标签的一些样式提取出来给 rich-text
  80. (function f(ns) {
  81. for (var i = ns.length, n; n = ns[--i];) {
  82. if (n.type == 'text') continue;
  83. if (!n.c) {
  84. var style = n.attrs.style;
  85. if (style) {
  86. var j, k, res;
  87. if ((j = style.indexOf('display')) != -1)
  88. res = style.substring(j, (k = style.indexOf(';', j)) == -1 ? style.length : k);
  89. if ((j = style.indexOf('float')) != -1)
  90. res += ';' + style.substring(j, (k = style.indexOf(';', j)) == -1 ? style.length : k);
  91. n.attrs.contain = res;
  92. }
  93. } else f(n.children);
  94. }
  95. })(this.DOM);
  96. // #endif
  97. if (this.DOM.length) {
  98. this.DOM[0].PoweredBy = 'Parser';
  99. if (this.title) this.DOM[0].title = this.title;
  100. }
  101. return this.DOM;
  102. }
  103. // 设置属性
  104. setAttr() {
  105. var name = this.getName(this.attrName);
  106. if (cfg.trustAttrs[name]) {
  107. if (!this.attrVal) {
  108. if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
  109. } else if (name == 'src') this.attrs[name] = this.getUrl(this.attrVal.replace(/&amp;/g, '&'));
  110. else this.attrs[name] = this.attrVal;
  111. }
  112. this.attrVal = '';
  113. while (blankChar[this.data[this.i]]) this.i++;
  114. if (this.isClose()) this.setNode();
  115. else {
  116. this.start = this.i;
  117. this.state = this.AttrName;
  118. }
  119. }
  120. // 设置文本节点
  121. setText() {
  122. var back, text = this.section();
  123. if (!text) return;
  124. text = (cfg.onText && cfg.onText(text, () => back = true)) || text;
  125. if (back) {
  126. this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);
  127. let j = this.start + text.length;
  128. for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);
  129. return;
  130. }
  131. if (!this.pre) {
  132. // 合并空白符
  133. var tmp = [];
  134. for (let i = text.length, c; c = text[--i];)
  135. if (!blankChar[c] || (!blankChar[tmp[0]] && (c = ' '))) tmp.unshift(c);
  136. text = tmp.join('');
  137. if (text == ' ') return;
  138. }
  139. // 处理实体
  140. var siblings = this.siblings(),
  141. i = -1,
  142. j, en;
  143. while (1) {
  144. if ((i = text.indexOf('&', i + 1)) == -1) break;
  145. if ((j = text.indexOf(';', i + 2)) == -1) break;
  146. if (text[i + 1] == '#') {
  147. en = parseInt((text[i + 2] == 'x' ? '0' : '') + text.substring(i + 2, j));
  148. if (!isNaN(en)) text = text.substr(0, i) + String.fromCharCode(en) + text.substring(j + 1);
  149. } else {
  150. en = text.substring(i + 1, j);
  151. // #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
  152. if (en == 'nbsp') text = text.substr(0, i) + '\xA0' + text.substr(j + 1); // 解决 &nbsp; 失效
  153. else if (en != 'lt' && en != 'gt' && en != 'amp' && en != 'ensp' && en != 'emsp' && en != 'quot' && en != 'apos') {
  154. i && siblings.push({
  155. type: 'text',
  156. text: text.substr(0, i)
  157. })
  158. siblings.push({
  159. type: 'text',
  160. text: `&${en};`,
  161. en: 1
  162. })
  163. text = text.substr(j + 1);
  164. i = -1;
  165. }
  166. // #endif
  167. // #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO
  168. if (entities[en]) text = text.substr(0, i) + entities[en] + text.substr(j + 1);
  169. // #endif
  170. }
  171. }
  172. text && siblings.push({
  173. type: 'text',
  174. text
  175. })
  176. }
  177. // 设置元素节点
  178. setNode() {
  179. var node = {
  180. name: this.tagName.toLowerCase(),
  181. attrs: this.attrs
  182. },
  183. close = cfg.selfClosingTags[node.name] || (this.xml && this.data[this.i] == '/');
  184. this.attrs = {};
  185. if (!cfg.ignoreTags[node.name]) {
  186. this.matchAttr(node);
  187. if (!close) {
  188. node.children = [];
  189. if (node.name == 'pre' && cfg.highlight) {
  190. this.remove(node);
  191. this.pre = node.pre = true;
  192. }
  193. this.siblings().push(node);
  194. this.STACK.push(node);
  195. } else if (!cfg.filter || cfg.filter(node, this) != false)
  196. this.siblings().push(node);
  197. } else {
  198. if (!close) this.remove(node);
  199. else if (node.name == 'source') {
  200. var parent = this.STACK[this.STACK.length - 1],
  201. attrs = node.attrs;
  202. if (parent && attrs.src)
  203. if (parent.name == 'video' || parent.name == 'audio')
  204. parent.attrs.source.push(attrs.src);
  205. else {
  206. var i, media = attrs.media;
  207. if (parent.name == 'picture' && !parent.attrs.src && !(attrs.src.indexOf('.webp') && system.includes('iOS')) &&
  208. (!media || (media.includes('px') &&
  209. (((i = media.indexOf('min-width')) != -1 && (i = media.indexOf(':', i + 8)) != -1 && screenWidth > parseInt(
  210. media.substr(i + 1))) ||
  211. ((i = media.indexOf('max-width')) != -1 && (i = media.indexOf(':', i + 8)) != -1 && screenWidth < parseInt(
  212. media.substr(i + 1)))))))
  213. parent.attrs.src = attrs.src;
  214. }
  215. } else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;
  216. }
  217. if (this.data[this.i] == '/') this.i++;
  218. this.start = this.i + 1;
  219. this.state = this.Text;
  220. }
  221. // 移除标签
  222. remove(node) {
  223. var name = node.name,
  224. j = this.i;
  225. while (1) {
  226. if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {
  227. if (name == 'pre' || name == 'svg') this.i = j;
  228. else this.i = this.data.length;
  229. return;
  230. }
  231. this.start = (this.i += 2);
  232. while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;
  233. if (this.getName(this.section()) == name) {
  234. // 代码块高亮
  235. if (name == 'pre') {
  236. this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) +
  237. this.data.substr(this.i - 5);
  238. return this.i = j;
  239. } else if (name == 'style')
  240. this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));
  241. else if (name == 'title')
  242. this.title = this.data.substring(j + 1, this.i - 7);
  243. if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
  244. // 处理 svg
  245. if (name == 'svg') {
  246. var src = this.data.substring(j, this.i + 1);
  247. if (!node.attrs.xmlns) src = ' xmlns="http://www.w3.org/2000/svg"' + src;
  248. var i = j;
  249. while (this.data[j] != '<') j--;
  250. src = this.data.substring(j, i) + src;
  251. var parent = this.STACK[this.STACK.length - 1];
  252. if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))
  253. parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;
  254. this.siblings().push({
  255. name: 'img',
  256. attrs: {
  257. src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
  258. ignore: 'T'
  259. }
  260. })
  261. }
  262. return;
  263. }
  264. }
  265. }
  266. // 处理属性
  267. matchAttr(node) {
  268. var attrs = node.attrs,
  269. style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
  270. styleObj = {};
  271. if (attrs.id) {
  272. if (this.compress & 1) attrs.id = void 0;
  273. else if (this.useAnchor) this.bubble();
  274. }
  275. if ((this.compress & 2) && attrs.class) attrs.class = void 0;
  276. switch (node.name) {
  277. case 'img':
  278. if (attrs['data-src']) {
  279. attrs.src = attrs.src || attrs['data-src'];
  280. attrs['data-src'] = void 0;
  281. }
  282. if (attrs.src && !attrs.ignore) {
  283. if (this.bubble()) attrs.i = (this.imgNum++).toString();
  284. else attrs.ignore = 'T';
  285. }
  286. break;
  287. case 'a':
  288. case 'ad':
  289. // #ifdef APP-PLUS
  290. case 'iframe':
  291. case 'embed':
  292. // #endif
  293. this.bubble();
  294. break;
  295. case 'font':
  296. if (attrs.color) {
  297. styleObj['color'] = attrs.color;
  298. attrs.color = void 0;
  299. }
  300. if (attrs.face) {
  301. styleObj['font-family'] = attrs.face;
  302. attrs.face = void 0;
  303. }
  304. if (attrs.size) {
  305. var size = parseInt(attrs.size);
  306. if (size < 1) size = 1;
  307. else if (size > 7) size = 7;
  308. var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
  309. styleObj['font-size'] = map[size - 1];
  310. attrs.size = void 0;
  311. }
  312. break;
  313. case 'video':
  314. case 'audio':
  315. if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);
  316. else this[`${node.name}Num`]++;
  317. if (node.name == 'video') {
  318. if (attrs.width) {
  319. style = `width:${parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')};${style}`;
  320. attrs.width = void 0;
  321. }
  322. if (attrs.height) {
  323. style = `height:${parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')};${style}`;
  324. attrs.height = void 0;
  325. }
  326. if (this.videoNum > 3) node.lazyLoad = true;
  327. }
  328. attrs.source = [];
  329. if (attrs.src) attrs.source.push(attrs.src);
  330. if (!attrs.controls && !attrs.autoplay)
  331. console.warn(`存在没有 controls 属性的 ${node.name} 标签,可能导致无法播放`, node);
  332. this.bubble();
  333. break;
  334. case 'td':
  335. case 'th':
  336. if (attrs.colspan || attrs.rowspan)
  337. for (var k = this.STACK.length, item; item = this.STACK[--k];)
  338. if (item.name == 'table') {
  339. item.c = void 0;
  340. break;
  341. }
  342. }
  343. if (attrs.align) {
  344. styleObj['text-align'] = attrs.align;
  345. attrs.align = void 0;
  346. }
  347. // 压缩 style
  348. var styles = style.replace(/&quot;/g, '"').replace(/&amp;/g, '&').split(';');
  349. style = '';
  350. for (var i = 0, len = styles.length; i < len; i++) {
  351. var info = styles[i].split(':');
  352. if (info.length < 2) continue;
  353. let key = info[0].trim().toLowerCase(),
  354. value = info.slice(1).join(':').trim();
  355. if (value.includes('-webkit') || value.includes('-moz') || value.includes('-ms') || value.includes('-o') || value
  356. .includes(
  357. 'safe'))
  358. style += `;${key}:${value}`;
  359. else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
  360. styleObj[key] = value;
  361. }
  362. if (node.name == 'img' && parseInt(styleObj.width || attrs.width) > screenWidth)
  363. styleObj.height = 'auto';
  364. for (var key in styleObj) {
  365. var value = styleObj[key];
  366. if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;
  367. // 填充链接
  368. if (value.includes('url')) {
  369. var j = value.indexOf('(');
  370. if (j++ != -1) {
  371. while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;
  372. value = value.substr(0, j) + this.getUrl(value.substr(j));
  373. }
  374. }
  375. // 转换 rpx
  376. else if (value.includes('rpx'))
  377. value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * screenWidth / 750 + 'px');
  378. else if (key == 'white-space' && value.includes('pre'))
  379. this.pre = node.pre = true;
  380. style += `;${key}:${value}`;
  381. }
  382. style = style.substr(1);
  383. if (style) attrs.style = style;
  384. }
  385. // 节点出栈处理
  386. popNode(node) {
  387. // 空白符处理
  388. if (node.pre) {
  389. node.pre = this.pre = void 0;
  390. for (let i = this.STACK.length; i--;)
  391. if (this.STACK[i].pre)
  392. this.pre = true;
  393. }
  394. if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
  395. return this.siblings().pop();
  396. var attrs = node.attrs;
  397. // 替换一些标签名
  398. if (node.name == 'picture') {
  399. node.name = 'img';
  400. if (!attrs.src && (node.children[0] || '').name == 'img')
  401. attrs.src = node.children[0].attrs.src;
  402. if (attrs.src && !attrs.ignore)
  403. attrs.i = (this.imgNum++).toString();
  404. return node.children = void 0;
  405. }
  406. if (cfg.blockTags[node.name]) node.name = 'div';
  407. else if (!cfg.trustTags[node.name]) node.name = 'span';
  408. // 处理列表
  409. if (node.c) {
  410. if (node.name == 'ul') {
  411. var floor = 1;
  412. for (let i = this.STACK.length; i--;)
  413. if (this.STACK[i].name == 'ul') floor++;
  414. if (floor != 1)
  415. for (let i = node.children.length; i--;)
  416. node.children[i].floor = floor;
  417. } else if (node.name == 'ol') {
  418. for (let i = 0, num = 1, child; child = node.children[i++];)
  419. if (child.name == 'li') {
  420. child.type = 'ol';
  421. child.num = ((num, type) => {
  422. if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);
  423. if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);
  424. if (type == 'i' || type == 'I') {
  425. num = (num - 1) % 99 + 1;
  426. var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
  427. ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
  428. res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');
  429. if (type == 'i') return res.toLowerCase();
  430. return res;
  431. }
  432. return num;
  433. })(num++, attrs.type) + '.';
  434. }
  435. }
  436. }
  437. // 处理表格的边框
  438. if (node.name == 'table') {
  439. var padding = attrs.cellpadding,
  440. spacing = attrs.cellspacing,
  441. border = attrs.border;
  442. if (node.c) {
  443. this.bubble();
  444. if (!padding) padding = 2;
  445. if (!spacing) spacing = 2;
  446. }
  447. if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
  448. if (spacing) attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
  449. if (border || padding)
  450. (function f(ns) {
  451. for (var i = 0, n; n = ns[i]; i++) {
  452. if (n.name == 'th' || n.name == 'td') {
  453. if (border) n.attrs.style = `border:${border}px solid gray;${n.attrs.style}`;
  454. if (padding) n.attrs.style = `padding:${padding}px;${n.attrs.style}`;
  455. } else f(n.children || []);
  456. }
  457. })(node.children)
  458. }
  459. this.CssHandler.pop && this.CssHandler.pop(node);
  460. // 自动压缩
  461. if (node.name == 'div' && !Object.keys(attrs).length) {
  462. var siblings = this.siblings();
  463. if (node.children.length == 1 && node.children[0].name == 'div')
  464. siblings[siblings.length - 1] = node.children[0];
  465. }
  466. }
  467. // 工具函数
  468. bubble() {
  469. for (var i = this.STACK.length, item; item = this.STACK[--i];) {
  470. if (cfg.richOnlyTags[item.name]) {
  471. if (item.name == 'table' && !Object.hasOwnProperty.call(item, 'c')) item.c = 1;
  472. return false;
  473. }
  474. item.c = 1;
  475. }
  476. return true;
  477. }
  478. getName = val => this.xml ? val : val.toLowerCase();
  479. getUrl(url) {
  480. if (url[0] == '/') {
  481. if (url[1] == '/') url = this.protocol + ':' + url;
  482. else if (this.domain) url = this.domain + url;
  483. } else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))
  484. url = this.domain + '/' + url;
  485. return url;
  486. }
  487. isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
  488. section = () => this.data.substring(this.start, this.i);
  489. siblings = () => this.STACK.length ? this.STACK[this.STACK.length - 1].children : this.DOM;
  490. // 状态机
  491. Text(c) {
  492. if (c == '<') {
  493. var next = this.data[this.i + 1],
  494. isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
  495. if (isLetter(next)) {
  496. this.setText();
  497. this.start = this.i + 1;
  498. this.state = this.TagName;
  499. } else if (next == '/') {
  500. this.setText();
  501. if (isLetter(this.data[++this.i + 1])) {
  502. this.start = this.i + 1;
  503. this.state = this.EndTag;
  504. } else
  505. this.Comment();
  506. } else if (next == '!') {
  507. this.setText();
  508. this.Comment();
  509. }
  510. }
  511. }
  512. Comment() {
  513. var key;
  514. if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';
  515. else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';
  516. else key = '>';
  517. if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;
  518. else this.i += key.length - 1;
  519. this.start = this.i + 1;
  520. this.state = this.Text;
  521. }
  522. TagName(c) {
  523. if (blankChar[c]) {
  524. this.tagName = this.section();
  525. while (blankChar[this.data[this.i]]) this.i++;
  526. if (this.isClose()) this.setNode();
  527. else {
  528. this.start = this.i;
  529. this.state = this.AttrName;
  530. }
  531. } else if (this.isClose()) {
  532. this.tagName = this.section();
  533. this.setNode();
  534. }
  535. }
  536. AttrName(c) {
  537. var blank = blankChar[c];
  538. if (blank) {
  539. this.attrName = this.section();
  540. c = this.data[this.i];
  541. }
  542. if (c == '=') {
  543. if (!blank) this.attrName = this.section();
  544. while (blankChar[this.data[++this.i]]);
  545. this.start = this.i--;
  546. this.state = this.AttrValue;
  547. } else if (blank) this.setAttr();
  548. else if (this.isClose()) {
  549. this.attrName = this.section();
  550. this.setAttr();
  551. }
  552. }
  553. AttrValue(c) {
  554. if (c == '"' || c == "'") {
  555. this.start++;
  556. if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;
  557. this.attrVal = this.section();
  558. this.i++;
  559. } else {
  560. for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);
  561. this.attrVal = this.section();
  562. }
  563. this.setAttr();
  564. }
  565. EndTag(c) {
  566. if (blankChar[c] || c == '>' || c == '/') {
  567. var name = this.getName(this.section());
  568. for (var i = this.STACK.length; i--;)
  569. if (this.STACK[i].name == name) break;
  570. if (i != -1) {
  571. var node;
  572. while ((node = this.STACK.pop()).name != name);
  573. this.popNode(node);
  574. } else if (name == 'p' || name == 'br')
  575. this.siblings().push({
  576. name,
  577. attrs: {}
  578. });
  579. this.i = this.data.indexOf('>', this.i);
  580. this.start = this.i + 1;
  581. if (this.i == -1) this.i = this.data.length;
  582. else this.state = this.Text;
  583. }
  584. }
  585. }
  586. module.exports = MpHtmlParser;