| /* |
| 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 + '-->'; |
| } |