diff --git a/.eslintignore b/.eslintignore index 35c9b40..5212924 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ /packages/**/dist +/packages/**/test diff --git a/packages/bbob-parser/lib/Parser.js b/packages/bbob-parser/lib/Parser.js deleted file mode 100644 index 6608df5..0000000 --- a/packages/bbob-parser/lib/Parser.js +++ /dev/null @@ -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; diff --git a/packages/bbob-parser/lib/parse.js b/packages/bbob-parser/lib/parse.js index ae91e5d..fe86b71 100644 --- a/packages/bbob-parser/lib/parse.js +++ b/packages/bbob-parser/lib/parse.js @@ -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; diff --git a/packages/bbob-parser/package.json b/packages/bbob-parser/package.json index 2b1362c..cebaca4 100644 --- a/packages/bbob-parser/package.json +++ b/packages/bbob-parser/package.json @@ -19,7 +19,6 @@ "html" ], "main": "lib/index.js", - "browser": "dist/umd.js", "repository": { "type": "git", "url": "git://github.com/JiLiZART/bbob.git" diff --git a/packages/bbob-parser/test/Parser.test.js b/packages/bbob-parser/test/Parser.test.js deleted file mode 100644 index afd0832..0000000 --- a/packages/bbob-parser/test/Parser.test.js +++ /dev/null @@ -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'], - }, - ]); - }); -}); diff --git a/packages/bbob-parser/test/parse.test.js b/packages/bbob-parser/test/parse.test.js index 346b07c..be4ff85 100644 --- a/packages/bbob-parser/test/parse.test.js +++ b/packages/bbob-parser/test/parse.test.js @@ -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'], + }, + ]); + }); });