mirror of
https://github.com/tenrok/BBob.git
synced 2026-06-05 16:42:27 +03:00
fix(296): parse with lost repeated closing tag (#297)
* fix: extract node list ot separate file, fix nested parsing * chore: node list tests * fix: nested tags parsing * chore: changeset * chore: remove unused files * chore: disable publish on every commit
This commit is contained in:
@@ -6,37 +6,7 @@ import { createLexer } from "./lexer.js";
|
||||
|
||||
import { Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD } from "./Token.js";
|
||||
|
||||
class NodeList<Value> {
|
||||
private n: Value[];
|
||||
|
||||
constructor() {
|
||||
this.n = [];
|
||||
}
|
||||
|
||||
last() {
|
||||
if (
|
||||
Array.isArray(this.n) &&
|
||||
this.n.length > 0 &&
|
||||
typeof this.n[this.n.length - 1] !== "undefined"
|
||||
) {
|
||||
return this.n[this.n.length - 1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
flush() {
|
||||
return this.n.length ? this.n.pop() : false;
|
||||
}
|
||||
|
||||
push(value: Value) {
|
||||
this.n.push(value);
|
||||
}
|
||||
|
||||
toArray() {
|
||||
return this.n;
|
||||
}
|
||||
}
|
||||
import { NodeList } from "./NodeList.js";
|
||||
|
||||
const createList = <Type>() => new NodeList<Type>();
|
||||
|
||||
@@ -67,13 +37,13 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
* @private
|
||||
* @type {NodeList}
|
||||
*/
|
||||
const tagNodes = createList<TagNode>();
|
||||
let activeTagNode: TagNode | null = null;
|
||||
/**
|
||||
* Temp buffer of tag attributes
|
||||
* @private
|
||||
* @type {NodeList}
|
||||
*/
|
||||
const tagNodesAttrName = createList<string>();
|
||||
let activeTagNodesAttrName: string | null = null;
|
||||
|
||||
/**
|
||||
* Cache for nested tags checks
|
||||
@@ -122,30 +92,31 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
* Flushes temp tag nodes and its attributes buffers
|
||||
* @private
|
||||
*/
|
||||
function tagNodesFlush() {
|
||||
if (tagNodes.flush()) {
|
||||
tagNodesAttrName.flush();
|
||||
function activeTagNodeFlush() {
|
||||
if (activeTagNode) {
|
||||
activeTagNode = null;
|
||||
activeTagNodesAttrName = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getNodes() {
|
||||
function getNodesContent() {
|
||||
const lastNestedNode = nestedNodes.last();
|
||||
|
||||
if (lastNestedNode && isTagNode(lastNestedNode)) {
|
||||
return lastNestedNode.content;
|
||||
}
|
||||
|
||||
return nodes.toArray();
|
||||
return nodes.ref();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function nodesAppendAsString(
|
||||
nodes?: TagNodeTree,
|
||||
nodes: TagNodeTree,
|
||||
node?: TagNode,
|
||||
isNested = true
|
||||
) {
|
||||
@@ -167,7 +138,9 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function nodesAppend(nodes?: TagNodeTree, node?: NodeContent) {
|
||||
function nodesAppend(node: NodeContent) {
|
||||
const nodes = getNodesContent() as TagNodeTree;
|
||||
|
||||
if (Array.isArray(nodes) && typeof node !== "undefined") {
|
||||
if (isTagNode(node)) {
|
||||
if (isTagAllowed(node.tag)) {
|
||||
@@ -186,18 +159,17 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
* @param {Token} token
|
||||
*/
|
||||
function tagHandleStart(token: Token) {
|
||||
tagNodesFlush();
|
||||
activeTagNodeFlush();
|
||||
|
||||
const tagNode = TagNode.create(token.getValue(), {}, [], { from: token.getStart(), to: token.getEnd() });
|
||||
const isNested = isTokenNested(token);
|
||||
|
||||
tagNodes.push(tagNode);
|
||||
activeTagNode = tagNode;
|
||||
|
||||
if (isNested) {
|
||||
nestedNodes.push(tagNode);
|
||||
} else {
|
||||
const nodes = getNodes();
|
||||
nodesAppend(nodes, tagNode);
|
||||
nodesAppend(tagNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,20 +181,16 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
const tagName = token.getValue().slice(1);
|
||||
const lastNestedNode = nestedNodes.flush();
|
||||
|
||||
tagNodesFlush();
|
||||
activeTagNodeFlush();
|
||||
|
||||
if (lastNestedNode) {
|
||||
const nodes = getNodes()
|
||||
|
||||
if (isTagNode(lastNestedNode)) {
|
||||
lastNestedNode.setEnd({ from: token.getStart(), to: token.getEnd() });
|
||||
}
|
||||
|
||||
nodesAppend(nodes, lastNestedNode);
|
||||
nodesAppend(lastNestedNode);
|
||||
} else if (!isTagNested(tagName)) { // when we have only close tag [/some] without any open tag
|
||||
const nodes = getNodes();
|
||||
|
||||
nodesAppend(nodes, token.toString({ openTag, closeTag }));
|
||||
nodesAppend(token.toString({ openTag, closeTag }));
|
||||
} else if (typeof options.onError === "function") {
|
||||
const tag = token.getValue();
|
||||
const line = token.getLine();
|
||||
@@ -241,31 +209,24 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
* @param {Token} token
|
||||
*/
|
||||
function nodeHandle(token: Token) {
|
||||
/**
|
||||
* @type {TagNode}
|
||||
*/
|
||||
const activeTagNode = tagNodes.last();
|
||||
|
||||
const tokenValue = token.getValue();
|
||||
const isNested = isTagNested(token.toString());
|
||||
const nodes = getNodes();
|
||||
|
||||
if (activeTagNode !== null) {
|
||||
if (activeTagNode) {
|
||||
switch (token.type) {
|
||||
case TYPE_ATTR_NAME:
|
||||
tagNodesAttrName.push(tokenValue);
|
||||
const attrName = tagNodesAttrName.last();
|
||||
activeTagNodesAttrName = tokenValue;
|
||||
|
||||
if (attrName) {
|
||||
activeTagNode.attr(attrName, "");
|
||||
if (tokenValue) {
|
||||
activeTagNode.attr(tokenValue, "");
|
||||
}
|
||||
break;
|
||||
|
||||
case TYPE_ATTR_VALUE:
|
||||
const attrValName = tagNodesAttrName.last();
|
||||
|
||||
if (attrValName) {
|
||||
activeTagNode.attr(attrValName, tokenValue);
|
||||
tagNodesAttrName.flush();
|
||||
if (activeTagNodesAttrName) {
|
||||
activeTagNode.attr(activeTagNodesAttrName, tokenValue);
|
||||
activeTagNodesAttrName = null;
|
||||
} else {
|
||||
activeTagNode.attr(tokenValue, tokenValue);
|
||||
}
|
||||
@@ -277,20 +238,20 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
if (isNested) {
|
||||
activeTagNode.append(tokenValue);
|
||||
} else {
|
||||
nodesAppend(nodes, tokenValue);
|
||||
nodesAppend(tokenValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case TYPE_TAG:
|
||||
// if tag is not allowed, just pass it as is
|
||||
nodesAppend(nodes, token.toString({ openTag, closeTag }));
|
||||
nodesAppend(token.toString({ openTag, closeTag }));
|
||||
break;
|
||||
}
|
||||
} else if (token.isText()) {
|
||||
nodesAppend(nodes, tokenValue);
|
||||
nodesAppend(tokenValue);
|
||||
} else if (token.isTag()) {
|
||||
// if tag is not allowed, just pass it as is
|
||||
nodesAppend(nodes, token.toString({ openTag, closeTag }));
|
||||
nodesAppend(token.toString({ openTag, closeTag }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,15 +291,20 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const tokens = tokenizer.tokenize();
|
||||
|
||||
// handles situations where we open tag, but forgot close them
|
||||
// handles situations where we opened tag, but forget to close them
|
||||
// for ex [q]test[/q][u]some[/u][q]some [u]some[/u] // forgot to close [/q]
|
||||
// so we need to flush nested content to nodes array
|
||||
const lastNestedNode = nestedNodes.flush();
|
||||
if (isTagNode(lastNestedNode) && isTagNested(lastNestedNode.tag)) {
|
||||
nodesAppendAsString(getNodes(), lastNestedNode, false);
|
||||
}
|
||||
do {
|
||||
const node = nestedNodes.flush();
|
||||
|
||||
return nodes.toArray();
|
||||
if (isTagNode(node) && isTagNested(node.tag)) {
|
||||
nodesAppendAsString(getNodesContent(), node, false);
|
||||
} else if (typeof node !== 'undefined') {
|
||||
nodesAppend(node);
|
||||
}
|
||||
} while (nestedNodes.has());
|
||||
|
||||
return nodes.ref();
|
||||
}
|
||||
|
||||
export { parse };
|
||||
|
||||
Reference in New Issue
Block a user