diff --git a/README.md b/README.md
index b6e590f..a3cf082 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,37 @@ Himalaya handles a lot of HTML's fringe cases, like:
### Preserves Whitespace
Himalaya does not cut corners and returns an accurate representation of the HTML supplied. To remove whitespace, post-process the JSON; check out [an example script](https://gist.github.com/andrejewski/773487d4f4a46b16865405d7b74eabf9).
+### Line, column, and index positions
+Himalaya can include the start and end positions of nodes in the parse output.
+To enable this, you can pass `parse` the `parseDefaults` extended with `includePositions: true`:
+
+```js
+import { parse, parseDefaults } from 'himalaya'
+parse('', { ...parseDefaults, includePositions: true })
+/* =>
+[
+ {
+ "type": "element",
+ "tagName": "img",
+ "attributes": [],
+ "children": [],
+ "position": {
+ "start": {
+ "index": 0,
+ "line": 0,
+ "column": 0
+ },
+ "end": {
+ "index": 5,
+ "line": 0,
+ "column": 5
+ }
+ }
+ }
+]
+*/
+```
+
## Going back to HTML
Himalaya provides a `stringify` method. The following example parses the HTML to JSON then parses the JSON back into HTML.
diff --git a/docs/dist/himalaya.js b/docs/dist/himalaya.js
index 00ad1b0..4910793 100644
--- a/docs/dist/himalaya.js
+++ b/docs/dist/himalaya.js
@@ -77,17 +77,19 @@ function unquote(str) {
return str;
}
-function format(nodes) {
+function format(nodes, options) {
return nodes.map(function (node) {
var type = node.type;
- if (type === 'element') {
- var tagName = node.tagName.toLowerCase();
- var attributes = formatAttributes(node.attributes);
- var children = format(node.children);
- return { type: type, tagName: tagName, attributes: attributes, children: children };
+ var outputNode = type === 'element' ? {
+ type: type,
+ tagName: node.tagName.toLowerCase(),
+ attributes: formatAttributes(node.attributes),
+ children: format(node.children, options)
+ } : { type: type, content: node.content };
+ if (options.includePositions) {
+ outputNode.position = node.position;
}
-
- return { type: type, content: node.content };
+ return outputNode;
});
}
@@ -130,7 +132,8 @@ var parseDefaults = exports.parseDefaults = {
voidTags: _tags.voidTags,
closingTags: _tags.closingTags,
childlessTags: _tags.childlessTags,
- closingTagAncestorBreakers: _tags.closingTagAncestorBreakers
+ closingTagAncestorBreakers: _tags.closingTagAncestorBreakers,
+ includePositions: false
};
function parse(str) {
@@ -153,6 +156,10 @@ function stringify(ast) {
Object.defineProperty(exports, "__esModule", {
value: true
});
+exports.feedPosition = feedPosition;
+exports.jumpPosition = jumpPosition;
+exports.makeInitialPosition = makeInitialPosition;
+exports.copyPosition = copyPosition;
exports.default = lexer;
exports.lex = lex;
exports.findTextEnd = findTextEnd;
@@ -166,30 +173,67 @@ exports.lexSkipTag = lexSkipTag;
var _compat = require('./compat');
-function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+function feedPosition(position, str, len) {
+ var start = position.index;
+ var end = position.index = start + len;
+ for (var i = start; i < end; i++) {
+ var char = str.charAt(i);
+ if (char === '\n') {
+ position.line++;
+ position.column = 0;
+ } else {
+ position.column++;
+ }
+ }
+}
+
+function jumpPosition(position, str, end) {
+ var len = end - position.index;
+ return feedPosition(position, str, len);
+}
+
+function makeInitialPosition() {
+ return {
+ index: 0,
+ column: 0,
+ line: 0
+ };
+}
+
+function copyPosition(position) {
+ return {
+ index: position.index,
+ line: position.line,
+ column: position.column
+ };
+}
function lexer(str, options) {
- var state = { str: str, options: options, cursor: 0, tokens: [] };
+ var state = {
+ str: str,
+ options: options,
+ position: makeInitialPosition(),
+ tokens: []
+ };
lex(state);
return state.tokens;
}
function lex(state) {
- var str = state.str;
+ var str = state.str,
+ childlessTags = state.options.childlessTags;
var len = str.length;
- while (state.cursor < len) {
- var start = state.cursor;
+ while (state.position.index < len) {
+ var start = state.position.index;
lexText(state);
- if (state.cursor === start) {
- var isComment = (0, _compat.startsWith)(str, '!--', state.cursor + 1);
+ if (state.position.index === start) {
+ var isComment = (0, _compat.startsWith)(str, '!--', start + 1);
if (isComment) {
lexComment(state);
} else {
var tagName = lexTag(state);
var safeTag = tagName.toLowerCase();
- var childlessTags = state.options.childlessTags;
-
if ((0, _compat.arrayIncludes)(childlessTags, safeTag)) {
lexSkipTag(tagName, state);
}
@@ -216,60 +260,64 @@ function findTextEnd(str, index) {
function lexText(state) {
var type = 'text';
var str = state.str,
- cursor = state.cursor;
+ position = state.position;
- var textEnd = findTextEnd(str, cursor);
+ var textEnd = findTextEnd(str, position.index);
+ if (textEnd === position.index) return;
if (textEnd === -1) {
- // there is only text left
- var _content = str.slice(cursor);
- state.cursor = str.length;
- state.tokens.push({ type: type, content: _content });
- return;
+ textEnd = str.length;
}
- if (textEnd === cursor) return;
-
- var content = str.slice(cursor, textEnd);
- state.cursor = textEnd;
- state.tokens.push({ type: type, content: content });
+ var start = copyPosition(position);
+ var content = str.slice(position.index, textEnd);
+ jumpPosition(position, str, textEnd);
+ var end = copyPosition(position);
+ state.tokens.push({ type: type, content: content, position: { start: start, end: end } });
}
function lexComment(state) {
- state.cursor += 4; // "', cursor);
- var type = 'comment';
- if (commentEnd === -1) {
- // there is only the comment left
- var _content2 = str.slice(cursor);
- state.cursor = str.length;
- state.tokens.push({ type: type, content: _content2 });
- return;
+ position = state.position;
+
+ var start = copyPosition(position);
+ feedPosition(position, str, 4); // "', position.index);
+ var commentEnd = contentEnd + 3; // "-->".length
+ if (contentEnd === -1) {
+ contentEnd = commentEnd = str.length;
}
- var content = str.slice(cursor, commentEnd);
- state.cursor = commentEnd + 3; // "-->".length
- state.tokens.push({ type: type, content: content });
+ var content = str.slice(position.index, contentEnd);
+ jumpPosition(position, str, commentEnd);
+ state.tokens.push({
+ type: 'comment',
+ content: content,
+ position: {
+ start: start,
+ end: copyPosition(position)
+ }
+ });
}
function lexTag(state) {
- var str = state.str;
+ var str = state.str,
+ position = state.position;
{
- var secondChar = str.charAt(state.cursor + 1);
+ var secondChar = str.charAt(position.index + 1);
var close = secondChar === '/';
- state.tokens.push({ type: 'tag-start', close: close });
- state.cursor += close ? 2 : 1;
+ var start = copyPosition(position);
+ feedPosition(position, str, close ? 2 : 1);
+ state.tokens.push({ type: 'tag-start', close: close, position: { start: start } });
}
var tagName = lexTagName(state);
lexTagAttributes(state);
{
- var firstChar = str.charAt(state.cursor);
+ var firstChar = str.charAt(position.index);
var _close = firstChar === '/';
- state.tokens.push({ type: 'tag-end', close: _close });
- state.cursor += _close ? 2 : 1;
+ feedPosition(position, str, _close ? 2 : 1);
+ var end = copyPosition(position);
+ state.tokens.push({ type: 'tag-end', close: _close, position: { end: end } });
}
return tagName;
}
@@ -282,10 +330,10 @@ function isWhitespaceChar(char) {
function lexTagName(state) {
var str = state.str,
- cursor = state.cursor;
+ position = state.position;
var len = str.length;
- var start = cursor;
+ var start = position.index;
while (start < len) {
var char = str.charAt(start);
var isTagChar = !(isWhitespaceChar(char) || char === '/' || char === '>');
@@ -301,17 +349,21 @@ function lexTagName(state) {
end++;
}
- state.cursor = end;
+ jumpPosition(position, str, end);
var tagName = str.slice(start, end);
- state.tokens.push({ type: 'tag', content: tagName });
+ state.tokens.push({
+ type: 'tag',
+ content: tagName
+ });
return tagName;
}
function lexTagAttributes(state) {
var str = state.str,
+ position = state.position,
tokens = state.tokens;
- var cursor = state.cursor;
+ var cursor = position.index;
var quote = null; // null, single-, or double-quote
var wordBegin = cursor; // index of word start
var words = []; // "key", "key=value", "key='value'", etc
@@ -354,7 +406,7 @@ function lexTagAttributes(state) {
cursor++;
}
- state.cursor = cursor;
+ jumpPosition(position, str, cursor);
var wLen = words.length;
var type = 'attribute';
@@ -398,13 +450,16 @@ function lexTagAttributes(state) {
}
}
+var push = [].push;
+
function lexSkipTag(tagName, state) {
var str = state.str,
- cursor = state.cursor,
+ position = state.position,
tokens = state.tokens;
+ var safeTagName = tagName.toLowerCase();
var len = str.length;
- var index = cursor;
+ var index = position.index;
while (index < len) {
var nextTag = str.indexOf('', index);
if (nextTag === -1) {
@@ -412,21 +467,30 @@ function lexSkipTag(tagName, state) {
break;
}
- var tagState = { str: str, cursor: nextTag + 2, tokens: [] };
- var name = lexTagName(tagState);
- var safeTagName = tagName.toLowerCase();
+ var tagStartPosition = copyPosition(position);
+ jumpPosition(tagStartPosition, str, nextTag);
+ var tagState = { str: str, position: tagStartPosition, tokens: [] };
+ var name = lexTag(tagState);
if (safeTagName !== name.toLowerCase()) {
- index = tagState.cursor;
+ index = tagState.position.index;
continue;
}
- var content = str.slice(cursor, nextTag);
- tokens.push({ type: 'text', content: content });
- var openTag = { type: 'tag-start', close: true };
- var closeTag = { type: 'tag-end', close: false };
- lexTagAttributes(tagState);
- tokens.push.apply(tokens, [openTag].concat(_toConsumableArray(tagState.tokens), [closeTag]));
- state.cursor = tagState.cursor + 1;
+ if (nextTag !== position.index) {
+ var textStart = copyPosition(position);
+ jumpPosition(position, str, nextTag);
+ tokens.push({
+ type: 'text',
+ content: str.slice(textStart.index, nextTag),
+ position: {
+ start: textStart,
+ end: copyPosition(position)
+ }
+ });
+ }
+
+ push.apply(tokens, tagState.tokens);
+ jumpPosition(position, str, tagState.position.index);
break;
}
}
@@ -439,6 +503,7 @@ Object.defineProperty(exports, "__esModule", {
});
exports.default = parser;
exports.hasTerminalParent = hasTerminalParent;
+exports.rewindStack = rewindStack;
exports.parse = parse;
var _compat = require('./compat');
@@ -468,6 +533,14 @@ function hasTerminalParent(tagName, stack, terminals) {
return false;
}
+function rewindStack(stack, newLength, childrenEndPosition, endPosition) {
+ stack[newLength].position.end = endPosition;
+ for (var i = newLength + 1, len = stack.length; i < len; i++) {
+ stack[i].position.end = childrenEndPosition;
+ }
+ stack.splice(newLength);
+}
+
function parse(state) {
var tokens = state.tokens,
options = state.options;
@@ -490,11 +563,10 @@ function parse(state) {
var tagName = tagToken.content.toLowerCase();
if (token.close) {
var index = stack.length;
- var didRewind = false;
+ var shouldRewind = false;
while (--index > -1) {
if (stack[index].tagName === tagName) {
- stack.splice(index);
- didRewind = true;
+ shouldRewind = true;
break;
}
}
@@ -503,7 +575,8 @@ function parse(state) {
if (endToken.type !== 'tag-end') break;
cursor++;
}
- if (didRewind) {
+ if (shouldRewind) {
+ rewindStack(stack, index, token.position.start, tokens[cursor - 1].position.end);
break;
} else {
continue;
@@ -524,7 +597,7 @@ function parse(state) {
var currentIndex = stack.length - 1;
while (currentIndex > 0) {
if (tagName === stack[currentIndex].tagName) {
- stack = stack.slice(0, currentIndex);
+ rewindStack(stack, currentIndex, token.position.start, token.position.start);
var previousIndex = currentIndex - 1;
nodes = stack[previousIndex].children;
break;
@@ -544,19 +617,29 @@ function parse(state) {
cursor++;
var children = [];
- nodes.push({
+ var position = {
+ start: token.position.start,
+ end: attrToken.position.end
+ };
+ var elementNode = {
type: 'element',
tagName: tagToken.content,
attributes: attributes,
- children: children
- });
+ children: children,
+ position: position
+ };
+ nodes.push(elementNode);
var hasChildren = !(attrToken.close || (0, _compat.arrayIncludes)(options.voidTags, tagName));
if (hasChildren) {
- stack.push({ tagName: tagName, children: children });
+ var size = stack.push({ tagName: tagName, children: children, position: position });
var innerState = { tokens: tokens, options: options, cursor: cursor, stack: stack };
parse(innerState);
cursor = innerState.cursor;
+ var rewoundInElement = stack.length === size;
+ if (rewoundInElement) {
+ elementNode.position.end = tokens[cursor - 1].position.end;
+ }
}
}
state.cursor = cursor;
diff --git a/docs/dist/himalaya.js.map b/docs/dist/himalaya.js.map
index 48c12fc..53ce6de 100644
--- a/docs/dist/himalaya.js.map
+++ b/docs/dist/himalaya.js.map
@@ -1 +1 @@
-{"version":3,"names":[],"mappings":"","sources":["himalaya.js"],"sourcesContent":["(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.himalaya = f()}})(function(){var define,module,exports;return (function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o= 0 ? lookupIndex : len + lookupIndex;\n while (searchIndex < len) {\n var element = array[searchIndex++];\n if (element === searchElement) return true;\n if (isNaNElement && isRealNaN(element)) return true;\n }\n\n return false;\n}\n\n},{}],2:[function(require,module,exports){\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.splitHead = splitHead;\nexports.unquote = unquote;\nexports.format = format;\nexports.formatAttributes = formatAttributes;\nfunction splitHead(str, sep) {\n var idx = str.indexOf(sep);\n if (idx === -1) return [str];\n return [str.slice(0, idx), str.slice(idx + sep.length)];\n}\n\nfunction unquote(str) {\n var car = str.charAt(0);\n var end = str.length - 1;\n var isQuoteStart = car === '\"' || car === \"'\";\n if (isQuoteStart && car === str.charAt(end)) {\n return str.slice(1, end);\n }\n return str;\n}\n\nfunction format(nodes) {\n return nodes.map(function (node) {\n var type = node.type;\n if (type === 'element') {\n var tagName = node.tagName.toLowerCase();\n var attributes = formatAttributes(node.attributes);\n var children = format(node.children);\n return { type: type, tagName: tagName, attributes: attributes, children: children };\n }\n\n return { type: type, content: node.content };\n });\n}\n\nfunction formatAttributes(attributes) {\n return attributes.map(function (attribute) {\n var parts = splitHead(attribute.trim(), '=');\n var key = parts[0];\n var value = typeof parts[1] === 'string' ? unquote(parts[1]) : null;\n return { key: key, value: value };\n });\n}\n\n},{}],3:[function(require,module,exports){\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.parseDefaults = undefined;\nexports.parse = parse;\nexports.stringify = stringify;\n\nvar _lexer = require('./lexer');\n\nvar _lexer2 = _interopRequireDefault(_lexer);\n\nvar _parser = require('./parser');\n\nvar _parser2 = _interopRequireDefault(_parser);\n\nvar _format = require('./format');\n\nvar _stringify = require('./stringify');\n\nvar _tags = require('./tags');\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar parseDefaults = exports.parseDefaults = {\n voidTags: _tags.voidTags,\n closingTags: _tags.closingTags,\n childlessTags: _tags.childlessTags,\n closingTagAncestorBreakers: _tags.closingTagAncestorBreakers\n};\n\nfunction parse(str) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : parseDefaults;\n\n var tokens = (0, _lexer2.default)(str, options);\n var nodes = (0, _parser2.default)(tokens, options);\n return (0, _format.format)(nodes, options);\n}\n\nfunction stringify(ast) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : parseDefaults;\n\n return (0, _stringify.toHTML)(ast, options);\n}\n\n},{\"./format\":2,\"./lexer\":4,\"./parser\":5,\"./stringify\":6,\"./tags\":7}],4:[function(require,module,exports){\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = lexer;\nexports.lex = lex;\nexports.findTextEnd = findTextEnd;\nexports.lexText = lexText;\nexports.lexComment = lexComment;\nexports.lexTag = lexTag;\nexports.isWhitespaceChar = isWhitespaceChar;\nexports.lexTagName = lexTagName;\nexports.lexTagAttributes = lexTagAttributes;\nexports.lexSkipTag = lexSkipTag;\n\nvar _compat = require('./compat');\n\nfunction _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\nfunction lexer(str, options) {\n var state = { str: str, options: options, cursor: 0, tokens: [] };\n lex(state);\n return state.tokens;\n}\n\nfunction lex(state) {\n var str = state.str;\n\n var len = str.length;\n while (state.cursor < len) {\n var start = state.cursor;\n lexText(state);\n if (state.cursor === start) {\n var isComment = (0, _compat.startsWith)(str, '!--', state.cursor + 1);\n if (isComment) {\n lexComment(state);\n } else {\n var tagName = lexTag(state);\n var safeTag = tagName.toLowerCase();\n var childlessTags = state.options.childlessTags;\n\n if ((0, _compat.arrayIncludes)(childlessTags, safeTag)) {\n lexSkipTag(tagName, state);\n }\n }\n }\n }\n}\n\nvar alphanumeric = /[A-Za-z0-9]/;\nfunction findTextEnd(str, index) {\n while (true) {\n var textEnd = str.indexOf('<', index);\n if (textEnd === -1) {\n return textEnd;\n }\n var char = str.charAt(textEnd + 1);\n if (char === '/' || char === '!' || alphanumeric.test(char)) {\n return textEnd;\n }\n index = textEnd + 1;\n }\n}\n\nfunction lexText(state) {\n var type = 'text';\n var str = state.str,\n cursor = state.cursor;\n\n var textEnd = findTextEnd(str, cursor);\n if (textEnd === -1) {\n // there is only text left\n var _content = str.slice(cursor);\n state.cursor = str.length;\n state.tokens.push({ type: type, content: _content });\n return;\n }\n\n if (textEnd === cursor) return;\n\n var content = str.slice(cursor, textEnd);\n state.cursor = textEnd;\n state.tokens.push({ type: type, content: content });\n}\n\nfunction lexComment(state) {\n state.cursor += 4; // \"', cursor);\n var type = 'comment';\n if (commentEnd === -1) {\n // there is only the comment left\n var _content2 = str.slice(cursor);\n state.cursor = str.length;\n state.tokens.push({ type: type, content: _content2 });\n return;\n }\n\n var content = str.slice(cursor, commentEnd);\n state.cursor = commentEnd + 3; // \"-->\".length\n state.tokens.push({ type: type, content: content });\n}\n\nfunction lexTag(state) {\n var str = state.str;\n\n {\n var secondChar = str.charAt(state.cursor + 1);\n var close = secondChar === '/';\n state.tokens.push({ type: 'tag-start', close: close });\n state.cursor += close ? 2 : 1;\n }\n var tagName = lexTagName(state);\n lexTagAttributes(state);\n {\n var firstChar = str.charAt(state.cursor);\n var _close = firstChar === '/';\n state.tokens.push({ type: 'tag-end', close: _close });\n state.cursor += _close ? 2 : 1;\n }\n return tagName;\n}\n\n// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#special-white-space\nvar whitespace = /\\s/;\nfunction isWhitespaceChar(char) {\n return whitespace.test(char);\n}\n\nfunction lexTagName(state) {\n var str = state.str,\n cursor = state.cursor;\n\n var len = str.length;\n var start = cursor;\n while (start < len) {\n var char = str.charAt(start);\n var isTagChar = !(isWhitespaceChar(char) || char === '/' || char === '>');\n if (isTagChar) break;\n start++;\n }\n\n var end = start + 1;\n while (end < len) {\n var _char = str.charAt(end);\n var _isTagChar = !(isWhitespaceChar(_char) || _char === '/' || _char === '>');\n if (!_isTagChar) break;\n end++;\n }\n\n state.cursor = end;\n var tagName = str.slice(start, end);\n state.tokens.push({ type: 'tag', content: tagName });\n return tagName;\n}\n\nfunction lexTagAttributes(state) {\n var str = state.str,\n tokens = state.tokens;\n\n var cursor = state.cursor;\n var quote = null; // null, single-, or double-quote\n var wordBegin = cursor; // index of word start\n var words = []; // \"key\", \"key=value\", \"key='value'\", etc\n var len = str.length;\n while (cursor < len) {\n var char = str.charAt(cursor);\n if (quote) {\n var isQuoteEnd = char === quote;\n if (isQuoteEnd) {\n quote = null;\n }\n cursor++;\n continue;\n }\n\n var isTagEnd = char === '/' || char === '>';\n if (isTagEnd) {\n if (cursor !== wordBegin) {\n words.push(str.slice(wordBegin, cursor));\n }\n break;\n }\n\n var isWordEnd = isWhitespaceChar(char);\n if (isWordEnd) {\n if (cursor !== wordBegin) {\n words.push(str.slice(wordBegin, cursor));\n }\n wordBegin = cursor + 1;\n cursor++;\n continue;\n }\n\n var isQuoteStart = char === '\\'' || char === '\"';\n if (isQuoteStart) {\n quote = char;\n cursor++;\n continue;\n }\n\n cursor++;\n }\n state.cursor = cursor;\n\n var wLen = words.length;\n var type = 'attribute';\n for (var i = 0; i < wLen; i++) {\n var word = words[i];\n var isNotPair = word.indexOf('=') === -1;\n if (isNotPair) {\n var secondWord = words[i + 1];\n if (secondWord && (0, _compat.startsWith)(secondWord, '=')) {\n if (secondWord.length > 1) {\n var newWord = word + secondWord;\n tokens.push({ type: type, content: newWord });\n i += 1;\n continue;\n }\n var thirdWord = words[i + 2];\n i += 1;\n if (thirdWord) {\n var _newWord = word + '=' + thirdWord;\n tokens.push({ type: type, content: _newWord });\n i += 1;\n continue;\n }\n }\n }\n if ((0, _compat.endsWith)(word, '=')) {\n var _secondWord = words[i + 1];\n if (_secondWord && !(0, _compat.stringIncludes)(_secondWord, '=')) {\n var _newWord3 = word + _secondWord;\n tokens.push({ type: type, content: _newWord3 });\n i += 1;\n continue;\n }\n\n var _newWord2 = word.slice(0, -1);\n tokens.push({ type: type, content: _newWord2 });\n continue;\n }\n\n tokens.push({ type: type, content: word });\n }\n}\n\nfunction lexSkipTag(tagName, state) {\n var str = state.str,\n cursor = state.cursor,\n tokens = state.tokens;\n\n var len = str.length;\n var index = cursor;\n while (index < len) {\n var nextTag = str.indexOf('', index);\n if (nextTag === -1) {\n lexText(state);\n break;\n }\n\n var tagState = { str: str, cursor: nextTag + 2, tokens: [] };\n var name = lexTagName(tagState);\n var safeTagName = tagName.toLowerCase();\n if (safeTagName !== name.toLowerCase()) {\n index = tagState.cursor;\n continue;\n }\n\n var content = str.slice(cursor, nextTag);\n tokens.push({ type: 'text', content: content });\n var openTag = { type: 'tag-start', close: true };\n var closeTag = { type: 'tag-end', close: false };\n lexTagAttributes(tagState);\n tokens.push.apply(tokens, [openTag].concat(_toConsumableArray(tagState.tokens), [closeTag]));\n state.cursor = tagState.cursor + 1;\n break;\n }\n}\n\n},{\"./compat\":1}],5:[function(require,module,exports){\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = parser;\nexports.hasTerminalParent = hasTerminalParent;\nexports.parse = parse;\n\nvar _compat = require('./compat');\n\nfunction parser(tokens, options) {\n var root = { tagName: null, children: [] };\n var state = { tokens: tokens, options: options, cursor: 0, stack: [root] };\n parse(state);\n return root.children;\n}\n\nfunction hasTerminalParent(tagName, stack, terminals) {\n var tagParents = terminals[tagName];\n if (tagParents) {\n var currentIndex = stack.length - 1;\n while (currentIndex >= 0) {\n var parentTagName = stack[currentIndex].tagName;\n if (parentTagName === tagName) {\n break;\n }\n if ((0, _compat.arrayIncludes)(tagParents, parentTagName)) {\n return true;\n }\n currentIndex--;\n }\n }\n return false;\n}\n\nfunction parse(state) {\n var tokens = state.tokens,\n options = state.options;\n var stack = state.stack;\n\n var nodes = stack[stack.length - 1].children;\n var len = tokens.length;\n var cursor = state.cursor;\n\n while (cursor < len) {\n var token = tokens[cursor];\n if (token.type !== 'tag-start') {\n nodes.push(token);\n cursor++;\n continue;\n }\n\n var tagToken = tokens[++cursor];\n cursor++;\n var tagName = tagToken.content.toLowerCase();\n if (token.close) {\n var index = stack.length;\n var didRewind = false;\n while (--index > -1) {\n if (stack[index].tagName === tagName) {\n stack.splice(index);\n didRewind = true;\n break;\n }\n }\n while (cursor < len) {\n var endToken = tokens[cursor];\n if (endToken.type !== 'tag-end') break;\n cursor++;\n }\n if (didRewind) {\n break;\n } else {\n continue;\n }\n }\n\n var isClosingTag = (0, _compat.arrayIncludes)(options.closingTags, tagName);\n var shouldRewindToAutoClose = isClosingTag;\n if (shouldRewindToAutoClose) {\n var terminals = options.closingTagAncestorBreakers;\n\n shouldRewindToAutoClose = !hasTerminalParent(tagName, stack, terminals);\n }\n\n if (shouldRewindToAutoClose) {\n // rewind the stack to just above the previous\n // closing tag of the same name\n var currentIndex = stack.length - 1;\n while (currentIndex > 0) {\n if (tagName === stack[currentIndex].tagName) {\n stack = stack.slice(0, currentIndex);\n var previousIndex = currentIndex - 1;\n nodes = stack[previousIndex].children;\n break;\n }\n currentIndex = currentIndex - 1;\n }\n }\n\n var attributes = [];\n var attrToken = void 0;\n while (cursor < len) {\n attrToken = tokens[cursor];\n if (attrToken.type === 'tag-end') break;\n attributes.push(attrToken.content);\n cursor++;\n }\n\n cursor++;\n var children = [];\n nodes.push({\n type: 'element',\n tagName: tagToken.content,\n attributes: attributes,\n children: children\n });\n\n var hasChildren = !(attrToken.close || (0, _compat.arrayIncludes)(options.voidTags, tagName));\n if (hasChildren) {\n stack.push({ tagName: tagName, children: children });\n var innerState = { tokens: tokens, options: options, cursor: cursor, stack: stack };\n parse(innerState);\n cursor = innerState.cursor;\n }\n }\n state.cursor = cursor;\n}\n\n},{\"./compat\":1}],6:[function(require,module,exports){\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.formatAttributes = formatAttributes;\nexports.toHTML = toHTML;\n\nvar _compat = require('./compat');\n\nfunction formatAttributes(attributes) {\n return attributes.reduce(function (attrs, attribute) {\n var key = attribute.key,\n value = attribute.value;\n\n if (value === null) {\n return attrs + ' ' + key;\n }\n var quoteEscape = value.indexOf('\\'') !== -1;\n var quote = quoteEscape ? '\"' : '\\'';\n return attrs + ' ' + key + '=' + quote + value + quote;\n }, '');\n}\n\nfunction toHTML(tree, options) {\n return tree.map(function (node) {\n if (node.type === 'text') {\n return node.content;\n }\n if (node.type === 'comment') {\n return '';\n }\n var tagName = node.tagName,\n attributes = node.attributes,\n children = node.children;\n\n var isSelfClosing = (0, _compat.arrayIncludes)(options.voidTags, tagName.toLowerCase());\n return isSelfClosing ? '<' + tagName + formatAttributes(attributes) + '>' : '<' + tagName + formatAttributes(attributes) + '>' + toHTML(children, options) + '' + tagName + '>';\n }).join('');\n}\n\nexports.default = { toHTML: toHTML };\n\n},{\"./compat\":1}],7:[function(require,module,exports){\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n/*\n Tags which contain arbitary non-parsed content\n For example: