index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. var SINGLE_TAGS = [
  2. 'area',
  3. 'base',
  4. 'br',
  5. 'col',
  6. 'command',
  7. 'embed',
  8. 'hr',
  9. 'img',
  10. 'input',
  11. 'keygen',
  12. 'link',
  13. 'menuitem',
  14. 'meta',
  15. 'param',
  16. 'source',
  17. 'track',
  18. 'wbr'
  19. ]
  20. var ATTRIBUTE_QUOTES_REQUIRED = /[\t\n\f\r " '`=<>]/
  21. /** Render PostHTML Tree to HTML
  22. *
  23. * @param {Array|Object} tree PostHTML Tree @param {Object} options Options
  24. *
  25. * @return {String} HTML
  26. */
  27. function render (tree, options) {
  28. /** Options
  29. *
  30. * @type {Object}
  31. *
  32. * @prop {Array<String|RegExp>} singleTags Custom single tags (selfClosing)
  33. * @prop {String} closingSingleTag Closing format for single tag @prop
  34. * @prop {Boolean} quoteAllAttributes If all attributes should be quoted.
  35. * Otherwise attributes will be unquoted when allowed.
  36. *
  37. * Formats:
  38. *
  39. * ``` tag: `<br></br>` ```, slash: `<br />` ```, ```default: `<br>` ```
  40. */
  41. options = options || {}
  42. var singleTags = options.singleTags ? SINGLE_TAGS.concat(options.singleTags) : SINGLE_TAGS
  43. var singleRegExp = singleTags.filter(function (tag) {
  44. return tag instanceof RegExp
  45. })
  46. var closingSingleTag = options.closingSingleTag
  47. var quoteAllAttributes = options.quoteAllAttributes
  48. if (typeof quoteAllAttributes === 'undefined') {
  49. quoteAllAttributes = true
  50. }
  51. return html(tree)
  52. /** @private */
  53. function isSingleTag (tag) {
  54. if (singleRegExp.length !== 0) {
  55. for (var i = 0; i < singleRegExp.length; i++) {
  56. return singleRegExp[i].test(tag)
  57. }
  58. }
  59. if (singleTags.indexOf(tag) === -1) {
  60. return false
  61. }
  62. return true
  63. }
  64. /** @private */
  65. function attrs (obj) {
  66. var attr = ''
  67. for (var key in obj) {
  68. if (typeof obj[key] === 'string') {
  69. if (quoteAllAttributes || obj[key].match(ATTRIBUTE_QUOTES_REQUIRED)) {
  70. attr += ' ' + key + '="' + obj[key].replace(/"/g, '&quot;') + '"'
  71. } else if (obj[key] === '') {
  72. attr += ' ' + key
  73. } else {
  74. attr += ' ' + key + '=' + obj[key]
  75. }
  76. } else if (obj[key] === true) {
  77. attr += ' ' + key
  78. } else if (typeof obj[key] === 'number') {
  79. attr += ' ' + key + '="' + obj[key] + '"'
  80. }
  81. }
  82. return attr
  83. }
  84. /** @private */
  85. function traverse (tree, cb) {
  86. if (tree !== undefined) {
  87. for (var i = 0, length = tree.length; i < length; i++) {
  88. traverse(cb(tree[i]), cb)
  89. }
  90. }
  91. }
  92. /**
  93. * HTML Stringifier
  94. *
  95. * @param {Array|Object} tree PostHTML Tree
  96. *
  97. * @return {String} result HTML
  98. */
  99. function html (tree) {
  100. var result = ''
  101. if (!Array.isArray(tree)) {
  102. tree = [tree]
  103. }
  104. traverse(tree, function (node) {
  105. // undefined, null, '', [], NaN
  106. if (node === undefined ||
  107. node === null ||
  108. node === false ||
  109. node.length === 0 ||
  110. Number.isNaN(node)) {
  111. return
  112. }
  113. // treat as new root tree if node is an array
  114. if (Array.isArray(node)) {
  115. result += html(node)
  116. return
  117. }
  118. if (typeof node === 'string' || typeof node === 'number') {
  119. result += node
  120. return
  121. }
  122. // skip node
  123. if (node.tag === false) {
  124. result += html(node.content)
  125. return
  126. }
  127. var tag = node.tag || 'div'
  128. result += '<' + tag
  129. if (node.attrs) {
  130. result += attrs(node.attrs)
  131. }
  132. if (isSingleTag(tag)) {
  133. switch (closingSingleTag) {
  134. case 'tag':
  135. result += '></' + tag + '>'
  136. break
  137. case 'slash':
  138. result += ' />'
  139. break
  140. default:
  141. result += '>'
  142. }
  143. result += html(node.content)
  144. } else {
  145. result += '>' + html(node.content) + '</' + tag + '>'
  146. }
  147. })
  148. return result
  149. }
  150. }
  151. /**
  152. * @module posthtml-render
  153. *
  154. * @version 1.1.5
  155. * @license MIT
  156. */
  157. module.exports = render