| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 | 
							- 'use strict';
 
- /**
 
-  * @typedef {import('./types').XastParent} XastParent
 
-  * @typedef {import('./types').XastRoot} XastRoot
 
-  * @typedef {import('./types').XastElement} XastElement
 
-  * @typedef {import('./types').XastInstruction} XastInstruction
 
-  * @typedef {import('./types').XastDoctype} XastDoctype
 
-  * @typedef {import('./types').XastText} XastText
 
-  * @typedef {import('./types').XastCdata} XastCdata
 
-  * @typedef {import('./types').XastComment} XastComment
 
-  * @typedef {import('./types').StringifyOptions} StringifyOptions
 
-  */
 
- const { textElems } = require('../plugins/_collections.js');
 
- /**
 
-  * @typedef {{
 
-  *   width: void | string,
 
-  *   height: void | string,
 
-  *   indent: string,
 
-  *   textContext: null | XastElement,
 
-  *   indentLevel: number,
 
-  * }} State
 
-  */
 
- /**
 
-  * @typedef {Required<StringifyOptions>} Options
 
-  */
 
- /**
 
-  * @type {(char: string) => string}
 
-  */
 
- const encodeEntity = (char) => {
 
-   return entities[char];
 
- };
 
- /**
 
-  * @type {Options}
 
-  */
 
