(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
	typeof define === 'function' && define.amd ? define(['exports'], factory) :
	(factory((global.emmet = {})));
}(this, (function (exports) { 'use strict';

var defaultOptions = {
	/**
	 * String for one-level indentation
	 * @type {String}
	 */
	indent: '\t',

	/**
	 * Tag case: 'lower', 'upper' or '' (keep as-is)
	 * @type {String}
	 */
	tagCase: '',

	/**
	 * Attribute name case: 'lower', 'upper' or '' (keep as-is)
	 * @type {String}
	 */
	attributeCase: '',

	/**
	 * Attribute value quotes: 'single' or 'double'
	 * @type {String}
	 */
	attributeQuotes: 'double',

	/**
	 * Enable output formatting (indentation and line breaks)
	 * @type {Boolean}
	 */
	format: true,

	/**
	 * A list of tag names that should not get inner indentation
	 * @type {Set}
	 */
	formatSkip: ['html'],

	/**
	 * A list of tag names that should *always* get inner indentation.
	 * @type {Set}
	 */
	formatForce: ['body'],

	/**
	 * How many inline sibling elements should force line break for each tag.
	 * Set to 0 to output all inline elements without formatting.
	 * Set to 1 to output all inline elements with formatting (same as block-level).
	 * @type {Number}
	 */
	inlineBreak: 3,

	/**
	 * Produce compact notation of boolean attribues: attributes where name equals value.
	 * With this option enabled, output `<div contenteditable>` instead of
	 * `<div contenteditable="contenteditable">`
	 * @type {Boolean}
	 */
	compactBooleanAttributes: false,

	/**
	 * A set of boolean attributes
	 * @type {Set}
	 */
	booleanAttributes: ['contenteditable', 'seamless', 'async', 'autofocus',
		'autoplay', 'checked', 'controls', 'defer', 'disabled', 'formnovalidate',
		'hidden', 'ismap', 'loop', 'multiple', 'muted', 'novalidate', 'readonly',
		'required', 'reversed', 'selected', 'typemustmatch'],

	/**
	 * Style of self-closing tags:
	 * 'html'  – <br>
	 * 'xml'   – <br/>
	 * 'xhtml' – <br />
	 * @type {String}
	 */
	selfClosingStyle: 'html',

	/**
	 * A set of inline-level elements
	 * @type {Set}
	 */
	inlineElements: ['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
		'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
		'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
		's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
		'textarea', 'tt', 'u', 'var']
};

/**
 * Creates output profile for given options (@see defaults)
 * @param {defaults} options
 */
class Profile {
    constructor(options) {
        this.options = Object.assign({}, defaultOptions, options);
        this.quoteChar = this.options.attributeQuotes === 'single' ? '\'' : '"';
    }

	/**
	 * Returns value of given option name
	 * @param {String} name
	 * @return {*}
	 */
	get(name) {
		return this.options[name];
	}

    /**
     * Quote given string according to profile
     * @param {String} str String to quote
     * @return {String}
     */
    quote(str) {
        return `${this.quoteChar}${str != null ? str : ''}${this.quoteChar}`;
    }

    /**
     * Output given tag name accoding to options
     * @param {String} name
     * @return {String}
     */
    name(name) {
        return strcase(name, this.options.tagCase);
    }

	/**
	 * Outputs attribute name accoding to current settings
	 * @param {String} Attribute name
	 * @return {String}
	 */
    attribute(attr) {
        return strcase(attr, this.options.attributeCase);
    }

    /**
     * Check if given attribute is boolean
     * @param {Attribute} attr
     * @return {Boolean}
     */
    isBooleanAttribute(attr) {
        return attr.options.boolean
			|| this.get('booleanAttributes').indexOf((attr.name || '').toLowerCase()) !== -1;
    }

	/**
	 * Returns a token for self-closing tag, depending on current options
	 * @return {String}
	 */
	selfClose() {
		switch (this.options.selfClosingStyle) {
			case 'xhtml': return ' /';
			case 'xml':   return '/';
			default:      return '';
		}
	}

	/**
	 * Returns indent for given level
	 * @param {Number} level Indentation level
	 * @return {String}
	 */
	indent(level) {
		level = level || 0;
		let output = '';
		while (level--) {
			output += this.options.indent;
		}

		return output;
	}

	/**
	 * Check if given tag name belongs to inline-level element
	 * @param {Node|String} node Parsed node or tag name
	 * @return {Boolean}
	 */
	isInline(node) {
        if (typeof node === 'string') {
            return this.get('inlineElements').indexOf(node.toLowerCase()) !== -1;
        }

        // inline node is a node either with inline-level name or text-only node
        return node.name != null ? this.isInline(node.name) : node.isTextOnly;
	}

	/**
	 * Outputs formatted field for given params
	 * @param {Number} index Field index
	 * @param {String} [placeholder] Field placeholder, can be empty
	 * @return {String}
	 */
	field(index, placeholder) {
		return this.options.field(index, placeholder);
	}
}

function strcase(string, type) {
    if (type) {
        string = type === 'upper' ? string.toUpperCase() : string.toLowerCase();
    }
    return string;
}

class Snippet {
    constructor(key, value) {
        this.key = key;
        this.value = value;
    }
}

class SnippetsStorage {
    constructor(data) {
        this._string = new Map();
        this._regexp = new Map();
        this._disabled = false;

        this.load(data);
    }

    get disabled() {
        return this._disabled;
    }

    /**
     * Disables current store. A disabled store always returns `undefined`
     * on `get()` method
     */
    disable() {
        this._disabled = true;
    }

    /**
     * Enables current store.
     */
    enable() {
        this._disabled = false;
    }

    /**
     * Registers a new snippet item
     * @param {String|Regexp} key
     * @param {String|Function} value
     */
    set(key, value) {
        if (typeof key === 'string') {
            key.split('|').forEach(k => this._string.set(k, new Snippet(k, value)));
        } else if (key instanceof RegExp) {
            this._regexp.set(key, new Snippet(key, value));
        } else {
            throw new Error('Unknow snippet key: ' + key);
        }

        return this;
    }

    /**
     * Returns a snippet matching given key. It first tries to find snippet
     * exact match in a string key map, then tries to match one with regexp key
     * @param {String} key
     * @return {Snippet}
     */
    get(key) {
        if (this.disabled) {
            return undefined;
        }

        if (this._string.has(key)) {
            return this._string.get(key);
        }

        const keys = Array.from(this._regexp.keys());
        for (let i = 0, il = keys.length; i < il; i++) {
            if (keys[i].test(key)) {
                return this._regexp.get(keys[i]);
            }
        }
    }

    /**
     * Batch load of snippets data
     * @param {Object|Map} data
     */
    load(data) {
        this.reset();
        if (data instanceof Map) {
            data.forEach((value, key) => this.set(key, value));
        } else if (data && typeof data === 'object') {
            Object.keys(data).forEach(key => this.set(key, data[key]));
        }
    }

    /**
     * Clears all stored snippets
     */
    reset() {
        this._string.clear();
        this._regexp.clear();
    }

    /**
     * Returns all available snippets from given store
     */
    values() {
        if (this.disabled) {
            return [];
        }
        
        const string = Array.from(this._string.values());
        const regexp = Array.from(this._regexp.values());
        return string.concat(regexp);
    }
}

/**
 * A snippets registry. Contains snippets, separated by store and sorted by
 * priority: a store with higher priority takes precedence when resolving snippet
 * for given key
 */
class SnippetsRegistry {
    /**
     * Creates snippets registry, filled with given `data`
     * @param {Object|Array} data Registry snippets. If array is given, adds items
     * from array in order of precedence, registers global snippets otherwise
     */
    constructor(data) {
        this._registry = [];

        if (Array.isArray(data)) {
            data.forEach((snippets, level) => this.add(level, snippets));
        } else if (typeof data === 'object') {
            this.add(data);
        }
    }

    /**
     * Return store for given level
     * @param {Number} level
     * @return {SnippetsStorage}
     */
    get(level) {
        for (let i = 0; i < this._registry.length; i++) {
            const item = this._registry[i];
            if (item.level === level) {
                return item.store;
            }
        }
    }

    /**
     * Adds new store for given level
     * @param {Number} [level] Store level (priority). Store with higher level
     * takes precedence when resolving snippets
     * @param {Object} [snippets] A snippets data for new store
     * @return {SnipetsStorage}
     */
    add(level, snippets) {
        if (level != null && typeof level === 'object') {
            snippets = level;
            level = 0;
        }

        const store = new SnippetsStorage(snippets);

        // remove previous store from same level
        this.remove(level);

        this._registry.push({level, store});
        this._registry.sort((a, b) => b.level - a.level);

        return store;
    }

    /**
     * Remove registry with given level or store
     * @param {Number|SnippetsStorage} data Either level or snippets store
     */
    remove(data) {
        this._registry = this._registry
        .filter(item => item.level !== data && item.store !== data);
    }

    /**
     * Returns snippet from registry that matches given name
     * @param {String} name
     * @return {Snippet}
     */
    resolve(name) {
        for (let i = 0; i < this._registry.length; i++) {
            const snippet = this._registry[i].store.get(name);
            if (snippet) {
                return snippet;
            }
        }
    }

    /**
     * Returns all available snippets from current registry. Snippets with the
     * same key are resolved by their storage priority.
     * @param {Object} options
     * @param {Object} options.type Return snippets only of given type: 'string'
     * or 'regexp'. Returns all snippets if not defined
     * @return {Array}
     */
    all(options) {
        options = options || {};
        const result = new Map();

        const fillResult = snippet => {
            const type = snippet.key instanceof RegExp ? 'regexp' : 'string';
            if ((!options.type || options.type === type) && !result.has(snippet.key)) {
                result.set(snippet.key, snippet);
            }
        };

        this._registry.forEach(item => {
            item.store.values().forEach(fillResult);
        });

        return Array.from(result.values());
    }

    /**
     * Removes all stores from registry
     */
    clear() {
        this._registry.length = 0;
    }
}

/**
 * Methods for consuming quoted values
 */

const SINGLE_QUOTE = 39; // '
const DOUBLE_QUOTE = 34; // "

const defaultOptions$1 = {
	escape: 92,   // \ character
	throws: false
};

/**
 * Consumes 'single' or "double"-quoted string from given string, if possible
 * @param  {StreamReader} stream
 * @param  {Number}  options.escape A character code of quote-escape symbol
 * @param  {Boolean} options.throws Throw error if quotes string can’t be properly consumed
 * @return {Boolean} `true` if quoted string was consumed. The contents
 *                   of quoted string will be availabe as `stream.current()`
 */
var eatQuoted = function(stream, options) {
	options = options ? Object.assign({}, defaultOptions$1, options) : defaultOptions$1;
	const start = stream.pos;
	const quote = stream.peek();

	if (stream.eat(isQuote)) {
		while (!stream.eof()) {
			switch (stream.next()) {
				case quote:
					stream.start = start;
					return true;

				case options.escape:
					stream.next();
					break;
			}
		}

		// If we’re here then stream wasn’t properly consumed.
		// Revert stream and decide what to do
		stream.pos = start;

		if (options.throws) {
			throw stream.error('Unable to consume quoted string');
		}
	}

	return false;
};

function isQuote(code) {
	return code === SINGLE_QUOTE || code === DOUBLE_QUOTE;
}

/**
 * Check if given code is a number
 * @param  {Number}  code
 * @return {Boolean}
 */
function isNumber(code) {
	return code > 47 && code < 58;
}

/**
 * Check if given character code is alpha code (letter through A to Z)
 * @param  {Number}  code
 * @param  {Number}  [from]
 * @param  {Number}  [to]
 * @return {Boolean}
 */
function isAlpha(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;
}

/**
 * Check if given character code is alpha-numeric (letter through A to Z or number)
 * @param  {Number}  code
 * @return {Boolean}
 */
function isAlphaNumeric(code) {
	return isNumber(code) || isAlpha(code);
}

function isWhiteSpace(code) {
	return code === 32   /* space */
		|| code === 9    /* tab */
		|| code === 160; /* non-breaking space */
}

/**
 * Check if given character code is a space
 * @param  {Number}  code
 * @return {Boolean}
 */
function isSpace(code) {
	return isWhiteSpace(code)
		|| code === 10  /* LF */
		|| code === 13; /* CR */
}

/**
 * Attribute descriptor of parsed abbreviation node
 * @param {String} name Attribute name
 * @param {String} value Attribute value
 * @param {Object} options Additional custom attribute options
 * @param {Boolean} options.boolean Attribute is boolean (e.g. name equals value)
 * @param {Boolean} options.implied Attribute is implied (e.g. must be outputted
 * only if contains non-null value)
 */
class Attribute {
	constructor(name, value, options) {
		this.name = name;
		this.value = value != null ? value : null;
		this.options = options || {};
	}

	/**
	 * Create a copy of current attribute
	 * @return {Attribute}
	 */
	clone() {
		return new Attribute(this.name, this.value, Object.assign({}, this.options));
	}

	/**
	 * A string representation of current node
	 */
	valueOf() {
		return `${this.name}="${this.value}"`;
	}
}

/**
 * A parsed abbreviation AST node. Nodes build up an abbreviation AST tree
 */
class Node {
	/**
	 * Creates a new node
	 * @param {String} [name] Node name
	 * @param {Array} [attributes] Array of attributes to add
	 */
	constructor(name, attributes) {
		// own properties
		this.name = name || null;
		this.value = null;
		this.repeat = null;
		this.selfClosing = false;

		this.children = [];

		/** @type {Node} Pointer to parent node */
		this.parent = null;

		/** @type {Node} Pointer to next sibling */
		this.next = null;

		/** @type {Node} Pointer to previous sibling */
		this.previous = null;

		this._attributes = [];

		if (Array.isArray(attributes)) {
			attributes.forEach(attr => this.setAttribute(attr));
		}
	}

	/**
	 * Array of current node attributes
	 * @return {Attribute[]} Array of attributes
	 */
	get attributes() {
		return this._attributes;
	}

	/**
	 * A shorthand to retreive node attributes as map
	 * @return {Object}
	 */
	get attributesMap() {
		return this.attributes.reduce((out, attr) => {
			out[attr.name] = attr.options.boolean ? attr.name : attr.value;
			return out;
		}, {});
	}

	/**
	 * Check if current node is a grouping one, e.g. has no actual representation
	 * and is used for grouping subsequent nodes only
	 * @return {Boolean}
	 */
	get isGroup() {
		return !this.name && !this.value && !this._attributes.length;
	}

	/**
	 * Check if given node is a text-only node, e.g. contains only value
	 * @return {Boolean}
	 */
	get isTextOnly() {
		return !this.name && !!this.value && !this._attributes.length;
	}

	/**
	 * Returns first child node
	 * @return {Node}
	 */
	get firstChild() {
		return this.children[0];
	}

	/**
	 * Returns last child of current node
	 * @return {Node}
	 */
	get lastChild() {
		return this.children[this.children.length - 1];
	}

	/**
	 * Return index of current node in its parent child list
	 * @return {Number} Returns -1 if current node is a root one
	 */
	get childIndex() {
		return this.parent ? this.parent.children.indexOf(this) : -1;
	}

	/**
	 * Returns next sibling of current node
	 * @return {Node}
	 */
	get nextSibling() {
		return this.next;
	}

	/**
	 * Returns previous sibling of current node
	 * @return {Node}
	 */
	get previousSibling() {
		return this.previous;
	}

	/**
	 * Returns array of unique class names in current node
	 * @return {String[]}
	 */
	get classList() {
		const attr = this.getAttribute('class');
		return attr && attr.value
			? attr.value.split(/\s+/g).filter(uniqueClass)
			: [];
	}

	/**
	 * Convenient alias to create a new node instance
	 * @param {String} [name] Node name
	 * @param {Object} [attributes] Attributes hash
	 * @return {Node}
	 */
	create(name, attributes) {
		return new Node(name, attributes);
	}

	/**
	 * Sets given attribute for current node
	 * @param {String|Object|Attribute} name Attribute name or attribute object
	 * @param {String} [value] Attribute value
	 */
	setAttribute(name, value) {
		const attr = createAttribute(name, value);
		const curAttr = this.getAttribute(name);
		if (curAttr) {
			this.replaceAttribute(curAttr, attr);
		} else {
			this._attributes.push(attr);
		}
	}

	/**
	 * Check if attribute with given name exists in node
	 * @param  {String} name
	 * @return {Boolean}
	 */
	hasAttribute(name) {
		return !!this.getAttribute(name);
	}

	/**
	 * Returns attribute object by given name
	 * @param  {String} name
	 * @return {Attribute}
	 */
	getAttribute(name) {
		if (typeof name === 'object') {
			name = name.name;
		}

		for (var i = 0; i < this._attributes.length; i++) {
			const attr = this._attributes[i];
			if (attr.name === name) {
				return attr;
			}
		}
	}

	/**
	 * Replaces attribute with new instance
	 * @param {String|Attribute} curAttribute Current attribute name or instance
	 * to replace
	 * @param {String|Object|Attribute} newName New attribute name or attribute object
	 * @param {String} [newValue] New attribute value
	 */
	replaceAttribute(curAttribute, newName, newValue) {
		if (typeof curAttribute === 'string') {
			curAttribute = this.getAttribute(curAttribute);
		}

		const ix = this._attributes.indexOf(curAttribute);
		if (ix !== -1) {
			this._attributes.splice(ix, 1, createAttribute(newName, newValue));
		}
	}

	/**
	 * Removes attribute with given name
	 * @param  {String|Attribute} attr Atrtibute name or instance
	 */
	removeAttribute(attr) {
		if (typeof attr === 'string') {
			attr = this.getAttribute(attr);
		}

		const ix = this._attributes.indexOf(attr);
		if (ix !== -1) {
			this._attributes.splice(ix, 1);
		}
	}

	/**
	 * Removes all attributes from current node
	 */
	clearAttributes() {
		this._attributes.length = 0;
	}

	/**
	 * Adds given class name to class attribute
	 * @param {String} token Class name token
	 */
	addClass(token) {
		token = normalize(token);

		if (!this.hasAttribute('class')) {
			this.setAttribute('class', token);
		} else if (token && !this.hasClass(token)) {
			this.setAttribute('class', this.classList.concat(token).join(' '));
		}
	}

	/**
	 * Check if current node contains given class name
	 * @param {String} token Class name token
	 * @return {Boolean}
	 */
	hasClass(token) {
		return this.classList.indexOf(normalize(token)) !== -1;
	}

	/**
	 * Removes given class name from class attribute
	 * @param {String} token Class name token
	 */
	removeClass(token) {
		token = normalize(token);
		if (this.hasClass(token)) {
			this.setAttribute('class', this.classList.filter(name => name !== token).join(' '));
		}
	}

	/**
	 * Appends child to current node
	 * @param {Node} node
	 */
	appendChild(node) {
		this.insertAt(node, this.children.length);
	}

	/**
	 * Inserts given `newNode` before `refNode` child node
	 * @param {Node} newNode
	 * @param {Node} refNode
	 */
	insertBefore(newNode, refNode) {
		this.insertAt(newNode, this.children.indexOf(refNode));
	}

	/**
	 * Insert given `node` at `pos` position of child list
	 * @param {Node} node
	 * @param {Number} pos
	 */
	insertAt(node, pos) {
		if (pos < 0 || pos > this.children.length) {
			throw new Error('Unable to insert node: position is out of child list range');
		}

		const prev = this.children[pos - 1];
		const next = this.children[pos];

		node.remove();
		node.parent = this;
		this.children.splice(pos, 0, node);

		if (prev) {
			node.previous = prev;
			prev.next = node;
		}

		if (next) {
			node.next = next;
			next.previous = node;
		}
	}

	/**
	 * Removes given child from current node
	 * @param {Node} node
	 */
	removeChild(node) {
		const ix = this.children.indexOf(node);
		if (ix !== -1) {
			this.children.splice(ix, 1);
			if (node.previous) {
				node.previous.next = node.next;
			}

			if (node.next) {
				node.next.previous = node.previous;
			}

			node.parent = node.next = node.previous = null;
		}
	}

	/**
	 * Removes current node from its parent
	 */
	remove() {
		if (this.parent) {
			this.parent.removeChild(this);
		}
	}

	/**
	 * Creates a detached copy of current node
	 * @param {Boolean} deep Clone node contents as well
	 * @return {Node}
	 */
	clone(deep) {
		const clone = new Node(this.name);
		clone.value = this.value;
		clone.selfClosing = this.selfClosing;
		if (this.repeat) {
			clone.repeat = Object.assign({}, this.repeat);
		}

		this._attributes.forEach(attr => clone.setAttribute(attr.clone()));

		if (deep) {
			this.children.forEach(child => clone.appendChild(child.clone(true)));
		}

		return clone;
	}

	/**
	 * Walks on each descendant node and invokes given `fn` function on it.
	 * The function receives two arguments: the node itself and its depth level
	 * from current node. If function returns `false`, it stops walking
	 * @param {Function} fn
	 */
	walk(fn, _level) {
		_level = _level || 0;
		let ctx = this.firstChild;

		while (ctx) {
			// in case if context node will be detached during `fn` call
			const next = ctx.next;

			if (fn(ctx, _level) === false || ctx.walk(fn, _level + 1) === false) {
				return false;
			}

			ctx = next;
		}
	}

	/**
	 * A helper method for transformation chaining: runs given `fn` function on
	 * current node and returns the same node
	 */
	use(fn) {
		const args = [this];
		for (var i = 1; i < arguments.length; i++) {
			args.push(arguments[i]);
		}

		fn.apply(null, args);
		return this;
	}

	toString() {
		const attrs = this.attributes.map(attr => {
			attr = this.getAttribute(attr.name);
			const opt = attr.options;
			let out = `${opt && opt.implied ? '!' : ''}${attr.name || ''}`;
			if (opt && opt.boolean) {
				out += '.';
			} else if (attr.value != null) {
				out += `="${attr.value}"`;
			}
			return out;
		});

		let out = `${this.name || ''}`;
		if (attrs.length) {
			out += `[${attrs.join(' ')}]`;
		}

		if (this.value != null) {
			out += `{${this.value}}`;
		}

		if (this.selfClosing) {
			out += '/';
		}

		if (this.repeat) {
			out += `*${this.repeat.count ? this.repeat.count : ''}`;
			if (this.repeat.value != null) {
				out += `@${this.repeat.value}`;
			}
		}

		return out;
	}
}

/**
 * Attribute factory
 * @param  {String|Attribute|Object} name  Attribute name or attribute descriptor
 * @param  {*} value Attribute value
 * @return {Attribute}
 */
function createAttribute(name, value) {
	if (name instanceof Attribute) {
		return name;
	}

	if (typeof name === 'string') {
		return new Attribute(name, value);
	}

	if (name && typeof name === 'object') {
		return new Attribute(name.name, name.value, name.options);
	}
}

/**
 * @param  {String} str
 * @return {String}
 */
function normalize(str) {
	return String(str).trim();
}

function uniqueClass(item, i, arr) {
	return item && arr.indexOf(item) === i;
}

/**
 * A streaming, character code-based string reader
 */
class StreamReader {
	constructor(string, start, end) {
		if (end == null && typeof string === 'string') {
			end = string.length;
		}

		this.string = string;
		this.pos = this.start = start || 0;
		this.end = end;
	}

	/**
	 * Returns true only if the stream is at the end of the file.
	 * @returns {Boolean}
	 */
	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
	 * @param  {Point} start
	 * @param  {Point} end
	 * @return {StreamReader}
	 */
	limit(start, end) {
		return new this.constructor(this.string, start, end);
	}

	/**
	 * Returns the next character code in the stream without advancing it.
	 * Will return NaN at the end of the file.
	 * @returns {Number}
	 */
	peek() {
		return this.string.charCodeAt(this.pos);
	}

	/**
	 * Returns the next character in the stream and advances it.
	 * Also returns <code>undefined</code> when no more characters are available.
	 * @returns {Number}
	 */
	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.
	 * @param {Number|Function} match
	 * @returns {Boolean}
	 */
	eat(match) {
		const ch = this.peek();
		const ok = typeof match === 'function' ? match(ch) : ch === match;

		if (ok) {
			this.next();
		}

		return ok;
	}

	/**
	 * Repeatedly calls <code>eat</code> with the given argument, until it
	 * fails. Returns <code>true</code> if any characters were eaten.
	 * @param {Object} match
	 * @returns {Boolean}
	 */
	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.
	 * @param {Number} n
	 */
	backUp(n) {
		this.pos -= (n || 1);
	}

	/**
	 * Get the string between the start of the current token and the
	 * current stream position.
	 * @returns {String}
	 */
	current() {
		return this.substring(this.start, this.pos);
	}

	/**
	 * Returns substring for given range
	 * @param  {Number} start
	 * @param  {Number} [end]
	 * @return {String}
	 */
	substring(start, end) {
		return this.string.slice(start, end);
	}

	/**
	 * Creates error object with current stream state
	 * @param {String} message
	 * @return {Error}
	 */
	error(message) {
		const err = new Error(`${message} at char ${this.pos + 1}`);
		err.originalMessage = message;
		err.pos = this.pos;
		err.string = this.string;
		return err;
	}
}

const ASTERISK = 42; // *

/**
 * Consumes node repeat token from current stream position and returns its
 * parsed value
 * @param  {StringReader} stream
 * @return {Object}
 */
function consumeRepeat(stream) {
	if (stream.eat(ASTERISK)) {
		stream.start = stream.pos;

		// XXX think about extending repeat syntax with through numbering
		return { count: stream.eatWhile(isNumber) ? +stream.current() : null };
	}
}

const opt = { throws: true };

/**
 * Consumes quoted literal from current stream position and returns it’s inner,
 * unquoted, value
 * @param  {StringReader} stream
 * @return {String} Returns `null` if unable to consume quoted value from current
 * position
 */
function consumeQuoted(stream) {
	if (eatQuoted(stream, opt)) {
		return stream.current().slice(1, -1);
	}
}

const TEXT_START = 123; // {
const TEXT_END = 125; // }
const ESCAPE =  92; // \ character

/**
 * Consumes text node `{...}` from stream
 * @param  {StreamReader} stream
 * @return {String} Returns consumed text value (without surrounding braces) or
 * `null` if there’s no text at starting position
 */
function consumeText(stream) {
	// NB using own implementation instead of `eatPair()` from @emmetio/stream-reader-utils
	// to disable quoted value consuming
	const start = stream.pos;

	if (stream.eat(TEXT_START)) {
		let stack = 1, ch;
		let result = '';
		let offset = stream.pos;

		while (!stream.eof()) {
			ch = stream.next();
			if (ch === TEXT_START) {
				stack++;
			} else if (ch === TEXT_END) {
				stack--;
				if (!stack) {
					stream.start = start;
					return result + stream.substring(offset, stream.pos - 1);
				}
			} else if (ch === ESCAPE) {
				ch = stream.next();
				if (ch === TEXT_START || ch === TEXT_END) {
					result += stream.substring(offset, stream.pos - 2) + String.fromCharCode(ch);
					offset = stream.pos;
				}
			}
		}

		// If we’re here then paired character can’t be consumed
		stream.pos = start;
		throw stream.error(`Unable to find closing ${String.fromCharCode(TEXT_END)} for text start`);
	}

	return null;
}

const EXCL       = 33; // .
const DOT        = 46; // .
const EQUALS     = 61; // =
const ATTR_OPEN  = 91; // [
const ATTR_CLOSE = 93; // ]

const reAttributeName = /^\!?[\w\-:\$@]+\.?$|^\!?\[[\w\-:\$@]+\]\.?$/;

/**
 * Consumes attributes defined in square braces from given stream.
 * Example:
 * [attr col=3 title="Quoted string" selected. support={react}]
 * @param {StringReader} stream
 * @returns {Array} Array of consumed attributes
 */
function consumeAttributes(stream) {
	if (!stream.eat(ATTR_OPEN)) {
		return null;
	}

	const result = [];
	let token, attr;

	while (!stream.eof()) {
		stream.eatWhile(isWhiteSpace);

		if (stream.eat(ATTR_CLOSE)) {
			return result; // End of attribute set
		} else if ((token = consumeQuoted(stream)) != null) {
			// Consumed quoted value: anonymous attribute
			result.push({
				name: null,
				value: token
			});
		} else if (eatUnquoted(stream)) {
			// Consumed next word: could be either attribute name or unquoted default value
			token = stream.current();

			// In angular attribute names can be surrounded by []
			if (token[0] === '[' && stream.peek() === ATTR_CLOSE) {
				stream.next();
				token = stream.current();
			}
			
			if (!reAttributeName.test(token)) {
				// anonymous attribute
				result.push({ name: null, value: token });
			} else {
				// Looks like a regular attribute
				attr = parseAttributeName(token);
				result.push(attr);

				if (stream.eat(EQUALS)) {
					// Explicitly defined value. Could be a word, a quoted string
					// or React-like expression
					if ((token = consumeQuoted(stream)) != null) {
						attr.value = token;
					} else if ((token = consumeText(stream)) != null) {
						attr.value = token;
						attr.options = {
							before: '{',
							after: '}'
						};
					} else if (eatUnquoted(stream)) {
						attr.value = stream.current();
					}
				}
			}
		} else {
			throw stream.error('Expected attribute name');
		}
	}

	throw stream.error('Expected closing "]" brace');
}

function parseAttributeName(name) {
	const options = {};

	// If a first character in attribute name is `!` — it’s an implied
	// default attribute
	if (name.charCodeAt(0) === EXCL) {
		name = name.slice(1);
		options.implied = true;
	}

	// Check for last character: if it’s a `.`, user wants boolean attribute
	if (name.charCodeAt(name.length - 1) === DOT) {
		name = name.slice(0, name.length - 1);
		options.boolean = true;
	}

	const attr = { name };
	if (Object.keys(options).length) {
		attr.options = options;
	}

	return attr;
}

/**
 * Eats token that can be an unquoted value from given stream
 * @param  {StreamReader} stream
 * @return {Boolean}
 */
function eatUnquoted(stream) {
	const start = stream.pos;
	if (stream.eatWhile(isUnquoted)) {
		stream.start = start;
		return true;
	}
}

function isUnquoted(code) {
	return !isSpace(code) && !isQuote(code)
		 && code !== ATTR_CLOSE && code !== EQUALS;
}

const HASH    = 35; // #
const DOT$1     = 46; // .
const SLASH   = 47; // /

/**
 * Consumes a single element node from current abbreviation stream
 * @param  {StringReader} stream
 * @return {Node}
 */
function consumeElement(stream) {
	// consume element name, if provided
	const start = stream.pos;
	const node = new Node(eatName(stream));
	let next;

	while (!stream.eof()) {
		if (stream.eat(DOT$1)) {
			node.addClass(eatName(stream));
		} else if (stream.eat(HASH)) {
			node.setAttribute('id', eatName(stream));
		} else if (stream.eat(SLASH)) {
			// A self-closing indicator must be at the end of non-grouping node
			if (node.isGroup) {
				stream.backUp(1);
				throw stream.error('Unexpected self-closing indicator');
			}
			node.selfClosing = true;
			if (next = consumeRepeat(stream)) {
				node.repeat = next;
			}
			break;
		} else if (next = consumeAttributes(stream)) {
			for (let i = 0, il = next.length; i < il; i++) {
				node.setAttribute(next[i]);
			}
		} else if ((next = consumeText(stream)) !== null) {
			node.value = next;
		} else if (next = consumeRepeat(stream)) {
			node.repeat = next;
		} else {
			break;
		}
	}

	if (start === stream.pos) {
		throw stream.error(`Unable to consume abbreviation node, unexpected ${stream.peek()}`);
	}

	return node;
}

function eatName(stream) {
	stream.start = stream.pos;
	stream.eatWhile(isName);
	return stream.current();
}

function isName(code) {
	return isAlphaNumeric(code)
		|| code === 45 /* - */
		|| code === 58 /* : */
		|| code === 36 /* $ */
		|| code === 64 /* @ */
		|| code === 33 /* ! */
		|| code === 95 /* _ */
		|| code === 37 /* % */;
}

const GROUP_START = 40; // (
const GROUP_END   = 41; // )
const OP_SIBLING  = 43; // +
const OP_CHILD    = 62; // >
const OP_CLIMB    = 94; // ^

/**
 * Parses given string into a node tree
 * @param  {String} str Abbreviation to parse
 * @return {Node}
 */
function parse(str) {
	const stream = new StreamReader(str.trim());
	const root = new Node();
	let ctx = root, groupStack = [], ch;

	while (!stream.eof()) {
		ch = stream.peek();

		if (ch === GROUP_START) { // start of group
			// The grouping node should be detached to properly handle
			// out-of-bounds `^` operator. Node will be attached right on group end
			const node = new Node();
			groupStack.push([node, ctx, stream.pos]);
			ctx = node;
			stream.next();
			continue;
		} else if (ch === GROUP_END) { // end of group
			const lastGroup = groupStack.pop();
			if (!lastGroup) {
				throw stream.error('Unexpected ")" group end');
			}

			const node = lastGroup[0];
			ctx = lastGroup[1];
			stream.next();

			// a group can have a repeater
			if (node.repeat = consumeRepeat(stream)) {
				ctx.appendChild(node);
			} else {
				// move all children of group into parent node
				while (node.firstChild) {
					ctx.appendChild(node.firstChild);
				}
			}
			// for convenience, groups can be joined with optional `+` operator
			stream.eat(OP_SIBLING);

			continue;
		}

		const node = consumeElement(stream);
		ctx.appendChild(node);

		if (stream.eof()) {
			break;
		}

		switch (stream.peek()) {
			case OP_SIBLING:
				stream.next();
				continue;

			case OP_CHILD:
				stream.next();
				ctx = node;
				continue;

			case OP_CLIMB:
				// it’s perfectly valid to have multiple `^` operators
				while (stream.eat(OP_CLIMB)) {
					ctx = ctx.parent || ctx;
				}
				continue;
		}
	}

	if (groupStack.length) {
		stream.pos = groupStack.pop()[2];
		throw stream.error('Expected group close');
	}

	return root;
}

/**
 * Parses given abbreviation and un-rolls it into a full tree: recursively
 * replaces repeated elements with actual nodes
 * @param  {String} abbr
 * @return {Node}
 */
function index(abbr) {
	const tree = parse(abbr);
	tree.walk(unroll);
	return tree;
}

function unroll(node) {
	if (!node.repeat || !node.repeat.count) {
		return;
	}

	const parent = node.parent;
	let ix = parent.children.indexOf(node);

	for (let i = 0; i < node.repeat.count; i++) {
		const clone = node.clone(true);
		clone.repeat.value = i + 1;
		clone.walk(unroll);

		if (clone.isGroup) {
			while (clone.children.length > 0) {
				clone.firstChild.repeat = clone.repeat;
				parent.insertAt(clone.firstChild, ix++);
			}
		} else {
			parent.insertAt(clone, ix++);
		}
	}

	node.parent.removeChild(node);
}

/**
 * For every node in given `tree`, 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, attribues and so on, possibly a complex
 * abbreviation with multiple elements. So we have to get snippet, parse it
 * and recursively resolve it.
 *
 * @param  {Node} tree                 Parsed Emmet abbreviation
 * @param  {SnippetsRegistry} registry Registry with all available snippets
 * @return {Node} Updated tree
 */

var index$1 = function(tree, registry) {
    tree.walk(node => resolveNode(node, registry));
    return tree;
};

function resolveNode(node, registry) {
    const stack = new Set();
    const resolve = node => {
        const snippet = registry.resolve(node.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.has(snippet)) {
            return;
        }

        // In case if matched snippet is a function, pass control into it
        if (typeof snippet.value === 'function') {
            return snippet.value(node, registry, resolve);
        }

        const tree = index(snippet.value);

        stack.add(snippet);
        tree.walk(resolve);
        stack.delete(snippet);

        // move current node contents into new tree
        const childTarget = findDeepestNode(tree);
        merge(childTarget, node);

        while (tree.firstChild) {
            node.parent.insertBefore(tree.firstChild, node);
        }

        childTarget.parent.insertBefore(node, childTarget);
        childTarget.remove();
    };

    resolve(node);
}

/**
 * Adds data from first node into second node and returns it
 * @param  {Node} from
 * @param  {Node} to
 * @return {Node}
 */
function merge(from, to) {
    to.name = from.name;

    if (from.selfClosing) {
        to.selfClosing = true;
    }

    if (from.value != null) {
        to.value = from.value;
    }

    if (from.repeat) {
        to.repeat = Object.assign({}, from.repeat);
    }

    return mergeAttributes(from, to);
}

/**
 * Transfer attributes from first element to second one and preserve first
 * element’s attributes order
 * @param  {Node} from
 * @param  {Node} to
 * @return {Node}
 */
function mergeAttributes(from, to) {
    mergeClassNames(from, to);

    // It’s important to preserve attributes order: ones in `from` have higher
    // pripority than in `to`. Collect attributes in map in order they should
    // appear in `to`
    const attrMap = new Map();

    let attrs = from.attributes;
    for (let i = 0; i < attrs.length; i++) {
        attrMap.set(attrs[i].name, attrs[i].clone());
    }

    attrs = to.attributes.slice();
    for (let i = 0, attr, a; i < attrs.length; i++) {
        attr = attrs[i];
        if (attrMap.has(attr.name)) {
            a = attrMap.get(attr.name);
            a.value = attr.value;

            // If user explicitly wrote attribute in abbreviation, it’s no longer
            // implied and should be outputted even if value is empty
            if (a.options.implied) {
                a.options.implied = false;
            }
        } else {
            attrMap.set(attr.name, attr);
        }

        to.removeAttribute(attr);
    }

    const newAttrs = Array.from(attrMap.values());
    for (let i = 0; i < newAttrs.length; i++) {
        to.setAttribute(newAttrs[i]);
    }

    return to;
}

/**
 * Adds class names from first node to second one
 * @param  {Node} from
 * @param  {Node} to
 * @return {Node}
 */
function mergeClassNames(from, to) {
    const classNames = from.classList;
    for (let i = 0; i < classNames.length; i++) {
        to.addClass(classNames[i]);
    }

    return to;
}

/**
 * Finds node which is the deepest for in current node or node iteself.
 * @param  {Node} node
 * @return {Node}
 */
function findDeepestNode(node) {
	while (node.children.length) {
		node = node.children[node.children.length - 1];
	}

	return node;
}

const inlineElements = new Set('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'.split(','));
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'
};

/**
 * Returns best child node name for given parent node name
 * @param  {String} parentName Name of parent node
 * @return {String}
 */
function resolveImplicitName(parentName) {
    parentName = (parentName || '').toLowerCase();
    return elementMap[parentName]
        || (inlineElements.has(parentName) ? 'span' : 'div');
}

/**
 * Adds missing tag names for given tree depending on node’s parent name
 */
var implicitTags = function(tree) {
    tree.walk(node => {
        // resolve only nameless nodes without content
        if (node.name == null && node.attributes.length) {
            node.name = resolveImplicitName(node.parent.name);
        }
    });
    return tree;
};

/**
 * Locates all occurances of given `token` which are not escaped (e.g. are not
 * preceded with `\`) given in `str`
 * @param  {String} str
 * @return {Array}  Array of token ranges
 */
function findUnescapedTokens(str, token) {
    const result = new Set();
    const tlen = token.length;

    // 1. Find all occurances of tokens
    let pos = 0;
    while ((pos = str.indexOf(token, pos)) !== -1) {
        result.add(pos);
        pos += tlen;
    }

    if (result.size) {
        // 2. Remove ones that escaped
        let pos = 0;
        const len = str.length;

        while (pos < len) {
            if (str[pos++] === '\\') {
                result.delete(pos++);
            }
        }
    }

    return Array.from(result).map(ix => range(ix, tlen));
}

/**
 * Replaces `ranges`, generated by `range()` function, with given `value` in `str`
 * @param  {String} str    Where to replace ranges
 * @param  {Array} ranges Ranes, created by `range()` function
 * @param  {String|Function} value  Replacement value. If it’s a function, it
 * will take a range value as argument and should return a new string
 * @return {String}
 */
function replaceRanges(str, ranges, value) {
	// should walk from the end of array to keep ranges valid after replacement
	for (let i = ranges.length - 1; i >= 0; i--) {
		const r = ranges[i];

        let offset = 0;
        let offsetLength = 0;
        if (str.substr(r[0] + r[1], 1) === '@'){
            const matches = str.substr(r[0] + r[1] + 1).match(/^(\d+)/);
            if (matches) {
                offsetLength = matches[1].length + 1;
                offset = parseInt(matches[1]) - 1;
            }
        }

		str = str.substring(0, r[0])
			+ (typeof value === 'function' ? value(str.substr(r[0], r[1]), offset) : value)
			+ str.substring(r[0] + r[1] + offsetLength);
	}

	return str;
}

function range(start, length) {
    return [start, length];
}

const numberingToken = '$';

/**
 * Numbering of expanded abbreviation: finds all nodes with `$` in value
 * or attributes and replaces its occurances with repeater value
 */
var applyNumbering = function(tree) {
    tree.walk(applyNumbering$1);
    return tree;
};

/**
 * Applies numbering for given node: replaces occurances of numbering token
 * in node’s name, content and attributes
 * @param  {Node} node
 * @return {Node}
 */
function applyNumbering$1(node) {
    const repeater = findRepeater(node);

    if (repeater && repeater.value != null) {
        // NB replace numbering in nodes with explicit repeater only:
        // it solves issues with abbreviations like `xsl:if[test=$foo]` where
        // `$foo` is preferred output
        const value = repeater.value;

        node.name = replaceNumbering(node.name, value);
        node.value = replaceNumbering(node.value, value);
        node.attributes.forEach(attr => {
            const copy = node.getAttribute(attr.name).clone();
            copy.name = replaceNumbering(attr.name, value);
            copy.value = replaceNumbering(attr.value, value);
            node.replaceAttribute(attr.name, copy);
        });
    }

    return node;
}

/**
 * Returns repeater object for given node
 * @param  {Node} node
 * @return {Object}
 */
function findRepeater(node) {
    while (node) {
        if (node.repeat) {
            return node.repeat;
        }

        node = node.parent;
    }
}

/**
 * Replaces numbering in given string
 * @param  {String} str
 * @param  {Number} value
 * @return {String}
 */
function replaceNumbering(str, value) {
    // replace numbering in strings only: skip explicit wrappers that could
    // contain unescaped numbering tokens
    if (typeof str === 'string') {
        const ranges = getNumberingRanges(str);
        return replaceNumberingRanges(str, ranges, value);
    }

    return str;
}

/**
 * Returns numbering ranges, e.g. ranges of `$` occurances, in given string.
 * Multiple adjacent ranges are combined
 * @param  {String} str
 * @return {Array}
 */
function getNumberingRanges(str) {
    return findUnescapedTokens(str || '', numberingToken)
    .reduce((out, range$$1) => {
        // skip ranges that actually belongs to output placeholder or tabstops
        if (!/[#{]/.test(str[range$$1[0] + 1] || '')) {
            const lastRange = out[out.length - 1];
            if (lastRange && lastRange[0] + lastRange[1] === range$$1[0]) {
                lastRange[1] += range$$1[1];
            } else {
                out.push(range$$1);
            }
        }

        return out;
    }, []);
}

/**
 * @param  {String} str
 * @param  {Array} ranges
 * @param  {Number} value
 * @return {String}
 */
function replaceNumberingRanges(str, ranges, value) {
    const replaced = replaceRanges(str, ranges, (token, offset) => {
        let _value = String(value + offset);
        // pad values for multiple numbering tokens, e.g. 3 for $$$ becomes 003
        while (_value.length < token.length) {
            _value = '0' + _value;
        }
        return _value;
    });

    // unescape screened numbering tokens
    return unescapeString(replaced);
}

/**
 * Unescapes characters, screened with `\`, in given string
 * @param  {String} str
 * @return {String}
 */
function unescapeString(str) {
    let i = 0, result = '';
    const len = str.length;

    while (i < len) {
        const ch = str[i++];
        result += (ch === '\\') ? (str[i++] || '') : ch;
    }

    return result;
}

/** Placeholder for inserted content */
const placeholder = '$#';

/** Placeholder for caret */
const caret = '|';

const reUrl = /^((?:https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
const reEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
const reProto = /^([a-z]+:)?\/\//i;

/**
 * Inserts content into node with implicit repeat count: this node is then
 * duplicated for each content item and content itself is inserted either into
 * deepest child or instead of a special token.
 *
 * This method uses two distinct steps: `prepare()` and `insert()` since most
 * likely these steps will be used separately to properly insert content
 * with unescaped `$` numbering markers.
 *
 * @param {Node} tree Parsed abbreviation
 * @param {String[]} content Array of content items to insert
 * @return {Node}
 */
/**
 * Finds nodes with implicit repeat and creates `amount` copies of it in tree
 * @param  {Node} tree
 * @param  {Number} amount
 * @return {Node}
 */
function prepare(tree, amount) {
    amount = amount || 1;
    tree.walk(node => {
        if (node.repeat && node.repeat.count === null) {
            for (let i = 0; i < amount; i++) {
                const clone = node.clone(true);
                clone.repeat.implicit = true;
                clone.repeat.count = amount;
                clone.repeat.value = i + 1;
                clone.repeat.index = i;
                node.parent.insertBefore(clone, node);
            }

            node.remove();
        }
    });

    return tree;
}

/**
 * Inserts content into implicitly repeated nodes, created by `prepare()` method
 * @param  {Node} tree
 * @param  {String[]} content
 * @return {Node}
 */
function insert(tree, content) {
    if (Array.isArray(content) && content.length) {
        let updated = false;
        tree.walk(node => {
            if (node.repeat && node.repeat.implicit) {
                updated = true;
                insertContent(node, content[node.repeat.index]);
            }
        });

        if (!updated) {
            // no node with implicit repeat was found, insert content as
            // deepest child
            setNodeContent(findDeepestNode$1(tree), content.join('\n'));
        }
    }

    return tree;
}

/**
 * Inserts `content` into given `node`: either replaces output placeholders
 * or inserts it into deepest child node
 * @param  {Node} node
 * @param  {String} content
 * @return {Node}
 */
function insertContent(node, content) {
	let inserted = insertContentIntoPlaceholder(node, content);
	node.walk(child => inserted |= insertContentIntoPlaceholder(child, content));

	if (!inserted) {
		// no placeholders were found in node, insert content into deepest child
		setNodeContent(findDeepestNode$1(node), content);
	}

	return node;
}

/**
 * Inserts given `content` into placeholders for given `node`. Placeholders
 * might be available in attribute values and node content
 * @param  {Node} node
 * @param  {String} content
 * @return {Boolean} Returns `true` if placeholders were found and replaced in node
 */
function insertContentIntoPlaceholder(node, content) {
	const state = {replaced: false};

	node.value = replacePlaceholder(node.value, content, state);
	node.attributes.forEach(attr => {
		if (attr.value) {
			node.setAttribute(attr.name, replacePlaceholder(attr.value, content, state));
		}
	});

	return state.replaced;
}

/**
 * Replaces all placeholder occurances in given `str` with `value`
 * @param  {String} str
 * @param  {String} value
 * @param  {Object} [_state] If provided, set `replaced` property of given
 * object to `true` if placeholder was found and replaced
 * @return {String}
 */
function replacePlaceholder(str, value, _state) {
	if (typeof str === 'string') {
		const ranges = findUnescapedTokens(str, placeholder);
		if (ranges.length) {
			if (_state) {
				_state.replaced = true;
			}

			str = replaceRanges(str, ranges, value);
		}
	}

	return str;
}

/**
 * Finds node which is the deepest for in current node or node iteself.
 * @param  {Node} node
 * @return {Node}
 */
function findDeepestNode$1(node) {
	while (node.children.length) {
		node = node.children[node.children.length - 1];
	}

	return node;
}

/**
 * Updates content of given node
 * @param {Node} node
 * @param {String} content
 */
function setNodeContent(node, content) {
	// find caret position and replace it with content, if possible
	if (node.value) {
		const ranges = findUnescapedTokens(node.value, caret);
		if (ranges.length) {
			node.value = replaceRanges(node.value, ranges, content);
			return;
		}
	}

	if (node.name.toLowerCase() === 'a' || node.hasAttribute('href')) {
		// special case: inserting content into `<a>` tag
		if (reUrl.test(content)) {
			node.setAttribute('href', (reProto.test(content) ? '' : 'http://') + content);
		} else if (reEmail.test(content)) {
			node.setAttribute('href', 'mailto:' + content);
		}
	}

	node.value = content;
}

const defaultOptions$2 = {
	element: '__',
	modifier: '_'
};

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);

/**
 * BEM transformer: updates class names written as `-element` and
 * `_modifier` into full class names as described in BEM specs. Also adds missing
 * class names: fir example, if node contains `.block_modifier` class, ensures
 * that element contains `.block` class as well
 */
var bem = function(tree, options) {
	options = Object.assign({}, defaultOptions$2, options);

	tree.walk(node => expandClassNames(node, options));

	const lookup = createBlockLookup(tree);
	tree.walk(node => expandShortNotation(node, lookup, options));

	return tree;
};

/**
 * 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
 * @param  {Node} node
 * @param  {Object} options
 * @return {Set}
 */
function expandClassNames(node, options) {
	const classNames = node.classList.reduce((out, cl) => {
		// remove all modifiers and element prefixes from class name to get a base element name
		const ix = cl.indexOf('_');
		if (ix > 0 && !cl.startsWith('-')) {
			out.add(cl.slice(0, ix));
		    out.add(cl.slice(ix));
			return out;
		}

		return out.add(cl);
	}, new Set());

	if (classNames.size) {
		node.setAttribute('class', Array.from(classNames).join(' '));
	}
}

/**
 * Expands short BEM notation, e.g. `-element` and `_modifier`
 * @param  {Node} node      Parsed Emmet abbreviation node
 * @param  {Map} lookup     BEM block name lookup
 * @param  {Object} options
 */
function expandShortNotation(node, lookup, options) {
	const classNames = node.classList.reduce((out, cl) => {
		let prefix, m;
		const originalClass = cl;

		// parse element definition (could be only one)
		if (m = cl.match(reElement)) {
			prefix = getBlockName(node, lookup, m[1]) + options.element + m[2];
			out.add(prefix);
			cl = cl.slice(m[0].length);
		}

		// parse modifiers definitions (may contain multiple)
		while (m = cl.match(reModifier)) {
			if (!prefix) {
				prefix = getBlockName(node, lookup, m[1]);
				out.add(prefix);
			}

			out.add(`${prefix}${options.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
			out.add(originalClass);
		}

		return out;
	}, new Set());

	const arrClassNames = Array.from(classNames).filter(Boolean);
	if (arrClassNames.length) {
		node.setAttribute('class', arrClassNames.join(' '));
	}
}

/**
 * Creates block name lookup for each node in given tree, e.g. finds block
 * name explicitly for each node
 * @param  {Node} tree
 * @return {Map}
 */
function createBlockLookup(tree) {
	const lookup = new Map();

	tree.walk(node => {
		const classNames = node.classList;
		if (classNames.length) {
			// guess best block name from class or use parent’s block name
			lookup.set(node,
				find(classNames, blockCandidates1)
				|| find(classNames, blockCandidates2)
				|| lookup.get(node.parent)
			);
		}
	});

	return lookup;
}

/**
 * Returns block name for given `node` by `prefix`, which tells the depth of
 * of parent node lookup
 * @param  {Node} node
 * @param  {Map} lookup
 * @param  {String} prefix
 * @return {String}
 */
function getBlockName(node, lookup, prefix) {
	let depth = prefix.length > 1 ? prefix.length : 0;

	// NB don’t walk up to root node, stay at first root child in case of
	// too deep prefix
	while (node.parent && node.parent.parent && depth--) {
		node = node.parent;
	}

	return lookup.get(node) || '';
}

function find(arr, filter) {
	return arr.filter(filter)[0];
}

/**
 * JSX transformer: replaces `class` and `for` attributes with `className` and
 * `htmlFor` attributes respectively
 */
var jsx = function(tree) {
	tree.walk(node => {
		replace(node, 'class', 'className');
		replace(node, 'for', 'htmlFor');
	});
	return tree;
};

function replace(node, oldName, newName) {
	let attr = node.getAttribute(oldName);
	if (attr) {
		attr.name = newName;
	}
}

const reSupporterNames = /^xsl:(variable|with\-param)$/i;

/**
 * XSL transformer: removes `select` attributes from certain nodes that contain
 * children
 */
var xsl = function(tree) {
	tree.walk(node => {
		if (reSupporterNames.test(node.name || '') && (node.children.length || node.value)) {
			node.removeAttribute('select');
		}
	});
	return tree;
};

const supportedAddons = { bem, jsx, xsl };

/**
 * Runs additional transforms on given tree.
 * These transforms may introduce side-effects and unexpected result
 * so they are not applied by default, authors must specify which addons
 * in `addons` argument as `{addonName: addonOptions}`
 * @param {Node} tree Parsed Emmet abbreviation
 * @param {Object} addons Add-ons to apply and their options
 */
var addons = function(tree, addons) {
    Object.keys(addons || {}).forEach(key => {
        if (key in supportedAddons) {
            const addonOpt = typeof addons[key] === 'object' ? addons[key] : null;
            tree = tree.use(supportedAddons[key], addonOpt);
        }
    });

    return tree;
};

/**
 * Applies basic HTML-specific transformations for given parsed abbreviation:
 * – resolve implied tag names
 * – insert repeated content
 * – resolve node numbering
 */
var index$2 = function(tree, content, appliedAddons) {
    if (typeof content === 'string') {
        content = [content];
    } else if (content && typeof content === 'object' && !Array.isArray(content)) {
        appliedAddons = content;
        content = null;
    }

    return tree
    .use(implicitTags)
    .use(prepare, Array.isArray(content) ? content.length : null)
    .use(applyNumbering)
    .use(insert, content)
    .use(addons, appliedAddons);
};

/**
 * Replaces all unescaped ${variable} occurances in given parsed abbreviation
 * `tree` with values provided in `variables` hash. Precede `$` with `\` to
 * escape it and skip replacement
 * @param {Node} tree Parsed abbreviation tree
 * @param {Object} variables Variables values
 * @return {Node}
 */
function replaceVariables(tree, variables) {
	variables = variables || {};
    tree.walk(node => replaceInNode(node, variables));
    return tree;
}

function replaceInNode(node, variables) {
    // Replace variables in attributes.
    const attrs = node.attributes;

    for (let i = 0, il = attrs.length; i < il; i++) {
        const attr = attrs[i];
        if (typeof attr.value === 'string') {
            node.setAttribute(attr.name, replaceInString(attr.value, variables));
        }
    }

    if (node.value != null) {
        node.value = replaceInString(node.value, variables);
    }

    return node;
}

/**
 * Replaces all unescaped `${variable}` occurances in given string with values
 * from `variables` object
 * @param  {String} string
 * @param  {Object} variables
 * @return {String}
 */
function replaceInString(string, variables) {
    const model = createModel(string);
    let offset = 0;
    let output = '';

    for (let i = 0, il = model.variables.length; i < il; i++) {
        const v = model.variables[i];
        let value = v.name in variables ? variables[v.name] : v.name;
        if (typeof value === 'function') {
            value = value(model.string, v, offset + v.location);
        }

        output += model.string.slice(offset, v.location) + value;
        offset = v.location + v.length;
    }

    return output + model.string.slice(offset);
}

/**
 * Creates variable model from given string. The model contains a `string` with
 * all escaped variable tokens written without escape symbol and `variables`
 * property with all unescaped variables and their ranges
 * @param  {String} string
 * @return {Object}
 */
function createModel(string) {
    const reVariable = /\$\{([a-z][\w\-]*)\}/ig;
    const escapeCharCode = 92; // `\` symbol
    const variables = [];

    // We have to replace unescaped (e.g. not preceded with `\`) tokens.
    // Instead of writing a stream parser, we’ll cut some edges here:
    // 1. Find all tokens
    // 2. Walk string char-by-char and resolve only tokens that are not escaped
    const tokens = new Map();
    let m;
    while (m = reVariable.exec(string)) {
        tokens.set(m.index, m);
    }

    if (tokens.size) {
        let start = 0, pos = 0, len = string.length;
        let output = '';
        while (pos < len) {
            if (string.charCodeAt(pos) === escapeCharCode && tokens.has(pos + 1)) {
                // Found escape symbol that escapes variable: we should
                // omit this symbol in output string and skip variable
                const token = tokens.get(pos + 1);
                output += string.slice(start, pos) + token[0];
                start = pos = token.index + token[0].length;
                tokens.delete(pos + 1);
                continue;
            }

            pos++;
        }

        string = output + string.slice(start);

        // Not using `.map()` here to reduce memory allocations
        const validMatches = Array.from(tokens.values());
        for (let i = 0, il = validMatches.length; i < il; i++) {
            const token = validMatches[i];
            variables.push({
                name: token[1],
                location: token.index,
                length: token[0].length
            });
        }
    }

    return {string, variables};
}

const DOLLAR      = 36;  // $
const COLON       = 58;  // :
const ESCAPE$1      = 92;  // \
const OPEN_BRACE  = 123; // {
const CLOSE_BRACE = 125; // }

/**
 * Finds fields in given string and returns object with field-less string
 * and array of fileds found
 * @param  {String} string
 * @return {Object}
 */
function parse$2(string) {
	const stream = new StreamReader(string);
	const fields = [];
	let cleanString = '', offset = 0, pos = 0;
	let code, field;

	while (!stream.eof()) {
		code = stream.peek();
		pos = stream.pos;

		if (code === ESCAPE$1) {
			stream.next();
			stream.next();
		} else if (field = consumeField(stream, cleanString.length + pos - offset)) {
			fields.push(field);
			cleanString += stream.string.slice(offset, pos) + field.placeholder;
			offset = stream.pos;
		} else {
			stream.next();
		}
	}

	return new FieldString(cleanString + stream.string.slice(offset), fields);
}

/**
 * Marks given `string` with `fields`: wraps each field range with
 * `${index:placeholder}` (by default) or any other token produced by `token`
 * function, if provided
 * @param  {String} string String to mark
 * @param  {Array} fields Array of field descriptor. A field descriptor is a
 * `{index, location, length}` array. It is important that fields in array
 * must be ordered by their location in string: some fields my refer the same
 * location so they must appear in order that user expects.
 * @param  {Function} [token] Function that generates field token. This function
 * received two arguments: `index` and `placeholder` and should return string
 * @return {String}  String with marked fields
 */
function mark(string, fields, token) {
	token = token || createToken;

	// order fields by their location and appearence
	// NB field ranges should not overlap! (not supported yet)
	const ordered = fields
	.map((field, order) => ({order, field, end: field.location + field.length}))
	.sort((a, b) => (a.end - b.end) || (a.order - b.order));

	// mark ranges in string
	let offset = 0;
	const result = ordered.map(item => {
		const placeholder = string.substr(item.field.location, item.field.length);
		const prefix = string.slice(offset, item.field.location);
		offset = item.end;
		return prefix + token(item.field.index, placeholder);
	});

	return result.join('') + string.slice(offset);
}

/**
 * Creates field token for string
 * @param  {Number} index       Field index
 * @param  {String} placeholder Field placeholder, could be empty string
 * @return {String}
 */
function createToken(index, placeholder) {
	return placeholder ? `\${${index}:${placeholder}}` : `\${${index}}`;
}

/**
 * Consumes field from current stream position: it can be an `$index` or
 * or `${index}` or `${index:placeholder}`
 * @param  {StreamReader} stream
 * @param  {Number}       location Field location in *clean* string
 * @return {Object} Object with `index` and `placeholder` properties if
 * fieald was successfully consumed, `null` otherwise
 */
function consumeField(stream, location) {
	const start = stream.pos;

	if (stream.eat(DOLLAR)) {
		// Possible start of field
		let index = consumeIndex(stream);
		let placeholder = '';

		// consumed $index placeholder
		if (index != null) {
			return new Field(index, placeholder, location);
		}

		if (stream.eat(OPEN_BRACE)) {
			index = consumeIndex(stream);
			if (index != null) {
				if (stream.eat(COLON)) {
					placeholder = consumePlaceholder(stream);
				}

				if (stream.eat(CLOSE_BRACE)) {
					return new Field(index, placeholder, location);
				}
			}
		}
	}

	// If we reached here then there’s no valid field here, revert
	// back to starting position
	stream.pos = start;
}

/**
 * Consumes a placeholder: value right after `:` in field. Could be empty
 * @param  {StreamReader} stream
 * @return {String}
 */
function consumePlaceholder(stream) {
	let code;
	const stack = [];
	stream.start = stream.pos;

	while (!stream.eof()) {
		code = stream.peek();

		if (code === OPEN_BRACE) {
			stack.push(stream.pos);
		} else if (code === CLOSE_BRACE) {
			if (!stack.length) {
				break;
			}
			stack.pop();
		}
		stream.next();
	}

	if (stack.length) {
		throw stream.error('Unable to find matching "}" for curly brace at ' + stack.pop());
	}

	return stream.current();
}

/**
 * Consumes integer from current stream position
 * @param  {StreamReader} stream
 * @return {Number}
 */
function consumeIndex(stream) {
	stream.start = stream.pos;
	if (stream.eatWhile(isNumber)) {
		return Number(stream.current());
	}
}

class Field {
	constructor(index, placeholder, location) {
		this.index = index;
		this.placeholder = placeholder;
		this.location = location;
		this.length = this.placeholder.length;
	}
}

class FieldString {
	/**
	 * @param {String} string
	 * @param {Field[]} fields
	 */
	constructor(string, fields) {
		this.string = string;
		this.fields = fields;
	}

	mark(token) {
		return mark(this.string, this.fields, token);
	}

	toString() {
		return string;
	}
}

const defaultFieldsRenderer = text => text;

/**
 * Output node is an object containing generated output for given Emmet
 * abbreviation node. Output node can be passed to various processors that
 * may shape-up final node output. The final output is simply a concatenation
 * of `.open`, `.text` and `.close` properties and its `.before*` and `.after*`
 * satellites
 * @param {Node}     node           Parsed Emmet abbreviation node
 * @param {Function} fieldsRenderer A function for rendering fielded text (text with
 * tabstops) for current node. @see ./render.js for details
 */
class OutputNode {
	constructor(node, fieldsRenderer, options) {
		if (typeof fieldsRenderer === 'object') {
			options = fieldsRenderer;
			fieldsRenderer = null;
		}

		this.node = node;
		this._fieldsRenderer = fieldsRenderer || defaultFieldsRenderer;

		this.open = null;
		this.beforeOpen = '';
		this.afterOpen = '';

		this.close = null;
		this.beforeClose = '';
		this.afterClose = '';

		this.text = null;
		this.beforeText = '';
		this.afterText = '';

		this.indent = '';
		this.newline = '';

		if (options) {
            Object.assign(this, options);
        }
	}

	clone() {
		return new this.constructor(this.node, this);
	}

	/**
	 * Properly indents given multiline text
	 * @param {String} text
	 */
	indentText(text) {
		const lines = splitByLines(text);
        if (lines.length === 1) {
            // no newlines, nothing to indent
            return text;
        }

        // No newline and no indent means no formatting at all:
        // in this case we should replace newlines with spaces
        const nl = (!this.newline && !this.indent) ? ' ' : this.newline;
        return lines.map((line, i) => i ? this.indent + line : line).join(nl);
	}

	/**
	 * Renders given text with fields
	 * @param {String} text
	 * @return {String}
	 */
	renderFields(text) {
		return this._fieldsRenderer(text);
	}

	toString(children) {
		const open = this._wrap(this.open, this.beforeOpen, this.afterOpen);
		const close = this._wrap(this.close, this.beforeClose, this.afterClose);
		const text = this._wrap(this.text, this.beforeText, this.afterText);

		return open + text + (children != null ? children : '') + close;
	}

	_wrap(str, before, after) {
		before = before != null ? before : '';
		after = after != null ? after : '';

        // automatically trim whitespace for non-empty wraps
        if (str != null) {
            str = before ? str.replace(/^\s+/, '') : str;
            str = after ? str.replace(/\s+$/, '') : str;
            return before + this.indentText(str) + after;
        }

        return '';
	}
}

/**
 * Splits given text by lines
 * @param  {String} text
 * @return {String[]}
 */
function splitByLines(text) {
	return (text || '').split(/\r\n|\r|\n/g);
}

/**
 * Default output of field (tabstop)
 * @param  {Number} index       Field index
 * @param  {String} placeholder Field placeholder, can be null
 * @return {String}
 */
const defaultField = (index, placeholder) => (placeholder || '');

/**
 * Renders given parsed abbreviation `tree` via `formatter` function.

 * @param {Node}     tree      Parsed Emmet abbreviation
 * @param {Function} [field]   Optional function to format field/tabstop (@see `defaultField`)
 * @param {Function} formatter Output formatter function. It takes an output node—
 * a special wrapper for parsed node that holds formatting and output properties—
 * and updates its output properties to shape-up node’s output.
 * Function arguments:
 * 	– `outNode`: OutputNode
 * 	– `renderFields`: a helper function that parses fields/tabstops from given
 * 	   text and replaces them with `field` function output.
 * 	   It also takes care about field indicies and ensures that the same indicies
 * 	   from different nodes won’t collide
 */
function render(tree, field, formatter) {
    if (typeof formatter === 'undefined') {
        formatter = field;
        field = null;
    }

    field = field || defaultField;

    // Each node may contain fields like `${1:placeholder}`.
	// Since most modern editors will link all fields with the same
	// index, we have to ensure that different nodes has their own indicies.
	// We’ll use this `fieldState` object to globally increment field indices
	// during output
	const fieldState = { index: 1 };

    const fieldsRenderer = text => text == null
        ? field(fieldState.index++)
        : getFieldsModel(text, fieldState).mark(field);

	return run(tree.children, formatter, fieldsRenderer);
}

function run(nodes, formatter, fieldsRenderer) {
	return nodes.map(node => {
		const outNode = formatter(new OutputNode(node, fieldsRenderer));
		return outNode ? outNode.toString(run(node.children, formatter, fieldsRenderer)) : '';
	}).join('');
}

/**
 * Returns fields (tab-stops) model with properly updated indices that won’t
 * collide with fields in other nodes of foprmatted tree
 * @param  {String|Object} text Text to get fields model from or model itself
 * @param  {Object} fieldState Abbreviation tree-wide field state reference
 * @return {Object} Field model
 */
function getFieldsModel(text, fieldState) {
	const model = typeof text === 'object' ? text : parse$2(text);
    let largestIndex = -1;

    model.fields.forEach(field => {
		field.index += fieldState.index;
		if (field.index > largestIndex) {
			largestIndex = field.index;
		}
	});

	if (largestIndex !== -1) {
		fieldState.index = largestIndex + 1;
	}

    return model;
}

const TOKEN       = /^(.*?)([A-Z_]+)(.*?)$/;
const TOKEN_OPEN  = 91; // [
const TOKEN_CLOSE = 93; // ]

/**
 * A basic templating engine.
 * Takes every `[TOKEN]` from given string and replaces it with
 * `TOKEN` value from given `data` attribute. The token itself may contain
 * various characters between `[`, token name and `]`. Contents of `[...]` will
 * be outputted only if `TOKEN` value is not empty. Also, only `TOKEN` name will
 * be replaced with actual value, all other characters will remain as is.
 *
 * Example:
 * ```
 * template('[<NAME>]', {NAME: 'foo'}) -> "<foo>"
 * template('[<NAME>]', {}) -> ""
 * ```
 */
function template(str, data) {
	if (str == null) {
		return str;
	}

	// NB since token may contain inner `[` and `]`, we can’t just use regexp
	// for replacement, should manually parse string instead
	const stack = [];
	const replacer = (str, left, token, right) =>
		data[token] != null ? left + data[token] + right : '';

	let output = '';
	let offset = 0, i = 0;
	let code, lastPos;

	while (i < str.length) {
		code = str.charCodeAt(i);
		if (code === TOKEN_OPEN) {
			stack.push(i);
		} else if (code === TOKEN_CLOSE) {
			lastPos = stack.pop();
			if (!stack.length) {
				output += str.slice(offset, lastPos) +
					str.slice(lastPos + 1, i).replace(TOKEN, replacer);
				offset = i + 1;
			}
		}

		i++;
	}

	return output + str.slice(offset);
}

/**
 * Various utility methods used by formatters
 */

/**
 * Splits given text by lines
 * @param  {String} text
 * @return {String[]}
 */
function splitByLines$1(text) {
	return (text || '').split(/\r\n|\r|\n/g);
}

/**
 * Check if given node is a first child in its parent
 * @param  {Node}  node
 * @return {Boolean}
 */
function isFirstChild(node) {
	return node.parent.firstChild === node;
}

/**
 * Check if given node is a last child in its parent node
 * @param  {Node}  node
 * @return {Boolean}
 */


/**
 * Check if given node is a root node
 * @param  {Node}  node
 * @return {Boolean}
 */
function isRoot(node) {
	return node && !node.parent;
}

/**
 * Check if given node is a pseudo-snippet: a text-only node with explicitly
 * defined children
 * @param  {Node}  node
 * @return {Boolean}
 */
function isPseudoSnippet(node) {
    return node.isTextOnly && !!node.children.length;
}

/**
 * Handles pseudo-snippet node.
 * A pseudo-snippet is a text-only node with explicitly defined children.
 * For such case, we have to figure out if pseudo-snippet contains fields
 * (tab-stops) in node value and “split” it: make contents before field with
 * lowest index node’s “open” part and contents after lowest index — “close”
 * part. With this trick a final output will look like node’s children
 * are nested inside node value
 * @param  {OutputNode} outNode
 * @return {Boolean} Returns “true” if given node is a pseudo-snippets,
 * `false` otherwise
 */
function handlePseudoSnippet(outNode) {
	const node = outNode.node; // original abbreviaiton node

	if (isPseudoSnippet(node)) {
		const fieldsModel = parse$2(node.value);
		const field = findLowestIndexField(fieldsModel);
		if (field) {
			const parts = splitFieldsModel(fieldsModel, field);
            outNode.open = outNode.renderFields(parts[0]);
			outNode.close = outNode.renderFields(parts[1]);
		} else {
			outNode.text = outNode.renderFields(fieldsModel);
		}

		return true;
	}

	return false;
}

/**
 * Finds field with lowest index in given text
 * @param  {Object} model
 * @return {Object}
 */
function findLowestIndexField(model) {
	return model.fields.reduce((result, field) =>
		!result || field.index < result.index ? field : result
		, null);
}

/**
 * Splits given fields model in two parts by given field
 * @param  {Object} model
 * @param  {Object} field
 * @return {Array} Two-items array
 */
function splitFieldsModel(model, field) {
	const ix = model.fields.indexOf(field);

	const left = new model.constructor(
		model.string.slice(0, field.location),
		model.fields.slice(0, ix)
	);

	const right = new model.constructor(
		model.string.slice(field.location + field.length),
		model.fields.slice(ix + 1)
	);

	return [left, right];
}

const commentOptions = {
	// enable node commenting
	enabled: false,

	// attributes that should trigger node commenting on specific node,
	// if commenting is enabled
	trigger: ['id', 'class'],

	// comment before opening tag
	before: '',

	// comment after closing tag
	after: '\n<!-- /[#ID][.CLASS] -->'
};

/**
 * Renders given parsed Emmet abbreviation as HTML, formatted according to
 * `profile` options
 * @param  {Node}     tree    Parsed Emmet abbreviation
 * @param  {Profile}  profile Output profile
 * @param  {Object}  [options] Additional formatter options
 * @return {String}
 */
function html(tree, profile, options) {
	options = Object.assign({}, options);
	options.comment = Object.assign({}, commentOptions, options.comment);

	return render(tree, options.field, outNode => {
		outNode = setFormatting(outNode, profile);

		if (!handlePseudoSnippet(outNode)) {
			const node = outNode.node;

			if (node.name) {
				const name = profile.name(node.name);
				const attrs = formatAttributes(outNode, profile);

				outNode.open = `<${name}${attrs}${node.selfClosing ? profile.selfClose() : ''}>`;
				if (!node.selfClosing) {
					outNode.close = `</${name}>`;
				}

				commentNode(outNode, options.comment);
			}

			// Do not generate fields for nodes with empty value and children
			// or if node is self-closed
			if (node.value || (!node.children.length && !node.selfClosing) ) {
				outNode.text = outNode.renderFields(node.value);
			}
		}

		return outNode;
	});
}

/**
 * Updates formatting properties for given output node
 * @param  {OutputNode} outNode Output wrapper of farsed abbreviation node
 * @param  {Profile}    profile Output profile
 * @return {OutputNode}
 */
function setFormatting(outNode, profile) {
	const node = outNode.node;

    if (shouldFormatNode(node, profile)) {
        outNode.indent = profile.indent(getIndentLevel(node, profile));
        outNode.newline = '\n';
        const prefix = outNode.newline + outNode.indent;

        // do not format the very first node in output
        if (!isRoot(node.parent) || !isFirstChild(node)) {
            outNode.beforeOpen = prefix;
            if (node.isTextOnly) {
                outNode.beforeText = prefix;
            }
        }

        if (hasInnerFormatting(node, profile)) {
            if (!node.isTextOnly) {
                outNode.beforeText = prefix + profile.indent(1);
            }
            outNode.beforeClose = prefix;
        }
    }

    return outNode;
}

/**
 * Check if given node should be formatted
 * @param  {Node} node
 * @param  {Profile} profile
 * @return {Boolean}
 */
function shouldFormatNode(node, profile) {
	if (!profile.get('format')) {
		return false;
	}

    if (node.parent.isTextOnly
        && node.parent.children.length === 1
        && parse$2(node.parent.value).fields.length) {
        // Edge case: do not format the only child of text-only node,
        // but only if parent contains fields
        return false;
    }

	return isInline(node, profile) ? shouldFormatInline(node, profile) : true;
}

/**
 * Check if given inline node should be formatted as well, e.g. it contains
 * enough adjacent siblings that should force formatting
 * @param  {Node} node
 * @param  {Profile} profile
 * @return {Boolean}
 */
function shouldFormatInline(node, profile) {
	if (!isInline(node, profile)) {
		return false;
	}

    if (isPseudoSnippet(node)) {
        return true;
    }

    // check if inline node is the next sibling of block-level node
    if (node.childIndex === 0) {
        // first node in parent: format if it’s followed by a block-level element
        let next = node;
        while (next = next.nextSibling) {
            if (!isInline(next, profile)) {
                return true;
            }
        }
    } else if (!isInline(node.previousSibling, profile)) {
        // node is right after block-level element
        return true;
    }

    if (profile.get('inlineBreak')) {
        // check for adjacent inline elements before and after current element
        let adjacentInline = 1;
        let before = node, after = node;

        while (isInlineElement((before = before.previousSibling), profile)) {
            adjacentInline++;
        }

        while (isInlineElement((after = after.nextSibling), profile)) {
            adjacentInline++;
        }

		if (adjacentInline >= profile.get('inlineBreak')) {
			return true;
		}
    }

	// Another edge case: inline node contains node that should receive foramtting
	for (let i = 0, il = node.children.length; i < il; i++) {
		if (shouldFormatNode(node.children[i], profile)) {
			return true;
		}
	}

    return false;
}

/**
 * Check if given node contains inner formatting, e.g. any of its children should
 * be formatted
 * @param  {Node} node
 * @param  {Profile} profile
 * @return {Boolean}
 */
function hasInnerFormatting(node, profile) {
    // check if node if forced for inner formatting
    const nodeName = (node.name || '').toLowerCase();
    if (profile.get('formatForce').indexOf(nodeName) !== -1) {
        return true;
    }

    // check if any of children should receive formatting
    // NB don’t use `childrent.some()` to reduce memory allocations
    for (let i = 0; i < node.children.length; i++) {
        if (shouldFormatNode(node.children[i], profile)) {
            return true;
        }
    }

    return false;
}

/**
 * Outputs attributes of given abbreviation node as HTML attributes
 * @param  {OutputNode} outNode
 * @param  {Profile}    profile
 * @return {String}
 */
function formatAttributes(outNode, profile) {
	const node = outNode.node;

    return node.attributes.map(attr => {
        if (attr.options.implied && attr.value == null) {
    		return null;
    	}

    	const attrName = profile.attribute(attr.name);
    	let attrValue = null;

        // handle boolean attributes
    	if (attr.options.boolean || profile.get('booleanAttributes').indexOf(attrName.toLowerCase()) !== -1) {
    		if (profile.get('compactBooleanAttributes') && attr.value == null) {
    			return ` ${attrName}`;
    		} else if (attr.value == null) {
    			attrValue = attrName;
    		}
    	}

    	if (attrValue == null) {
    		attrValue = outNode.renderFields(attr.value);
    	}

			// For https://github.com/Microsoft/vscode/issues/63703
			// https://github.com/emmetio/markup-formatters/pull/2/files
    	return attr.options.before && attr.options.after
				? ` ${attrName}=${attr.options.before+attrValue+attr.options.after}`
				: ` ${attrName}=${profile.quote(attrValue)}`;
    }).join('');
}

/**
 * Check if given node is inline-level
 * @param  {Node}  node
 * @param  {Profile}  profile
 * @return {Boolean}
 */
function isInline(node, profile) {
	return (node && node.isTextOnly) || isInlineElement(node, profile);
}

/**
 * Check if given node is inline-level element, e.g. element with explicitly
 * defined node name
 * @param  {Node}  node
 * @param  {Profile}  profile
 * @return {Boolean}
 */
function isInlineElement(node, profile) {
	return node && profile.isInline(node);
}

/**
 * Computes indent level for given node
 * @param  {Node} node
 * @param  {Profile} profile
 * @param  {Number} level
 * @return {Number}
 */
function getIndentLevel(node, profile) {
	// Increase indent level IF NOT:
	// * parent is text-only node
	// * there’s a parent node with a name that is explicitly set to decrease level
	const skip = profile.get('formatSkip') || [];
	let level = node.parent.isTextOnly ? -2 : -1;
	let ctx = node;
	while (ctx = ctx.parent) {
		if (skip.indexOf( (ctx.name || '').toLowerCase() ) === -1) {
			level++;
		}
	}

	return level < 0 ? 0 : level;
}

/**
 * Comments given output node, if required
 * @param  {OutputNode} outNode
 * @param  {Object} options
 */
function commentNode(outNode, options) {
	const node = outNode.node;

	if (!options.enabled || !options.trigger || !node.name) {
		return;
	}

	const attrs = outNode.node.attributes.reduce((out, attr) => {
		if (attr.name && attr.value != null) {
			out[attr.name.toUpperCase().replace(/-/g, '_')] = attr.value;
		}

		return out;
	}, {});

	// add comment only if attribute trigger is present
	for (let i = 0, il = options.trigger.length; i < il; i++) {
		if (options.trigger[i].toUpperCase() in attrs) {
			outNode.open = template(options.before, attrs) + outNode.open;
			if (outNode.close) {
				outNode.close += template(options.after, attrs);
			}
			break;
		}
	}
}

/**
 * Common utility methods for indent-based syntaxes (Slim, Pug, etc.)
 */

const reId = /^id$/i;
const reClass = /^class$/i;
const defaultAttrOptions = {
	primary: attrs => attrs.join(''),
	secondary: attrs => attrs.map(attr => attr.isBoolean ? attr.name : `${attr.name}=${attr.value}`).join(', ')
};

const defaultNodeOptions = {
	open: null,
	close: null,
	omitName: /^div$/i,
	attributes: defaultAttrOptions
};

function indentFormat(outNode, profile, options) {
	options = Object.assign({}, defaultNodeOptions, options);
	const node = outNode.node;

	outNode.indent = profile.indent(getIndentLevel$1(node, profile));
	outNode.newline = '\n';

	// Do not format the very first node in output
    if (!isRoot(node.parent) || !isFirstChild(node)) {
        outNode.beforeOpen = outNode.newline + outNode.indent;
    }

	if (node.name) {
		const data = Object.assign({
			NAME: profile.name(node.name),
			SELF_CLOSE: node.selfClosing ? options.selfClose : null
		}, getAttributes(outNode, profile, options.attributes));

		// omit tag name if node has primary attributes
		if (options.omitName && options.omitName.test(data.NAME) && data.PRIMARY_ATTRS) {
			data.NAME = null;
		}

		if (options.open != null) {
			outNode.open = template(options.open, data);
		}

		if (options.close != null) {
			outNode.close = template(options.close, data);
		}
	}

	return outNode;
}

/**
 * Formats attributes of given node into a string.
 * @param  {OutputNode} node          Output node wrapper
 * @param  {Profile}    profile       Output profile
 * @param  {Object}     options       Additional formatting options
 * @return {String}
 */
function getAttributes(outNode, profile, options) {
	options = Object.assign({}, defaultAttrOptions, options);
	const primary = [], secondary = [];
	const node = outNode.node;

	node.attributes.forEach(attr => {
		if (attr.options.implied && attr.value == null) {
			return null;
		}

		const name = profile.attribute(attr.name);
		const value = outNode.renderFields(attr.value);

		if (reId.test(name)) {
			value && primary.push(`#${value}`);
		} else if (reClass.test(name)) {
			value && primary.push(`.${value.replace(/\s+/g, '.')}`);
		} else {
			const isBoolean = attr.value == null
				&& (attr.options.boolean || profile.get('booleanAttributes').indexOf(name.toLowerCase()) !== -1);

			secondary.push({ name, value, isBoolean });
		}
	});

	return {
		PRIMARY_ATTRS: options.primary(primary) || null,
		SECONDARY_ATTRS: options.secondary(secondary) || null
	};
}

/**
 * Computes indent level for given node
 * @param  {Node} node
 * @param  {Profile} profile
 * @param  {Number} level
 * @return {Number}
 */
function getIndentLevel$1(node, profile) {
	let level = node.parent.isTextOnly ? -2 : -1;
	let ctx = node;
	while (ctx = ctx.parent) {
		level++;
	}

	return level < 0 ? 0 : level;
}

const reNl = /\n|\r/;

/**
 * Renders given parsed Emmet abbreviation as HAML, formatted according to
 * `profile` options
 * @param  {Node}    tree      Parsed Emmet abbreviation
 * @param  {Profile} profile   Output profile
 * @param  {Object}  [options] Additional formatter options
 * @return {String}
 */
function haml(tree, profile, options) {
	options = options || {};
	const nodeOptions = {
		open: '[%NAME][PRIMARY_ATTRS][(SECONDARY_ATTRS)][SELF_CLOSE]',
		selfClose: '/',
		attributes: {
			secondary(attrs) {
				return attrs.map(attr => attr.isBoolean
					? `${attr.name}${profile.get('compactBooleanAttributes') ? '' : '=true'}`
					: `${attr.name}=${profile.quote(attr.value)}`
				).join(' ');
			}
		}
	};

	return render(tree, options.field, outNode => {
		outNode = indentFormat(outNode, profile, nodeOptions);
		outNode = updateFormatting(outNode, profile);

		if (!handlePseudoSnippet(outNode)) {
			const node = outNode.node;

			// Do not generate fields for nodes with empty value and children
			// or if node is self-closed
			if (node.value || (!node.children.length && !node.selfClosing) ) {
				outNode.text = outNode.renderFields(formatNodeValue(node, profile));
			}
		}

        return outNode;
	});
}

/**
 * Updates formatting properties for given output node
 * NB Unlike HTML, HAML is indent-based format so some formatting options from
 * `profile` will not take effect, otherwise output will be broken
 * @param  {OutputNode} outNode Output wrapper of parsed abbreviation node
 * @param  {Profile}    profile Output profile
 * @return {OutputNode}
 */
function updateFormatting(outNode, profile) {
	const node = outNode.node;

    if (!node.isTextOnly && node.value) {
        // node with text: put a space before single-line text
        outNode.beforeText = reNl.test(node.value)
			? outNode.newline + outNode.indent + profile.indent(1)
			: ' ';
    }

	return outNode;
}
/**
 * Formats value of given node: for multiline text we should add a ` |` suffix
 * at the end of each line. Also ensure that text is perfectly aligned.
 * @param  {Node}    node
 * @param  {Profile} profile
 * @return {String|null}
 */
function formatNodeValue(node, profile) {
	if (node.value != null && reNl.test(node.value)) {
		const lines = splitByLines$1(node.value);
		const indent = profile.indent(1);
		const maxLength = lines.reduce((prev, line) => Math.max(prev, line.length), 0);

		return lines.map((line, i) => `${i ? indent : ''}${pad(line, maxLength)} |`).join('\n');
	}

	return node.value;
}

function pad(text, len) {
	while (text.length < len) {
		text += ' ';
	}

	return text;
}

const reNl$1 = /\n|\r/;
const secondaryAttrs = {
	none:   '[ SECONDARY_ATTRS]',
	round:  '[(SECONDARY_ATTRS)]',
	curly:  '[{SECONDARY_ATTRS}]',
	square: '[[SECONDARY_ATTRS]'
};

/**
 * Renders given parsed Emmet abbreviation as Slim, formatted according to
 * `profile` options
 * @param  {Node}    tree      Parsed Emmet abbreviation
 * @param  {Profile} profile   Output profile
 * @param  {Object}  [options] Additional formatter options
 * @return {String}
 */
function slim(tree, profile, options) {
	options = options || {};
	const SECONDARY_ATTRS = options.attributeWrap
		&& secondaryAttrs[options.attributeWrap]
		|| secondaryAttrs.none;

	const booleanAttr = SECONDARY_ATTRS === secondaryAttrs.none
		? attr => `${attr.name}=true`
		: attr => attr.name;

	const nodeOptions = {
		open: `[NAME][PRIMARY_ATTRS]${SECONDARY_ATTRS}[SELF_CLOSE]`,
		selfClose: '/',
		attributes: {
			secondary(attrs) {
				return attrs.map(attr => attr.isBoolean
					? booleanAttr(attr)
					: `${attr.name}=${profile.quote(attr.value)}`
				).join(' ');
			}
		}
	};

	return render(tree, options.field, (outNode, renderFields) => {
		outNode = indentFormat(outNode, profile, nodeOptions);
		outNode = updateFormatting$1(outNode, profile);

		if (!handlePseudoSnippet(outNode)) {
			const node = outNode.node;

			// Do not generate fields for nodes with empty value and children
			// or if node is self-closed
			if (node.value || (!node.children.length && !node.selfClosing) ) {
				outNode.text = outNode.renderFields(formatNodeValue$1(node, profile));
			}
		}

        return outNode;
	});
}

/**
 * Updates formatting properties for given output node
 * NB Unlike HTML, Slim is indent-based format so some formatting options from
 * `profile` will not take effect, otherwise output will be broken
 * @param  {OutputNode} outNode Output wrapper of farsed abbreviation node
 * @param  {Profile}    profile Output profile
 * @return {OutputNode}
 */
function updateFormatting$1(outNode, profile) {
	const node = outNode.node;
	const parent = node.parent;

	// Edge case: a single inline-level child inside node without text:
	// allow it to be inlined
	if (profile.get('inlineBreak') === 0 && isInline$1(node, profile)
		&& !isRoot(parent) && parent.value == null && parent.children.length === 1) {
		outNode.beforeOpen = ': ';
	}

    if (!node.isTextOnly && node.value) {
        // node with text: put a space before single-line text
        outNode.beforeText = reNl$1.test(node.value)
			? outNode.newline + outNode.indent + profile.indent(1)
			: ' ';
    }

	return outNode;
}

/**
 * Formats value of given node: for multiline text we should precede each
 * line with `| ` with one-level deep indent
 * @param  {Node} node
 * @param  {Profile} profile
 * @return {String|null}
 */
function formatNodeValue$1(node, profile) {
	if (node.value != null && reNl$1.test(node.value)) {
		const indent = profile.indent(1);
		return splitByLines$1(node.value).map((line, i) => `${indent}${i ? ' ' : '|'} ${line}`).join('\n');
	}

	return node.value;
}

/**
 * Check if given node is inline-level
 * @param  {Node}  node
 * @param  {Profile}  profile
 * @return {Boolean}
 */
function isInline$1(node, profile) {
	return node && (node.isTextOnly || profile.isInline(node));
}

const reNl$2 = /\n|\r/;

/**
 * Renders given parsed Emmet abbreviation as Pug, formatted according to
 * `profile` options
 * @param  {Node}    tree      Parsed Emmet abbreviation
 * @param  {Profile} profile   Output profile
 * @param  {Object}  [options] Additional formatter options
 * @return {String}
 */
function pug(tree, profile, options) {
	options = options || {};
	const nodeOptions = {
		open: '[NAME][PRIMARY_ATTRS][(SECONDARY_ATTRS)]',
		attributes: {
			secondary(attrs) {
				return attrs.map(attr => attr.isBoolean ? attr.name : `${attr.name}=${profile.quote(attr.value)}`).join(', ');
			}
		}
	};

	return render(tree, options.field, outNode => {
		outNode = indentFormat(outNode, profile, nodeOptions);
		outNode = updateFormatting$2(outNode, profile);

		if (!handlePseudoSnippet(outNode)) {
			const node = outNode.node;
			// Do not generate fields for nodes with empty value and children
			// or if node is self-closed
			if (node.value || (!node.children.length && !node.selfClosing) ) {
				outNode.text = outNode.renderFields(formatNodeValue$2(node, profile));
			}
		}

        return outNode;
	});
}

/**
 * Updates formatting properties for given output node
 * NB Unlike HTML, Pug is indent-based format so some formatting options from
 * `profile` will not take effect, otherwise output will be broken
 * @param  {OutputNode} outNode Output wrapper of parsed abbreviation node
 * @param  {Profile}    profile Output profile
 * @return {OutputNode}
 */
function updateFormatting$2(outNode, profile) {
	const node = outNode.node;

    if (!node.isTextOnly && node.value) {
        // node with text: put a space before single-line text
        outNode.beforeText = reNl$2.test(node.value)
			? outNode.newline + outNode.indent + profile.indent(1)
			: ' ';
    }

	return outNode;
}

/**
 * Formats value of given node: for multiline text we should precede each
 * line with `| ` with one-level deep indent
 * @param  {Node} node
 * @param  {Profile} profile
 * @return {String|null}
 */
function formatNodeValue$2(node, profile) {
	if (node.value != null && reNl$2.test(node.value)) {
		const indent = profile.indent(1);
		return splitByLines$1(node.value).map(line => `${indent}| ${line}`).join('\n');
	}

	return node.value;
}

const supportedSyntaxed = { html, haml, slim, pug };

/**
 * Outputs given parsed abbreviation in specified syntax
 * @param {Node}     tree     Parsed abbreviation tree
 * @param {Profile}  profile  Output profile
 * @param {String}   [syntax] Output syntax. If not given, `html` syntax is used
 * @param {Function} options.field A function to output field/tabstop for
 * host editor. This function takes two arguments: `index` and `placeholder` and
 * should return a string that represents tabstop in host editor. By default
 * only a placeholder is returned
 * @example
 * {
 * 	field(index, placeholder) {
 * 		// return field in TextMate-style, e.g. ${1} or ${2:foo}
 * 		return `\${${index}${placeholder ? ':' + placeholder : ''}}`;
 *  }
 * }
 * @return {String}
 */
var index$3 = function(tree, profile, syntax, options) {
	if (typeof syntax === 'object') {
		options = syntax;
		syntax = null;
	}

	if (!supports(syntax)) {
		// fallback to HTML if given syntax is not supported
		syntax = 'html';
	}

	return supportedSyntaxed[syntax](tree, profile, options);
};

/**
 * Check if given syntax is supported
 * @param {String} syntax
 * @return {Boolean}
 */
function supports(syntax) {
	return !!syntax && syntax in supportedSyntaxed;
}

/**
 * Expands given abbreviation into code
 * @param  {String|Node} abbr    Abbreviation to parse or already parsed abbreviation
 * @param  {Object} options
 * @return {String}
 */
function expand(abbr, options) {
	options = options || {};

	if (typeof abbr === 'string') {
		abbr = parse$3(abbr, options);
	}

	return index$3(abbr, options.profile, options.syntax, options.format);
}

/**
 * Parses given Emmet abbreviation into a final abbreviation tree with all
 * required transformations applied
 * @param {String} Abbreviation to parse
 * @param  {Object} options
 * @return {Node}
 */
function parse$3(abbr, options) {
	return index(abbr)
	.use(index$1, options.snippets)
	.use(replaceVariables, options.variables)
	.use(index$2, options.text, options.addons);
}

/**
 * A wrapper for holding CSS value
 */
class CSSValue {
	constructor() {
		this.type = 'css-value';
		this.value = [];
	}

	get size() {
		return this.value.length;
	}

	add(value) {
		this.value.push(value);
	}

	has(value) {
		return this.value.indexOf(value) !== -1;
	}

	toString() {
		return this.value.join(' ');
	}
}

const HASH$1 = 35; // #
const DOT$2  = 46; // .

/**
 * Consumes a color token from given string
 * @param  {StreamReader} stream
 * @return {Color} Returns consumend color object, `undefined` otherwise
 */
var consumeColor = function(stream) {
	// supported color variations:
	// #abc   → #aabbccc
	// #0     → #000000
	// #fff.5 → rgba(255, 255, 255, 0.5)
	// #t     → transparent
	if (stream.peek() === HASH$1) {
		stream.start = stream.pos;
		stream.next();

		stream.eat(116) /* t */ || stream.eatWhile(isHex);
		const base = stream.current();

		// a hex color can be followed by `.num` alpha value
		stream.start = stream.pos;
		if (stream.eat(DOT$2) && !stream.eatWhile(isNumber)) {
			throw stream.error('Unexpected character for alpha value of color');
		}

		return new Color(base, stream.current());
	}
};

class Color {
	constructor(value, alpha) {
		this.type = 'color';
		this.raw = value;
		this.alpha = Number(alpha != null && alpha !== '' ? alpha : 1);
		value = value.slice(1); // remove #

		let r = 0, g = 0, b = 0;

		if (value === 't') {
			this.alpha = 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);
			}
		}

		this.r = parseInt(r, 16);
		this.g = parseInt(g, 16);
		this.b = parseInt(b, 16);
	}

	/**
	 * Output current color as hex value
	 * @param {Boolean} shor Produce short value (e.g. #fff instead of #ffffff), if possible
	 * @return {String}
	 */
	toHex(short) {
		const fn = (short && isShortHex(this.r) && isShortHex(this.g) && isShortHex(this.b))
			? toShortHex : toHex;

		return '#' + fn(this.r)  + fn(this.g) + fn(this.b);
	}

	/**
	 * Output current color as `rgba?(...)` CSS color
	 * @return {String}
	 */
	toRGB() {
		const values = [this.r, this.g, this.b];
		if (this.alpha !== 1) {
			values.push(this.alpha.toFixed(8).replace(/\.?0+$/, ''));
		}

		return `${values.length === 3 ? 'rgb' : 'rgba'}(${values.join(', ')})`;
	}

	toString(short) {
		if (!this.r && !this.g && !this.b && !this.alpha) {
			return 'transparent';
		}
		return this.alpha === 1 ? this.toHex(short) : this.toRGB();
	}
}

/**
 * Check if given code is a hex value (/0-9a-f/)
 * @param  {Number}  code
 * @return {Boolean}
 */
function isHex(code) {
	return isNumber(code) || isAlpha(code, 65, 70); // A-F
}

function isShortHex(hex) {
	return !(hex % 17);
}

function toShortHex(num) {
	return (num >> 4).toString(16);
}

function toHex(num) {
	return pad$1(num.toString(16), 2);
}

function pad$1(value, len) {
	while (value.length < len) {
		value = '0' + value;
	}
	return value;
}

/**
 * @param  {Number}  code
 * @return {Boolean}
 */
function isAlphaNumericWord(code) {
	return isNumber(code) || isAlphaWord(code);
}

/**
 * @param  {Number}  code
 * @return {Boolean}
 */
function isAlphaWord(code) {
	return code === 95 /* _ */ || isAlpha(code);
}

const PERCENT = 37; // %
const DOT$1$1     = 46; // .
const DASH$1    = 45; // -

/**
 * Consumes numeric CSS value (number with optional unit) from current stream,
 * if possible
 * @param  {StreamReader} stream
 * @return {NumericValue}
 */
var consumeNumericValue = function(stream) {
	stream.start = stream.pos;
	if (eatNumber(stream)) {
		const num = stream.current();
		stream.start = stream.pos;

		// eat unit, which can be a % or alpha word
		stream.eat(PERCENT) || stream.eatWhile(isAlphaWord);
		return new NumericValue(num, stream.current());
	}
};

/**
 * A numeric CSS value with optional unit
 */
class NumericValue {
	constructor(value, unit) {
		this.type = 'numeric';
		this.value = Number(value);
		this.unit = unit || '';
	}

	toString() {
		return `${this.value}${this.unit}`;
	}
}

/**
 * Eats number value from given stream
 * @param  {StreamReader} stream
 * @return {Boolean} Returns `true` if number was consumed
 */
function eatNumber(stream) {
	const start = stream.pos;
	const negative = stream.eat(DASH$1);
	let hadDot = false, consumed = false, code;

	while (!stream.eof()) {
		code = stream.peek();

		// either a second dot or not a number: stop parsing
		if (code === DOT$1$1 ? hadDot : !isNumber(code)) {
			break;
		}

		consumed = true;

		if (code === DOT$1$1) {
			hadDot = true;
		}

		stream.next();
	}

	if (negative && !consumed) {
		// edge case: consumed dash only, bail out
		stream.pos = start;
	}

	return start !== stream.pos;
}

const DOLLAR$1 = 36; // $
const DASH$2   = 45; // -
const AT$1     = 64; // @

/**
 * Consumes a keyword: either a variable (a word that starts with $ or @) or CSS
 * keyword or shorthand
 * @param  {StreamReader} stream
 * @param  {Boolean} [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
 * @return {String} Consumed variable
 */
var consumeKeyword = function(stream, short) {
	stream.start = stream.pos;

	if (stream.eat(DOLLAR$1) || stream.eat(AT$1)) {
		// SCSS or LESS variable
		stream.eatWhile(isVariableName);
	} else if (short) {
		stream.eatWhile(isAlphaWord);
	} else {
		stream.eatWhile(isKeyword);
	}

	return stream.start !== stream.pos ? new Keyword(stream.current()) : null;
};

class Keyword {
	constructor(value) {
		this.type = 'keyword';
		this.value = value;
	}

	toString() {
		return this.value;
	}
}

function isKeyword(code) {
	return isAlphaNumericWord(code) || code === DASH$2;
}

function isVariableName(code) {
	return code === 45 /* - */ || isAlphaNumericWord(code);
}

const opt$1 = { throws: true };

/**
 * Consumes 'single' or "double"-quoted string from given string, if possible
 * @param  {StreamReader} stream
 * @return {String}
 */
var consumeQuoted$1 = function(stream) {
	if (eatQuoted(stream, opt$1)) {
		return new QuotedString(stream.current());
	}
};

class QuotedString {
	constructor(value) {
		this.type = 'string';
		this.value = value;
	}

	toString() {
		return this.value;
	}
}

const LBRACE = 40; // (
const RBRACE = 41; // )
const COMMA  = 44; // ,

/**
 * Consumes arguments from given string.
 * Arguments are comma-separated list of CSS values inside round braces, e.g.
 * `(1, a2, 'a3')`. Nested lists and quoted strings are supported
 * @param  {StreamReader} stream
 * @return {Array}        Array of arguments, `null` if arguments cannot be consumed
 */
function consumeArgumentList(stream) {
	if (!stream.eat(LBRACE)) {
		// not an argument list
		return null;
	}

	let level = 1, code, arg;
	const argsList = [];

	while (!stream.eof()) {
		if (arg = consumeArgument(stream)) {
			argsList.push(arg);
		} else {
			// didn’t consumed argument, expect argument separator or end-of-arguments
			stream.eatWhile(isWhiteSpace);

			if (stream.eat(RBRACE)) {
				// end of arguments list
				break;
			}

			if (!stream.eat(COMMA)) {
				throw stream.error('Expected , or )');
			}
		}
	}

	return argsList;
}

/**
 * Consumes a single argument. An argument is a `CSSValue`, e.g. it could be
 * a space-separated string of value
 * @param  {StreamReader} stream
 * @return {CSSValue}
 */
function consumeArgument(stream) {
	const result = new CSSValue();
	let value;

	while (!stream.eof()) {
		stream.eatWhile(isWhiteSpace);
		value = consumeNumericValue(stream) || consumeColor(stream)
			|| consumeQuoted$1(stream) || consumeKeywordOrFunction(stream);

		if (!value) {
			break;
		}

		result.add(value);
	}

	return result.size ? result : null;
}

/**
 * Consumes either function call like `foo()` or keyword like `foo`
 * @param  {StreamReader} stream
 * @return {Keyword|FunctionCall}
 */
function consumeKeywordOrFunction(stream) {
	const kw = consumeKeyword(stream);
	if (kw) {
		const args = consumeArgumentList(stream);
		return args ? new FunctionCall(kw.toString(), args) : kw;
	}
}

class FunctionCall {
	/**
	 * @param {String} name Function name
	 * @param {Array}  args Function arguments
	 */
	constructor(name, args) {
		this.type = 'function';
		this.name = name;
		this.args = args || [];
	}

	toString() {
		return `${this.name}(${this.args.join(', ')})`;
	}
}

const EXCL$1   = 33; // !
const DOLLAR$2 = 36; // $
const PLUS   = 43; // +
const DASH   = 45; // -
const COLON$1  = 58; // :
const AT     = 64; // @

/**
 * Parses given Emmet CSS abbreviation and returns it as parsed Node tree
 * @param {String} abbr
 * @return {Node}
 */
var index$4 = function(abbr) {
	const root = new Node();
	const stream = new StreamReader(abbr);
	while (!stream.eof()) {
		let node = new Node(consumeIdent(stream));
		node.value = consumeValue(stream);

		const args = consumeArgumentList(stream);
		if (args) {
			// technically, arguments in CSS are anonymous Emmet Node attributes,
			// but since Emmet can support only one anonymous, `null`-name
			// attribute (for good reasons), we’ll use argument index as name
			for (let i = 0; i < args.length; i++) {
				node.setAttribute(String(i), args[i]);
			}
		}

		// Consume `!important` modifier at the end of expression
		if (stream.eat(EXCL$1)) {
			node.value.add('!');
		}

		root.appendChild(node);

		// CSS abbreviations cannot be nested, only listed
		if (!stream.eat(PLUS)) {
			break;
		}
	}

	if (!stream.eof()) {
		throw stream.error('Unexpected character');
	}

	return root;
};

/**
 * Consumes CSS property identifier from given stream
 * @param  {StreamReader} stream
 * @return {String}
 */
function consumeIdent(stream) {
	stream.start = stream.pos;
	stream.eatWhile(isIdentPrefix);
	stream.eatWhile(isIdent);
	return stream.start !== stream.pos ? stream.current() : null;
}

/**
 * Consumes embedded value from Emmet CSS abbreviation stream
 * @param  {StreamReader} stream
 * @return {CSSValue}
 */
function consumeValue(stream) {
	const values = new CSSValue();
	let value;

	while (!stream.eof()) {
		// use colon as value separator
		stream.eat(COLON$1);
		if (value = consumeNumericValue(stream) || consumeColor(stream)) {
			// edge case: a dash after unit-less numeric value or color should
			// be treated as value separator, not negative sign
			if (!value.unit) {
				stream.eat(DASH);
			}
		} else {
			stream.eat(DASH);
			value = consumeKeyword(stream, true);
		}

		if (!value) {
			break;
		}

		values.add(value);
	}

	return values;
}

/**
 * @param  {Number}  code
 * @return {Boolean}
 */
function isIdent(code) {
	return isAlphaWord(code);
}

/**
 * @param  {Number}  code
 * @return {Boolean}
 */
function isIdentPrefix(code) {
	return code === AT || code === DOLLAR$2 || code === EXCL$1;
}

const DASH$3 = 45; // -

/**
 * Calculates fuzzy match score of how close `abbr` matches given `string`.
 * @param  {String} abbr        Abbreviation to score
 * @param  {String} string      String to match
 * @param  {Number} [fuzziness] Fuzzy factor
 * @return {Number}             Match score
 */
var stringScore = function(abbr, string) {
    abbr = abbr.toLowerCase();
    string = string.toLowerCase();
    
    if (abbr === string) {
        return 1;
    }

    // a string MUST start with the same character as abbreviation
    if (!string || abbr.charCodeAt(0) !== string.charCodeAt(0)) {
        return 0;
    }

    const abbrLength = abbr.length;
    const stringLength = string.length;
    let i = 1, j = 1, score = stringLength;
    let ch1, ch2, found, acronym;

    while (i < abbrLength) {
        ch1 = abbr.charCodeAt(i);
        found = false;
        acronym = false;

        while (j < stringLength) {
            ch2 = string.charCodeAt(j);

            if (ch1 === ch2) {
                found = true;
                score += (stringLength - j) * (acronym ? 2 : 1);
                break;
            }

            // add acronym bonus for exactly next match after unmatched `-`
            acronym = ch2 === DASH$3;
            j++;
        }

        if (!found) {
            break;
        }

        i++;
    }

    return score && score * (i / abbrLength) / sum(stringLength);
};

/**
 * Calculates sum of first `n` natural numbers, e.g. 1+2+3+...n
 * @param  {Number} n
 * @return {Number}
 */
function sum(n) {
    return n * (n + 1) / 2;
}

const reProperty = /^([a-z\-]+)(?:\s*:\s*([^\n\r]+))?$/;
const DASH$1$1 = 45; // -

/**
 * Creates a special structure for resolving CSS properties from plain CSS
 * snippets.
 * Almost all CSS snippets are aliases for real CSS properties with available
 * value variants, optionally separated by `|`. Most values are keywords that
 * can be fuzzy-resolved as well. Some CSS properties are shorthands for other,
 * more specific properties, like `border` and `border-style`. For such cases
 * keywords from more specific properties should be available in shorthands too.
 * @param {Snippet[]} snippets
 * @return {CSSSnippet[]}
 */
var cssSnippets = function(snippets) {
    return nest( snippets.map(snippet => new CSSSnippet(snippet.key, snippet.value)) );
};

class CSSSnippet {
    constructor(key, value) {
        this.key = key;
        this.value = value;
        this.property = null;

        // detect if given snippet is a property
        const m = value && value.match(reProperty);
        if (m) {
            this.property = m[1];
            this.value = m[2];
        }

        this.dependencies = [];
    }

    addDependency(dep) {
        this.dependencies.push(dep);
    }

    get defaulValue() {
        return this.value != null ? splitValue(this.value)[0] : null;
    }

    /**
     * Returns list of unique keywords for current CSS snippet and its dependencies
     * @return {String[]}
     */
    keywords() {
        const stack = [];
        const keywords = new Set();
        let i = 0, item, candidates;

        if (this.property) {
            // scan valid CSS-properties only
            stack.push(this);
        }

        while (i < stack.length) {
            // NB Keep items in stack instead of push/pop to avoid possible
            // circular references
            item = stack[i++];

            if (item.value) {
                candidates = splitValue(item.value).filter(isKeyword$1);

                // extract possible keywords from snippet value
                for (let j = 0; j < candidates.length; j++) {
                    keywords.add(candidates[j].trim());
                }

                // add dependencies into scan stack
                for (let j = 0, deps = item.dependencies; j < deps.length; j++) {
                    if (stack.indexOf(deps[j]) === -1) {
                        stack.push(deps[j]);
                    }
                }
            }
        }

        return Array.from(keywords);
    }
}

/**
 * Nests more specific CSS properties into shorthand ones, e.g.
 * background-position-x -> background-position -> background
 * @param  {CSSSnippet[]} snippets
 * @return {CSSSnippet[]}
 */
function nest(snippets) {
    snippets = snippets.sort(snippetsSort);
    const stack = [];

    // For sorted list of CSS properties, create dependency graph where each
    // shorthand property contains its more specific one, e.g.
    // backgound -> background-position -> background-position-x
    for (let i = 0, cur, prev; i < snippets.length; i++) {
        cur = snippets[i];

        if (!cur.property) {
            // not a CSS property, skip it
            continue;
        }

        // Check if current property belongs to one from parent stack.
        // Since `snippets` array is sorted, items are perfectly aligned
        // from shorthands to more specific variants
        while (stack.length) {
            prev = stack[stack.length - 1];

            if (cur.property.indexOf(prev.property) === 0
                && cur.property.charCodeAt(prev.property.length) === DASH$1$1) {
                prev.addDependency(cur);
                stack.push(cur);
                break;
            }

            stack.pop();
        }

        if (!stack.length) {
            stack.push(cur);
        }
    }

    return snippets;
}

/**
 * A sorting function for array of snippets
 * @param  {CSSSnippet} a
 * @param  {CSSSnippet} b
 * @return {Number}
 */
function snippetsSort(a, b) {
    if (a.key === b.key) {
        return 0;
    }

    return a.key < b.key ? -1 : 1;
}

/**
 * Check if given string is a keyword candidate
 * @param  {String}  str
 * @return {Boolean}
 */
function isKeyword$1(str) {
    return /^\s*[\w\-]+/.test(str);
}

function splitValue(value) {
    return String(value).split('|');
}

const globalKeywords = ['auto', 'inherit', 'unset'];
const unitlessProperties = [
    'z-index', 'line-height', 'opacity', 'font-weight', 'zoom',
    'flex', 'flex-grow', 'flex-shrink'
];

const defaultOptions$3 = {
	intUnit: 'px',
	floatUnit: 'em',
	unitAliases: {
		e :'em',
		p: '%',
		x: 'ex',
		r: 'rem'
	},
	fuzzySearchMinScore: 0
};

/**
 * For every node in given `tree`, finds matching snippet from `registry` and
 * updates node with snippet data.
 *
 * This resolver uses fuzzy matching for searching matched snippets and their
 * keyword values.
 */

var index$5 = function(tree, registry, options) {
	const snippets = convertToCSSSnippets(registry);
	options = {
		intUnit: (options && options.intUnit) || defaultOptions$3.intUnit,
		floatUnit: (options && options.floatUnit) || defaultOptions$3.floatUnit,
		unitAliases: Object.assign({}, defaultOptions$3.unitAliases, options && options.unitAliases),
		fuzzySearchMinScore: (options && options.fuzzySearchMinScore) || defaultOptions$3.fuzzySearchMinScore
	};
	tree.walk(node => resolveNode$1(node, snippets, options));
	return tree;
};

function convertToCSSSnippets(registry) {
    return cssSnippets(registry.all({type: 'string'}))
}

/**
 * Resolves given node: finds matched CSS snippets using fuzzy match and resolves
 * keyword aliases from node value
 * @param  {Node} node
 * @param  {CSSSnippet[]} snippets
 * @param  {Object} options
 * @return {Node}
 */
function resolveNode$1(node, snippets, options) {
	const snippet = findBestMatch(node.name, snippets, 'key', options.fuzzySearchMinScore);

	if (!snippet) {
		// Edge case: `!important` snippet
		return node.name === '!' ? setNodeAsText(node, '!important') : node;
	}

	return snippet.property
		? resolveAsProperty(node, snippet, options)
		: resolveAsSnippet(node, snippet);
}

/**
 * Resolves given parsed abbreviation node as CSS propery
 * @param {Node} node
 * @param {CSSSnippet} snippet
 * @param  {Object} formatOptions
 * @return {Node}
 */
function resolveAsProperty(node, snippet, formatOptions) {
    const abbr = node.name;
	node.name = snippet.property;

	if (node.value && typeof node.value === 'object') {
		// resolve keyword shortcuts
		const keywords = snippet.keywords();

		if (!node.value.size) {
			// no value defined, try to resolve unmatched part as a keyword alias
			let kw = findBestMatch(getUnmatchedPart(abbr, snippet.key), keywords);

            if (!kw) {
                // no matching value, try to get default one
                kw = snippet.defaulValue;
                if (kw && kw.indexOf('${') === -1) {
                    // Quick and dirty test for existing field. If not, wrap
                    // default value in a field
                    kw = `\${1:${kw}}`;
                }
            }

			if (kw) {
				node.value.add(kw);
			}
		} else {
			// replace keyword aliases in current node value
			for (let i = 0, token; i < node.value.value.length; i++) {
				token = node.value.value[i];

				if (token === '!') {
					token = `${!i ? '${1} ' : ''}!important`;
				} else if (isKeyword$2(token)) {
					token = findBestMatch(token.value, keywords)
						|| findBestMatch(token.value, globalKeywords)
						|| token;
				} else if (isNumericValue(token)) {
                    token = resolveNumericValue(node.name, token, formatOptions);
                }

                node.value.value[i] = token;
			}
		}
	}

	return node;
}

/**
 * Resolves given parsed abbreviation node as a snippet: a plain code chunk
 * @param {Node} node
 * @param {CSSSnippet} snippet
 * @return {Node}
 */
function resolveAsSnippet(node, snippet) {
	return setNodeAsText(node, snippet.value);
}

/**
 * Sets given parsed abbreviation node as a text snippet
 * @param {Node} node
 * @param {String} text
 * @return {Node}
 */
function setNodeAsText(node, text) {
	node.name = null;
	node.value = text;
	return node;
}

/**
 * Finds best matching item from `items` array
 * @param {String} abbr  Abbreviation to match
 * @param {Array}  items List of items for match
 * @param {String} [key] If `items` is a list of objects, use `key` as object
 * property to test against
 * @param {Number} fuzzySearchMinScore The minimum score the best matched item should have to be a valid match.
 * @return {*}
 */
function findBestMatch(abbr, items, key, fuzzySearchMinScore) {
	if (!abbr) {
		return null;
	}

	let matchedItem = null;
	let maxScore = 0;
	fuzzySearchMinScore = fuzzySearchMinScore || 0;

	for (let i = 0, item; i < items.length; i++) {
		item = items[i];
		const score = stringScore(abbr, getScoringPart(item, key));

		if (score === 1) {
			// direct hit, no need to look further
			return item;
		}

		if (score && score >= maxScore) {
			maxScore = score;
			matchedItem = item;
		}
	}

	return maxScore >= fuzzySearchMinScore ? matchedItem : null;
}

function getScoringPart(item, key) {
    const value = item && typeof item === 'object' ? item[key] : item;
    const m = (value || '').match(/^[\w-@]+/);
    return m ? m[0] : value;
}

/**
 * Returns a part of `abbr` that wasn’t directly matched agains `string`.
 * For example, if abbreviation `poas` is matched against `position`, the unmatched part will be `as`
 * since `a` wasn’t found in string stream
 * @param {String} abbr
 * @param {String} string
 * @return {String}
 */
function getUnmatchedPart(abbr, string) {
	for (let i = 0, lastPos = 0; i < abbr.length; i++) {
		lastPos = string.indexOf(abbr[i], lastPos);
		if (lastPos === -1) {
			return abbr.slice(i);
		}
        lastPos++;
	}

	return '';
}

/**
 * Check if given CSS value token is a keyword
 * @param {*} token
 * @return {Boolean}
 */
function isKeyword$2(token) {
	return tokenTypeOf(token, 'keyword');
}

/**
 * Check if given CSS value token is a numeric value
 * @param  {*}  token
 * @return {Boolean}
 */
function isNumericValue(token) {
    return tokenTypeOf(token, 'numeric');
}

function tokenTypeOf(token, type) {
	return token && typeof token === 'object' && token.type === type;
}

/**
 * Resolves numeric value for given CSS property
 * @param  {String} property    CSS property name
 * @param  {NumericValue} token CSS numeric value token
 * @param  {Object} formatOptions Formatting options for units
 * @return {NumericValue}
 */
function resolveNumericValue(property, token, formatOptions) {
    if (token.unit) {
        token.unit = formatOptions.unitAliases[token.unit] || token.unit;
    } else if (token.value !== 0 && unitlessProperties.indexOf(property) === -1) {
        // use `px` for integers, `em` for floats
        // NB: num|0 is a quick alternative to Math.round(0)
        token.unit = token.value === (token.value|0) ? formatOptions.intUnit : formatOptions.floatUnit;
    }

    return token;
}

const defaultOptions$4 = {
	shortHex: true,
	format: {
		between: ': ',
		after: ';'
	}
};

/**
 * Renders given parsed Emmet CSS abbreviation as CSS-like
 * stylesheet, formatted according to `profile` options
 * @param  {Node}     tree    Parsed Emmet abbreviation
 * @param  {Profile}  profile Output profile
 * @param  {Object}  [options] Additional formatter options
 * @return {String}
 */
function css(tree, profile, options) {
	options = Object.assign({}, defaultOptions$4, options);

	return render(tree, options.field, outNode => {
		const node = outNode.node;
		let value = String(node.value || '');

		if (node.attributes.length) {
			const fieldValues = node.attributes.map(attr => stringifyAttribute(attr, options));
			value = injectFields(value, fieldValues);
		}

		outNode.open = node.name && profile.name(node.name);
		outNode.afterOpen = options.format.between;
		outNode.text = outNode.renderFields(value || null);

		if (outNode.open && (!outNode.text || !outNode.text.endsWith(';'))) {
			outNode.afterText = options.format.after;
		}

		if (profile.get('format')) {
			outNode.newline = '\n';
			if (tree.lastChild !== node) {
				outNode.afterText += outNode.newline;
			}
		}

		return outNode;
	});
}

/**
 * Injects given field values at each field of given string
 * @param  {String}   string
 * @param  {String[]} attributes
 * @return {FieldString}
 */
function injectFields(string, values) {
	const fieldsModel = parse$2(string);
	const fieldsAmount = fieldsModel.fields.length;

	if (fieldsAmount) {
		values = values.slice();
		if (values.length > fieldsAmount) {
			// More values that output fields: collapse rest values into
			// a single token
			values = values.slice(0, fieldsAmount - 1)
				.concat(values.slice(fieldsAmount - 1).join(', '));
		}

		while (values.length) {
			const value = values.shift();
			const field = fieldsModel.fields.shift();
			const delta = value.length - field.length;

			fieldsModel.string = fieldsModel.string.slice(0, field.location)
				+ value
				+ fieldsModel.string.slice(field.location + field.length);

			// Update location of the rest fields in string
			for (let i = 0, il = fieldsModel.fields.length; i < il; i++) {
				fieldsModel.fields[i].location += delta;
			}
		}
	}

	return fieldsModel;
}

function stringifyAttribute(attr, options) {
	if (attr.value && typeof attr.value === 'object' && attr.value.type === 'css-value') {
		return attr.value.value
		.map(token => {
			if (token && typeof token === 'object') {
				return token.type === 'color'
					? token.toString(options.shortHex)
					: token.toString();
			}

			return String(token);
		})
		.join(' ');
	}

	return attr.value != null ? String(attr.value) : '';
}

const syntaxFormat = {
	css: {
		between: ': ',
		after: ';'
	},
	scss: 'css',
	less: 'css',
	sass: {
		between: ': ',
		after: ''
	},
	stylus: {
		between: ' ',
		after: ''
	}
};

/**
 * Outputs given parsed abbreviation in specified stylesheet syntax
 * @param {Node}     tree     Parsed abbreviation tree
 * @param {Profile}  profile  Output profile
 * @param {String}   [syntax] Output syntax. If not given, `css` syntax is used
 * @param {Function} options.field A function to output field/tabstop for
 * host editor. This function takes two arguments: `index` and `placeholder` and
 * should return a string that represents tabstop in host editor. By default
 * only a placeholder is returned
 * @example
 * {
 * 	field(index, placeholder) {
 * 		// return field in TextMate-style, e.g. ${1} or ${2:foo}
 * 		return `\${${index}${placeholder ? ':' + placeholder : ''}}`;
 *  }
 * }
 * @return {String}
 */
var index$6 = function(tree, profile, syntax, options) {
	if (typeof syntax === 'object') {
		options = syntax;
		syntax = null;
	}

	if (!supports$1(syntax)) {
		// fallback to CSS if given syntax is not supported
		syntax = 'css';
	}

	options = Object.assign({}, options, {
		format: getFormat(syntax, options)
	});

	// CSS abbreviations doesn’t support nesting so simply
	// output root node children
	return css(tree, profile, options);
};

/**
 * Check if given syntax is supported
 * @param {String} syntax
 * @return {Boolean}
 */
function supports$1(syntax) {
	return !!syntax && syntax in syntaxFormat;
}

/**
 * Returns formatter object for given syntax
 * @param  {String} syntax
 * @param  {Object} [options]
 * @return {Object} Formatter object as defined in `syntaxFormat`
 */
function getFormat(syntax, options) {
	let format = syntaxFormat[syntax];
	if (typeof format === 'string') {
		format = syntaxFormat[format];
	}

	return Object.assign({}, format, options && options.stylesheet);
}

/**
 * Expands given abbreviation into code
 * @param  {String|Node} abbr    Abbreviation to parse or already parsed abbreviation
 * @param  {Object} options
 * @return {String}
 */
function expand$1(abbr, options) {
	options = options || {};

	if (typeof abbr === 'string') {
		abbr = parse$4(abbr, options);
	}

	return index$6(abbr, options.profile, options.syntax, options.format);
}

/**
 * Parses given Emmet abbreviation into a final abbreviation tree with all
 * required transformations applied
 * @param {String|Node} abbr Abbreviation to parse or already parsed abbreviation
 * @param  {Object} options
 * @return {Node}
 */
function parse$4(abbr, options) {
	if (typeof abbr === 'string') {
		abbr = index$4(abbr);
	}

	return abbr.use(index$5, options.snippets, options.format ? options.format.stylesheet : {});
}

var html$1 = {
	"a": "a[href]",
	"a:blank": "a[href='http://${0}' target='_blank' rel='noopener noreferrer']",
	"a:link": "a[href='http://${0}']",
	"a:mail": "a[href='mailto:${0}']",
	"a:tel": "a[href='tel:+${0}']",
	"abbr": "abbr[title]",
	"acr|acronym": "acronym[title]",
	"base": "base[href]/",
	"basefont": "basefont/",
	"br": "br/",
	"frame": "frame/",
	"hr": "hr/",
	"bdo": "bdo[dir]",
	"bdo:r": "bdo[dir=rtl]",
	"bdo:l": "bdo[dir=ltr]",
	"col": "col/",
	"link": "link[rel=stylesheet href]/",
	"link:css": "link[href='${1:style}.css']",
	"link:print": "link[href='${1:print}.css' media=print]",
	"link:favicon": "link[rel='shortcut icon' type=image/x-icon href='${1:favicon.ico}']",
	"link:mf|link:manifest": "link[rel='manifest' href='${1:manifest.json}']",
	"link:touch": "link[rel=apple-touch-icon href='${1:favicon.png}']",
	"link:rss": "link[rel=alternate type=application/rss+xml title=RSS href='${1:rss.xml}']",
	"link:atom": "link[rel=alternate type=application/atom+xml title=Atom href='${1:atom.xml}']",
	"link:im|link:import": "link[rel=import href='${1:component}.html']",
	"meta": "meta/",
	"meta:utf": "meta[http-equiv=Content-Type content='text/html;charset=UTF-8']",
	"meta:vp": "meta[name=viewport content='width=${1:device-width}, initial-scale=${2:1.0}']",
	"meta:compat": "meta[http-equiv=X-UA-Compatible content='${1:IE=7}']",
	"meta:edge": "meta:compat[content='${1:ie=edge}']",
	"meta:redirect": "meta[http-equiv=refresh content='0; url=${1:http://example.com}']",
	"style": "style",
	"script": "script[!src]",
	"script:src": "script[src]",
	"img": "img[src alt]/",
	"img:s|img:srcset": "img[srcset src alt]",
	"img:z|img:sizes": "img[sizes srcset src alt]",
	"picture": "picture",
	"src|source": "source/",
	"src:sc|source:src": "source[src type]",
	"src:s|source:srcset": "source[srcset]",
	"src:t|source:type": "source[srcset type='${1:image/}']",
	"src:z|source:sizes": "source[sizes srcset]",
	"src:m|source:media": "source[media='(${1:min-width: })' srcset]",
	"src:mt|source:media:type": "source:media[type='${2:image/}']",
	"src:mz|source:media:sizes": "source:media[sizes srcset]",
	"src:zt|source:sizes:type": "source[sizes srcset type='${1:image/}']",
	"iframe": "iframe[src frameborder=0]",
	"embed": "embed[src type]/",
	"object": "object[data type]",
	"param": "param[name value]/",
	"map": "map[name]",
	"area": "area[shape coords href alt]/",
	"area:d": "area[shape=default]",
	"area:c": "area[shape=circle]",
	"area:r": "area[shape=rect]",
	"area:p": "area[shape=poly]",
	"form": "form[action]",
	"form:get": "form[method=get]",
	"form:post": "form[method=post]",
	"label": "label[for]",
	"input": "input[type=${1:text}]/",
	"inp": "input[name=${1} id=${1}]",
	"input:h|input:hidden": "input[type=hidden name]",
	"input:t|input:text": "inp[type=text]",
	"input:search": "inp[type=search]",
	"input:email": "inp[type=email]",
	"input:url": "inp[type=url]",
	"input:p|input:password": "inp[type=password]",
	"input:datetime": "inp[type=datetime]",
	"input:date": "inp[type=date]",
	"input:datetime-local": "inp[type=datetime-local]",
	"input:month": "inp[type=month]",
	"input:week": "inp[type=week]",
	"input:time": "inp[type=time]",
	"input:tel": "inp[type=tel]",
	"input:number": "inp[type=number]",
	"input:color": "inp[type=color]",
	"input:c|input:checkbox": "inp[type=checkbox]",
	"input:r|input:radio": "inp[type=radio]",
	"input:range": "inp[type=range]",
	"input:f|input:file": "inp[type=file]",
	"input:s|input:submit": "input[type=submit value]",
	"input:i|input:image": "input[type=image src alt]",
	"input:b|input:button": "input[type=button value]",
    "input:reset": "input:button[type=reset]",
	"isindex": "isindex/",
	"select": "select[name=${1} id=${1}]",
	"select:d|select:disabled": "select[disabled.]",
	"opt|option": "option[value]",
	"textarea": "textarea[name=${1} id=${1} cols=${2:30} rows=${3:10}]",
	"marquee": "marquee[behavior direction]",
	"menu:c|menu:context": "menu[type=context]",
	"menu:t|menu:toolbar": "menu[type=toolbar]",
	"video": "video[src]",
	"audio": "audio[src]",
	"html:xml": "html[xmlns=http://www.w3.org/1999/xhtml]",
	"keygen": "keygen/",
	"command": "command/",
	"btn:s|button:s|button:submit" : "button[type=submit]",
	"btn:r|button:r|button:reset" : "button[type=reset]",
	"btn:d|button:d|button:disabled" : "button[disabled.]",
	"fst:d|fset:d|fieldset:d|fieldset:disabled" : "fieldset[disabled.]",

	"bq": "blockquote",
	"fig": "figure",
	"figc": "figcaption",
	"pic": "picture",
	"ifr": "iframe",
	"emb": "embed",
	"obj": "object",
	"cap": "caption",
	"colg": "colgroup",
	"fst": "fieldset",
	"btn": "button",
	"optg": "optgroup",
	"tarea": "textarea",
	"leg": "legend",
	"sect": "section",
	"art": "article",
	"hdr": "header",
	"ftr": "footer",
	"adr": "address",
	"dlg": "dialog",
	"str": "strong",
	"prog": "progress",
	"mn": "main",
	"tem": "template",
	"fset": "fieldset",
	"datag": "datagrid",
	"datal": "datalist",
	"kg": "keygen",
	"out": "output",
	"det": "details",
	"cmd": "command",

	"ri:d|ri:dpr": "img:s",
	"ri:v|ri:viewport": "img:z",
	"ri:a|ri:art": "pic>src:m+img",
	"ri:t|ri:type": "pic>src:t+img",

	"!!!": "{<!DOCTYPE html>}",
	"doc": "html[lang=${lang}]>(head>meta[charset=${charset}]+meta:vp+meta:edge+title{${1:Document}})+body",
	"!|html:5": "!!!+doc",

	"c": "{<!-- ${0} -->}",
	"cc:ie": "{<!--[if IE]>${0}<![endif]-->}",
	"cc:noie": "{<!--[if !IE]><!-->${0}<!--<![endif]-->}"
};

var css$1 = {
	"@f": "@font-face {\n\tfont-family: ${1};\n\tsrc: url(${1});\n}",
	"@ff": "@font-face {\n\tfont-family: '${1:FontName}';\n\tsrc: url('${2:FileName}.eot');\n\tsrc: url('${2:FileName}.eot?#iefix') format('embedded-opentype'),\n\t\t url('${2:FileName}.woff') format('woff'),\n\t\t url('${2:FileName}.ttf') format('truetype'),\n\t\t url('${2:FileName}.svg#${1:FontName}') format('svg');\n\tfont-style: ${3:normal};\n\tfont-weight: ${4:normal};\n}",
	"@i|@import": "@import url(${0});",
	"@kf": "@keyframes ${1:identifier} {\n\t${2}\n}",
	"@m|@media": "@media ${1:screen} {\n\t${0}\n}",
	"ac": "align-content:flex-start|flex-end|center|space-between|space-around|stretch",
	"ai": "align-items:flex-start|flex-end|center|baseline|stretch",
	"anim": "animation:${1:name} ${2:duration} ${3:timing-function} ${4:delay} ${5:iteration-count} ${6:direction} ${7:fill-mode}",
	"animdel": "animation-delay:${1:time}",
	"animdir": "animation-direction:normal|reverse|alternate|alternate-reverse",
	"animdur": "animation-duration:${1:0}s",
	"animfm": "animation-fill-mode:both|forwards|backwards",
	"animic": "animation-iteration-count:1|infinite",
	"animn": "animation-name",
	"animps": "animation-play-state:running|paused",
	"animtf": "animation-timing-function:linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(${1:0.1}, ${2:0.7}, ${3:1.0}, ${3:0.1})",
	"ap": "appearance:none",
	"as": "align-self:auto|flex-start|flex-end|center|baseline|stretch",
	"b": "bottom",
	"bd": "border:${1:1px} ${2:solid} ${3:#000}",
	"bdb": "border-bottom:${1:1px} ${2:solid} ${3:#000}",
	"bdbc": "border-bottom-color:#${1:000}",
	"bdbi": "border-bottom-image:url(${0})",
	"bdbk": "border-break:close",
	"bdbli": "border-bottom-left-image:url(${0})|continue",
	"bdblrs": "border-bottom-left-radius",
	"bdbri": "border-bottom-right-image:url(${0})|continue",
	"bdbrrs": "border-bottom-right-radius",
	"bdbs": "border-bottom-style",
	"bdbw": "border-bottom-width",
	"bdc": "border-color:#${1:000}",
	"bdci": "border-corner-image:url(${0})|continue",
	"bdcl": "border-collapse:collapse|separate",
	"bdf": "border-fit:repeat|clip|scale|stretch|overwrite|overflow|space",
	"bdi": "border-image:url(${0})",
	"bdl": "border-left:${1:1px} ${2:solid} ${3:#000}",
	"bdlc": "border-left-color:#${1:000}",
	"bdlen": "border-length",
	"bdli": "border-left-image:url(${0})",
	"bdls": "border-left-style",
	"bdlw": "border-left-width",
	"bdr": "border-right:${1:1px} ${2:solid} ${3:#000}",
	"bdrc": "border-right-color:#${1:000}",
	"bdri": "border-right-image:url(${0})",
	"bdrs": "border-radius",
	"bdrst": "border-right-style",
	"bdrw": "border-right-width",
	"bds": "border-style:none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset",
	"bdsp": "border-spacing",
	"bdt": "border-top:${1:1px} ${2:solid} ${3:#000}",
	"bdtc": "border-top-color:#${1:000}",
	"bdti": "border-top-image:url(${0})",
	"bdtli": "border-top-left-image:url(${0})|continue",
	"bdtlrs": "border-top-left-radius",
	"bdtri": "border-top-right-image:url(${0})|continue",
	"bdtrrs": "border-top-right-radius",
	"bdts": "border-top-style",
	"bdtw": "border-top-width",
	"bdw": "border-width",
	"bfv": "backface-visibility:hidden|visible",
	"bg": "background:#${1:000}",
	"bga": "background-attachment:fixed|scroll",
	"bgbk": "background-break:bounding-box|each-box|continuous",
	"bgc": "background-color:#${1:fff}",
	"bgcp": "background-clip:padding-box|border-box|content-box|no-clip",
	"bgi": "background-image:url(${0})",
	"bgo": "background-origin:padding-box|border-box|content-box",
	"bgp": "background-position:${1:0} ${2:0}",
	"bgpx": "background-position-x",
	"bgpy": "background-position-y",
	"bgr": "background-repeat:no-repeat|repeat-x|repeat-y|space|round",
	"bgsz": "background-size:contain|cover",
	"bxsh": "box-shadow:${1:inset }${2:hoff} ${3:voff} ${4:blur} #${5:000}|none",
	"bxsz": "box-sizing:border-box|content-box|border-box",
	"c": "color:#${1:000}",
	"cl": "clear:both|left|right|none",
	"cm": "/* ${0} */",
	"cnt": "content:'${0}'|normal|open-quote|no-open-quote|close-quote|no-close-quote|attr(${0})|counter(${0})|counters({$0})",
	"coi": "counter-increment",
	"colm": "columns",
	"colmc": "column-count",
	"colmf": "column-fill",
	"colmg": "column-gap",
	"colmr": "column-rule",
	"colmrc": "column-rule-color",
	"colmrs": "column-rule-style",
	"colmrw": "column-rule-width",
	"colms": "column-span",
	"colmw": "column-width",
	"cor": "counter-reset",
	"cp": "clip:auto|rect(${1:top} ${2:right} ${3:bottom} ${4:left})",
	"cps": "caption-side:top|bottom",
	"cur": "cursor:pointer|auto|default|crosshair|hand|help|move|pointer|text",
	"d": "display:block|none|flex|inline-flex|inline|inline-block|list-item|run-in|compact|table|inline-table|table-caption|table-column|table-column-group|table-header-group|table-footer-group|table-row|table-row-group|table-cell|ruby|ruby-base|ruby-base-group|ruby-text|ruby-text-group",
	"ec": "empty-cells:show|hide",
	"f": "font:${1:1em} ${2:sans-serif}",
	"fd": "font-display:auto|block|swap|fallback|optional",
	"fef": "font-effect:none|engrave|emboss|outline",
	"fem": "font-emphasize",
	"femp": "font-emphasize-position:before|after",
	"fems": "font-emphasize-style:none|accent|dot|circle|disc",
	"ff": "font-family:serif|sans-serif|cursive|fantasy|monospace",
	"fft": "font-family:\"Times New Roman\", Times, Baskerville, Georgia, serif",
	"ffa": "font-family:Arial, \"Helvetica Neue\", Helvetica, sans-serif",
	"ffv": "font-family:Verdana, Geneva, sans-serif",
	"fl": "float:left|right|none",
	"fs": "font-style:italic|normal|oblique",
	"fsm": "font-smoothing:antialiased|subpixel-antialiased|none",
	"fst": "font-stretch:normal|ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded",
	"fv": "font-variant:normal|small-caps",
	"fvs": "font-variation-settings:normal|inherit|initial|unset",
	"fw": "font-weight:normal|bold|bolder|lighter",
	"fx": "flex",
	"fxb": "flex-basis:fill|max-content|min-content|fit-content|content",
	"fxd": "flex-direction:row|row-reverse|column|column-reverse",
	"fxf": "flex-flow",
	"fxg": "flex-grow",
	"fxsh": "flex-shrink",
	"fxw": "flex-wrap:nowrap|wrap|wrap-reverse",
	"fz": "font-size",
	"fza": "font-size-adjust",
	"h": "height",
	"jc": "justify-content:flex-start|flex-end|center|space-between|space-around",
	"l": "left",
	"lg": "background-image:linear-gradient(${1})",
	"lh": "line-height",
	"lis": "list-style",
	"lisi": "list-style-image",
	"lisp": "list-style-position:inside|outside",
	"list": "list-style-type:disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman",
	"lts": "letter-spacing:normal",
	"m": "margin",
	"mah": "max-height",
	"mar": "max-resolution",
	"maw": "max-width",
	"mb": "margin-bottom",
	"mih": "min-height",
	"mir": "min-resolution",
	"miw": "min-width",
	"ml": "margin-left",
	"mr": "margin-right",
	"mt": "margin-top",
	"ol": "outline",
	"olc": "outline-color:#${1:000}|invert",
	"olo": "outline-offset",
	"ols": "outline-style:none|dotted|dashed|solid|double|groove|ridge|inset|outset",
	"olw": "outline-width|thin|medium|thick",
	"op": "opacity",
	"ord": "order",
	"ori": "orientation:landscape|portrait",
	"orp": "orphans",
	"ov": "overflow:hidden|visible|hidden|scroll|auto",
	"ovs": "overflow-style:scrollbar|auto|scrollbar|panner|move|marquee",
	"ovx": "overflow-x:hidden|visible|hidden|scroll|auto",
	"ovy": "overflow-y:hidden|visible|hidden|scroll|auto",
	"p": "padding",
	"pb": "padding-bottom",
	"pgba": "page-break-after:auto|always|left|right",
	"pgbb": "page-break-before:auto|always|left|right",
	"pgbi": "page-break-inside:auto|avoid",
	"pl": "padding-left",
	"pos": "position:relative|absolute|relative|fixed|static",
	"pr": "padding-right",
	"pt": "padding-top",
	"q": "quotes",
	"qen": "quotes:'\\201C' '\\201D' '\\2018' '\\2019'",
	"qru": "quotes:'\\00AB' '\\00BB' '\\201E' '\\201C'",
	"r": "right",
	"rsz": "resize:none|both|horizontal|vertical",
	"t": "top",
	"ta": "text-align:left|center|right|justify",
	"tal": "text-align-last:left|center|right",
	"tbl": "table-layout:fixed",
	"td": "text-decoration:none|underline|overline|line-through",
	"te": "text-emphasis:none|accent|dot|circle|disc|before|after",
	"th": "text-height:auto|font-size|text-size|max-size",
	"ti": "text-indent",
	"tj": "text-justify:auto|inter-word|inter-ideograph|inter-cluster|distribute|kashida|tibetan",
	"to": "text-outline:${1:0} ${2:0} ${3:#000}",
	"tov": "text-overflow:ellipsis|clip",
	"tr": "text-replace",
	"trf": "transform:${1}|skewX(${1:angle})|skewY(${1:angle})|scale(${1:x}, ${2:y})|scaleX(${1:x})|scaleY(${1:y})|scaleZ(${1:z})|scale3d(${1:x}, ${2:y}, ${3:z})|rotate(${1:angle})|rotateX(${1:angle})|rotateY(${1:angle})|rotateZ(${1:angle})|translate(${1:x}, ${2:y})|translateX(${1:x})|translateY(${1:y})|translateZ(${1:z})|translate3d(${1:tx}, ${2:ty}, ${3:tz})",
	"trfo": "transform-origin",
	"trfs": "transform-style:preserve-3d",
	"trs": "transition:${1:prop} ${2:time}",
	"trsde": "transition-delay:${1:time}",
	"trsdu": "transition-duration:${1:time}",
	"trsp": "transition-property:${1:prop}",
	"trstf": "transition-timing-function:${1:fn}",
	"tsh": "text-shadow:${1:hoff} ${2:voff} ${3:blur} ${4:#000}",
	"tt": "text-transform:uppercase|lowercase|capitalize|none",
	"tw": "text-wrap:none|normal|unrestricted|suppress",
	"us": "user-select:none",
	"v": "visibility:hidden|visible|collapse",
	"va": "vertical-align:top|super|text-top|middle|baseline|bottom|text-bottom|sub",
	"w": "width",
	"whs": "white-space:nowrap|pre|pre-wrap|pre-line|normal",
	"whsc": "white-space-collapse:normal|keep-all|loose|break-strict|break-all",
	"wid": "widows",
	"wm": "writing-mode:lr-tb|lr-tb|lr-bt|rl-tb|rl-bt|tb-rl|tb-lr|bt-lr|bt-rl",
	"wob": "word-break:normal|keep-all|break-all",
	"wos": "word-spacing",
	"wow": "word-wrap:none|unrestricted|suppress|break-word|normal",
	"z": "z-index",
	"zom": "zoom:1"
};

var xsl$1 = {
    "tm|tmatch": "xsl:template[match mode]",
    "tn|tname": "xsl:template[name]",
    "call": "xsl:call-template[name]",
    "ap": "xsl:apply-templates[select mode]",
    "api": "xsl:apply-imports",
    "imp": "xsl:import[href]",
    "inc": "xsl:include[href]",
    "ch": "xsl:choose",
    "wh|xsl:when": "xsl:when[test]",
    "ot": "xsl:otherwise",
    "if": "xsl:if[test]",
    "par": "xsl:param[name]",
    "pare": "xsl:param[name select]",
    "var": "xsl:variable[name]",
    "vare": "xsl:variable[name select]",
    "wp": "xsl:with-param[name select]",
    "key": "xsl:key[name match use]",
    "elem": "xsl:element[name]",
    "attr": "xsl:attribute[name]",
    "attrs": "xsl:attribute-set[name]",
    "cp": "xsl:copy[select]",
    "co": "xsl:copy-of[select]",
    "val": "xsl:value-of[select]",
    "for|each": "xsl:for-each[select]",
    "tex": "xsl:text",
    "com": "xsl:comment",
    "msg": "xsl:message[terminate=no]",
    "fall": "xsl:fallback",
    "num": "xsl:number[value]",
    "nam": "namespace-alias[stylesheet-prefix result-prefix]",
    "pres": "xsl:preserve-space[elements]",
    "strip": "xsl:strip-space[elements]",
    "proc": "xsl:processing-instruction[name]",
    "sort": "xsl:sort[select order]",
    "choose": "xsl:choose>xsl:when+xsl:otherwise",
    "xsl": "!!!+xsl:stylesheet[version=1.0 xmlns:xsl=http://www.w3.org/1999/XSL/Transform]>{\n|}",
    "!!!": "{<?xml version=\"1.0\" encoding=\"UTF-8\"?>}"
};

var index$7 = { html: html$1, css: css$1, xsl: xsl$1 };

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 langs = { latin, ru, sp };

const defaultOptions$5 = {
	wordCount: 30,
	skipCommon: false,
	lang: 'latin'
};

/**
 * Replaces given parsed Emmet abbreviation node with nodes filled with
 * Lorem Ipsum stub text.
 * @param {Node} node
 * @return {Node}
 */
var index$8 = function(node, options) {
	options = Object.assign({}, defaultOptions$5, options);
	const dict = langs[options.lang] || langs.latin;
    const startWithCommon = !options.skipCommon && !isRepeating(node);

	if (!node.repeat && !isRoot$1(node.parent)) {
		// non-repeating element, insert text stub as a content of parent node
		// and remove current one
		node.parent.value = paragraph(dict, options.wordCount, startWithCommon);
		node.remove();
	} else {
		// Replace named node with generated content
		node.value = paragraph(dict, options.wordCount, startWithCommon);
		node.name = node.parent.name ? resolveImplicitName(node.parent.name) : null;
	}

	return node;
};

function isRoot$1(node) {
	return !node.parent;
}

/**
 * Returns random integer between <code>from</code> and <code>to</code> values
 * @param {Number} from
 * @param {Number} to
 * @returns {Number}
 */
function rand(from, to) {
	return Math.floor(Math.random() * (to - from) + from);
}

/**
 * @param {Array} arr
 * @param {Number} count
 * @returns {Array}
 */
function sample(arr, count) {
	const len = arr.length;
	const iterations = Math.min(len, count);
	const result = new Set();

	while (result.size < iterations) {
		result.add(arr[rand(0, len)]);
	}

	return Array.from(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 <code>words</code> array
 * @param {Array} words
 */
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 {Object} dict Words dictionary (see `lang/*.json`)
 * @param {Number} wordCount Words count in paragraph
 * @param {Boolean} startWithCommon Should paragraph start with common
 * "lorem ipsum" sentence.
 * @returns {String}
 */
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(' ');
}

/**
 * Check if given node is in repeating context, e.g. node itself or one of its
 * parent is repeated
 * @param  {Node}  node
 * @return {Boolean}
 */
function isRepeating(node) {
    while (node.parent) {
        if (node.repeat && node.repeat.value && node.repeat.value > 1) {
            return true;
        }

        node = node.parent;
    }

    return false;
}

const reLorem = /^lorem([a-z]*)(\d*)$/i;

/**
 * Constructs a snippets registry, filled with snippets, for given options
 * @param  {String} syntax  Abbreviation syntax
 * @param  {Object|Object[]} snippets Additional snippets
 * @return {SnippetsRegistry}
 */
function snippetsRegistryFactory(syntax, snippets) {
	const registrySnippets = [index$7[syntax] || index$7.html];

	if (Array.isArray(snippets)) {
		snippets.forEach(item => {
			// if array item is a string, treat it as a reference to globally
			// defined snippets
			registrySnippets.push(typeof item === 'string' ? index$7[item] : item);
		});
	} else if (typeof snippets === 'object') {
		registrySnippets.push(snippets);
	}

	const registry = new SnippetsRegistry(registrySnippets.filter(Boolean));

	// for non-stylesheet syntaxes add Lorem Ipsum generator
	if (syntax !== 'css') {
		registry.get(0).set(reLorem, loremGenerator);
	}

	return registry;
}

function loremGenerator(node) {
	const options = {};
	const m = node.name.match(reLorem);
	if (m[1]) {
		options.lang = m[1];
	}

	if (m[2]) {
		options.wordCount = +m[2];
	}

	return index$8(node, options);
}

/**
 * Default variables used in snippets to insert common values into predefined snippets
 * @type {Object}
 */
const defaultVariables = {
	lang: 'en',
	locale: 'en-US',
	charset: 'UTF-8'
};

/**
 * A list of syntaxes that should use Emmet CSS abbreviations:
 * a variations of default abbreivation that holds values right in abbreviation name
 * @type {Set}
 */
const stylesheetSyntaxes = new Set(['css', 'sass', 'scss', 'less', 'stylus', 'sss']);

const defaultOptions$6 = {
	/**
	 * Abbreviation output syntax
	 * @type {String}
	 */
	syntax: 'html',

	/**
	 * Field/tabstop generator for editor. Most editors support TextMate-style
	 * fields: ${0} or ${1:item}. So for TextMate-style fields this function
	 * will look like this:
	 * @example
	 * (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`
	 *
	 * @param  {Number} index         Placeholder index. Fields with the same indices
	 * should be linked
	 * @param  {String} [placeholder] Field placeholder
	 * @return {String}
	 */
	field: (index, placeholder) => placeholder || '',

	/**
	 * Insert given text string(s) into expanded abbreviation
	 * If array of strings is given, the implicitly repeated element (e.g. `li*`)
	 * will be repeated by the amount of items in array
	 * @type {String|String[]}
	 */
	text: null,

	/**
	 * Either predefined output profile or options for output profile. Used for
	 * abbreviation output
	 * @type {Profile|Object}
	 */
	profile: null,

	/**
	 * Custom variables for variable resolver
	 * @see @emmetio/variable-resolver
	 * @type {Object}
	 */
	variables: {},

	/**
	 * Custom predefined snippets for abbreviation. The expanded abbreviation
	 * will try to match given snippets that may contain custom elements,
	 * predefined attributes etc.
	 * May also contain array of items: either snippets (Object) or references
	 * to default syntax snippets (String; the key in default snippets hash)
	 * @see @emmetio/snippets
	 * @type {Object|SnippetsRegistry}
	 */
	snippets: {},

	/**
	 * Hash of additional transformations that should be applied to expanded
	 * abbreviation, like BEM or JSX. Since these transformations introduce
	 * side-effect, they are disabled by default and should be enabled by
	 * providing a transform name as a key and transform options as value:
	 * @example
	 * {
	 *     bem: {element: '--'},
	 *     jsx: true // no options, just enable transform
	 * }
	 * @see @emmetio/html-transform/lib/addons
	 * @type {Object}
	 */
	addons: null,

	/**
	 * Additional options for syntax formatter
	 * @see @emmetio/markup-formatters
	 * @type {Object}
	 */
	format: null
};

/**
 * Expands given abbreviation into string, formatted according to provided
 * syntax and options
 * @param  {String|Node} abbr        Abbreviation string or parsed abbreviation tree
 * @param  {String|Object} [options] Parsing and formatting options (object) or
 * abbreviation syntax (string)
 * @return {String}
 */
function expand$2(abbr, options) {
	options = createOptions(options);

	return isStylesheet(options.syntax)
		? expand$1(abbr, options)
		: expand(abbr, options);
}

/**
 * Parses given abbreviation into AST tree. This tree can be later formatted to
 * string with `expand` function
 * @param  {String} abbr             Abbreviation to parse
 * @param  {String|Object} [options] Parsing and formatting options (object) or
 * abbreviation syntax (string)
 * @return {Node}
 */
function parse$5(abbr, options) {
	options = createOptions(options);

	return isStylesheet(options.syntax)
		? parse$4(abbr, options)
		: parse$3(abbr, options);
}

/**
 * Creates snippets registry for given syntax and additional `snippets`
 * @param  {String} syntax   Snippets syntax, used for retreiving predefined snippets
 * @param  {SnippetsRegistry|Object|Object[]} [snippets] Additional snippets
 * @return {SnippetsRegistry}
 */
function createSnippetsRegistry(syntax, snippets) {
	return snippets instanceof SnippetsRegistry
		? snippets
		: snippetsRegistryFactory(isStylesheet(syntax) ? 'css' : syntax, snippets);
}

function createOptions(options) {
	if (typeof options === 'string') {
		options = { syntax: options };
	}

	options = Object.assign({}, defaultOptions$6, options);
	options.format = Object.assign({field: options.field}, options.format);
	options.profile = createProfile(options);
	options.variables = Object.assign({}, defaultVariables, options.variables);
	options.snippets = createSnippetsRegistry(isStylesheet(options.syntax) ? 'css' : options.syntax, options.snippets);

	return options;
}

/**
 * Check if given syntax belongs to stylesheet markup.
 * Emmet uses different abbreviation flavours: one is a default markup syntax,
 * used for HTML, Slim, Pug etc, the other one is used for stylesheets and
 * allows embedded values in abbreviation name
 * @param  {String}  syntax
 * @return {Boolean}
 */
function isStylesheet(syntax) {
	return stylesheetSyntaxes.has(syntax);
}

/**
 * Creates output profile from given options
 * @param  {Object} options
 * @return {Profile}
 */
function createProfile(options) {
	return options.profile instanceof Profile
		? options.profile
		: new Profile(options.profile);
}

exports.expand = expand$2;
exports.parse = parse$5;
exports.createSnippetsRegistry = createSnippetsRegistry;
exports.createOptions = createOptions;
exports.isStylesheet = isStylesheet;
exports.createProfile = createProfile;

Object.defineProperty(exports, '__esModule', { value: true });

})));
