/* 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 += ''; } } 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 ''; } function renderComment(elem) { return ''; }