class HTMLParser { constructor() { this._listeners = {}; this._mapCdataTags = this.makeMap('script,style'); this._arrBlocks = []; this.lastEvent = null; } makeMap(str) { var obj = {}, items = str.split(','); for (var i = 0; i < items.length; i++) { obj[items[i]] = true; } return obj; } parse(html) { var self = this, mapCdataTags = self._mapCdataTags; var regTag = /<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g, regAttr = /\s*([^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"'>]*)))?/g, regLine = /\r?\n/g; var match, matchIndex, lastIndex = 0, tagName, arrAttrs, tagCDATA, attrsCDATA, arrCDATA, lastCDATAIndex = 0, text; var lastLineIndex = 0, line = 1; var arrBlocks = self._arrBlocks; self.fire('start', { pos: 0, line: 1, col: 1 }); // Memory block function saveBlock(type, raw, pos, data) { var col = pos - lastLineIndex + 1; if (data === undefined) { data = {}; } data.raw = raw; data.pos = pos; data.line = line; data.col = col; arrBlocks.push(data); self.fire(type, data); var lineMatch; while ((lineMatch = regLine.exec(raw))) { line++; lastLineIndex = pos + regLine.lastIndex; } } while ((match = regTag.exec(html))) { matchIndex = match.index; if (matchIndex > lastIndex) { // Save the previous text or CDATA text = html.substring(lastIndex, matchIndex); if (tagCDATA) { arrCDATA.push(text); } else { // text saveBlock('text', text, lastIndex); } } lastIndex = regTag.lastIndex; if ((tagName = match[1])) { if (tagCDATA && tagName === tagCDATA) { // Output CDATA before closing the label text = arrCDATA.join(''); saveBlock('cdata', text, lastCDATAIndex, { tagName: tagCDATA, attrs: attrsCDATA }); tagCDATA = null; attrsCDATA = null; arrCDATA = null; } if (!tagCDATA) { // End of label saveBlock('tagend', match[0], matchIndex, { tagName: tagName }); continue; } } if (tagCDATA) { arrCDATA.push(match[0]); } else { if ((tagName = match[4])) { // Label start arrAttrs = []; var attrs = match[5], attrMatch, attrMatchCount = 0; while ((attrMatch = regAttr.exec(attrs))) { var name = attrMatch[1], quote = attrMatch[2] ? attrMatch[2] : attrMatch[4] ? attrMatch[4] : '', value = attrMatch[3] ? attrMatch[3] : attrMatch[5] ? attrMatch[5] : attrMatch[6] ? attrMatch[6] : ''; arrAttrs.push({ name: name, value: value, quote: quote, index: attrMatch.index, raw: attrMatch[0] }); attrMatchCount += attrMatch[0].length; } if (attrMatchCount === attrs.length) { saveBlock('tagstart', match[0], matchIndex, { tagName: tagName, attrs: arrAttrs, close: match[6] }); if (mapCdataTags[tagName]) { tagCDATA = tagName; attrsCDATA = arrAttrs.concat(); arrCDATA = []; lastCDATAIndex = lastIndex; } } else { // If a miss match occurs, the current content is matched to text saveBlock('text', match[0], matchIndex); } } else if (match[2] || match[3]) { // Comment tag saveBlock('comment', match[0], matchIndex, { content: match[2] || match[3], long: match[2] ? true : false }); } } } if (html.length > lastIndex) { // End text text = html.substring(lastIndex, html.length); saveBlock('text', text, lastIndex); } self.fire('end', { pos: lastIndex, line: line, col: html.length - lastLineIndex + 1 }); } addListener(types, listener) { var _listeners = this._listeners; var arrTypes = types.split(/[,\s]/), type; for (var i = 0, l = arrTypes.length; i < l; i++) { type = arrTypes[i]; if (_listeners[type] === undefined) { _listeners[type] = []; } _listeners[type].push(listener); } } fire(type, data) { if (data === undefined) { data = {}; } data.type = type; var self = this, listeners = [], listenersType = self._listeners[type], listenersAll = self._listeners['all']; if (listenersType !== undefined) { listeners = listeners.concat(listenersType); } if (listenersAll !== undefined) { listeners = listeners.concat(listenersAll); } var lastEvent = self.lastEvent; if (lastEvent !== null) { delete lastEvent['lastEvent']; data.lastEvent = lastEvent; } self.lastEvent = data; for (var i = 0, l = listeners.length; i < l; i++) { listeners[i].call(self, data); } } removeListener(type, listener) { var listenersType = this._listeners[type]; if (listenersType !== undefined) { for (var i = 0, l = listenersType.length; i < l; i++) { if (listenersType[i] === listener) { listenersType.splice(i, 1); break; } } } } fixPos(event, index) { var text = event.raw.substr(0, index); var arrLines = text.split(/\r?\n/), lineCount = arrLines.length - 1, line = event.line, col; if (lineCount > 0) { line += lineCount; col = arrLines[lineCount].length + 1; } else { col = event.col + index; } return { line: line, col: col }; } getMapAttrs(arrAttrs) { var mapAttrs = {}, attr; for (var i = 0, l = arrAttrs.length; i < l; i++) { attr = arrAttrs[i]; mapAttrs[attr.name] = attr.value; } return mapAttrs; } } export default HTMLParser;