2
0
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:
Nikolay Kost
2025-10-14 04:11:19 +02:00
committed by GitHub
parent 40041a0680
commit 29f909a589
7 changed files with 183 additions and 77 deletions
+42 -76
View File
@@ -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 };