@mapcss/preset-svg
Version:
SVG as CSS for MapCSS
236 lines (235 loc) • 8.4 kB
JavaScript
const SINGLE_QUOTE = "'".charCodeAt(0);
const DOUBLE_QUOTE = '"'.charCodeAt(0);
const BACKSLASH = "\\".charCodeAt(0);
const SLASH = "/".charCodeAt(0);
const NEWLINE = "\n".charCodeAt(0);
const SPACE = " ".charCodeAt(0);
const FEED = "\f".charCodeAt(0);
const TAB = "\t".charCodeAt(0);
const CR = "\r".charCodeAt(0);
const OPEN_SQUARE = "[".charCodeAt(0);
const CLOSE_SQUARE = "]".charCodeAt(0);
const OPEN_PARENTHESES = "(".charCodeAt(0);
const CLOSE_PARENTHESES = ")".charCodeAt(0);
const OPEN_CURLY = "{".charCodeAt(0);
const CLOSE_CURLY = "}".charCodeAt(0);
const SEMICOLON = ";".charCodeAt(0);
const ASTERISK = "*".charCodeAt(0);
const COLON = ":".charCodeAt(0);
const AT = "@".charCodeAt(0);
const RE_AT_END = /[\t\n\f\r "#'()/;[\\\]{}]/g;
const RE_WORD_END = /[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g;
const RE_BAD_BRACKET = /.[\n"'(/\\]/;
const RE_HEX_ESCAPE = /[\da-f]/i;
export default function tokenizer(input, options = {}) {
let css = input.css.valueOf();
let ignore = options.ignoreErrors;
let code, next, quote, content, escape;
let escaped, escapePos, prev, n, currentToken;
let length = css.length;
let pos = 0;
let buffer = [];
let returned = [];
function position() {
return pos;
}
function unclosed(what) {
throw input.error("Unclosed " + what, pos);
}
function endOfFile() {
return returned.length === 0 && pos >= length;
}
function nextToken(opts) {
if (returned.length)
return returned.pop();
if (pos >= length)
return;
let ignoreUnclosed = opts ? opts.ignoreUnclosed : false;
code = css.charCodeAt(pos);
switch (code) {
case NEWLINE:
case SPACE:
case TAB:
case CR:
case FEED: {
next = pos;
do {
next += 1;
code = css.charCodeAt(next);
} while (code === SPACE ||
code === NEWLINE ||
code === TAB ||
code === CR ||
code === FEED);
currentToken = ["space", css.slice(pos, next)];
pos = next - 1;
break;
}
case OPEN_SQUARE:
case CLOSE_SQUARE:
case OPEN_CURLY:
case CLOSE_CURLY:
case COLON:
case SEMICOLON:
case CLOSE_PARENTHESES: {
let controlChar = String.fromCharCode(code);
currentToken = [controlChar, controlChar, pos];
break;
}
case OPEN_PARENTHESES: {
prev = buffer.length ? buffer.pop()[1] : "";
n = css.charCodeAt(pos + 1);
if (prev === "url" &&
n !== SINGLE_QUOTE &&
n !== DOUBLE_QUOTE &&
n !== SPACE &&
n !== NEWLINE &&
n !== TAB &&
n !== FEED &&
n !== CR) {
next = pos;
do {
escaped = false;
next = css.indexOf(")", next + 1);
if (next === -1) {
if (ignore || ignoreUnclosed) {
next = pos;
break;
}
else {
unclosed("bracket");
}
}
escapePos = next;
while (css.charCodeAt(escapePos - 1) === BACKSLASH) {
escapePos -= 1;
escaped = !escaped;
}
} while (escaped);
currentToken = ["brackets", css.slice(pos, next + 1), pos, next];
pos = next;
}
else {
next = css.indexOf(")", pos + 1);
content = css.slice(pos, next + 1);
if (next === -1 || RE_BAD_BRACKET.test(content)) {
currentToken = ["(", "(", pos];
}
else {
currentToken = ["brackets", content, pos, next];
pos = next;
}
}
break;
}
case SINGLE_QUOTE:
case DOUBLE_QUOTE: {
quote = code === SINGLE_QUOTE ? "'" : '"';
next = pos;
do {
escaped = false;
next = css.indexOf(quote, next + 1);
if (next === -1) {
if (ignore || ignoreUnclosed) {
next = pos + 1;
break;
}
else {
unclosed("string");
}
}
escapePos = next;
while (css.charCodeAt(escapePos - 1) === BACKSLASH) {
escapePos -= 1;
escaped = !escaped;
}
} while (escaped);
currentToken = ["string", css.slice(pos, next + 1), pos, next];
pos = next;
break;
}
case AT: {
RE_AT_END.lastIndex = pos + 1;
RE_AT_END.test(css);
if (RE_AT_END.lastIndex === 0) {
next = css.length - 1;
}
else {
next = RE_AT_END.lastIndex - 2;
}
currentToken = ["at-word", css.slice(pos, next + 1), pos, next];
pos = next;
break;
}
case BACKSLASH: {
next = pos;
escape = true;
while (css.charCodeAt(next + 1) === BACKSLASH) {
next += 1;
escape = !escape;
}
code = css.charCodeAt(next + 1);
if (escape &&
code !== SLASH &&
code !== SPACE &&
code !== NEWLINE &&
code !== TAB &&
code !== CR &&
code !== FEED) {
next += 1;
if (RE_HEX_ESCAPE.test(css.charAt(next))) {
while (RE_HEX_ESCAPE.test(css.charAt(next + 1))) {
next += 1;
}
if (css.charCodeAt(next + 1) === SPACE) {
next += 1;
}
}
}
currentToken = ["word", css.slice(pos, next + 1), pos, next];
pos = next;
break;
}
default: {
if (code === SLASH && css.charCodeAt(pos + 1) === ASTERISK) {
next = css.indexOf("*/", pos + 2) + 1;
if (next === 0) {
if (ignore || ignoreUnclosed) {
next = css.length;
}
else {
unclosed("comment");
}
}
currentToken = ["comment", css.slice(pos, next + 1), pos, next];
pos = next;
}
else {
RE_WORD_END.lastIndex = pos + 1;
RE_WORD_END.test(css);
if (RE_WORD_END.lastIndex === 0) {
next = css.length - 1;
}
else {
next = RE_WORD_END.lastIndex - 2;
}
currentToken = ["word", css.slice(pos, next + 1), pos, next];
buffer.push(currentToken);
pos = next;
}
break;
}
}
pos++;
return currentToken;
}
function back(token) {
returned.push(token);
}
return {
back,
nextToken,
endOfFile,
position,
};
}