html5parser
Version:
A super fast & tiny HTML5 parser
256 lines (249 loc) • 7.62 kB
text/typescript
/*!
*
* Copyright 2017 - acrazing
*
* @author acrazing joking.young@gmail.com
* @since 2017-08-19 14:15:25
* @version 1.0.0
* @desc tokenize.spec.ts
*
* @formatter:off
*/
import * as assert from 'assert';
import { IToken, tokenize, TokenKind } from './tokenize';
interface ICase {
name: string;
input: string;
tokens: IToken[];
}
export let tokenIndex = 0;
export function token(value: string, type: TokenKind = TokenKind.Literal, start = tokenIndex) {
const v = {
start: start,
end: start + value.length,
value,
type,
};
tokenIndex = v.end;
return v;
}
const cases: ICase[] = [
{
name: 'single Literal',
input: 'hello',
tokens: [token('hello', TokenKind.Literal, 0)],
},
{
name: 'Literal end with <',
input: 'hello<',
tokens: [token('hello', void 0, 0), token('<')],
},
{
name: 'Literal unexpected <',
input: 'hello< world',
tokens: [token('hello', void 0, 0), token('< world')],
},
{
name: 'OpenTag EOF',
input: '<div',
tokens: [token('div', TokenKind.OpenTag, 1)],
},
{
name: 'attribute names',
input: "<div a1 'b2' \"c3\" 'd4'e5 'f6\"' \"g7'\"></div>",
tokens: [
token('div', TokenKind.OpenTag, 1),
token(' ', TokenKind.Whitespace),
token('a1', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token("'b2'", TokenKind.AttrValueSq),
token(' ', TokenKind.Whitespace),
token('"c3"', TokenKind.AttrValueDq),
token(' ', TokenKind.Whitespace),
token("'d4'", TokenKind.AttrValueSq),
token('e5', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token("'f6\"'", TokenKind.AttrValueSq),
token(' ', TokenKind.Whitespace),
token('"g7\'"', TokenKind.AttrValueDq),
token('', TokenKind.OpenTagEnd),
token('div', TokenKind.CloseTag, tokenIndex + 3),
],
},
{
name: 'attribute values',
input: '<div a b= c=1 d e = f = g \'h\'=i "j"k=lmn o=\'pq\' r="st"u>M</div>',
tokens: [
token('div', TokenKind.OpenTag, 1),
token(' ', TokenKind.Whitespace),
token('a', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token('b', TokenKind.AttrValueNq),
token('=', TokenKind.AttrValueEq),
token(' ', TokenKind.Whitespace),
token('c', TokenKind.AttrValueNq),
token('=', TokenKind.AttrValueEq),
token('1', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token('d', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token('e', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token('=', TokenKind.AttrValueEq),
token(' ', TokenKind.Whitespace),
token('f', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token('=', TokenKind.AttrValueEq),
token(' ', TokenKind.Whitespace),
token('g', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token("'h'", TokenKind.AttrValueSq),
token('=', TokenKind.AttrValueEq),
token('i', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token('"j"', TokenKind.AttrValueDq),
token('k', TokenKind.AttrValueNq),
token('=', TokenKind.AttrValueEq),
token('lmn', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token('o', TokenKind.AttrValueNq),
token('=', TokenKind.AttrValueEq),
token("'pq'", TokenKind.AttrValueSq),
token(' ', TokenKind.Whitespace),
token('r', TokenKind.AttrValueNq),
token('=', TokenKind.AttrValueEq),
token('"st"', TokenKind.AttrValueDq),
token('u', TokenKind.AttrValueNq),
token('', TokenKind.OpenTagEnd),
token('M', void 0, tokenIndex + 1),
token('div', TokenKind.CloseTag, tokenIndex + 2),
],
},
{
name: 'normal doctype',
input: '<!doctype html>',
tokens: [
token('!doctype', TokenKind.OpenTag, 1),
token(' ', TokenKind.Whitespace),
token('html', TokenKind.AttrValueNq),
token('', TokenKind.OpenTagEnd),
],
},
{
name: 'unexpected eof end doctype',
input: '<!doctype',
tokens: [token('!doctype', TokenKind.OpenTag, 1)],
},
{
name: 'unexpected eof in doctype',
input: '<!doctyp',
tokens: [token('!', TokenKind.OpenTag, 1), token('doctyp')],
},
{
name: 'normal comment',
input: '<!-- hello world -->',
tokens: [
token('!--', TokenKind.OpenTag, 1),
token(' hello world '),
token('--', TokenKind.OpenTagEnd),
],
},
{
name: 'short comment',
input: '<? hello world ?><!- hello world ->',
tokens: [
token('', TokenKind.OpenTag, 1),
token('? hello world ?'),
token('', TokenKind.OpenTagEnd),
token('!', TokenKind.OpenTag, tokenIndex + 2),
token('- hello world -'),
token('', TokenKind.OpenTagEnd),
],
},
{
name: 'open tag end',
input: '<a1><b2/><c3 /><d4 / ><e5 f6/><g7 /h8><i9 /j10/><k11//>',
tokens: [
token('a1', TokenKind.OpenTag, 1),
token('', TokenKind.OpenTagEnd),
token('b2', TokenKind.OpenTag, tokenIndex + 2),
token('/', TokenKind.OpenTagEnd),
token('c3', TokenKind.OpenTag, tokenIndex + 2),
token(' ', TokenKind.Whitespace),
token('/', TokenKind.OpenTagEnd),
token('d4', TokenKind.OpenTag, tokenIndex + 2),
token(' ', TokenKind.Whitespace),
token('/', TokenKind.AttrValueNq),
token(' ', TokenKind.Whitespace),
token('', TokenKind.OpenTagEnd),
token('e5', TokenKind.OpenTag, tokenIndex + 2),
token(' ', TokenKind.Whitespace),
token('f6', TokenKind.AttrValueNq),
token('/', TokenKind.OpenTagEnd),
token('g7', TokenKind.OpenTag, tokenIndex + 2),
token(' ', TokenKind.Whitespace),
token('/', TokenKind.AttrValueNq),
token('h8', TokenKind.AttrValueNq),
token('', TokenKind.OpenTagEnd),
token('i9', TokenKind.OpenTag, tokenIndex + 2),
token(' ', TokenKind.Whitespace),
token('/', TokenKind.AttrValueNq),
token('j10', TokenKind.AttrValueNq),
token('/', TokenKind.OpenTagEnd),
token('k11', TokenKind.OpenTag, tokenIndex + 2),
token('/', TokenKind.AttrValueNq),
token('/', TokenKind.OpenTagEnd),
],
},
{
name: 'close tag',
input: '</div></ div >',
tokens: [
token('div', TokenKind.CloseTag, 2),
token(' div ', TokenKind.CloseTag, tokenIndex + 3),
],
},
{
name: 'special normal comment',
input: '<!---- - -- ---->',
tokens: [
token('!--', TokenKind.OpenTag, 1),
token('-- '),
token('- '),
token('-- '),
token('-'),
token('-'),
token('--', TokenKind.OpenTagEnd),
],
},
{
name: 'script',
input: '<script></div></script</script >',
tokens: [
token('script', TokenKind.OpenTag, 1),
token('', TokenKind.OpenTagEnd),
token('</div>', TokenKind.Literal, tokenIndex + 1),
token('</script'),
token('script ', TokenKind.CloseTag, tokenIndex + 2),
],
},
{
name: 'style',
input: '<style></div></style</style >',
tokens: [
token('style', TokenKind.OpenTag, 1),
token('', TokenKind.OpenTagEnd),
token('</div>', TokenKind.Literal, tokenIndex + 1),
token('</style'),
token('style ', TokenKind.CloseTag, tokenIndex + 2),
],
},
];
describe('simple cases', () => {
for (const _case of cases) {
it(`case "${_case.name}"`, () => {
const tokens = tokenize(_case.input);
assert.deepStrictEqual(tokens, _case.tokens);
});
}
});