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.
168 lines
5.2 KiB
168 lines
5.2 KiB
import { TAG_NAMES as $, NS, hasUnescapedText } from '../common/html.js'; |
|
import { escapeText, escapeAttribute } from 'entities/lib/escape.js'; |
|
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 html = 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) { |
|
html += attr.name; |
|
} |
|
else |
|
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}`; |
|
} |
|
} |
|
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)}>`; |
|
} |
|
//# sourceMappingURL=index.js.map
|