Spaces:
Paused
Paused
| import { TAG_NAMES as $, NS, hasUnescapedText } from '../common/html.js'; | |
| import { escapeText, escapeAttribute } from 'entities/escape'; | |
| import { defaultTreeAdapter } from '../tree-adapters/default.js'; | |
| // Sets | |
| const VOID_ELEMENTS = new Set([ | |
| $.AREA, | |
| $.BASE, | |
| $.BASEFONT, | |
| $.BGSOUND, | |
| $.BR, | |
| $.COL, | |
| $.EMBED, | |
| $.FRAME, | |
| $.HR, | |
| $.IMG, | |
| $.INPUT, | |
| $.KEYGEN, | |
| $.LINK, | |
| $.META, | |
| $.PARAM, | |
| $.SOURCE, | |
| $.TRACK, | |
| $.WBR, | |
| ]); | |
| function isVoidElement(node, options) { | |
| return (options.treeAdapter.isElementNode(node) && | |
| options.treeAdapter.getNamespaceURI(node) === NS.HTML && | |
| VOID_ELEMENTS.has(options.treeAdapter.getTagName(node))); | |
| } | |
| const defaultOpts = { treeAdapter: defaultTreeAdapter, scriptingEnabled: true }; | |
| /** | |
| * Serializes an AST node to an HTML string. | |
| * | |
| * @example | |
| * | |
| * ```js | |
| * const parse5 = require('parse5'); | |
| * | |
| * const document = parse5.parse('<!DOCTYPE html><html><head></head><body>Hi there!</body></html>'); | |
| * | |
| * // Serializes a document. | |
| * const html = parse5.serialize(document); | |
| * | |
| * // Serializes the <html> element content. | |
| * const str = parse5.serialize(document.childNodes[1]); | |
| * | |
| * console.log(str); //> '<head></head><body>Hi there!</body>' | |
| * ``` | |
| * | |
| * @param node Node to serialize. | |
| * @param options Serialization options. | |
| */ | |
| export function serialize(node, options) { | |
| const opts = { ...defaultOpts, ...options }; | |
| if (isVoidElement(node, opts)) { | |
| return ''; | |
| } | |
| return serializeChildNodes(node, opts); | |
| } | |
| /** | |
| * Serializes an AST element node to an HTML string, including the element node. | |
| * | |
| * @example | |
| * | |
| * ```js | |
| * const parse5 = require('parse5'); | |
| * | |
| * const document = parse5.parseFragment('<div>Hello, <b>world</b>!</div>'); | |
| * | |
| * // Serializes the <div> element. | |
| * const str = parse5.serializeOuter(document.childNodes[0]); | |
| * | |
| * console.log(str); //> '<div>Hello, <b>world</b>!</div>' | |
| * ``` | |
| * | |
| * @param node Node to serialize. | |
| * @param options Serialization options. | |
| */ | |
| export function serializeOuter(node, options) { | |
| const opts = { ...defaultOpts, ...options }; | |
| return serializeNode(node, opts); | |
| } | |
| function serializeChildNodes(parentNode, options) { | |
| let html = ''; | |
| // Get container of the child nodes | |
| const container = options.treeAdapter.isElementNode(parentNode) && | |
| options.treeAdapter.getTagName(parentNode) === $.TEMPLATE && | |
| options.treeAdapter.getNamespaceURI(parentNode) === NS.HTML | |
| ? options.treeAdapter.getTemplateContent(parentNode) | |
| : parentNode; | |
| const childNodes = options.treeAdapter.getChildNodes(container); | |
| if (childNodes) { | |
| for (const currentNode of childNodes) { | |
| html += serializeNode(currentNode, options); | |
| } | |
| } | |
| return html; | |
| } | |
| function serializeNode(node, options) { | |
| if (options.treeAdapter.isElementNode(node)) { | |
| return serializeElement(node, options); | |
| } | |
| if (options.treeAdapter.isTextNode(node)) { | |
| return serializeTextNode(node, options); | |
| } | |
| if (options.treeAdapter.isCommentNode(node)) { | |
| return serializeCommentNode(node, options); | |
| } | |
| if (options.treeAdapter.isDocumentTypeNode(node)) { | |
| return serializeDocumentTypeNode(node, options); | |
| } | |
| // Return an empty string for unknown nodes | |
| return ''; | |
| } | |
| function serializeElement(node, options) { | |
| const tn = options.treeAdapter.getTagName(node); | |
| return `<${tn}${serializeAttributes(node, options)}>${isVoidElement(node, options) ? '' : `${serializeChildNodes(node, options)}</${tn}>`}`; | |
| } | |
| function serializeAttributes(node, { treeAdapter }) { | |
| let html = ''; | |
| for (const attr of treeAdapter.getAttrList(node)) { | |
| html += ' '; | |
| if (attr.namespace) { | |
| switch (attr.namespace) { | |
| case NS.XML: { | |
| html += `xml:${attr.name}`; | |
| break; | |
| } | |
| case NS.XMLNS: { | |
| if (attr.name !== 'xmlns') { | |
| html += 'xmlns:'; | |
| } | |
| html += attr.name; | |
| break; | |
| } | |
| case NS.XLINK: { | |
| html += `xlink:${attr.name}`; | |
| break; | |
| } | |
| default: { | |
| html += `${attr.prefix}:${attr.name}`; | |
| } | |
| } | |
| } | |
| else { | |
| html += attr.name; | |
| } | |
| html += `="${escapeAttribute(attr.value)}"`; | |
| } | |
| return html; | |
| } | |
| function serializeTextNode(node, options) { | |
| const { treeAdapter } = options; | |
| const content = treeAdapter.getTextNodeContent(node); | |
| const parent = treeAdapter.getParentNode(node); | |
| const parentTn = parent && treeAdapter.isElementNode(parent) && treeAdapter.getTagName(parent); | |
| return parentTn && | |
| treeAdapter.getNamespaceURI(parent) === NS.HTML && | |
| hasUnescapedText(parentTn, options.scriptingEnabled) | |
| ? content | |
| : escapeText(content); | |
| } | |
| function serializeCommentNode(node, { treeAdapter }) { | |
| return `<!--${treeAdapter.getCommentNodeContent(node)}-->`; | |
| } | |
| function serializeDocumentTypeNode(node, { treeAdapter }) { | |
| return `<!DOCTYPE ${treeAdapter.getDocumentTypeNodeName(node)}>`; | |
| } | |