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.
		
		
		
		
			
				
					223 lines
				
				4.5 KiB
			
		
		
			
		
	
	
					223 lines
				
				4.5 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								const SINGLE_TAGS = [
							 | 
						||
| 
								 | 
							
								  'area',
							 | 
						||
| 
								 | 
							
								  'base',
							 | 
						||
| 
								 | 
							
								  'br',
							 | 
						||
| 
								 | 
							
								  'col',
							 | 
						||
| 
								 | 
							
								  'command',
							 | 
						||
| 
								 | 
							
								  'embed',
							 | 
						||
| 
								 | 
							
								  'hr',
							 | 
						||
| 
								 | 
							
								  'img',
							 | 
						||
| 
								 | 
							
								  'input',
							 | 
						||
| 
								 | 
							
								  'keygen',
							 | 
						||
| 
								 | 
							
								  'link',
							 | 
						||
| 
								 | 
							
								  'menuitem',
							 | 
						||
| 
								 | 
							
								  'meta',
							 | 
						||
| 
								 | 
							
								  'param',
							 | 
						||
| 
								 | 
							
								  'source',
							 | 
						||
| 
								 | 
							
								  'track',
							 | 
						||
| 
								 | 
							
								  'wbr'
							 | 
						||
| 
								 | 
							
								];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const ATTRIBUTE_QUOTES_REQUIRED = /[\t\n\f\r "'`=<>]/;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/** Render PostHTML Tree to HTML
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Array|Object} tree PostHTML Tree @param  {Object} options Options
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @return {String} HTML
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function render(tree, options) {
							 | 
						||
| 
								 | 
							
								  /** Options
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @type {Object}
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @prop {Array<String|RegExp>} singleTags  Custom single tags (selfClosing)
							 | 
						||
| 
								 | 
							
								   * @prop {String} closingSingleTag Closing format for single tag @prop
							 | 
						||
| 
								 | 
							
								   * @prop {Boolean} quoteAllAttributes If all attributes should be quoted.
							 | 
						||
| 
								 | 
							
								   * Otherwise attributes will be unquoted when allowed.
							 | 
						||
| 
								 | 
							
								   * @prop {Boolean} replaceQuote Replaces quotes in attribute values with `"e;`
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * Formats:
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * ``` tag: `<br></br>` ```, slash: `<br />` ```, ```default: `<br>` ```
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  options = options || {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const singleTags = options.singleTags ? SINGLE_TAGS.concat(options.singleTags) : SINGLE_TAGS;
							 | 
						||
| 
								 | 
							
								  const singleRegExp = singleTags.filter(tag => {
							 | 
						||
| 
								 | 
							
								    return tag instanceof RegExp;
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const {closingSingleTag} = options;
							 | 
						||
| 
								 | 
							
								  let {quoteAllAttributes} = options;
							 | 
						||
| 
								 | 
							
								  if (quoteAllAttributes === undefined) {
							 | 
						||
| 
								 | 
							
								    quoteAllAttributes = true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let {replaceQuote} = options;
							 | 
						||
| 
								 | 
							
								  if (replaceQuote === undefined) {
							 | 
						||
| 
								 | 
							
								    replaceQuote = true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let {quoteStyle} = options;
							 | 
						||
| 
								 | 
							
								  if (quoteStyle === undefined) {
							 | 
						||
| 
								 | 
							
								    quoteStyle = 2;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return html(tree);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /** @private */
							 | 
						||
| 
								 | 
							
								  function isSingleTag(tag) {
							 | 
						||
| 
								 | 
							
								    if (singleRegExp.length > 0) {
							 | 
						||
| 
								 | 
							
								      return singleRegExp.some(reg => reg.test(tag));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!singleTags.includes(tag)) {
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /** @private */
							 | 
						||
| 
								 | 
							
								  function attrs(object) {
							 | 
						||
| 
								 | 
							
								    let attr = '';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for (const key in object) {
							 | 
						||
| 
								 | 
							
								      if (typeof object[key] === 'string') {
							 | 
						||
| 
								 | 
							
								        if (quoteAllAttributes || object[key].match(ATTRIBUTE_QUOTES_REQUIRED)) {
							 | 
						||
| 
								 | 
							
								          let attrValue = object[key];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          if (replaceQuote) {
							 | 
						||
| 
								 | 
							
								            attrValue = object[key].replace(/"/g, '"');
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          attr += makeAttr(key, attrValue, quoteStyle);
							 | 
						||
| 
								 | 
							
								        } else if (object[key] === '') {
							 | 
						||
| 
								 | 
							
								          attr += ' ' + key;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          attr += ' ' + key + '=' + object[key];
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else if (object[key] === true) {
							 | 
						||
| 
								 | 
							
								        attr += ' ' + key;
							 | 
						||
| 
								 | 
							
								      } else if (typeof object[key] === 'number') {
							 | 
						||
| 
								 | 
							
								        attr += makeAttr(key, object[key], quoteStyle);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return attr;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /** @private */
							 | 
						||
| 
								 | 
							
								  function traverse(tree, cb) {
							 | 
						||
| 
								 | 
							
								    if (tree !== undefined) {
							 | 
						||
| 
								 | 
							
								      for (let i = 0, {length} = tree; i < length; i++) {
							 | 
						||
| 
								 | 
							
								        traverse(cb(tree[i]), cb);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /** @private */
							 | 
						||
| 
								 | 
							
								  function makeAttr(key, attrValue, quoteStyle = 1) {
							 | 
						||
| 
								 | 
							
								    if (quoteStyle === 1) {
							 | 
						||
| 
								 | 
							
								      // Single Quote
							 | 
						||
| 
								 | 
							
								      return ` ${key}='${attrValue}'`;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (quoteStyle === 2) {
							 | 
						||
| 
								 | 
							
								      // Double Quote
							 | 
						||
| 
								 | 
							
								      return ` ${key}="${attrValue}"`;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Smart Quote
							 | 
						||
| 
								 | 
							
								    if (attrValue.includes('"')) {
							 | 
						||
| 
								 | 
							
								      return ` ${key}='${attrValue}'`;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return ` ${key}="${attrValue}"`;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * HTML Stringifier
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param  {Array|Object} tree PostHTML Tree
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @return {String} result HTML
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function html(tree) {
							 | 
						||
| 
								 | 
							
								    let result = '';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!Array.isArray(tree)) {
							 | 
						||
| 
								 | 
							
								      tree = [tree];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    traverse(tree, node => {
							 | 
						||
| 
								 | 
							
								      // Undefined, null, '', [], NaN
							 | 
						||
| 
								 | 
							
								      if (node === undefined ||
							 | 
						||
| 
								 | 
							
								        node === null ||
							 | 
						||
| 
								 | 
							
								        node === false ||
							 | 
						||
| 
								 | 
							
								        node.length === 0 ||
							 | 
						||
| 
								 | 
							
								        Number.isNaN(node)) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Treat as new root tree if node is an array
							 | 
						||
| 
								 | 
							
								      if (Array.isArray(node)) {
							 | 
						||
| 
								 | 
							
								        result += html(node);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (typeof node === 'string' || typeof node === 'number') {
							 | 
						||
| 
								 | 
							
								        result += node;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Skip node
							 | 
						||
| 
								 | 
							
								      if (node.tag === false) {
							 | 
						||
| 
								 | 
							
								        result += html(node.content);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const tag = node.tag || 'div';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      result += '<' + tag;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (node.attrs) {
							 | 
						||
| 
								 | 
							
								        result += attrs(node.attrs);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (isSingleTag(tag)) {
							 | 
						||
| 
								 | 
							
								        switch (closingSingleTag) {
							 | 
						||
| 
								 | 
							
								          case 'tag':
							 | 
						||
| 
								 | 
							
								            result += '></' + tag + '>';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								          case 'slash':
							 | 
						||
| 
								 | 
							
								            result += ' />';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								          default:
							 | 
						||
| 
								 | 
							
								            result += '>';
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        result += html(node.content);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        result += '>' + html(node.content) + '</' + tag + '>';
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return result;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @module posthtml-render
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @version 1.1.5
							 | 
						||
| 
								 | 
							
								 * @license MIT
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								module.exports = render;
							 |