mirror of
https://github.com/tenrok/BBob.git
synced 2026-06-14 18:42:24 +03:00
fix lint error, move helper methods to char and token files
This commit is contained in:
@@ -7,5 +7,7 @@
|
|||||||
"node": true,
|
"node": true,
|
||||||
"jest/globals": true
|
"jest/globals": true
|
||||||
},
|
},
|
||||||
"rules": {}
|
"rules": {
|
||||||
|
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
function attrs(obj) {
|
function attrs(obj) {
|
||||||
let attr = '';
|
let attr = '';
|
||||||
|
|
||||||
@@ -27,9 +26,7 @@ function traverse(tree, cb) {
|
|||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(tree, options) {
|
function render(ast) {
|
||||||
return html(tree);
|
|
||||||
|
|
||||||
function html(tree) {
|
function html(tree) {
|
||||||
let result = '';
|
let result = '';
|
||||||
|
|
||||||
@@ -42,43 +39,20 @@ function render(tree, options) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof node.tag === 'boolean' && !node.tag) {
|
if (typeof node === 'object') {
|
||||||
typeof node.content !== 'object' && (result += node.content);
|
result += `<${node.tag} ${attrs(node.attrs)}></${node.tag}>`;
|
||||||
|
|
||||||
return node.content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// treat as new root tree if node is an array
|
// treat as new root tree if node is an array
|
||||||
if (Array.isArray(node)) {
|
if (Array.isArray(node)) {
|
||||||
result += html(node);
|
result += html(node);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tag = node.tag || 'div';
|
|
||||||
|
|
||||||
if (isSingleTag(tag, singleTags, singleRegExp)) {
|
|
||||||
result += `<${tag}${attrs(node.attrs)}`;
|
|
||||||
|
|
||||||
switch (closingSingleTag) {
|
|
||||||
case 'tag':
|
|
||||||
result += `></${tag}>`;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'slash':
|
|
||||||
result += ' />';
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
result += '>';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result += `<${tag}${node.attrs ? attrs(node.attrs) : ''}>${node.content ? html(node.content) : ''}</${tag}>`;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return html(ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = render;
|
module.exports = render;
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
describe('bbob-html', () => {
|
||||||
|
test('render proper markup', () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ const {
|
|||||||
isTagToken,
|
isTagToken,
|
||||||
isTextToken,
|
isTextToken,
|
||||||
isTagEnd,
|
isTagEnd,
|
||||||
} = require('./Tokenizer');
|
} = require('./token');
|
||||||
|
|
||||||
const Tokenizer = require('./Tokenizer');
|
const {
|
||||||
|
SLASH,
|
||||||
const TokenChar = Tokenizer.CHAR;
|
getChar,
|
||||||
const getChar = Tokenizer.getChar;
|
} = require('./char');
|
||||||
|
|
||||||
const createTagNode = (tag, attrs = {}, content = []) => ({ tag, attrs, content });
|
const createTagNode = (tag, attrs = {}, content = []) => ({ tag, attrs, content });
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ module.exports = class Parser {
|
|||||||
const curTags = [];
|
const curTags = [];
|
||||||
const curTagsAttrName = [];
|
const curTagsAttrName = [];
|
||||||
|
|
||||||
const closableTags = this.findNestedTags(this.tokens);
|
const closableTags = this.findNestedTags();
|
||||||
|
|
||||||
const isNestedTag = token => closableTags.indexOf(getTokenValue(token)) >= 0;
|
const isNestedTag = token => closableTags.indexOf(getTokenValue(token)) >= 0;
|
||||||
|
|
||||||
@@ -123,6 +123,7 @@ module.exports = class Parser {
|
|||||||
if (lastNestedNode) {
|
if (lastNestedNode) {
|
||||||
getNodes().push(lastNestedNode);
|
getNodes().push(lastNestedNode);
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.warn(`Inconsistent tag '${getTokenValue(token)}' on line ${getTokenLine(token)} and column ${getTokenColumn(token)}`);
|
console.warn(`Inconsistent tag '${getTokenValue(token)}' on line ${getTokenLine(token)} and column ${getTokenColumn(token)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,14 +150,14 @@ module.exports = class Parser {
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
findNestedTags(tokens) {
|
findNestedTags() {
|
||||||
const tags = tokens.filter(isTagToken).reduce((acc, token) => {
|
const tags = this.tokens.filter(isTagToken).reduce((acc, token) => {
|
||||||
acc[getTokenValue(token)] = true;
|
acc[getTokenValue(token)] = true;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const closeChar = getChar(TokenChar.SLASH);
|
const closeChar = getChar(SLASH);
|
||||||
|
|
||||||
return Object.keys(tags).reduce((arr, key) => {
|
return Object.keys(tags).reduce((arr, key) => {
|
||||||
if (tags[key] && tags[closeChar + key]) {
|
if (tags[key] && tags[closeChar + key]) {
|
||||||
@@ -168,10 +169,12 @@ module.exports = class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAllowedTag(value) {
|
isAllowedTag(value) {
|
||||||
if (this.options.allowOnlyTags && this.options.allowOnlyTags.length) {
|
if (this.options.onlyAllowTags && this.options.onlyAllowTags.length) {
|
||||||
return this.options.allowOnlyTags.indexOf(value) >= 0;
|
return this.options.onlyAllowTags.indexOf(value) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.createTagNode = createTagNode;
|
||||||
|
|||||||
+217
-263
@@ -1,299 +1,253 @@
|
|||||||
const CHAR = require('./char');
|
const {
|
||||||
|
getChar,
|
||||||
|
OPEN_BRAKET,
|
||||||
|
CLOSE_BRAKET, EQ, TAB, SPACE, N, QUOTEMARK,
|
||||||
|
PLACEHOLDER_SPACE, PLACEHOLDER_SPACE_TAB,
|
||||||
|
} = require('./char');
|
||||||
const TOKEN = require('./token');
|
const TOKEN = require('./token');
|
||||||
const getChar = String.fromCharCode;
|
|
||||||
|
|
||||||
|
|
||||||
const getTokenValue = (token) => token[Tokenizer.TOKEN.VALUE_ID];
|
|
||||||
|
|
||||||
const getTokenLine = (token) => token[Tokenizer.TOKEN.LINE_ID];
|
|
||||||
const getTokenColumn = (token) => token[Tokenizer.TOKEN.COLUMN_ID];
|
|
||||||
|
|
||||||
const isTextToken = (token) => {
|
|
||||||
const type = token[Tokenizer.TOKEN.TYPE_ID];
|
|
||||||
|
|
||||||
return type === TOKEN.TYPE_SPACE || type === TOKEN.TYPE_NEW_LINE || type === TOKEN.TYPE_WORD
|
|
||||||
};
|
|
||||||
|
|
||||||
const isTagToken = (token) => token[Tokenizer.TOKEN.TYPE_ID] === TOKEN.TYPE_TAG;
|
|
||||||
|
|
||||||
const isTagStart = (token) => !isTagEnd(token);
|
|
||||||
|
|
||||||
const isTagEnd = (token) => getTokenValue(token).charCodeAt(0) === CHAR.SLASH;
|
|
||||||
|
|
||||||
const isAttrNameToken = (token) => token[Tokenizer.TOKEN.TYPE_ID] === TOKEN.TYPE_ATTR_NAME;
|
|
||||||
|
|
||||||
const isAttrValueToken = (token) => token[Tokenizer.TOKEN.TYPE_ID] === TOKEN.TYPE_ATTR_VALUE;
|
|
||||||
|
|
||||||
const getTagName = (token) => {
|
|
||||||
const value = getTokenValue(token);
|
|
||||||
|
|
||||||
return isTagEnd(token) ? value.slice(1) : value
|
|
||||||
};
|
|
||||||
|
|
||||||
const convertTagToText = (token) => {
|
|
||||||
let text = getChar(CHAR.OPEN_BRAKET);
|
|
||||||
|
|
||||||
if (isTagEnd(token)) {
|
|
||||||
text += getChar(CHAR.SLASH)
|
|
||||||
}
|
|
||||||
|
|
||||||
text += getTokenValue(token);
|
|
||||||
text += getChar(CHAR.CLOSE_BRAKET);
|
|
||||||
|
|
||||||
return text
|
|
||||||
};
|
|
||||||
|
|
||||||
const SPACE_TAB = ' ';
|
|
||||||
const SPACE = ' ';
|
|
||||||
|
|
||||||
class Tokenizer {
|
class Tokenizer {
|
||||||
constructor(input) {
|
constructor(input) {
|
||||||
this.buffer = input;
|
this.buffer = input;
|
||||||
this.colPos = 0;
|
this.colPos = 0;
|
||||||
this.rowPos = 0;
|
this.rowPos = 0;
|
||||||
this.index = 0;
|
this.index = 0;
|
||||||
|
|
||||||
this.tokenIndex = -1;
|
this.tokenIndex = -1;
|
||||||
this.tokens = [];
|
this.tokens = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
appendToken(token) {
|
appendToken(token) {
|
||||||
this.tokenIndex++;
|
this.tokenIndex += 1;
|
||||||
this.tokens[this.tokenIndex] = token;
|
this.tokens[this.tokenIndex] = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenize() {
|
nextCol() {
|
||||||
let wordToken = null;
|
this.colPos += 1;
|
||||||
let tagToken = null;
|
}
|
||||||
let attrNameToken = null;
|
|
||||||
let attrValueToken = null;
|
|
||||||
let attrTokens = [];
|
|
||||||
this.tokens = new Array(Math.floor(this.buffer.length / 2));
|
|
||||||
|
|
||||||
const flushWord = () => {
|
nextLine() {
|
||||||
if (wordToken && wordToken[TOKEN.VALUE_ID]) {
|
this.rowPos += 1;
|
||||||
this.appendToken(wordToken);
|
}
|
||||||
wordToken = this.createWordToken('')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createWord = (value, line, row) => {
|
tokenize() {
|
||||||
if (!wordToken) {
|
let wordToken = null;
|
||||||
wordToken = this.createWordToken(value, line, row)
|
let tagToken = null;
|
||||||
}
|
let attrNameToken = null;
|
||||||
};
|
let attrValueToken = null;
|
||||||
|
let attrTokens = [];
|
||||||
|
this.tokens = new Array(Math.floor(this.buffer.length / 2));
|
||||||
|
|
||||||
const flushTag = () => {
|
const flushWord = () => {
|
||||||
if (tagToken !== null) {
|
if (wordToken && wordToken[TOKEN.VALUE_ID]) {
|
||||||
// [] and [=] tag case
|
this.appendToken(wordToken);
|
||||||
if (!tagToken[TOKEN.VALUE_ID]) {
|
wordToken = this.createWordToken('');
|
||||||
const value = attrValueToken ? getChar(CHAR.EQ) : '';
|
}
|
||||||
const word = getChar(CHAR.OPEN_BRAKET) + value + getChar(CHAR.CLOSE_BRAKET);
|
};
|
||||||
|
|
||||||
createWord('', 0, 0);
|
const createWord = (value, line, row) => {
|
||||||
wordToken[TOKEN.VALUE_ID] += word;
|
if (!wordToken) {
|
||||||
|
wordToken = this.createWordToken(value, line, row);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
tagToken = null;
|
const flushTag = () => {
|
||||||
|
if (tagToken !== null) {
|
||||||
|
// [] and [=] tag case
|
||||||
|
if (!tagToken[TOKEN.VALUE_ID]) {
|
||||||
|
const value = attrValueToken ? getChar(EQ) : '';
|
||||||
|
const word = getChar(OPEN_BRAKET) + value + getChar(CLOSE_BRAKET);
|
||||||
|
|
||||||
if (attrValueToken) {
|
createWord('', 0, 0);
|
||||||
attrValueToken = null
|
wordToken[TOKEN.VALUE_ID] += word;
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
tagToken = null;
|
||||||
}
|
|
||||||
|
|
||||||
if (attrNameToken && !attrValueToken) {
|
if (attrValueToken) {
|
||||||
tagToken[TOKEN.VALUE_ID] += SPACE + attrNameToken[TOKEN.VALUE_ID];
|
attrValueToken = null;
|
||||||
attrNameToken = null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.appendToken(tagToken);
|
return;
|
||||||
tagToken = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const flushUnclosedTag = () => {
|
|
||||||
if (tagToken !== null) {
|
|
||||||
const value = tagToken[TOKEN.VALUE_ID] + (attrValueToken ? getChar(CHAR.EQ) : '');
|
|
||||||
|
|
||||||
tagToken[TOKEN.TYPE_ID] = TOKEN.TYPE_WORD;
|
|
||||||
tagToken[TOKEN.VALUE_ID] = getChar(CHAR.OPEN_BRAKET) + value;
|
|
||||||
|
|
||||||
this.appendToken(tagToken);
|
|
||||||
|
|
||||||
tagToken = null;
|
|
||||||
|
|
||||||
if (attrValueToken) {
|
|
||||||
attrValueToken = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const flushAttrNames = () => {
|
|
||||||
if (attrNameToken) {
|
|
||||||
attrTokens.push(attrNameToken);
|
|
||||||
attrNameToken = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attrValueToken) {
|
|
||||||
attrTokens.push(attrValueToken);
|
|
||||||
attrValueToken = null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const flushAttrs = () => {
|
|
||||||
if (attrTokens.length) {
|
|
||||||
attrTokens.forEach(this.appendToken.bind(this));
|
|
||||||
attrTokens = [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// console.time('Lexer.tokenize');
|
|
||||||
|
|
||||||
while (this.index < this.buffer.length) {
|
|
||||||
const charCode = this.buffer.charCodeAt(this.index);
|
|
||||||
|
|
||||||
switch (charCode) {
|
|
||||||
case CHAR.TAB:
|
|
||||||
case CHAR.SPACE:
|
|
||||||
flushWord();
|
|
||||||
|
|
||||||
if (tagToken) {
|
|
||||||
attrNameToken = this.createAttrNameToken('');
|
|
||||||
} else {
|
|
||||||
const spaceCode = charCode === CHAR.TAB ? SPACE_TAB : SPACE;
|
|
||||||
|
|
||||||
this.appendToken(this.createSpaceToken(spaceCode));
|
|
||||||
}
|
|
||||||
this.colPos++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CHAR.N:
|
|
||||||
flushWord();
|
|
||||||
this.appendToken(this.createNewLineToken(getChar(charCode)));
|
|
||||||
|
|
||||||
this.rowPos++;
|
|
||||||
this.colPos = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CHAR.OPEN_BRAKET:
|
|
||||||
flushWord();
|
|
||||||
tagToken = this.createTagToken('');
|
|
||||||
|
|
||||||
this.colPos++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CHAR.CLOSE_BRAKET:
|
|
||||||
flushTag();
|
|
||||||
flushAttrNames();
|
|
||||||
flushAttrs();
|
|
||||||
|
|
||||||
this.colPos++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CHAR.EQ:
|
|
||||||
if (tagToken) {
|
|
||||||
attrValueToken = this.createAttrValueToken('')
|
|
||||||
} else {
|
|
||||||
wordToken[TOKEN.VALUE_ID] += getChar(charCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.colPos++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CHAR.QUOTEMARK:
|
|
||||||
if (attrValueToken && attrValueToken[TOKEN.VALUE_ID] > 0) {
|
|
||||||
flushAttrNames();
|
|
||||||
} else if (tagToken === null) {
|
|
||||||
wordToken[TOKEN.VALUE_ID] += getChar(charCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.colPos++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (tagToken && attrValueToken) {
|
|
||||||
attrValueToken[TOKEN.VALUE_ID] += getChar(charCode)
|
|
||||||
} else if (tagToken && attrNameToken) {
|
|
||||||
attrNameToken[TOKEN.VALUE_ID] += getChar(charCode)
|
|
||||||
} else if (tagToken) {
|
|
||||||
tagToken[TOKEN.VALUE_ID] += getChar(charCode)
|
|
||||||
} else {
|
|
||||||
createWord();
|
|
||||||
|
|
||||||
wordToken[TOKEN.VALUE_ID] += getChar(charCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.colPos++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.index++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flushWord();
|
if (attrNameToken && !attrValueToken) {
|
||||||
flushUnclosedTag();
|
tagToken[TOKEN.VALUE_ID] += PLACEHOLDER_SPACE + attrNameToken[TOKEN.VALUE_ID];
|
||||||
|
attrNameToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.tokens.length = this.tokenIndex + 1;
|
this.appendToken(tagToken);
|
||||||
|
tagToken = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return this.tokens;
|
const flushUnclosedTag = () => {
|
||||||
|
if (tagToken !== null) {
|
||||||
|
const value = tagToken[TOKEN.VALUE_ID] + (attrValueToken ? getChar(EQ) : '');
|
||||||
|
|
||||||
|
tagToken[TOKEN.TYPE_ID] = TOKEN.TYPE_WORD;
|
||||||
|
tagToken[TOKEN.VALUE_ID] = getChar(OPEN_BRAKET) + value;
|
||||||
|
|
||||||
|
this.appendToken(tagToken);
|
||||||
|
|
||||||
|
tagToken = null;
|
||||||
|
|
||||||
|
if (attrValueToken) {
|
||||||
|
attrValueToken = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const flushAttrNames = () => {
|
||||||
|
if (attrNameToken) {
|
||||||
|
attrTokens.push(attrNameToken);
|
||||||
|
attrNameToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrValueToken) {
|
||||||
|
attrTokens.push(attrValueToken);
|
||||||
|
attrValueToken = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const flushAttrs = () => {
|
||||||
|
if (attrTokens.length) {
|
||||||
|
attrTokens.forEach(this.appendToken.bind(this));
|
||||||
|
attrTokens = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// console.time('Lexer.tokenize');
|
||||||
|
|
||||||
|
while (this.index < this.buffer.length) {
|
||||||
|
const charCode = this.buffer.charCodeAt(this.index);
|
||||||
|
|
||||||
|
switch (charCode) {
|
||||||
|
case TAB:
|
||||||
|
case SPACE:
|
||||||
|
flushWord();
|
||||||
|
|
||||||
|
if (tagToken) {
|
||||||
|
attrNameToken = this.createAttrNameToken('');
|
||||||
|
} else {
|
||||||
|
const spaceCode = charCode === TAB ? PLACEHOLDER_SPACE_TAB : PLACEHOLDER_SPACE;
|
||||||
|
|
||||||
|
this.appendToken(this.createSpaceToken(spaceCode));
|
||||||
|
}
|
||||||
|
this.nextCol();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case N:
|
||||||
|
flushWord();
|
||||||
|
this.appendToken(this.createNewLineToken(getChar(charCode)));
|
||||||
|
|
||||||
|
this.nextLine();
|
||||||
|
this.colPos = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPEN_BRAKET:
|
||||||
|
flushWord();
|
||||||
|
tagToken = this.createTagToken('');
|
||||||
|
|
||||||
|
this.nextCol();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CLOSE_BRAKET:
|
||||||
|
flushTag();
|
||||||
|
flushAttrNames();
|
||||||
|
flushAttrs();
|
||||||
|
|
||||||
|
this.nextCol();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EQ:
|
||||||
|
if (tagToken) {
|
||||||
|
attrValueToken = this.createAttrValueToken('');
|
||||||
|
} else {
|
||||||
|
wordToken[TOKEN.VALUE_ID] += getChar(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nextCol();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QUOTEMARK:
|
||||||
|
if (attrValueToken && attrValueToken[TOKEN.VALUE_ID] > 0) {
|
||||||
|
flushAttrNames();
|
||||||
|
} else if (tagToken === null) {
|
||||||
|
wordToken[TOKEN.VALUE_ID] += getChar(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nextCol();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (tagToken && attrValueToken) {
|
||||||
|
attrValueToken[TOKEN.VALUE_ID] += getChar(charCode);
|
||||||
|
} else if (tagToken && attrNameToken) {
|
||||||
|
attrNameToken[TOKEN.VALUE_ID] += getChar(charCode);
|
||||||
|
} else if (tagToken) {
|
||||||
|
tagToken[TOKEN.VALUE_ID] += getChar(charCode);
|
||||||
|
} else {
|
||||||
|
createWord();
|
||||||
|
|
||||||
|
wordToken[TOKEN.VALUE_ID] += getChar(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nextCol();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
createWordToken(value = '', line = this.colPos, row = this.rowPos) {
|
flushWord();
|
||||||
return [TOKEN.TYPE_WORD, value, line, row]
|
flushUnclosedTag();
|
||||||
}
|
|
||||||
|
|
||||||
createTagToken(value, line = this.colPos, row = this.rowPos) {
|
this.tokens.length = this.tokenIndex + 1;
|
||||||
return [TOKEN.TYPE_TAG, value, line, row]
|
|
||||||
}
|
|
||||||
|
|
||||||
createAttrNameToken(value, line = this.colPos, row = this.rowPos) {
|
return this.tokens;
|
||||||
return [TOKEN.TYPE_ATTR_NAME, value, line, row]
|
}
|
||||||
}
|
|
||||||
|
|
||||||
createAttrValueToken(value, line = this.colPos, row = this.rowPos) {
|
createWordToken(value = '', line = this.colPos, row = this.rowPos) {
|
||||||
return [TOKEN.TYPE_ATTR_VALUE, value, line, row]
|
return [TOKEN.TYPE_WORD, value, line, row];
|
||||||
}
|
}
|
||||||
|
|
||||||
createSpaceToken(value, line = this.colPos, row = this.rowPos) {
|
createTagToken(value, line = this.colPos, row = this.rowPos) {
|
||||||
return [TOKEN.TYPE_SPACE, value, line, row]
|
return [TOKEN.TYPE_TAG, value, line, row];
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewLineToken(value, line = this.colPos, row = this.rowPos) {
|
createAttrNameToken(value, line = this.colPos, row = this.rowPos) {
|
||||||
return [TOKEN.TYPE_NEW_LINE, value, line, row]
|
return [TOKEN.TYPE_ATTR_NAME, value, line, row];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createAttrValueToken(value, line = this.colPos, row = this.rowPos) {
|
||||||
|
return [TOKEN.TYPE_ATTR_VALUE, value, line, row];
|
||||||
|
}
|
||||||
|
|
||||||
|
createSpaceToken(value, line = this.colPos, row = this.rowPos) {
|
||||||
|
return [TOKEN.TYPE_SPACE, value, line, row];
|
||||||
|
}
|
||||||
|
|
||||||
|
createNewLineToken(value, line = this.colPos, row = this.rowPos) {
|
||||||
|
return [TOKEN.TYPE_NEW_LINE, value, line, row];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// warm up tokenizer to elimitate code branches that never execute
|
// warm up tokenizer to elimitate code branches that never execute
|
||||||
new Tokenizer(`[sc=asdasd`).tokenize();
|
new Tokenizer('[b param="hello"]Sample text[/b]\n\t[Chorus 2]').tokenize();
|
||||||
//new Tokenizer(`[b param="hello"]Sample text[/b]\n\t[Chorus]`).tokenize();
|
|
||||||
|
|
||||||
module.exports = Tokenizer;
|
module.exports = Tokenizer;
|
||||||
module.exports.CHAR = CHAR;
|
|
||||||
module.exports.TYPE = {
|
module.exports.TYPE = {
|
||||||
WORD: TOKEN.TYPE_WORD,
|
WORD: TOKEN.TYPE_WORD,
|
||||||
TAG: TOKEN.TYPE_TAG,
|
TAG: TOKEN.TYPE_TAG,
|
||||||
ATTR_NAME: TOKEN.TYPE_ATTR_NAME,
|
ATTR_NAME: TOKEN.TYPE_ATTR_NAME,
|
||||||
ATTR_VALUE: TOKEN.TYPE_ATTR_VALUE,
|
ATTR_VALUE: TOKEN.TYPE_ATTR_VALUE,
|
||||||
SPACE: TOKEN.TYPE_SPACE,
|
SPACE: TOKEN.TYPE_SPACE,
|
||||||
NEW_LINE: TOKEN.TYPE_NEW_LINE,
|
NEW_LINE: TOKEN.TYPE_NEW_LINE,
|
||||||
};
|
};
|
||||||
module.exports.TOKEN = {
|
module.exports.TOKEN = {
|
||||||
TYPE_ID: TOKEN.TYPE_ID,
|
TYPE_ID: TOKEN.TYPE_ID,
|
||||||
VALUE_ID: TOKEN.VALUE_ID,
|
VALUE_ID: TOKEN.VALUE_ID,
|
||||||
LINE_ID: TOKEN.LINE_ID,
|
LINE_ID: TOKEN.LINE_ID,
|
||||||
COLUMN_ID: TOKEN.COLUMN_ID,
|
COLUMN_ID: TOKEN.COLUMN_ID,
|
||||||
};
|
};
|
||||||
module.exports.getChar = getChar;
|
|
||||||
module.exports.getTokenValue = getTokenValue;
|
|
||||||
module.exports.getTokenLine = getTokenLine;
|
|
||||||
module.exports.getTokenColumn = getTokenColumn;
|
|
||||||
module.exports.isTextToken = isTextToken;
|
|
||||||
module.exports.isTagToken = isTagToken;
|
|
||||||
module.exports.isTagStart = isTagStart;
|
|
||||||
module.exports.isTagEnd = isTagEnd;
|
|
||||||
module.exports.isAttrNameToken = isAttrNameToken;
|
|
||||||
module.exports.isAttrValueToken = isAttrValueToken;
|
|
||||||
module.exports.getTagName = getTagName;
|
|
||||||
module.exports.convertTokenToText = convertTagToText;
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const Tokenizer = require('./Tokenizer');
|
const Tokenizer = require('./Tokenizer');
|
||||||
|
|
||||||
const TYPE = Tokenizer.TYPE;
|
const { TYPE } = Tokenizer;
|
||||||
|
|
||||||
const tokenize = input => (new Tokenizer(input).tokenize());
|
const tokenize = input => (new Tokenizer(input).tokenize());
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ const CLOSE_BRAKET = ']'.charCodeAt(0);
|
|||||||
|
|
||||||
const SLASH = '/'.charCodeAt(0);
|
const SLASH = '/'.charCodeAt(0);
|
||||||
|
|
||||||
|
const PLACEHOLDER_SPACE_TAB = ' ';
|
||||||
|
const PLACEHOLDER_SPACE = ' ';
|
||||||
|
|
||||||
|
const getChar = String.fromCharCode;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
getChar,
|
||||||
N,
|
N,
|
||||||
F,
|
F,
|
||||||
R,
|
R,
|
||||||
@@ -23,4 +29,6 @@ module.exports = {
|
|||||||
OPEN_BRAKET,
|
OPEN_BRAKET,
|
||||||
CLOSE_BRAKET,
|
CLOSE_BRAKET,
|
||||||
SLASH,
|
SLASH,
|
||||||
|
PLACEHOLDER_SPACE_TAB,
|
||||||
|
PLACEHOLDER_SPACE,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
module.exports = require('./parse');
|
module.exports = require('./parse');
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
const parse = require('./index');
|
const parse = require('./index');
|
||||||
|
|
||||||
const options = {
|
|
||||||
allowOnlyTags: ['ch', 'syllable', 'tab'],
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('parse', () => {
|
describe('parse', () => {
|
||||||
test('tag with spaces', () => {
|
test('tag with spaces', () => {
|
||||||
const ast = parse('[Verse 2]');
|
const ast = parse('[Verse 2]');
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
const {
|
||||||
|
getChar,
|
||||||
|
OPEN_BRAKET,
|
||||||
|
CLOSE_BRAKET,
|
||||||
|
SLASH,
|
||||||
|
} = require('./char');
|
||||||
|
|
||||||
const TOKEN_TYPE_ID = 0;
|
const TOKEN_TYPE_ID = 0;
|
||||||
const TOKEN_VALUE_ID = 1;
|
const TOKEN_VALUE_ID = 1;
|
||||||
const TOKEN_COLUMN_ID = 2;
|
const TOKEN_COLUMN_ID = 2;
|
||||||
@@ -10,6 +17,43 @@ const TOKEN_TYPE_ATTR_VALUE = 'attr-value';
|
|||||||
const TOKEN_TYPE_SPACE = 'space';
|
const TOKEN_TYPE_SPACE = 'space';
|
||||||
const TOKEN_TYPE_NEW_LINE = 'new-line';
|
const TOKEN_TYPE_NEW_LINE = 'new-line';
|
||||||
|
|
||||||
|
const getTokenValue = token => token[TOKEN_VALUE_ID];
|
||||||
|
const getTokenLine = token => token[TOKEN_LINE_ID];
|
||||||
|
const getTokenColumn = token => token[TOKEN_COLUMN_ID];
|
||||||
|
|
||||||
|
const isTextToken = (token) => {
|
||||||
|
const type = token[TOKEN_TYPE_ID];
|
||||||
|
|
||||||
|
return type === TOKEN_TYPE_SPACE || type === TOKEN_TYPE_NEW_LINE || type === TOKEN_TYPE_WORD;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTagToken = token => token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG;
|
||||||
|
const isTagEnd = token => getTokenValue(token).charCodeAt(0) === SLASH;
|
||||||
|
const isTagStart = token => !isTagEnd(token);
|
||||||
|
const isAttrNameToken = token => token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_NAME;
|
||||||
|
const isAttrValueToken = token => token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE;
|
||||||
|
|
||||||
|
const getTagName = (token) => {
|
||||||
|
const value = getTokenValue(token);
|
||||||
|
|
||||||
|
return isTagEnd(token) ? value.slice(1) : value;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const convertTagToText = (token) => {
|
||||||
|
let text = getChar(OPEN_BRAKET);
|
||||||
|
|
||||||
|
if (isTagEnd(token)) {
|
||||||
|
text += getChar(SLASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
text += getTokenValue(token);
|
||||||
|
text += getChar(CLOSE_BRAKET);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
TYPE_ID: TOKEN_TYPE_ID,
|
TYPE_ID: TOKEN_TYPE_ID,
|
||||||
VALUE_ID: TOKEN_VALUE_ID,
|
VALUE_ID: TOKEN_VALUE_ID,
|
||||||
@@ -20,5 +64,16 @@ module.exports = {
|
|||||||
TYPE_ATTR_NAME: TOKEN_TYPE_ATTR_NAME,
|
TYPE_ATTR_NAME: TOKEN_TYPE_ATTR_NAME,
|
||||||
TYPE_ATTR_VALUE: TOKEN_TYPE_ATTR_VALUE,
|
TYPE_ATTR_VALUE: TOKEN_TYPE_ATTR_VALUE,
|
||||||
TYPE_SPACE: TOKEN_TYPE_SPACE,
|
TYPE_SPACE: TOKEN_TYPE_SPACE,
|
||||||
TYPE_NEW_LINE: TOKEN_TYPE_NEW_LINE
|
TYPE_NEW_LINE: TOKEN_TYPE_NEW_LINE,
|
||||||
|
convertTagToText,
|
||||||
|
getTagName,
|
||||||
|
getTokenColumn,
|
||||||
|
getTokenLine,
|
||||||
|
getTokenValue,
|
||||||
|
isAttrNameToken,
|
||||||
|
isAttrValueToken,
|
||||||
|
isTagStart,
|
||||||
|
isTagToken,
|
||||||
|
isTextToken,
|
||||||
|
isTagEnd,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,41 +1,47 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const parse = require('bbob-html');
|
const PropTypes = require('prop-types');
|
||||||
|
const parse = require('@bbob/html');
|
||||||
|
|
||||||
class BBCode extends React.Component {
|
class BBCode extends React.Component {
|
||||||
render() {
|
|
||||||
const Container = this.props.container;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
{this.content()}
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
content() {
|
content() {
|
||||||
if (this.props.source) {
|
if (this.props.source) {
|
||||||
|
// eslint-disable-next-line react/no-danger
|
||||||
return <span dangerouslySetInnerHTML={{ __html: this.renderBBCode(this.props.source) }} />;
|
return <span dangerouslySetInnerHTML={{ __html: this.renderBBCode(this.props.source) }} />;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return React.Children.map(this.props.children, child => {
|
return React.Children.map(this.props.children, (child) => {
|
||||||
if (typeof child === 'string') {
|
if (typeof child === 'string') {
|
||||||
return <span dangerouslySetInnerHTML={{ __html: this.renderBBCode(child) }} />;
|
// eslint-disable-next-line react/no-danger
|
||||||
}
|
return <span dangerouslySetInnerHTML={{ __html: this.renderBBCode(child) }} />;
|
||||||
else {
|
}
|
||||||
return child;
|
return child;
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBBCode(source) {
|
renderBBCode(source) {
|
||||||
return parse(source)
|
return parse(source, this.props.options);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const Container = this.props.container;
|
||||||
|
|
||||||
|
return (<Container>{this.content()}</Container>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BBCode.propTypes = {
|
||||||
|
container: PropTypes.node,
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
source: PropTypes.string,
|
||||||
|
options: PropTypes.shape({
|
||||||
|
prop: PropTypes.bool,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
BBCode.defaultProps = {
|
BBCode.defaultProps = {
|
||||||
container: 'div',
|
container: 'div',
|
||||||
options: {},
|
options: {},
|
||||||
|
source: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = BBCode;
|
module.exports = BBCode;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
describe('React BBCode', () => {
|
describe('React BBCode', () => {
|
||||||
|
|
||||||
test('render markup properly', () => {
|
test('render markup properly', () => {
|
||||||
|
|
||||||
})
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,5 +12,13 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://registry.npmjs.org/"
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@bbob/html": "^1.0.1",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"react": "^15.6.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "15.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user