mirror of
https://github.com/tenrok/BBob.git
synced 2026-05-15 11:59:37 +03:00
convert Parser class to set of function for better compression
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
/packages/**/dist
|
/packages/**/dist
|
||||||
|
/packages/**/test
|
||||||
|
|||||||
@@ -1,202 +0,0 @@
|
|||||||
const {
|
|
||||||
convertTokenToText,
|
|
||||||
getTagName,
|
|
||||||
getTokenColumn,
|
|
||||||
getTokenLine,
|
|
||||||
getTokenValue,
|
|
||||||
isAttrNameToken,
|
|
||||||
isAttrValueToken,
|
|
||||||
isTagStart,
|
|
||||||
isTagToken,
|
|
||||||
isTextToken,
|
|
||||||
isTagEnd,
|
|
||||||
} = require('./Token');
|
|
||||||
|
|
||||||
const Tokenizer = require('./Tokenizer');
|
|
||||||
const TagNode = require('./TagNode');
|
|
||||||
|
|
||||||
const createTagNode = (tag, attrs = {}, content = []) => new TagNode(tag, attrs, content);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
{
|
|
||||||
tag: 'div',
|
|
||||||
attrs: {
|
|
||||||
class: 'foo'
|
|
||||||
},
|
|
||||||
content: ['hello world!']
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
class Parser {
|
|
||||||
constructor(input, options = {}) {
|
|
||||||
this.createTokenizer(input);
|
|
||||||
|
|
||||||
this.options = options;
|
|
||||||
|
|
||||||
this.nodes = [];
|
|
||||||
this.nestedNodes = [];
|
|
||||||
this.tagNodes = [];
|
|
||||||
this.tagNodesAttrName = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
createTokenizer(input) {
|
|
||||||
this.tokenizer = new Tokenizer(input, {
|
|
||||||
onToken: (token) => {
|
|
||||||
this.parseToken(token);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isTagNested(token) {
|
|
||||||
return this.tokenizer.isTokenNested(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {TagNode}
|
|
||||||
*/
|
|
||||||
getTagNode() {
|
|
||||||
if (this.tagNodes.length) {
|
|
||||||
return this.tagNodes[this.tagNodes.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
createTagNode(token) {
|
|
||||||
this.tagNodes.push(createTagNode(getTokenValue(token)));
|
|
||||||
}
|
|
||||||
|
|
||||||
createTagNodeAttrName(token) {
|
|
||||||
this.tagNodesAttrName.push(getTokenValue(token));
|
|
||||||
}
|
|
||||||
|
|
||||||
getTagNodeAttrName() {
|
|
||||||
if (this.tagNodesAttrName.length) {
|
|
||||||
return this.tagNodesAttrName[this.tagNodesAttrName.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getTagNode().tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTagNodeAttrName() {
|
|
||||||
if (this.tagNodesAttrName.length) {
|
|
||||||
this.tagNodesAttrName.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTagNode() {
|
|
||||||
if (this.tagNodes.length) {
|
|
||||||
this.tagNodes.pop();
|
|
||||||
|
|
||||||
this.clearTagNodeAttrName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getNodes() {
|
|
||||||
if (this.nestedNodes.length) {
|
|
||||||
const nestedNode = this.nestedNodes[this.nestedNodes.length - 1];
|
|
||||||
return nestedNode.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
appendNode(tag) {
|
|
||||||
this.getNodes().push(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTagStart(token) {
|
|
||||||
if (isTagStart(token)) {
|
|
||||||
this.createTagNode(token);
|
|
||||||
|
|
||||||
if (this.isTagNested(token)) {
|
|
||||||
this.nestedNodes.push(this.getTagNode());
|
|
||||||
} else {
|
|
||||||
this.appendNode(this.getTagNode());
|
|
||||||
this.clearTagNode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTagEnd(token) {
|
|
||||||
if (isTagEnd(token)) {
|
|
||||||
this.clearTagNode();
|
|
||||||
|
|
||||||
const lastNestedNode = this.nestedNodes.pop();
|
|
||||||
|
|
||||||
if (lastNestedNode) {
|
|
||||||
this.appendNode(lastNestedNode);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn(`Inconsistent tag '${getTokenValue(token)}' on line ${getTokenLine(token)} and column ${getTokenColumn(token)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTagToken(token) {
|
|
||||||
if (isTagToken(token)) {
|
|
||||||
if (this.isAllowedTag(getTagName(token))) {
|
|
||||||
// [tag]
|
|
||||||
this.handleTagStart(token);
|
|
||||||
|
|
||||||
// [/tag]
|
|
||||||
this.handleTagEnd(token);
|
|
||||||
} else {
|
|
||||||
this.appendNode(convertTokenToText(token));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTagNode(token) {
|
|
||||||
const tagNode = this.getTagNode();
|
|
||||||
|
|
||||||
if (tagNode) {
|
|
||||||
if (isAttrNameToken(token)) {
|
|
||||||
this.createTagNodeAttrName(token);
|
|
||||||
tagNode.attr(this.getTagNodeAttrName(), null);
|
|
||||||
} else if (isAttrValueToken(token)) {
|
|
||||||
tagNode.attr(this.getTagNodeAttrName(), getTokenValue(token));
|
|
||||||
this.clearTagNodeAttrName();
|
|
||||||
} else if (isTextToken(token)) {
|
|
||||||
tagNode.append(getTokenValue(token));
|
|
||||||
}
|
|
||||||
} else if (isTextToken(token)) {
|
|
||||||
this.appendNode(getTokenValue(token));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parseToken(token) {
|
|
||||||
this.handleTagToken(token);
|
|
||||||
this.handleTagNode(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
parse() {
|
|
||||||
if (this.tokens) {
|
|
||||||
let token;
|
|
||||||
// eslint-disable-next-line no-cond-assign
|
|
||||||
while (token = this.tokens.shift()) {
|
|
||||||
if (!token) {
|
|
||||||
// eslint-disable-next-line no-continue
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.parseToken(token);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.tokens = this.tokenizer.tokenize();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
isAllowedTag(value) {
|
|
||||||
if (this.options.onlyAllowTags && this.options.onlyAllowTags.length) {
|
|
||||||
return this.options.onlyAllowTags.indexOf(value) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Parser;
|
|
||||||
module.exports.createTagNode = createTagNode;
|
|
||||||
@@ -1,8 +1,243 @@
|
|||||||
const Parser = require('./Parser');
|
const {
|
||||||
|
convertTokenToText,
|
||||||
|
getTagName,
|
||||||
|
getTokenColumn,
|
||||||
|
getTokenLine,
|
||||||
|
getTokenValue,
|
||||||
|
isAttrNameToken,
|
||||||
|
isAttrValueToken,
|
||||||
|
isTagStart,
|
||||||
|
isTagToken,
|
||||||
|
isTextToken,
|
||||||
|
isTagEnd,
|
||||||
|
} = require('./Token');
|
||||||
|
|
||||||
module.exports = function parse(input, options) {
|
const Tokenizer = require('./Tokenizer');
|
||||||
const parser = new Parser(input, options);
|
const TagNode = require('./TagNode');
|
||||||
const ast = parser.parse();
|
|
||||||
|
|
||||||
return ast;
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
let nodes;
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
let nestedNodes;
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
let tagNodes;
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
let tagNodesAttrName;
|
||||||
|
|
||||||
|
let options = {};
|
||||||
|
let tokenizer = null;
|
||||||
|
let tokens = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param tag
|
||||||
|
* @param attrs
|
||||||
|
* @param content
|
||||||
|
*/
|
||||||
|
const newTagNode = (tag, attrs = {}, content = []) => new TagNode(tag, attrs, content);
|
||||||
|
|
||||||
|
const createTokenizer = (input, onToken) => new Tokenizer(input, { onToken });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param token
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
const isTagNested = token => tokenizer.isTokenNested(token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @return {TagNode}
|
||||||
|
*/
|
||||||
|
const getTagNode = () => (tagNodes.length ? tagNodes[tagNodes.length - 1] : null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
const createTagNode = token => tagNodes.push(newTagNode(getTokenValue(token)));
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
const createTagNodeAttrName = token => tagNodesAttrName.push(getTokenValue(token));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
const getTagNodeAttrName = () =>
|
||||||
|
(tagNodesAttrName.length ? tagNodesAttrName[tagNodesAttrName.length - 1] : getTagNode().tag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
const clearTagNodeAttrName = () => {
|
||||||
|
if (tagNodesAttrName.length) {
|
||||||
|
tagNodesAttrName.pop();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
const clearTagNode = () => {
|
||||||
|
if (tagNodes.length) {
|
||||||
|
tagNodes.pop();
|
||||||
|
|
||||||
|
clearTagNodeAttrName();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
const getNodes = () => {
|
||||||
|
if (nestedNodes.length) {
|
||||||
|
const nestedNode = nestedNodes[nestedNodes.length - 1];
|
||||||
|
return nestedNode.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param tag
|
||||||
|
*/
|
||||||
|
const appendNode = (tag) => {
|
||||||
|
getNodes().push(tag);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param value
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isAllowedTag = (value) => {
|
||||||
|
if (options.onlyAllowTags && options.onlyAllowTags.length) {
|
||||||
|
return options.onlyAllowTags.indexOf(value) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param token
|
||||||
|
*/
|
||||||
|
const handleTagStart = (token) => {
|
||||||
|
if (isTagStart(token)) {
|
||||||
|
createTagNode(token);
|
||||||
|
|
||||||
|
if (isTagNested(token)) {
|
||||||
|
nestedNodes.push(getTagNode());
|
||||||
|
} else {
|
||||||
|
appendNode(getTagNode());
|
||||||
|
clearTagNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param token
|
||||||
|
*/
|
||||||
|
const handleTagEnd = (token) => {
|
||||||
|
if (isTagEnd(token)) {
|
||||||
|
clearTagNode();
|
||||||
|
|
||||||
|
const lastNestedNode = nestedNodes.pop();
|
||||||
|
|
||||||
|
if (lastNestedNode) {
|
||||||
|
appendNode(lastNestedNode);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(`Inconsistent tag '${getTokenValue(token)}' on line ${getTokenLine(token)} and column ${getTokenColumn(token)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param token
|
||||||
|
*/
|
||||||
|
const handleTagToken = (token) => {
|
||||||
|
if (isTagToken(token)) {
|
||||||
|
if (isAllowedTag(getTagName(token))) {
|
||||||
|
// [tag]
|
||||||
|
handleTagStart(token);
|
||||||
|
|
||||||
|
// [/tag]
|
||||||
|
handleTagEnd(token);
|
||||||
|
} else {
|
||||||
|
appendNode(convertTokenToText(token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param token
|
||||||
|
*/
|
||||||
|
const handleTagNode = (token) => {
|
||||||
|
const tagNode = getTagNode();
|
||||||
|
|
||||||
|
if (tagNode) {
|
||||||
|
if (isAttrNameToken(token)) {
|
||||||
|
createTagNodeAttrName(token);
|
||||||
|
tagNode.attr(getTagNodeAttrName(), null);
|
||||||
|
} else if (isAttrValueToken(token)) {
|
||||||
|
tagNode.attr(getTagNodeAttrName(), getTokenValue(token));
|
||||||
|
clearTagNodeAttrName();
|
||||||
|
} else if (isTextToken(token)) {
|
||||||
|
tagNode.append(getTokenValue(token));
|
||||||
|
}
|
||||||
|
} else if (isTextToken(token)) {
|
||||||
|
appendNode(getTokenValue(token));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param token
|
||||||
|
*/
|
||||||
|
const parseToken = (token) => {
|
||||||
|
handleTagToken(token);
|
||||||
|
handleTagNode(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
const parse = (input, opts = {}) => {
|
||||||
|
tokenizer = createTokenizer(input, parseToken);
|
||||||
|
options = opts;
|
||||||
|
|
||||||
|
nodes = [];
|
||||||
|
nestedNodes = [];
|
||||||
|
tagNodes = [];
|
||||||
|
tagNodesAttrName = [];
|
||||||
|
|
||||||
|
tokens = tokenizer.tokenize();
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = parse;
|
||||||
|
module.exports.createTagNode = createTagNode;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
"html"
|
"html"
|
||||||
],
|
],
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"browser": "dist/umd.js",
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/JiLiZART/bbob.git"
|
"url": "git://github.com/JiLiZART/bbob.git"
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
const Parser = require('../lib/Parser');
|
|
||||||
|
|
||||||
const parse = input => (new Parser(input).parse());
|
|
||||||
|
|
||||||
describe('Parser', () => {
|
|
||||||
test('parse paired tags tokens', () => {
|
|
||||||
const ast = parse('[best name=value]Foo Bar[/best]');
|
|
||||||
|
|
||||||
expect(ast).toBeInstanceOf(Array);
|
|
||||||
expect(ast).toEqual([
|
|
||||||
{
|
|
||||||
tag: 'best',
|
|
||||||
attrs: {
|
|
||||||
name: 'value',
|
|
||||||
},
|
|
||||||
content: [
|
|
||||||
'Foo',
|
|
||||||
' ',
|
|
||||||
'Bar',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parse tag with value param', () => {
|
|
||||||
const ast = parse('[url=https://github.com/jilizart/bbob]BBob[/url]');
|
|
||||||
|
|
||||||
expect(ast).toBeInstanceOf(Array);
|
|
||||||
expect(ast).toEqual([
|
|
||||||
{
|
|
||||||
tag: 'url',
|
|
||||||
attrs: {
|
|
||||||
url: 'https://github.com/jilizart/bbob',
|
|
||||||
},
|
|
||||||
content: ['BBob'],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parse tag with quoted param with spaces', () => {
|
|
||||||
const ast = parse('[url href=https://ru.wikipedia.org target=_blank text="Foo Bar"]Text[/url]');
|
|
||||||
|
|
||||||
expect(ast).toBeInstanceOf(Array);
|
|
||||||
expect(ast).toEqual([
|
|
||||||
{
|
|
||||||
tag: 'url',
|
|
||||||
attrs: {
|
|
||||||
href: 'https://ru.wikipedia.org',
|
|
||||||
target: '_blank',
|
|
||||||
text: 'Foo Bar',
|
|
||||||
},
|
|
||||||
content: ['Text'],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,33 +1,54 @@
|
|||||||
const parse = require('../lib/index');
|
const parse = require('../lib/parse');
|
||||||
|
|
||||||
describe('parse', () => {
|
describe('Parser', () => {
|
||||||
test('tag with spaces', () => {
|
test('parse paired tags tokens', () => {
|
||||||
const ast = parse('[Verse 2]');
|
const ast = parse('[best name=value]Foo Bar[/best]');
|
||||||
|
|
||||||
expect(ast).toEqual([{ tag: 'Verse 2', attrs: {}, content: [] }]);
|
expect(ast).toBeInstanceOf(Array);
|
||||||
|
expect(ast).toEqual([
|
||||||
|
{
|
||||||
|
tag: 'best',
|
||||||
|
attrs: {
|
||||||
|
name: 'value',
|
||||||
|
},
|
||||||
|
content: [
|
||||||
|
'Foo',
|
||||||
|
' ',
|
||||||
|
'Bar',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// test("pass invalid tags", () => {
|
test('parse tag with value param', () => {
|
||||||
// const inputs = [
|
const ast = parse('[url=https://github.com/jilizart/bbob]BBob[/url]');
|
||||||
// '[]',
|
|
||||||
// '',
|
expect(ast).toBeInstanceOf(Array);
|
||||||
// 'x html([a. title][, alt][, classes]) x',
|
expect(ast).toEqual([
|
||||||
// '[/y]',
|
{
|
||||||
// '[sc',
|
tag: 'url',
|
||||||
// '[sc / [/sc]',
|
attrs: {
|
||||||
// '[sc arg="val',
|
url: 'https://github.com/jilizart/bbob',
|
||||||
// ];
|
},
|
||||||
//
|
content: ['BBob'],
|
||||||
// const ast1 = parse(inputs[0]);
|
},
|
||||||
//
|
]);
|
||||||
//
|
});
|
||||||
//
|
|
||||||
// console.log('ast1', ast1);
|
test('parse tag with quoted param with spaces', () => {
|
||||||
//
|
const ast = parse('[url href=https://ru.wikipedia.org target=_blank text="Foo Bar"]Text[/url]');
|
||||||
//
|
|
||||||
//
|
expect(ast).toBeInstanceOf(Array);
|
||||||
// expect(ast1).toEqual([
|
expect(ast).toEqual([
|
||||||
//
|
{
|
||||||
// ]);
|
tag: 'url',
|
||||||
// })
|
attrs: {
|
||||||
|
href: 'https://ru.wikipedia.org',
|
||||||
|
target: '_blank',
|
||||||
|
text: 'Foo Bar',
|
||||||
|
},
|
||||||
|
content: ['Text'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user