--- /dev/null
+/*
+ Module dependencies
+*/
+var ElementType = require('domelementtype');
+var entities = require('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
+*/
+var foreignNames = require('./foreignNames.json');
+foreignNames.elementNames.__proto__ = null; /* use as a simple dictionary */
+foreignNames.attributeNames.__proto__ = null;
+
+var unencodedElements = {
+ __proto__: null,
+ style: true,
+ script: true,
+ xmp: true,
+ iframe: true,
+ noembed: true,
+ noframes: true,
+ plaintext: true,
+ noscript: true
+};
+
+/*
+ Format attributes
+*/
+function formatAttrs(attributes, opts) {
+ if (!attributes) return;
+
+ var output = '';
+ var value;
+
+ // Loop through the attributes
+ for (var key in attributes) {
+ value = attributes[key];
+ if (output) {
+ output += ' ';
+ }
+
+ if (opts.xmlMode === 'foreign') {
+ /* fix up mixed-case attribute names */
+ key = foreignNames.attributeNames[key] || key;
+ }
+ output += key;
+ if ((value !== null && value !== '') || opts.xmlMode) {
+ output +=
+ '="' +
+ (opts.decodeEntities
+ ? entities.encodeXML(value)
+ : value.replace(/\"/g, '"')) +
+ '"';
+ }
+ }
+
+ return output;
+}
+
+/*
+ Self-enclosing tags (stolen from node-htmlparser)
+*/
+var singleTag = {
+ __proto__: null,
+ area: true,
+ base: true,
+ basefont: true,
+ br: true,
+ col: true,
+ command: true,
+ embed: true,
+ frame: true,
+ hr: true,
+ img: true,
+ input: true,
+ isindex: true,
+ keygen: true,
+ link: true,
+ meta: true,
+ param: true,
+ source: true,
+ track: true,
+ wbr: true
+};
+
+var render = (module.exports = function(dom, opts) {
+ if (!Array.isArray(dom) && !dom.cheerio) dom = [dom];
+ opts = opts || {};
+
+ var output = '';
+
+ for (var i = 0; i < dom.length; i++) {
+ var elem = dom[i];
+
+ if (elem.type === 'root') output += render(elem.children, opts);
+ else if (ElementType.isTag(elem)) output += renderTag(elem, opts);
+ else if (elem.type === ElementType.Directive)
+ output += renderDirective(elem);
+ else if (elem.type === ElementType.Comment) output += renderComment(elem);
+ else if (elem.type === ElementType.CDATA) output += renderCdata(elem);
+ else output += renderText(elem, opts);
+ }
+
+ return output;
+});
+
+var foreignModeIntegrationPoints = [
+ 'mi',
+ 'mo',
+ 'mn',
+ 'ms',
+ 'mtext',
+ 'annotation-xml',
+ 'foreignObject',
+ 'desc',
+ 'title'
+];
+
+function renderTag(elem, opts) {
+ // Handle SVG / MathML in HTML
+ if (opts.xmlMode === 'foreign') {
+ /* fix up mixed-case element names */
+ elem.name = foreignNames.elementNames[elem.name] || elem.name;
+ /* exit foreign mode at integration points */
+ if (
+ elem.parent &&
+ foreignModeIntegrationPoints.indexOf(elem.parent.name) >= 0
+ )
+ opts = Object.assign({}, opts, { xmlMode: false });
+ }
+ if (!opts.xmlMode && ['svg', 'math'].indexOf(elem.name) >= 0) {
+ opts = Object.assign({}, opts, { xmlMode: 'foreign' });
+ }
+
+ var tag = '<' + elem.name;
+ var attribs = formatAttrs(elem.attribs, opts);
+
+ if (attribs) {
+ tag += ' ' + attribs;
+ }
+
+ if (opts.xmlMode && (!elem.children || elem.children.length === 0)) {
+ tag += '/>';
+ } else {
+ tag += '>';
+ if (elem.children) {
+ tag += render(elem.children, opts);
+ }
+
+ if (!singleTag[elem.name] || opts.xmlMode) {
+ tag += '</' + elem.name + '>';
+ }
+ }
+
+ return tag;
+}
+
+function renderDirective(elem) {
+ return '<' + elem.data + '>';
+}
+
+function renderText(elem, opts) {
+ var data = elem.data || '';
+
+ // if entities weren't decoded, no need to encode them back
+ if (
+ opts.decodeEntities &&
+ !(elem.parent && elem.parent.name in unencodedElements)
+ ) {
+ data = entities.encodeXML(data);
+ }
+
+ return data;
+}
+
+function renderCdata(elem) {
+ return '<![CDATA[' + elem.children[0].data + ']]>';
+}
+
+function renderComment(elem) {
+ return '<!--' + elem.data + '-->';
+}