I'm writing a Chrome extension that involves doing a lot of the following job: sanitizing strings that might contain HTML tags, by converting <
, >
and &
to <
, >
and &
, respectively.
(In other words, the same as PHP's htmlspecialchars(str, ENT_NOQUOTES)
– I don't think there's any real need to convert double-quote characters.)
This is the fastest function I have found so far:
function safe_tags(str) {
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') ;
}
But there's still a big lag when I have to run a few thousand strings through it in one go.
Can anyone improve on this? It's mostly for strings between 10 and 150 characters, if that makes a difference.
(One idea I had was not to bother encoding the greater-than sign – would there be any real danger with that?)
This question is related to
javascript
html
regex
performance
string
I'll add XMLSerializer
to the pile. It provides the fastest result without using any object caching (not on the serializer, nor on the Text node).
function serializeTextNode(text) {
return new XMLSerializer().serializeToString(document.createTextNode(text));
}
The added bonus is that it supports attributes which is serialized differently than text nodes:
function serializeAttributeValue(value) {
const attr = document.createAttribute('a');
attr.value = value;
return new XMLSerializer().serializeToString(attr);
}
You can see what it's actually replacing by checking the spec, both for text nodes and for attribute values. The full documentation has more node types, but the concept is the same.
As for performance, it's the fastest when not cached. When you do allow caching, then calling innerHTML
on an HTMLElement with a child Text node is fastest. Regex would be slowest (as proven by other comments). Of course, XMLSerializer could be faster on other browsers, but in my (limited) testing, a innerHTML
is fastest.
Fastest single line:
new XMLSerializer().serializeToString(document.createTextNode(text));
Fastest with caching:
const cachedElementParent = document.createElement('div');
const cachedChildTextNode = document.createTextNode('');
cachedElementParent.appendChild(cachedChildTextNode);
function serializeTextNode(text) {
cachedChildTextNode.nodeValue = text;
return cachedElementParent.innerHTML;
}
Here's one way you can do this:
var escape = document.createElement('textarea');
function escapeHTML(html) {
escape.textContent = html;
return escape.innerHTML;
}
function unescapeHTML(html) {
escape.innerHTML = html;
return escape.textContent;
}
All-in-one script:
// HTML entities Encode/Decode
function htmlspecialchars(str) {
var map = {
"&": "&",
"<": "<",
">": ">",
"\"": """,
"'": "'" // ' -> ' for XML only
};
return str.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function htmlspecialchars_decode(str) {
var map = {
"&": "&",
"<": "<",
">": ">",
""": "\"",
"'": "'"
};
return str.replace(/(&|<|>|"|')/g, function(m) { return map[m]; });
}
function htmlentities(str) {
var textarea = document.createElement("textarea");
textarea.innerHTML = str;
return textarea.innerHTML;
}
function htmlentities_decode(str) {
var textarea = document.createElement("textarea");
textarea.innerHTML = str;
return textarea.value;
}
A bit late to the show, but what's wrong with using encodeURIComponent() and decodeURIComponent()?
Martijn's method as single function with handling " mark (using in javascript) :
function escapeHTML(html) {
var fn=function(tag) {
var charsToReplace = {
'&': '&',
'<': '<',
'>': '>',
'"': '"'
};
return charsToReplace[tag] || tag;
}
return html.replace(/[&<>"]/g, fn);
}
Martijn's method as a prototype function:
String.prototype.escape = function() {
var tagsToReplace = {
'&': '&',
'<': '<',
'>': '>'
};
return this.replace(/[&<>]/g, function(tag) {
return tagsToReplace[tag] || tag;
});
};
var a = "<abc>";
var b = a.escape(); // "<abc>"
function encode(r) {_x000D_
return r.replace(/[\x26\x0A\x3c\x3e\x22\x27]/g, function(r) {_x000D_
return "&#" + r.charCodeAt(0) + ";";_x000D_
});_x000D_
}_x000D_
_x000D_
test.value=encode('How to encode\nonly html tags &<>\'" nice & fast!');_x000D_
_x000D_
/*_x000D_
\x26 is &ersand (it has to be first),_x000D_
\x0A is newline,_x000D_
\x22 is ",_x000D_
\x27 is ',_x000D_
\x3c is <,_x000D_
\x3e is >_x000D_
*/
_x000D_
<textarea id=test rows=11 cols=55>www.WHAK.com</textarea>
_x000D_
I'm not entirely sure about speed, but if you are looking for simplicity I would suggest using the lodash/underscore escape function.
The fastest method is:
function escapeHTML(html) {
return document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML;
}
This method is about twice faster than the methods based on 'replace', see http://jsperf.com/htmlencoderegex/35 .
An even quicker/shorter solution is:
escaped = new Option(html).innerHTML
This is related to some weird vestige of JavaScript whereby the Option element retains a constructor that does this sort of escaping automatically.
The AngularJS source code also has a version inside of angular-sanitize.js.
var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns {string} escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&').
replace(SURROGATE_PAIR_REGEXP, function(value) {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(NON_ALPHANUMERIC_REGEXP, function(value) {
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '<').
replace(/>/g, '>');
}
Source: Stackoverflow.com