mirror of
https://github.com/tenrok/BBob.git
synced 2026-05-15 11:59:37 +03:00
feat: add start and end positions of tag nodes (#246)
Closes #134 * feat: Add start and end positions of tag nodes Improves accuracy of row/col error reporting. Now targets the start of the relevant token instead of the end. * Simplify language for TagNode and Token * Update static TagNode.create to ingest setStart() logic improve readability of end pos offset for no attr tags
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
---
|
||||
"@bbob/plugin-helper": minor
|
||||
"@bbob/parser": minor
|
||||
"@bbob/types": minor
|
||||
"@bbob/cli": minor
|
||||
"@bbob/core": minor
|
||||
"@bbob/html": minor
|
||||
"@bbob/preset": minor
|
||||
"@bbob/preset-html5": minor
|
||||
"@bbob/preset-react": minor
|
||||
"@bbob/preset-vue": minor
|
||||
"@bbob/react": minor
|
||||
"@bbob/vue2": minor
|
||||
"@bbob/vue3": minor
|
||||
---
|
||||
|
||||
feat: Add start and end positions of tag nodes
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TagNode } from '@bbob/parser'
|
||||
import core, { BBobPluginFunction, BBobPlugins } from '../src'
|
||||
import { TagNode } from '@bbob/parser';
|
||||
import core, { BBobPluginFunction, BBobPlugins } from '../src';
|
||||
import { isTagNode } from "@bbob/plugin-helper";
|
||||
|
||||
const stringify = (val: unknown) => JSON.stringify(val);
|
||||
@@ -11,15 +11,17 @@ describe('@bbob/core', () => {
|
||||
const res = process([], '[style size="15px"]Large Text[/style]');
|
||||
const ast = res.tree;
|
||||
|
||||
expect(res.html).toBe('[{"tag":"style","attrs":{"size":"15px"},"content":["Large"," ","Text"]}]');
|
||||
expect(res.html).toBe('[{"tag":"style","attrs":{"size":"15px"},"content":["Large"," ","Text"],"start":{"from":0,"to":19},"end":{"from":29,"to":37}}]');
|
||||
expect(ast).toBeInstanceOf(Array);
|
||||
expect(stringify(ast)).toEqual(stringify([
|
||||
{
|
||||
tag: 'style',
|
||||
attrs: { size: '15px' },
|
||||
content: ["Large", " ", "Text"]
|
||||
content: ["Large", " ", "Text"],
|
||||
start: { from: 0, to: 19 },
|
||||
end: { from: 29, to: 37 },
|
||||
}
|
||||
]))
|
||||
]));
|
||||
});
|
||||
|
||||
test('plugin walk api node', () => {
|
||||
@@ -39,11 +41,11 @@ describe('@bbob/core', () => {
|
||||
|
||||
}
|
||||
|
||||
return node
|
||||
return node;
|
||||
});
|
||||
|
||||
return plugin
|
||||
}
|
||||
return plugin;
|
||||
};
|
||||
|
||||
const res = process([testPlugin()], '[mytag size="15px"]Large Text[/mytag]');
|
||||
const ast = res.tree;
|
||||
@@ -61,7 +63,15 @@ describe('@bbob/core', () => {
|
||||
' ',
|
||||
'Text',
|
||||
'Test'
|
||||
]
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 19
|
||||
},
|
||||
end: {
|
||||
from: 29,
|
||||
to: 37
|
||||
}
|
||||
}
|
||||
]));
|
||||
});
|
||||
@@ -71,13 +81,13 @@ describe('@bbob/core', () => {
|
||||
|
||||
const plugin: BBobPluginFunction = (tree) => tree.walk(node => {
|
||||
if (node === ':)') {
|
||||
return TagNode.create('test-tag', {}, [])
|
||||
return TagNode.create('test-tag', {}, []);
|
||||
}
|
||||
|
||||
return node
|
||||
})
|
||||
return node;
|
||||
});
|
||||
|
||||
return plugin
|
||||
return plugin;
|
||||
};
|
||||
|
||||
const res = process([testPlugin()], '[mytag]Large Text :)[/mytag]');
|
||||
@@ -99,7 +109,15 @@ describe('@bbob/core', () => {
|
||||
attrs: {},
|
||||
content: [],
|
||||
}
|
||||
]
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 7
|
||||
},
|
||||
end: {
|
||||
from: 20,
|
||||
to: 28
|
||||
}
|
||||
}
|
||||
]));
|
||||
});
|
||||
@@ -109,13 +127,13 @@ describe('@bbob/core', () => {
|
||||
|
||||
const plugin: BBobPluginFunction = (tree) => tree.match([{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
|
||||
if (isTagNode(node) && node.attrs) {
|
||||
node.attrs['pass'] = 1
|
||||
node.attrs['pass'] = 1;
|
||||
}
|
||||
|
||||
return node
|
||||
})
|
||||
return node;
|
||||
});
|
||||
|
||||
return plugin
|
||||
return plugin;
|
||||
};
|
||||
|
||||
const res = process([testPlugin()], `[mytag1 size="15"]Tag1[/mytag1][mytag2 size="16"]Tag2[/mytag2][mytag3]Tag3[/mytag3]`);
|
||||
@@ -132,7 +150,9 @@ describe('@bbob/core', () => {
|
||||
},
|
||||
content: [
|
||||
'Tag1'
|
||||
]
|
||||
],
|
||||
start: { from: 0, to: 18 },
|
||||
end: { from: 22, to: 31 }
|
||||
},
|
||||
{
|
||||
tag: 'mytag2',
|
||||
@@ -142,15 +162,19 @@ describe('@bbob/core', () => {
|
||||
},
|
||||
content: [
|
||||
'Tag2'
|
||||
]
|
||||
],
|
||||
start: { from: 31, to: 49 },
|
||||
end: { from: 53, to: 62 }
|
||||
},
|
||||
{
|
||||
tag: 'mytag3',
|
||||
attrs: {},
|
||||
content: [
|
||||
'Tag3'
|
||||
]
|
||||
],
|
||||
start: { from: 62, to: 70 },
|
||||
end: { from: 74, to: 83 }
|
||||
}
|
||||
]));
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,14 @@ import {
|
||||
} from '@bbob/plugin-helper';
|
||||
import type { Token as TokenInterface } from "@bbob/types";
|
||||
|
||||
// type, value, line, row,
|
||||
// type, value, line, row, start pos, end pos
|
||||
|
||||
const TOKEN_TYPE_ID = 't'; // 0;
|
||||
const TOKEN_VALUE_ID = 'v'; // 1;
|
||||
const TOKEN_COLUMN_ID = 'r'; // 2;
|
||||
const TOKEN_LINE_ID = 'l'; // 3;
|
||||
const TOKEN_START_POS_ID = 's'; // 4;
|
||||
const TOKEN_END_POS_ID = 'e'; // 5;
|
||||
|
||||
const TOKEN_TYPE_WORD = 1; // 'word';
|
||||
const TOKEN_TYPE_TAG = 2; // 'tag';
|
||||
@@ -31,11 +33,15 @@ const getTokenLine = (token: Token) => (token && token[TOKEN_LINE_ID]) || 0;
|
||||
|
||||
const getTokenColumn = (token: Token) => (token && token[TOKEN_COLUMN_ID]) || 0;
|
||||
|
||||
const getStartPosition = (token: Token) => (token && token[TOKEN_START_POS_ID]) || 0;
|
||||
|
||||
const getEndPosition = (token: Token) => (token && token[TOKEN_END_POS_ID]) || 0;
|
||||
|
||||
const isTextToken = (token: Token) => {
|
||||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
||||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE
|
||||
|| token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE
|
||||
|| token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD;
|
||||
|| token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE
|
||||
|| token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -88,21 +94,25 @@ const tokenToText = (token: Token) => {
|
||||
* @export
|
||||
* @class Token
|
||||
*/
|
||||
class Token<TokenValue = string> implements TokenInterface {
|
||||
readonly t: number // type
|
||||
readonly v: string // value
|
||||
readonly l: number // line
|
||||
readonly r: number // row
|
||||
class Token<TokenValue = string> implements TokenInterface {
|
||||
readonly t: number; // type
|
||||
readonly v: string; // value
|
||||
readonly l: number; // line
|
||||
readonly r: number; // row
|
||||
readonly s: number; // start pos
|
||||
readonly e: number; // end pos
|
||||
|
||||
constructor(type?: number, value?: TokenValue, row: number = 0, col: number = 0) {
|
||||
constructor(type?: number, value?: TokenValue, row: number = 0, col: number = 0, start: number = 0, end: number = 0) {
|
||||
this[TOKEN_LINE_ID] = row;
|
||||
this[TOKEN_COLUMN_ID] = col;
|
||||
this[TOKEN_TYPE_ID] = type || 0;
|
||||
this[TOKEN_VALUE_ID] = String(value);
|
||||
this[TOKEN_START_POS_ID] = start;
|
||||
this[TOKEN_END_POS_ID] = end;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this[TOKEN_TYPE_ID]
|
||||
return this[TOKEN_TYPE_ID];
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
@@ -149,6 +159,14 @@ class Token<TokenValue = string> implements TokenInterface {
|
||||
return getTokenColumn(this);
|
||||
}
|
||||
|
||||
getStart() {
|
||||
return getStartPosition(this);
|
||||
}
|
||||
|
||||
getEnd() {
|
||||
return getEndPosition(this);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return tokenToText(this);
|
||||
}
|
||||
@@ -158,6 +176,8 @@ export const TYPE_ID = TOKEN_TYPE_ID;
|
||||
export const VALUE_ID = TOKEN_VALUE_ID;
|
||||
export const LINE_ID = TOKEN_LINE_ID;
|
||||
export const COLUMN_ID = TOKEN_COLUMN_ID;
|
||||
export const START_POS_ID = TOKEN_START_POS_ID;
|
||||
export const END_POS_ID = TOKEN_END_POS_ID;
|
||||
export const TYPE_WORD = TOKEN_TYPE_WORD;
|
||||
export const TYPE_TAG = TOKEN_TYPE_TAG;
|
||||
export const TYPE_ATTR_NAME = TOKEN_TYPE_ATTR_NAME;
|
||||
|
||||
@@ -20,8 +20,8 @@ import { CharGrabber, createCharGrabber, trimChar, unquote } from './utils';
|
||||
// for cases <!-- -->
|
||||
const EM = '!';
|
||||
|
||||
export function createTokenOfType(type: number, value: string, r = 0, cl = 0) {
|
||||
return new Token(type, value, r, cl)
|
||||
export function createTokenOfType(type: number, value: string, r = 0, cl = 0, p = 0, e = 0) {
|
||||
return new Token(type, value, r, cl, p, e);
|
||||
}
|
||||
|
||||
const STATE_WORD = 0;
|
||||
@@ -34,6 +34,7 @@ const TAG_STATE_VALUE = 2;
|
||||
|
||||
const WHITESPACES = [SPACE, TAB];
|
||||
const SPECIAL_CHARS = [EQ, SPACE, TAB];
|
||||
const END_POS_OFFSET = 2; // length + start position offset
|
||||
|
||||
const isWhiteSpace = (char: string) => (WHITESPACES.indexOf(char) >= 0);
|
||||
const isEscapeChar = (char: string) => char === BACKSLASH;
|
||||
@@ -43,6 +44,7 @@ const unq = (val: string) => unquote(trimChar(val, QUOTEMARK));
|
||||
|
||||
export function createLexer(buffer: string, options: LexerOptions = {}): LexerTokenizer {
|
||||
let row = 0;
|
||||
let prevCol = 0;
|
||||
let col = 0;
|
||||
|
||||
let tokenIndex = -1;
|
||||
@@ -89,16 +91,17 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
* @param {Number} type
|
||||
* @param {String} value
|
||||
*/
|
||||
function emitToken(type: number, value: string) {
|
||||
const token = createTokenOfType(type, value, row, col);
|
||||
function emitToken(type: number, value: string, startPos?: number, endPos?: number) {
|
||||
const token = createTokenOfType(type, value, row, prevCol, startPos, endPos);
|
||||
|
||||
onToken(token);
|
||||
|
||||
prevCol = col;
|
||||
tokenIndex += 1;
|
||||
tokens[tokenIndex] = token;
|
||||
}
|
||||
|
||||
function nextTagState(tagChars: CharGrabber, isSingleValueTag: boolean) {
|
||||
function nextTagState(tagChars: CharGrabber, isSingleValueTag: boolean, masterStartPos: number) {
|
||||
if (tagMode === TAG_STATE_ATTR) {
|
||||
const validAttrName = (char: string) => !(char === EQ || isWhiteSpace(char));
|
||||
const name = tagChars.grabWhile(validAttrName);
|
||||
@@ -161,6 +164,9 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
tagChars.skip();
|
||||
|
||||
emitToken(TYPE_ATTR_VALUE, unq(name));
|
||||
if (tagChars.getPrev() === QUOTEMARK) {
|
||||
prevCol++;
|
||||
}
|
||||
|
||||
if (tagChars.isLast()) {
|
||||
return TAG_STATE_NAME;
|
||||
@@ -169,13 +175,15 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
return TAG_STATE_ATTR;
|
||||
}
|
||||
|
||||
const start = masterStartPos + tagChars.getPos() - 1;
|
||||
const validName = (char: string) => !(char === EQ || isWhiteSpace(char) || tagChars.isLast());
|
||||
const name = tagChars.grabWhile(validName);
|
||||
|
||||
emitToken(TYPE_TAG, name);
|
||||
emitToken(TYPE_TAG, name, start, masterStartPos + tagChars.getLength() + 1);
|
||||
checkContextFreeMode(name);
|
||||
|
||||
tagChars.skip();
|
||||
prevCol++;
|
||||
|
||||
// in cases when we has [url=someval]GET[/url] and we dont need to parse all
|
||||
if (isSingleValueTag) {
|
||||
@@ -209,11 +217,13 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
const isClosingTag = substr[0] === SLASH;
|
||||
|
||||
if (isNoAttrsInTag || isClosingTag) {
|
||||
const startPos = chars.getPos() - 1;
|
||||
const name = chars.grabWhile((char) => char !== closeTag);
|
||||
const endPos = startPos + name.length + END_POS_OFFSET;
|
||||
|
||||
chars.skip(); // skip closeTag
|
||||
|
||||
emitToken(TYPE_TAG, name);
|
||||
emitToken(TYPE_TAG, name, startPos, endPos);
|
||||
checkContextFreeMode(name, isClosingTag);
|
||||
|
||||
return STATE_WORD;
|
||||
@@ -223,6 +233,7 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
}
|
||||
|
||||
function stateAttrs() {
|
||||
const startPos = chars.getPos();
|
||||
const silent = true;
|
||||
const tagStr = chars.grabWhile((char) => char !== closeTag, silent);
|
||||
const tagGrabber = createCharGrabber(tagStr, { onSkip });
|
||||
@@ -231,7 +242,7 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
tagMode = TAG_STATE_NAME;
|
||||
|
||||
while (tagGrabber.hasNext()) {
|
||||
tagMode = nextTagState(tagGrabber, !hasSpace);
|
||||
tagMode = nextTagState(tagGrabber, !hasSpace, startPos);
|
||||
}
|
||||
|
||||
chars.skip(); // skip closeTag
|
||||
@@ -246,6 +257,7 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
chars.skip();
|
||||
|
||||
col = 0;
|
||||
prevCol = 0;
|
||||
row++;
|
||||
|
||||
return STATE_WORD;
|
||||
@@ -276,6 +288,7 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
emitToken(TYPE_WORD, chars.getCurr());
|
||||
|
||||
chars.skip();
|
||||
prevCol++;
|
||||
|
||||
return STATE_WORD;
|
||||
}
|
||||
@@ -345,7 +358,7 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
if (nestedMap.has(value)) {
|
||||
return !!nestedMap.get(value);
|
||||
} else {
|
||||
const status = (buffer.indexOf(value) > -1)
|
||||
const status = (buffer.indexOf(value) > -1);
|
||||
|
||||
nestedMap.set(value, status);
|
||||
|
||||
@@ -356,5 +369,5 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
return {
|
||||
tokenize,
|
||||
isTokenNested,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
function handleTagStart(token: Token) {
|
||||
flushTagNodes();
|
||||
|
||||
const tagNode = TagNode.create(token.getValue(), {}, []);
|
||||
const tagNode = TagNode.create(token.getValue(), {}, [], { from: token.getStart(), to: token.getEnd() });
|
||||
const isNested = isTokenNested(token);
|
||||
|
||||
tagNodes.push(tagNode);
|
||||
@@ -203,6 +203,10 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
* @param {Token} token
|
||||
*/
|
||||
function handleTagEnd(token: Token) {
|
||||
const lastTagNode = nestedNodes.last();
|
||||
if (isTagNode(lastTagNode)) {
|
||||
lastTagNode.setEnd({ from: token.getStart(), to: token.getEnd() });
|
||||
}
|
||||
flushTagNodes();
|
||||
|
||||
const lastNestedNode = nestedNodes.flush();
|
||||
|
||||
@@ -42,6 +42,14 @@ export class CharGrabber {
|
||||
return this.s[this.c.pos]
|
||||
}
|
||||
|
||||
getPos() {
|
||||
return this.c.pos;
|
||||
}
|
||||
|
||||
getLength() {
|
||||
return this.c.len;
|
||||
}
|
||||
|
||||
getRest() {
|
||||
return this.s.substring(this.c.pos)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Token, { TYPE_WORD, TYPE_TAG, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_SPACE, TYPE_NEW_LINE } from '../src/Token'
|
||||
import Token, { TYPE_WORD, TYPE_TAG, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_SPACE, TYPE_NEW_LINE } from '../src/Token';
|
||||
|
||||
describe('Token', () => {
|
||||
test('isEmpty', () => {
|
||||
const token = new Token();
|
||||
|
||||
expect(token.isEmpty()).toBeTruthy()
|
||||
expect(token.isEmpty()).toBeTruthy();
|
||||
});
|
||||
test('isText', () => {
|
||||
const token = new Token(TYPE_WORD);
|
||||
@@ -56,6 +56,16 @@ describe('Token', () => {
|
||||
|
||||
expect(token.getColumn()).toBe(14);
|
||||
});
|
||||
test('getStartPos', () => {
|
||||
const token = new Token(TYPE_TAG, 'my-tag', 12, 14, 50);
|
||||
|
||||
expect(token.getStart()).toBe(50);
|
||||
});
|
||||
test('getEndPos', () => {
|
||||
const token = new Token(TYPE_TAG, 'my-tag', 12, 14, 50, 60);
|
||||
|
||||
expect(token.getEnd()).toBe(60);
|
||||
});
|
||||
test('toString', () => {
|
||||
const tokenEnd = new Token(TYPE_TAG, '/my-tag', 12, 14);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TYPE_ID, VALUE_ID, TYPE_WORD, TYPE_TAG, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_SPACE, TYPE_NEW_LINE} from '../src/Token'
|
||||
import { createLexer } from '../src/lexer'
|
||||
import { TYPE_ID, VALUE_ID, TYPE_WORD, TYPE_TAG, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_SPACE, TYPE_NEW_LINE, LINE_ID, COLUMN_ID, START_POS_ID, END_POS_ID } from '../src/Token';
|
||||
import { createLexer } from '../src/lexer';
|
||||
|
||||
declare global {
|
||||
namespace jest {
|
||||
@@ -32,19 +32,19 @@ describe('lexer', () => {
|
||||
if (tokens.length !== output.length) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected tokens length ${tokens.length} to be ${output.length}`,
|
||||
`expected tokens length ${tokens.length} to be ${output.length}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
for (let idx = 0; idx < tokens.length; idx++) {
|
||||
const token = tokens[idx];
|
||||
const [type, value] = output[idx];
|
||||
const [type, value, col, row, startPos, endPos] = output[idx];
|
||||
|
||||
if (typeof token !== 'object') {
|
||||
return {
|
||||
message: () =>
|
||||
`token must to be Object`,
|
||||
`token must to be Object`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
@@ -52,7 +52,7 @@ describe('lexer', () => {
|
||||
if (token[TYPE_ID] !== type) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected token type ${TYPE_NAMES[type]} but recieved ${TYPE_NAMES[token[TYPE_ID]]} for ${JSON.stringify(output[idx])}`,
|
||||
`expected token type ${TYPE_NAMES[type]} but received ${TYPE_NAMES[token[TYPE_ID]]} for ${JSON.stringify(output[idx])}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
@@ -60,7 +60,39 @@ describe('lexer', () => {
|
||||
if (token[VALUE_ID] !== value) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected token value ${value} but recieved ${token[VALUE_ID]} for ${JSON.stringify(output[idx])}`,
|
||||
`expected token value ${value} but received ${token[VALUE_ID]} for ${JSON.stringify(output[idx])}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (token[LINE_ID] !== row) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected token row ${row} but received ${token[LINE_ID]} for ${JSON.stringify(output[idx])}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (token[COLUMN_ID] !== col) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected token col ${col} but received ${token[COLUMN_ID]} for ${JSON.stringify(output[idx])}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === TYPE.TAG && token[START_POS_ID] !== startPos) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected token start pos ${startPos} but received ${token[START_POS_ID]} for ${JSON.stringify(output[idx])}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === TYPE.TAG && token[END_POS_ID] !== endPos) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected token end pos ${endPos} but received ${token[END_POS_ID]} for ${JSON.stringify(output[idx])}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
@@ -68,7 +100,7 @@ describe('lexer', () => {
|
||||
|
||||
return {
|
||||
message: () =>
|
||||
`no valid output`,
|
||||
`no valid output`,
|
||||
pass: true,
|
||||
};
|
||||
},
|
||||
@@ -78,7 +110,7 @@ describe('lexer', () => {
|
||||
const input = '[SingleTag]';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'SingleTag', '0', '0'],
|
||||
[TYPE.TAG, 'SingleTag', 0, 0, 0, 11],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -88,8 +120,8 @@ describe('lexer', () => {
|
||||
const input = '[user=111]';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'user', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, '111', '0', '0'],
|
||||
[TYPE.TAG, 'user', 0, 0, 0, 10],
|
||||
[TYPE.ATTR_VALUE, '111', 6, 0],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -99,10 +131,10 @@ describe('lexer', () => {
|
||||
const input = '[url=someval]GET[/url]';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'url', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, 'someval', '0', '0'],
|
||||
[TYPE.WORD, 'GET', '0', '0'],
|
||||
[TYPE.TAG, '/url', '0', '0'],
|
||||
[TYPE.TAG, 'url', 0, 0, 0, 13],
|
||||
[TYPE.ATTR_VALUE, 'someval', 5, 0],
|
||||
[TYPE.WORD, 'GET', 13, 0],
|
||||
[TYPE.TAG, '/url', 17, 0, 16, 22],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -112,9 +144,9 @@ describe('lexer', () => {
|
||||
const input = '[ user=111]';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'user=111]', '0', '0'],
|
||||
[TYPE.WORD, '[', 0, 0, 0],
|
||||
[TYPE.SPACE, ' ', 1, 0, 1],
|
||||
[TYPE.WORD, 'user=111]', 2, 0, 2],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -125,7 +157,7 @@ describe('lexer', () => {
|
||||
const tokens = tokenize(input);
|
||||
|
||||
const output = [
|
||||
[TYPE.TAG, 'Single Tag', '0', '0'],
|
||||
[TYPE.TAG, 'Single Tag', 0, 0, 0, 12],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -137,10 +169,10 @@ describe('lexer', () => {
|
||||
const tokens = tokenize(input);
|
||||
|
||||
const output = [
|
||||
[TYPE.TAG, 'textarea', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, 'disabled', '0', '0'],
|
||||
[TYPE.WORD, 'world"', '0', '0'],
|
||||
[TYPE.TAG, '/textarea', '0', '0'],
|
||||
[TYPE.TAG, 'textarea', 0, 0],
|
||||
[TYPE.ATTR_VALUE, 'disabled', 10, 0],
|
||||
[TYPE.WORD, 'world"', 19, 0],
|
||||
[TYPE.TAG, '/textarea', 25, 0],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -153,32 +185,32 @@ describe('lexer', () => {
|
||||
const tokens = tokenize(input);
|
||||
|
||||
const output = [
|
||||
[TYPE.TAG, 'url', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'href', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, '/groups/123/', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'isNowrap', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, 'true', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'isTextOverflow', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, 'true', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'state', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, 'primary', '0', '0'],
|
||||
[TYPE.NEW_LINE, '\n', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.TAG, 'avatar', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'href', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, '/avatar/4/3/b/1606.jpg@20x20?cache=1561462725&bgclr=ffffff', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'size', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, 'xs', '0', '0'],
|
||||
[TYPE.TAG, '/avatar', '0', '0'],
|
||||
[TYPE.NEW_LINE, '\n', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'Group', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'Name', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'Go', '0', '0'],
|
||||
[TYPE.TAG, '/url', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.TAG, 'url', 0, 0, 0, 73],
|
||||
[TYPE.ATTR_NAME, 'href', 5, 0, 5],
|
||||
[TYPE.ATTR_VALUE, '/groups/123/', 10, 0, 10],
|
||||
[TYPE.ATTR_NAME, 'isNowrap', 25, 0, 25],
|
||||
[TYPE.ATTR_VALUE, 'true', 34, 0, 34],
|
||||
[TYPE.ATTR_NAME, 'isTextOverflow', 39, 0, 39],
|
||||
[TYPE.ATTR_VALUE, 'true', 54, 0, 54],
|
||||
[TYPE.ATTR_NAME, 'state', 59, 0, 59],
|
||||
[TYPE.ATTR_VALUE, 'primary', 65, 0, 65],
|
||||
[TYPE.NEW_LINE, '\n', 73, 0, 73],
|
||||
[TYPE.SPACE, ' ', 0, 1, 74],
|
||||
[TYPE.TAG, 'avatar', 8, 1, 82, 164],
|
||||
[TYPE.ATTR_NAME, 'href', 16, 1, 90],
|
||||
[TYPE.ATTR_VALUE, '/avatar/4/3/b/1606.jpg@20x20?cache=1561462725&bgclr=ffffff', 21, 1, 95],
|
||||
[TYPE.ATTR_NAME, 'size', 82, 1, 156],
|
||||
[TYPE.ATTR_VALUE, 'xs', 87, 1, 161],
|
||||
[TYPE.TAG, '/avatar', 90, 1, 164, 173],
|
||||
[TYPE.NEW_LINE, '\n', 100, 1, 173],
|
||||
[TYPE.SPACE, ' ', 0, 2, 174],
|
||||
[TYPE.WORD, 'Group', 9, 2, 184],
|
||||
[TYPE.SPACE, ' ', 14, 2, 189],
|
||||
[TYPE.WORD, 'Name', 15, 2, 190],
|
||||
[TYPE.SPACE, ' ', 19, 2, 194],
|
||||
[TYPE.WORD, 'Go', 20, 2, 195],
|
||||
[TYPE.TAG, '/url', 22, 2, 196, 202],
|
||||
[TYPE.SPACE, ' ', 28, 2, 203],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -189,15 +221,15 @@ describe('lexer', () => {
|
||||
const tokens = tokenize(input);
|
||||
|
||||
const output = [
|
||||
[TYPE.WORD, '"Someone', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '8', '0'],
|
||||
[TYPE.WORD, 'Like', '8', '0'],
|
||||
[TYPE.SPACE, ' ', '13', '0'],
|
||||
[TYPE.WORD, 'You"', '13', '0'],
|
||||
[TYPE.SPACE, ' ', '18', '0'],
|
||||
[TYPE.WORD, 'by', '18', '0'],
|
||||
[TYPE.SPACE, ' ', '21', '0'],
|
||||
[TYPE.WORD, 'Adele', '21', '0'],
|
||||
[TYPE.WORD, '"Someone', 0, 0],
|
||||
[TYPE.SPACE, ' ', 8, 0],
|
||||
[TYPE.WORD, 'Like', 9, 0],
|
||||
[TYPE.SPACE, ' ', 13, 0],
|
||||
[TYPE.WORD, 'You"', 14, 0],
|
||||
[TYPE.SPACE, ' ', 18, 0],
|
||||
[TYPE.WORD, 'by', 19, 0],
|
||||
[TYPE.SPACE, ' ', 21, 0],
|
||||
[TYPE.WORD, 'Adele', 22, 0],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -208,13 +240,13 @@ describe('lexer', () => {
|
||||
const tokens = tokenize(input);
|
||||
|
||||
const output = [
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '1', '0'],
|
||||
[TYPE.TAG, 'h1', '2', '0'],
|
||||
[TYPE.WORD, 'G', '1', '0'],
|
||||
[TYPE.TAG, '/h1', '7', '0'],
|
||||
[TYPE.SPACE, ' ', '12', '0'],
|
||||
[TYPE.WORD, ']', '7', '0'],
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.SPACE, ' ', 1, 0],
|
||||
[TYPE.TAG, 'h1', 2, 0, 2, 6],
|
||||
[TYPE.WORD, 'G', 6, 0],
|
||||
[TYPE.TAG, '/h1', 7, 0, 7, 12],
|
||||
[TYPE.SPACE, ' ', 12, 0],
|
||||
[TYPE.WORD, ']', 13, 0],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -224,10 +256,10 @@ describe('lexer', () => {
|
||||
const input = '[color="#ff0000"]Text[/color]';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'color', '0', '0'],
|
||||
[TYPE.ATTR_VALUE, '#ff0000', '6', '0'],
|
||||
[TYPE.WORD, 'Text', '17', '0'],
|
||||
[TYPE.TAG, '/color', '21', '0'],
|
||||
[TYPE.TAG, 'color', 0, 0, 0, 17],
|
||||
[TYPE.ATTR_VALUE, '#ff0000', 7, 0],
|
||||
[TYPE.WORD, 'Text', 17, 0],
|
||||
[TYPE.TAG, '/color', 21, 0, 21, 29],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -237,13 +269,13 @@ describe('lexer', () => {
|
||||
const input = '[url text="Foo Bar" text2="Foo Bar 2"]Text[/url]';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'url', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'text', '4', '0'],
|
||||
[TYPE.ATTR_VALUE, 'Foo Bar', '9', '0'],
|
||||
[TYPE.ATTR_NAME, 'text2', '4', '0'],
|
||||
[TYPE.ATTR_VALUE, 'Foo Bar 2', '9', '0'],
|
||||
[TYPE.WORD, 'Text', '20', '0'],
|
||||
[TYPE.TAG, '/url', '24', '0'],
|
||||
[TYPE.TAG, 'url', 0, 0, 0, 38],
|
||||
[TYPE.ATTR_NAME, 'text', 5, 0],
|
||||
[TYPE.ATTR_VALUE, 'Foo Bar', 10, 0],
|
||||
[TYPE.ATTR_NAME, 'text2', 20, 0],
|
||||
[TYPE.ATTR_VALUE, 'Foo Bar 2', 26, 0],
|
||||
[TYPE.WORD, 'Text', 38, 0],
|
||||
[TYPE.TAG, '/url', 42, 0, 42, 48],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -253,11 +285,11 @@ describe('lexer', () => {
|
||||
const input = `[url text="Foo \\"Bar"]Text[/url]`;
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'url', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'text', '4', '0'],
|
||||
[TYPE.ATTR_VALUE, 'Foo "Bar', '9', '0'],
|
||||
[TYPE.WORD, 'Text', '22', '0'],
|
||||
[TYPE.TAG, '/url', '26', '0'],
|
||||
[TYPE.TAG, 'url', 0, 0, 0, 22],
|
||||
[TYPE.ATTR_NAME, 'text', 5, 0],
|
||||
[TYPE.ATTR_VALUE, 'Foo "Bar', 10, 0],
|
||||
[TYPE.WORD, 'Text', 22, 0],
|
||||
[TYPE.TAG, '/url', 26, 0, 26, 32],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -267,11 +299,11 @@ describe('lexer', () => {
|
||||
const input = '[style color=#ff0000]Text[/style]';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'style', '0', '0'],
|
||||
[TYPE.ATTR_NAME, 'color', '6', '0'],
|
||||
[TYPE.ATTR_VALUE, '#ff0000', '12', '0'],
|
||||
[TYPE.WORD, 'Text', '21', '0'],
|
||||
[TYPE.TAG, '/style', '25', '0'],
|
||||
[TYPE.TAG, 'style', 0, 0, 0, 21],
|
||||
[TYPE.ATTR_NAME, 'color', 7, 0],
|
||||
[TYPE.ATTR_VALUE, '#ff0000', 13, 0],
|
||||
[TYPE.WORD, 'Text', 21, 0],
|
||||
[TYPE.TAG, '/style', 26, 0, 25, 33],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -286,30 +318,30 @@ describe('lexer', () => {
|
||||
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'list', '0', '0'],
|
||||
[TYPE.NEW_LINE, '\n', '6', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '1'],
|
||||
[TYPE.TAG, '*', '3', '1'],
|
||||
[TYPE.SPACE, ' ', '6', '1'],
|
||||
[TYPE.WORD, 'Item', '7', '1'],
|
||||
[TYPE.SPACE, ' ', '11', '1'],
|
||||
[TYPE.WORD, '1.', '11', '1'],
|
||||
[TYPE.NEW_LINE, '\n', '14', '1'],
|
||||
[TYPE.SPACE, ' ', '0', '2'],
|
||||
[TYPE.TAG, '*', '3', '2'],
|
||||
[TYPE.SPACE, ' ', '6', '2'],
|
||||
[TYPE.WORD, 'Item', '14', '1'],
|
||||
[TYPE.SPACE, ' ', '11', '2'],
|
||||
[TYPE.WORD, '2.', '11', '2'],
|
||||
[TYPE.NEW_LINE, '\n', '14', '2'],
|
||||
[TYPE.SPACE, ' ', '0', '3'],
|
||||
[TYPE.TAG, '*', '3', '3'],
|
||||
[TYPE.SPACE, ' ', '6', '3'],
|
||||
[TYPE.WORD, 'Item', '14', '2'],
|
||||
[TYPE.SPACE, ' ', '11', '3'],
|
||||
[TYPE.WORD, '3.', '11', '3'],
|
||||
[TYPE.NEW_LINE, '\n', '14', '3'],
|
||||
[TYPE.TAG, '/list', '0', '4'],
|
||||
[TYPE.TAG, 'list', 0, 0, 0, 6],
|
||||
[TYPE.NEW_LINE, '\n', 6, 0],
|
||||
[TYPE.SPACE, ' ', 0, 1],
|
||||
[TYPE.TAG, '*', 3, 1, 10, 13],
|
||||
[TYPE.SPACE, ' ', 6, 1],
|
||||
[TYPE.WORD, 'Item', 7, 1],
|
||||
[TYPE.SPACE, ' ', 11, 1],
|
||||
[TYPE.WORD, '1.', 12, 1],
|
||||
[TYPE.NEW_LINE, '\n', 14, 1],
|
||||
[TYPE.SPACE, ' ', 0, 2],
|
||||
[TYPE.TAG, '*', 3, 2, 25, 28],
|
||||
[TYPE.SPACE, ' ', 6, 2],
|
||||
[TYPE.WORD, 'Item', 7, 2],
|
||||
[TYPE.SPACE, ' ', 11, 2],
|
||||
[TYPE.WORD, '2.', 12, 2],
|
||||
[TYPE.NEW_LINE, '\n', 14, 2],
|
||||
[TYPE.SPACE, ' ', 0, 3],
|
||||
[TYPE.TAG, '*', 3, 3, 40, 43],
|
||||
[TYPE.SPACE, ' ', 6, 3],
|
||||
[TYPE.WORD, 'Item', 7, 3],
|
||||
[TYPE.SPACE, ' ', 11, 3],
|
||||
[TYPE.WORD, '3.', 12, 3],
|
||||
[TYPE.NEW_LINE, '\n', 14, 3],
|
||||
[TYPE.TAG, '/list', 0, 4, 52, 59],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -319,19 +351,19 @@ describe('lexer', () => {
|
||||
const input = '[mytag1 size="15"]Tag1[/mytag1][mytag2 size="16"]Tag2[/mytag2][mytag3]Tag3[/mytag3]';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'mytag1', 0, 0],
|
||||
[TYPE.ATTR_NAME, 'size', 0, 0],
|
||||
[TYPE.ATTR_VALUE, '15', 0, 0],
|
||||
[TYPE.WORD, 'Tag1', 0, 0],
|
||||
[TYPE.TAG, '/mytag1', 0, 0],
|
||||
[TYPE.TAG, 'mytag2', 0, 0],
|
||||
[TYPE.ATTR_NAME, 'size', 0, 0],
|
||||
[TYPE.ATTR_VALUE, '16', 0, 0],
|
||||
[TYPE.WORD, 'Tag2', 0, 0],
|
||||
[TYPE.TAG, '/mytag2', 0, 0],
|
||||
[TYPE.TAG, 'mytag3', 0, 0],
|
||||
[TYPE.WORD, 'Tag3', 0, 0],
|
||||
[TYPE.TAG, '/mytag3', 0, 0],
|
||||
[TYPE.TAG, 'mytag1', 0, 0, 0, 18],
|
||||
[TYPE.ATTR_NAME, 'size', 8, 0],
|
||||
[TYPE.ATTR_VALUE, '15', 13, 0],
|
||||
[TYPE.WORD, 'Tag1', 18, 0],
|
||||
[TYPE.TAG, '/mytag1', 22, 0, 22, 31],
|
||||
[TYPE.TAG, 'mytag2', 31, 0, 31, 49],
|
||||
[TYPE.ATTR_NAME, 'size', 39, 0],
|
||||
[TYPE.ATTR_VALUE, '16', 44, 0],
|
||||
[TYPE.WORD, 'Tag2', 49, 0],
|
||||
[TYPE.TAG, '/mytag2', 53, 0, 53, 62],
|
||||
[TYPE.TAG, 'mytag3', 62, 0, 62, 70],
|
||||
[TYPE.WORD, 'Tag3', 70, 0],
|
||||
[TYPE.TAG, '/mytag3', 74, 0, 74, 83],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -351,51 +383,50 @@ describe('lexer', () => {
|
||||
|
||||
const asserts = [
|
||||
[
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, ']', '0', '0']
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, ']', 1, 0]
|
||||
],
|
||||
[
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, '=]', '0', '0']
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, '=]', 1, 0]
|
||||
],
|
||||
[
|
||||
[TYPE.WORD, '!', '0', '0'],
|
||||
[TYPE.WORD, '[', '1', '0'],
|
||||
[TYPE.WORD, '](image.jpg)', '1', '0'],
|
||||
// [TYPE.WORD, '', '1', '0'],
|
||||
[TYPE.WORD, '!', 0, 0],
|
||||
[TYPE.WORD, '[', 1, 0],
|
||||
[TYPE.WORD, '](image.jpg)', 2, 0],
|
||||
],
|
||||
[
|
||||
[TYPE.WORD, 'x', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '1', '0'],
|
||||
[TYPE.WORD, 'html(', '1', '0'],
|
||||
[TYPE.TAG, 'a. title', '7', '0'],
|
||||
[TYPE.TAG, ', alt', '17', '0'],
|
||||
[TYPE.TAG, ', classes', '24', '0'],
|
||||
[TYPE.WORD, ')', '7', '0'],
|
||||
[TYPE.SPACE, ' ', '36', '0'],
|
||||
[TYPE.WORD, 'x', '36', '0'],
|
||||
[TYPE.WORD, 'x', 0, 0],
|
||||
[TYPE.SPACE, ' ', 1, 0],
|
||||
[TYPE.WORD, 'html(', 2, 0],
|
||||
[TYPE.TAG, 'a. title', 7, 0, 7, 17],
|
||||
[TYPE.TAG, ', alt', 17, 0, 17, 24],
|
||||
[TYPE.TAG, ', classes', 24, 0, 24, 35],
|
||||
[TYPE.WORD, ')', 35, 0],
|
||||
[TYPE.SPACE, ' ', 36, 0],
|
||||
[TYPE.WORD, 'x', 37, 0],
|
||||
],
|
||||
[
|
||||
[TYPE.TAG, '/y', '0', '0']
|
||||
[TYPE.TAG, '/y', 0, 0, 0, 4]
|
||||
],
|
||||
[
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, 'sc', '0', '0']
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, 'sc', 1, 0]
|
||||
],
|
||||
[
|
||||
// [sc /
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, 'sc', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, '/', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.TAG, '/sc', '0', '0']
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, 'sc', 1, 0],
|
||||
[TYPE.SPACE, ' ', 3, 0],
|
||||
[TYPE.WORD, '/', 4, 0],
|
||||
[TYPE.SPACE, ' ', 5, 0],
|
||||
[TYPE.TAG, '/sc', 6, 0, 6, 11]
|
||||
],
|
||||
[
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, 'sc', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'arg="val', '0', '0'],
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, 'sc', 1, 0],
|
||||
[TYPE.SPACE, ' ', 3, 0],
|
||||
[TYPE.WORD, 'arg="val', 4, 0],
|
||||
]
|
||||
];
|
||||
|
||||
@@ -411,14 +442,14 @@ describe('lexer', () => {
|
||||
const input = `[Finger Part A [Finger]`;
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, 'Finger', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'Part', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'A', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.TAG, 'Finger', '0', '0']
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, 'Finger', 1, 0],
|
||||
[TYPE.SPACE, ' ', 7, 0],
|
||||
[TYPE.WORD, 'Part', 8, 0],
|
||||
[TYPE.SPACE, ' ', 12, 0],
|
||||
[TYPE.WORD, 'A', 13, 0],
|
||||
[TYPE.SPACE, ' ', 14, 0],
|
||||
[TYPE.TAG, 'Finger', 15, 0, 15, 23]
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -428,12 +459,12 @@ describe('lexer', () => {
|
||||
const input = '[Finger Part A';
|
||||
const tokens = tokenize(input);
|
||||
const output = [
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, 'Finger', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'Part', '0', '0'],
|
||||
[TYPE.SPACE, ' ', '0', '0'],
|
||||
[TYPE.WORD, 'A', '0', '0'],
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, 'Finger', 1, 0],
|
||||
[TYPE.SPACE, ' ', 7, 0],
|
||||
[TYPE.WORD, 'Part', 8, 0],
|
||||
[TYPE.SPACE, ' ', 12, 0],
|
||||
[TYPE.WORD, 'A', 13, 0],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -444,11 +475,11 @@ describe('lexer', () => {
|
||||
const tokens = tokenizeEscape(input);
|
||||
|
||||
const output = [
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, 'b', '0', '0'],
|
||||
[TYPE.WORD, ']', '0', '0'],
|
||||
[TYPE.WORD, 'test', '0', '0'],
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, 'b', 2, 0],
|
||||
[TYPE.WORD, ']', 3, 0],
|
||||
[TYPE.WORD, 'test', 5, 0],
|
||||
[TYPE.WORD, '[', 9, 0],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -458,67 +489,67 @@ describe('lexer', () => {
|
||||
const input = '\\\\\\[b\\\\\\]test\\\\\\[/b\\\\\\]';
|
||||
const tokens = tokenizeEscape(input);
|
||||
const output = [
|
||||
[TYPE.WORD, '\\', '0', '0'],
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, 'b', '0', '0'],
|
||||
[TYPE.WORD, '\\', '0', '0'],
|
||||
[TYPE.WORD, ']', '0', '0'],
|
||||
[TYPE.WORD, 'test', '0', '0'],
|
||||
[TYPE.WORD, '\\', '0', '0'],
|
||||
[TYPE.WORD, '[', '0', '0'],
|
||||
[TYPE.WORD, '/b', '0', '0'],
|
||||
[TYPE.WORD, '\\', '0', '0'],
|
||||
[TYPE.WORD, ']', '0', '0'],
|
||||
[TYPE.WORD, '\\', 0, 0],
|
||||
[TYPE.WORD, '[', 2, 0],
|
||||
[TYPE.WORD, 'b', 4, 0],
|
||||
[TYPE.WORD, '\\', 5, 0],
|
||||
[TYPE.WORD, ']', 7, 0],
|
||||
[TYPE.WORD, 'test', 9, 0],
|
||||
[TYPE.WORD, '\\', 13, 0],
|
||||
[TYPE.WORD, '[', 15, 0],
|
||||
[TYPE.WORD, '/b', 17, 0],
|
||||
[TYPE.WORD, '\\', 19, 0],
|
||||
[TYPE.WORD, ']', 21, 0],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
});
|
||||
|
||||
test('context free tag [code]', () => {
|
||||
const input = '[code] [b]some string[/b][/code]'
|
||||
const input = '[code] [b]some string[/b][/code]';
|
||||
const tokens = tokenizeContextFreeTags(input, ['code']);
|
||||
const output = [
|
||||
[TYPE.TAG, 'code', 0, 0],
|
||||
[TYPE.SPACE, ' ', 0, 0],
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, 'b]some', 0, 0],
|
||||
[TYPE.SPACE, ' ', 0, 0],
|
||||
[TYPE.WORD, 'string', 0, 0],
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, '/b]', 0, 0],
|
||||
[TYPE.TAG, '/code', 0, 0],
|
||||
]
|
||||
[TYPE.TAG, 'code', 0, 0, 0, 6],
|
||||
[TYPE.SPACE, ' ', 6, 0],
|
||||
[TYPE.WORD, '[', 7, 0],
|
||||
[TYPE.WORD, 'b]some', 8, 0],
|
||||
[TYPE.SPACE, ' ', 14, 0],
|
||||
[TYPE.WORD, 'string', 15, 0],
|
||||
[TYPE.WORD, '[', 21, 0],
|
||||
[TYPE.WORD, '/b]', 22, 0],
|
||||
[TYPE.TAG, '/code', 25, 0, 25, 32],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
})
|
||||
});
|
||||
|
||||
test('context free tag case insensitive [CODE]', () => {
|
||||
const input = '[CODE] [b]some string[/b][/CODE]'
|
||||
const input = '[CODE] [b]some string[/b][/CODE]';
|
||||
const tokens = tokenizeContextFreeTags(input, ['code']);
|
||||
const output = [
|
||||
[TYPE.TAG, 'CODE', 0, 0],
|
||||
[TYPE.SPACE, ' ', 0, 0],
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, 'b]some', 0, 0],
|
||||
[TYPE.SPACE, ' ', 0, 0],
|
||||
[TYPE.WORD, 'string', 0, 0],
|
||||
[TYPE.WORD, '[', 0, 0],
|
||||
[TYPE.WORD, '/b]', 0, 0],
|
||||
[TYPE.TAG, '/CODE', 0, 0],
|
||||
]
|
||||
[TYPE.TAG, 'CODE', 0, 0, 0, 6],
|
||||
[TYPE.SPACE, ' ', 6, 0],
|
||||
[TYPE.WORD, '[', 7, 0],
|
||||
[TYPE.WORD, 'b]some', 8, 0],
|
||||
[TYPE.SPACE, ' ', 14, 0],
|
||||
[TYPE.WORD, 'string', 15, 0],
|
||||
[TYPE.WORD, '[', 21, 0],
|
||||
[TYPE.WORD, '/b]', 22, 0],
|
||||
[TYPE.TAG, '/CODE', 25, 0, 25, 32],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
})
|
||||
});
|
||||
|
||||
test('bad closed tag with escaped backslash', () => {
|
||||
const input = `[b]test[\\b]`;
|
||||
const tokens = tokenizeEscape(input);
|
||||
const output = [
|
||||
[TYPE.TAG, 'b', '0', '3'],
|
||||
[TYPE.WORD, 'test', '0', '7'],
|
||||
[TYPE.WORD, '[', '0', '8'],
|
||||
[TYPE.WORD, '\\', '0', '9'],
|
||||
[TYPE.WORD, 'b]', '0', '11'],
|
||||
[TYPE.TAG, 'b', 0, 0, 0, 3],
|
||||
[TYPE.WORD, 'test', 3, 0],
|
||||
[TYPE.WORD, '[', 7, 0],
|
||||
[TYPE.WORD, '\\', 8, 0],
|
||||
[TYPE.WORD, 'b]', 9, 0],
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -531,17 +562,17 @@ describe('lexer', () => {
|
||||
const content = `<button id="test0" class="value0" title="value1">class="value0" title="value1"</button>`;
|
||||
const tokens = tokenizeHTML(content);
|
||||
const output = [
|
||||
[TYPE.TAG, 'button', 2, 0],
|
||||
[TYPE.ATTR_NAME, 'id', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'test0', 2, 0],
|
||||
[TYPE.ATTR_NAME, 'class', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'value0', 2, 0],
|
||||
[TYPE.ATTR_NAME, 'title', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'value1', 2, 0],
|
||||
[TYPE.WORD, "class=\"value0\"", 2, 0],
|
||||
[TYPE.SPACE, " ", 2, 0],
|
||||
[TYPE.WORD, "title=\"value1\"", 2, 0],
|
||||
[TYPE.TAG, '/button', 2, 0]
|
||||
[TYPE.TAG, 'button', 0, 0, 0, 49],
|
||||
[TYPE.ATTR_NAME, 'id', 8, 0],
|
||||
[TYPE.ATTR_VALUE, 'test0', 11, 0],
|
||||
[TYPE.ATTR_NAME, 'class', 19, 0],
|
||||
[TYPE.ATTR_VALUE, 'value0', 25, 0],
|
||||
[TYPE.ATTR_NAME, 'title', 34, 0],
|
||||
[TYPE.ATTR_VALUE, 'value1', 40, 0],
|
||||
[TYPE.WORD, "class=\"value0\"", 49, 0],
|
||||
[TYPE.SPACE, " ", 63, 0],
|
||||
[TYPE.WORD, "title=\"value1\"", 64, 0],
|
||||
[TYPE.TAG, '/button', 78, 0, 78, 87]
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -551,16 +582,16 @@ describe('lexer', () => {
|
||||
const content = `<button id="test1" class=value2 disabled>class=value2 disabled</button>`;
|
||||
const tokens = tokenizeHTML(content);
|
||||
const output = [
|
||||
[TYPE.TAG, 'button', 2, 0],
|
||||
[TYPE.ATTR_NAME, 'id', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'test1', 2, 0],
|
||||
[TYPE.ATTR_NAME, 'class', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'value2', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'disabled', 2, 0],
|
||||
[TYPE.WORD, "class=value2", 2, 0],
|
||||
[TYPE.SPACE, " ", 2, 0],
|
||||
[TYPE.WORD, "disabled", 2, 0],
|
||||
[TYPE.TAG, '/button', 2, 0]
|
||||
[TYPE.TAG, 'button', 0, 0, 0, 41],
|
||||
[TYPE.ATTR_NAME, 'id', 8, 0],
|
||||
[TYPE.ATTR_VALUE, 'test1', 11, 0],
|
||||
[TYPE.ATTR_NAME, 'class', 19, 0],
|
||||
[TYPE.ATTR_VALUE, 'value2', 25, 0],
|
||||
[TYPE.ATTR_VALUE, 'disabled', 32, 0],
|
||||
[TYPE.WORD, "class=value2", 41, 0],
|
||||
[TYPE.SPACE, " ", 54, 0],
|
||||
[TYPE.WORD, "disabled", 55, 0],
|
||||
[TYPE.TAG, '/button', 63, 0, 62, 71]
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -570,15 +601,15 @@ describe('lexer', () => {
|
||||
const content = `<button id="test2" class="value4"title="value5">class="value4"title="value5"</button>`;
|
||||
const tokens = tokenizeHTML(content);
|
||||
const output = [
|
||||
[TYPE.TAG, 'button', 2, 0],
|
||||
[TYPE.ATTR_NAME, 'id', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'test2', 2, 0],
|
||||
[TYPE.ATTR_NAME, 'class', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'value4', 2, 0],
|
||||
[TYPE.ATTR_NAME, 'title', 2, 0],
|
||||
[TYPE.ATTR_VALUE, 'value5', 2, 0],
|
||||
[TYPE.WORD, "class=\"value4\"title=\"value5\"", 2, 0],
|
||||
[TYPE.TAG, '/button', 2, 0]
|
||||
[TYPE.TAG, 'button', 0, 0, 0, 48],
|
||||
[TYPE.ATTR_NAME, 'id', 8, 0],
|
||||
[TYPE.ATTR_VALUE, 'test2', 11, 0],
|
||||
[TYPE.ATTR_NAME, 'class', 19, 0],
|
||||
[TYPE.ATTR_VALUE, 'value4', 25, 0],
|
||||
[TYPE.ATTR_NAME, 'title', 34, 0],
|
||||
[TYPE.ATTR_VALUE, 'value5', 39, 0],
|
||||
[TYPE.WORD, "class=\"value4\"title=\"value5\"", 48, 0],
|
||||
[TYPE.TAG, '/button', 76, 0, 76, 85]
|
||||
];
|
||||
|
||||
expect(tokens).toBeMantchOutput(output);
|
||||
@@ -601,7 +632,7 @@ input{padding:0px;margin:0px;font-size:9pt}
|
||||
input.medium{width:100px;height:18px}
|
||||
input.buttonred{cursor:hand;font-family:verdana;background:#d12124;color:#fff;height:1.4em;font-weight:bold;font-size:9pt;padding:0px 2px;margin:0px;border:0px none #000}
|
||||
-->
|
||||
</style>`
|
||||
</style>`;
|
||||
const tokens = tokenizeHTML(content);
|
||||
expect(tokens).toBeMantchOutput([]);
|
||||
});
|
||||
@@ -615,6 +646,6 @@ input.buttonred{cursor:hand;font-family:verdana;background:#d12124;color:#fff;he
|
||||
</script>`;
|
||||
const tokens = tokenizeHTML(content);
|
||||
expect(tokens).toBeMantchOutput([]);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { parse } from '../src'
|
||||
import type { TagNodeTree } from "@bbob/plugin-helper";
|
||||
import { parse } from '../src';
|
||||
import type { TagNode, TagNodeTree } from "@bbob/types";
|
||||
|
||||
describe('Parser', () => {
|
||||
const expectOutput = (ast: TagNodeTree, output: Partial<TagNodeTree>) => {
|
||||
expect(ast).toBeInstanceOf(Array);
|
||||
expect(ast).toEqual(output);
|
||||
expect(ast).toMatchObject(output as {} | TagNode[]);
|
||||
};
|
||||
|
||||
test('parse paired tags tokens', () => {
|
||||
@@ -20,6 +20,14 @@ describe('Parser', () => {
|
||||
' ',
|
||||
'Bar',
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 17,
|
||||
},
|
||||
end: {
|
||||
from: 24,
|
||||
to: 31,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -37,6 +45,14 @@ describe('Parser', () => {
|
||||
' ',
|
||||
'Bar',
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 5,
|
||||
},
|
||||
end: {
|
||||
from: 12,
|
||||
to: 18,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -60,6 +76,14 @@ describe('Parser', () => {
|
||||
'[Bar]',
|
||||
' '
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 15,
|
||||
},
|
||||
end: {
|
||||
from: 25,
|
||||
to: 30,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -78,7 +102,7 @@ describe('Parser', () => {
|
||||
'[blah foo="bar"]',
|
||||
'world',
|
||||
'[/blah]'
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
test('parse only allowed tags with named param', () => {
|
||||
@@ -93,7 +117,7 @@ describe('Parser', () => {
|
||||
'[blah="bar"]',
|
||||
'world',
|
||||
'[/blah]'
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
test('parse only allowed tags inside disabled tags', () => {
|
||||
@@ -107,6 +131,14 @@ describe('Parser', () => {
|
||||
tag: 'ch',
|
||||
attrs: {},
|
||||
content: ['E'],
|
||||
start: {
|
||||
from: 7,
|
||||
to: 11,
|
||||
},
|
||||
end: {
|
||||
from: 12,
|
||||
to: 17,
|
||||
},
|
||||
},
|
||||
'\n',
|
||||
'A',
|
||||
@@ -126,6 +158,14 @@ describe('Parser', () => {
|
||||
tag: 'ch',
|
||||
attrs: {},
|
||||
content: ['A'],
|
||||
start: {
|
||||
from: 81,
|
||||
to: 85,
|
||||
},
|
||||
end: {
|
||||
from: 86,
|
||||
to: 91,
|
||||
},
|
||||
},
|
||||
'\n',
|
||||
'All',
|
||||
@@ -159,12 +199,20 @@ describe('Parser', () => {
|
||||
'[Bar]',
|
||||
' '
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 15,
|
||||
},
|
||||
end: {
|
||||
from: 25,
|
||||
to: 30,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
expectOutput(ast, output);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('contextFreeTags', () => {
|
||||
test('context free tag [code]', () => {
|
||||
@@ -176,20 +224,28 @@ describe('Parser', () => {
|
||||
tag: 'code',
|
||||
attrs: {},
|
||||
content: [
|
||||
' ',
|
||||
'[',
|
||||
'b]some',
|
||||
' ',
|
||||
'string',
|
||||
'[',
|
||||
'/b]'
|
||||
]
|
||||
' ',
|
||||
'[',
|
||||
'b]some',
|
||||
' ',
|
||||
'string',
|
||||
'[',
|
||||
'/b]'
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 6,
|
||||
},
|
||||
end: {
|
||||
from: 25,
|
||||
to: 32,
|
||||
},
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
expectOutput(ast, output);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
test('parse inconsistent tags', () => {
|
||||
const ast = parse('[h1 name=value]Foo [Bar] /h1]');
|
||||
@@ -199,14 +255,22 @@ describe('Parser', () => {
|
||||
name: 'value'
|
||||
},
|
||||
tag: 'h1',
|
||||
content: []
|
||||
content: [],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 15,
|
||||
},
|
||||
},
|
||||
'Foo',
|
||||
' ',
|
||||
{
|
||||
tag: 'bar',
|
||||
attrs: {},
|
||||
content: []
|
||||
content: [],
|
||||
start: {
|
||||
from: 19,
|
||||
to: 24,
|
||||
},
|
||||
},
|
||||
' ',
|
||||
'/h1]',
|
||||
@@ -224,6 +288,14 @@ describe('Parser', () => {
|
||||
'https://github.com/jilizart/bbob': 'https://github.com/jilizart/bbob',
|
||||
},
|
||||
content: ['BBob'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 38,
|
||||
},
|
||||
end: {
|
||||
from: 42,
|
||||
to: 48,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -241,6 +313,14 @@ describe('Parser', () => {
|
||||
text: 'Foo Bar',
|
||||
},
|
||||
content: ['Text'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 64,
|
||||
},
|
||||
end: {
|
||||
from: 68,
|
||||
to: 74,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -256,6 +336,10 @@ describe('Parser', () => {
|
||||
'https://github.com/jilizart/bbob': 'https://github.com/jilizart/bbob',
|
||||
},
|
||||
content: [],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 38,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -279,6 +363,14 @@ describe('Parser', () => {
|
||||
size: '15',
|
||||
},
|
||||
content: ['Tag1'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 18,
|
||||
},
|
||||
end: {
|
||||
from: 22,
|
||||
to: 31,
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'mytag2',
|
||||
@@ -286,11 +378,27 @@ describe('Parser', () => {
|
||||
size: '16',
|
||||
},
|
||||
content: ['Tag2'],
|
||||
start: {
|
||||
from: 31,
|
||||
to: 49,
|
||||
},
|
||||
end: {
|
||||
from: 53,
|
||||
to: 62,
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'mytag3',
|
||||
attrs: {},
|
||||
content: ['Tag3'],
|
||||
start: {
|
||||
from: 62,
|
||||
to: 70,
|
||||
},
|
||||
end: {
|
||||
from: 74,
|
||||
to: 83,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -306,6 +414,14 @@ describe('Parser', () => {
|
||||
tag: 'b',
|
||||
attrs: {},
|
||||
content: ['hello'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 17,
|
||||
},
|
||||
end: {
|
||||
from: 24,
|
||||
to: 31,
|
||||
},
|
||||
},
|
||||
' ',
|
||||
{
|
||||
@@ -314,6 +430,14 @@ describe('Parser', () => {
|
||||
disabled: 'disabled',
|
||||
},
|
||||
content: ['world'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 17,
|
||||
},
|
||||
end: {
|
||||
from: 24,
|
||||
to: 31,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -328,10 +452,120 @@ describe('Parser', () => {
|
||||
'https://github.com/JiLiZART/bbob/search?q=any&unscoped_q=any': 'https://github.com/JiLiZART/bbob/search?q=any&unscoped_q=any',
|
||||
},
|
||||
content: ['GET'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 66,
|
||||
},
|
||||
end: {
|
||||
from: 69,
|
||||
to: 75,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('parse triple nested tags', () => {
|
||||
const ast = parse(`this is outside [spoiler title="name with
|
||||
multiline
|
||||
attr value"] this is a spoiler
|
||||
[b]this is bold [i]this is bold and italic[/i] this is bold again[/b]
|
||||
[/spoiler]this is outside again`);
|
||||
expectOutput(ast, [
|
||||
"this",
|
||||
" ",
|
||||
"is",
|
||||
" ",
|
||||
"outside",
|
||||
" ",
|
||||
{
|
||||
attrs: {
|
||||
"title": "name with\n multiline\n attr value",
|
||||
},
|
||||
content: [
|
||||
" ",
|
||||
"this",
|
||||
" ",
|
||||
"is",
|
||||
" ",
|
||||
"a",
|
||||
" ",
|
||||
"spoiler",
|
||||
"\n",
|
||||
" ",
|
||||
{
|
||||
attrs: {},
|
||||
content: [
|
||||
"this",
|
||||
" ",
|
||||
"is",
|
||||
" ",
|
||||
"bold",
|
||||
" ",
|
||||
{
|
||||
attrs: {},
|
||||
content: [
|
||||
"this",
|
||||
" ",
|
||||
"is",
|
||||
" ",
|
||||
"bold",
|
||||
" ",
|
||||
"and",
|
||||
" ",
|
||||
"italic",
|
||||
],
|
||||
end: {
|
||||
to: 147,
|
||||
from: 143,
|
||||
},
|
||||
start: {
|
||||
"to": 120,
|
||||
"from": 117,
|
||||
},
|
||||
"tag": "i",
|
||||
},
|
||||
" ",
|
||||
"this",
|
||||
" ",
|
||||
"is",
|
||||
" ",
|
||||
"bold",
|
||||
" ",
|
||||
"again",
|
||||
],
|
||||
end: {
|
||||
"to": 170,
|
||||
"from": 166,
|
||||
},
|
||||
start: {
|
||||
"to": 104,
|
||||
"from": 101,
|
||||
},
|
||||
tag: "b",
|
||||
},
|
||||
"\n",
|
||||
" ",
|
||||
],
|
||||
end: {
|
||||
"to": 187,
|
||||
"from": 177,
|
||||
},
|
||||
start: {
|
||||
"to": 76,
|
||||
"from": 16,
|
||||
},
|
||||
tag: "spoiler",
|
||||
},
|
||||
"this",
|
||||
" ",
|
||||
"is",
|
||||
" ",
|
||||
"outside",
|
||||
" ",
|
||||
"again",
|
||||
]);
|
||||
});
|
||||
|
||||
test('parse tag with camelCase params', () => {
|
||||
const ast = parse(`[url href="/groups/123/" isNowrap=true isTextOverflow=true state=primary]
|
||||
[avatar href="/avatar/4/3/b/1606.jpg@20x20?cache=1561462725&bgclr=ffffff" size=xs][/avatar]
|
||||
@@ -355,7 +589,15 @@ describe('Parser', () => {
|
||||
href: '/avatar/4/3/b/1606.jpg@20x20?cache=1561462725&bgclr=ffffff',
|
||||
size: 'xs'
|
||||
},
|
||||
content: []
|
||||
content: [],
|
||||
start: {
|
||||
from: 82,
|
||||
to: 164,
|
||||
},
|
||||
end: {
|
||||
from: 164,
|
||||
to: 173,
|
||||
},
|
||||
},
|
||||
'\n',
|
||||
' ',
|
||||
@@ -365,6 +607,14 @@ describe('Parser', () => {
|
||||
' ',
|
||||
'Go',
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 73,
|
||||
},
|
||||
end: {
|
||||
from: 196,
|
||||
to: 202,
|
||||
},
|
||||
},
|
||||
' ',
|
||||
]);
|
||||
@@ -380,6 +630,14 @@ describe('Parser', () => {
|
||||
href: 'https://docs.google.com/spreadsheets/d/1W9VPUESF_NkbSa_HtRFrQNl0nYo8vPCxJFy7jD3Tpio/edit#gid=0',
|
||||
},
|
||||
content: ['Docs'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 105,
|
||||
},
|
||||
end: {
|
||||
from: 109,
|
||||
to: 115,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -389,56 +647,146 @@ describe('Parser', () => {
|
||||
[quote]xxxsdfasdf
|
||||
sdfasdfasdf
|
||||
|
||||
[url=xxx]xxx[/url]`
|
||||
[url=xxx]xxx[/url]`;
|
||||
|
||||
expectOutput(
|
||||
parse(str),
|
||||
[
|
||||
{ tag: 'quote', attrs: {}, content: ['some'] },
|
||||
{ tag: 'color', attrs: { red: 'red' }, content: ['test'] },
|
||||
'\n',
|
||||
'[quote]',
|
||||
'xxxsdfasdf',
|
||||
'\n',
|
||||
'sdfasdfasdf',
|
||||
'\n',
|
||||
'\n',
|
||||
{ tag: 'url', attrs: { xxx: 'xxx' }, content: ['xxx'] }
|
||||
]
|
||||
)
|
||||
})
|
||||
parse(str),
|
||||
[
|
||||
{
|
||||
tag: 'quote', attrs: {}, content: ['some'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 7,
|
||||
},
|
||||
end: {
|
||||
from: 11,
|
||||
to: 19,
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'color', attrs: { red: 'red' }, content: ['test'],
|
||||
start: {
|
||||
from: 19,
|
||||
to: 30,
|
||||
},
|
||||
end: {
|
||||
from: 34,
|
||||
to: 42,
|
||||
},
|
||||
},
|
||||
'\n',
|
||||
'[quote]',
|
||||
'xxxsdfasdf',
|
||||
'\n',
|
||||
'sdfasdfasdf',
|
||||
'\n',
|
||||
'\n',
|
||||
{
|
||||
tag: 'url', attrs: { xxx: 'xxx' }, content: ['xxx'],
|
||||
start: {
|
||||
from: 74,
|
||||
to: 83,
|
||||
},
|
||||
end: {
|
||||
from: 86,
|
||||
to: 92,
|
||||
},
|
||||
}
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('parse with lost closing tag on start', () => {
|
||||
const str = `[quote]xxxsdfasdf[quote]some[/quote][color=red]test[/color]sdfasdfasdf[url=xxx]xxx[/url]`
|
||||
test('parse with lost closing tag on from', () => {
|
||||
const str = `[quote]xxxsdfasdf[quote]some[/quote][color=red]test[/color]sdfasdfasdf[url=xxx]xxx[/url]`;
|
||||
|
||||
expectOutput(
|
||||
parse(str),
|
||||
[
|
||||
'[quote]',
|
||||
'xxxsdfasdf',
|
||||
{ tag: 'quote', attrs: {}, content: ['some'] },
|
||||
{ tag: 'color', attrs: { red: 'red' }, content: ['test'] },
|
||||
'sdfasdfasdf',
|
||||
{ tag: 'url', attrs: { xxx: 'xxx' }, content: ['xxx'] }
|
||||
]
|
||||
)
|
||||
})
|
||||
parse(str),
|
||||
[
|
||||
'[quote]',
|
||||
'xxxsdfasdf',
|
||||
{
|
||||
tag: 'quote', attrs: {}, content: ['some'],
|
||||
start: {
|
||||
from: 17,
|
||||
to: 24,
|
||||
},
|
||||
end: {
|
||||
from: 28,
|
||||
to: 36,
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'color', attrs: { red: 'red' }, content: ['test'],
|
||||
start: {
|
||||
from: 36,
|
||||
to: 47,
|
||||
},
|
||||
end: {
|
||||
from: 51,
|
||||
to: 59,
|
||||
},
|
||||
},
|
||||
'sdfasdfasdf',
|
||||
{
|
||||
tag: 'url', attrs: { xxx: 'xxx' }, content: ['xxx'],
|
||||
start: {
|
||||
from: 70,
|
||||
to: 79,
|
||||
},
|
||||
end: {
|
||||
from: 82,
|
||||
to: 88,
|
||||
},
|
||||
}
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('parse with lost closing tag on end', () => {
|
||||
const str = `[quote]some[/quote][color=red]test[/color]sdfasdfasdf[url=xxx]xxx[/url][quote]xxxsdfasdf`
|
||||
test('parse with lost closing tag on to', () => {
|
||||
const str = `[quote]some[/quote][color=red]test[/color]sdfasdfasdf[url=xxx]xxx[/url][quote]xxxsdfasdf`;
|
||||
|
||||
expectOutput(
|
||||
parse(str),
|
||||
[
|
||||
{ tag: 'quote', attrs: {}, content: ['some'] },
|
||||
{ tag: 'color', attrs: { red: 'red' }, content: ['test'] },
|
||||
'sdfasdfasdf',
|
||||
{ tag: 'url', attrs: { xxx: 'xxx' }, content: ['xxx'] },
|
||||
'[quote]',
|
||||
'xxxsdfasdf',
|
||||
]
|
||||
)
|
||||
})
|
||||
parse(str),
|
||||
[
|
||||
{
|
||||
tag: 'quote', attrs: {}, content: ['some'],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 7,
|
||||
},
|
||||
end: {
|
||||
from: 11,
|
||||
to: 19,
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'color', attrs: { red: 'red' }, content: ['test'],
|
||||
start: {
|
||||
from: 19,
|
||||
to: 30,
|
||||
},
|
||||
end: {
|
||||
from: 34,
|
||||
to: 42,
|
||||
},
|
||||
},
|
||||
'sdfasdfasdf',
|
||||
{
|
||||
tag: 'url', attrs: { xxx: 'xxx' }, content: ['xxx'],
|
||||
start: {
|
||||
from: 53,
|
||||
to: 62,
|
||||
},
|
||||
end: {
|
||||
from: 65,
|
||||
to: 71,
|
||||
},
|
||||
},
|
||||
'[quote]',
|
||||
'xxxsdfasdf',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
describe('html', () => {
|
||||
const parseHTML = (input: string) => parse(input, { openTag: '<', closeTag: '>' });
|
||||
@@ -459,7 +807,15 @@ sdfasdfasdf
|
||||
"class=\"value0\"",
|
||||
" ",
|
||||
"title=\"value1\""
|
||||
]
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 49,
|
||||
},
|
||||
end: {
|
||||
from: 78,
|
||||
to: 87,
|
||||
},
|
||||
}
|
||||
]);
|
||||
});
|
||||
@@ -481,7 +837,15 @@ sdfasdfasdf
|
||||
"class=value2",
|
||||
" ",
|
||||
"disabled"
|
||||
]
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 50,
|
||||
},
|
||||
end: {
|
||||
from: 71,
|
||||
to: 80,
|
||||
},
|
||||
}
|
||||
]);
|
||||
});
|
||||
@@ -500,7 +864,15 @@ sdfasdfasdf
|
||||
},
|
||||
"content": [
|
||||
"class=\"value4\"title=\"value5\""
|
||||
]
|
||||
],
|
||||
start: {
|
||||
from: 0,
|
||||
to: 48,
|
||||
},
|
||||
end: {
|
||||
from: 76,
|
||||
to: 85,
|
||||
},
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NodeContent, TagNodeObject, TagNodeTree } from "@bbob/types";
|
||||
import type { NodeContent, TagNodeObject, TagNodeTree, TagPosition } from "@bbob/types";
|
||||
|
||||
import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char';
|
||||
import {
|
||||
@@ -30,38 +30,40 @@ const getTagAttrs = <AttrValue>(tag: string, params: Record<string, AttrValue>)
|
||||
const renderContent = (content: TagNodeTree, openTag: string, closeTag: string) => {
|
||||
const toString = (node: NodeContent) => {
|
||||
if (isTagNode(node)) {
|
||||
return node.toString({ openTag, closeTag })
|
||||
return node.toString({ openTag, closeTag });
|
||||
}
|
||||
|
||||
return String(node)
|
||||
}
|
||||
return String(node);
|
||||
};
|
||||
|
||||
if (Array.isArray(content)) {
|
||||
return content.reduce<string>((r, node) => {
|
||||
if (node !== null) {
|
||||
return r + toString(node)
|
||||
return r + toString(node);
|
||||
}
|
||||
|
||||
return r
|
||||
}, '')
|
||||
return r;
|
||||
}, '');
|
||||
}
|
||||
|
||||
if (content) {
|
||||
return toString(content)
|
||||
return toString(content);
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export class TagNode<TagValue extends any = any> implements TagNodeObject {
|
||||
public readonly tag: string | TagValue
|
||||
public attrs: Record<string, unknown>
|
||||
public content: TagNodeTree
|
||||
public readonly tag: string | TagValue;
|
||||
public attrs: Record<string, unknown>;
|
||||
public content: TagNodeTree;
|
||||
public start?: TagPosition;
|
||||
public end?: TagPosition;
|
||||
|
||||
constructor(tag: string | TagValue, attrs: Record<string, unknown>, content: TagNodeTree) {
|
||||
this.tag = tag;
|
||||
this.attrs = attrs;
|
||||
this.content = content
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
attr(name: string, value?: unknown) {
|
||||
@@ -76,6 +78,14 @@ export class TagNode<TagValue extends any = any> implements TagNodeObject {
|
||||
return appendToNode(this, value);
|
||||
}
|
||||
|
||||
setStart(value: TagPosition) {
|
||||
this.start = value;
|
||||
}
|
||||
|
||||
setEnd(value: TagPosition) {
|
||||
this.end = value;
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return getNodeLength(this);
|
||||
}
|
||||
@@ -91,25 +101,36 @@ export class TagNode<TagValue extends any = any> implements TagNodeObject {
|
||||
}
|
||||
|
||||
toTagNode() {
|
||||
return new TagNode(String(this.tag).toLowerCase(), this.attrs, this.content);
|
||||
const newNode = new TagNode(String(this.tag).toLowerCase(), this.attrs, this.content);
|
||||
if (this.start) {
|
||||
newNode.setStart(this.start);
|
||||
}
|
||||
if (this.end) {
|
||||
newNode.setEnd(this.end);
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}): string {
|
||||
const content = this.content ? renderContent(this.content, openTag, closeTag) : ''
|
||||
const content = this.content ? renderContent(this.content, openTag, closeTag) : '';
|
||||
const tagStart = this.toTagStart({ openTag, closeTag });
|
||||
|
||||
if (this.content === null || Array.isArray(this.content) && this.content.length === 0) {
|
||||
if (this.content === null || Array.isArray(this.content) && this.content.length === 0) {
|
||||
return tagStart;
|
||||
}
|
||||
|
||||
return `${tagStart}${content}${this.toTagEnd({ openTag, closeTag })}`;
|
||||
}
|
||||
|
||||
static create(tag: string, attrs: Record<string, unknown> = {}, content: TagNodeTree = null) {
|
||||
return new TagNode(tag, attrs, content)
|
||||
static create(tag: string, attrs: Record<string, unknown> = {}, content: TagNodeTree = null, start?: TagPosition) {
|
||||
const node = new TagNode(tag, attrs, content);
|
||||
if (start) {
|
||||
node.setStart(start);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static isOf(node: TagNode, type: string) {
|
||||
return (node.tag === type)
|
||||
return (node.tag === type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TagNode } from '../src'
|
||||
|
||||
describe('@bbob/plugin-helper/TagNode', () => {
|
||||
test('create', () => {
|
||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello'], {from: 0, to: 10});
|
||||
|
||||
expect(tagNode).toBeInstanceOf(TagNode)
|
||||
});
|
||||
@@ -36,12 +36,15 @@ describe('@bbob/plugin-helper/TagNode', () => {
|
||||
});
|
||||
|
||||
test('toTagNode', () => {
|
||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello'], {from: 0, to: 10});
|
||||
tagNode.setEnd({from: 20, to: 27});
|
||||
const newTagNode = tagNode.toTagNode()
|
||||
|
||||
expect(newTagNode !== tagNode).toBe(true);
|
||||
expect(newTagNode.tag).toEqual(tagNode.tag);
|
||||
expect(newTagNode.content).toEqual(tagNode.content);
|
||||
expect(newTagNode.start).toEqual(tagNode.start);
|
||||
expect(newTagNode.end).toEqual(tagNode.end);
|
||||
});
|
||||
|
||||
test('null content', () => {
|
||||
@@ -56,6 +59,20 @@ describe('@bbob/plugin-helper/TagNode', () => {
|
||||
expect(String(tagNode)).toBe('[img]');
|
||||
});
|
||||
|
||||
test('setStart', () => {
|
||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||
tagNode.setStart({from: 0, to: 10});
|
||||
|
||||
expect(tagNode.start).toEqual({from: 0, to: 10});
|
||||
});
|
||||
|
||||
test('setEnd', () => {
|
||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||
tagNode.setEnd({from: 20, to: 27});
|
||||
|
||||
expect(tagNode.end).toEqual({from: 20, to: 27});
|
||||
});
|
||||
|
||||
describe('toString', () => {
|
||||
test('tag with content and params', () => {
|
||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
export type StringNode = string | number
|
||||
export type StringNode = string | number;
|
||||
|
||||
export interface TagNodeObject<TagValue extends any = any> {
|
||||
readonly tag: TagValue
|
||||
attrs?: Record<string, unknown>
|
||||
content?: TagNodeTree<TagValue>
|
||||
readonly tag: TagValue;
|
||||
attrs?: Record<string, unknown>;
|
||||
content?: TagNodeTree<TagValue>;
|
||||
start?: TagPosition;
|
||||
end?: TagPosition;
|
||||
}
|
||||
|
||||
export type NodeContent<TagValue extends any = any> = TagNodeObject<TagValue> | StringNode | null
|
||||
export type NodeContent<TagValue extends any = any> = TagNodeObject<TagValue> | StringNode | null;
|
||||
|
||||
export type PartialNodeContent<TagValue extends any = any> = Partial<TagNodeObject<TagValue>> | StringNode | null
|
||||
export type PartialNodeContent<TagValue extends any = any> = Partial<TagNodeObject<TagValue>> | StringNode | null;
|
||||
|
||||
export type TagNodeTree<TagValue extends any = any> = NodeContent<TagValue> | NodeContent<TagValue>[] | null
|
||||
export type TagNodeTree<TagValue extends any = any> = NodeContent<TagValue> | NodeContent<TagValue>[] | null;
|
||||
|
||||
export type TagPosition = { from: number; to: number; };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TagNodeTree } from "./common";
|
||||
import { TagNodeTree, TagPosition } from "./common";
|
||||
|
||||
export interface ParseError {
|
||||
tagName: string;
|
||||
@@ -9,7 +9,9 @@ export interface ParseError {
|
||||
export interface TagNode {
|
||||
readonly tag: string
|
||||
attrs?: Record<string, unknown>
|
||||
content?: TagNodeTree
|
||||
content?: TagNodeTree,
|
||||
start?: TagPosition;
|
||||
end?: TagPosition;
|
||||
}
|
||||
|
||||
export interface Token<TokenValue = string> {
|
||||
|
||||
Reference in New Issue
Block a user