My dotfiles
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

5860 regels
159 KiB

  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (factory((global.emmet = {})));
  5. }(this, (function (exports) { 'use strict';
  6. var defaultOptions = {
  7. /**
  8. * String for one-level indentation
  9. * @type {String}
  10. */
  11. indent: '\t',
  12. /**
  13. * Tag case: 'lower', 'upper' or '' (keep as-is)
  14. * @type {String}
  15. */
  16. tagCase: '',
  17. /**
  18. * Attribute name case: 'lower', 'upper' or '' (keep as-is)
  19. * @type {String}
  20. */
  21. attributeCase: '',
  22. /**
  23. * Attribute value quotes: 'single' or 'double'
  24. * @type {String}
  25. */
  26. attributeQuotes: 'double',
  27. /**
  28. * Enable output formatting (indentation and line breaks)
  29. * @type {Boolean}
  30. */
  31. format: true,
  32. /**
  33. * A list of tag names that should not get inner indentation
  34. * @type {Set}
  35. */
  36. formatSkip: ['html'],
  37. /**
  38. * A list of tag names that should *always* get inner indentation.
  39. * @type {Set}
  40. */
  41. formatForce: ['body'],
  42. /**
  43. * How many inline sibling elements should force line break for each tag.
  44. * Set to 0 to output all inline elements without formatting.
  45. * Set to 1 to output all inline elements with formatting (same as block-level).
  46. * @type {Number}
  47. */
  48. inlineBreak: 3,
  49. /**
  50. * Produce compact notation of boolean attribues: attributes where name equals value.
  51. * With this option enabled, output `<div contenteditable>` instead of
  52. * `<div contenteditable="contenteditable">`
  53. * @type {Boolean}
  54. */
  55. compactBooleanAttributes: false,
  56. /**
  57. * A set of boolean attributes
  58. * @type {Set}
  59. */
  60. booleanAttributes: ['contenteditable', 'seamless', 'async', 'autofocus',
  61. 'autoplay', 'checked', 'controls', 'defer', 'disabled', 'formnovalidate',
  62. 'hidden', 'ismap', 'loop', 'multiple', 'muted', 'novalidate', 'readonly',
  63. 'required', 'reversed', 'selected', 'typemustmatch'],
  64. /**
  65. * Style of self-closing tags:
  66. * 'html' – <br>
  67. * 'xml' – <br/>
  68. * 'xhtml' – <br />
  69. * @type {String}
  70. */
  71. selfClosingStyle: 'html',
  72. /**
  73. * A set of inline-level elements
  74. * @type {Set}
  75. */
  76. inlineElements: ['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
  77. 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
  78. 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
  79. 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
  80. 'textarea', 'tt', 'u', 'var']
  81. };
  82. /**
  83. * Creates output profile for given options (@see defaults)
  84. * @param {defaults} options
  85. */
  86. class Profile {
  87. constructor(options) {
  88. this.options = Object.assign({}, defaultOptions, options);
  89. this.quoteChar = this.options.attributeQuotes === 'single' ? '\'' : '"';
  90. }
  91. /**
  92. * Returns value of given option name
  93. * @param {String} name
  94. * @return {*}
  95. */
  96. get(name) {
  97. return this.options[name];
  98. }
  99. /**
  100. * Quote given string according to profile
  101. * @param {String} str String to quote
  102. * @return {String}
  103. */
  104. quote(str) {
  105. return `${this.quoteChar}${str != null ? str : ''}${this.quoteChar}`;
  106. }
  107. /**
  108. * Output given tag name accoding to options
  109. * @param {String} name
  110. * @return {String}
  111. */
  112. name(name) {
  113. return strcase(name, this.options.tagCase);
  114. }
  115. /**
  116. * Outputs attribute name accoding to current settings
  117. * @param {String} Attribute name
  118. * @return {String}
  119. */
  120. attribute(attr) {
  121. return strcase(attr, this.options.attributeCase);
  122. }
  123. /**
  124. * Check if given attribute is boolean
  125. * @param {Attribute} attr
  126. * @return {Boolean}
  127. */
  128. isBooleanAttribute(attr) {
  129. return attr.options.boolean
  130. || this.get('booleanAttributes').indexOf((attr.name || '').toLowerCase()) !== -1;
  131. }
  132. /**
  133. * Returns a token for self-closing tag, depending on current options
  134. * @return {String}
  135. */
  136. selfClose() {
  137. switch (this.options.selfClosingStyle) {
  138. case 'xhtml': return ' /';
  139. case 'xml': return '/';
  140. default: return '';
  141. }
  142. }
  143. /**
  144. * Returns indent for given level
  145. * @param {Number} level Indentation level
  146. * @return {String}
  147. */
  148. indent(level) {
  149. level = level || 0;
  150. let output = '';
  151. while (level--) {
  152. output += this.options.indent;
  153. }
  154. return output;
  155. }
  156. /**
  157. * Check if given tag name belongs to inline-level element
  158. * @param {Node|String} node Parsed node or tag name
  159. * @return {Boolean}
  160. */
  161. isInline(node) {
  162. if (typeof node === 'string') {
  163. return this.get('inlineElements').indexOf(node.toLowerCase()) !== -1;
  164. }
  165. // inline node is a node either with inline-level name or text-only node
  166. return node.name != null ? this.isInline(node.name) : node.isTextOnly;
  167. }
  168. /**
  169. * Outputs formatted field for given params
  170. * @param {Number} index Field index
  171. * @param {String} [placeholder] Field placeholder, can be empty
  172. * @return {String}
  173. */
  174. field(index, placeholder) {
  175. return this.options.field(index, placeholder);
  176. }
  177. }
  178. function strcase(string, type) {
  179. if (type) {
  180. string = type === 'upper' ? string.toUpperCase() : string.toLowerCase();
  181. }
  182. return string;
  183. }
  184. class Snippet {
  185. constructor(key, value) {
  186. this.key = key;
  187. this.value = value;
  188. }
  189. }
  190. class SnippetsStorage {
  191. constructor(data) {
  192. this._string = new Map();
  193. this._regexp = new Map();
  194. this._disabled = false;
  195. this.load(data);
  196. }
  197. get disabled() {
  198. return this._disabled;
  199. }
  200. /**
  201. * Disables current store. A disabled store always returns `undefined`
  202. * on `get()` method
  203. */
  204. disable() {
  205. this._disabled = true;
  206. }
  207. /**
  208. * Enables current store.
  209. */
  210. enable() {
  211. this._disabled = false;
  212. }
  213. /**
  214. * Registers a new snippet item
  215. * @param {String|Regexp} key
  216. * @param {String|Function} value
  217. */
  218. set(key, value) {
  219. if (typeof key === 'string') {
  220. key.split('|').forEach(k => this._string.set(k, new Snippet(k, value)));
  221. } else if (key instanceof RegExp) {
  222. this._regexp.set(key, new Snippet(key, value));
  223. } else {
  224. throw new Error('Unknow snippet key: ' + key);
  225. }
  226. return this;
  227. }
  228. /**
  229. * Returns a snippet matching given key. It first tries to find snippet
  230. * exact match in a string key map, then tries to match one with regexp key
  231. * @param {String} key
  232. * @return {Snippet}
  233. */
  234. get(key) {
  235. if (this.disabled) {
  236. return undefined;
  237. }
  238. if (this._string.has(key)) {
  239. return this._string.get(key);
  240. }
  241. const keys = Array.from(this._regexp.keys());
  242. for (let i = 0, il = keys.length; i < il; i++) {
  243. if (keys[i].test(key)) {
  244. return this._regexp.get(keys[i]);
  245. }
  246. }
  247. }
  248. /**
  249. * Batch load of snippets data
  250. * @param {Object|Map} data
  251. */
  252. load(data) {
  253. this.reset();
  254. if (data instanceof Map) {
  255. data.forEach((value, key) => this.set(key, value));
  256. } else if (data && typeof data === 'object') {
  257. Object.keys(data).forEach(key => this.set(key, data[key]));
  258. }
  259. }
  260. /**
  261. * Clears all stored snippets
  262. */
  263. reset() {
  264. this._string.clear();
  265. this._regexp.clear();
  266. }
  267. /**
  268. * Returns all available snippets from given store
  269. */
  270. values() {
  271. if (this.disabled) {
  272. return [];
  273. }
  274. const string = Array.from(this._string.values());
  275. const regexp = Array.from(this._regexp.values());
  276. return string.concat(regexp);
  277. }
  278. }
  279. /**
  280. * A snippets registry. Contains snippets, separated by store and sorted by
  281. * priority: a store with higher priority takes precedence when resolving snippet
  282. * for given key
  283. */
  284. class SnippetsRegistry {
  285. /**
  286. * Creates snippets registry, filled with given `data`
  287. * @param {Object|Array} data Registry snippets. If array is given, adds items
  288. * from array in order of precedence, registers global snippets otherwise
  289. */
  290. constructor(data) {
  291. this._registry = [];
  292. if (Array.isArray(data)) {
  293. data.forEach((snippets, level) => this.add(level, snippets));
  294. } else if (typeof data === 'object') {
  295. this.add(data);
  296. }
  297. }
  298. /**
  299. * Return store for given level
  300. * @param {Number} level
  301. * @return {SnippetsStorage}
  302. */
  303. get(level) {
  304. for (let i = 0; i < this._registry.length; i++) {
  305. const item = this._registry[i];
  306. if (item.level === level) {
  307. return item.store;
  308. }
  309. }
  310. }
  311. /**
  312. * Adds new store for given level
  313. * @param {Number} [level] Store level (priority). Store with higher level
  314. * takes precedence when resolving snippets
  315. * @param {Object} [snippets] A snippets data for new store
  316. * @return {SnipetsStorage}
  317. */
  318. add(level, snippets) {
  319. if (level != null && typeof level === 'object') {
  320. snippets = level;
  321. level = 0;
  322. }
  323. const store = new SnippetsStorage(snippets);
  324. // remove previous store from same level
  325. this.remove(level);
  326. this._registry.push({level, store});
  327. this._registry.sort((a, b) => b.level - a.level);
  328. return store;
  329. }
  330. /**
  331. * Remove registry with given level or store
  332. * @param {Number|SnippetsStorage} data Either level or snippets store
  333. */
  334. remove(data) {
  335. this._registry = this._registry
  336. .filter(item => item.level !== data && item.store !== data);
  337. }
  338. /**
  339. * Returns snippet from registry that matches given name
  340. * @param {String} name
  341. * @return {Snippet}
  342. */
  343. resolve(name) {
  344. for (let i = 0; i < this._registry.length; i++) {
  345. const snippet = this._registry[i].store.get(name);
  346. if (snippet) {
  347. return snippet;
  348. }
  349. }
  350. }
  351. /**
  352. * Returns all available snippets from current registry. Snippets with the
  353. * same key are resolved by their storage priority.
  354. * @param {Object} options
  355. * @param {Object} options.type Return snippets only of given type: 'string'
  356. * or 'regexp'. Returns all snippets if not defined
  357. * @return {Array}
  358. */
  359. all(options) {
  360. options = options || {};
  361. const result = new Map();
  362. const fillResult = snippet => {
  363. const type = snippet.key instanceof RegExp ? 'regexp' : 'string';
  364. if ((!options.type || options.type === type) && !result.has(snippet.key)) {
  365. result.set(snippet.key, snippet);
  366. }
  367. };
  368. this._registry.forEach(item => {
  369. item.store.values().forEach(fillResult);
  370. });
  371. return Array.from(result.values());
  372. }
  373. /**
  374. * Removes all stores from registry
  375. */
  376. clear() {
  377. this._registry.length = 0;
  378. }
  379. }
  380. /**
  381. * Methods for consuming quoted values
  382. */
  383. const SINGLE_QUOTE = 39; // '
  384. const DOUBLE_QUOTE = 34; // "
  385. const defaultOptions$1 = {
  386. escape: 92, // \ character
  387. throws: false
  388. };
  389. /**
  390. * Consumes 'single' or "double"-quoted string from given string, if possible
  391. * @param {StreamReader} stream
  392. * @param {Number} options.escape A character code of quote-escape symbol
  393. * @param {Boolean} options.throws Throw error if quotes string can’t be properly consumed
  394. * @return {Boolean} `true` if quoted string was consumed. The contents
  395. * of quoted string will be availabe as `stream.current()`
  396. */
  397. var eatQuoted = function(stream, options) {
  398. options = options ? Object.assign({}, defaultOptions$1, options) : defaultOptions$1;
  399. const start = stream.pos;
  400. const quote = stream.peek();
  401. if (stream.eat(isQuote)) {
  402. while (!stream.eof()) {
  403. switch (stream.next()) {
  404. case quote:
  405. stream.start = start;
  406. return true;
  407. case options.escape:
  408. stream.next();
  409. break;
  410. }
  411. }
  412. // If we’re here then stream wasn’t properly consumed.
  413. // Revert stream and decide what to do
  414. stream.pos = start;
  415. if (options.throws) {
  416. throw stream.error('Unable to consume quoted string');
  417. }
  418. }
  419. return false;
  420. };
  421. function isQuote(code) {
  422. return code === SINGLE_QUOTE || code === DOUBLE_QUOTE;
  423. }
  424. /**
  425. * Check if given code is a number
  426. * @param {Number} code
  427. * @return {Boolean}
  428. */
  429. function isNumber(code) {
  430. return code > 47 && code < 58;
  431. }
  432. /**
  433. * Check if given character code is alpha code (letter through A to Z)
  434. * @param {Number} code
  435. * @param {Number} [from]
  436. * @param {Number} [to]
  437. * @return {Boolean}
  438. */
  439. function isAlpha(code, from, to) {
  440. from = from || 65; // A
  441. to = to || 90; // Z
  442. code &= ~32; // quick hack to convert any char code to uppercase char code
  443. return code >= from && code <= to;
  444. }
  445. /**
  446. * Check if given character code is alpha-numeric (letter through A to Z or number)
  447. * @param {Number} code
  448. * @return {Boolean}
  449. */
  450. function isAlphaNumeric(code) {
  451. return isNumber(code) || isAlpha(code);
  452. }
  453. function isWhiteSpace(code) {
  454. return code === 32 /* space */
  455. || code === 9 /* tab */
  456. || code === 160; /* non-breaking space */
  457. }
  458. /**
  459. * Check if given character code is a space
  460. * @param {Number} code
  461. * @return {Boolean}
  462. */
  463. function isSpace(code) {
  464. return isWhiteSpace(code)
  465. || code === 10 /* LF */
  466. || code === 13; /* CR */
  467. }
  468. /**
  469. * Attribute descriptor of parsed abbreviation node
  470. * @param {String} name Attribute name
  471. * @param {String} value Attribute value
  472. * @param {Object} options Additional custom attribute options
  473. * @param {Boolean} options.boolean Attribute is boolean (e.g. name equals value)
  474. * @param {Boolean} options.implied Attribute is implied (e.g. must be outputted
  475. * only if contains non-null value)
  476. */
  477. class Attribute {
  478. constructor(name, value, options) {
  479. this.name = name;
  480. this.value = value != null ? value : null;
  481. this.options = options || {};
  482. }
  483. /**
  484. * Create a copy of current attribute
  485. * @return {Attribute}
  486. */
  487. clone() {
  488. return new Attribute(this.name, this.value, Object.assign({}, this.options));
  489. }
  490. /**
  491. * A string representation of current node
  492. */
  493. valueOf() {
  494. return `${this.name}="${this.value}"`;
  495. }
  496. }
  497. /**
  498. * A parsed abbreviation AST node. Nodes build up an abbreviation AST tree
  499. */
  500. class Node {
  501. /**
  502. * Creates a new node
  503. * @param {String} [name] Node name
  504. * @param {Array} [attributes] Array of attributes to add
  505. */
  506. constructor(name, attributes) {
  507. // own properties
  508. this.name = name || null;
  509. this.value = null;
  510. this.repeat = null;
  511. this.selfClosing = false;
  512. this.children = [];
  513. /** @type {Node} Pointer to parent node */
  514. this.parent = null;
  515. /** @type {Node} Pointer to next sibling */
  516. this.next = null;
  517. /** @type {Node} Pointer to previous sibling */
  518. this.previous = null;
  519. this._attributes = [];
  520. if (Array.isArray(attributes)) {
  521. attributes.forEach(attr => this.setAttribute(attr));
  522. }
  523. }
  524. /**
  525. * Array of current node attributes
  526. * @return {Attribute[]} Array of attributes
  527. */
  528. get attributes() {
  529. return this._attributes;
  530. }
  531. /**
  532. * A shorthand to retreive node attributes as map
  533. * @return {Object}
  534. */
  535. get attributesMap() {
  536. return this.attributes.reduce((out, attr) => {
  537. out[attr.name] = attr.options.boolean ? attr.name : attr.value;
  538. return out;
  539. }, {});
  540. }
  541. /**
  542. * Check if current node is a grouping one, e.g. has no actual representation
  543. * and is used for grouping subsequent nodes only
  544. * @return {Boolean}
  545. */
  546. get isGroup() {
  547. return !this.name && !this.value && !this._attributes.length;
  548. }
  549. /**
  550. * Check if given node is a text-only node, e.g. contains only value
  551. * @return {Boolean}
  552. */
  553. get isTextOnly() {
  554. return !this.name && !!this.value && !this._attributes.length;
  555. }
  556. /**
  557. * Returns first child node
  558. * @return {Node}
  559. */
  560. get firstChild() {
  561. return this.children[0];
  562. }
  563. /**
  564. * Returns last child of current node
  565. * @return {Node}
  566. */
  567. get lastChild() {
  568. return this.children[this.children.length - 1];
  569. }
  570. /**
  571. * Return index of current node in its parent child list
  572. * @return {Number} Returns -1 if current node is a root one
  573. */
  574. get childIndex() {
  575. return this.parent ? this.parent.children.indexOf(this) : -1;
  576. }
  577. /**
  578. * Returns next sibling of current node
  579. * @return {Node}
  580. */
  581. get nextSibling() {
  582. return this.next;
  583. }
  584. /**
  585. * Returns previous sibling of current node
  586. * @return {Node}
  587. */
  588. get previousSibling() {
  589. return this.previous;
  590. }
  591. /**
  592. * Returns array of unique class names in current node
  593. * @return {String[]}
  594. */
  595. get classList() {
  596. const attr = this.getAttribute('class');
  597. return attr && attr.value
  598. ? attr.value.split(/\s+/g).filter(uniqueClass)
  599. : [];
  600. }
  601. /**
  602. * Convenient alias to create a new node instance
  603. * @param {String} [name] Node name
  604. * @param {Object} [attributes] Attributes hash
  605. * @return {Node}
  606. */
  607. create(name, attributes) {
  608. return new Node(name, attributes);
  609. }
  610. /**
  611. * Sets given attribute for current node
  612. * @param {String|Object|Attribute} name Attribute name or attribute object
  613. * @param {String} [value] Attribute value
  614. */
  615. setAttribute(name, value) {
  616. const attr = createAttribute(name, value);
  617. const curAttr = this.getAttribute(name);
  618. if (curAttr) {
  619. this.replaceAttribute(curAttr, attr);
  620. } else {
  621. this._attributes.push(attr);
  622. }
  623. }
  624. /**
  625. * Check if attribute with given name exists in node
  626. * @param {String} name
  627. * @return {Boolean}
  628. */
  629. hasAttribute(name) {
  630. return !!this.getAttribute(name);
  631. }
  632. /**
  633. * Returns attribute object by given name
  634. * @param {String} name
  635. * @return {Attribute}
  636. */
  637. getAttribute(name) {
  638. if (typeof name === 'object') {
  639. name = name.name;
  640. }
  641. for (var i = 0; i < this._attributes.length; i++) {
  642. const attr = this._attributes[i];
  643. if (attr.name === name) {
  644. return attr;
  645. }
  646. }
  647. }
  648. /**
  649. * Replaces attribute with new instance
  650. * @param {String|Attribute} curAttribute Current attribute name or instance
  651. * to replace
  652. * @param {String|Object|Attribute} newName New attribute name or attribute object
  653. * @param {String} [newValue] New attribute value
  654. */
  655. replaceAttribute(curAttribute, newName, newValue) {
  656. if (typeof curAttribute === 'string') {
  657. curAttribute = this.getAttribute(curAttribute);
  658. }
  659. const ix = this._attributes.indexOf(curAttribute);
  660. if (ix !== -1) {
  661. this._attributes.splice(ix, 1, createAttribute(newName, newValue));
  662. }
  663. }
  664. /**
  665. * Removes attribute with given name
  666. * @param {String|Attribute} attr Atrtibute name or instance
  667. */
  668. removeAttribute(attr) {
  669. if (typeof attr === 'string') {
  670. attr = this.getAttribute(attr);
  671. }
  672. const ix = this._attributes.indexOf(attr);
  673. if (ix !== -1) {
  674. this._attributes.splice(ix, 1);
  675. }
  676. }
  677. /**
  678. * Removes all attributes from current node
  679. */
  680. clearAttributes() {
  681. this._attributes.length = 0;
  682. }
  683. /**
  684. * Adds given class name to class attribute
  685. * @param {String} token Class name token
  686. */
  687. addClass(token) {
  688. token = normalize(token);
  689. if (!this.hasAttribute('class')) {
  690. this.setAttribute('class', token);
  691. } else if (token && !this.hasClass(token)) {
  692. this.setAttribute('class', this.classList.concat(token).join(' '));
  693. }
  694. }
  695. /**
  696. * Check if current node contains given class name
  697. * @param {String} token Class name token
  698. * @return {Boolean}
  699. */
  700. hasClass(token) {
  701. return this.classList.indexOf(normalize(token)) !== -1;
  702. }
  703. /**
  704. * Removes given class name from class attribute
  705. * @param {String} token Class name token
  706. */
  707. removeClass(token) {
  708. token = normalize(token);
  709. if (this.hasClass(token)) {
  710. this.setAttribute('class', this.classList.filter(name => name !== token).join(' '));
  711. }
  712. }
  713. /**
  714. * Appends child to current node
  715. * @param {Node} node
  716. */
  717. appendChild(node) {
  718. this.insertAt(node, this.children.length);
  719. }
  720. /**
  721. * Inserts given `newNode` before `refNode` child node
  722. * @param {Node} newNode
  723. * @param {Node} refNode
  724. */
  725. insertBefore(newNode, refNode) {
  726. this.insertAt(newNode, this.children.indexOf(refNode));
  727. }
  728. /**
  729. * Insert given `node` at `pos` position of child list
  730. * @param {Node} node
  731. * @param {Number} pos
  732. */
  733. insertAt(node, pos) {
  734. if (pos < 0 || pos > this.children.length) {
  735. throw new Error('Unable to insert node: position is out of child list range');
  736. }
  737. const prev = this.children[pos - 1];
  738. const next = this.children[pos];
  739. node.remove();
  740. node.parent = this;
  741. this.children.splice(pos, 0, node);
  742. if (prev) {
  743. node.previous = prev;
  744. prev.next = node;
  745. }
  746. if (next) {
  747. node.next = next;
  748. next.previous = node;
  749. }
  750. }
  751. /**
  752. * Removes given child from current node
  753. * @param {Node} node
  754. */
  755. removeChild(node) {
  756. const ix = this.children.indexOf(node);
  757. if (ix !== -1) {
  758. this.children.splice(ix, 1);
  759. if (node.previous) {
  760. node.previous.next = node.next;
  761. }
  762. if (node.next) {
  763. node.next.previous = node.previous;
  764. }
  765. node.parent = node.next = node.previous = null;
  766. }
  767. }
  768. /**
  769. * Removes current node from its parent
  770. */
  771. remove() {
  772. if (this.parent) {
  773. this.parent.removeChild(this);
  774. }
  775. }
  776. /**
  777. * Creates a detached copy of current node
  778. * @param {Boolean} deep Clone node contents as well
  779. * @return {Node}
  780. */
  781. clone(deep) {
  782. const clone = new Node(this.name);
  783. clone.value = this.value;
  784. clone.selfClosing = this.selfClosing;
  785. if (this.repeat) {
  786. clone.repeat = Object.assign({}, this.repeat);
  787. }
  788. this._attributes.forEach(attr => clone.setAttribute(attr.clone()));
  789. if (deep) {
  790. this.children.forEach(child => clone.appendChild(child.clone(true)));
  791. }
  792. return clone;
  793. }
  794. /**
  795. * Walks on each descendant node and invokes given `fn` function on it.
  796. * The function receives two arguments: the node itself and its depth level
  797. * from current node. If function returns `false`, it stops walking
  798. * @param {Function} fn
  799. */
  800. walk(fn, _level) {
  801. _level = _level || 0;
  802. let ctx = this.firstChild;
  803. while (ctx) {
  804. // in case if context node will be detached during `fn` call
  805. const next = ctx.next;
  806. if (fn(ctx, _level) === false || ctx.walk(fn, _level + 1) === false) {
  807. return false;
  808. }
  809. ctx = next;
  810. }
  811. }
  812. /**
  813. * A helper method for transformation chaining: runs given `fn` function on
  814. * current node and returns the same node
  815. */
  816. use(fn) {
  817. const args = [this];
  818. for (var i = 1; i < arguments.length; i++) {
  819. args.push(arguments[i]);
  820. }
  821. fn.apply(null, args);
  822. return this;
  823. }
  824. toString() {
  825. const attrs = this.attributes.map(attr => {
  826. attr = this.getAttribute(attr.name);
  827. const opt = attr.options;
  828. let out = `${opt && opt.implied ? '!' : ''}${attr.name || ''}`;
  829. if (opt && opt.boolean) {
  830. out += '.';
  831. } else if (attr.value != null) {
  832. out += `="${attr.value}"`;
  833. }
  834. return out;
  835. });
  836. let out = `${this.name || ''}`;
  837. if (attrs.length) {
  838. out += `[${attrs.join(' ')}]`;
  839. }
  840. if (this.value != null) {
  841. out += `{${this.value}}`;
  842. }
  843. if (this.selfClosing) {
  844. out += '/';
  845. }
  846. if (this.repeat) {
  847. out += `*${this.repeat.count ? this.repeat.count : ''}`;
  848. if (this.repeat.value != null) {
  849. out += `@${this.repeat.value}`;
  850. }
  851. }
  852. return out;
  853. }
  854. }
  855. /**
  856. * Attribute factory
  857. * @param {String|Attribute|Object} name Attribute name or attribute descriptor
  858. * @param {*} value Attribute value
  859. * @return {Attribute}
  860. */
  861. function createAttribute(name, value) {
  862. if (name instanceof Attribute) {
  863. return name;
  864. }
  865. if (typeof name === 'string') {
  866. return new Attribute(name, value);
  867. }
  868. if (name && typeof name === 'object') {
  869. return new Attribute(name.name, name.value, name.options);
  870. }
  871. }
  872. /**
  873. * @param {String} str
  874. * @return {String}
  875. */
  876. function normalize(str) {
  877. return String(str).trim();
  878. }
  879. function uniqueClass(item, i, arr) {
  880. return item && arr.indexOf(item) === i;
  881. }
  882. /**
  883. * A streaming, character code-based string reader
  884. */
  885. class StreamReader {
  886. constructor(string, start, end) {
  887. if (end == null && typeof string === 'string') {
  888. end = string.length;
  889. }
  890. this.string = string;
  891. this.pos = this.start = start || 0;
  892. this.end = end;
  893. }
  894. /**
  895. * Returns true only if the stream is at the end of the file.
  896. * @returns {Boolean}
  897. */
  898. eof() {
  899. return this.pos >= this.end;
  900. }
  901. /**
  902. * Creates a new stream instance which is limited to given `start` and `end`
  903. * range. E.g. its `eof()` method will look at `end` property, not actual
  904. * stream end
  905. * @param {Point} start
  906. * @param {Point} end
  907. * @return {StreamReader}
  908. */
  909. limit(start, end) {
  910. return new this.constructor(this.string, start, end);
  911. }
  912. /**
  913. * Returns the next character code in the stream without advancing it.
  914. * Will return NaN at the end of the file.
  915. * @returns {Number}
  916. */
  917. peek() {
  918. return this.string.charCodeAt(this.pos);
  919. }
  920. /**
  921. * Returns the next character in the stream and advances it.
  922. * Also returns <code>undefined</code> when no more characters are available.
  923. * @returns {Number}
  924. */
  925. next() {
  926. if (this.pos < this.string.length) {
  927. return this.string.charCodeAt(this.pos++);
  928. }
  929. }
  930. /**
  931. * `match` can be a character code or a function that takes a character code
  932. * and returns a boolean. If the next character in the stream 'matches'
  933. * the given argument, it is consumed and returned.
  934. * Otherwise, `false` is returned.
  935. * @param {Number|Function} match
  936. * @returns {Boolean}
  937. */
  938. eat(match) {
  939. const ch = this.peek();
  940. const ok = typeof match === 'function' ? match(ch) : ch === match;
  941. if (ok) {
  942. this.next();
  943. }
  944. return ok;
  945. }
  946. /**
  947. * Repeatedly calls <code>eat</code> with the given argument, until it
  948. * fails. Returns <code>true</code> if any characters were eaten.
  949. * @param {Object} match
  950. * @returns {Boolean}
  951. */
  952. eatWhile(match) {
  953. const start = this.pos;
  954. while (!this.eof() && this.eat(match)) {}
  955. return this.pos !== start;
  956. }
  957. /**
  958. * Backs up the stream n characters. Backing it up further than the
  959. * start of the current token will cause things to break, so be careful.
  960. * @param {Number} n
  961. */
  962. backUp(n) {
  963. this.pos -= (n || 1);
  964. }
  965. /**
  966. * Get the string between the start of the current token and the
  967. * current stream position.
  968. * @returns {String}
  969. */
  970. current() {
  971. return this.substring(this.start, this.pos);
  972. }
  973. /**
  974. * Returns substring for given range
  975. * @param {Number} start
  976. * @param {Number} [end]
  977. * @return {String}
  978. */
  979. substring(start, end) {
  980. return this.string.slice(start, end);
  981. }
  982. /**
  983. * Creates error object with current stream state
  984. * @param {String} message
  985. * @return {Error}
  986. */
  987. error(message) {
  988. const err = new Error(`${message} at char ${this.pos + 1}`);
  989. err.originalMessage = message;
  990. err.pos = this.pos;
  991. err.string = this.string;
  992. return err;
  993. }
  994. }
  995. const ASTERISK = 42; // *
  996. /**
  997. * Consumes node repeat token from current stream position and returns its
  998. * parsed value
  999. * @param {StringReader} stream
  1000. * @return {Object}
  1001. */
  1002. function consumeRepeat(stream) {
  1003. if (stream.eat(ASTERISK)) {
  1004. stream.start = stream.pos;
  1005. // XXX think about extending repeat syntax with through numbering
  1006. return { count: stream.eatWhile(isNumber) ? +stream.current() : null };
  1007. }
  1008. }
  1009. const opt = { throws: true };
  1010. /**
  1011. * Consumes quoted literal from current stream position and returns it’s inner,
  1012. * unquoted, value
  1013. * @param {StringReader} stream
  1014. * @return {String} Returns `null` if unable to consume quoted value from current
  1015. * position
  1016. */
  1017. function consumeQuoted(stream) {
  1018. if (eatQuoted(stream, opt)) {
  1019. return stream.current().slice(1, -1);
  1020. }
  1021. }
  1022. const TEXT_START = 123; // {
  1023. const TEXT_END = 125; // }
  1024. const ESCAPE = 92; // \ character
  1025. /**
  1026. * Consumes text node `{...}` from stream
  1027. * @param {StreamReader} stream
  1028. * @return {String} Returns consumed text value (without surrounding braces) or
  1029. * `null` if there’s no text at starting position
  1030. */
  1031. function consumeText(stream) {
  1032. // NB using own implementation instead of `eatPair()` from @emmetio/stream-reader-utils
  1033. // to disable quoted value consuming
  1034. const start = stream.pos;
  1035. if (stream.eat(TEXT_START)) {
  1036. let stack = 1, ch;
  1037. let result = '';
  1038. let offset = stream.pos;
  1039. while (!stream.eof()) {
  1040. ch = stream.next();
  1041. if (ch === TEXT_START) {
  1042. stack++;
  1043. } else if (ch === TEXT_END) {
  1044. stack--;
  1045. if (!stack) {
  1046. stream.start = start;
  1047. return result + stream.substring(offset, stream.pos - 1);
  1048. }
  1049. } else if (ch === ESCAPE) {
  1050. ch = stream.next();
  1051. if (ch === TEXT_START || ch === TEXT_END) {
  1052. result += stream.substring(offset, stream.pos - 2) + String.fromCharCode(ch);
  1053. offset = stream.pos;
  1054. }
  1055. }
  1056. }
  1057. // If we’re here then paired character can’t be consumed
  1058. stream.pos = start;
  1059. throw stream.error(`Unable to find closing ${String.fromCharCode(TEXT_END)} for text start`);
  1060. }
  1061. return null;
  1062. }
  1063. const EXCL = 33; // .
  1064. const DOT = 46; // .
  1065. const EQUALS = 61; // =
  1066. const ATTR_OPEN = 91; // [
  1067. const ATTR_CLOSE = 93; // ]
  1068. const reAttributeName = /^\!?[\w\-:\$@]+\.?$|^\!?\[[\w\-:\$@]+\]\.?$/;
  1069. /**
  1070. * Consumes attributes defined in square braces from given stream.
  1071. * Example:
  1072. * [attr col=3 title="Quoted string" selected. support={react}]
  1073. * @param {StringReader} stream
  1074. * @returns {Array} Array of consumed attributes
  1075. */
  1076. function consumeAttributes(stream) {
  1077. if (!stream.eat(ATTR_OPEN)) {
  1078. return null;
  1079. }
  1080. const result = [];
  1081. let token, attr;
  1082. while (!stream.eof()) {
  1083. stream.eatWhile(isWhiteSpace);
  1084. if (stream.eat(ATTR_CLOSE)) {
  1085. return result; // End of attribute set
  1086. } else if ((token = consumeQuoted(stream)) != null) {
  1087. // Consumed quoted value: anonymous attribute
  1088. result.push({
  1089. name: null,
  1090. value: token
  1091. });
  1092. } else if (eatUnquoted(stream)) {
  1093. // Consumed next word: could be either attribute name or unquoted default value
  1094. token = stream.current();
  1095. // In angular attribute names can be surrounded by []
  1096. if (token[0] === '[' && stream.peek() === ATTR_CLOSE) {
  1097. stream.next();
  1098. token = stream.current();
  1099. }
  1100. if (!reAttributeName.test(token)) {
  1101. // anonymous attribute
  1102. result.push({ name: null, value: token });
  1103. } else {
  1104. // Looks like a regular attribute
  1105. attr = parseAttributeName(token);
  1106. result.push(attr);
  1107. if (stream.eat(EQUALS)) {
  1108. // Explicitly defined value. Could be a word, a quoted string
  1109. // or React-like expression
  1110. if ((token = consumeQuoted(stream)) != null) {
  1111. attr.value = token;
  1112. } else if ((token = consumeText(stream)) != null) {
  1113. attr.value = token;
  1114. attr.options = {
  1115. before: '{',
  1116. after: '}'
  1117. };
  1118. } else if (eatUnquoted(stream)) {
  1119. attr.value = stream.current();
  1120. }
  1121. }
  1122. }
  1123. } else {
  1124. throw stream.error('Expected attribute name');
  1125. }
  1126. }
  1127. throw stream.error('Expected closing "]" brace');
  1128. }
  1129. function parseAttributeName(name) {
  1130. const options = {};
  1131. // If a first character in attribute name is `!` — it’s an implied
  1132. // default attribute
  1133. if (name.charCodeAt(0) === EXCL) {
  1134. name = name.slice(1);
  1135. options.implied = true;
  1136. }
  1137. // Check for last character: if it’s a `.`, user wants boolean attribute
  1138. if (name.charCodeAt(name.length - 1) === DOT) {
  1139. name = name.slice(0, name.length - 1);
  1140. options.boolean = true;
  1141. }
  1142. const attr = { name };
  1143. if (Object.keys(options).length) {
  1144. attr.options = options;
  1145. }
  1146. return attr;
  1147. }
  1148. /**
  1149. * Eats token that can be an unquoted value from given stream
  1150. * @param {StreamReader} stream
  1151. * @return {Boolean}
  1152. */
  1153. function eatUnquoted(stream) {
  1154. const start = stream.pos;
  1155. if (stream.eatWhile(isUnquoted)) {
  1156. stream.start = start;
  1157. return true;
  1158. }
  1159. }
  1160. function isUnquoted(code) {
  1161. return !isSpace(code) && !isQuote(code)
  1162. && code !== ATTR_CLOSE && code !== EQUALS;
  1163. }
  1164. const HASH = 35; // #
  1165. const DOT$1 = 46; // .
  1166. const SLASH = 47; // /
  1167. /**
  1168. * Consumes a single element node from current abbreviation stream
  1169. * @param {StringReader} stream
  1170. * @return {Node}
  1171. */
  1172. function consumeElement(stream) {
  1173. // consume element name, if provided
  1174. const start = stream.pos;
  1175. const node = new Node(eatName(stream));
  1176. let next;
  1177. while (!stream.eof()) {
  1178. if (stream.eat(DOT$1)) {
  1179. node.addClass(eatName(stream));
  1180. } else if (stream.eat(HASH)) {
  1181. node.setAttribute('id', eatName(stream));
  1182. } else if (stream.eat(SLASH)) {
  1183. // A self-closing indicator must be at the end of non-grouping node
  1184. if (node.isGroup) {
  1185. stream.backUp(1);
  1186. throw stream.error('Unexpected self-closing indicator');
  1187. }
  1188. node.selfClosing = true;
  1189. if (next = consumeRepeat(stream)) {
  1190. node.repeat = next;
  1191. }
  1192. break;
  1193. } else if (next = consumeAttributes(stream)) {
  1194. for (let i = 0, il = next.length; i < il; i++) {
  1195. node.setAttribute(next[i]);
  1196. }
  1197. } else if ((next = consumeText(stream)) !== null) {
  1198. node.value = next;
  1199. } else if (next = consumeRepeat(stream)) {
  1200. node.repeat = next;
  1201. } else {
  1202. break;
  1203. }
  1204. }
  1205. if (start === stream.pos) {
  1206. throw stream.error(`Unable to consume abbreviation node, unexpected ${stream.peek()}`);
  1207. }
  1208. return node;
  1209. }
  1210. function eatName(stream) {
  1211. stream.start = stream.pos;
  1212. stream.eatWhile(isName);
  1213. return stream.current();
  1214. }
  1215. function isName(code) {
  1216. return isAlphaNumeric(code)
  1217. || code === 45 /* - */
  1218. || code === 58 /* : */
  1219. || code === 36 /* $ */
  1220. || code === 64 /* @ */
  1221. || code === 33 /* ! */
  1222. || code === 95 /* _ */
  1223. || code === 37 /* % */;
  1224. }
  1225. const GROUP_START = 40; // (
  1226. const GROUP_END = 41; // )
  1227. const OP_SIBLING = 43; // +
  1228. const OP_CHILD = 62; // >
  1229. const OP_CLIMB = 94; // ^
  1230. /**
  1231. * Parses given string into a node tree
  1232. * @param {String} str Abbreviation to parse
  1233. * @return {Node}
  1234. */
  1235. function parse(str) {
  1236. const stream = new StreamReader(str.trim());
  1237. const root = new Node();
  1238. let ctx = root, groupStack = [], ch;
  1239. while (!stream.eof()) {
  1240. ch = stream.peek();
  1241. if (ch === GROUP_START) { // start of group
  1242. // The grouping node should be detached to properly handle
  1243. // out-of-bounds `^` operator. Node will be attached right on group end
  1244. const node = new Node();
  1245. groupStack.push([node, ctx, stream.pos]);
  1246. ctx = node;
  1247. stream.next();
  1248. continue;
  1249. } else if (ch === GROUP_END) { // end of group
  1250. const lastGroup = groupStack.pop();
  1251. if (!lastGroup) {
  1252. throw stream.error('Unexpected ")" group end');
  1253. }
  1254. const node = lastGroup[0];
  1255. ctx = lastGroup[1];
  1256. stream.next();
  1257. // a group can have a repeater
  1258. if (node.repeat = consumeRepeat(stream)) {
  1259. ctx.appendChild(node);
  1260. } else {
  1261. // move all children of group into parent node
  1262. while (node.firstChild) {
  1263. ctx.appendChild(node.firstChild);
  1264. }
  1265. }
  1266. // for convenience, groups can be joined with optional `+` operator
  1267. stream.eat(OP_SIBLING);
  1268. continue;
  1269. }
  1270. const node = consumeElement(stream);
  1271. ctx.appendChild(node);
  1272. if (stream.eof()) {
  1273. break;
  1274. }
  1275. switch (stream.peek()) {
  1276. case OP_SIBLING:
  1277. stream.next();
  1278. continue;
  1279. case OP_CHILD:
  1280. stream.next();
  1281. ctx = node;
  1282. continue;
  1283. case OP_CLIMB:
  1284. // it’s perfectly valid to have multiple `^` operators
  1285. while (stream.eat(OP_CLIMB)) {
  1286. ctx = ctx.parent || ctx;
  1287. }
  1288. continue;
  1289. }
  1290. }
  1291. if (groupStack.length) {
  1292. stream.pos = groupStack.pop()[2];
  1293. throw stream.error('Expected group close');
  1294. }
  1295. return root;
  1296. }
  1297. /**
  1298. * Parses given abbreviation and un-rolls it into a full tree: recursively
  1299. * replaces repeated elements with actual nodes
  1300. * @param {String} abbr
  1301. * @return {Node}
  1302. */
  1303. function index(abbr) {
  1304. const tree = parse(abbr);
  1305. tree.walk(unroll);
  1306. return tree;
  1307. }
  1308. function unroll(node) {
  1309. if (!node.repeat || !node.repeat.count) {
  1310. return;
  1311. }
  1312. const parent = node.parent;
  1313. let ix = parent.children.indexOf(node);
  1314. for (let i = 0; i < node.repeat.count; i++) {
  1315. const clone = node.clone(true);
  1316. clone.repeat.value = i + 1;
  1317. clone.walk(unroll);
  1318. if (clone.isGroup) {
  1319. while (clone.children.length > 0) {
  1320. clone.firstChild.repeat = clone.repeat;
  1321. parent.insertAt(clone.firstChild, ix++);
  1322. }
  1323. } else {
  1324. parent.insertAt(clone, ix++);
  1325. }
  1326. }
  1327. node.parent.removeChild(node);
  1328. }
  1329. /**
  1330. * For every node in given `tree`, finds matching snippet from `registry` and
  1331. * resolves it into a parsed abbreviation. Resolved node is then updated or
  1332. * replaced with matched abbreviation tree.
  1333. *
  1334. * A HTML registry basically contains aliases to another Emmet abbreviations,
  1335. * e.g. a predefined set of name, attribues and so on, possibly a complex
  1336. * abbreviation with multiple elements. So we have to get snippet, parse it
  1337. * and recursively resolve it.
  1338. *
  1339. * @param {Node} tree Parsed Emmet abbreviation
  1340. * @param {SnippetsRegistry} registry Registry with all available snippets
  1341. * @return {Node} Updated tree
  1342. */
  1343. var index$1 = function(tree, registry) {
  1344. tree.walk(node => resolveNode(node, registry));
  1345. return tree;
  1346. };
  1347. function resolveNode(node, registry) {
  1348. const stack = new Set();
  1349. const resolve = node => {
  1350. const snippet = registry.resolve(node.name);
  1351. // A snippet in stack means circular reference.
  1352. // It can be either a user error or a perfectly valid snippet like
  1353. // "img": "img[src alt]/", e.g. an element with predefined shape.
  1354. // In any case, simply stop parsing and keep element as is
  1355. if (!snippet || stack.has(snippet)) {
  1356. return;
  1357. }
  1358. // In case if matched snippet is a function, pass control into it
  1359. if (typeof snippet.value === 'function') {
  1360. return snippet.value(node, registry, resolve);
  1361. }
  1362. const tree = index(snippet.value);
  1363. stack.add(snippet);
  1364. tree.walk(resolve);
  1365. stack.delete(snippet);
  1366. // move current node contents into new tree
  1367. const childTarget = findDeepestNode(tree);
  1368. merge(childTarget, node);
  1369. while (tree.firstChild) {
  1370. node.parent.insertBefore(tree.firstChild, node);
  1371. }
  1372. childTarget.parent.insertBefore(node, childTarget);
  1373. childTarget.remove();
  1374. };
  1375. resolve(node);
  1376. }
  1377. /**
  1378. * Adds data from first node into second node and returns it
  1379. * @param {Node} from
  1380. * @param {Node} to
  1381. * @return {Node}
  1382. */
  1383. function merge(from, to) {
  1384. to.name = from.name;
  1385. if (from.selfClosing) {
  1386. to.selfClosing = true;
  1387. }
  1388. if (from.value != null) {
  1389. to.value = from.value;
  1390. }
  1391. if (from.repeat) {
  1392. to.repeat = Object.assign({}, from.repeat);
  1393. }
  1394. return mergeAttributes(from, to);
  1395. }
  1396. /**
  1397. * Transfer attributes from first element to second one and preserve first
  1398. * element’s attributes order
  1399. * @param {Node} from
  1400. * @param {Node} to
  1401. * @return {Node}
  1402. */
  1403. function mergeAttributes(from, to) {
  1404. mergeClassNames(from, to);
  1405. // It’s important to preserve attributes order: ones in `from` have higher
  1406. // pripority than in `to`. Collect attributes in map in order they should
  1407. // appear in `to`
  1408. const attrMap = new Map();
  1409. let attrs = from.attributes;
  1410. for (let i = 0; i < attrs.length; i++) {
  1411. attrMap.set(attrs[i].name, attrs[i].clone());
  1412. }
  1413. attrs = to.attributes.slice();
  1414. for (let i = 0, attr, a; i < attrs.length; i++) {
  1415. attr = attrs[i];
  1416. if (attrMap.has(attr.name)) {
  1417. a = attrMap.get(attr.name);
  1418. a.value = attr.value;
  1419. // If user explicitly wrote attribute in abbreviation, it’s no longer
  1420. // implied and should be outputted even if value is empty
  1421. if (a.options.implied) {
  1422. a.options.implied = false;
  1423. }
  1424. } else {
  1425. attrMap.set(attr.name, attr);
  1426. }
  1427. to.removeAttribute(attr);
  1428. }
  1429. const newAttrs = Array.from(attrMap.values());
  1430. for (let i = 0; i < newAttrs.length; i++) {
  1431. to.setAttribute(newAttrs[i]);
  1432. }
  1433. return to;
  1434. }
  1435. /**
  1436. * Adds class names from first node to second one
  1437. * @param {Node} from
  1438. * @param {Node} to
  1439. * @return {Node}
  1440. */
  1441. function mergeClassNames(from, to) {
  1442. const classNames = from.classList;
  1443. for (let i = 0; i < classNames.length; i++) {
  1444. to.addClass(classNames[i]);
  1445. }
  1446. return to;
  1447. }
  1448. /**
  1449. * Finds node which is the deepest for in current node or node iteself.
  1450. * @param {Node} node
  1451. * @return {Node}
  1452. */
  1453. function findDeepestNode(node) {
  1454. while (node.children.length) {
  1455. node = node.children[node.children.length - 1];
  1456. }
  1457. return node;
  1458. }
  1459. 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(','));
  1460. const elementMap = {
  1461. p: 'span',
  1462. ul: 'li',
  1463. ol: 'li',
  1464. table: 'tr',
  1465. tr: 'td',
  1466. tbody: 'tr',
  1467. thead: 'tr',
  1468. tfoot: 'tr',
  1469. colgroup: 'col',
  1470. select: 'option',
  1471. optgroup: 'option',
  1472. audio: 'source',
  1473. video: 'source',
  1474. object: 'param',
  1475. map: 'area'
  1476. };
  1477. /**
  1478. * Returns best child node name for given parent node name
  1479. * @param {String} parentName Name of parent node
  1480. * @return {String}
  1481. */
  1482. function resolveImplicitName(parentName) {
  1483. parentName = (parentName || '').toLowerCase();
  1484. return elementMap[parentName]
  1485. || (inlineElements.has(parentName) ? 'span' : 'div');
  1486. }
  1487. /**
  1488. * Adds missing tag names for given tree depending on node’s parent name
  1489. */
  1490. var implicitTags = function(tree) {
  1491. tree.walk(node => {
  1492. // resolve only nameless nodes without content
  1493. if (node.name == null && node.attributes.length) {
  1494. node.name = resolveImplicitName(node.parent.name);
  1495. }
  1496. });
  1497. return tree;
  1498. };
  1499. /**
  1500. * Locates all occurances of given `token` which are not escaped (e.g. are not
  1501. * preceded with `\`) given in `str`
  1502. * @param {String} str
  1503. * @return {Array} Array of token ranges
  1504. */
  1505. function findUnescapedTokens(str, token) {
  1506. const result = new Set();
  1507. const tlen = token.length;
  1508. // 1. Find all occurances of tokens
  1509. let pos = 0;
  1510. while ((pos = str.indexOf(token, pos)) !== -1) {
  1511. result.add(pos);
  1512. pos += tlen;
  1513. }
  1514. if (result.size) {
  1515. // 2. Remove ones that escaped
  1516. let pos = 0;
  1517. const len = str.length;
  1518. while (pos < len) {
  1519. if (str[pos++] === '\\') {
  1520. result.delete(pos++);
  1521. }
  1522. }
  1523. }
  1524. return Array.from(result).map(ix => range(ix, tlen));
  1525. }
  1526. /**
  1527. * Replaces `ranges`, generated by `range()` function, with given `value` in `str`
  1528. * @param {String} str Where to replace ranges
  1529. * @param {Array} ranges Ranes, created by `range()` function
  1530. * @param {String|Function} value Replacement value. If it’s a function, it
  1531. * will take a range value as argument and should return a new string
  1532. * @return {String}
  1533. */
  1534. function replaceRanges(str, ranges, value) {
  1535. // should walk from the end of array to keep ranges valid after replacement
  1536. for (let i = ranges.length - 1; i >= 0; i--) {
  1537. const r = ranges[i];
  1538. let offset = 0;
  1539. let offsetLength = 0;
  1540. if (str.substr(r[0] + r[1], 1) === '@'){
  1541. const matches = str.substr(r[0] + r[1] + 1).match(/^(\d+)/);
  1542. if (matches) {
  1543. offsetLength = matches[1].length + 1;
  1544. offset = parseInt(matches[1]) - 1;
  1545. }
  1546. }
  1547. str = str.substring(0, r[0])
  1548. + (typeof value === 'function' ? value(str.substr(r[0], r[1]), offset) : value)
  1549. + str.substring(r[0] + r[1] + offsetLength);
  1550. }
  1551. return str;
  1552. }
  1553. function range(start, length) {
  1554. return [start, length];
  1555. }
  1556. const numberingToken = '$';
  1557. /**
  1558. * Numbering of expanded abbreviation: finds all nodes with `$` in value
  1559. * or attributes and replaces its occurances with repeater value
  1560. */
  1561. var applyNumbering = function(tree) {
  1562. tree.walk(applyNumbering$1);
  1563. return tree;
  1564. };
  1565. /**
  1566. * Applies numbering for given node: replaces occurances of numbering token
  1567. * in node’s name, content and attributes
  1568. * @param {Node} node
  1569. * @return {Node}
  1570. */
  1571. function applyNumbering$1(node) {
  1572. const repeater = findRepeater(node);
  1573. if (repeater && repeater.value != null) {
  1574. // NB replace numbering in nodes with explicit repeater only:
  1575. // it solves issues with abbreviations like `xsl:if[test=$foo]` where
  1576. // `$foo` is preferred output
  1577. const value = repeater.value;
  1578. node.name = replaceNumbering(node.name, value);
  1579. node.value = replaceNumbering(node.value, value);
  1580. node.attributes.forEach(attr => {
  1581. const copy = node.getAttribute(attr.name).clone();
  1582. copy.name = replaceNumbering(attr.name, value);
  1583. copy.value = replaceNumbering(attr.value, value);
  1584. node.replaceAttribute(attr.name, copy);
  1585. });
  1586. }
  1587. return node;
  1588. }
  1589. /**
  1590. * Returns repeater object for given node
  1591. * @param {Node} node
  1592. * @return {Object}
  1593. */
  1594. function findRepeater(node) {
  1595. while (node) {
  1596. if (node.repeat) {
  1597. return node.repeat;
  1598. }
  1599. node = node.parent;
  1600. }
  1601. }
  1602. /**
  1603. * Replaces numbering in given string
  1604. * @param {String} str
  1605. * @param {Number} value
  1606. * @return {String}
  1607. */
  1608. function replaceNumbering(str, value) {
  1609. // replace numbering in strings only: skip explicit wrappers that could
  1610. // contain unescaped numbering tokens
  1611. if (typeof str === 'string') {
  1612. const ranges = getNumberingRanges(str);
  1613. return replaceNumberingRanges(str, ranges, value);
  1614. }
  1615. return str;
  1616. }
  1617. /**
  1618. * Returns numbering ranges, e.g. ranges of `$` occurances, in given string.
  1619. * Multiple adjacent ranges are combined
  1620. * @param {String} str
  1621. * @return {Array}
  1622. */
  1623. function getNumberingRanges(str) {
  1624. return findUnescapedTokens(str || '', numberingToken)
  1625. .reduce((out, range$$1) => {
  1626. // skip ranges that actually belongs to output placeholder or tabstops
  1627. if (!/[#{]/.test(str[range$$1[0] + 1] || '')) {
  1628. const lastRange = out[out.length - 1];
  1629. if (lastRange && lastRange[0] + lastRange[1] === range$$1[0]) {
  1630. lastRange[1] += range$$1[1];
  1631. } else {
  1632. out.push(range$$1);
  1633. }
  1634. }
  1635. return out;
  1636. }, []);
  1637. }
  1638. /**
  1639. * @param {String} str
  1640. * @param {Array} ranges
  1641. * @param {Number} value
  1642. * @return {String}
  1643. */
  1644. function replaceNumberingRanges(str, ranges, value) {
  1645. const replaced = replaceRanges(str, ranges, (token, offset) => {
  1646. let _value = String(value + offset);
  1647. // pad values for multiple numbering tokens, e.g. 3 for $$$ becomes 003
  1648. while (_value.length < token.length) {
  1649. _value = '0' + _value;
  1650. }
  1651. return _value;
  1652. });
  1653. // unescape screened numbering tokens
  1654. return unescapeString(replaced);
  1655. }
  1656. /**
  1657. * Unescapes characters, screened with `\`, in given string
  1658. * @param {String} str
  1659. * @return {String}
  1660. */
  1661. function unescapeString(str) {
  1662. let i = 0, result = '';
  1663. const len = str.length;
  1664. while (i < len) {
  1665. const ch = str[i++];
  1666. result += (ch === '\\') ? (str[i++] || '') : ch;
  1667. }
  1668. return result;
  1669. }
  1670. /** Placeholder for inserted content */
  1671. const placeholder = '$#';
  1672. /** Placeholder for caret */
  1673. const caret = '|';
  1674. const reUrl = /^((?:https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
  1675. const reEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
  1676. const reProto = /^([a-z]+:)?\/\//i;
  1677. /**
  1678. * Inserts content into node with implicit repeat count: this node is then
  1679. * duplicated for each content item and content itself is inserted either into
  1680. * deepest child or instead of a special token.
  1681. *
  1682. * This method uses two distinct steps: `prepare()` and `insert()` since most
  1683. * likely these steps will be used separately to properly insert content
  1684. * with unescaped `$` numbering markers.
  1685. *
  1686. * @param {Node} tree Parsed abbreviation
  1687. * @param {String[]} content Array of content items to insert
  1688. * @return {Node}
  1689. */
  1690. /**
  1691. * Finds nodes with implicit repeat and creates `amount` copies of it in tree
  1692. * @param {Node} tree
  1693. * @param {Number} amount
  1694. * @return {Node}
  1695. */
  1696. function prepare(tree, amount) {
  1697. amount = amount || 1;
  1698. tree.walk(node => {
  1699. if (node.repeat && node.repeat.count === null) {
  1700. for (let i = 0; i < amount; i++) {
  1701. const clone = node.clone(true);
  1702. clone.repeat.implicit = true;
  1703. clone.repeat.count = amount;
  1704. clone.repeat.value = i + 1;
  1705. clone.repeat.index = i;
  1706. node.parent.insertBefore(clone, node);
  1707. }
  1708. node.remove();
  1709. }
  1710. });
  1711. return tree;
  1712. }
  1713. /**
  1714. * Inserts content into implicitly repeated nodes, created by `prepare()` method
  1715. * @param {Node} tree
  1716. * @param {String[]} content
  1717. * @return {Node}
  1718. */
  1719. function insert(tree, content) {
  1720. if (Array.isArray(content) && content.length) {
  1721. let updated = false;
  1722. tree.walk(node => {
  1723. if (node.repeat && node.repeat.implicit) {
  1724. updated = true;
  1725. insertContent(node, content[node.repeat.index]);
  1726. }
  1727. });
  1728. if (!updated) {
  1729. // no node with implicit repeat was found, insert content as
  1730. // deepest child
  1731. setNodeContent(findDeepestNode$1(tree), content.join('\n'));
  1732. }
  1733. }
  1734. return tree;
  1735. }
  1736. /**
  1737. * Inserts `content` into given `node`: either replaces output placeholders
  1738. * or inserts it into deepest child node
  1739. * @param {Node} node
  1740. * @param {String} content
  1741. * @return {Node}
  1742. */
  1743. function insertContent(node, content) {
  1744. let inserted = insertContentIntoPlaceholder(node, content);
  1745. node.walk(child => inserted |= insertContentIntoPlaceholder(child, content));
  1746. if (!inserted) {
  1747. // no placeholders were found in node, insert content into deepest child
  1748. setNodeContent(findDeepestNode$1(node), content);
  1749. }
  1750. return node;
  1751. }
  1752. /**
  1753. * Inserts given `content` into placeholders for given `node`. Placeholders
  1754. * might be available in attribute values and node content
  1755. * @param {Node} node
  1756. * @param {String} content
  1757. * @return {Boolean} Returns `true` if placeholders were found and replaced in node
  1758. */
  1759. function insertContentIntoPlaceholder(node, content) {
  1760. const state = {replaced: false};
  1761. node.value = replacePlaceholder(node.value, content, state);
  1762. node.attributes.forEach(attr => {
  1763. if (attr.value) {
  1764. node.setAttribute(attr.name, replacePlaceholder(attr.value, content, state));
  1765. }
  1766. });
  1767. return state.replaced;
  1768. }
  1769. /**
  1770. * Replaces all placeholder occurances in given `str` with `value`
  1771. * @param {String} str
  1772. * @param {String} value
  1773. * @param {Object} [_state] If provided, set `replaced` property of given
  1774. * object to `true` if placeholder was found and replaced
  1775. * @return {String}
  1776. */
  1777. function replacePlaceholder(str, value, _state) {
  1778. if (typeof str === 'string') {
  1779. const ranges = findUnescapedTokens(str, placeholder);
  1780. if (ranges.length) {
  1781. if (_state) {
  1782. _state.replaced = true;
  1783. }
  1784. str = replaceRanges(str, ranges, value);
  1785. }
  1786. }
  1787. return str;
  1788. }
  1789. /**
  1790. * Finds node which is the deepest for in current node or node iteself.
  1791. * @param {Node} node
  1792. * @return {Node}
  1793. */
  1794. function findDeepestNode$1(node) {
  1795. while (node.children.length) {
  1796. node = node.children[node.children.length - 1];
  1797. }
  1798. return node;
  1799. }
  1800. /**
  1801. * Updates content of given node
  1802. * @param {Node} node
  1803. * @param {String} content
  1804. */
  1805. function setNodeContent(node, content) {
  1806. // find caret position and replace it with content, if possible
  1807. if (node.value) {
  1808. const ranges = findUnescapedTokens(node.value, caret);
  1809. if (ranges.length) {
  1810. node.value = replaceRanges(node.value, ranges, content);
  1811. return;
  1812. }
  1813. }
  1814. if (node.name.toLowerCase() === 'a' || node.hasAttribute('href')) {
  1815. // special case: inserting content into `<a>` tag
  1816. if (reUrl.test(content)) {
  1817. node.setAttribute('href', (reProto.test(content) ? '' : 'http://') + content);
  1818. } else if (reEmail.test(content)) {
  1819. node.setAttribute('href', 'mailto:' + content);
  1820. }
  1821. }
  1822. node.value = content;
  1823. }
  1824. const defaultOptions$2 = {
  1825. element: '__',
  1826. modifier: '_'
  1827. };
  1828. const reElement = /^(-+)([a-z0-9]+[a-z0-9-]*)/i;
  1829. const reModifier = /^(_+)([a-z0-9]+[a-z0-9-]*)/i;
  1830. const blockCandidates1 = className => /^[a-z]\-/i.test(className);
  1831. const blockCandidates2 = className => /^[a-z]/i.test(className);
  1832. /**
  1833. * BEM transformer: updates class names written as `-element` and
  1834. * `_modifier` into full class names as described in BEM specs. Also adds missing
  1835. * class names: fir example, if node contains `.block_modifier` class, ensures
  1836. * that element contains `.block` class as well
  1837. */
  1838. var bem = function(tree, options) {
  1839. options = Object.assign({}, defaultOptions$2, options);
  1840. tree.walk(node => expandClassNames(node, options));
  1841. const lookup = createBlockLookup(tree);
  1842. tree.walk(node => expandShortNotation(node, lookup, options));
  1843. return tree;
  1844. };
  1845. /**
  1846. * Expands existing class names in BEM notation in given `node`.
  1847. * For example, if node contains `b__el_mod` class name, this method ensures
  1848. * that element contains `b__el` class as well
  1849. * @param {Node} node
  1850. * @param {Object} options
  1851. * @return {Set}
  1852. */
  1853. function expandClassNames(node, options) {
  1854. const classNames = node.classList.reduce((out, cl) => {
  1855. // remove all modifiers and element prefixes from class name to get a base element name
  1856. const ix = cl.indexOf('_');
  1857. if (ix > 0 && !cl.startsWith('-')) {
  1858. out.add(cl.slice(0, ix));
  1859. out.add(cl.slice(ix));
  1860. return out;
  1861. }
  1862. return out.add(cl);
  1863. }, new Set());
  1864. if (classNames.size) {
  1865. node.setAttribute('class', Array.from(classNames).join(' '));
  1866. }
  1867. }
  1868. /**
  1869. * Expands short BEM notation, e.g. `-element` and `_modifier`
  1870. * @param {Node} node Parsed Emmet abbreviation node
  1871. * @param {Map} lookup BEM block name lookup
  1872. * @param {Object} options
  1873. */
  1874. function expandShortNotation(node, lookup, options) {
  1875. const classNames = node.classList.reduce((out, cl) => {
  1876. let prefix, m;
  1877. const originalClass = cl;
  1878. // parse element definition (could be only one)
  1879. if (m = cl.match(reElement)) {
  1880. prefix = getBlockName(node, lookup, m[1]) + options.element + m[2];
  1881. out.add(prefix);
  1882. cl = cl.slice(m[0].length);
  1883. }
  1884. // parse modifiers definitions (may contain multiple)
  1885. while (m = cl.match(reModifier)) {
  1886. if (!prefix) {
  1887. prefix = getBlockName(node, lookup, m[1]);
  1888. out.add(prefix);
  1889. }
  1890. out.add(`${prefix}${options.modifier}${m[2]}`);
  1891. cl = cl.slice(m[0].length);
  1892. }
  1893. if (cl === originalClass) {
  1894. // class name wasn’t modified: it’s not a BEM-specific class,
  1895. // add it as-is into output
  1896. out.add(originalClass);
  1897. }
  1898. return out;
  1899. }, new Set());
  1900. const arrClassNames = Array.from(classNames).filter(Boolean);
  1901. if (arrClassNames.length) {
  1902. node.setAttribute('class', arrClassNames.join(' '));
  1903. }
  1904. }
  1905. /**
  1906. * Creates block name lookup for each node in given tree, e.g. finds block
  1907. * name explicitly for each node
  1908. * @param {Node} tree
  1909. * @return {Map}
  1910. */
  1911. function createBlockLookup(tree) {
  1912. const lookup = new Map();
  1913. tree.walk(node => {
  1914. const classNames = node.classList;
  1915. if (classNames.length) {
  1916. // guess best block name from class or use parent’s block name
  1917. lookup.set(node,
  1918. find(classNames, blockCandidates1)
  1919. || find(classNames, blockCandidates2)
  1920. || lookup.get(node.parent)
  1921. );
  1922. }
  1923. });
  1924. return lookup;
  1925. }
  1926. /**
  1927. * Returns block name for given `node` by `prefix`, which tells the depth of
  1928. * of parent node lookup
  1929. * @param {Node} node
  1930. * @param {Map} lookup
  1931. * @param {String} prefix
  1932. * @return {String}
  1933. */
  1934. function getBlockName(node, lookup, prefix) {
  1935. let depth = prefix.length > 1 ? prefix.length : 0;
  1936. // NB don’t walk up to root node, stay at first root child in case of
  1937. // too deep prefix
  1938. while (node.parent && node.parent.parent && depth--) {
  1939. node = node.parent;
  1940. }
  1941. return lookup.get(node) || '';
  1942. }
  1943. function find(arr, filter) {
  1944. return arr.filter(filter)[0];
  1945. }
  1946. /**
  1947. * JSX transformer: replaces `class` and `for` attributes with `className` and
  1948. * `htmlFor` attributes respectively
  1949. */
  1950. var jsx = function(tree) {
  1951. tree.walk(node => {
  1952. replace(node, 'class', 'className');
  1953. replace(node, 'for', 'htmlFor');
  1954. });
  1955. return tree;
  1956. };
  1957. function replace(node, oldName, newName) {
  1958. let attr = node.getAttribute(oldName);
  1959. if (attr) {
  1960. attr.name = newName;
  1961. }
  1962. }
  1963. const reSupporterNames = /^xsl:(variable|with\-param)$/i;
  1964. /**
  1965. * XSL transformer: removes `select` attributes from certain nodes that contain
  1966. * children
  1967. */
  1968. var xsl = function(tree) {
  1969. tree.walk(node => {
  1970. if (reSupporterNames.test(node.name || '') && (node.children.length || node.value)) {
  1971. node.removeAttribute('select');
  1972. }
  1973. });
  1974. return tree;
  1975. };
  1976. const supportedAddons = { bem, jsx, xsl };
  1977. /**
  1978. * Runs additional transforms on given tree.
  1979. * These transforms may introduce side-effects and unexpected result
  1980. * so they are not applied by default, authors must specify which addons
  1981. * in `addons` argument as `{addonName: addonOptions}`
  1982. * @param {Node} tree Parsed Emmet abbreviation
  1983. * @param {Object} addons Add-ons to apply and their options
  1984. */
  1985. var addons = function(tree, addons) {
  1986. Object.keys(addons || {}).forEach(key => {
  1987. if (key in supportedAddons) {
  1988. const addonOpt = typeof addons[key] === 'object' ? addons[key] : null;
  1989. tree = tree.use(supportedAddons[key], addonOpt);
  1990. }
  1991. });
  1992. return tree;
  1993. };
  1994. /**
  1995. * Applies basic HTML-specific transformations for given parsed abbreviation:
  1996. * – resolve implied tag names
  1997. * – insert repeated content
  1998. * – resolve node numbering
  1999. */
  2000. var index$2 = function(tree, content, appliedAddons) {
  2001. if (typeof content === 'string') {
  2002. content = [content];
  2003. } else if (content && typeof content === 'object' && !Array.isArray(content)) {
  2004. appliedAddons = content;
  2005. content = null;
  2006. }
  2007. return tree
  2008. .use(implicitTags)
  2009. .use(prepare, Array.isArray(content) ? content.length : null)
  2010. .use(applyNumbering)
  2011. .use(insert, content)
  2012. .use(addons, appliedAddons);
  2013. };
  2014. /**
  2015. * Replaces all unescaped ${variable} occurances in given parsed abbreviation
  2016. * `tree` with values provided in `variables` hash. Precede `$` with `\` to
  2017. * escape it and skip replacement
  2018. * @param {Node} tree Parsed abbreviation tree
  2019. * @param {Object} variables Variables values
  2020. * @return {Node}
  2021. */
  2022. function replaceVariables(tree, variables) {
  2023. variables = variables || {};
  2024. tree.walk(node => replaceInNode(node, variables));
  2025. return tree;
  2026. }
  2027. function replaceInNode(node, variables) {
  2028. // Replace variables in attributes.
  2029. const attrs = node.attributes;
  2030. for (let i = 0, il = attrs.length; i < il; i++) {
  2031. const attr = attrs[i];
  2032. if (typeof attr.value === 'string') {
  2033. node.setAttribute(attr.name, replaceInString(attr.value, variables));
  2034. }
  2035. }
  2036. if (node.value != null) {
  2037. node.value = replaceInString(node.value, variables);
  2038. }
  2039. return node;
  2040. }
  2041. /**
  2042. * Replaces all unescaped `${variable}` occurances in given string with values
  2043. * from `variables` object
  2044. * @param {String} string
  2045. * @param {Object} variables
  2046. * @return {String}
  2047. */
  2048. function replaceInString(string, variables) {
  2049. const model = createModel(string);
  2050. let offset = 0;
  2051. let output = '';
  2052. for (let i = 0, il = model.variables.length; i < il; i++) {
  2053. const v = model.variables[i];
  2054. let value = v.name in variables ? variables[v.name] : v.name;
  2055. if (typeof value === 'function') {
  2056. value = value(model.string, v, offset + v.location);
  2057. }
  2058. output += model.string.slice(offset, v.location) + value;
  2059. offset = v.location + v.length;
  2060. }
  2061. return output + model.string.slice(offset);
  2062. }
  2063. /**
  2064. * Creates variable model from given string. The model contains a `string` with
  2065. * all escaped variable tokens written without escape symbol and `variables`
  2066. * property with all unescaped variables and their ranges
  2067. * @param {String} string
  2068. * @return {Object}
  2069. */
  2070. function createModel(string) {
  2071. const reVariable = /\$\{([a-z][\w\-]*)\}/ig;
  2072. const escapeCharCode = 92; // `\` symbol
  2073. const variables = [];
  2074. // We have to replace unescaped (e.g. not preceded with `\`) tokens.
  2075. // Instead of writing a stream parser, we’ll cut some edges here:
  2076. // 1. Find all tokens
  2077. // 2. Walk string char-by-char and resolve only tokens that are not escaped
  2078. const tokens = new Map();
  2079. let m;
  2080. while (m = reVariable.exec(string)) {
  2081. tokens.set(m.index, m);
  2082. }
  2083. if (tokens.size) {
  2084. let start = 0, pos = 0, len = string.length;
  2085. let output = '';
  2086. while (pos < len) {
  2087. if (string.charCodeAt(pos) === escapeCharCode && tokens.has(pos + 1)) {
  2088. // Found escape symbol that escapes variable: we should
  2089. // omit this symbol in output string and skip variable
  2090. const token = tokens.get(pos + 1);
  2091. output += string.slice(start, pos) + token[0];
  2092. start = pos = token.index + token[0].length;
  2093. tokens.delete(pos + 1);
  2094. continue;
  2095. }
  2096. pos++;
  2097. }
  2098. string = output + string.slice(start);
  2099. // Not using `.map()` here to reduce memory allocations
  2100. const validMatches = Array.from(tokens.values());
  2101. for (let i = 0, il = validMatches.length; i < il; i++) {
  2102. const token = validMatches[i];
  2103. variables.push({
  2104. name: token[1],
  2105. location: token.index,
  2106. length: token[0].length
  2107. });
  2108. }
  2109. }
  2110. return {string, variables};
  2111. }
  2112. const DOLLAR = 36; // $
  2113. const COLON = 58; // :
  2114. const ESCAPE$1 = 92; // \
  2115. const OPEN_BRACE = 123; // {
  2116. const CLOSE_BRACE = 125; // }
  2117. /**
  2118. * Finds fields in given string and returns object with field-less string
  2119. * and array of fileds found
  2120. * @param {String} string
  2121. * @return {Object}
  2122. */
  2123. function parse$2(string) {
  2124. const stream = new StreamReader(string);
  2125. const fields = [];
  2126. let cleanString = '', offset = 0, pos = 0;
  2127. let code, field;
  2128. while (!stream.eof()) {
  2129. code = stream.peek();
  2130. pos = stream.pos;
  2131. if (code === ESCAPE$1) {
  2132. stream.next();
  2133. stream.next();
  2134. } else if (field = consumeField(stream, cleanString.length + pos - offset)) {
  2135. fields.push(field);
  2136. cleanString += stream.string.slice(offset, pos) + field.placeholder;
  2137. offset = stream.pos;
  2138. } else {
  2139. stream.next();
  2140. }
  2141. }
  2142. return new FieldString(cleanString + stream.string.slice(offset), fields);
  2143. }
  2144. /**
  2145. * Marks given `string` with `fields`: wraps each field range with
  2146. * `${index:placeholder}` (by default) or any other token produced by `token`
  2147. * function, if provided
  2148. * @param {String} string String to mark
  2149. * @param {Array} fields Array of field descriptor. A field descriptor is a
  2150. * `{index, location, length}` array. It is important that fields in array
  2151. * must be ordered by their location in string: some fields my refer the same
  2152. * location so they must appear in order that user expects.
  2153. * @param {Function} [token] Function that generates field token. This function
  2154. * received two arguments: `index` and `placeholder` and should return string
  2155. * @return {String} String with marked fields
  2156. */
  2157. function mark(string, fields, token) {
  2158. token = token || createToken;
  2159. // order fields by their location and appearence
  2160. // NB field ranges should not overlap! (not supported yet)
  2161. const ordered = fields
  2162. .map((field, order) => ({order, field, end: field.location + field.length}))
  2163. .sort((a, b) => (a.end - b.end) || (a.order - b.order));
  2164. // mark ranges in string
  2165. let offset = 0;
  2166. const result = ordered.map(item => {
  2167. const placeholder = string.substr(item.field.location, item.field.length);
  2168. const prefix = string.slice(offset, item.field.location);
  2169. offset = item.end;
  2170. return prefix + token(item.field.index, placeholder);
  2171. });
  2172. return result.join('') + string.slice(offset);
  2173. }
  2174. /**
  2175. * Creates field token for string
  2176. * @param {Number} index Field index
  2177. * @param {String} placeholder Field placeholder, could be empty string
  2178. * @return {String}
  2179. */
  2180. function createToken(index, placeholder) {
  2181. return placeholder ? `\${${index}:${placeholder}}` : `\${${index}}`;
  2182. }
  2183. /**
  2184. * Consumes field from current stream position: it can be an `$index` or
  2185. * or `${index}` or `${index:placeholder}`
  2186. * @param {StreamReader} stream
  2187. * @param {Number} location Field location in *clean* string
  2188. * @return {Object} Object with `index` and `placeholder` properties if
  2189. * fieald was successfully consumed, `null` otherwise
  2190. */
  2191. function consumeField(stream, location) {
  2192. const start = stream.pos;
  2193. if (stream.eat(DOLLAR)) {
  2194. // Possible start of field
  2195. let index = consumeIndex(stream);
  2196. let placeholder = '';
  2197. // consumed $index placeholder
  2198. if (index != null) {
  2199. return new Field(index, placeholder, location);
  2200. }
  2201. if (stream.eat(OPEN_BRACE)) {
  2202. index = consumeIndex(stream);
  2203. if (index != null) {
  2204. if (stream.eat(COLON)) {
  2205. placeholder = consumePlaceholder(stream);
  2206. }
  2207. if (stream.eat(CLOSE_BRACE)) {
  2208. return new Field(index, placeholder, location);
  2209. }
  2210. }
  2211. }
  2212. }
  2213. // If we reached here then there’s no valid field here, revert
  2214. // back to starting position
  2215. stream.pos = start;
  2216. }
  2217. /**
  2218. * Consumes a placeholder: value right after `:` in field. Could be empty
  2219. * @param {StreamReader} stream
  2220. * @return {String}
  2221. */
  2222. function consumePlaceholder(stream) {
  2223. let code;
  2224. const stack = [];
  2225. stream.start = stream.pos;
  2226. while (!stream.eof()) {
  2227. code = stream.peek();
  2228. if (code === OPEN_BRACE) {
  2229. stack.push(stream.pos);
  2230. } else if (code === CLOSE_BRACE) {
  2231. if (!stack.length) {
  2232. break;
  2233. }
  2234. stack.pop();
  2235. }
  2236. stream.next();
  2237. }
  2238. if (stack.length) {
  2239. throw stream.error('Unable to find matching "}" for curly brace at ' + stack.pop());
  2240. }
  2241. return stream.current();
  2242. }
  2243. /**
  2244. * Consumes integer from current stream position
  2245. * @param {StreamReader} stream
  2246. * @return {Number}
  2247. */
  2248. function consumeIndex(stream) {
  2249. stream.start = stream.pos;
  2250. if (stream.eatWhile(isNumber)) {
  2251. return Number(stream.current());
  2252. }
  2253. }
  2254. class Field {
  2255. constructor(index, placeholder, location) {
  2256. this.index = index;
  2257. this.placeholder = placeholder;
  2258. this.location = location;
  2259. this.length = this.placeholder.length;
  2260. }
  2261. }
  2262. class FieldString {
  2263. /**
  2264. * @param {String} string
  2265. * @param {Field[]} fields
  2266. */
  2267. constructor(string, fields) {
  2268. this.string = string;
  2269. this.fields = fields;
  2270. }
  2271. mark(token) {
  2272. return mark(this.string, this.fields, token);
  2273. }
  2274. toString() {
  2275. return string;
  2276. }
  2277. }
  2278. const defaultFieldsRenderer = text => text;
  2279. /**
  2280. * Output node is an object containing generated output for given Emmet
  2281. * abbreviation node. Output node can be passed to various processors that
  2282. * may shape-up final node output. The final output is simply a concatenation
  2283. * of `.open`, `.text` and `.close` properties and its `.before*` and `.after*`
  2284. * satellites
  2285. * @param {Node} node Parsed Emmet abbreviation node
  2286. * @param {Function} fieldsRenderer A function for rendering fielded text (text with
  2287. * tabstops) for current node. @see ./render.js for details
  2288. */
  2289. class OutputNode {
  2290. constructor(node, fieldsRenderer, options) {
  2291. if (typeof fieldsRenderer === 'object') {
  2292. options = fieldsRenderer;
  2293. fieldsRenderer = null;
  2294. }
  2295. this.node = node;
  2296. this._fieldsRenderer = fieldsRenderer || defaultFieldsRenderer;
  2297. this.open = null;
  2298. this.beforeOpen = '';
  2299. this.afterOpen = '';
  2300. this.close = null;
  2301. this.beforeClose = '';
  2302. this.afterClose = '';
  2303. this.text = null;
  2304. this.beforeText = '';
  2305. this.afterText = '';
  2306. this.indent = '';
  2307. this.newline = '';
  2308. if (options) {
  2309. Object.assign(this, options);
  2310. }
  2311. }
  2312. clone() {
  2313. return new this.constructor(this.node, this);
  2314. }
  2315. /**
  2316. * Properly indents given multiline text
  2317. * @param {String} text
  2318. */
  2319. indentText(text) {
  2320. const lines = splitByLines(text);
  2321. if (lines.length === 1) {
  2322. // no newlines, nothing to indent
  2323. return text;
  2324. }
  2325. // No newline and no indent means no formatting at all:
  2326. // in this case we should replace newlines with spaces
  2327. const nl = (!this.newline && !this.indent) ? ' ' : this.newline;
  2328. return lines.map((line, i) => i ? this.indent + line : line).join(nl);
  2329. }
  2330. /**
  2331. * Renders given text with fields
  2332. * @param {String} text
  2333. * @return {String}
  2334. */
  2335. renderFields(text) {
  2336. return this._fieldsRenderer(text);
  2337. }
  2338. toString(children) {
  2339. const open = this._wrap(this.open, this.beforeOpen, this.afterOpen);
  2340. const close = this._wrap(this.close, this.beforeClose, this.afterClose);
  2341. const text = this._wrap(this.text, this.beforeText, this.afterText);
  2342. return open + text + (children != null ? children : '') + close;
  2343. }
  2344. _wrap(str, before, after) {
  2345. before = before != null ? before : '';
  2346. after = after != null ? after : '';
  2347. // automatically trim whitespace for non-empty wraps
  2348. if (str != null) {
  2349. str = before ? str.replace(/^\s+/, '') : str;
  2350. str = after ? str.replace(/\s+$/, '') : str;
  2351. return before + this.indentText(str) + after;
  2352. }
  2353. return '';
  2354. }
  2355. }
  2356. /**
  2357. * Splits given text by lines
  2358. * @param {String} text
  2359. * @return {String[]}
  2360. */
  2361. function splitByLines(text) {
  2362. return (text || '').split(/\r\n|\r|\n/g);
  2363. }
  2364. /**
  2365. * Default output of field (tabstop)
  2366. * @param {Number} index Field index
  2367. * @param {String} placeholder Field placeholder, can be null
  2368. * @return {String}
  2369. */
  2370. const defaultField = (index, placeholder) => (placeholder || '');
  2371. /**
  2372. * Renders given parsed abbreviation `tree` via `formatter` function.
  2373. * @param {Node} tree Parsed Emmet abbreviation
  2374. * @param {Function} [field] Optional function to format field/tabstop (@see `defaultField`)
  2375. * @param {Function} formatter Output formatter function. It takes an output node—
  2376. * a special wrapper for parsed node that holds formatting and output properties—
  2377. * and updates its output properties to shape-up node’s output.
  2378. * Function arguments:
  2379. * – `outNode`: OutputNode
  2380. * – `renderFields`: a helper function that parses fields/tabstops from given
  2381. * text and replaces them with `field` function output.
  2382. * It also takes care about field indicies and ensures that the same indicies
  2383. * from different nodes won’t collide
  2384. */
  2385. function render(tree, field, formatter) {
  2386. if (typeof formatter === 'undefined') {
  2387. formatter = field;
  2388. field = null;
  2389. }
  2390. field = field || defaultField;
  2391. // Each node may contain fields like `${1:placeholder}`.
  2392. // Since most modern editors will link all fields with the same
  2393. // index, we have to ensure that different nodes has their own indicies.
  2394. // We’ll use this `fieldState` object to globally increment field indices
  2395. // during output
  2396. const fieldState = { index: 1 };
  2397. const fieldsRenderer = text => text == null
  2398. ? field(fieldState.index++)
  2399. : getFieldsModel(text, fieldState).mark(field);
  2400. return run(tree.children, formatter, fieldsRenderer);
  2401. }
  2402. function run(nodes, formatter, fieldsRenderer) {
  2403. return nodes.map(node => {
  2404. const outNode = formatter(new OutputNode(node, fieldsRenderer));
  2405. return outNode ? outNode.toString(run(node.children, formatter, fieldsRenderer)) : '';
  2406. }).join('');
  2407. }
  2408. /**
  2409. * Returns fields (tab-stops) model with properly updated indices that won’t
  2410. * collide with fields in other nodes of foprmatted tree
  2411. * @param {String|Object} text Text to get fields model from or model itself
  2412. * @param {Object} fieldState Abbreviation tree-wide field state reference
  2413. * @return {Object} Field model
  2414. */
  2415. function getFieldsModel(text, fieldState) {
  2416. const model = typeof text === 'object' ? text : parse$2(text);
  2417. let largestIndex = -1;
  2418. model.fields.forEach(field => {
  2419. field.index += fieldState.index;
  2420. if (field.index > largestIndex) {
  2421. largestIndex = field.index;
  2422. }
  2423. });
  2424. if (largestIndex !== -1) {
  2425. fieldState.index = largestIndex + 1;
  2426. }
  2427. return model;
  2428. }
  2429. const TOKEN = /^(.*?)([A-Z_]+)(.*?)$/;
  2430. const TOKEN_OPEN = 91; // [
  2431. const TOKEN_CLOSE = 93; // ]
  2432. /**
  2433. * A basic templating engine.
  2434. * Takes every `[TOKEN]` from given string and replaces it with
  2435. * `TOKEN` value from given `data` attribute. The token itself may contain
  2436. * various characters between `[`, token name and `]`. Contents of `[...]` will
  2437. * be outputted only if `TOKEN` value is not empty. Also, only `TOKEN` name will
  2438. * be replaced with actual value, all other characters will remain as is.
  2439. *
  2440. * Example:
  2441. * ```
  2442. * template('[<NAME>]', {NAME: 'foo'}) -> "<foo>"
  2443. * template('[<NAME>]', {}) -> ""
  2444. * ```
  2445. */
  2446. function template(str, data) {
  2447. if (str == null) {
  2448. return str;
  2449. }
  2450. // NB since token may contain inner `[` and `]`, we can’t just use regexp
  2451. // for replacement, should manually parse string instead
  2452. const stack = [];
  2453. const replacer = (str, left, token, right) =>
  2454. data[token] != null ? left + data[token] + right : '';
  2455. let output = '';
  2456. let offset = 0, i = 0;
  2457. let code, lastPos;
  2458. while (i < str.length) {
  2459. code = str.charCodeAt(i);
  2460. if (code === TOKEN_OPEN) {
  2461. stack.push(i);
  2462. } else if (code === TOKEN_CLOSE) {
  2463. lastPos = stack.pop();
  2464. if (!stack.length) {
  2465. output += str.slice(offset, lastPos) +
  2466. str.slice(lastPos + 1, i).replace(TOKEN, replacer);
  2467. offset = i + 1;
  2468. }
  2469. }
  2470. i++;
  2471. }
  2472. return output + str.slice(offset);
  2473. }
  2474. /**
  2475. * Various utility methods used by formatters
  2476. */
  2477. /**
  2478. * Splits given text by lines
  2479. * @param {String} text
  2480. * @return {String[]}
  2481. */
  2482. function splitByLines$1(text) {
  2483. return (text || '').split(/\r\n|\r|\n/g);
  2484. }
  2485. /**
  2486. * Check if given node is a first child in its parent
  2487. * @param {Node} node
  2488. * @return {Boolean}
  2489. */
  2490. function isFirstChild(node) {
  2491. return node.parent.firstChild === node;
  2492. }
  2493. /**
  2494. * Check if given node is a last child in its parent node
  2495. * @param {Node} node
  2496. * @return {Boolean}
  2497. */
  2498. /**
  2499. * Check if given node is a root node
  2500. * @param {Node} node
  2501. * @return {Boolean}
  2502. */
  2503. function isRoot(node) {
  2504. return node && !node.parent;
  2505. }
  2506. /**
  2507. * Check if given node is a pseudo-snippet: a text-only node with explicitly
  2508. * defined children
  2509. * @param {Node} node
  2510. * @return {Boolean}
  2511. */
  2512. function isPseudoSnippet(node) {
  2513. return node.isTextOnly && !!node.children.length;
  2514. }
  2515. /**
  2516. * Handles pseudo-snippet node.
  2517. * A pseudo-snippet is a text-only node with explicitly defined children.
  2518. * For such case, we have to figure out if pseudo-snippet contains fields
  2519. * (tab-stops) in node value and “split” it: make contents before field with
  2520. * lowest index node’s “open” part and contents after lowest index — “close”
  2521. * part. With this trick a final output will look like node’s children
  2522. * are nested inside node value
  2523. * @param {OutputNode} outNode
  2524. * @return {Boolean} Returns “true” if given node is a pseudo-snippets,
  2525. * `false` otherwise
  2526. */
  2527. function handlePseudoSnippet(outNode) {
  2528. const node = outNode.node; // original abbreviaiton node
  2529. if (isPseudoSnippet(node)) {
  2530. const fieldsModel = parse$2(node.value);
  2531. const field = findLowestIndexField(fieldsModel);
  2532. if (field) {
  2533. const parts = splitFieldsModel(fieldsModel, field);
  2534. outNode.open = outNode.renderFields(parts[0]);
  2535. outNode.close = outNode.renderFields(parts[1]);
  2536. } else {
  2537. outNode.text = outNode.renderFields(fieldsModel);
  2538. }
  2539. return true;
  2540. }
  2541. return false;
  2542. }
  2543. /**
  2544. * Finds field with lowest index in given text
  2545. * @param {Object} model
  2546. * @return {Object}
  2547. */
  2548. function findLowestIndexField(model) {
  2549. return model.fields.reduce((result, field) =>
  2550. !result || field.index < result.index ? field : result
  2551. , null);
  2552. }
  2553. /**
  2554. * Splits given fields model in two parts by given field
  2555. * @param {Object} model
  2556. * @param {Object} field
  2557. * @return {Array} Two-items array
  2558. */
  2559. function splitFieldsModel(model, field) {
  2560. const ix = model.fields.indexOf(field);
  2561. const left = new model.constructor(
  2562. model.string.slice(0, field.location),
  2563. model.fields.slice(0, ix)
  2564. );
  2565. const right = new model.constructor(
  2566. model.string.slice(field.location + field.length),
  2567. model.fields.slice(ix + 1)
  2568. );
  2569. return [left, right];
  2570. }
  2571. const commentOptions = {
  2572. // enable node commenting
  2573. enabled: false,
  2574. // attributes that should trigger node commenting on specific node,
  2575. // if commenting is enabled
  2576. trigger: ['id', 'class'],
  2577. // comment before opening tag
  2578. before: '',
  2579. // comment after closing tag
  2580. after: '\n<!-- /[#ID][.CLASS] -->'
  2581. };
  2582. /**
  2583. * Renders given parsed Emmet abbreviation as HTML, formatted according to
  2584. * `profile` options
  2585. * @param {Node} tree Parsed Emmet abbreviation
  2586. * @param {Profile} profile Output profile
  2587. * @param {Object} [options] Additional formatter options
  2588. * @return {String}
  2589. */
  2590. function html(tree, profile, options) {
  2591. options = Object.assign({}, options);
  2592. options.comment = Object.assign({}, commentOptions, options.comment);
  2593. return render(tree, options.field, outNode => {
  2594. outNode = setFormatting(outNode, profile);
  2595. if (!handlePseudoSnippet(outNode)) {
  2596. const node = outNode.node;
  2597. if (node.name) {
  2598. const name = profile.name(node.name);
  2599. const attrs = formatAttributes(outNode, profile);
  2600. outNode.open = `<${name}${attrs}${node.selfClosing ? profile.selfClose() : ''}>`;
  2601. if (!node.selfClosing) {
  2602. outNode.close = `</${name}>`;
  2603. }
  2604. commentNode(outNode, options.comment);
  2605. }
  2606. // Do not generate fields for nodes with empty value and children
  2607. // or if node is self-closed
  2608. if (node.value || (!node.children.length && !node.selfClosing) ) {
  2609. outNode.text = outNode.renderFields(node.value);
  2610. }
  2611. }
  2612. return outNode;
  2613. });
  2614. }
  2615. /**
  2616. * Updates formatting properties for given output node
  2617. * @param {OutputNode} outNode Output wrapper of farsed abbreviation node
  2618. * @param {Profile} profile Output profile
  2619. * @return {OutputNode}
  2620. */
  2621. function setFormatting(outNode, profile) {
  2622. const node = outNode.node;
  2623. if (shouldFormatNode(node, profile)) {
  2624. outNode.indent = profile.indent(getIndentLevel(node, profile));
  2625. outNode.newline = '\n';
  2626. const prefix = outNode.newline + outNode.indent;
  2627. // do not format the very first node in output
  2628. if (!isRoot(node.parent) || !isFirstChild(node)) {
  2629. outNode.beforeOpen = prefix;
  2630. if (node.isTextOnly) {
  2631. outNode.beforeText = prefix;
  2632. }
  2633. }
  2634. if (hasInnerFormatting(node, profile)) {
  2635. if (!node.isTextOnly) {
  2636. outNode.beforeText = prefix + profile.indent(1);
  2637. }
  2638. outNode.beforeClose = prefix;
  2639. }
  2640. }
  2641. return outNode;
  2642. }
  2643. /**
  2644. * Check if given node should be formatted
  2645. * @param {Node} node
  2646. * @param {Profile} profile
  2647. * @return {Boolean}
  2648. */
  2649. function shouldFormatNode(node, profile) {
  2650. if (!profile.get('format')) {
  2651. return false;
  2652. }
  2653. if (node.parent.isTextOnly
  2654. && node.parent.children.length === 1
  2655. && parse$2(node.parent.value).fields.length) {
  2656. // Edge case: do not format the only child of text-only node,
  2657. // but only if parent contains fields
  2658. return false;
  2659. }
  2660. return isInline(node, profile) ? shouldFormatInline(node, profile) : true;
  2661. }
  2662. /**
  2663. * Check if given inline node should be formatted as well, e.g. it contains
  2664. * enough adjacent siblings that should force formatting
  2665. * @param {Node} node
  2666. * @param {Profile} profile
  2667. * @return {Boolean}
  2668. */
  2669. function shouldFormatInline(node, profile) {
  2670. if (!isInline(node, profile)) {
  2671. return false;
  2672. }
  2673. if (isPseudoSnippet(node)) {
  2674. return true;
  2675. }
  2676. // check if inline node is the next sibling of block-level node
  2677. if (node.childIndex === 0) {
  2678. // first node in parent: format if it’s followed by a block-level element
  2679. let next = node;
  2680. while (next = next.nextSibling) {
  2681. if (!isInline(next, profile)) {
  2682. return true;
  2683. }
  2684. }
  2685. } else if (!isInline(node.previousSibling, profile)) {
  2686. // node is right after block-level element
  2687. return true;
  2688. }
  2689. if (profile.get('inlineBreak')) {
  2690. // check for adjacent inline elements before and after current element
  2691. let adjacentInline = 1;
  2692. let before = node, after = node;
  2693. while (isInlineElement((before = before.previousSibling), profile)) {
  2694. adjacentInline++;
  2695. }
  2696. while (isInlineElement((after = after.nextSibling), profile)) {
  2697. adjacentInline++;
  2698. }
  2699. if (adjacentInline >= profile.get('inlineBreak')) {
  2700. return true;
  2701. }
  2702. }
  2703. // Another edge case: inline node contains node that should receive foramtting
  2704. for (let i = 0, il = node.children.length; i < il; i++) {
  2705. if (shouldFormatNode(node.children[i], profile)) {
  2706. return true;
  2707. }
  2708. }
  2709. return false;
  2710. }
  2711. /**
  2712. * Check if given node contains inner formatting, e.g. any of its children should
  2713. * be formatted
  2714. * @param {Node} node
  2715. * @param {Profile} profile
  2716. * @return {Boolean}
  2717. */
  2718. function hasInnerFormatting(node, profile) {
  2719. // check if node if forced for inner formatting
  2720. const nodeName = (node.name || '').toLowerCase();
  2721. if (profile.get('formatForce').indexOf(nodeName) !== -1) {
  2722. return true;
  2723. }
  2724. // check if any of children should receive formatting
  2725. // NB don’t use `childrent.some()` to reduce memory allocations
  2726. for (let i = 0; i < node.children.length; i++) {
  2727. if (shouldFormatNode(node.children[i], profile)) {
  2728. return true;
  2729. }
  2730. }
  2731. return false;
  2732. }
  2733. /**
  2734. * Outputs attributes of given abbreviation node as HTML attributes
  2735. * @param {OutputNode} outNode
  2736. * @param {Profile} profile
  2737. * @return {String}
  2738. */
  2739. function formatAttributes(outNode, profile) {
  2740. const node = outNode.node;
  2741. return node.attributes.map(attr => {
  2742. if (attr.options.implied && attr.value == null) {
  2743. return null;
  2744. }
  2745. const attrName = profile.attribute(attr.name);
  2746. let attrValue = null;
  2747. // handle boolean attributes
  2748. if (attr.options.boolean || profile.get('booleanAttributes').indexOf(attrName.toLowerCase()) !== -1) {
  2749. if (profile.get('compactBooleanAttributes') && attr.value == null) {
  2750. return ` ${attrName}`;
  2751. } else if (attr.value == null) {
  2752. attrValue = attrName;
  2753. }
  2754. }
  2755. if (attrValue == null) {
  2756. attrValue = outNode.renderFields(attr.value);
  2757. }
  2758. // For https://github.com/Microsoft/vscode/issues/63703
  2759. // https://github.com/emmetio/markup-formatters/pull/2/files
  2760. return attr.options.before && attr.options.after
  2761. ? ` ${attrName}=${attr.options.before+attrValue+attr.options.after}`
  2762. : ` ${attrName}=${profile.quote(attrValue)}`;
  2763. }).join('');
  2764. }
  2765. /**
  2766. * Check if given node is inline-level
  2767. * @param {Node} node
  2768. * @param {Profile} profile
  2769. * @return {Boolean}
  2770. */
  2771. function isInline(node, profile) {
  2772. return (node && node.isTextOnly) || isInlineElement(node, profile);
  2773. }
  2774. /**
  2775. * Check if given node is inline-level element, e.g. element with explicitly
  2776. * defined node name
  2777. * @param {Node} node
  2778. * @param {Profile} profile
  2779. * @return {Boolean}
  2780. */
  2781. function isInlineElement(node, profile) {
  2782. return node && profile.isInline(node);
  2783. }
  2784. /**
  2785. * Computes indent level for given node
  2786. * @param {Node} node
  2787. * @param {Profile} profile
  2788. * @param {Number} level
  2789. * @return {Number}
  2790. */
  2791. function getIndentLevel(node, profile) {
  2792. // Increase indent level IF NOT:
  2793. // * parent is text-only node
  2794. // * there’s a parent node with a name that is explicitly set to decrease level
  2795. const skip = profile.get('formatSkip') || [];
  2796. let level = node.parent.isTextOnly ? -2 : -1;
  2797. let ctx = node;
  2798. while (ctx = ctx.parent) {
  2799. if (skip.indexOf( (ctx.name || '').toLowerCase() ) === -1) {
  2800. level++;
  2801. }
  2802. }
  2803. return level < 0 ? 0 : level;
  2804. }
  2805. /**
  2806. * Comments given output node, if required
  2807. * @param {OutputNode} outNode
  2808. * @param {Object} options
  2809. */
  2810. function commentNode(outNode, options) {
  2811. const node = outNode.node;
  2812. if (!options.enabled || !options.trigger || !node.name) {
  2813. return;
  2814. }
  2815. const attrs = outNode.node.attributes.reduce((out, attr) => {
  2816. if (attr.name && attr.value != null) {
  2817. out[attr.name.toUpperCase().replace(/-/g, '_')] = attr.value;
  2818. }
  2819. return out;
  2820. }, {});
  2821. // add comment only if attribute trigger is present
  2822. for (let i = 0, il = options.trigger.length; i < il; i++) {
  2823. if (options.trigger[i].toUpperCase() in attrs) {
  2824. outNode.open = template(options.before, attrs) + outNode.open;
  2825. if (outNode.close) {
  2826. outNode.close += template(options.after, attrs);
  2827. }
  2828. break;
  2829. }
  2830. }
  2831. }
  2832. /**
  2833. * Common utility methods for indent-based syntaxes (Slim, Pug, etc.)
  2834. */
  2835. const reId = /^id$/i;
  2836. const reClass = /^class$/i;
  2837. const defaultAttrOptions = {
  2838. primary: attrs => attrs.join(''),
  2839. secondary: attrs => attrs.map(attr => attr.isBoolean ? attr.name : `${attr.name}=${attr.value}`).join(', ')
  2840. };
  2841. const defaultNodeOptions = {
  2842. open: null,
  2843. close: null,
  2844. omitName: /^div$/i,
  2845. attributes: defaultAttrOptions
  2846. };
  2847. function indentFormat(outNode, profile, options) {
  2848. options = Object.assign({}, defaultNodeOptions, options);
  2849. const node = outNode.node;
  2850. outNode.indent = profile.indent(getIndentLevel$1(node, profile));
  2851. outNode.newline = '\n';
  2852. // Do not format the very first node in output
  2853. if (!isRoot(node.parent) || !isFirstChild(node)) {
  2854. outNode.beforeOpen = outNode.newline + outNode.indent;
  2855. }
  2856. if (node.name) {
  2857. const data = Object.assign({
  2858. NAME: profile.name(node.name),
  2859. SELF_CLOSE: node.selfClosing ? options.selfClose : null
  2860. }, getAttributes(outNode, profile, options.attributes));
  2861. // omit tag name if node has primary attributes
  2862. if (options.omitName && options.omitName.test(data.NAME) && data.PRIMARY_ATTRS) {
  2863. data.NAME = null;
  2864. }
  2865. if (options.open != null) {
  2866. outNode.open = template(options.open, data);
  2867. }
  2868. if (options.close != null) {
  2869. outNode.close = template(options.close, data);
  2870. }
  2871. }
  2872. return outNode;
  2873. }
  2874. /**
  2875. * Formats attributes of given node into a string.
  2876. * @param {OutputNode} node Output node wrapper
  2877. * @param {Profile} profile Output profile
  2878. * @param {Object} options Additional formatting options
  2879. * @return {String}
  2880. */
  2881. function getAttributes(outNode, profile, options) {
  2882. options = Object.assign({}, defaultAttrOptions, options);
  2883. const primary = [], secondary = [];
  2884. const node = outNode.node;
  2885. node.attributes.forEach(attr => {
  2886. if (attr.options.implied && attr.value == null) {
  2887. return null;
  2888. }
  2889. const name = profile.attribute(attr.name);
  2890. const value = outNode.renderFields(attr.value);
  2891. if (reId.test(name)) {
  2892. value && primary.push(`#${value}`);
  2893. } else if (reClass.test(name)) {
  2894. value && primary.push(`.${value.replace(/\s+/g, '.')}`);
  2895. } else {
  2896. const isBoolean = attr.value == null
  2897. && (attr.options.boolean || profile.get('booleanAttributes').indexOf(name.toLowerCase()) !== -1);
  2898. secondary.push({ name, value, isBoolean });
  2899. }
  2900. });
  2901. return {
  2902. PRIMARY_ATTRS: options.primary(primary) || null,
  2903. SECONDARY_ATTRS: options.secondary(secondary) || null
  2904. };
  2905. }
  2906. /**
  2907. * Computes indent level for given node
  2908. * @param {Node} node
  2909. * @param {Profile} profile
  2910. * @param {Number} level
  2911. * @return {Number}
  2912. */
  2913. function getIndentLevel$1(node, profile) {
  2914. let level = node.parent.isTextOnly ? -2 : -1;
  2915. let ctx = node;
  2916. while (ctx = ctx.parent) {
  2917. level++;
  2918. }
  2919. return level < 0 ? 0 : level;
  2920. }
  2921. const reNl = /\n|\r/;
  2922. /**
  2923. * Renders given parsed Emmet abbreviation as HAML, formatted according to
  2924. * `profile` options
  2925. * @param {Node} tree Parsed Emmet abbreviation
  2926. * @param {Profile} profile Output profile
  2927. * @param {Object} [options] Additional formatter options
  2928. * @return {String}
  2929. */
  2930. function haml(tree, profile, options) {
  2931. options = options || {};
  2932. const nodeOptions = {
  2933. open: '[%NAME][PRIMARY_ATTRS][(SECONDARY_ATTRS)][SELF_CLOSE]',
  2934. selfClose: '/',
  2935. attributes: {
  2936. secondary(attrs) {
  2937. return attrs.map(attr => attr.isBoolean
  2938. ? `${attr.name}${profile.get('compactBooleanAttributes') ? '' : '=true'}`
  2939. : `${attr.name}=${profile.quote(attr.value)}`
  2940. ).join(' ');
  2941. }
  2942. }
  2943. };
  2944. return render(tree, options.field, outNode => {
  2945. outNode = indentFormat(outNode, profile, nodeOptions);
  2946. outNode = updateFormatting(outNode, profile);
  2947. if (!handlePseudoSnippet(outNode)) {
  2948. const node = outNode.node;
  2949. // Do not generate fields for nodes with empty value and children
  2950. // or if node is self-closed
  2951. if (node.value || (!node.children.length && !node.selfClosing) ) {
  2952. outNode.text = outNode.renderFields(formatNodeValue(node, profile));
  2953. }
  2954. }
  2955. return outNode;
  2956. });
  2957. }
  2958. /**
  2959. * Updates formatting properties for given output node
  2960. * NB Unlike HTML, HAML is indent-based format so some formatting options from
  2961. * `profile` will not take effect, otherwise output will be broken
  2962. * @param {OutputNode} outNode Output wrapper of parsed abbreviation node
  2963. * @param {Profile} profile Output profile
  2964. * @return {OutputNode}
  2965. */
  2966. function updateFormatting(outNode, profile) {
  2967. const node = outNode.node;
  2968. if (!node.isTextOnly && node.value) {
  2969. // node with text: put a space before single-line text
  2970. outNode.beforeText = reNl.test(node.value)
  2971. ? outNode.newline + outNode.indent + profile.indent(1)
  2972. : ' ';
  2973. }
  2974. return outNode;
  2975. }
  2976. /**
  2977. * Formats value of given node: for multiline text we should add a ` |` suffix
  2978. * at the end of each line. Also ensure that text is perfectly aligned.
  2979. * @param {Node} node
  2980. * @param {Profile} profile
  2981. * @return {String|null}
  2982. */
  2983. function formatNodeValue(node, profile) {
  2984. if (node.value != null && reNl.test(node.value)) {
  2985. const lines = splitByLines$1(node.value);
  2986. const indent = profile.indent(1);
  2987. const maxLength = lines.reduce((prev, line) => Math.max(prev, line.length), 0);
  2988. return lines.map((line, i) => `${i ? indent : ''}${pad(line, maxLength)} |`).join('\n');
  2989. }
  2990. return node.value;
  2991. }
  2992. function pad(text, len) {
  2993. while (text.length < len) {
  2994. text += ' ';
  2995. }
  2996. return text;
  2997. }
  2998. const reNl$1 = /\n|\r/;
  2999. const secondaryAttrs = {
  3000. none: '[ SECONDARY_ATTRS]',
  3001. round: '[(SECONDARY_ATTRS)]',
  3002. curly: '[{SECONDARY_ATTRS}]',
  3003. square: '[[SECONDARY_ATTRS]'
  3004. };
  3005. /**
  3006. * Renders given parsed Emmet abbreviation as Slim, formatted according to
  3007. * `profile` options
  3008. * @param {Node} tree Parsed Emmet abbreviation
  3009. * @param {Profile} profile Output profile
  3010. * @param {Object} [options] Additional formatter options
  3011. * @return {String}
  3012. */
  3013. function slim(tree, profile, options) {
  3014. options = options || {};
  3015. const SECONDARY_ATTRS = options.attributeWrap
  3016. && secondaryAttrs[options.attributeWrap]
  3017. || secondaryAttrs.none;
  3018. const booleanAttr = SECONDARY_ATTRS === secondaryAttrs.none
  3019. ? attr => `${attr.name}=true`
  3020. : attr => attr.name;
  3021. const nodeOptions = {
  3022. open: `[NAME][PRIMARY_ATTRS]${SECONDARY_ATTRS}[SELF_CLOSE]`,
  3023. selfClose: '/',
  3024. attributes: {
  3025. secondary(attrs) {
  3026. return attrs.map(attr => attr.isBoolean
  3027. ? booleanAttr(attr)
  3028. : `${attr.name}=${profile.quote(attr.value)}`
  3029. ).join(' ');
  3030. }
  3031. }
  3032. };
  3033. return render(tree, options.field, (outNode, renderFields) => {
  3034. outNode = indentFormat(outNode, profile, nodeOptions);
  3035. outNode = updateFormatting$1(outNode, profile);
  3036. if (!handlePseudoSnippet(outNode)) {
  3037. const node = outNode.node;
  3038. // Do not generate fields for nodes with empty value and children
  3039. // or if node is self-closed
  3040. if (node.value || (!node.children.length && !node.selfClosing) ) {
  3041. outNode.text = outNode.renderFields(formatNodeValue$1(node, profile));
  3042. }
  3043. }
  3044. return outNode;
  3045. });
  3046. }
  3047. /**
  3048. * Updates formatting properties for given output node
  3049. * NB Unlike HTML, Slim is indent-based format so some formatting options from
  3050. * `profile` will not take effect, otherwise output will be broken
  3051. * @param {OutputNode} outNode Output wrapper of farsed abbreviation node
  3052. * @param {Profile} profile Output profile
  3053. * @return {OutputNode}
  3054. */
  3055. function updateFormatting$1(outNode, profile) {
  3056. const node = outNode.node;
  3057. const parent = node.parent;
  3058. // Edge case: a single inline-level child inside node without text:
  3059. // allow it to be inlined
  3060. if (profile.get('inlineBreak') === 0 && isInline$1(node, profile)
  3061. && !isRoot(parent) && parent.value == null && parent.children.length === 1) {
  3062. outNode.beforeOpen = ': ';
  3063. }
  3064. if (!node.isTextOnly && node.value) {
  3065. // node with text: put a space before single-line text
  3066. outNode.beforeText = reNl$1.test(node.value)
  3067. ? outNode.newline + outNode.indent + profile.indent(1)
  3068. : ' ';
  3069. }
  3070. return outNode;
  3071. }
  3072. /**
  3073. * Formats value of given node: for multiline text we should precede each
  3074. * line with `| ` with one-level deep indent
  3075. * @param {Node} node
  3076. * @param {Profile} profile
  3077. * @return {String|null}
  3078. */
  3079. function formatNodeValue$1(node, profile) {
  3080. if (node.value != null && reNl$1.test(node.value)) {
  3081. const indent = profile.indent(1);
  3082. return splitByLines$1(node.value).map((line, i) => `${indent}${i ? ' ' : '|'} ${line}`).join('\n');
  3083. }
  3084. return node.value;
  3085. }
  3086. /**
  3087. * Check if given node is inline-level
  3088. * @param {Node} node
  3089. * @param {Profile} profile
  3090. * @return {Boolean}
  3091. */
  3092. function isInline$1(node, profile) {
  3093. return node && (node.isTextOnly || profile.isInline(node));
  3094. }
  3095. const reNl$2 = /\n|\r/;
  3096. /**
  3097. * Renders given parsed Emmet abbreviation as Pug, formatted according to
  3098. * `profile` options
  3099. * @param {Node} tree Parsed Emmet abbreviation
  3100. * @param {Profile} profile Output profile
  3101. * @param {Object} [options] Additional formatter options
  3102. * @return {String}
  3103. */
  3104. function pug(tree, profile, options) {
  3105. options = options || {};
  3106. const nodeOptions = {
  3107. open: '[NAME][PRIMARY_ATTRS][(SECONDARY_ATTRS)]',
  3108. attributes: {
  3109. secondary(attrs) {
  3110. return attrs.map(attr => attr.isBoolean ? attr.name : `${attr.name}=${profile.quote(attr.value)}`).join(', ');
  3111. }
  3112. }
  3113. };
  3114. return render(tree, options.field, outNode => {
  3115. outNode = indentFormat(outNode, profile, nodeOptions);
  3116. outNode = updateFormatting$2(outNode, profile);
  3117. if (!handlePseudoSnippet(outNode)) {
  3118. const node = outNode.node;
  3119. // Do not generate fields for nodes with empty value and children
  3120. // or if node is self-closed
  3121. if (node.value || (!node.children.length && !node.selfClosing) ) {
  3122. outNode.text = outNode.renderFields(formatNodeValue$2(node, profile));
  3123. }
  3124. }
  3125. return outNode;
  3126. });
  3127. }
  3128. /**
  3129. * Updates formatting properties for given output node
  3130. * NB Unlike HTML, Pug is indent-based format so some formatting options from
  3131. * `profile` will not take effect, otherwise output will be broken
  3132. * @param {OutputNode} outNode Output wrapper of parsed abbreviation node
  3133. * @param {Profile} profile Output profile
  3134. * @return {OutputNode}
  3135. */
  3136. function updateFormatting$2(outNode, profile) {
  3137. const node = outNode.node;
  3138. if (!node.isTextOnly && node.value) {
  3139. // node with text: put a space before single-line text
  3140. outNode.beforeText = reNl$2.test(node.value)
  3141. ? outNode.newline + outNode.indent + profile.indent(1)
  3142. : ' ';
  3143. }
  3144. return outNode;
  3145. }
  3146. /**
  3147. * Formats value of given node: for multiline text we should precede each
  3148. * line with `| ` with one-level deep indent
  3149. * @param {Node} node
  3150. * @param {Profile} profile
  3151. * @return {String|null}
  3152. */
  3153. function formatNodeValue$2(node, profile) {
  3154. if (node.value != null && reNl$2.test(node.value)) {
  3155. const indent = profile.indent(1);
  3156. return splitByLines$1(node.value).map(line => `${indent}| ${line}`).join('\n');
  3157. }
  3158. return node.value;
  3159. }
  3160. const supportedSyntaxed = { html, haml, slim, pug };
  3161. /**
  3162. * Outputs given parsed abbreviation in specified syntax
  3163. * @param {Node} tree Parsed abbreviation tree
  3164. * @param {Profile} profile Output profile
  3165. * @param {String} [syntax] Output syntax. If not given, `html` syntax is used
  3166. * @param {Function} options.field A function to output field/tabstop for
  3167. * host editor. This function takes two arguments: `index` and `placeholder` and
  3168. * should return a string that represents tabstop in host editor. By default
  3169. * only a placeholder is returned
  3170. * @example
  3171. * {
  3172. * field(index, placeholder) {
  3173. * // return field in TextMate-style, e.g. ${1} or ${2:foo}
  3174. * return `\${${index}${placeholder ? ':' + placeholder : ''}}`;
  3175. * }
  3176. * }
  3177. * @return {String}
  3178. */
  3179. var index$3 = function(tree, profile, syntax, options) {
  3180. if (typeof syntax === 'object') {
  3181. options = syntax;
  3182. syntax = null;
  3183. }
  3184. if (!supports(syntax)) {
  3185. // fallback to HTML if given syntax is not supported
  3186. syntax = 'html';
  3187. }
  3188. return supportedSyntaxed[syntax](tree, profile, options);
  3189. };
  3190. /**
  3191. * Check if given syntax is supported
  3192. * @param {String} syntax
  3193. * @return {Boolean}
  3194. */
  3195. function supports(syntax) {
  3196. return !!syntax && syntax in supportedSyntaxed;
  3197. }
  3198. /**
  3199. * Expands given abbreviation into code
  3200. * @param {String|Node} abbr Abbreviation to parse or already parsed abbreviation
  3201. * @param {Object} options
  3202. * @return {String}
  3203. */
  3204. function expand(abbr, options) {
  3205. options = options || {};
  3206. if (typeof abbr === 'string') {
  3207. abbr = parse$3(abbr, options);
  3208. }
  3209. return index$3(abbr, options.profile, options.syntax, options.format);
  3210. }
  3211. /**
  3212. * Parses given Emmet abbreviation into a final abbreviation tree with all
  3213. * required transformations applied
  3214. * @param {String} Abbreviation to parse
  3215. * @param {Object} options
  3216. * @return {Node}
  3217. */
  3218. function parse$3(abbr, options) {
  3219. return index(abbr)
  3220. .use(index$1, options.snippets)
  3221. .use(replaceVariables, options.variables)
  3222. .use(index$2, options.text, options.addons);
  3223. }
  3224. /**
  3225. * A wrapper for holding CSS value
  3226. */
  3227. class CSSValue {
  3228. constructor() {
  3229. this.type = 'css-value';
  3230. this.value = [];
  3231. }
  3232. get size() {
  3233. return this.value.length;
  3234. }
  3235. add(value) {
  3236. this.value.push(value);
  3237. }
  3238. has(value) {
  3239. return this.value.indexOf(value) !== -1;
  3240. }
  3241. toString() {
  3242. return this.value.join(' ');
  3243. }
  3244. }
  3245. const HASH$1 = 35; // #
  3246. const DOT$2 = 46; // .
  3247. /**
  3248. * Consumes a color token from given string
  3249. * @param {StreamReader} stream
  3250. * @return {Color} Returns consumend color object, `undefined` otherwise
  3251. */
  3252. var consumeColor = function(stream) {
  3253. // supported color variations:
  3254. // #abc → #aabbccc
  3255. // #0 → #000000
  3256. // #fff.5 → rgba(255, 255, 255, 0.5)
  3257. // #t → transparent
  3258. if (stream.peek() === HASH$1) {
  3259. stream.start = stream.pos;
  3260. stream.next();
  3261. stream.eat(116) /* t */ || stream.eatWhile(isHex);
  3262. const base = stream.current();
  3263. // a hex color can be followed by `.num` alpha value
  3264. stream.start = stream.pos;
  3265. if (stream.eat(DOT$2) && !stream.eatWhile(isNumber)) {
  3266. throw stream.error('Unexpected character for alpha value of color');
  3267. }
  3268. return new Color(base, stream.current());
  3269. }
  3270. };
  3271. class Color {
  3272. constructor(value, alpha) {
  3273. this.type = 'color';
  3274. this.raw = value;
  3275. this.alpha = Number(alpha != null && alpha !== '' ? alpha : 1);
  3276. value = value.slice(1); // remove #
  3277. let r = 0, g = 0, b = 0;
  3278. if (value === 't') {
  3279. this.alpha = 0;
  3280. } else {
  3281. switch (value.length) {
  3282. case 0:
  3283. break;
  3284. case 1:
  3285. r = g = b = value + value;
  3286. break;
  3287. case 2:
  3288. r = g = b = value;
  3289. break;
  3290. case 3:
  3291. r = value[0] + value[0];
  3292. g = value[1] + value[1];
  3293. b = value[2] + value[2];
  3294. break;
  3295. default:
  3296. value += value;
  3297. r = value.slice(0, 2);
  3298. g = value.slice(2, 4);
  3299. b = value.slice(4, 6);
  3300. }
  3301. }
  3302. this.r = parseInt(r, 16);
  3303. this.g = parseInt(g, 16);
  3304. this.b = parseInt(b, 16);
  3305. }
  3306. /**
  3307. * Output current color as hex value
  3308. * @param {Boolean} shor Produce short value (e.g. #fff instead of #ffffff), if possible
  3309. * @return {String}
  3310. */
  3311. toHex(short) {
  3312. const fn = (short && isShortHex(this.r) && isShortHex(this.g) && isShortHex(this.b))
  3313. ? toShortHex : toHex;
  3314. return '#' + fn(this.r) + fn(this.g) + fn(this.b);
  3315. }
  3316. /**
  3317. * Output current color as `rgba?(...)` CSS color
  3318. * @return {String}
  3319. */
  3320. toRGB() {
  3321. const values = [this.r, this.g, this.b];
  3322. if (this.alpha !== 1) {
  3323. values.push(this.alpha.toFixed(8).replace(/\.?0+$/, ''));
  3324. }
  3325. return `${values.length === 3 ? 'rgb' : 'rgba'}(${values.join(', ')})`;
  3326. }
  3327. toString(short) {
  3328. if (!this.r && !this.g && !this.b && !this.alpha) {
  3329. return 'transparent';
  3330. }
  3331. return this.alpha === 1 ? this.toHex(short) : this.toRGB();
  3332. }
  3333. }
  3334. /**
  3335. * Check if given code is a hex value (/0-9a-f/)
  3336. * @param {Number} code
  3337. * @return {Boolean}
  3338. */
  3339. function isHex(code) {
  3340. return isNumber(code) || isAlpha(code, 65, 70); // A-F
  3341. }
  3342. function isShortHex(hex) {
  3343. return !(hex % 17);
  3344. }
  3345. function toShortHex(num) {
  3346. return (num >> 4).toString(16);
  3347. }
  3348. function toHex(num) {
  3349. return pad$1(num.toString(16), 2);
  3350. }
  3351. function pad$1(value, len) {
  3352. while (value.length < len) {
  3353. value = '0' + value;
  3354. }
  3355. return value;
  3356. }
  3357. /**
  3358. * @param {Number} code
  3359. * @return {Boolean}
  3360. */
  3361. function isAlphaNumericWord(code) {
  3362. return isNumber(code) || isAlphaWord(code);
  3363. }
  3364. /**
  3365. * @param {Number} code
  3366. * @return {Boolean}
  3367. */
  3368. function isAlphaWord(code) {
  3369. return code === 95 /* _ */ || isAlpha(code);
  3370. }
  3371. const PERCENT = 37; // %
  3372. const DOT$1$1 = 46; // .
  3373. const DASH$1 = 45; // -
  3374. /**
  3375. * Consumes numeric CSS value (number with optional unit) from current stream,
  3376. * if possible
  3377. * @param {StreamReader} stream
  3378. * @return {NumericValue}
  3379. */
  3380. var consumeNumericValue = function(stream) {
  3381. stream.start = stream.pos;
  3382. if (eatNumber(stream)) {
  3383. const num = stream.current();
  3384. stream.start = stream.pos;
  3385. // eat unit, which can be a % or alpha word
  3386. stream.eat(PERCENT) || stream.eatWhile(isAlphaWord);
  3387. return new NumericValue(num, stream.current());
  3388. }
  3389. };
  3390. /**
  3391. * A numeric CSS value with optional unit
  3392. */
  3393. class NumericValue {
  3394. constructor(value, unit) {
  3395. this.type = 'numeric';
  3396. this.value = Number(value);
  3397. this.unit = unit || '';
  3398. }
  3399. toString() {
  3400. return `${this.value}${this.unit}`;
  3401. }
  3402. }
  3403. /**
  3404. * Eats number value from given stream
  3405. * @param {StreamReader} stream
  3406. * @return {Boolean} Returns `true` if number was consumed
  3407. */
  3408. function eatNumber(stream) {
  3409. const start = stream.pos;
  3410. const negative = stream.eat(DASH$1);
  3411. let hadDot = false, consumed = false, code;
  3412. while (!stream.eof()) {
  3413. code = stream.peek();
  3414. // either a second dot or not a number: stop parsing
  3415. if (code === DOT$1$1 ? hadDot : !isNumber(code)) {
  3416. break;
  3417. }
  3418. consumed = true;
  3419. if (code === DOT$1$1) {
  3420. hadDot = true;
  3421. }
  3422. stream.next();
  3423. }
  3424. if (negative && !consumed) {
  3425. // edge case: consumed dash only, bail out
  3426. stream.pos = start;
  3427. }
  3428. return start !== stream.pos;
  3429. }
  3430. const DOLLAR$1 = 36; // $
  3431. const DASH$2 = 45; // -
  3432. const AT$1 = 64; // @
  3433. /**
  3434. * Consumes a keyword: either a variable (a word that starts with $ or @) or CSS
  3435. * keyword or shorthand
  3436. * @param {StreamReader} stream
  3437. * @param {Boolean} [short] Use short notation for consuming value.
  3438. * The difference between “short” and “full” notation is that first one uses
  3439. * alpha characters only and used for extracting keywords from abbreviation,
  3440. * while “full” notation also supports numbers and dashes
  3441. * @return {String} Consumed variable
  3442. */
  3443. var consumeKeyword = function(stream, short) {
  3444. stream.start = stream.pos;
  3445. if (stream.eat(DOLLAR$1) || stream.eat(AT$1)) {
  3446. // SCSS or LESS variable
  3447. stream.eatWhile(isVariableName);
  3448. } else if (short) {
  3449. stream.eatWhile(isAlphaWord);
  3450. } else {
  3451. stream.eatWhile(isKeyword);
  3452. }
  3453. return stream.start !== stream.pos ? new Keyword(stream.current()) : null;
  3454. };
  3455. class Keyword {
  3456. constructor(value) {
  3457. this.type = 'keyword';
  3458. this.value = value;
  3459. }
  3460. toString() {
  3461. return this.value;
  3462. }
  3463. }
  3464. function isKeyword(code) {
  3465. return isAlphaNumericWord(code) || code === DASH$2;
  3466. }
  3467. function isVariableName(code) {
  3468. return code === 45 /* - */ || isAlphaNumericWord(code);
  3469. }
  3470. const opt$1 = { throws: true };
  3471. /**
  3472. * Consumes 'single' or "double"-quoted string from given string, if possible
  3473. * @param {StreamReader} stream
  3474. * @return {String}
  3475. */
  3476. var consumeQuoted$1 = function(stream) {
  3477. if (eatQuoted(stream, opt$1)) {
  3478. return new QuotedString(stream.current());
  3479. }
  3480. };
  3481. class QuotedString {
  3482. constructor(value) {
  3483. this.type = 'string';
  3484. this.value = value;
  3485. }
  3486. toString() {
  3487. return this.value;
  3488. }
  3489. }
  3490. const LBRACE = 40; // (
  3491. const RBRACE = 41; // )
  3492. const COMMA = 44; // ,
  3493. /**
  3494. * Consumes arguments from given string.
  3495. * Arguments are comma-separated list of CSS values inside round braces, e.g.
  3496. * `(1, a2, 'a3')`. Nested lists and quoted strings are supported
  3497. * @param {StreamReader} stream
  3498. * @return {Array} Array of arguments, `null` if arguments cannot be consumed
  3499. */
  3500. function consumeArgumentList(stream) {
  3501. if (!stream.eat(LBRACE)) {
  3502. // not an argument list
  3503. return null;
  3504. }
  3505. let level = 1, code, arg;
  3506. const argsList = [];
  3507. while (!stream.eof()) {
  3508. if (arg = consumeArgument(stream)) {
  3509. argsList.push(arg);
  3510. } else {
  3511. // didn’t consumed argument, expect argument separator or end-of-arguments
  3512. stream.eatWhile(isWhiteSpace);
  3513. if (stream.eat(RBRACE)) {
  3514. // end of arguments list
  3515. break;
  3516. }
  3517. if (!stream.eat(COMMA)) {
  3518. throw stream.error('Expected , or )');
  3519. }
  3520. }
  3521. }
  3522. return argsList;
  3523. }
  3524. /**
  3525. * Consumes a single argument. An argument is a `CSSValue`, e.g. it could be
  3526. * a space-separated string of value
  3527. * @param {StreamReader} stream
  3528. * @return {CSSValue}
  3529. */
  3530. function consumeArgument(stream) {
  3531. const result = new CSSValue();
  3532. let value;
  3533. while (!stream.eof()) {
  3534. stream.eatWhile(isWhiteSpace);
  3535. value = consumeNumericValue(stream) || consumeColor(stream)
  3536. || consumeQuoted$1(stream) || consumeKeywordOrFunction(stream);
  3537. if (!value) {
  3538. break;
  3539. }
  3540. result.add(value);
  3541. }
  3542. return result.size ? result : null;
  3543. }
  3544. /**
  3545. * Consumes either function call like `foo()` or keyword like `foo`
  3546. * @param {StreamReader} stream
  3547. * @return {Keyword|FunctionCall}
  3548. */
  3549. function consumeKeywordOrFunction(stream) {
  3550. const kw = consumeKeyword(stream);
  3551. if (kw) {
  3552. const args = consumeArgumentList(stream);
  3553. return args ? new FunctionCall(kw.toString(), args) : kw;
  3554. }
  3555. }
  3556. class FunctionCall {
  3557. /**
  3558. * @param {String} name Function name
  3559. * @param {Array} args Function arguments
  3560. */
  3561. constructor(name, args) {
  3562. this.type = 'function';
  3563. this.name = name;
  3564. this.args = args || [];
  3565. }
  3566. toString() {
  3567. return `${this.name}(${this.args.join(', ')})`;
  3568. }
  3569. }
  3570. const EXCL$1 = 33; // !
  3571. const DOLLAR$2 = 36; // $
  3572. const PLUS = 43; // +
  3573. const DASH = 45; // -
  3574. const COLON$1 = 58; // :
  3575. const AT = 64; // @
  3576. /**
  3577. * Parses given Emmet CSS abbreviation and returns it as parsed Node tree
  3578. * @param {String} abbr
  3579. * @return {Node}
  3580. */
  3581. var index$4 = function(abbr) {
  3582. const root = new Node();
  3583. const stream = new StreamReader(abbr);
  3584. while (!stream.eof()) {
  3585. let node = new Node(consumeIdent(stream));
  3586. node.value = consumeValue(stream);
  3587. const args = consumeArgumentList(stream);
  3588. if (args) {
  3589. // technically, arguments in CSS are anonymous Emmet Node attributes,
  3590. // but since Emmet can support only one anonymous, `null`-name
  3591. // attribute (for good reasons), we’ll use argument index as name
  3592. for (let i = 0; i < args.length; i++) {
  3593. node.setAttribute(String(i), args[i]);
  3594. }
  3595. }
  3596. // Consume `!important` modifier at the end of expression
  3597. if (stream.eat(EXCL$1)) {
  3598. node.value.add('!');
  3599. }
  3600. root.appendChild(node);
  3601. // CSS abbreviations cannot be nested, only listed
  3602. if (!stream.eat(PLUS)) {
  3603. break;
  3604. }
  3605. }
  3606. if (!stream.eof()) {
  3607. throw stream.error('Unexpected character');
  3608. }
  3609. return root;
  3610. };
  3611. /**
  3612. * Consumes CSS property identifier from given stream
  3613. * @param {StreamReader} stream
  3614. * @return {String}
  3615. */
  3616. function consumeIdent(stream) {
  3617. stream.start = stream.pos;
  3618. stream.eatWhile(isIdentPrefix);
  3619. stream.eatWhile(isIdent);
  3620. return stream.start !== stream.pos ? stream.current() : null;
  3621. }
  3622. /**
  3623. * Consumes embedded value from Emmet CSS abbreviation stream
  3624. * @param {StreamReader} stream
  3625. * @return {CSSValue}
  3626. */
  3627. function consumeValue(stream) {
  3628. const values = new CSSValue();
  3629. let value;
  3630. while (!stream.eof()) {
  3631. // use colon as value separator
  3632. stream.eat(COLON$1);
  3633. if (value = consumeNumericValue(stream) || consumeColor(stream)) {
  3634. // edge case: a dash after unit-less numeric value or color should
  3635. // be treated as value separator, not negative sign
  3636. if (!value.unit) {
  3637. stream.eat(DASH);
  3638. }
  3639. } else {
  3640. stream.eat(DASH);
  3641. value = consumeKeyword(stream, true);
  3642. }
  3643. if (!value) {
  3644. break;
  3645. }
  3646. values.add(value);
  3647. }
  3648. return values;
  3649. }
  3650. /**
  3651. * @param {Number} code
  3652. * @return {Boolean}
  3653. */
  3654. function isIdent(code) {
  3655. return isAlphaWord(code);
  3656. }
  3657. /**
  3658. * @param {Number} code
  3659. * @return {Boolean}
  3660. */
  3661. function isIdentPrefix(code) {
  3662. return code === AT || code === DOLLAR$2 || code === EXCL$1;
  3663. }
  3664. const DASH$3 = 45; // -
  3665. /**
  3666. * Calculates fuzzy match score of how close `abbr` matches given `string`.
  3667. * @param {String} abbr Abbreviation to score
  3668. * @param {String} string String to match
  3669. * @param {Number} [fuzziness] Fuzzy factor
  3670. * @return {Number} Match score
  3671. */
  3672. var stringScore = function(abbr, string) {
  3673. abbr = abbr.toLowerCase();
  3674. string = string.toLowerCase();
  3675. if (abbr === string) {
  3676. return 1;
  3677. }
  3678. // a string MUST start with the same character as abbreviation
  3679. if (!string || abbr.charCodeAt(0) !== string.charCodeAt(0)) {
  3680. return 0;
  3681. }
  3682. const abbrLength = abbr.length;
  3683. const stringLength = string.length;
  3684. let i = 1, j = 1, score = stringLength;
  3685. let ch1, ch2, found, acronym;
  3686. while (i < abbrLength) {
  3687. ch1 = abbr.charCodeAt(i);
  3688. found = false;
  3689. acronym = false;
  3690. while (j < stringLength) {
  3691. ch2 = string.charCodeAt(j);
  3692. if (ch1 === ch2) {
  3693. found = true;
  3694. score += (stringLength - j) * (acronym ? 2 : 1);
  3695. break;
  3696. }
  3697. // add acronym bonus for exactly next match after unmatched `-`
  3698. acronym = ch2 === DASH$3;
  3699. j++;
  3700. }
  3701. if (!found) {
  3702. break;
  3703. }
  3704. i++;
  3705. }
  3706. return score && score * (i / abbrLength) / sum(stringLength);
  3707. };
  3708. /**
  3709. * Calculates sum of first `n` natural numbers, e.g. 1+2+3+...n
  3710. * @param {Number} n
  3711. * @return {Number}
  3712. */
  3713. function sum(n) {
  3714. return n * (n + 1) / 2;
  3715. }
  3716. const reProperty = /^([a-z\-]+)(?:\s*:\s*([^\n\r]+))?$/;
  3717. const DASH$1$1 = 45; // -
  3718. /**
  3719. * Creates a special structure for resolving CSS properties from plain CSS
  3720. * snippets.
  3721. * Almost all CSS snippets are aliases for real CSS properties with available
  3722. * value variants, optionally separated by `|`. Most values are keywords that
  3723. * can be fuzzy-resolved as well. Some CSS properties are shorthands for other,
  3724. * more specific properties, like `border` and `border-style`. For such cases
  3725. * keywords from more specific properties should be available in shorthands too.
  3726. * @param {Snippet[]} snippets
  3727. * @return {CSSSnippet[]}
  3728. */
  3729. var cssSnippets = function(snippets) {
  3730. return nest( snippets.map(snippet => new CSSSnippet(snippet.key, snippet.value)) );
  3731. };
  3732. class CSSSnippet {
  3733. constructor(key, value) {
  3734. this.key = key;
  3735. this.value = value;
  3736. this.property = null;
  3737. // detect if given snippet is a property
  3738. const m = value && value.match(reProperty);
  3739. if (m) {
  3740. this.property = m[1];
  3741. this.value = m[2];
  3742. }
  3743. this.dependencies = [];
  3744. }
  3745. addDependency(dep) {
  3746. this.dependencies.push(dep);
  3747. }
  3748. get defaulValue() {
  3749. return this.value != null ? splitValue(this.value)[0] : null;
  3750. }
  3751. /**
  3752. * Returns list of unique keywords for current CSS snippet and its dependencies
  3753. * @return {String[]}
  3754. */
  3755. keywords() {
  3756. const stack = [];
  3757. const keywords = new Set();
  3758. let i = 0, item, candidates;
  3759. if (this.property) {
  3760. // scan valid CSS-properties only
  3761. stack.push(this);
  3762. }
  3763. while (i < stack.length) {
  3764. // NB Keep items in stack instead of push/pop to avoid possible
  3765. // circular references
  3766. item = stack[i++];
  3767. if (item.value) {
  3768. candidates = splitValue(item.value).filter(isKeyword$1);
  3769. // extract possible keywords from snippet value
  3770. for (let j = 0; j < candidates.length; j++) {
  3771. keywords.add(candidates[j].trim());
  3772. }
  3773. // add dependencies into scan stack
  3774. for (let j = 0, deps = item.dependencies; j < deps.length; j++) {
  3775. if (stack.indexOf(deps[j]) === -1) {
  3776. stack.push(deps[j]);
  3777. }
  3778. }
  3779. }
  3780. }
  3781. return Array.from(keywords);
  3782. }
  3783. }
  3784. /**
  3785. * Nests more specific CSS properties into shorthand ones, e.g.
  3786. * background-position-x -> background-position -> background
  3787. * @param {CSSSnippet[]} snippets
  3788. * @return {CSSSnippet[]}
  3789. */
  3790. function nest(snippets) {
  3791. snippets = snippets.sort(snippetsSort);
  3792. const stack = [];
  3793. // For sorted list of CSS properties, create dependency graph where each
  3794. // shorthand property contains its more specific one, e.g.
  3795. // backgound -> background-position -> background-position-x
  3796. for (let i = 0, cur, prev; i < snippets.length; i++) {
  3797. cur = snippets[i];
  3798. if (!cur.property) {
  3799. // not a CSS property, skip it
  3800. continue;
  3801. }
  3802. // Check if current property belongs to one from parent stack.
  3803. // Since `snippets` array is sorted, items are perfectly aligned
  3804. // from shorthands to more specific variants
  3805. while (stack.length) {
  3806. prev = stack[stack.length - 1];
  3807. if (cur.property.indexOf(prev.property) === 0
  3808. && cur.property.charCodeAt(prev.property.length) === DASH$1$1) {
  3809. prev.addDependency(cur);
  3810. stack.push(cur);
  3811. break;
  3812. }
  3813. stack.pop();
  3814. }
  3815. if (!stack.length) {
  3816. stack.push(cur);
  3817. }
  3818. }
  3819. return snippets;
  3820. }
  3821. /**
  3822. * A sorting function for array of snippets
  3823. * @param {CSSSnippet} a
  3824. * @param {CSSSnippet} b
  3825. * @return {Number}
  3826. */
  3827. function snippetsSort(a, b) {
  3828. if (a.key === b.key) {
  3829. return 0;
  3830. }
  3831. return a.key < b.key ? -1 : 1;
  3832. }
  3833. /**
  3834. * Check if given string is a keyword candidate
  3835. * @param {String} str
  3836. * @return {Boolean}
  3837. */
  3838. function isKeyword$1(str) {
  3839. return /^\s*[\w\-]+/.test(str);
  3840. }
  3841. function splitValue(value) {
  3842. return String(value).split('|');
  3843. }
  3844. const globalKeywords = ['auto', 'inherit', 'unset'];
  3845. const unitlessProperties = [
  3846. 'z-index', 'line-height', 'opacity', 'font-weight', 'zoom',
  3847. 'flex', 'flex-grow', 'flex-shrink'
  3848. ];
  3849. const defaultOptions$3 = {
  3850. intUnit: 'px',
  3851. floatUnit: 'em',
  3852. unitAliases: {
  3853. e :'em',
  3854. p: '%',
  3855. x: 'ex',
  3856. r: 'rem'
  3857. },
  3858. fuzzySearchMinScore: 0
  3859. };
  3860. /**
  3861. * For every node in given `tree`, finds matching snippet from `registry` and
  3862. * updates node with snippet data.
  3863. *
  3864. * This resolver uses fuzzy matching for searching matched snippets and their
  3865. * keyword values.
  3866. */
  3867. var index$5 = function(tree, registry, options) {
  3868. const snippets = convertToCSSSnippets(registry);
  3869. options = {
  3870. intUnit: (options && options.intUnit) || defaultOptions$3.intUnit,
  3871. floatUnit: (options && options.floatUnit) || defaultOptions$3.floatUnit,
  3872. unitAliases: Object.assign({}, defaultOptions$3.unitAliases, options && options.unitAliases),
  3873. fuzzySearchMinScore: (options && options.fuzzySearchMinScore) || defaultOptions$3.fuzzySearchMinScore
  3874. };
  3875. tree.walk(node => resolveNode$1(node, snippets, options));
  3876. return tree;
  3877. };
  3878. function convertToCSSSnippets(registry) {
  3879. return cssSnippets(registry.all({type: 'string'}))
  3880. }
  3881. /**
  3882. * Resolves given node: finds matched CSS snippets using fuzzy match and resolves
  3883. * keyword aliases from node value
  3884. * @param {Node} node
  3885. * @param {CSSSnippet[]} snippets
  3886. * @param {Object} options
  3887. * @return {Node}
  3888. */
  3889. function resolveNode$1(node, snippets, options) {
  3890. const snippet = findBestMatch(node.name, snippets, 'key', options.fuzzySearchMinScore);
  3891. if (!snippet) {
  3892. // Edge case: `!important` snippet
  3893. return node.name === '!' ? setNodeAsText(node, '!important') : node;
  3894. }
  3895. return snippet.property
  3896. ? resolveAsProperty(node, snippet, options)
  3897. : resolveAsSnippet(node, snippet);
  3898. }
  3899. /**
  3900. * Resolves given parsed abbreviation node as CSS propery
  3901. * @param {Node} node
  3902. * @param {CSSSnippet} snippet
  3903. * @param {Object} formatOptions
  3904. * @return {Node}
  3905. */
  3906. function resolveAsProperty(node, snippet, formatOptions) {
  3907. const abbr = node.name;
  3908. node.name = snippet.property;
  3909. if (node.value && typeof node.value === 'object') {
  3910. // resolve keyword shortcuts
  3911. const keywords = snippet.keywords();
  3912. if (!node.value.size) {
  3913. // no value defined, try to resolve unmatched part as a keyword alias
  3914. let kw = findBestMatch(getUnmatchedPart(abbr, snippet.key), keywords);
  3915. if (!kw) {
  3916. // no matching value, try to get default one
  3917. kw = snippet.defaulValue;
  3918. if (kw && kw.indexOf('${') === -1) {
  3919. // Quick and dirty test for existing field. If not, wrap
  3920. // default value in a field
  3921. kw = `\${1:${kw}}`;
  3922. }
  3923. }
  3924. if (kw) {
  3925. node.value.add(kw);
  3926. }
  3927. } else {
  3928. // replace keyword aliases in current node value
  3929. for (let i = 0, token; i < node.value.value.length; i++) {
  3930. token = node.value.value[i];
  3931. if (token === '!') {
  3932. token = `${!i ? '${1} ' : ''}!important`;
  3933. } else if (isKeyword$2(token)) {
  3934. token = findBestMatch(token.value, keywords)
  3935. || findBestMatch(token.value, globalKeywords)
  3936. || token;
  3937. } else if (isNumericValue(token)) {
  3938. token = resolveNumericValue(node.name, token, formatOptions);
  3939. }
  3940. node.value.value[i] = token;
  3941. }
  3942. }
  3943. }
  3944. return node;
  3945. }
  3946. /**
  3947. * Resolves given parsed abbreviation node as a snippet: a plain code chunk
  3948. * @param {Node} node
  3949. * @param {CSSSnippet} snippet
  3950. * @return {Node}
  3951. */
  3952. function resolveAsSnippet(node, snippet) {
  3953. return setNodeAsText(node, snippet.value);
  3954. }
  3955. /**
  3956. * Sets given parsed abbreviation node as a text snippet
  3957. * @param {Node} node
  3958. * @param {String} text
  3959. * @return {Node}
  3960. */
  3961. function setNodeAsText(node, text) {
  3962. node.name = null;
  3963. node.value = text;
  3964. return node;
  3965. }
  3966. /**
  3967. * Finds best matching item from `items` array
  3968. * @param {String} abbr Abbreviation to match
  3969. * @param {Array} items List of items for match
  3970. * @param {String} [key] If `items` is a list of objects, use `key` as object
  3971. * property to test against
  3972. * @param {Number} fuzzySearchMinScore The minimum score the best matched item should have to be a valid match.
  3973. * @return {*}
  3974. */
  3975. function findBestMatch(abbr, items, key, fuzzySearchMinScore) {
  3976. if (!abbr) {
  3977. return null;
  3978. }
  3979. let matchedItem = null;
  3980. let maxScore = 0;
  3981. fuzzySearchMinScore = fuzzySearchMinScore || 0;
  3982. for (let i = 0, item; i < items.length; i++) {
  3983. item = items[i];
  3984. const score = stringScore(abbr, getScoringPart(item, key));
  3985. if (score === 1) {
  3986. // direct hit, no need to look further
  3987. return item;
  3988. }
  3989. if (score && score >= maxScore) {
  3990. maxScore = score;
  3991. matchedItem = item;
  3992. }
  3993. }
  3994. return maxScore >= fuzzySearchMinScore ? matchedItem : null;
  3995. }
  3996. function getScoringPart(item, key) {
  3997. const value = item && typeof item === 'object' ? item[key] : item;
  3998. const m = (value || '').match(/^[\w-@]+/);
  3999. return m ? m[0] : value;
  4000. }
  4001. /**
  4002. * Returns a part of `abbr` that wasn’t directly matched agains `string`.
  4003. * For example, if abbreviation `poas` is matched against `position`, the unmatched part will be `as`
  4004. * since `a` wasn’t found in string stream
  4005. * @param {String} abbr
  4006. * @param {String} string
  4007. * @return {String}
  4008. */
  4009. function getUnmatchedPart(abbr, string) {
  4010. for (let i = 0, lastPos = 0; i < abbr.length; i++) {
  4011. lastPos = string.indexOf(abbr[i], lastPos);
  4012. if (lastPos === -1) {
  4013. return abbr.slice(i);
  4014. }
  4015. lastPos++;
  4016. }
  4017. return '';
  4018. }
  4019. /**
  4020. * Check if given CSS value token is a keyword
  4021. * @param {*} token
  4022. * @return {Boolean}
  4023. */
  4024. function isKeyword$2(token) {
  4025. return tokenTypeOf(token, 'keyword');
  4026. }
  4027. /**
  4028. * Check if given CSS value token is a numeric value
  4029. * @param {*} token
  4030. * @return {Boolean}
  4031. */
  4032. function isNumericValue(token) {
  4033. return tokenTypeOf(token, 'numeric');
  4034. }
  4035. function tokenTypeOf(token, type) {
  4036. return token && typeof token === 'object' && token.type === type;
  4037. }
  4038. /**
  4039. * Resolves numeric value for given CSS property
  4040. * @param {String} property CSS property name
  4041. * @param {NumericValue} token CSS numeric value token
  4042. * @param {Object} formatOptions Formatting options for units
  4043. * @return {NumericValue}
  4044. */
  4045. function resolveNumericValue(property, token, formatOptions) {
  4046. if (token.unit) {
  4047. token.unit = formatOptions.unitAliases[token.unit] || token.unit;
  4048. } else if (token.value !== 0 && unitlessProperties.indexOf(property) === -1) {
  4049. // use `px` for integers, `em` for floats
  4050. // NB: num|0 is a quick alternative to Math.round(0)
  4051. token.unit = token.value === (token.value|0) ? formatOptions.intUnit : formatOptions.floatUnit;
  4052. }
  4053. return token;
  4054. }
  4055. const defaultOptions$4 = {
  4056. shortHex: true,
  4057. format: {
  4058. between: ': ',
  4059. after: ';'
  4060. }
  4061. };
  4062. /**
  4063. * Renders given parsed Emmet CSS abbreviation as CSS-like
  4064. * stylesheet, formatted according to `profile` options
  4065. * @param {Node} tree Parsed Emmet abbreviation
  4066. * @param {Profile} profile Output profile
  4067. * @param {Object} [options] Additional formatter options
  4068. * @return {String}
  4069. */
  4070. function css(tree, profile, options) {
  4071. options = Object.assign({}, defaultOptions$4, options);
  4072. return render(tree, options.field, outNode => {
  4073. const node = outNode.node;
  4074. let value = String(node.value || '');
  4075. if (node.attributes.length) {
  4076. const fieldValues = node.attributes.map(attr => stringifyAttribute(attr, options));
  4077. value = injectFields(value, fieldValues);
  4078. }
  4079. outNode.open = node.name && profile.name(node.name);
  4080. outNode.afterOpen = options.format.between;
  4081. outNode.text = outNode.renderFields(value || null);
  4082. if (outNode.open && (!outNode.text || !outNode.text.endsWith(';'))) {
  4083. outNode.afterText = options.format.after;
  4084. }
  4085. if (profile.get('format')) {
  4086. outNode.newline = '\n';
  4087. if (tree.lastChild !== node) {
  4088. outNode.afterText += outNode.newline;
  4089. }
  4090. }
  4091. return outNode;
  4092. });
  4093. }
  4094. /**
  4095. * Injects given field values at each field of given string
  4096. * @param {String} string
  4097. * @param {String[]} attributes
  4098. * @return {FieldString}
  4099. */
  4100. function injectFields(string, values) {
  4101. const fieldsModel = parse$2(string);
  4102. const fieldsAmount = fieldsModel.fields.length;
  4103. if (fieldsAmount) {
  4104. values = values.slice();
  4105. if (values.length > fieldsAmount) {
  4106. // More values that output fields: collapse rest values into
  4107. // a single token
  4108. values = values.slice(0, fieldsAmount - 1)
  4109. .concat(values.slice(fieldsAmount - 1).join(', '));
  4110. }
  4111. while (values.length) {
  4112. const value = values.shift();
  4113. const field = fieldsModel.fields.shift();
  4114. const delta = value.length - field.length;
  4115. fieldsModel.string = fieldsModel.string.slice(0, field.location)
  4116. + value
  4117. + fieldsModel.string.slice(field.location + field.length);
  4118. // Update location of the rest fields in string
  4119. for (let i = 0, il = fieldsModel.fields.length; i < il; i++) {
  4120. fieldsModel.fields[i].location += delta;
  4121. }
  4122. }
  4123. }
  4124. return fieldsModel;
  4125. }
  4126. function stringifyAttribute(attr, options) {
  4127. if (attr.value && typeof attr.value === 'object' && attr.value.type === 'css-value') {
  4128. return attr.value.value
  4129. .map(token => {
  4130. if (token && typeof token === 'object') {
  4131. return token.type === 'color'
  4132. ? token.toString(options.shortHex)
  4133. : token.toString();
  4134. }
  4135. return String(token);
  4136. })
  4137. .join(' ');
  4138. }
  4139. return attr.value != null ? String(attr.value) : '';
  4140. }
  4141. const syntaxFormat = {
  4142. css: {
  4143. between: ': ',
  4144. after: ';'
  4145. },
  4146. scss: 'css',
  4147. less: 'css',
  4148. sass: {
  4149. between: ': ',
  4150. after: ''
  4151. },
  4152. stylus: {
  4153. between: ' ',
  4154. after: ''
  4155. }
  4156. };
  4157. /**
  4158. * Outputs given parsed abbreviation in specified stylesheet syntax
  4159. * @param {Node} tree Parsed abbreviation tree
  4160. * @param {Profile} profile Output profile
  4161. * @param {String} [syntax] Output syntax. If not given, `css` syntax is used
  4162. * @param {Function} options.field A function to output field/tabstop for
  4163. * host editor. This function takes two arguments: `index` and `placeholder` and
  4164. * should return a string that represents tabstop in host editor. By default
  4165. * only a placeholder is returned
  4166. * @example
  4167. * {
  4168. * field(index, placeholder) {
  4169. * // return field in TextMate-style, e.g. ${1} or ${2:foo}
  4170. * return `\${${index}${placeholder ? ':' + placeholder : ''}}`;
  4171. * }
  4172. * }
  4173. * @return {String}
  4174. */
  4175. var index$6 = function(tree, profile, syntax, options) {
  4176. if (typeof syntax === 'object') {
  4177. options = syntax;
  4178. syntax = null;
  4179. }
  4180. if (!supports$1(syntax)) {
  4181. // fallback to CSS if given syntax is not supported
  4182. syntax = 'css';
  4183. }
  4184. options = Object.assign({}, options, {
  4185. format: getFormat(syntax, options)
  4186. });
  4187. // CSS abbreviations doesn’t support nesting so simply
  4188. // output root node children
  4189. return css(tree, profile, options);
  4190. };
  4191. /**
  4192. * Check if given syntax is supported
  4193. * @param {String} syntax
  4194. * @return {Boolean}
  4195. */
  4196. function supports$1(syntax) {
  4197. return !!syntax && syntax in syntaxFormat;
  4198. }
  4199. /**
  4200. * Returns formatter object for given syntax
  4201. * @param {String} syntax
  4202. * @param {Object} [options]
  4203. * @return {Object} Formatter object as defined in `syntaxFormat`
  4204. */
  4205. function getFormat(syntax, options) {
  4206. let format = syntaxFormat[syntax];
  4207. if (typeof format === 'string') {
  4208. format = syntaxFormat[format];
  4209. }
  4210. return Object.assign({}, format, options && options.stylesheet);
  4211. }
  4212. /**
  4213. * Expands given abbreviation into code
  4214. * @param {String|Node} abbr Abbreviation to parse or already parsed abbreviation
  4215. * @param {Object} options
  4216. * @return {String}
  4217. */
  4218. function expand$1(abbr, options) {
  4219. options = options || {};
  4220. if (typeof abbr === 'string') {
  4221. abbr = parse$4(abbr, options);
  4222. }
  4223. return index$6(abbr, options.profile, options.syntax, options.format);
  4224. }
  4225. /**
  4226. * Parses given Emmet abbreviation into a final abbreviation tree with all
  4227. * required transformations applied
  4228. * @param {String|Node} abbr Abbreviation to parse or already parsed abbreviation
  4229. * @param {Object} options
  4230. * @return {Node}
  4231. */
  4232. function parse$4(abbr, options) {
  4233. if (typeof abbr === 'string') {
  4234. abbr = index$4(abbr);
  4235. }
  4236. return abbr.use(index$5, options.snippets, options.format ? options.format.stylesheet : {});
  4237. }
  4238. var html$1 = {
  4239. "a": "a[href]",
  4240. "a:blank": "a[href='http://${0}' target='_blank' rel='noopener noreferrer']",
  4241. "a:link": "a[href='http://${0}']",
  4242. "a:mail": "a[href='mailto:${0}']",
  4243. "a:tel": "a[href='tel:+${0}']",
  4244. "abbr": "abbr[title]",
  4245. "acr|acronym": "acronym[title]",
  4246. "base": "base[href]/",
  4247. "basefont": "basefont/",
  4248. "br": "br/",
  4249. "frame": "frame/",
  4250. "hr": "hr/",
  4251. "bdo": "bdo[dir]",
  4252. "bdo:r": "bdo[dir=rtl]",
  4253. "bdo:l": "bdo[dir=ltr]",
  4254. "col": "col/",
  4255. "link": "link[rel=stylesheet href]/",
  4256. "link:css": "link[href='${1:style}.css']",
  4257. "link:print": "link[href='${1:print}.css' media=print]",
  4258. "link:favicon": "link[rel='shortcut icon' type=image/x-icon href='${1:favicon.ico}']",
  4259. "link:mf|link:manifest": "link[rel='manifest' href='${1:manifest.json}']",
  4260. "link:touch": "link[rel=apple-touch-icon href='${1:favicon.png}']",
  4261. "link:rss": "link[rel=alternate type=application/rss+xml title=RSS href='${1:rss.xml}']",
  4262. "link:atom": "link[rel=alternate type=application/atom+xml title=Atom href='${1:atom.xml}']",
  4263. "link:im|link:import": "link[rel=import href='${1:component}.html']",
  4264. "meta": "meta/",
  4265. "meta:utf": "meta[http-equiv=Content-Type content='text/html;charset=UTF-8']",
  4266. "meta:vp": "meta[name=viewport content='width=${1:device-width}, initial-scale=${2:1.0}']",
  4267. "meta:compat": "meta[http-equiv=X-UA-Compatible content='${1:IE=7}']",
  4268. "meta:edge": "meta:compat[content='${1:ie=edge}']",
  4269. "meta:redirect": "meta[http-equiv=refresh content='0; url=${1:http://example.com}']",
  4270. "meta:kw": "meta[name=keywords content]",
  4271. "meta:desc": "meta[name=description content]",
  4272. "style": "style",
  4273. "script": "script",
  4274. "script:src": "script[src]",
  4275. "img": "img[src alt]/",
  4276. "img:s|img:srcset": "img[srcset src alt]",
  4277. "img:z|img:sizes": "img[sizes srcset src alt]",
  4278. "picture": "picture",
  4279. "src|source": "source/",
  4280. "src:sc|source:src": "source[src type]",
  4281. "src:s|source:srcset": "source[srcset]",
  4282. "src:t|source:type": "source[srcset type='${1:image/}']",
  4283. "src:z|source:sizes": "source[sizes srcset]",
  4284. "src:m|source:media": "source[media='(${1:min-width: })' srcset]",
  4285. "src:mt|source:media:type": "source:media[type='${2:image/}']",
  4286. "src:mz|source:media:sizes": "source:media[sizes srcset]",
  4287. "src:zt|source:sizes:type": "source[sizes srcset type='${1:image/}']",
  4288. "iframe": "iframe[src frameborder=0]",
  4289. "embed": "embed[src type]/",
  4290. "object": "object[data type]",
  4291. "param": "param[name value]/",
  4292. "map": "map[name]",
  4293. "area": "area[shape coords href alt]/",
  4294. "area:d": "area[shape=default]",
  4295. "area:c": "area[shape=circle]",
  4296. "area:r": "area[shape=rect]",
  4297. "area:p": "area[shape=poly]",
  4298. "form": "form[action]",
  4299. "form:get": "form[method=get]",
  4300. "form:post": "form[method=post]",
  4301. "label": "label[for]",
  4302. "input": "input[type=${1:text}]/",
  4303. "inp": "input[name=${1} id=${1}]",
  4304. "input:h|input:hidden": "input[type=hidden name]",
  4305. "input:t|input:text": "inp[type=text]",
  4306. "input:search": "inp[type=search]",
  4307. "input:email": "inp[type=email]",
  4308. "input:url": "inp[type=url]",
  4309. "input:p|input:password": "inp[type=password]",
  4310. "input:datetime": "inp[type=datetime]",
  4311. "input:date": "inp[type=date]",
  4312. "input:datetime-local": "inp[type=datetime-local]",
  4313. "input:month": "inp[type=month]",
  4314. "input:week": "inp[type=week]",
  4315. "input:time": "inp[type=time]",
  4316. "input:tel": "inp[type=tel]",
  4317. "input:number": "inp[type=number]",
  4318. "input:color": "inp[type=color]",
  4319. "input:c|input:checkbox": "inp[type=checkbox]",
  4320. "input:r|input:radio": "inp[type=radio]",
  4321. "input:range": "inp[type=range]",
  4322. "input:f|input:file": "inp[type=file]",
  4323. "input:s|input:submit": "input[type=submit value]",
  4324. "input:i|input:image": "input[type=image src alt]",
  4325. "input:b|input:button": "input[type=button value]",
  4326. "input:reset": "input:button[type=reset]",
  4327. "isindex": "isindex/",
  4328. "select": "select[name=${1} id=${1}]",
  4329. "select:d|select:disabled": "select[disabled.]",
  4330. "opt|option": "option[value]",
  4331. "textarea": "textarea[name=${1} id=${1} cols=${2:30} rows=${3:10}]",
  4332. "marquee": "marquee[behavior direction]",
  4333. "menu:c|menu:context": "menu[type=context]",
  4334. "menu:t|menu:toolbar": "menu[type=toolbar]",
  4335. "video": "video[src]",
  4336. "audio": "audio[src]",
  4337. "html:xml": "html[xmlns=http://www.w3.org/1999/xhtml]",
  4338. "keygen": "keygen/",
  4339. "command": "command/",
  4340. "btn:s|button:s|button:submit" : "button[type=submit]",
  4341. "btn:r|button:r|button:reset" : "button[type=reset]",
  4342. "btn:d|button:d|button:disabled" : "button[disabled.]",
  4343. "fst:d|fset:d|fieldset:d|fieldset:disabled" : "fieldset[disabled.]",
  4344. "bq": "blockquote",
  4345. "fig": "figure",
  4346. "figc": "figcaption",
  4347. "pic": "picture",
  4348. "ifr": "iframe",
  4349. "emb": "embed",
  4350. "obj": "object",
  4351. "cap": "caption",
  4352. "colg": "colgroup",
  4353. "fst": "fieldset",
  4354. "btn": "button",
  4355. "optg": "optgroup",
  4356. "tarea": "textarea",
  4357. "leg": "legend",
  4358. "sect": "section",
  4359. "art": "article",
  4360. "hdr": "header",
  4361. "ftr": "footer",
  4362. "adr": "address",
  4363. "dlg": "dialog",
  4364. "str": "strong",
  4365. "prog": "progress",
  4366. "mn": "main",
  4367. "tem": "template",
  4368. "fset": "fieldset",
  4369. "datag": "datagrid",
  4370. "datal": "datalist",
  4371. "kg": "keygen",
  4372. "out": "output",
  4373. "det": "details",
  4374. "cmd": "command",
  4375. "ri:d|ri:dpr": "img:s",
  4376. "ri:v|ri:viewport": "img:z",
  4377. "ri:a|ri:art": "pic>src:m+img",
  4378. "ri:t|ri:type": "pic>src:t+img",
  4379. "!!!": "{<!DOCTYPE html>}",
  4380. "doc": "html[lang=${lang}]>(head>meta[charset=${charset}]+meta:vp+title{${1:Document}})+body",
  4381. "!|html:5": "!!!+doc",
  4382. "c": "{<!-- ${0} -->}",
  4383. "cc:ie": "{<!--[if IE]>${0}<![endif]-->}",
  4384. "cc:noie": "{<!--[if !IE]><!-->${0}<!--<![endif]-->}"
  4385. };
  4386. var css$1 = {
  4387. "@f": "@font-face {\n\tfont-family: ${1};\n\tsrc: url(${1});\n}",
  4388. "@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}",
  4389. "@i|@import": "@import url(${0});",
  4390. "@kf": "@keyframes ${1:identifier} {\n\t${2}\n}",
  4391. "@m|@media": "@media ${1:screen} {\n\t${0}\n}",
  4392. "ac": "align-content:start|end|flex-start|flex-end|center|space-between|space-around|stretch|space-evenly",
  4393. "ai": "align-items:start|end|flex-start|flex-end|center|baseline|stretch",
  4394. "anim": "animation:${1:name} ${2:duration} ${3:timing-function} ${4:delay} ${5:iteration-count} ${6:direction} ${7:fill-mode}",
  4395. "animdel": "animation-delay:time",
  4396. "animdir": "animation-direction:normal|reverse|alternate|alternate-reverse",
  4397. "animdur": "animation-duration:${1:0}s",
  4398. "animfm": "animation-fill-mode:both|forwards|backwards",
  4399. "animic": "animation-iteration-count:1|infinite",
  4400. "animn": "animation-name",
  4401. "animps": "animation-play-state:running|paused",
  4402. "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})",
  4403. "ap": "appearance:none",
  4404. "as": "align-self:start|end|auto|flex-start|flex-end|center|baseline|stretch",
  4405. "b": "bottom",
  4406. "bd": "border:${1:1px} ${2:solid} ${3:#000}",
  4407. "bdb": "border-bottom:${1:1px} ${2:solid} ${3:#000}",
  4408. "bdbc": "border-bottom-color:${1:#000}",
  4409. "bdbi": "border-bottom-image:url(${0})",
  4410. "bdbk": "border-break:close",
  4411. "bdbli": "border-bottom-left-image:url(${0})|continue",
  4412. "bdblrs": "border-bottom-left-radius",
  4413. "bdbri": "border-bottom-right-image:url(${0})|continue",
  4414. "bdbrrs": "border-bottom-right-radius",
  4415. "bdbs": "border-bottom-style",
  4416. "bdbw": "border-bottom-width",
  4417. "bdc": "border-color:${1:#000}",
  4418. "bdci": "border-corner-image:url(${0})|continue",
  4419. "bdcl": "border-collapse:collapse|separate",
  4420. "bdf": "border-fit:repeat|clip|scale|stretch|overwrite|overflow|space",
  4421. "bdi": "border-image:url(${0})",
  4422. "bdl": "border-left:${1:1px} ${2:solid} ${3:#000}",
  4423. "bdlc": "border-left-color:${1:#000}",
  4424. "bdlen": "border-length",
  4425. "bdli": "border-left-image:url(${0})",
  4426. "bdls": "border-left-style",
  4427. "bdlw": "border-left-width",
  4428. "bdr": "border-right:${1:1px} ${2:solid} ${3:#000}",
  4429. "bdrc": "border-right-color:${1:#000}",
  4430. "bdri": "border-right-image:url(${0})",
  4431. "bdrs": "border-radius",
  4432. "bdrst": "border-right-style",
  4433. "bdrw": "border-right-width",
  4434. "bds": "border-style:none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset",
  4435. "bdsp": "border-spacing",
  4436. "bdt": "border-top:${1:1px} ${2:solid} ${3:#000}",
  4437. "bdtc": "border-top-color:${1:#000}",
  4438. "bdti": "border-top-image:url(${0})",
  4439. "bdtli": "border-top-left-image:url(${0})|continue",
  4440. "bdtlrs": "border-top-left-radius",
  4441. "bdtri": "border-top-right-image:url(${0})|continue",
  4442. "bdtrrs": "border-top-right-radius",
  4443. "bdts": "border-top-style",
  4444. "bdtw": "border-top-width",
  4445. "bdw": "border-width",
  4446. "bfv": "backface-visibility:hidden|visible",
  4447. "bg": "background:${1:#000}",
  4448. "bga": "background-attachment:fixed|scroll",
  4449. "bgbk": "background-break:bounding-box|each-box|continuous",
  4450. "bgc": "background-color:#${1:fff}",
  4451. "bgcp": "background-clip:padding-box|border-box|content-box|no-clip",
  4452. "bgi": "background-image:url(${0})",
  4453. "bgo": "background-origin:padding-box|border-box|content-box",
  4454. "bgp": "background-position:${1:0} ${2:0}",
  4455. "bgpx": "background-position-x",
  4456. "bgpy": "background-position-y",
  4457. "bgr": "background-repeat:no-repeat|repeat-x|repeat-y|space|round",
  4458. "bgsz": "background-size:contain|cover",
  4459. "bxsh": "box-shadow:${1:inset }${2:hoff} ${3:voff} ${4:blur} ${5:#000}|none",
  4460. "bxsz": "box-sizing:border-box|content-box|border-box",
  4461. "c": "color:${1:#000}",
  4462. "cl": "clear:both|left|right|none",
  4463. "cm": "/* ${0} */",
  4464. "cnt": "content:'${0}'|normal|open-quote|no-open-quote|close-quote|no-close-quote|attr(${0})|counter(${0})|counters(${0})",
  4465. "coi": "counter-increment",
  4466. "colm": "columns",
  4467. "colmc": "column-count",
  4468. "colmf": "column-fill",
  4469. "colmg": "column-gap",
  4470. "colmr": "column-rule",
  4471. "colmrc": "column-rule-color",
  4472. "colmrs": "column-rule-style",
  4473. "colmrw": "column-rule-width",
  4474. "colms": "column-span",
  4475. "colmw": "column-width",
  4476. "cor": "counter-reset",
  4477. "cp": "clip:auto|rect(${1:top} ${2:right} ${3:bottom} ${4:left})",
  4478. "cps": "caption-side:top|bottom",
  4479. "cur": "cursor:pointer|auto|default|crosshair|hand|help|move|pointer|text",
  4480. "d": "display:grid|inline-grid|subgrid|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",
  4481. "ec": "empty-cells:show|hide",
  4482. "f": "font:${1:1em} ${2:sans-serif}",
  4483. "fd": "font-display:auto|block|swap|fallback|optional",
  4484. "fef": "font-effect:none|engrave|emboss|outline",
  4485. "fem": "font-emphasize",
  4486. "femp": "font-emphasize-position:before|after",
  4487. "fems": "font-emphasize-style:none|accent|dot|circle|disc",
  4488. "ff": "font-family:serif|sans-serif|cursive|fantasy|monospace",
  4489. "fft": "font-family:\"Times New Roman\", Times, Baskerville, Georgia, serif",
  4490. "ffa": "font-family:Arial, \"Helvetica Neue\", Helvetica, sans-serif",
  4491. "ffv": "font-family:Verdana, Geneva, sans-serif",
  4492. "fl": "float:left|right|none",
  4493. "fs": "font-style:italic|normal|oblique",
  4494. "fsm": "font-smoothing:antialiased|subpixel-antialiased|none",
  4495. "fst": "font-stretch:normal|ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded",
  4496. "fv": "font-variant:normal|small-caps",
  4497. "fvs": "font-variation-settings:normal|inherit|initial|unset",
  4498. "fw": "font-weight:normal|bold|bolder|lighter",
  4499. "fx": "flex",
  4500. "fxb": "flex-basis:fill|max-content|min-content|fit-content|content",
  4501. "fxd": "flex-direction:row|row-reverse|column|column-reverse",
  4502. "fxf": "flex-flow",
  4503. "fxg": "flex-grow",
  4504. "fxsh": "flex-shrink",
  4505. "fxw": "flex-wrap:nowrap|wrap|wrap-reverse",
  4506. "fsz": "font-size",
  4507. "fsza": "font-size-adjust",
  4508. "gtc": "grid-template-columns:repeat()|minmax()",
  4509. "gtr": "grid-template-rows:repeat()|minmax()",
  4510. "gta": "grid-template-areas",
  4511. "gt": "grid-template",
  4512. "gg": "grid-gap",
  4513. "gcg": "grid-column-gap",
  4514. "grg": "grid-row-gap",
  4515. "gac": "grid-auto-columns:auto|minmax()",
  4516. "gar": "grid-auto-rows:auto|minmax()",
  4517. "gaf": "grid-auto-flow:row|column|dense|inherit|initial|unset",
  4518. "gd": "grid",
  4519. "gc": "grid-column",
  4520. "gcs": "grid-column-start",
  4521. "gce": "grid-column-end",
  4522. "gr": "grid-row",
  4523. "grs": "grid-row-start",
  4524. "gre": "grid-row-end",
  4525. "ga": "grid-area",
  4526. "h": "height",
  4527. "jc": "justify-content:start|end|stretch|flex-start|flex-end|center|space-between|space-around|space-evenly",
  4528. "ji": "justify-items:start|end|center|stretch",
  4529. "js": "justify-self:start|end|center|stretch",
  4530. "l": "left",
  4531. "lg": "background-image:linear-gradient(${1})",
  4532. "lh": "line-height",
  4533. "lis": "list-style",
  4534. "lisi": "list-style-image",
  4535. "lisp": "list-style-position:inside|outside",
  4536. "list": "list-style-type:disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman",
  4537. "lts": "letter-spacing:normal",
  4538. "m": "margin",
  4539. "mah": "max-height",
  4540. "mar": "max-resolution",
  4541. "maw": "max-width",
  4542. "mb": "margin-bottom",
  4543. "mih": "min-height",
  4544. "mir": "min-resolution",
  4545. "miw": "min-width",
  4546. "ml": "margin-left",
  4547. "mr": "margin-right",
  4548. "mt": "margin-top",
  4549. "ol": "outline",
  4550. "olc": "outline-color:${1:#000}|invert",
  4551. "olo": "outline-offset",
  4552. "ols": "outline-style:none|dotted|dashed|solid|double|groove|ridge|inset|outset",
  4553. "olw": "outline-width|thin|medium|thick",
  4554. "op": "opacity",
  4555. "ord": "order",
  4556. "ori": "orientation:landscape|portrait",
  4557. "orp": "orphans",
  4558. "ov": "overflow:hidden|visible|hidden|scroll|auto",
  4559. "ovs": "overflow-style:scrollbar|auto|scrollbar|panner|move|marquee",
  4560. "ovx": "overflow-x:hidden|visible|hidden|scroll|auto",
  4561. "ovy": "overflow-y:hidden|visible|hidden|scroll|auto",
  4562. "p": "padding",
  4563. "pb": "padding-bottom",
  4564. "pgba": "page-break-after:auto|always|left|right",
  4565. "pgbb": "page-break-before:auto|always|left|right",
  4566. "pgbi": "page-break-inside:auto|avoid",
  4567. "pl": "padding-left",
  4568. "pos": "position:relative|absolute|relative|fixed|static",
  4569. "pr": "padding-right",
  4570. "pt": "padding-top",
  4571. "q": "quotes",
  4572. "qen": "quotes:'\\201C' '\\201D' '\\2018' '\\2019'",
  4573. "qru": "quotes:'\\00AB' '\\00BB' '\\201E' '\\201C'",
  4574. "r": "right",
  4575. "rsz": "resize:none|both|horizontal|vertical",
  4576. "t": "top",
  4577. "ta": "text-align:left|center|right|justify",
  4578. "tal": "text-align-last:left|center|right",
  4579. "tbl": "table-layout:fixed",
  4580. "td": "text-decoration:none|underline|overline|line-through",
  4581. "te": "text-emphasis:none|accent|dot|circle|disc|before|after",
  4582. "th": "text-height:auto|font-size|text-size|max-size",
  4583. "ti": "text-indent",
  4584. "tj": "text-justify:auto|inter-word|inter-ideograph|inter-cluster|distribute|kashida|tibetan",
  4585. "to": "text-outline:${1:0} ${2:0} ${3:#000}",
  4586. "tov": "text-overflow:ellipsis|clip",
  4587. "tr": "text-replace",
  4588. "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})",
  4589. "trfo": "transform-origin",
  4590. "trfs": "transform-style:preserve-3d",
  4591. "trs": "transition:${1:prop} ${2:time}",
  4592. "trsde": "transition-delay:${1:time}",
  4593. "trsdu": "transition-duration:${1:time}",
  4594. "trsp": "transition-property:${1:prop}",
  4595. "trstf": "transition-timing-function:${1:fn}",
  4596. "tsh": "text-shadow:${1:hoff} ${2:voff} ${3:blur} ${4:#000}",
  4597. "tt": "text-transform:uppercase|lowercase|capitalize|none",
  4598. "tw": "text-wrap:none|normal|unrestricted|suppress",
  4599. "us": "user-select:none",
  4600. "v": "visibility:hidden|visible|collapse",
  4601. "va": "vertical-align:top|super|text-top|middle|baseline|bottom|text-bottom|sub",
  4602. "w": "width",
  4603. "whs": "white-space:nowrap|pre|pre-wrap|pre-line|normal",
  4604. "whsc": "white-space-collapse:normal|keep-all|loose|break-strict|break-all",
  4605. "wid": "widows",
  4606. "wm": "writing-mode:lr-tb|lr-tb|lr-bt|rl-tb|rl-bt|tb-rl|tb-lr|bt-lr|bt-rl",
  4607. "wob": "word-break:normal|keep-all|break-all",
  4608. "wos": "word-spacing",
  4609. "wow": "word-wrap:none|unrestricted|suppress|break-word|normal",
  4610. "z": "z-index",
  4611. "zom": "zoom:1"
  4612. };
  4613. var xsl$1 = {
  4614. "tm|tmatch": "xsl:template[match mode]",
  4615. "tn|tname": "xsl:template[name]",
  4616. "call": "xsl:call-template[name]",
  4617. "ap": "xsl:apply-templates[select mode]",
  4618. "api": "xsl:apply-imports",
  4619. "imp": "xsl:import[href]",
  4620. "inc": "xsl:include[href]",
  4621. "ch": "xsl:choose",
  4622. "wh|xsl:when": "xsl:when[test]",
  4623. "ot": "xsl:otherwise",
  4624. "if": "xsl:if[test]",
  4625. "par": "xsl:param[name]",
  4626. "pare": "xsl:param[name select]",
  4627. "var": "xsl:variable[name]",
  4628. "vare": "xsl:variable[name select]",
  4629. "wp": "xsl:with-param[name select]",
  4630. "key": "xsl:key[name match use]",
  4631. "elem": "xsl:element[name]",
  4632. "attr": "xsl:attribute[name]",
  4633. "attrs": "xsl:attribute-set[name]",
  4634. "cp": "xsl:copy[select]",
  4635. "co": "xsl:copy-of[select]",
  4636. "val": "xsl:value-of[select]",
  4637. "for|each": "xsl:for-each[select]",
  4638. "tex": "xsl:text",
  4639. "com": "xsl:comment",
  4640. "msg": "xsl:message[terminate=no]",
  4641. "fall": "xsl:fallback",
  4642. "num": "xsl:number[value]",
  4643. "nam": "namespace-alias[stylesheet-prefix result-prefix]",
  4644. "pres": "xsl:preserve-space[elements]",
  4645. "strip": "xsl:strip-space[elements]",
  4646. "proc": "xsl:processing-instruction[name]",
  4647. "sort": "xsl:sort[select order]",
  4648. "choose": "xsl:choose>xsl:when+xsl:otherwise",
  4649. "xsl": "!!!+xsl:stylesheet[version=1.0 xmlns:xsl=http://www.w3.org/1999/XSL/Transform]>{\n|}",
  4650. "!!!": "{<?xml version=\"1.0\" encoding=\"UTF-8\"?>}"
  4651. };
  4652. var index$7 = { html: html$1, css: css$1, xsl: xsl$1 };
  4653. var latin = {
  4654. "common": ["lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipisicing", "elit"],
  4655. "words": ["exercitationem", "perferendis", "perspiciatis", "laborum", "eveniet",
  4656. "sunt", "iure", "nam", "nobis", "eum", "cum", "officiis", "excepturi",
  4657. "odio", "consectetur", "quasi", "aut", "quisquam", "vel", "eligendi",
  4658. "itaque", "non", "odit", "tempore", "quaerat", "dignissimos",
  4659. "facilis", "neque", "nihil", "expedita", "vitae", "vero", "ipsum",
  4660. "nisi", "animi", "cumque", "pariatur", "velit", "modi", "natus",
  4661. "iusto", "eaque", "sequi", "illo", "sed", "ex", "et", "voluptatibus",
  4662. "tempora", "veritatis", "ratione", "assumenda", "incidunt", "nostrum",
  4663. "placeat", "aliquid", "fuga", "provident", "praesentium", "rem",
  4664. "necessitatibus", "suscipit", "adipisci", "quidem", "possimus",
  4665. "voluptas", "debitis", "sint", "accusantium", "unde", "sapiente",
  4666. "voluptate", "qui", "aspernatur", "laudantium", "soluta", "amet",
  4667. "quo", "aliquam", "saepe", "culpa", "libero", "ipsa", "dicta",
  4668. "reiciendis", "nesciunt", "doloribus", "autem", "impedit", "minima",
  4669. "maiores", "repudiandae", "ipsam", "obcaecati", "ullam", "enim",
  4670. "totam", "delectus", "ducimus", "quis", "voluptates", "dolores",
  4671. "molestiae", "harum", "dolorem", "quia", "voluptatem", "molestias",
  4672. "magni", "distinctio", "omnis", "illum", "dolorum", "voluptatum", "ea",
  4673. "quas", "quam", "corporis", "quae", "blanditiis", "atque", "deserunt",
  4674. "laboriosam", "earum", "consequuntur", "hic", "cupiditate",
  4675. "quibusdam", "accusamus", "ut", "rerum", "error", "minus", "eius",
  4676. "ab", "ad", "nemo", "fugit", "officia", "at", "in", "id", "quos",
  4677. "reprehenderit", "numquam", "iste", "fugiat", "sit", "inventore",
  4678. "beatae", "repellendus", "magnam", "recusandae", "quod", "explicabo",
  4679. "doloremque", "aperiam", "consequatur", "asperiores", "commodi",
  4680. "optio", "dolor", "labore", "temporibus", "repellat", "veniam",
  4681. "architecto", "est", "esse", "mollitia", "nulla", "a", "similique",
  4682. "eos", "alias", "dolore", "tenetur", "deleniti", "porro", "facere",
  4683. "maxime", "corrupti"]
  4684. };
  4685. var ru = {
  4686. "common": ["далеко-далеко", "за", "словесными", "горами", "в стране", "гласных", "и согласных", "живут", "рыбные", "тексты"],
  4687. "words": ["вдали", "от всех", "они", "буквенных", "домах", "на берегу", "семантика",
  4688. "большого", "языкового", "океана", "маленький", "ручеек", "даль",
  4689. "журчит", "по всей", "обеспечивает", "ее","всеми", "необходимыми",
  4690. "правилами", "эта", "парадигматическая", "страна", "которой", "жаренные",
  4691. "предложения", "залетают", "прямо", "рот", "даже", "всемогущая",
  4692. "пунктуация", "не", "имеет", "власти", "над", "рыбными", "текстами",
  4693. "ведущими", "безорфографичный", "образ", "жизни", "однажды", "одна",
  4694. "маленькая", "строчка","рыбного", "текста", "имени", "lorem", "ipsum",
  4695. "решила", "выйти", "большой", "мир", "грамматики", "великий", "оксмокс",
  4696. "предупреждал", "о", "злых", "запятых", "диких", "знаках", "вопроса",
  4697. "коварных", "точках", "запятой", "но", "текст", "дал", "сбить",
  4698. "себя", "толку", "он", "собрал", "семь", "своих", "заглавных", "букв",
  4699. "подпоясал", "инициал", "за", "пояс", "пустился", "дорогу",
  4700. "взобравшись", "первую", "вершину", "курсивных", "гор", "бросил",
  4701. "последний", "взгляд", "назад", "силуэт", "своего", "родного", "города",
  4702. "буквоград", "заголовок", "деревни", "алфавит", "подзаголовок", "своего",
  4703. "переулка", "грустный", "реторический", "вопрос", "скатился", "его",
  4704. "щеке", "продолжил", "свой", "путь", "дороге", "встретил", "рукопись",
  4705. "она", "предупредила", "моей", "все", "переписывается", "несколько",
  4706. "раз", "единственное", "что", "меня", "осталось", "это", "приставка",
  4707. "возвращайся", "ты", "лучше", "свою", "безопасную", "страну", "послушавшись",
  4708. "рукописи", "наш", "продолжил", "свой", "путь", "вскоре", "ему",
  4709. "повстречался", "коварный", "составитель", "рекламных", "текстов",
  4710. "напоивший", "языком", "речью", "заманивший", "свое", "агентство",
  4711. "которое", "использовало", "снова", "снова", "своих", "проектах",
  4712. "если", "переписали", "то", "живет", "там", "до", "сих", "пор"]
  4713. };
  4714. var sp = {
  4715. "common": ["mujer", "uno", "dolor", "más", "de", "poder", "mismo", "si"],
  4716. "words": ["ejercicio", "preferencia", "perspicacia", "laboral", "paño",
  4717. "suntuoso", "molde", "namibia", "planeador", "mirar", "demás", "oficinista", "excepción",
  4718. "odio", "consecuencia", "casi", "auto", "chicharra", "velo", "elixir",
  4719. "ataque", "no", "odio", "temporal", "cuórum", "dignísimo",
  4720. "facilismo", "letra", "nihilista", "expedición", "alma", "alveolar", "aparte",
  4721. "león", "animal", "como", "paria", "belleza", "modo", "natividad",
  4722. "justo", "ataque", "séquito", "pillo", "sed", "ex", "y", "voluminoso",
  4723. "temporalidad", "verdades", "racional", "asunción", "incidente", "marejada",
  4724. "placenta", "amanecer", "fuga", "previsor", "presentación", "lejos",
  4725. "necesariamente", "sospechoso", "adiposidad", "quindío", "pócima",
  4726. "voluble", "débito", "sintió", "accesorio", "falda", "sapiencia",
  4727. "volutas", "queso", "permacultura", "laudo", "soluciones", "entero",
  4728. "pan", "litro", "tonelada", "culpa", "libertario", "mosca", "dictado",
  4729. "reincidente", "nascimiento", "dolor", "escolar", "impedimento", "mínima",
  4730. "mayores", "repugnante", "dulce", "obcecado", "montaña", "enigma",
  4731. "total", "deletéreo", "décima", "cábala", "fotografía", "dolores",
  4732. "molesto", "olvido", "paciencia", "resiliencia", "voluntad", "molestias",
  4733. "magnífico", "distinción", "ovni", "marejada", "cerro", "torre", "y",
  4734. "abogada", "manantial", "corporal", "agua", "crepúsculo", "ataque", "desierto",
  4735. "laboriosamente", "angustia", "afortunado", "alma", "encefalograma",
  4736. "materialidad", "cosas", "o", "renuncia", "error", "menos", "conejo",
  4737. "abadía", "analfabeto", "remo", "fugacidad", "oficio", "en", "almácigo", "vos", "pan",
  4738. "represión", "números", "triste", "refugiado", "trote", "inventor",
  4739. "corchea", "repelente", "magma", "recusado", "patrón", "explícito",
  4740. "paloma", "síndrome", "inmune", "autoinmune", "comodidad",
  4741. "ley", "vietnamita", "demonio", "tasmania", "repeler", "apéndice",
  4742. "arquitecto", "columna", "yugo", "computador", "mula", "a", "propósito",
  4743. "fantasía", "alias", "rayo", "tenedor", "deleznable", "ventana", "cara",
  4744. "anemia", "corrupto"]
  4745. };
  4746. const langs = { latin, ru, sp };
  4747. const defaultOptions$5 = {
  4748. wordCount: 30,
  4749. skipCommon: false,
  4750. lang: 'latin'
  4751. };
  4752. /**
  4753. * Replaces given parsed Emmet abbreviation node with nodes filled with
  4754. * Lorem Ipsum stub text.
  4755. * @param {Node} node
  4756. * @return {Node}
  4757. */
  4758. var index$8 = function(node, options) {
  4759. options = Object.assign({}, defaultOptions$5, options);
  4760. const dict = langs[options.lang] || langs.latin;
  4761. const startWithCommon = !options.skipCommon && !isRepeating(node);
  4762. if (!node.repeat && !isRoot$1(node.parent)) {
  4763. // non-repeating element, insert text stub as a content of parent node
  4764. // and remove current one
  4765. node.parent.value = paragraph(dict, options.wordCount, startWithCommon);
  4766. node.remove();
  4767. } else {
  4768. // Replace named node with generated content
  4769. node.value = paragraph(dict, options.wordCount, startWithCommon);
  4770. node.name = node.parent.name ? resolveImplicitName(node.parent.name) : null;
  4771. }
  4772. return node;
  4773. };
  4774. function isRoot$1(node) {
  4775. return !node.parent;
  4776. }
  4777. /**
  4778. * Returns random integer between <code>from</code> and <code>to</code> values
  4779. * @param {Number} from
  4780. * @param {Number} to
  4781. * @returns {Number}
  4782. */
  4783. function rand(from, to) {
  4784. return Math.floor(Math.random() * (to - from) + from);
  4785. }
  4786. /**
  4787. * @param {Array} arr
  4788. * @param {Number} count
  4789. * @returns {Array}
  4790. */
  4791. function sample(arr, count) {
  4792. const len = arr.length;
  4793. const iterations = Math.min(len, count);
  4794. const result = new Set();
  4795. while (result.size < iterations) {
  4796. result.add(arr[rand(0, len)]);
  4797. }
  4798. return Array.from(result);
  4799. }
  4800. function choice(val) {
  4801. return val[rand(0, val.length - 1)];
  4802. }
  4803. function sentence(words, end) {
  4804. if (words.length) {
  4805. words = [capitalize(words[0])].concat(words.slice(1));
  4806. }
  4807. return words.join(' ') + (end || choice('?!...')); // more dots than question marks
  4808. }
  4809. function capitalize(word) {
  4810. return word[0].toUpperCase() + word.slice(1);
  4811. }
  4812. /**
  4813. * Insert commas at randomly selected words. This function modifies values
  4814. * inside <code>words</code> array
  4815. * @param {Array} words
  4816. */
  4817. function insertCommas(words) {
  4818. if (words.length < 2) {
  4819. return words;
  4820. }
  4821. words = words.slice();
  4822. const len = words.length;
  4823. const hasComma = /,$/;
  4824. let totalCommas = 0;
  4825. if (len > 3 && len <= 6) {
  4826. totalCommas = rand(0, 1);
  4827. } else if (len > 6 && len <= 12) {
  4828. totalCommas = rand(0, 2);
  4829. } else {
  4830. totalCommas = rand(1, 4);
  4831. }
  4832. for (let i = 0, pos; i < totalCommas; i++) {
  4833. pos = rand(0, len - 2);
  4834. if (!hasComma.test(words[pos])) {
  4835. words[pos] += ',';
  4836. }
  4837. }
  4838. return words;
  4839. }
  4840. /**
  4841. * Generate a paragraph of "Lorem ipsum" text
  4842. * @param {Object} dict Words dictionary (see `lang/*.json`)
  4843. * @param {Number} wordCount Words count in paragraph
  4844. * @param {Boolean} startWithCommon Should paragraph start with common
  4845. * "lorem ipsum" sentence.
  4846. * @returns {String}
  4847. */
  4848. function paragraph(dict, wordCount, startWithCommon) {
  4849. const result = [];
  4850. let totalWords = 0;
  4851. let words;
  4852. if (startWithCommon && dict.common) {
  4853. words = dict.common.slice(0, wordCount);
  4854. totalWords += words.length;
  4855. result.push(sentence(insertCommas(words), '.'));
  4856. }
  4857. while (totalWords < wordCount) {
  4858. words = sample(dict.words, Math.min(rand(2, 30), wordCount - totalWords));
  4859. totalWords += words.length;
  4860. result.push(sentence(insertCommas(words)));
  4861. }
  4862. return result.join(' ');
  4863. }
  4864. /**
  4865. * Check if given node is in repeating context, e.g. node itself or one of its
  4866. * parent is repeated
  4867. * @param {Node} node
  4868. * @return {Boolean}
  4869. */
  4870. function isRepeating(node) {
  4871. while (node.parent) {
  4872. if (node.repeat && node.repeat.value && node.repeat.value > 1) {
  4873. return true;
  4874. }
  4875. node = node.parent;
  4876. }
  4877. return false;
  4878. }
  4879. const reLorem = /^lorem([a-z]*)(\d*)$/i;
  4880. /**
  4881. * Constructs a snippets registry, filled with snippets, for given options
  4882. * @param {String} syntax Abbreviation syntax
  4883. * @param {Object|Object[]} snippets Additional snippets
  4884. * @return {SnippetsRegistry}
  4885. */
  4886. function snippetsRegistryFactory(syntax, snippets) {
  4887. const registrySnippets = [index$7[syntax] || index$7.html];
  4888. if (Array.isArray(snippets)) {
  4889. snippets.forEach(item => {
  4890. // if array item is a string, treat it as a reference to globally
  4891. // defined snippets
  4892. registrySnippets.push(typeof item === 'string' ? index$7[item] : item);
  4893. });
  4894. } else if (typeof snippets === 'object') {
  4895. registrySnippets.push(snippets);
  4896. }
  4897. const registry = new SnippetsRegistry(registrySnippets.filter(Boolean));
  4898. // for non-stylesheet syntaxes add Lorem Ipsum generator
  4899. if (syntax !== 'css') {
  4900. registry.get(0).set(reLorem, loremGenerator);
  4901. }
  4902. return registry;
  4903. }
  4904. function loremGenerator(node) {
  4905. const options = {};
  4906. const m = node.name.match(reLorem);
  4907. if (m[1]) {
  4908. options.lang = m[1];
  4909. }
  4910. if (m[2]) {
  4911. options.wordCount = +m[2];
  4912. }
  4913. return index$8(node, options);
  4914. }
  4915. /**
  4916. * Default variables used in snippets to insert common values into predefined snippets
  4917. * @type {Object}
  4918. */
  4919. const defaultVariables = {
  4920. lang: 'en',
  4921. locale: 'en-US',
  4922. charset: 'UTF-8'
  4923. };
  4924. /**
  4925. * A list of syntaxes that should use Emmet CSS abbreviations:
  4926. * a variations of default abbreivation that holds values right in abbreviation name
  4927. * @type {Set}
  4928. */
  4929. const stylesheetSyntaxes = new Set(['css', 'sass', 'scss', 'less', 'stylus', 'sss']);
  4930. const defaultOptions$6 = {
  4931. /**
  4932. * Abbreviation output syntax
  4933. * @type {String}
  4934. */
  4935. syntax: 'html',
  4936. /**
  4937. * Field/tabstop generator for editor. Most editors support TextMate-style
  4938. * fields: ${0} or ${1:item}. So for TextMate-style fields this function
  4939. * will look like this:
  4940. * @example
  4941. * (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`
  4942. *
  4943. * @param {Number} index Placeholder index. Fields with the same indices
  4944. * should be linked
  4945. * @param {String} [placeholder] Field placeholder
  4946. * @return {String}
  4947. */
  4948. field: (index, placeholder) => placeholder || '',
  4949. /**
  4950. * Insert given text string(s) into expanded abbreviation
  4951. * If array of strings is given, the implicitly repeated element (e.g. `li*`)
  4952. * will be repeated by the amount of items in array
  4953. * @type {String|String[]}
  4954. */
  4955. text: null,
  4956. /**
  4957. * Either predefined output profile or options for output profile. Used for
  4958. * abbreviation output
  4959. * @type {Profile|Object}
  4960. */
  4961. profile: null,
  4962. /**
  4963. * Custom variables for variable resolver
  4964. * @see @emmetio/variable-resolver
  4965. * @type {Object}
  4966. */
  4967. variables: {},
  4968. /**
  4969. * Custom predefined snippets for abbreviation. The expanded abbreviation
  4970. * will try to match given snippets that may contain custom elements,
  4971. * predefined attributes etc.
  4972. * May also contain array of items: either snippets (Object) or references
  4973. * to default syntax snippets (String; the key in default snippets hash)
  4974. * @see @emmetio/snippets
  4975. * @type {Object|SnippetsRegistry}
  4976. */
  4977. snippets: {},
  4978. /**
  4979. * Hash of additional transformations that should be applied to expanded
  4980. * abbreviation, like BEM or JSX. Since these transformations introduce
  4981. * side-effect, they are disabled by default and should be enabled by
  4982. * providing a transform name as a key and transform options as value:
  4983. * @example
  4984. * {
  4985. * bem: {element: '--'},
  4986. * jsx: true // no options, just enable transform
  4987. * }
  4988. * @see @emmetio/html-transform/lib/addons
  4989. * @type {Object}
  4990. */
  4991. addons: null,
  4992. /**
  4993. * Additional options for syntax formatter
  4994. * @see @emmetio/markup-formatters
  4995. * @type {Object}
  4996. */
  4997. format: null
  4998. };
  4999. /**
  5000. * Expands given abbreviation into string, formatted according to provided
  5001. * syntax and options
  5002. * @param {String|Node} abbr Abbreviation string or parsed abbreviation tree
  5003. * @param {String|Object} [options] Parsing and formatting options (object) or
  5004. * abbreviation syntax (string)
  5005. * @return {String}
  5006. */
  5007. function expand$2(abbr, options) {
  5008. options = createOptions(options);
  5009. return isStylesheet(options.syntax)
  5010. ? expand$1(abbr, options)
  5011. : expand(abbr, options);
  5012. }
  5013. /**
  5014. * Parses given abbreviation into AST tree. This tree can be later formatted to
  5015. * string with `expand` function
  5016. * @param {String} abbr Abbreviation to parse
  5017. * @param {String|Object} [options] Parsing and formatting options (object) or
  5018. * abbreviation syntax (string)
  5019. * @return {Node}
  5020. */
  5021. function parse$5(abbr, options) {
  5022. options = createOptions(options);
  5023. return isStylesheet(options.syntax)
  5024. ? parse$4(abbr, options)
  5025. : parse$3(abbr, options);
  5026. }
  5027. /**
  5028. * Creates snippets registry for given syntax and additional `snippets`
  5029. * @param {String} syntax Snippets syntax, used for retreiving predefined snippets
  5030. * @param {SnippetsRegistry|Object|Object[]} [snippets] Additional snippets
  5031. * @return {SnippetsRegistry}
  5032. */
  5033. function createSnippetsRegistry(syntax, snippets) {
  5034. return snippets instanceof SnippetsRegistry
  5035. ? snippets
  5036. : snippetsRegistryFactory(isStylesheet(syntax) ? 'css' : syntax, snippets);
  5037. }
  5038. function createOptions(options) {
  5039. if (typeof options === 'string') {
  5040. options = { syntax: options };
  5041. }
  5042. options = Object.assign({}, defaultOptions$6, options);
  5043. options.format = Object.assign({field: options.field}, options.format);
  5044. options.profile = createProfile(options);
  5045. options.variables = Object.assign({}, defaultVariables, options.variables);
  5046. options.snippets = createSnippetsRegistry(isStylesheet(options.syntax) ? 'css' : options.syntax, options.snippets);
  5047. return options;
  5048. }
  5049. /**
  5050. * Check if given syntax belongs to stylesheet markup.
  5051. * Emmet uses different abbreviation flavours: one is a default markup syntax,
  5052. * used for HTML, Slim, Pug etc, the other one is used for stylesheets and
  5053. * allows embedded values in abbreviation name
  5054. * @param {String} syntax
  5055. * @return {Boolean}
  5056. */
  5057. function isStylesheet(syntax) {
  5058. return stylesheetSyntaxes.has(syntax);
  5059. }
  5060. /**
  5061. * Creates output profile from given options
  5062. * @param {Object} options
  5063. * @return {Profile}
  5064. */
  5065. function createProfile(options) {
  5066. return options.profile instanceof Profile
  5067. ? options.profile
  5068. : new Profile(options.profile);
  5069. }
  5070. exports.expand = expand$2;
  5071. exports.parse = parse$5;
  5072. exports.createSnippetsRegistry = createSnippetsRegistry;
  5073. exports.createOptions = createOptions;
  5074. exports.isStylesheet = isStylesheet;
  5075. exports.createProfile = createProfile;
  5076. Object.defineProperty(exports, '__esModule', { value: true });
  5077. })));