|
|
|
|
|
|
|
import * as ElementType from "domelementtype"; |
|
import { encodeXML, escapeAttribute, escapeText } from "entities"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
import { elementNames, attributeNames } from "./foreignNames.js"; |
|
const unencodedElements = new Set([ |
|
"style", |
|
"script", |
|
"xmp", |
|
"iframe", |
|
"noembed", |
|
"noframes", |
|
"plaintext", |
|
"noscript", |
|
]); |
|
function replaceQuotes(value) { |
|
return value.replace(/"/g, """); |
|
} |
|
|
|
|
|
|
|
function formatAttributes(attributes, opts) { |
|
var _a; |
|
if (!attributes) |
|
return; |
|
const encode = ((_a = opts.encodeEntities) !== null && _a !== void 0 ? _a : opts.decodeEntities) === false |
|
? replaceQuotes |
|
: opts.xmlMode || opts.encodeEntities !== "utf8" |
|
? encodeXML |
|
: escapeAttribute; |
|
return Object.keys(attributes) |
|
.map((key) => { |
|
var _a, _b; |
|
const value = (_a = attributes[key]) !== null && _a !== void 0 ? _a : ""; |
|
if (opts.xmlMode === "foreign") { |
|
|
|
key = (_b = attributeNames.get(key)) !== null && _b !== void 0 ? _b : key; |
|
} |
|
if (!opts.emptyAttrs && !opts.xmlMode && value === "") { |
|
return key; |
|
} |
|
return `${key}="${encode(value)}"`; |
|
}) |
|
.join(" "); |
|
} |
|
|
|
|
|
|
|
const singleTag = new Set([ |
|
"area", |
|
"base", |
|
"basefont", |
|
"br", |
|
"col", |
|
"command", |
|
"embed", |
|
"frame", |
|
"hr", |
|
"img", |
|
"input", |
|
"isindex", |
|
"keygen", |
|
"link", |
|
"meta", |
|
"param", |
|
"source", |
|
"track", |
|
"wbr", |
|
]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function render(node, options = {}) { |
|
const nodes = "length" in node ? node : [node]; |
|
let output = ""; |
|
for (let i = 0; i < nodes.length; i++) { |
|
output += renderNode(nodes[i], options); |
|
} |
|
return output; |
|
} |
|
export default render; |
|
function renderNode(node, options) { |
|
switch (node.type) { |
|
case ElementType.Root: |
|
return render(node.children, options); |
|
|
|
case ElementType.Doctype: |
|
case ElementType.Directive: |
|
return renderDirective(node); |
|
case ElementType.Comment: |
|
return renderComment(node); |
|
case ElementType.CDATA: |
|
return renderCdata(node); |
|
case ElementType.Script: |
|
case ElementType.Style: |
|
case ElementType.Tag: |
|
return renderTag(node, options); |
|
case ElementType.Text: |
|
return renderText(node, options); |
|
} |
|
} |
|
const foreignModeIntegrationPoints = new Set([ |
|
"mi", |
|
"mo", |
|
"mn", |
|
"ms", |
|
"mtext", |
|
"annotation-xml", |
|
"foreignObject", |
|
"desc", |
|
"title", |
|
]); |
|
const foreignElements = new Set(["svg", "math"]); |
|
function renderTag(elem, opts) { |
|
var _a; |
|
|
|
if (opts.xmlMode === "foreign") { |
|
|
|
elem.name = (_a = elementNames.get(elem.name)) !== null && _a !== void 0 ? _a : elem.name; |
|
|
|
if (elem.parent && |
|
foreignModeIntegrationPoints.has(elem.parent.name)) { |
|
opts = { ...opts, xmlMode: false }; |
|
} |
|
} |
|
if (!opts.xmlMode && foreignElements.has(elem.name)) { |
|
opts = { ...opts, xmlMode: "foreign" }; |
|
} |
|
let tag = `<${elem.name}`; |
|
const attribs = formatAttributes(elem.attribs, opts); |
|
if (attribs) { |
|
tag += ` ${attribs}`; |
|
} |
|
if (elem.children.length === 0 && |
|
(opts.xmlMode |
|
? |
|
opts.selfClosingTags !== false |
|
: |
|
opts.selfClosingTags && singleTag.has(elem.name))) { |
|
if (!opts.xmlMode) |
|
tag += " "; |
|
tag += "/>"; |
|
} |
|
else { |
|
tag += ">"; |
|
if (elem.children.length > 0) { |
|
tag += render(elem.children, opts); |
|
} |
|
if (opts.xmlMode || !singleTag.has(elem.name)) { |
|
tag += `</${elem.name}>`; |
|
} |
|
} |
|
return tag; |
|
} |
|
function renderDirective(elem) { |
|
return `<${elem.data}>`; |
|
} |
|
function renderText(elem, opts) { |
|
var _a; |
|
let data = elem.data || ""; |
|
|
|
if (((_a = opts.encodeEntities) !== null && _a !== void 0 ? _a : opts.decodeEntities) !== false && |
|
!(!opts.xmlMode && |
|
elem.parent && |
|
unencodedElements.has(elem.parent.name))) { |
|
data = |
|
opts.xmlMode || opts.encodeEntities !== "utf8" |
|
? encodeXML(data) |
|
: escapeText(data); |
|
} |
|
return data; |
|
} |
|
function renderCdata(elem) { |
|
return `<![CDATA[${elem.children[0].data}]]>`; |
|
} |
|
function renderComment(elem) { |
|
return `<!--${elem.data}-->`; |
|
} |
|
|