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