'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**
* Check if given code is a number
*/
function isNumber$1(code) {
return code > 47 && code < 58;
}
/**
* Check if given character code is alpha code (letter through A to Z)
*/
function isAlpha$1(code, from, to) {
from = from || 65; // A
to = to || 90; // Z
code &= ~32; // quick hack to convert any char code to uppercase char code
return code >= from && code <= to;
}
function isAlphaNumericWord(code) {
return isNumber$1(code) || isAlphaWord(code);
}
function isAlphaWord(code) {
return code === 95 /* _ */ || isAlpha$1(code);
}
/**
* Check for Umlauts i.e. ä, Ä, ö, Ö, ü and Ü
*/
function isUmlaut(code) {
return code === 196
|| code == 214
|| code === 220
|| code === 228
|| code === 246
|| code === 252;
}
/**
* Check if given character code is a white-space character: a space character
* or line breaks
*/
function isWhiteSpace$3(code) {
return code === 32 /* space */
|| code === 9 /* tab */
|| code === 160; /* non-breaking space */
}
/**
* Check if given character code is a space character
*/
function isSpace(code) {
return isWhiteSpace$3(code)
|| code === 10 /* LF */
|| code === 13; /* CR */
}
/**
* Check if given character code is a quote character
*/
function isQuote$2(code) {
return code === 39 /* ' */ || code === 34 /* " */;
}
/**
* A streaming, character code-based string reader
*/
class Scanner {
constructor(str, start, end) {
if (end == null && typeof str === 'string') {
end = str.length;
}
this.string = str;
this.pos = this.start = start || 0;
this.end = end || 0;
}
/**
* Returns true only if the stream is at the end of the file.
*/
eof() {
return this.pos >= this.end;
}
/**
* Creates a new stream instance which is limited to given `start` and `end`
* range. E.g. its `eof()` method will look at `end` property, not actual
* stream end
*/
limit(start, end) {
return new Scanner(this.string, start, end);
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
*/
peek() {
return this.string.charCodeAt(this.pos);
}
/**
* Returns the next character in the stream and advances it.
* Also returns undefined
when no more characters are available.
*/
next() {
if (this.pos < this.string.length) {
return this.string.charCodeAt(this.pos++);
}
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls eat
with the given argument, until it
* fails. Returns true
if any characters were eaten.
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) { /* */ }
return this.pos !== start;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n) {
this.pos -= (n || 1);
}
/**
* Get the string between the start of the current token and the
* current stream position.
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns substring for given range
*/
substring(start, end) {
return this.string.slice(start, end);
}
/**
* Creates error object with current stream state
*/
error(message, pos = this.pos) {
return new ScannerError(`${message} at ${pos + 1}`, pos, this.string);
}
}
class ScannerError extends Error {
constructor(message, pos, str) {
super(message);
this.pos = pos;
this.string = str;
}
}
function tokenScanner$1(tokens) {
return {
tokens,
start: 0,
pos: 0,
size: tokens.length
};
}
function peek$3(scanner) {
return scanner.tokens[scanner.pos];
}
function next(scanner) {
return scanner.tokens[scanner.pos++];
}
function slice(scanner, from = scanner.start, to = scanner.pos) {
return scanner.tokens.slice(from, to);
}
function readable$1(scanner) {
return scanner.pos < scanner.size;
}
function consume$2(scanner, test) {
const token = peek$3(scanner);
if (token && test(token)) {
scanner.pos++;
return true;
}
return false;
}
function error$1(scanner, message, token = peek$3(scanner)) {
if (token && token.start != null) {
message += ` at ${token.start}`;
}
const err = new Error(message);
err['pos'] = token && token.start;
return err;
}
function abbreviation(abbr, options = {}) {
const scanner = tokenScanner$1(abbr);
const result = statements(scanner, options);
if (readable$1(scanner)) {
throw error$1(scanner, 'Unexpected character');
}
return result;
}
function statements(scanner, options) {
const result = {
type: 'TokenGroup',
elements: []
};
let ctx = result;
let node;
const stack = [];
while (readable$1(scanner)) {
if (node = element$2(scanner, options) || group(scanner, options)) {
ctx.elements.push(node);
if (consume$2(scanner, isChildOperator)) {
stack.push(ctx);
ctx = node;
}
else if (consume$2(scanner, isSiblingOperator$1)) {
continue;
}
else if (consume$2(scanner, isClimbOperator)) {
do {
if (stack.length) {
ctx = stack.pop();
}
} while (consume$2(scanner, isClimbOperator));
}
}
else {
break;
}
}
return result;
}
/**
* Consumes group from given scanner
*/
function group(scanner, options) {
if (consume$2(scanner, isGroupStart)) {
const result = statements(scanner, options);
const token = next(scanner);
if (isBracket$2(token, 'group', false)) {
result.repeat = repeater$1(scanner);
}
return result;
}
}
/**
* Consumes single element from given scanner
*/
function element$2(scanner, options) {
let attr;
const elem = {
type: 'TokenElement',
name: void 0,
attributes: void 0,
value: void 0,
repeat: void 0,
selfClose: false,
elements: []
};
if (elementName(scanner, options)) {
elem.name = slice(scanner);
}
while (readable$1(scanner)) {
scanner.start = scanner.pos;
if (!elem.repeat && !isEmpty(elem) && consume$2(scanner, isRepeater)) {
elem.repeat = scanner.tokens[scanner.pos - 1];
}
else if (!elem.value && text(scanner)) {
elem.value = getText(scanner);
}
else if (attr = shortAttribute(scanner, 'id', options) || shortAttribute(scanner, 'class', options) || attributeSet(scanner)) {
if (!elem.attributes) {
elem.attributes = Array.isArray(attr) ? attr.slice() : [attr];
}
else {
elem.attributes = elem.attributes.concat(attr);
}
}
else {
if (!isEmpty(elem) && consume$2(scanner, isCloseOperator)) {
elem.selfClose = true;
if (!elem.repeat && consume$2(scanner, isRepeater)) {
elem.repeat = scanner.tokens[scanner.pos - 1];
}
}
break;
}
}
return !isEmpty(elem) ? elem : void 0;
}
/**
* Consumes attribute set from given scanner
*/
function attributeSet(scanner) {
if (consume$2(scanner, isAttributeSetStart)) {
const attributes = [];
let attr;
while (readable$1(scanner)) {
if (attr = attribute(scanner)) {
attributes.push(attr);
}
else if (consume$2(scanner, isAttributeSetEnd)) {
break;
}
else if (!consume$2(scanner, isWhiteSpace$2)) {
throw error$1(scanner, `Unexpected "${peek$3(scanner).type}" token`);
}
}
return attributes;
}
}
/**
* Consumes attribute shorthand (class or id) from given scanner
*/
function shortAttribute(scanner, type, options) {
if (isOperator$1(peek$3(scanner), type)) {
scanner.pos++;
// Consume multiple operators
let count = 1;
while (isOperator$1(peek$3(scanner), type)) {
scanner.pos++;
count++;
}
const attr = {
name: [createLiteral$1(type)]
};
if (count > 1) {
attr.multiple = true;
}
// Consume expression after shorthand start for React-like components
if (options.jsx && text(scanner)) {
attr.value = getText(scanner);
attr.expression = true;
}
else {
attr.value = literal$1$1(scanner) ? slice(scanner) : void 0;
}
return attr;
}
}
/**
* Consumes single attribute from given scanner
*/
function attribute(scanner) {
if (quoted(scanner)) {
// Consumed quoted value: it’s a value for default attribute
return {
value: slice(scanner)
};
}
if (literal$1$1(scanner, true)) {
const name = slice(scanner);
let value;
if (consume$2(scanner, isEquals)) {
if (quoted(scanner) || literal$1$1(scanner, true)) {
value = slice(scanner);
}
}
return { name, value };
}
}
function repeater$1(scanner) {
return isRepeater(peek$3(scanner))
? scanner.tokens[scanner.pos++]
: void 0;
}
/**
* Consumes quoted value from given scanner, if possible
*/
function quoted(scanner) {
const start = scanner.pos;
const quote = peek$3(scanner);
if (isQuote$1(quote)) {
scanner.pos++;
while (readable$1(scanner)) {
if (isQuote$1(next(scanner), quote.single)) {
scanner.start = start;
return true;
}
}
throw error$1(scanner, 'Unclosed quote', quote);
}
return false;
}
/**
* Consumes literal (unquoted value) from given scanner
*/
function literal$1$1(scanner, allowBrackets) {
const start = scanner.pos;
const brackets = {
attribute: 0,
expression: 0,
group: 0
};
while (readable$1(scanner)) {
const token = peek$3(scanner);
if (brackets.expression) {
// If we’re inside expression, we should consume all content in it
if (isBracket$2(token, 'expression')) {
brackets[token.context] += token.open ? 1 : -1;
}
}
else if (isQuote$1(token) || isOperator$1(token) || isWhiteSpace$2(token) || isRepeater(token)) {
break;
}
else if (isBracket$2(token)) {
if (!allowBrackets) {
break;
}
if (token.open) {
brackets[token.context]++;
}
else if (!brackets[token.context]) {
// Stop if found unmatched closing brace: it must be handled
// by parent consumer
break;
}
else {
brackets[token.context]--;
}
}
scanner.pos++;
}
if (start !== scanner.pos) {
scanner.start = start;
return true;
}
return false;
}
/**
* Consumes element name from given scanner
*/
function elementName(scanner, options) {
const start = scanner.pos;
if (options.jsx && consume$2(scanner, isCapitalizedLiteral)) {
// Check for edge case: consume immediate capitalized class names
// for React-like components, e.g. `Foo.Bar.Baz`
while (readable$1(scanner)) {
const { pos } = scanner;
if (!consume$2(scanner, isClassNameOperator) || !consume$2(scanner, isCapitalizedLiteral)) {
scanner.pos = pos;
break;
}
}
}
while (readable$1(scanner) && consume$2(scanner, isElementName$1)) {
// empty
}
if (scanner.pos !== start) {
scanner.start = start;
return true;
}
return false;
}
/**
* Consumes text value from given scanner
*/
function text(scanner) {
const start = scanner.pos;
if (consume$2(scanner, isTextStart)) {
let brackets = 0;
while (readable$1(scanner)) {
const token = next(scanner);
if (isBracket$2(token, 'expression')) {
if (token.open) {
brackets++;
}
else if (!brackets) {
break;
}
else {
brackets--;
}
}
}
scanner.start = start;
return true;
}
return false;
}
function getText(scanner) {
let from = scanner.start;
let to = scanner.pos;
if (isBracket$2(scanner.tokens[from], 'expression', true)) {
from++;
}
if (isBracket$2(scanner.tokens[to - 1], 'expression', false)) {
to--;
}
return slice(scanner, from, to);
}
function isBracket$2(token, context, isOpen) {
return Boolean(token && token.type === 'Bracket'
&& (!context || token.context === context)
&& (isOpen == null || token.open === isOpen));
}
function isOperator$1(token, type) {
return Boolean(token && token.type === 'Operator' && (!type || token.operator === type));
}
function isQuote$1(token, isSingle) {
return Boolean(token && token.type === 'Quote' && (isSingle == null || token.single === isSingle));
}
function isWhiteSpace$2(token) {
return Boolean(token && token.type === 'WhiteSpace');
}
function isEquals(token) {
return isOperator$1(token, 'equal');
}
function isRepeater(token) {
return Boolean(token && token.type === 'Repeater');
}
function isLiteral$2(token) {
return token.type === 'Literal';
}
function isCapitalizedLiteral(token) {
if (isLiteral$2(token)) {
const ch = token.value.charCodeAt(0);
return ch >= 65 && ch <= 90;
}
return false;
}
function isElementName$1(token) {
return token.type === 'Literal' || token.type === 'RepeaterNumber' || token.type === 'RepeaterPlaceholder';
}
function isClassNameOperator(token) {
return isOperator$1(token, 'class');
}
function isAttributeSetStart(token) {
return isBracket$2(token, 'attribute', true);
}
function isAttributeSetEnd(token) {
return isBracket$2(token, 'attribute', false);
}
function isTextStart(token) {
return isBracket$2(token, 'expression', true);
}
function isGroupStart(token) {
return isBracket$2(token, 'group', true);
}
function createLiteral$1(value) {
return { type: 'Literal', value };
}
function isEmpty(elem) {
return !elem.name && !elem.value && !elem.attributes;
}
function isChildOperator(token) {
return isOperator$1(token, 'child');
}
function isSiblingOperator$1(token) {
return isOperator$1(token, 'sibling');
}
function isClimbOperator(token) {
return isOperator$1(token, 'climb');
}
function isCloseOperator(token) {
return isOperator$1(token, 'close');
}
var Chars$3;
(function (Chars) {
/** `{` character */
Chars[Chars["CurlyBracketOpen"] = 123] = "CurlyBracketOpen";
/** `}` character */
Chars[Chars["CurlyBracketClose"] = 125] = "CurlyBracketClose";
/** `\\` character */
Chars[Chars["Escape"] = 92] = "Escape";
/** `=` character */
Chars[Chars["Equals"] = 61] = "Equals";
/** `[` character */
Chars[Chars["SquareBracketOpen"] = 91] = "SquareBracketOpen";
/** `]` character */
Chars[Chars["SquareBracketClose"] = 93] = "SquareBracketClose";
/** `*` character */
Chars[Chars["Asterisk"] = 42] = "Asterisk";
/** `#` character */
Chars[Chars["Hash"] = 35] = "Hash";
/** `$` character */
Chars[Chars["Dollar"] = 36] = "Dollar";
/** `-` character */
Chars[Chars["Dash"] = 45] = "Dash";
/** `.` character */
Chars[Chars["Dot"] = 46] = "Dot";
/** `/` character */
Chars[Chars["Slash"] = 47] = "Slash";
/** `:` character */
Chars[Chars["Colon"] = 58] = "Colon";
/** `!` character */
Chars[Chars["Excl"] = 33] = "Excl";
/** `@` character */
Chars[Chars["At"] = 64] = "At";
/** `_` character */
Chars[Chars["Underscore"] = 95] = "Underscore";
/** `(` character */
Chars[Chars["RoundBracketOpen"] = 40] = "RoundBracketOpen";
/** `)` character */
Chars[Chars["RoundBracketClose"] = 41] = "RoundBracketClose";
/** `+` character */
Chars[Chars["Sibling"] = 43] = "Sibling";
/** `>` character */
Chars[Chars["Child"] = 62] = "Child";
/** `^` character */
Chars[Chars["Climb"] = 94] = "Climb";
/** `'` character */
Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
/** `""` character */
Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
})(Chars$3 || (Chars$3 = {}));
/**
* If consumes escape character, sets current stream range to escaped value
*/
function escaped(scanner) {
if (scanner.eat(Chars$3.Escape)) {
scanner.start = scanner.pos;
if (!scanner.eof()) {
scanner.pos++;
}
return true;
}
return false;
}
function tokenize$1(source) {
const scanner = new Scanner(source);
const result = [];
const ctx = {
group: 0,
attribute: 0,
expression: 0,
quote: 0
};
let ch = 0;
let token;
while (!scanner.eof()) {
ch = scanner.peek();
token = getToken$1(scanner, ctx);
if (token) {
result.push(token);
if (token.type === 'Quote') {
ctx.quote = ch === ctx.quote ? 0 : ch;
}
else if (token.type === 'Bracket') {
ctx[token.context] += token.open ? 1 : -1;
}
}
else {
throw scanner.error('Unexpected character');
}
}
return result;
}
/**
* Returns next token from given scanner, if possible
*/
function getToken$1(scanner, ctx) {
return field$2(scanner, ctx)
|| repeaterPlaceholder(scanner)
|| repeaterNumber(scanner)
|| repeater(scanner)
|| whiteSpace$1(scanner)
|| literal$2(scanner, ctx)
|| operator$1(scanner)
|| quote(scanner)
|| bracket$1(scanner);
}
/**
* Consumes literal from given scanner
*/
function literal$2(scanner, ctx) {
const start = scanner.pos;
const expressionStart = ctx.expression;
let value = '';
while (!scanner.eof()) {
// Consume escaped sequence no matter of context
if (escaped(scanner)) {
value += scanner.current();
continue;
}
const ch = scanner.peek();
if (ch === Chars$3.Slash && !ctx.quote && !ctx.expression && !ctx.attribute) {
// Special case for `/` character between numbers in class names
const prev = scanner.string.charCodeAt(scanner.pos - 1);
const next = scanner.string.charCodeAt(scanner.pos + 1);
if (isNumber$1(prev) && isNumber$1(next)) {
value += scanner.string[scanner.pos++];
continue;
}
}
if (ch === ctx.quote || ch === Chars$3.Dollar || isAllowedOperator(ch, ctx)) {
// 1. Found matching quote
// 2. The `$` character has special meaning in every context
// 3. Depending on context, some characters should be treated as operators
break;
}
if (expressionStart) {
// Consume nested expressions, e.g. span{{foo}}
if (ch === Chars$3.CurlyBracketOpen) {
ctx.expression++;
}
else if (ch === Chars$3.CurlyBracketClose) {
if (ctx.expression > expressionStart) {
ctx.expression--;
}
else {
break;
}
}
}
else if (!ctx.quote) {
// Consuming element name
if (!ctx.attribute && !isElementName(ch)) {
break;
}
if (isAllowedSpace(ch, ctx) || isAllowedRepeater(ch, ctx) || isQuote$2(ch) || bracketType(ch)) {
// Stop for characters not allowed in unquoted literal
break;
}
}
value += scanner.string[scanner.pos++];
}
if (start !== scanner.pos) {
scanner.start = start;
return {
type: 'Literal',
value,
start,
end: scanner.pos
};
}
}
/**
* Consumes white space characters as string literal from given scanner
*/
function whiteSpace$1(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(isSpace)) {
return {
type: 'WhiteSpace',
start,
end: scanner.pos,
value: scanner.substring(start, scanner.pos)
};
}
}
/**
* Consumes quote from given scanner
*/
function quote(scanner) {
const ch = scanner.peek();
if (isQuote$2(ch)) {
return {
type: 'Quote',
single: ch === Chars$3.SingleQuote,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes bracket from given scanner
*/
function bracket$1(scanner) {
const ch = scanner.peek();
const context = bracketType(ch);
if (context) {
return {
type: 'Bracket',
open: isOpenBracket$2(ch),
context,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes operator from given scanner
*/
function operator$1(scanner) {
const op = operatorType$1(scanner.peek());
if (op) {
return {
type: 'Operator',
operator: op,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes node repeat token from current stream position and returns its
* parsed value
*/
function repeater(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars$3.Asterisk)) {
scanner.start = scanner.pos;
let count = 1;
let implicit = false;
if (scanner.eatWhile(isNumber$1)) {
count = Number(scanner.current());
}
else {
implicit = true;
}
return {
type: 'Repeater',
count,
value: 0,
implicit,
start,
end: scanner.pos
};
}
}
/**
* Consumes repeater placeholder `$#` from given scanner
*/
function repeaterPlaceholder(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars$3.Dollar) && scanner.eat(Chars$3.Hash)) {
return {
type: 'RepeaterPlaceholder',
value: void 0,
start,
end: scanner.pos
};
}
scanner.pos = start;
}
/**
* Consumes numbering token like `$` from given scanner state
*/
function repeaterNumber(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(Chars$3.Dollar)) {
const size = scanner.pos - start;
let reverse = false;
let base = 1;
let parent = 0;
if (scanner.eat(Chars$3.At)) {
// Consume numbering modifiers
while (scanner.eat(Chars$3.Climb)) {
parent++;
}
reverse = scanner.eat(Chars$3.Dash);
scanner.start = scanner.pos;
if (scanner.eatWhile(isNumber$1)) {
base = Number(scanner.current());
}
}
scanner.start = start;
return {
type: 'RepeaterNumber',
size,
reverse,
base,
parent,
start,
end: scanner.pos
};
}
}
function field$2(scanner, ctx) {
const start = scanner.pos;
// Fields are allowed inside expressions and attributes
if ((ctx.expression || ctx.attribute) && scanner.eat(Chars$3.Dollar) && scanner.eat(Chars$3.CurlyBracketOpen)) {
scanner.start = scanner.pos;
let index;
let name = '';
if (scanner.eatWhile(isNumber$1)) {
// It’s a field
index = Number(scanner.current());
name = scanner.eat(Chars$3.Colon) ? consumePlaceholder$2(scanner) : '';
}
else if (isAlpha$1(scanner.peek())) {
// It’s a variable
name = consumePlaceholder$2(scanner);
}
if (scanner.eat(Chars$3.CurlyBracketClose)) {
return {
type: 'Field',
index, name,
start,
end: scanner.pos
};
}
throw scanner.error('Expecting }');
}
// If we reached here then there’s no valid field here, revert
// back to starting position
scanner.pos = start;
}
/**
* Consumes a placeholder: value right after `:` in field. Could be empty
*/
function consumePlaceholder$2(stream) {
const stack = [];
stream.start = stream.pos;
while (!stream.eof()) {
if (stream.eat(Chars$3.CurlyBracketOpen)) {
stack.push(stream.pos);
}
else if (stream.eat(Chars$3.CurlyBracketClose)) {
if (!stack.length) {
stream.pos--;
break;
}
stack.pop();
}
else {
stream.pos++;
}
}
if (stack.length) {
stream.pos = stack.pop();
throw stream.error(`Expecting }`);
}
return stream.current();
}
/**
* Check if given character code is an operator and it’s allowed in current context
*/
function isAllowedOperator(ch, ctx) {
const op = operatorType$1(ch);
if (!op || ctx.quote || ctx.expression) {
// No operators inside quoted values or expressions
return false;
}
// Inside attributes, only `equals` is allowed
return !ctx.attribute || op === 'equal';
}
/**
* Check if given character is a space character and is allowed to be consumed
* as a space token in current context
*/
function isAllowedSpace(ch, ctx) {
return isSpace(ch) && !ctx.expression;
}
/**
* Check if given character can be consumed as repeater in current context
*/
function isAllowedRepeater(ch, ctx) {
return ch === Chars$3.Asterisk && !ctx.attribute && !ctx.expression;
}
/**
* If given character is a bracket, returns it’s type
*/
function bracketType(ch) {
if (ch === Chars$3.RoundBracketOpen || ch === Chars$3.RoundBracketClose) {
return 'group';
}
if (ch === Chars$3.SquareBracketOpen || ch === Chars$3.SquareBracketClose) {
return 'attribute';
}
if (ch === Chars$3.CurlyBracketOpen || ch === Chars$3.CurlyBracketClose) {
return 'expression';
}
}
/**
* If given character is an operator, returns it’s type
*/
function operatorType$1(ch) {
return (ch === Chars$3.Child && 'child')
|| (ch === Chars$3.Sibling && 'sibling')
|| (ch === Chars$3.Climb && 'climb')
|| (ch === Chars$3.Dot && 'class')
|| (ch === Chars$3.Hash && 'id')
|| (ch === Chars$3.Slash && 'close')
|| (ch === Chars$3.Equals && 'equal')
|| void 0;
}
/**
* Check if given character is an open bracket
*/
function isOpenBracket$2(ch) {
return ch === Chars$3.CurlyBracketOpen
|| ch === Chars$3.SquareBracketOpen
|| ch === Chars$3.RoundBracketOpen;
}
/**
* Check if given character is allowed in element name
*/
function isElementName(ch) {
return isAlphaNumericWord(ch)
|| isUmlaut(ch)
|| ch === Chars$3.Dash
|| ch === Chars$3.Colon
|| ch === Chars$3.Excl;
}
const operators = {
child: '>',
class: '.',
climb: '^',
id: '#',
equal: '=',
close: '/',
sibling: '+'
};
const tokenVisitor = {
Literal(token) {
return token.value;
},
Quote(token) {
return token.single ? '\'' : '"';
},
Bracket(token) {
if (token.context === 'attribute') {
return token.open ? '[' : ']';
}
else if (token.context === 'expression') {
return token.open ? '{' : '}';
}
else {
return token.open ? '(' : '}';
}
},
Operator(token) {
return operators[token.operator];
},
Field(token, state) {
if (token.index != null) {
// It’s a field: by default, return TextMate-compatible field
return token.name
? `\${${token.index}:${token.name}}`
: `\${${token.index}`;
}
else if (token.name) {
// It’s a variable
return state.getVariable(token.name);
}
return '';
},
RepeaterPlaceholder(token, state) {
// Find closest implicit repeater
let repeater;
for (let i = state.repeaters.length - 1; i >= 0; i--) {
if (state.repeaters[i].implicit) {
repeater = state.repeaters[i];
break;
}
}
state.inserted = true;
return state.getText(repeater && repeater.value);
},
RepeaterNumber(token, state) {
let value = 1;
const lastIx = state.repeaters.length - 1;
// const repeaterIx = Math.max(0, state.repeaters.length - 1 - token.parent);
const repeater = state.repeaters[lastIx];
if (repeater) {
value = token.reverse
? token.base + repeater.count - repeater.value - 1
: token.base + repeater.value;
if (token.parent) {
const parentIx = Math.max(0, lastIx - token.parent);
if (parentIx !== lastIx) {
const parentRepeater = state.repeaters[parentIx];
value += repeater.count * parentRepeater.value;
}
}
}
let result = String(value);
while (result.length < token.size) {
result = '0' + result;
}
return result;
},
WhiteSpace(token) {
return token.value;
}
};
/**
* Converts given value token to string
*/
function stringify$1(token, state) {
if (!tokenVisitor[token.type]) {
throw new Error(`Unknown token ${token.type}`);
}
return tokenVisitor[token.type](token, state);
}
const urlRegex = /^((https?:|ftp:|file:)?\/\/|(www|ftp)\.)[^ ]*$/;
const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,5}$/;
/**
* Converts given token-based abbreviation into simplified and unrolled node-based
* abbreviation
*/
function convert(abbr, options = {}) {
let textInserted = false;
let cleanText;
if (options.text) {
if (Array.isArray(options.text)) {
cleanText = options.text.filter(s => s.trim());
}
else {
cleanText = options.text;
}
}
const result = {
type: 'Abbreviation',
children: convertGroup(abbr, {
inserted: false,
repeaters: [],
text: options.text,
cleanText,
repeatGuard: options.maxRepeat || Number.POSITIVE_INFINITY,
getText(pos) {
var _a;
textInserted = true;
let value;
if (Array.isArray(options.text)) {
if (pos !== undefined && pos >= 0 && pos < cleanText.length) {
return cleanText[pos];
}
value = pos !== undefined ? options.text[pos] : options.text.join('\n');
}
else {
value = (_a = options.text) !== null && _a !== void 0 ? _a : '';
}
return value;
},
getVariable(name) {
const varValue = options.variables && options.variables[name];
return varValue != null ? varValue : name;
}
})
};
if (options.text != null && !textInserted) {
// Text given but no implicitly repeated elements: insert it into
// deepest child
const deepest = deepestNode(last$1(result.children));
if (deepest) {
const text = Array.isArray(options.text) ? options.text.join('\n') : options.text;
insertText(deepest, text);
if (deepest.name === 'a' && options.href) {
// Automatically update value of `` element if inserting URL or email
insertHref(deepest, text);
}
}
}
return result;
}
/**
* Converts given statement to abbreviation nodes
*/
function convertStatement(node, state) {
let result = [];
if (node.repeat) {
// Node is repeated: we should create copies of given node
// and supply context token with actual repeater state
const original = node.repeat;
const repeat = Object.assign({}, original);
repeat.count = repeat.implicit && Array.isArray(state.text)
? state.cleanText.length
: (repeat.count || 1);
let items;
state.repeaters.push(repeat);
for (let i = 0; i < repeat.count; i++) {
repeat.value = i;
node.repeat = repeat;
items = isGroup(node)
? convertGroup(node, state)
: convertElement(node, state);
if (repeat.implicit && !state.inserted) {
// It’s an implicit repeater but no repeater placeholders found inside,
// we should insert text into deepest node
const target = last$1(items);
const deepest = target && deepestNode(target);
if (deepest) {
insertText(deepest, state.getText(repeat.value));
}
}
result = result.concat(items);
// We should output at least one repeated item even if it’s reached
// repeat limit
if (--state.repeatGuard <= 0) {
break;
}
}
state.repeaters.pop();
node.repeat = original;
if (repeat.implicit) {
state.inserted = true;
}
}
else {
result = result.concat(isGroup(node) ? convertGroup(node, state) : convertElement(node, state));
}
return result;
}
function convertElement(node, state) {
let children = [];
const elem = {
type: 'AbbreviationNode',
name: node.name && stringifyName(node.name, state),
value: node.value && stringifyValue$1(node.value, state),
attributes: void 0,
children,
repeat: node.repeat && Object.assign({}, node.repeat),
selfClosing: node.selfClose,
};
let result = [elem];
for (const child of node.elements) {
children = children.concat(convertStatement(child, state));
}
if (node.attributes) {
elem.attributes = [];
for (const attr of node.attributes) {
elem.attributes.push(convertAttribute(attr, state));
}
}
// In case if current node is a text-only snippet without fields, we should
// put all children as siblings
if (!elem.name && !elem.attributes && elem.value && !elem.value.some(isField$1)) {
// XXX it’s unclear that `children` is not bound to `elem`
// due to concat operation
result = result.concat(children);
}
else {
elem.children = children;
}
return result;
}
function convertGroup(node, state) {
let result = [];
for (const child of node.elements) {
result = result.concat(convertStatement(child, state));
}
if (node.repeat) {
result = attachRepeater(result, node.repeat);
}
return result;
}
function convertAttribute(node, state) {
let implied = false;
let isBoolean = false;
let valueType = node.expression ? 'expression' : 'raw';
let value;
const name = node.name && stringifyName(node.name, state);
if (name && name[0] === '!') {
implied = true;
}
if (name && name[name.length - 1] === '.') {
isBoolean = true;
}
if (node.value) {
const tokens = node.value.slice();
if (isQuote$1(tokens[0])) {
// It’s a quoted value: remove quotes from output but mark attribute
// value as quoted
const quote = tokens.shift();
if (tokens.length && last$1(tokens).type === quote.type) {
tokens.pop();
}
valueType = quote.single ? 'singleQuote' : 'doubleQuote';
}
else if (isBracket$2(tokens[0], 'expression', true)) {
// Value is expression: remove brackets but mark value type
valueType = 'expression';
tokens.shift();
if (isBracket$2(last$1(tokens), 'expression', false)) {
tokens.pop();
}
}
value = stringifyValue$1(tokens, state);
}
return {
name: isBoolean || implied
? name.slice(implied ? 1 : 0, isBoolean ? -1 : void 0)
: name,
value,
boolean: isBoolean,
implied,
valueType,
multiple: node.multiple
};
}
/**
* Converts given token list to string
*/
function stringifyName(tokens, state) {
let str = '';
for (let i = 0; i < tokens.length; i++) {
str += stringify$1(tokens[i], state);
}
return str;
}
/**
* Converts given token list to value list
*/
function stringifyValue$1(tokens, state) {
const result = [];
let str = '';
for (let i = 0, token; i < tokens.length; i++) {
token = tokens[i];
if (isField$1(token)) {
// We should keep original fields in output since some editors has their
// own syntax for field or doesn’t support fields at all so we should
// capture actual field location in output stream
if (str) {
result.push(str);
str = '';
}
result.push(token);
}
else {
str += stringify$1(token, state);
}
}
if (str) {
result.push(str);
}
return result;
}
function isGroup(node) {
return node.type === 'TokenGroup';
}
function isField$1(token) {
return typeof token === 'object' && token.type === 'Field' && token.index != null;
}
function last$1(arr) {
return arr[arr.length - 1];
}
function deepestNode(node) {
return node.children.length ? deepestNode(last$1(node.children)) : node;
}
function insertText(node, text) {
if (node.value) {
const lastToken = last$1(node.value);
if (typeof lastToken === 'string') {
node.value[node.value.length - 1] += text;
}
else {
node.value.push(text);
}
}
else {
node.value = [text];
}
}
function insertHref(node, text) {
var _a;
let href = '';
if (urlRegex.test(text)) {
href = text;
if (!/\w+:/.test(href) && !href.startsWith('//')) {
href = `http://${href}`;
}
}
else if (emailRegex.test(text)) {
href = `mailto:${text}`;
}
const hrefAttribute = (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.find(attr => attr.name === 'href');
if (!hrefAttribute) {
if (!node.attributes) {
node.attributes = [];
}
node.attributes.push({ name: 'href', value: [href], valueType: 'doubleQuote' });
}
else if (!hrefAttribute.value) {
hrefAttribute.value = [href];
}
}
function attachRepeater(items, repeater) {
for (const item of items) {
if (!item.repeat) {
item.repeat = Object.assign({}, repeater);
}
}
return items;
}
/**
* Parses given abbreviation into node tree
*/
function parseAbbreviation(abbr, options) {
try {
const tokens = typeof abbr === 'string' ? tokenize$1(abbr) : abbr;
return convert(abbreviation(tokens, options), options);
}
catch (err) {
if (err instanceof ScannerError && typeof abbr === 'string') {
err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`;
}
throw err;
}
}
var OperatorType;
(function (OperatorType) {
OperatorType["Sibling"] = "+";
OperatorType["Important"] = "!";
OperatorType["ArgumentDelimiter"] = ",";
OperatorType["ValueDelimiter"] = "-";
OperatorType["PropertyDelimiter"] = ":";
})(OperatorType || (OperatorType = {}));
var Chars$2;
(function (Chars) {
/** `#` character */
Chars[Chars["Hash"] = 35] = "Hash";
/** `$` character */
Chars[Chars["Dollar"] = 36] = "Dollar";
/** `-` character */
Chars[Chars["Dash"] = 45] = "Dash";
/** `.` character */
Chars[Chars["Dot"] = 46] = "Dot";
/** `:` character */
Chars[Chars["Colon"] = 58] = "Colon";
/** `,` character */
Chars[Chars["Comma"] = 44] = "Comma";
/** `!` character */
Chars[Chars["Excl"] = 33] = "Excl";
/** `@` character */
Chars[Chars["At"] = 64] = "At";
/** `%` character */
Chars[Chars["Percent"] = 37] = "Percent";
/** `_` character */
Chars[Chars["Underscore"] = 95] = "Underscore";
/** `(` character */
Chars[Chars["RoundBracketOpen"] = 40] = "RoundBracketOpen";
/** `)` character */
Chars[Chars["RoundBracketClose"] = 41] = "RoundBracketClose";
/** `{` character */
Chars[Chars["CurlyBracketOpen"] = 123] = "CurlyBracketOpen";
/** `}` character */
Chars[Chars["CurlyBracketClose"] = 125] = "CurlyBracketClose";
/** `+` character */
Chars[Chars["Sibling"] = 43] = "Sibling";
/** `'` character */
Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
/** `"` character */
Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
/** `t` character */
Chars[Chars["Transparent"] = 116] = "Transparent";
/** `/` character */
Chars[Chars["Slash"] = 47] = "Slash";
})(Chars$2 || (Chars$2 = {}));
function tokenize(abbr, isValue) {
let brackets = 0;
let token;
const scanner = new Scanner(abbr);
const tokens = [];
while (!scanner.eof()) {
token = getToken(scanner, brackets === 0 && !isValue);
if (!token) {
throw scanner.error('Unexpected character');
}
if (token.type === 'Bracket') {
if (!brackets && token.open) {
mergeTokens(scanner, tokens);
}
brackets += token.open ? 1 : -1;
if (brackets < 0) {
throw scanner.error('Unexpected bracket', token.start);
}
}
tokens.push(token);
// Forcibly consume next operator after unit-less numeric value or color:
// next dash `-` must be used as value delimiter
if (shouldConsumeDashAfter(token) && (token = operator(scanner))) {
tokens.push(token);
}
}
return tokens;
}
/**
* Returns next token from given scanner, if possible
*/
function getToken(scanner, short) {
return field$1(scanner)
|| customProperty(scanner)
|| numberValue(scanner)
|| colorValue(scanner)
|| stringValue(scanner)
|| bracket(scanner)
|| operator(scanner)
|| whiteSpace(scanner)
|| literal$1(scanner, short);
}
function field$1(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars$2.Dollar) && scanner.eat(Chars$2.CurlyBracketOpen)) {
scanner.start = scanner.pos;
let index;
let name = '';
if (scanner.eatWhile(isNumber$1)) {
// It’s a field
index = Number(scanner.current());
name = scanner.eat(Chars$2.Colon) ? consumePlaceholder$1(scanner) : '';
}
else if (isAlpha$1(scanner.peek())) {
// It’s a variable
name = consumePlaceholder$1(scanner);
}
if (scanner.eat(Chars$2.CurlyBracketClose)) {
return {
type: 'Field',
index, name,
start,
end: scanner.pos
};
}
throw scanner.error('Expecting }');
}
// If we reached here then there’s no valid field here, revert
// back to starting position
scanner.pos = start;
}
/**
* Consumes a placeholder: value right after `:` in field. Could be empty
*/
function consumePlaceholder$1(stream) {
const stack = [];
stream.start = stream.pos;
while (!stream.eof()) {
if (stream.eat(Chars$2.CurlyBracketOpen)) {
stack.push(stream.pos);
}
else if (stream.eat(Chars$2.CurlyBracketClose)) {
if (!stack.length) {
stream.pos--;
break;
}
stack.pop();
}
else {
stream.pos++;
}
}
if (stack.length) {
stream.pos = stack.pop();
throw stream.error(`Expecting }`);
}
return stream.current();
}
/**
* Consumes literal from given scanner
* @param short Use short notation for consuming value.
* The difference between “short” and “full” notation is that first one uses
* alpha characters only and used for extracting keywords from abbreviation,
* while “full” notation also supports numbers and dashes
*/
function literal$1(scanner, short) {
const start = scanner.pos;
if (scanner.eat(isIdentPrefix)) {
// SCSS or LESS variable
// NB a bit dirty hack: if abbreviation starts with identifier prefix,
// consume alpha characters only to allow embedded variables
scanner.eatWhile(start ? isKeyword : isLiteral$1);
}
else if (scanner.eat(isAlphaWord)) {
scanner.eatWhile(short ? isLiteral$1 : isKeyword);
}
else {
// Allow dots only at the beginning of literal
scanner.eat(Chars$2.Dot);
scanner.eatWhile(isLiteral$1);
}
if (start !== scanner.pos) {
scanner.start = start;
return createLiteral(scanner, scanner.start = start);
}
}
function createLiteral(scanner, start = scanner.start, end = scanner.pos) {
return {
type: 'Literal',
value: scanner.substring(start, end),
start,
end
};
}
/**
* Consumes numeric CSS value (number with optional unit) from current stream,
* if possible
*/
function numberValue(scanner) {
const start = scanner.pos;
if (consumeNumber(scanner)) {
scanner.start = start;
const rawValue = scanner.current();
// eat unit, which can be a % or alpha word
scanner.start = scanner.pos;
scanner.eat(Chars$2.Percent) || scanner.eatWhile(isAlphaWord);
return {
type: 'NumberValue',
value: Number(rawValue),
rawValue,
unit: scanner.current(),
start,
end: scanner.pos
};
}
}
/**
* Consumes quoted string value from given scanner
*/
function stringValue(scanner) {
const ch = scanner.peek();
const start = scanner.pos;
let finished = false;
if (isQuote$2(ch)) {
scanner.pos++;
while (!scanner.eof()) {
// Do not throw error on malformed string
if (scanner.eat(ch)) {
finished = true;
break;
}
else {
scanner.pos++;
}
}
scanner.start = start;
return {
type: 'StringValue',
value: scanner.substring(start + 1, scanner.pos - (finished ? 1 : 0)),
quote: ch === Chars$2.SingleQuote ? 'single' : 'double',
start,
end: scanner.pos
};
}
}
/**
* Consumes a color token from given string
*/
function colorValue(scanner) {
// supported color variations:
// #abc → #aabbccc
// #0 → #000000
// #fff.5 → rgba(255, 255, 255, 0.5)
// #t → transparent
const start = scanner.pos;
if (scanner.eat(Chars$2.Hash)) {
const valueStart = scanner.pos;
let color = '';
let alpha = '';
if (scanner.eatWhile(isHex)) {
color = scanner.substring(valueStart, scanner.pos);
alpha = colorAlpha(scanner);
}
else if (scanner.eat(Chars$2.Transparent)) {
color = '0';
alpha = colorAlpha(scanner) || '0';
}
else {
alpha = colorAlpha(scanner);
}
if (color || alpha || scanner.eof()) {
const { r, g, b, a } = parseColor(color, alpha);
return {
type: 'ColorValue',
r, g, b, a,
raw: scanner.substring(start + 1, scanner.pos),
start,
end: scanner.pos
};
}
else {
// Consumed # but no actual value: invalid color value, treat it as literal
return createLiteral(scanner, start);
}
}
scanner.pos = start;
}
/**
* Consumes alpha value of color: `.1`
*/
function colorAlpha(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars$2.Dot)) {
scanner.start = start;
if (scanner.eatWhile(isNumber$1)) {
return scanner.current();
}
return '1';
}
return '';
}
/**
* Consumes white space characters as string literal from given scanner
*/
function whiteSpace(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(isSpace)) {
return {
type: 'WhiteSpace',
start,
end: scanner.pos
};
}
}
/**
* Consumes custom CSS property: --foo-bar
*/
function customProperty(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars$2.Dash) && scanner.eat(Chars$2.Dash)) {
scanner.start = start;
scanner.eatWhile(isKeyword);
return {
type: 'CustomProperty',
value: scanner.current(),
start,
end: scanner.pos
};
}
scanner.pos = start;
}
/**
* Consumes bracket from given scanner
*/
function bracket(scanner) {
const ch = scanner.peek();
if (isBracket$1(ch)) {
return {
type: 'Bracket',
open: ch === Chars$2.RoundBracketOpen,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes operator from given scanner
*/
function operator(scanner) {
const op = operatorType(scanner.peek());
if (op) {
return {
type: 'Operator',
operator: op,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Eats number value from given stream
* @return Returns `true` if number was consumed
*/
function consumeNumber(stream) {
const start = stream.pos;
stream.eat(Chars$2.Dash);
const afterNegative = stream.pos;
const hasDecimal = stream.eatWhile(isNumber$1);
const prevPos = stream.pos;
if (stream.eat(Chars$2.Dot)) {
// It’s perfectly valid to have numbers like `1.`, which enforces
// value to float unit type
const hasFloat = stream.eatWhile(isNumber$1);
if (!hasDecimal && !hasFloat) {
// Lone dot
stream.pos = prevPos;
}
}
// Edge case: consumed dash only: not a number, bail-out
if (stream.pos === afterNegative) {
stream.pos = start;
}
return stream.pos !== start;
}
function isIdentPrefix(code) {
return code === Chars$2.At || code === Chars$2.Dollar;
}
/**
* If given character is an operator, returns it’s type
*/
function operatorType(ch) {
return (ch === Chars$2.Sibling && OperatorType.Sibling)
|| (ch === Chars$2.Excl && OperatorType.Important)
|| (ch === Chars$2.Comma && OperatorType.ArgumentDelimiter)
|| (ch === Chars$2.Colon && OperatorType.PropertyDelimiter)
|| (ch === Chars$2.Dash && OperatorType.ValueDelimiter)
|| void 0;
}
/**
* Check if given code is a hex value (/0-9a-f/)
*/
function isHex(code) {
return isNumber$1(code) || isAlpha$1(code, 65, 70); // A-F
}
function isKeyword(code) {
return isAlphaNumericWord(code) || code === Chars$2.Dash;
}
function isBracket$1(code) {
return code === Chars$2.RoundBracketOpen || code === Chars$2.RoundBracketClose;
}
function isLiteral$1(code) {
return isAlphaWord(code) || code === Chars$2.Percent || code === Chars$2.Slash;
}
/**
* Parses given color value from abbreviation into RGBA format
*/
function parseColor(value, alpha) {
let r = '0';
let g = '0';
let b = '0';
let a = Number(alpha != null && alpha !== '' ? alpha : 1);
if (value === 't') {
a = 0;
}
else {
switch (value.length) {
case 0:
break;
case 1:
r = g = b = value + value;
break;
case 2:
r = g = b = value;
break;
case 3:
r = value[0] + value[0];
g = value[1] + value[1];
b = value[2] + value[2];
break;
default:
value += value;
r = value.slice(0, 2);
g = value.slice(2, 4);
b = value.slice(4, 6);
}
}
return {
r: parseInt(r, 16),
g: parseInt(g, 16),
b: parseInt(b, 16),
a
};
}
/**
* Check if scanner reader must consume dash after given token.
* Used in cases where user must explicitly separate numeric values
*/
function shouldConsumeDashAfter(token) {
return token.type === 'ColorValue' || (token.type === 'NumberValue' && !token.unit);
}
/**
* Merges last adjacent tokens into a single literal.
* This function is used to overcome edge case when function name was parsed
* as a list of separate tokens. For example, a `scale3d()` value will be
* parsed as literal and number tokens (`scale` and `3d`) which is a perfectly
* valid abbreviation but undesired result. This function will detect last adjacent
* literal and number values and combine them into single literal
*/
function mergeTokens(scanner, tokens) {
let start = 0;
let end = 0;
while (tokens.length) {
const token = last(tokens);
if (token.type === 'Literal' || token.type === 'NumberValue') {
start = token.start;
if (!end) {
end = token.end;
}
tokens.pop();
}
else {
break;
}
}
if (start !== end) {
tokens.push(createLiteral(scanner, start, end));
}
}
function last(arr) {
return arr[arr.length - 1];
}
function tokenScanner(tokens) {
return {
tokens,
start: 0,
pos: 0,
size: tokens.length
};
}
function peek$2(scanner) {
return scanner.tokens[scanner.pos];
}
function readable(scanner) {
return scanner.pos < scanner.size;
}
function consume$1(scanner, test) {
if (test(peek$2(scanner))) {
scanner.pos++;
return true;
}
return false;
}
function error(scanner, message, token = peek$2(scanner)) {
if (token && token.start != null) {
message += ` at ${token.start}`;
}
const err = new Error(message);
err['pos'] = token && token.start;
return err;
}
function parser(tokens, options = {}) {
const scanner = tokenScanner(tokens);
const result = [];
let property;
while (readable(scanner)) {
if (property = consumeProperty(scanner, options)) {
result.push(property);
}
else if (!consume$1(scanner, isSiblingOperator)) {
throw error(scanner, 'Unexpected token');
}
}
return result;
}
/**
* Consumes single CSS property
*/
function consumeProperty(scanner, options) {
let name;
let important = false;
let valueFragment;
const value = [];
const token = peek$2(scanner);
const valueMode = !!options.value;
if (!valueMode && isLiteral(token) && !isFunctionStart(scanner)) {
scanner.pos++;
name = token.value;
// Consume any following value delimiter after property name
consume$1(scanner, isValueDelimiter);
}
// Skip whitespace right after property name, if any
if (valueMode) {
consume$1(scanner, isWhiteSpace$1);
}
while (readable(scanner)) {
if (consume$1(scanner, isImportant)) {
important = true;
}
else if (valueFragment = consumeValue(scanner, valueMode)) {
value.push(valueFragment);
}
else if (!consume$1(scanner, isFragmentDelimiter)) {
break;
}
}
if (name || value.length || important) {
return { name, value, important };
}
}
/**
* Consumes single value fragment, e.g. all value tokens before comma
*/
function consumeValue(scanner, inArgument) {
const result = [];
let token;
let args;
while (readable(scanner)) {
token = peek$2(scanner);
if (isValue(token)) {
scanner.pos++;
if (isLiteral(token) && (args = consumeArguments(scanner))) {
result.push({
type: 'FunctionCall',
name: token.value,
arguments: args
});
}
else {
result.push(token);
}
}
else if (isValueDelimiter(token) || (inArgument && isWhiteSpace$1(token))) {
scanner.pos++;
}
else {
break;
}
}
return result.length
? { type: 'CSSValue', value: result }
: void 0;
}
function consumeArguments(scanner) {
const start = scanner.pos;
if (consume$1(scanner, isOpenBracket$1)) {
const args = [];
let value;
while (readable(scanner) && !consume$1(scanner, isCloseBracket$1)) {
if (value = consumeValue(scanner, true)) {
args.push(value);
}
else if (!consume$1(scanner, isWhiteSpace$1) && !consume$1(scanner, isArgumentDelimiter)) {
throw error(scanner, 'Unexpected token');
}
}
scanner.start = start;
return args;
}
}
function isLiteral(token) {
return token && token.type === 'Literal';
}
function isBracket(token, open) {
return token && token.type === 'Bracket' && (open == null || token.open === open);
}
function isOpenBracket$1(token) {
return isBracket(token, true);
}
function isCloseBracket$1(token) {
return isBracket(token, false);
}
function isWhiteSpace$1(token) {
return token && token.type === 'WhiteSpace';
}
function isOperator(token, operator) {
return token && token.type === 'Operator' && (!operator || token.operator === operator);
}
function isSiblingOperator(token) {
return isOperator(token, OperatorType.Sibling);
}
function isArgumentDelimiter(token) {
return isOperator(token, OperatorType.ArgumentDelimiter);
}
function isFragmentDelimiter(token) {
return isArgumentDelimiter(token);
}
function isImportant(token) {
return isOperator(token, OperatorType.Important);
}
function isValue(token) {
return token.type === 'StringValue'
|| token.type === 'ColorValue'
|| token.type === 'NumberValue'
|| token.type === 'Literal'
|| token.type === 'Field'
|| token.type === 'CustomProperty';
}
function isValueDelimiter(token) {
return isOperator(token, OperatorType.PropertyDelimiter)
|| isOperator(token, OperatorType.ValueDelimiter);
}
function isFunctionStart(scanner) {
const t1 = scanner.tokens[scanner.pos];
const t2 = scanner.tokens[scanner.pos + 1];
return t1 && t2 && isLiteral(t1) && t2.type === 'Bracket';
}
/**
* Parses given abbreviation into property set
*/
function parse$2(abbr, options) {
try {
const tokens = typeof abbr === 'string' ? tokenize(abbr, options && options.value) : abbr;
return parser(tokens, options);
}
catch (err) {
if (err instanceof ScannerError && typeof abbr === 'string') {
err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`;
}
throw err;
}
}
/**
* Merges attributes in current node: de-duplicates attributes with the same name
* and merges class names
*/
function mergeAttributes(node, config) {
if (!node.attributes) {
return;
}
const attributes = [];
const lookup = {};
for (const attr of node.attributes) {
if (attr.name) {
const attrName = attr.name;
if (attrName in lookup) {
const prev = lookup[attrName];
if (attrName === 'class') {
prev.value = mergeValue(prev.value, attr.value, ' ');
}
else {
mergeDeclarations(prev, attr, config);
}
}
else {
// Create new attribute instance so we can safely modify it later
attributes.push(lookup[attrName] = Object.assign({}, attr));
}
}
else {
attributes.push(attr);
}
}
node.attributes = attributes;
}
/**
* Merges two token lists into single list. Adjacent strings are merged together
*/
function mergeValue(prev, next, glue) {
if (prev && next) {
if (prev.length && glue) {
append(prev, glue);
}
for (const t of next) {
append(prev, t);
}
return prev;
}
const result = prev || next;
return result && result.slice();
}
/**
* Merges data from `src` attribute into `dest` and returns it
*/
function mergeDeclarations(dest, src, config) {
dest.name = src.name;
if (!config.options['output.reverseAttributes']) {
dest.value = src.value;
}
// Keep high-priority properties
if (!dest.implied) {
dest.implied = src.implied;
}
if (!dest.boolean) {
dest.boolean = src.boolean;
}
if (dest.valueType !== 'expression') {
dest.valueType = src.valueType;
}
return dest;
}
function append(tokens, value) {
const lastIx = tokens.length - 1;
if (typeof tokens[lastIx] === 'string' && typeof value === 'string') {
tokens[lastIx] += value;
}
else {
tokens.push(value);
}
}
/**
* Walks over each child node of given markup abbreviation AST node (not including
* given one) and invokes `fn` on each node.
* The `fn` callback accepts context node, list of ancestor nodes and optional
* state object
*/
function walk$1(node, fn, state) {
const ancestors = [node];
const callback = (ctx) => {
fn(ctx, ancestors, state);
ancestors.push(ctx);
ctx.children.forEach(callback);
ancestors.pop();
};
node.children.forEach(callback);
}
/**
* Finds first child node that matches given `callback`
*/
function find$1(node, callback) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (callback(child)) {
return child;
}
const result = find$1(child, callback);
if (result) {
return result;
}
}
}
/**
* Finds node which is the deepest for in current node or node itself.
*/
function findDeepest(node) {
let parent;
while (node.children.length) {
parent = node;
node = node.children[node.children.length - 1];
}
return { parent, node };
}
function isNode(node) {
return node.type === 'AbbreviationNode';
}
/**
* Finds matching snippet from `registry` and resolves it into a parsed abbreviation.
* Resolved node is then updated or replaced with matched abbreviation tree.
*
* A HTML registry basically contains aliases to another Emmet abbreviations,
* e.g. a predefined set of name, attributes and so on, possibly a complex
* abbreviation with multiple elements. So we have to get snippet, parse it
* and recursively resolve it.
*/
function resolveSnippets(abbr, config) {
const stack = [];
const reversed = config.options['output.reverseAttributes'];
const { warn } = config;
const resolve = (child) => {
const snippet = child.name && config.snippets[child.name];
// A snippet in stack means circular reference.
// It can be either a user error or a perfectly valid snippet like
// "img": "img[src alt]/", e.g. an element with predefined shape.
// In any case, simply stop parsing and keep element as is
if (!snippet || stack.includes(snippet)) {
return null;
}
let snippetAbbr;
try {
// User may add invlid snippet. In this case, silently bail out
snippetAbbr = parseAbbreviation(snippet, config);
}
catch (err) {
warn === null || warn === void 0 ? void 0 : warn(`Unable to parse "${snippet}" snippet`, err);
return null;
}
stack.push(snippet);
walkResolve(snippetAbbr, resolve);
stack.pop();
// Add attributes from current node into every top-level node of parsed abbreviation
for (const topNode of snippetAbbr.children) {
if (child.attributes) {
const from = topNode.attributes || [];
const to = child.attributes || [];
topNode.attributes = reversed ? to.concat(from) : from.concat(to);
}
mergeNodes(child, topNode);
}
return snippetAbbr;
};
walkResolve(abbr, resolve);
return abbr;
}
function walkResolve(node, resolve, config) {
let children = [];
for (const child of node.children) {
const resolved = resolve(child);
if (resolved) {
children = children.concat(resolved.children);
const deepest = findDeepest(resolved);
if (isNode(deepest.node)) {
deepest.node.children = deepest.node.children.concat(walkResolve(child, resolve));
}
}
else {
children.push(child);
child.children = walkResolve(child, resolve);
}
}
return node.children = children;
}
/**
* Adds data from first node into second node
*/
function mergeNodes(from, to) {
if (from.selfClosing) {
to.selfClosing = true;
}
if (from.value != null) {
to.value = from.value;
}
if (from.repeat) {
to.repeat = from.repeat;
}
}
const expressionStart = '{';
const expressionEnd = '}';
function createOutputStream(options, level = 0) {
return {
options,
value: '',
level,
offset: 0,
line: 0,
column: 0
};
}
/**
* Pushes plain string into output stream without newline processing
*/
function push(stream, text) {
const processText = stream.options['output.text'];
_push(stream, processText(text, stream.offset, stream.line, stream.column));
}
/**
* Pushes given string with possible newline formatting into output
*/
function pushString(stream, value) {
// If given value contains newlines, we should push content line-by-line and
// use `pushNewline()` to maintain proper line/column state
const lines = splitByLines$1(value);
for (let i = 0, il = lines.length - 1; i <= il; i++) {
push(stream, lines[i]);
if (i !== il) {
pushNewline(stream, true);
}
}
}
/**
* Pushes new line into given output stream
*/
function pushNewline(stream, indent) {
const baseIndent = stream.options['output.baseIndent'];
const newline = stream.options['output.newline'];
push(stream, newline + baseIndent);
stream.line++;
stream.column = baseIndent.length;
if (indent) {
pushIndent(stream, indent === true ? stream.level : indent);
}
}
/**
* Adds indentation of `size` to current output stream
*/
function pushIndent(stream, size = stream.level) {
const indent = stream.options['output.indent'];
push(stream, indent.repeat(Math.max(size, 0)));
}
/**
* Pushes field/tabstop into output stream
*/
function pushField(stream, index, placeholder) {
const field = stream.options['output.field'];
// NB: use `_push` instead of `push` to skip text processing
_push(stream, field(index, placeholder, stream.offset, stream.line, stream.column));
}
/**
* Returns given tag name formatted according to given config
*/
function tagName(name, config) {
return strCase(name, config.options['output.tagCase']);
}
/**
* Returns given attribute name formatted according to given config
*/
function attrName(name, config) {
return strCase(name, config.options['output.attributeCase']);
}
/**
* Returns character for quoting value of given attribute
*/
function attrQuote(attr, config, isOpen) {
if (attr.valueType === 'expression') {
return isOpen ? expressionStart : expressionEnd;
}
return config.options['output.attributeQuotes'] === 'single' ? '\'' : '"';
}
/**
* Check if given attribute is boolean
*/
function isBooleanAttribute(attr, config) {
return attr.boolean
|| config.options['output.booleanAttributes'].includes((attr.name || '').toLowerCase());
}
/**
* Returns a token for self-closing tag, depending on current options
*/
function selfClose(config) {
switch (config.options['output.selfClosingStyle']) {
case 'xhtml': return ' /';
case 'xml': return '/';
default: return '';
}
}
/**
* Check if given tag name belongs to inline-level element
* @param node Parsed node or tag name
*/
function isInline(node, config) {
if (typeof node === 'string') {
return config.options.inlineElements.includes(node.toLowerCase());
}
// inline node is a node either with inline-level name or text-only node
return node.name ? isInline(node.name, config) : Boolean(node.value && !node.attributes);
}
/**
* Splits given text by lines
*/
function splitByLines$1(text) {
return text.split(/\r\n|\r|\n/g);
}
/**
* Pushes raw string into output stream without any processing
*/
function _push(stream, text) {
stream.value += text;
stream.offset += text.length;
stream.column += text.length;
}
function strCase(str, type) {
if (type) {
return type === 'upper' ? str.toUpperCase() : str.toLowerCase();
}
return str;
}
const elementMap = {
p: 'span',
ul: 'li',
ol: 'li',
table: 'tr',
tr: 'td',
tbody: 'tr',
thead: 'tr',
tfoot: 'tr',
colgroup: 'col',
select: 'option',
optgroup: 'option',
audio: 'source',
video: 'source',
object: 'param',
map: 'area'
};
function implicitTag(node, ancestors, config) {
if (!node.name && node.attributes) {
resolveImplicitTag(node, ancestors, config);
}
}
function resolveImplicitTag(node, ancestors, config) {
const parent = getParentElement(ancestors);
const contextName = config.context ? config.context.name : '';
const parentName = lowercase(parent ? parent.name : contextName);
node.name = elementMap[parentName]
|| (isInline(parentName, config) ? 'span' : 'div');
}
function lowercase(str) {
return (str || '').toLowerCase();
}
/**
* Returns closest element node from given ancestors list
*/
function getParentElement(ancestors) {
for (let i = ancestors.length - 1; i >= 0; i--) {
const elem = ancestors[i];
if (isNode(elem)) {
return elem;
}
}
}
var latin = {
"common": ["lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipisicing", "elit"],
"words": ["exercitationem", "perferendis", "perspiciatis", "laborum", "eveniet",
"sunt", "iure", "nam", "nobis", "eum", "cum", "officiis", "excepturi",
"odio", "consectetur", "quasi", "aut", "quisquam", "vel", "eligendi",
"itaque", "non", "odit", "tempore", "quaerat", "dignissimos",
"facilis", "neque", "nihil", "expedita", "vitae", "vero", "ipsum",
"nisi", "animi", "cumque", "pariatur", "velit", "modi", "natus",
"iusto", "eaque", "sequi", "illo", "sed", "ex", "et", "voluptatibus",
"tempora", "veritatis", "ratione", "assumenda", "incidunt", "nostrum",
"placeat", "aliquid", "fuga", "provident", "praesentium", "rem",
"necessitatibus", "suscipit", "adipisci", "quidem", "possimus",
"voluptas", "debitis", "sint", "accusantium", "unde", "sapiente",
"voluptate", "qui", "aspernatur", "laudantium", "soluta", "amet",
"quo", "aliquam", "saepe", "culpa", "libero", "ipsa", "dicta",
"reiciendis", "nesciunt", "doloribus", "autem", "impedit", "minima",
"maiores", "repudiandae", "ipsam", "obcaecati", "ullam", "enim",
"totam", "delectus", "ducimus", "quis", "voluptates", "dolores",
"molestiae", "harum", "dolorem", "quia", "voluptatem", "molestias",
"magni", "distinctio", "omnis", "illum", "dolorum", "voluptatum", "ea",
"quas", "quam", "corporis", "quae", "blanditiis", "atque", "deserunt",
"laboriosam", "earum", "consequuntur", "hic", "cupiditate",
"quibusdam", "accusamus", "ut", "rerum", "error", "minus", "eius",
"ab", "ad", "nemo", "fugit", "officia", "at", "in", "id", "quos",
"reprehenderit", "numquam", "iste", "fugiat", "sit", "inventore",
"beatae", "repellendus", "magnam", "recusandae", "quod", "explicabo",
"doloremque", "aperiam", "consequatur", "asperiores", "commodi",
"optio", "dolor", "labore", "temporibus", "repellat", "veniam",
"architecto", "est", "esse", "mollitia", "nulla", "a", "similique",
"eos", "alias", "dolore", "tenetur", "deleniti", "porro", "facere",
"maxime", "corrupti"]
};
var ru = {
"common": ["далеко-далеко", "за", "словесными", "горами", "в стране", "гласных", "и согласных", "живут", "рыбные", "тексты"],
"words": ["вдали", "от всех", "они", "буквенных", "домах", "на берегу", "семантика",
"большого", "языкового", "океана", "маленький", "ручеек", "даль",
"журчит", "по всей", "обеспечивает", "ее","всеми", "необходимыми",
"правилами", "эта", "парадигматическая", "страна", "которой", "жаренные",
"предложения", "залетают", "прямо", "рот", "даже", "всемогущая",
"пунктуация", "не", "имеет", "власти", "над", "рыбными", "текстами",
"ведущими", "безорфографичный", "образ", "жизни", "однажды", "одна",
"маленькая", "строчка","рыбного", "текста", "имени", "lorem", "ipsum",
"решила", "выйти", "большой", "мир", "грамматики", "великий", "оксмокс",
"предупреждал", "о", "злых", "запятых", "диких", "знаках", "вопроса",
"коварных", "точках", "запятой", "но", "текст", "дал", "сбить",
"себя", "толку", "он", "собрал", "семь", "своих", "заглавных", "букв",
"подпоясал", "инициал", "за", "пояс", "пустился", "дорогу",
"взобравшись", "первую", "вершину", "курсивных", "гор", "бросил",
"последний", "взгляд", "назад", "силуэт", "своего", "родного", "города",
"буквоград", "заголовок", "деревни", "алфавит", "подзаголовок", "своего",
"переулка", "грустный", "реторический", "вопрос", "скатился", "его",
"щеке", "продолжил", "свой", "путь", "дороге", "встретил", "рукопись",
"она", "предупредила", "моей", "все", "переписывается", "несколько",
"раз", "единственное", "что", "меня", "осталось", "это", "приставка",
"возвращайся", "ты", "лучше", "свою", "безопасную", "страну", "послушавшись",
"рукописи", "наш", "продолжил", "свой", "путь", "вскоре", "ему",
"повстречался", "коварный", "составитель", "рекламных", "текстов",
"напоивший", "языком", "речью", "заманивший", "свое", "агентство",
"которое", "использовало", "снова", "снова", "своих", "проектах",
"если", "переписали", "то", "живет", "там", "до", "сих", "пор"]
};
var sp = {
"common": ["mujer", "uno", "dolor", "más", "de", "poder", "mismo", "si"],
"words": ["ejercicio", "preferencia", "perspicacia", "laboral", "paño",
"suntuoso", "molde", "namibia", "planeador", "mirar", "demás", "oficinista", "excepción",
"odio", "consecuencia", "casi", "auto", "chicharra", "velo", "elixir",
"ataque", "no", "odio", "temporal", "cuórum", "dignísimo",
"facilismo", "letra", "nihilista", "expedición", "alma", "alveolar", "aparte",
"león", "animal", "como", "paria", "belleza", "modo", "natividad",
"justo", "ataque", "séquito", "pillo", "sed", "ex", "y", "voluminoso",
"temporalidad", "verdades", "racional", "asunción", "incidente", "marejada",
"placenta", "amanecer", "fuga", "previsor", "presentación", "lejos",
"necesariamente", "sospechoso", "adiposidad", "quindío", "pócima",
"voluble", "débito", "sintió", "accesorio", "falda", "sapiencia",
"volutas", "queso", "permacultura", "laudo", "soluciones", "entero",
"pan", "litro", "tonelada", "culpa", "libertario", "mosca", "dictado",
"reincidente", "nascimiento", "dolor", "escolar", "impedimento", "mínima",
"mayores", "repugnante", "dulce", "obcecado", "montaña", "enigma",
"total", "deletéreo", "décima", "cábala", "fotografía", "dolores",
"molesto", "olvido", "paciencia", "resiliencia", "voluntad", "molestias",
"magnífico", "distinción", "ovni", "marejada", "cerro", "torre", "y",
"abogada", "manantial", "corporal", "agua", "crepúsculo", "ataque", "desierto",
"laboriosamente", "angustia", "afortunado", "alma", "encefalograma",
"materialidad", "cosas", "o", "renuncia", "error", "menos", "conejo",
"abadía", "analfabeto", "remo", "fugacidad", "oficio", "en", "almácigo", "vos", "pan",
"represión", "números", "triste", "refugiado", "trote", "inventor",
"corchea", "repelente", "magma", "recusado", "patrón", "explícito",
"paloma", "síndrome", "inmune", "autoinmune", "comodidad",
"ley", "vietnamita", "demonio", "tasmania", "repeler", "apéndice",
"arquitecto", "columna", "yugo", "computador", "mula", "a", "propósito",
"fantasía", "alias", "rayo", "tenedor", "deleznable", "ventana", "cara",
"anemia", "corrupto"]
};
const vocabularies = { ru, sp, latin };
const reLorem = /^lorem([a-z]*)(\d*)(-\d*)?$/i;
function lorem(node, ancestors, config) {
let m;
if (node.name && (m = node.name.match(reLorem))) {
const db = vocabularies[m[1]] || vocabularies.latin;
const minWordCount = m[2] ? Math.max(1, Number(m[2])) : 30;
const maxWordCount = m[3] ? Math.max(minWordCount, Number(m[3].slice(1))) : minWordCount;
const wordCount = rand(minWordCount, maxWordCount);
const repeat = node.repeat || findRepeater(ancestors);
node.name = node.attributes = void 0;
node.value = [paragraph(db, wordCount, !repeat || repeat.value === 0)];
if (node.repeat && ancestors.length > 1) {
resolveImplicitTag(node, ancestors, config);
}
}
}
/**
* Returns random integer between from
and to
values
*/
function rand(from, to) {
return Math.floor(Math.random() * (to - from) + from);
}
function sample(arr, count) {
const len = arr.length;
const iterations = Math.min(len, count);
const result = [];
while (result.length < iterations) {
const str = arr[rand(0, len)];
if (!result.includes(str)) {
result.push(str);
}
}
return result;
}
function choice(val) {
return val[rand(0, val.length - 1)];
}
function sentence(words, end) {
if (words.length) {
words = [capitalize(words[0])].concat(words.slice(1));
}
return words.join(' ') + (end || choice('?!...')); // more dots than question marks
}
function capitalize(word) {
return word[0].toUpperCase() + word.slice(1);
}
/**
* Insert commas at randomly selected words. This function modifies values
* inside `words` array
*/
function insertCommas(words) {
if (words.length < 2) {
return words;
}
words = words.slice();
const len = words.length;
const hasComma = /,$/;
let totalCommas = 0;
if (len > 3 && len <= 6) {
totalCommas = rand(0, 1);
}
else if (len > 6 && len <= 12) {
totalCommas = rand(0, 2);
}
else {
totalCommas = rand(1, 4);
}
for (let i = 0, pos; i < totalCommas; i++) {
pos = rand(0, len - 2);
if (!hasComma.test(words[pos])) {
words[pos] += ',';
}
}
return words;
}
/**
* Generate a paragraph of "Lorem ipsum" text
* @param dict Words dictionary
* @param wordCount Words count in paragraph
* @param startWithCommon Should paragraph start with common "lorem ipsum" sentence.
*/
function paragraph(dict, wordCount, startWithCommon) {
const result = [];
let totalWords = 0;
let words;
if (startWithCommon && dict.common) {
words = dict.common.slice(0, wordCount);
totalWords += words.length;
result.push(sentence(insertCommas(words), '.'));
}
while (totalWords < wordCount) {
words = sample(dict.words, Math.min(rand(2, 30), wordCount - totalWords));
totalWords += words.length;
result.push(sentence(insertCommas(words)));
}
return result.join(' ');
}
function findRepeater(ancestors) {
for (let i = ancestors.length - 1; i >= 0; i--) {
const element = ancestors[i];
if (element.type === 'AbbreviationNode' && element.repeat) {
return element.repeat;
}
}
}
/**
* XSL transformer: removes `select` attributes from certain nodes that contain
* children
*/
function xsl(node) {
if (matchesName(node.name) && node.attributes && (node.children.length || node.value)) {
node.attributes = node.attributes.filter(isAllowed);
}
}
function isAllowed(attr) {
return attr.name !== 'select';
}
function matchesName(name) {
return name === 'xsl:variable' || name === 'xsl:with-param';
}
const reElement = /^(-+)([a-z0-9]+[a-z0-9-]*)/i;
const reModifier = /^(_+)([a-z0-9]+[a-z0-9-_]*)/i;
const blockCandidates1 = (className) => /^[a-z]\-/i.test(className);
const blockCandidates2 = (className) => /^[a-z]/i.test(className);
function bem(node, ancestors, config) {
expandClassNames(node);
expandShortNotation(node, ancestors, config);
}
/**
* Expands existing class names in BEM notation in given `node`.
* For example, if node contains `b__el_mod` class name, this method ensures
* that element contains `b__el` class as well
*/
function expandClassNames(node) {
const data = getBEMData(node);
const classNames = [];
for (const cl of data.classNames) {
// remove all modifiers and element prefixes from class name to get a base element name
const ix = cl.indexOf('_');
if (ix > 0 && !cl.startsWith('-')) {
classNames.push(cl.slice(0, ix));
classNames.push(cl.slice(ix));
}
else {
classNames.push(cl);
}
}
if (classNames.length) {
data.classNames = classNames.filter(uniqueClass);
data.block = findBlockName(data.classNames);
updateClass(node, data.classNames.join(' '));
}
}
/**
* Expands short BEM notation, e.g. `-element` and `_modifier`
*/
function expandShortNotation(node, ancestors, config) {
const data = getBEMData(node);
const classNames = [];
const { options } = config;
const path = ancestors.slice(1).concat(node);
for (let cl of data.classNames) {
let prefix = '';
let m;
const originalClass = cl;
// parse element definition (could be only one)
if (m = cl.match(reElement)) {
prefix = getBlockName(path, m[1].length, config.context) + options['bem.element'] + m[2];
classNames.push(prefix);
cl = cl.slice(m[0].length);
}
// parse modifiers definitions
if (m = cl.match(reModifier)) {
if (!prefix) {
prefix = getBlockName(path, m[1].length);
classNames.push(prefix);
}
classNames.push(`${prefix}${options['bem.modifier']}${m[2]}`);
cl = cl.slice(m[0].length);
}
if (cl === originalClass) {
// class name wasn’t modified: it’s not a BEM-specific class,
// add it as-is into output
classNames.push(originalClass);
}
}
const arrClassNames = classNames.filter(uniqueClass);
if (arrClassNames.length) {
updateClass(node, arrClassNames.join(' '));
}
}
/**
* Returns BEM data from given abbreviation node
*/
function getBEMData(node) {
if (!node._bem) {
let classValue = '';
if (node.attributes) {
for (const attr of node.attributes) {
if (attr.name === 'class' && attr.value) {
classValue = stringifyValue(attr.value);
break;
}
}
}
node._bem = parseBEM(classValue);
}
return node._bem;
}
function getBEMDataFromContext(context) {
if (!context._bem) {
context._bem = parseBEM(context.attributes && context.attributes.class || '');
}
return context._bem;
}
/**
* Parses BEM data from given class name
*/
function parseBEM(classValue) {
const classNames = classValue ? classValue.split(/\s+/) : [];
return {
classNames,
block: findBlockName(classNames)
};
}
/**
* Returns block name for given `node` by `prefix`, which tells the depth of
* of parent node lookup
*/
function getBlockName(ancestors, depth = 0, context) {
const maxParentIx = 0;
let parentIx = Math.max(ancestors.length - depth, maxParentIx);
do {
const parent = ancestors[parentIx];
if (parent) {
const data = getBEMData(parent);
if (data.block) {
return data.block;
}
}
} while (maxParentIx < parentIx--);
if (context) {
const data = getBEMDataFromContext(context);
if (data.block) {
return data.block;
}
}
return '';
}
function findBlockName(classNames) {
return find(classNames, blockCandidates1)
|| find(classNames, blockCandidates2)
|| void 0;
}
/**
* Finds class name from given list which may be used as block name
*/
function find(classNames, filter) {
for (const cl of classNames) {
if (reElement.test(cl) || reModifier.test(cl)) {
break;
}
if (filter(cl)) {
return cl;
}
}
}
function updateClass(node, value) {
for (const attr of node.attributes) {
if (attr.name === 'class') {
attr.value = [value];
break;
}
}
}
function stringifyValue(value) {
let result = '';
for (const t of value) {
result += typeof t === 'string' ? t : t.name;
}
return result;
}
function uniqueClass(item, ix, arr) {
return !!item && arr.indexOf(item) === ix;
}
/**
* Preprocessor of `