index.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /*
  2. Module dependencies
  3. */
  4. var ElementType = require('domelementtype');
  5. var entities = require('entities');
  6. var unencodedElements = {
  7. __proto__: null,
  8. style: true,
  9. script: true,
  10. xmp: true,
  11. iframe: true,
  12. noembed: true,
  13. noframes: true,
  14. plaintext: true,
  15. noscript: true
  16. };
  17. /*
  18. Format attributes
  19. */
  20. function formatAttrs(attributes, opts) {
  21. if (!attributes) return;
  22. var output = '',
  23. value;
  24. // Loop through the attributes
  25. for (var key in attributes) {
  26. value = attributes[key];
  27. if (output) {
  28. output += ' ';
  29. }
  30. output += key;
  31. if ((value !== null && value !== '') || opts.xmlMode) {
  32. output += '="' + (opts.decodeEntities ? entities.encodeXML(value) : value) + '"';
  33. }
  34. }
  35. return output;
  36. }
  37. /*
  38. Self-enclosing tags (stolen from node-htmlparser)
  39. */
  40. var singleTag = {
  41. __proto__: null,
  42. area: true,
  43. base: true,
  44. basefont: true,
  45. br: true,
  46. col: true,
  47. command: true,
  48. embed: true,
  49. frame: true,
  50. hr: true,
  51. img: true,
  52. input: true,
  53. isindex: true,
  54. keygen: true,
  55. link: true,
  56. meta: true,
  57. param: true,
  58. source: true,
  59. track: true,
  60. wbr: true,
  61. };
  62. var render = module.exports = function(dom, opts) {
  63. if (!Array.isArray(dom) && !dom.cheerio) dom = [dom];
  64. opts = opts || {};
  65. var output = '';
  66. for(var i = 0; i < dom.length; i++){
  67. var elem = dom[i];
  68. if (elem.type === 'root')
  69. output += render(elem.children, opts);
  70. else if (ElementType.isTag(elem))
  71. output += renderTag(elem, opts);
  72. else if (elem.type === ElementType.Directive)
  73. output += renderDirective(elem);
  74. else if (elem.type === ElementType.Comment)
  75. output += renderComment(elem);
  76. else if (elem.type === ElementType.CDATA)
  77. output += renderCdata(elem);
  78. else
  79. output += renderText(elem, opts);
  80. }
  81. return output;
  82. };
  83. function renderTag(elem, opts) {
  84. // Handle SVG
  85. if (elem.name === "svg") opts = {decodeEntities: opts.decodeEntities, xmlMode: true};
  86. var tag = '<' + elem.name,
  87. attribs = formatAttrs(elem.attribs, opts);
  88. if (attribs) {
  89. tag += ' ' + attribs;
  90. }
  91. if (
  92. opts.xmlMode
  93. && (!elem.children || elem.children.length === 0)
  94. ) {
  95. tag += '/>';
  96. } else {
  97. tag += '>';
  98. if (elem.children) {
  99. tag += render(elem.children, opts);
  100. }
  101. if (!singleTag[elem.name] || opts.xmlMode) {
  102. tag += '</' + elem.name + '>';
  103. }
  104. }
  105. return tag;
  106. }
  107. function renderDirective(elem) {
  108. return '<' + elem.data + '>';
  109. }
  110. function renderText(elem, opts) {
  111. var data = elem.data || '';
  112. // if entities weren't decoded, no need to encode them back
  113. if (opts.decodeEntities && !(elem.parent && elem.parent.name in unencodedElements)) {
  114. data = entities.encodeXML(data);
  115. }
  116. return data;
  117. }
  118. function renderCdata(elem) {
  119. return '<![CDATA[' + elem.children[0].data + ']]>';
  120. }
  121. function renderComment(elem) {
  122. return '<!--' + elem.data + '-->';
  123. }