Spaces:
Running
Running
File size: 5,381 Bytes
a5f860b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
/*
* Module dependencies
*/
import * as ElementType from "domelementtype";
import { encodeXML, escapeAttribute, escapeText } from "entities";
/**
* Mixed-case SVG and MathML tags & attributes
* recognized by the HTML parser.
*
* @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign
*/
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, """);
}
/**
* Format attributes
*/
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") {
/* Fix up mixed-case attribute names */
key = (_b = attributeNames.get(key)) !== null && _b !== void 0 ? _b : key;
}
if (!opts.emptyAttrs && !opts.xmlMode && value === "") {
return key;
}
return `${key}="${encode(value)}"`;
})
.join(" ");
}
/**
* Self-enclosing tags
*/
const singleTag = new Set([
"area",
"base",
"basefont",
"br",
"col",
"command",
"embed",
"frame",
"hr",
"img",
"input",
"isindex",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr",
]);
/**
* Renders a DOM node or an array of DOM nodes to a string.
*
* Can be thought of as the equivalent of the `outerHTML` of the passed node(s).
*
* @param node Node to be rendered.
* @param options Changes serialization behavior
*/
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);
// @ts-expect-error We don't use `Doctype` yet
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;
// Handle SVG / MathML in HTML
if (opts.xmlMode === "foreign") {
/* Fix up mixed-case element names */
elem.name = (_a = elementNames.get(elem.name)) !== null && _a !== void 0 ? _a : elem.name;
/* Exit foreign mode at integration points */
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
? // In XML mode or foreign mode, and user hasn't explicitly turned off self-closing tags
opts.selfClosingTags !== false
: // User explicitly asked for self-closing tags, even in HTML mode
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 entities weren't decoded, no need to encode them back
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}-->`;
}
|