You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
3.3 KiB
178 lines
3.3 KiB
/* |
|
Module dependencies |
|
*/ |
|
var ElementType = require('domelementtype'); |
|
var entities = require('entities'); |
|
|
|
/* |
|
Boolean Attributes |
|
*/ |
|
var booleanAttributes = { |
|
__proto__: null, |
|
allowfullscreen: true, |
|
async: true, |
|
autofocus: true, |
|
autoplay: true, |
|
checked: true, |
|
controls: true, |
|
default: true, |
|
defer: true, |
|
disabled: true, |
|
hidden: true, |
|
ismap: true, |
|
loop: true, |
|
multiple: true, |
|
muted: true, |
|
open: true, |
|
readonly: true, |
|
required: true, |
|
reversed: true, |
|
scoped: true, |
|
seamless: true, |
|
selected: true, |
|
typemustmatch: true |
|
}; |
|
|
|
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 = '', |
|
value; |
|
|
|
// Loop through the attributes |
|
for (var key in attributes) { |
|
value = attributes[key]; |
|
if (output) { |
|
output += ' '; |
|
} |
|
|
|
if (!value && booleanAttributes[key]) { |
|
output += key; |
|
} else { |
|
output += key + '="' + (opts.decodeEntities ? entities.encodeXML(value) : value) + '"'; |
|
} |
|
} |
|
|
|
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; |
|
}; |
|
|
|
function renderTag(elem, opts) { |
|
// Handle SVG |
|
if (elem.name === "svg") opts = {decodeEntities: opts.decodeEntities, xmlMode: true}; |
|
|
|
var tag = '<' + elem.name, |
|
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 + '-->'; |
|
}
|
|
|