'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var Scanner = require('@emmetio/scanner'); function tokenScanner(tokens) { return { tokens, start: 0, pos: 0, size: tokens.length }; } function peek(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(scanner) { return scanner.pos < scanner.size; } function consume(scanner, test) { const token = peek(scanner); if (token && test(token)) { scanner.pos++; return true; } return false; } function error(scanner, message, token = peek(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(abbr); const result = statements(scanner, options); if (readable(scanner)) { throw error(scanner, 'Unexpected character'); } return result; } function statements(scanner, options) { const result = { type: 'TokenGroup', elements: [] }; let ctx = result; let node; const stack = []; while (readable(scanner)) { if (node = element(scanner, options) || group(scanner, options)) { ctx.elements.push(node); if (consume(scanner, isChildOperator)) { stack.push(ctx); ctx = node; } else if (consume(scanner, isSiblingOperator)) { continue; } else if (consume(scanner, isClimbOperator)) { do { if (stack.length) { ctx = stack.pop(); } } while (consume(scanner, isClimbOperator)); } } else { break; } } return result; } /** * Consumes group from given scanner */ function group(scanner, options) { if (consume(scanner, isGroupStart)) { const result = statements(scanner, options); const token = next(scanner); if (isBracket(token, 'group', false)) { result.repeat = repeater$1(scanner); } return result; } } /** * Consumes single element from given scanner */ function element(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(scanner)) { scanner.start = scanner.pos; if (!elem.repeat && !isEmpty(elem) && consume(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(scanner, isCloseOperator)) { elem.selfClose = true; if (!elem.repeat && consume(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(scanner, isAttributeSetStart)) { const attributes = []; let attr; while (readable(scanner)) { if (attr = attribute(scanner)) { attributes.push(attr); } else if (consume(scanner, isAttributeSetEnd)) { break; } else if (!consume(scanner, isWhiteSpace)) { throw error(scanner, `Unexpected "${peek(scanner).type}" token`); } } return attributes; } } /** * Consumes attribute shorthand (class or id) from given scanner */ function shortAttribute(scanner, type, options) { if (isOperator(peek(scanner), type)) { scanner.pos++; // Consume multiple operators let count = 1; while (isOperator(peek(scanner), type)) { scanner.pos++; count++; } const attr = { name: [createLiteral(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(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(scanner, true)) { const name = slice(scanner); let value; if (consume(scanner, isEquals)) { if (quoted(scanner) || literal$1(scanner, true)) { value = slice(scanner); } } return { name, value }; } } function repeater$1(scanner) { return isRepeater(peek(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(scanner); if (isQuote(quote)) { scanner.pos++; while (readable(scanner)) { if (isQuote(next(scanner), quote.single)) { scanner.start = start; return true; } } throw error(scanner, 'Unclosed quote', quote); } return false; } /** * Consumes literal (unquoted value) from given scanner */ function literal$1(scanner, allowBrackets) { const start = scanner.pos; const brackets = { attribute: 0, expression: 0, group: 0 }; while (readable(scanner)) { const token = peek(scanner); if (brackets.expression) { // If we’re inside expression, we should consume all content in it if (isBracket(token, 'expression')) { brackets[token.context] += token.open ? 1 : -1; } } else if (isQuote(token) || isOperator(token) || isWhiteSpace(token) || isRepeater(token)) { break; } else if (isBracket(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(scanner, isCapitalizedLiteral)) { // Check for edge case: consume immediate capitalized class names // for React-like components, e.g. `Foo.Bar.Baz` while (readable(scanner)) { const { pos } = scanner; if (!consume(scanner, isClassNameOperator) || !consume(scanner, isCapitalizedLiteral)) { scanner.pos = pos; break; } } } while (readable(scanner) && consume(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(scanner, isTextStart)) { let brackets = 0; while (readable(scanner)) { const token = next(scanner); if (isBracket(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(scanner.tokens[from], 'expression', true)) { from++; } if (isBracket(scanner.tokens[to - 1], 'expression', false)) { to--; } return slice(scanner, from, to); } function isBracket(token, context, isOpen) { return Boolean(token && token.type === 'Bracket' && (!context || token.context === context) && (isOpen == null || token.open === isOpen)); } function isOperator(token, type) { return Boolean(token && token.type === 'Operator' && (!type || token.operator === type)); } function isQuote(token, isSingle) { return Boolean(token && token.type === 'Quote' && (isSingle == null || token.single === isSingle)); } function isWhiteSpace(token) { return Boolean(token && token.type === 'WhiteSpace'); } function isEquals(token) { return isOperator(token, 'equal'); } function isRepeater(token) { return Boolean(token && token.type === 'Repeater'); } function isLiteral(token) { return token.type === 'Literal'; } function isCapitalizedLiteral(token) { if (isLiteral(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(token, 'class'); } function isAttributeSetStart(token) { return isBracket(token, 'attribute', true); } function isAttributeSetEnd(token) { return isBracket(token, 'attribute', false); } function isTextStart(token) { return isBracket(token, 'expression', true); } function isGroupStart(token) { return isBracket(token, 'group', true); } function createLiteral(value) { return { type: 'Literal', value }; } function isEmpty(elem) { return !elem.name && !elem.value && !elem.attributes; } function isChildOperator(token) { return isOperator(token, 'child'); } function isSiblingOperator(token) { return isOperator(token, 'sibling'); } function isClimbOperator(token) { return isOperator(token, 'climb'); } function isCloseOperator(token) { return isOperator(token, 'close'); } var Chars; (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 || (Chars = {})); /** * If consumes escape character, sets current stream range to escaped value */ function escaped(scanner) { if (scanner.eat(Chars.Escape)) { scanner.start = scanner.pos; if (!scanner.eof()) { scanner.pos++; } return true; } return false; } function tokenize(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(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(scanner, ctx) { return field(scanner, ctx) || repeaterPlaceholder(scanner) || repeaterNumber(scanner) || repeater(scanner) || whiteSpace(scanner) || literal(scanner, ctx) || operator(scanner) || quote(scanner) || bracket(scanner); } /** * Consumes literal from given scanner */ function literal(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.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 (Scanner.isNumber(prev) && Scanner.isNumber(next)) { value += scanner.string[scanner.pos++]; continue; } } if (ch === ctx.quote || ch === Chars.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.CurlyBracketOpen) { ctx.expression++; } else if (ch === Chars.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) || Scanner.isQuote(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(scanner) { const start = scanner.pos; if (scanner.eatWhile(Scanner.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 (Scanner.isQuote(ch)) { return { type: 'Quote', single: ch === Chars.SingleQuote, start: scanner.pos++, end: scanner.pos }; } } /** * Consumes bracket from given scanner */ function bracket(scanner) { const ch = scanner.peek(); const context = bracketType(ch); if (context) { return { type: 'Bracket', open: isOpenBracket(ch), context, 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 }; } } /** * Consumes node repeat token from current stream position and returns its * parsed value */ function repeater(scanner) { const start = scanner.pos; if (scanner.eat(Chars.Asterisk)) { scanner.start = scanner.pos; let count = 1; let implicit = false; if (scanner.eatWhile(Scanner.isNumber)) { 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.Dollar) && scanner.eat(Chars.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.Dollar)) { const size = scanner.pos - start; let reverse = false; let base = 1; let parent = 0; if (scanner.eat(Chars.At)) { // Consume numbering modifiers while (scanner.eat(Chars.Climb)) { parent++; } reverse = scanner.eat(Chars.Dash); scanner.start = scanner.pos; if (scanner.eatWhile(Scanner.isNumber)) { base = Number(scanner.current()); } } scanner.start = start; return { type: 'RepeaterNumber', size, reverse, base, parent, start, end: scanner.pos }; } } function field(scanner, ctx) { const start = scanner.pos; // Fields are allowed inside expressions and attributes if ((ctx.expression || ctx.attribute) && scanner.eat(Chars.Dollar) && scanner.eat(Chars.CurlyBracketOpen)) { scanner.start = scanner.pos; let index; let name = ''; if (scanner.eatWhile(Scanner.isNumber)) { // It’s a field index = Number(scanner.current()); name = scanner.eat(Chars.Colon) ? consumePlaceholder(scanner) : ''; } else if (Scanner.isAlpha(scanner.peek())) { // It’s a variable name = consumePlaceholder(scanner); } if (scanner.eat(Chars.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(stream) { const stack = []; stream.start = stream.pos; while (!stream.eof()) { if (stream.eat(Chars.CurlyBracketOpen)) { stack.push(stream.pos); } else if (stream.eat(Chars.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(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 Scanner.isSpace(ch) && !ctx.expression; } /** * Check if given character can be consumed as repeater in current context */ function isAllowedRepeater(ch, ctx) { return ch === Chars.Asterisk && !ctx.attribute && !ctx.expression; } /** * If given character is a bracket, returns it’s type */ function bracketType(ch) { if (ch === Chars.RoundBracketOpen || ch === Chars.RoundBracketClose) { return 'group'; } if (ch === Chars.SquareBracketOpen || ch === Chars.SquareBracketClose) { return 'attribute'; } if (ch === Chars.CurlyBracketOpen || ch === Chars.CurlyBracketClose) { return 'expression'; } } /** * If given character is an operator, returns it’s type */ function operatorType(ch) { return (ch === Chars.Child && 'child') || (ch === Chars.Sibling && 'sibling') || (ch === Chars.Climb && 'climb') || (ch === Chars.Dot && 'class') || (ch === Chars.Hash && 'id') || (ch === Chars.Slash && 'close') || (ch === Chars.Equals && 'equal') || void 0; } /** * Check if given character is an open bracket */ function isOpenBracket(ch) { return ch === Chars.CurlyBracketOpen || ch === Chars.SquareBracketOpen || ch === Chars.RoundBracketOpen; } /** * Check if given character is allowed in element name */ function isElementName(ch) { return Scanner.isAlphaNumericWord(ch) || Scanner.isUmlaut(ch) || ch === Chars.Dash || ch === Chars.Colon || ch === Chars.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(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(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(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(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)) { // 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(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(tokens).type === quote.type) { tokens.pop(); } valueType = quote.single ? 'singleQuote' : 'doubleQuote'; } else if (isBracket(tokens[0], 'expression', true)) { // Value is expression: remove brackets but mark value type valueType = 'expression'; tokens.shift(); if (isBracket(last(tokens), 'expression', false)) { tokens.pop(); } } value = stringifyValue(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(tokens[i], state); } return str; } /** * Converts given token list to value list */ function stringifyValue(tokens, state) { const result = []; let str = ''; for (let i = 0, token; i < tokens.length; i++) { token = tokens[i]; if (isField(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(token, state); } } if (str) { result.push(str); } return result; } function isGroup(node) { return node.type === 'TokenGroup'; } function isField(token) { return typeof token === 'object' && token.type === 'Field' && token.index != null; } function last(arr) { return arr[arr.length - 1]; } function deepestNode(node) { return node.children.length ? deepestNode(last(node.children)) : node; } function insertText(node, text) { if (node.value) { const lastToken = last(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(abbr) : abbr; return convert(abbreviation(tokens, options), options); } catch (err) { if (err instanceof Scanner.ScannerError && typeof abbr === 'string') { err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`; } throw err; } } exports.convert = convert; exports.default = parseAbbreviation; exports.getToken = getToken; exports.parse = abbreviation; exports.tokenize = tokenize; //# sourceMappingURL=index.cjs.map