- const defaults = {
 
-   doctypeStart: '<!DOCTYPE',
 
-   doctypeEnd: '>',
 
-   procInstStart: '<?',
 
-   procInstEnd: '?>',
 
-   tagOpenStart: '<',
 
-   tagOpenEnd: '>',
 
-   tagCloseStart: '</',
 
-   tagCloseEnd: '>',
 
-   tagShortStart: '<',
 
-   tagShortEnd: '/>',
 
-   attrStart: '="',
 
-   attrEnd: '"',
 
-   commentStart: '<!--',
 
-   commentEnd: '-->',
 
-   cdataStart: '<![CDATA[',
 
-   cdataEnd: ']]>',
 
-   textStart: '',
 
-   textEnd: '',
 
-   indent: 4,
 
-   regEntities: /[&'"<>]/g,
 
-   regValEntities: /[&"<>]/g,
 
-   encodeEntity: encodeEntity,
 
-   pretty: false,
 
-   useShortTags: true,
 
-   eol: 'lf',
 
-   finalNewline: false,
 
- };
 
- /**
 
-  * @type {Record<string, string>}
 
-  */
 
- const entities = {
 
-   '&': '&',
 
-   "'": ''',
 
-   '"': '"',
 
-   '>': '>',
 
-   '<': '<',
 
- };
 
- /**
 
-  * convert XAST to SVG string
 
-  *
 
-  * @type {(data: XastRoot, config: StringifyOptions) => {
 
-  *   data: string,
 
-  *   info: {
 
-  *     width: void | string,
 
-  *     height: void | string
 
-  *   }
 
-  * }}
 
-  */
 
- const stringifySvg = (data, userOptions = {}) => {
 
-   /**
 
-    * @type {Options}
 
-    */
 
-   const config = { ...defaults, ...userOptions };
 
-   const indent = config.indent;
 
-   let newIndent = '    ';
 
-   if (typeof indent === 'number' && Number.isNaN(indent) === false) {
 
-     newIndent = indent < 0 ? '\t' : ' '.repeat(indent);
 
-   } else if (typeof indent === 'string') {
 
-     newIndent = indent;
 
-   }
 
-   /**
 
-    * @type {State}
 
-    */
 
-   const state = {
 
-     // TODO remove width and height in v3
 
-     width: undefined,
 
-     height: undefined,
 
-     indent: newIndent,
 
-     textContext: null,
 
-     indentLevel: 0,
 
-   };
 
-   const eol = config.eol === 'crlf' ? '\r\n' : '\n';
 
-   if (config.pretty) {
 
-     config.doctypeEnd += eol;
 
-     config.procInstEnd += eol;
 
-     config.commentEnd += eol;
 
-     config.cdataEnd += eol;
 
-     config.tagShortEnd += eol;
 
-     config.tagOpenEnd += eol;
 
-     config.tagCloseEnd += eol;
 
-     config.textEnd += eol;
 
-   }
 
-   let svg = stringifyNode(data, config, state);
 
-   if (config.finalNewline && svg.length > 0 && svg[svg.length - 1] !== '\n') {
 
-     svg += eol;
 
-   }
 
-   return {
 
-     data: svg,
 
-     info: {
 
-       width: state.width,
 
-       height: state.height,
 
-     },
 
-   };
 
- };
 
- exports.stringifySvg = stringifySvg;
 
- /**
 
-  * @type {(node: XastParent, config: Options, state: State) => string}
 
-  */
 
- const stringifyNode = (data, config, state) => {
 
-   let svg = '';
 
-   state.indentLevel += 1;
 
-   for (const item of data.children) {
 
-     if (item.type === 'element') {
 
-       svg += stringifyElement(item, config, state);
 
-     }
 
-     if (item.type === 'text') {
 
-       svg += stringifyText(item, config, state);
 
-     }
 
-     if (item.type === 'doctype') {
 
-       svg += stringifyDoctype(item, config);
 
-     }
 
-     if (item.type === 'instruction') {
 
-       svg += stringifyInstruction(item, config);
 
-     }
 
-     if (item.type === 'comment') {
 
-       svg += stringifyComment(item, config);
 
-     }
 
-     if (item.type === 'cdata') {
 
-       svg += stringifyCdata(item, config, state);
 
-     }
 
-   }
 
-   state.indentLevel -= 1;
 
-   return svg;
 
- };
 
- /**
 
-  * create indent string in accordance with the current node level.
 
-  *
 
-  * @type {(config: Options, state: State) => string}
 
-  */
 
- const createIndent = (config, state) => {
 
-   let indent = '';
 
-   if (config.pretty && state.textContext == null) {
 
-     indent = state.indent.repeat(state.indentLevel - 1);
 
-   }
 
-   return indent;
 
- };
 
- /**
 
-  * @type {(node: XastDoctype, config: Options) => string}
 
-  */
 
- const stringifyDoctype = (node, config) => {
 
-   return config.doctypeStart + node.data.doctype + config.doctypeEnd;
 
- };
 
- /**
 
-  * @type {(node: XastInstruction, config: Options) => string}
 
-  */
 
- const stringifyInstruction = (node, config) => {
 
-   return (
 
-     config.procInstStart + node.name + ' ' + node.value + config.procInstEnd
 
-   );
 
- };
 
- /**
 
-  * @type {(node: XastComment, config: Options) => string}
 
-  */
 
- const stringifyComment = (node, config) => {
 
-   return config.commentStart + node.value + config.commentEnd;
 
- };
 
- /**
 
-  * @type {(node: XastCdata, config: Options, state: State) => string}
 
-  */
 
- const stringifyCdata = (node, config, state) => {
 
-   return (
 
-     createIndent(config, state) +
 
-     config.cdataStart +
 
-     node.value +
 
-     config.cdataEnd
 
-   );
 
- };
 
- /**
 
-  * @type {(node: XastElement, config: Options, state: State) => string}
 
-  */
 
- const stringifyElement = (node, config, state) => {
 
-   // beautiful injection for obtaining SVG information :)
 
-   if (
 
-     node.name === 'svg' &&
 
-     node.attributes.width != null &&
 
-     node.attributes.height != null
 
-   ) {
 
-     state.width = node.attributes.width;
 
-     state.height = node.attributes.height;
 
-   }
 
-   // empty element and short tag
 
-   if (node.children.length === 0) {
 
-     if (config.useShortTags) {
 
-       return (
 
-         createIndent(config, state) +
 
-         config.tagShortStart +
 
-         node.name +
 
-         stringifyAttributes(node, config) +
 
-         config.tagShortEnd
 
-       );
 
-     } else {
 
-       return (
 
-         createIndent(config, state) +
 
-         config.tagShortStart +
 
-         node.name +
 
-         stringifyAttributes(node, config) +
 
-         config.tagOpenEnd +
 
-         config.tagCloseStart +
 
-         node.name +
 
-         config.tagCloseEnd
 
-       );
 
-     }
 
-     // non-empty element
 
-   } else {
 
-     let tagOpenStart = config.tagOpenStart;
 
-     let tagOpenEnd = config.tagOpenEnd;
 
-     let tagCloseStart = config.tagCloseStart;
 
-     let tagCloseEnd = config.tagCloseEnd;
 
-     let openIndent = createIndent(config, state);
 
-     let closeIndent = createIndent(config, state);
 
-     if (state.textContext) {
 
-       tagOpenStart = defaults.tagOpenStart;
 
-       tagOpenEnd = defaults.tagOpenEnd;
 
-       tagCloseStart = defaults.tagCloseStart;
 
-       tagCloseEnd = defaults.tagCloseEnd;
 
-       openIndent = '';
 
-     } else if (textElems.includes(node.name)) {
 
-       tagOpenEnd = defaults.tagOpenEnd;
 
-       tagCloseStart = defaults.tagCloseStart;
 
-       closeIndent = '';
 
-       state.textContext = node;
 
-     }
 
-     const children = stringifyNode(node, config, state);
 
-     if (state.textContext === node) {
 
-       state.textContext = null;
 
-     }
 
-     return (
 
-       openIndent +
 
-       tagOpenStart +
 
-       node.name +
 
-       stringifyAttributes(node, config) +
 
-       tagOpenEnd +
 
-       children +
 
-       closeIndent +
 
-       tagCloseStart +
 
-       node.name +
 
-       tagCloseEnd
 
-     );
 
-   }
 
- };
 
- /**
 
-  * @type {(node: XastElement, config: Options) => string}
 
-  */
 
- const stringifyAttributes = (node, config) => {
 
-   let attrs = '';
 
-   for (const [name, value] of Object.entries(node.attributes)) {
 
-     // TODO remove attributes without values support in v3
 
-     if (value !== undefined) {
 
-       const encodedValue = value
 
-         .toString()
 
-         .replace(config.regValEntities, config.encodeEntity);
 
-       attrs += ' ' + name + config.attrStart + encodedValue + config.attrEnd;
 
-     } else {
 
-       attrs += ' ' + name;
 
-     }
 
-   }
 
-   return attrs;
 
- };
 
- /**
 
-  * @type {(node: XastText, config: Options, state: State) => string}
 
-  */
 
- const stringifyText = (node, config, state) => {
 
-   return (
 
-     createIndent(config, state) +
 
-     config.textStart +
 
-     node.value.replace(config.regEntities, config.encodeEntity) +
 
-     (state.textContext ? '' : config.textEnd)
 
-   );
 
- };
 
 
  |