2
0
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:
Nikolay Kostyurin
2018-07-09 21:58:41 +02:00
parent ee3cff8409
commit 3c750d95c2
6 changed files with 290 additions and 292 deletions
+1
View File
@@ -1 +1,2 @@
/packages/**/dist
/packages/**/test
-202
View File
@@ -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;
+240 -5
View File
@@ -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 parser = new Parser(input, options);
const ast = parser.parse();
const Tokenizer = require('./Tokenizer');
const TagNode = require('./TagNode');
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;
-1
View File
@@ -19,7 +19,6 @@
"html"
],
"main": "lib/index.js",
"browser": "dist/umd.js",
"repository": {
"type": "git",
"url": "git://github.com/JiLiZART/bbob.git"
-56
View File
@@ -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'],
},
]);
});
});
+49 -28
View File
@@ -1,33 +1,54 @@
const parse = require('../lib/index');
const parse = require('../lib/parse');
describe('parse', () => {
test('tag with spaces', () => {
const ast = parse('[Verse 2]');
describe('Parser', () => {
test('parse paired tags tokens', () => {
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", () => {
// const inputs = [
// '[]',
// '![](image.jpg)',
// 'x html([a. title][, alt][, classes]) x',
// '[/y]',
// '[sc',
// '[sc / [/sc]',
// '[sc arg="val',
// ];
//
// const ast1 = parse(inputs[0]);
//
//
//
// console.log('ast1', ast1);
//
//
//
// expect(ast1).toEqual([
//
// ]);
// })
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'],
},
]);
});
});