150 lines
4.8 KiB
JavaScript
150 lines
4.8 KiB
JavaScript
const htmlspecialchars_table = {
|
|
nbsp: "\xA0",
|
|
quot: '"',
|
|
apos: "'",
|
|
lt: "<",
|
|
gt: ">",
|
|
amp: "&"
|
|
};
|
|
|
|
function htmlspecialchars_decode(s)
|
|
{
|
|
// Only a limited set of named entries is supported by design,
|
|
// because supporting all of them would require a large translation table
|
|
if (s == null)
|
|
{
|
|
// or undefined, because null == undefined
|
|
return "";
|
|
}
|
|
return s.replace(
|
|
/&(nbsp|quot|apos|lt|gt|amp|#x[0-9a-f]+|#[0-9]+);/gi,
|
|
(m, m1) =>
|
|
{
|
|
if (m1[0] == "#")
|
|
{
|
|
return String.fromCharCode(
|
|
m1[1] == "x"
|
|
? parseInt(m1.substr(2), 16)
|
|
: parseInt(m1.substr(1), 10)
|
|
);
|
|
}
|
|
return htmlspecialchars_table[m1];
|
|
}
|
|
);
|
|
}
|
|
|
|
function parse(content, components, createElement)
|
|
{
|
|
content = content.replace(/>\s+</g, "><");
|
|
const tag_re = /<(\/?)([a-z0-9\-]+)(([^>'"]+|"[^"]*"|'[^']*')*)>/i;
|
|
const attr_re = /^\s*([^\s="']+)(?:=([^"'\s]+|"[^"]*"|'[^']*'))?/i;
|
|
let m;
|
|
let r = [];
|
|
let stack = [r];
|
|
while ((m = tag_re.exec(content)))
|
|
{
|
|
let text = content.substr(0, m.index);
|
|
let close = m[1];
|
|
let tag = m[2];
|
|
let attrs = m[3];
|
|
content = content.substr(m.index + m[0].length);
|
|
text = text.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
if (text !== "")
|
|
{
|
|
r.push(htmlspecialchars_decode(text));
|
|
}
|
|
if (close && stack.length > 1)
|
|
{
|
|
stack.pop();
|
|
r = stack[stack.length - 1];
|
|
}
|
|
else
|
|
{
|
|
attrs = attrs.replace(/\s+$/, "");
|
|
if (attrs[attrs.length - 1] == "/")
|
|
{
|
|
close = true;
|
|
attrs = attrs.substr(0, attrs.length - 1).replace(/\s+$/, "");
|
|
}
|
|
let attrhash = {};
|
|
while ((m = attr_re.exec(attrs)))
|
|
{
|
|
let key = m[1].toLowerCase();
|
|
let value = m[2];
|
|
if (value != null)
|
|
{
|
|
// remember that null == undefined, so this checks for undefined too
|
|
if (value[0] == '"' || value[0] == "'")
|
|
{
|
|
value = value.substr(1, value.length - 2);
|
|
}
|
|
value = htmlspecialchars_decode(value);
|
|
}
|
|
else
|
|
{
|
|
value = "true";
|
|
}
|
|
if (key === "href")
|
|
{
|
|
// 0x0A-0B-0C are ignored in schema value in IE
|
|
value = value.replace(/[\x0a\x0b\x0c]+/g, "");
|
|
}
|
|
if (key.substr(0, 2) !== "on" &&
|
|
(key !== "href" || !/^javascript:/.exec(value)))
|
|
{
|
|
if (key === "class")
|
|
{
|
|
// Convert to className for React
|
|
key = "className";
|
|
}
|
|
else if (key === "style")
|
|
{
|
|
// Convert to an object for React
|
|
let obj = {};
|
|
value = value.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
for (let part of value.split(/\s*;\s*/))
|
|
{
|
|
let pos = part.indexOf(":");
|
|
if (pos >= 0)
|
|
{
|
|
let part_key = part
|
|
.substr(0, pos)
|
|
.replace(/\s+$/, "")
|
|
.replace(/-([a-z])/g, (m, m1) =>
|
|
m1.toUpperCase()
|
|
);
|
|
let part_value = part
|
|
.substr(pos + 1)
|
|
.replace(/^\s+/, "");
|
|
obj[part_key] = part_value;
|
|
}
|
|
}
|
|
value = obj;
|
|
}
|
|
attrhash[key] = value;
|
|
}
|
|
attrs = attrs.substr(m[0].length);
|
|
}
|
|
if (!close)
|
|
{
|
|
attrhash["children"] = [];
|
|
}
|
|
attrhash["key"] = r.length;
|
|
r.push(createElement(components[tag] || tag, attrhash));
|
|
if (!close)
|
|
{
|
|
stack.push(attrhash["children"]);
|
|
r = attrhash["children"];
|
|
}
|
|
}
|
|
}
|
|
content = content.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
if (content !== "")
|
|
{
|
|
r.push(htmlspecialchars_decode(content));
|
|
}
|
|
return stack[0];
|
|
}
|
|
|
|
module.exports = { htmlspecialchars_decode, parse };
|