index.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.serialize = serialize;
  4. exports.serializeOuter = serializeOuter;
  5. const html_js_1 = require("../common/html.js");
  6. const escape_js_1 = require("entities/lib/escape.js");
  7. const default_js_1 = require("../tree-adapters/default.js");
  8. // Sets
  9. const VOID_ELEMENTS = new Set([
  10. html_js_1.TAG_NAMES.AREA,
  11. html_js_1.TAG_NAMES.BASE,
  12. html_js_1.TAG_NAMES.BASEFONT,
  13. html_js_1.TAG_NAMES.BGSOUND,
  14. html_js_1.TAG_NAMES.BR,
  15. html_js_1.TAG_NAMES.COL,
  16. html_js_1.TAG_NAMES.EMBED,
  17. html_js_1.TAG_NAMES.FRAME,
  18. html_js_1.TAG_NAMES.HR,
  19. html_js_1.TAG_NAMES.IMG,
  20. html_js_1.TAG_NAMES.INPUT,
  21. html_js_1.TAG_NAMES.KEYGEN,
  22. html_js_1.TAG_NAMES.LINK,
  23. html_js_1.TAG_NAMES.META,
  24. html_js_1.TAG_NAMES.PARAM,
  25. html_js_1.TAG_NAMES.SOURCE,
  26. html_js_1.TAG_NAMES.TRACK,
  27. html_js_1.TAG_NAMES.WBR,
  28. ]);
  29. function isVoidElement(node, options) {
  30. return (options.treeAdapter.isElementNode(node) &&
  31. options.treeAdapter.getNamespaceURI(node) === html_js_1.NS.HTML &&
  32. VOID_ELEMENTS.has(options.treeAdapter.getTagName(node)));
  33. }
  34. const defaultOpts = { treeAdapter: default_js_1.defaultTreeAdapter, scriptingEnabled: true };
  35. /**
  36. * Serializes an AST node to an HTML string.
  37. *
  38. * @example
  39. *
  40. * ```js
  41. * const parse5 = require('parse5');
  42. *
  43. * const document = parse5.parse('<!DOCTYPE html><html><head></head><body>Hi there!</body></html>');
  44. *
  45. * // Serializes a document.
  46. * const html = parse5.serialize(document);
  47. *
  48. * // Serializes the <html> element content.
  49. * const str = parse5.serialize(document.childNodes[1]);
  50. *
  51. * console.log(str); //> '<head></head><body>Hi there!</body>'
  52. * ```
  53. *
  54. * @param node Node to serialize.
  55. * @param options Serialization options.
  56. */
  57. function serialize(node, options) {
  58. const opts = Object.assign(Object.assign({}, defaultOpts), options);
  59. if (isVoidElement(node, opts)) {
  60. return '';
  61. }
  62. return serializeChildNodes(node, opts);
  63. }
  64. /**
  65. * Serializes an AST element node to an HTML string, including the element node.
  66. *
  67. * @example
  68. *
  69. * ```js
  70. * const parse5 = require('parse5');
  71. *
  72. * const document = parse5.parseFragment('<div>Hello, <b>world</b>!</div>');
  73. *
  74. * // Serializes the <div> element.
  75. * const str = parse5.serializeOuter(document.childNodes[0]);
  76. *
  77. * console.log(str); //> '<div>Hello, <b>world</b>!</div>'
  78. * ```
  79. *
  80. * @param node Node to serialize.
  81. * @param options Serialization options.
  82. */
  83. function serializeOuter(node, options) {
  84. const opts = Object.assign(Object.assign({}, defaultOpts), options);
  85. return serializeNode(node, opts);
  86. }
  87. function serializeChildNodes(parentNode, options) {
  88. let html = '';
  89. // Get container of the child nodes
  90. const container = options.treeAdapter.isElementNode(parentNode) &&
  91. options.treeAdapter.getTagName(parentNode) === html_js_1.TAG_NAMES.TEMPLATE &&
  92. options.treeAdapter.getNamespaceURI(parentNode) === html_js_1.NS.HTML
  93. ? options.treeAdapter.getTemplateContent(parentNode)
  94. : parentNode;
  95. const childNodes = options.treeAdapter.getChildNodes(container);
  96. if (childNodes) {
  97. for (const currentNode of childNodes) {
  98. html += serializeNode(currentNode, options);
  99. }
  100. }
  101. return html;
  102. }
  103. function serializeNode(node, options) {
  104. if (options.treeAdapter.isElementNode(node)) {
  105. return serializeElement(node, options);
  106. }
  107. if (options.treeAdapter.isTextNode(node)) {
  108. return serializeTextNode(node, options);
  109. }
  110. if (options.treeAdapter.isCommentNode(node)) {
  111. return serializeCommentNode(node, options);
  112. }
  113. if (options.treeAdapter.isDocumentTypeNode(node)) {
  114. return serializeDocumentTypeNode(node, options);
  115. }
  116. // Return an empty string for unknown nodes
  117. return '';
  118. }
  119. function serializeElement(node, options) {
  120. const tn = options.treeAdapter.getTagName(node);
  121. return `<${tn}${serializeAttributes(node, options)}>${isVoidElement(node, options) ? '' : `${serializeChildNodes(node, options)}</${tn}>`}`;
  122. }
  123. function serializeAttributes(node, { treeAdapter }) {
  124. let html = '';
  125. for (const attr of treeAdapter.getAttrList(node)) {
  126. html += ' ';
  127. if (attr.namespace) {
  128. switch (attr.namespace) {
  129. case html_js_1.NS.XML: {
  130. html += `xml:${attr.name}`;
  131. break;
  132. }
  133. case html_js_1.NS.XMLNS: {
  134. if (attr.name !== 'xmlns') {
  135. html += 'xmlns:';
  136. }
  137. html += attr.name;
  138. break;
  139. }
  140. case html_js_1.NS.XLINK: {
  141. html += `xlink:${attr.name}`;
  142. break;
  143. }
  144. default: {
  145. html += `${attr.prefix}:${attr.name}`;
  146. }
  147. }
  148. }
  149. else {
  150. html += attr.name;
  151. }
  152. html += `="${(0, escape_js_1.escapeAttribute)(attr.value)}"`;
  153. }
  154. return html;
  155. }
  156. function serializeTextNode(node, options) {
  157. const { treeAdapter } = options;
  158. const content = treeAdapter.getTextNodeContent(node);
  159. const parent = treeAdapter.getParentNode(node);
  160. const parentTn = parent && treeAdapter.isElementNode(parent) && treeAdapter.getTagName(parent);
  161. return parentTn &&
  162. treeAdapter.getNamespaceURI(parent) === html_js_1.NS.HTML &&
  163. (0, html_js_1.hasUnescapedText)(parentTn, options.scriptingEnabled)
  164. ? content
  165. : (0, escape_js_1.escapeText)(content);
  166. }
  167. function serializeCommentNode(node, { treeAdapter }) {
  168. return `<!--${treeAdapter.getCommentNodeContent(node)}-->`;
  169. }
  170. function serializeDocumentTypeNode(node, { treeAdapter }) {
  171. return `<!DOCTYPE ${treeAdapter.getDocumentTypeNodeName(node)}>`;
  172. }