Unescape HTML entities in Javascript?

I have some Javascript code that communicates with an XML-RPC backend. The XML-RPC returns strings of the form:

<img src='myimage.jpg'>

However, when I use the Javascript to insert the strings into HTML, they render literally. I don't see an image, I literally see the string:

<img src='myimage.jpg'>

My guess is that the HTML is being escaped over the XML-RPC channel.

How can I unescape the string in Javascript? I tried the techniques on this page, unsuccessfully: http://paulschreiber.com/blog/2008/09/20/javascript-how-to-unescape-html-entities/

What are other ways to diagnose the issue?

Answers:

Answer

EDIT: You should use the DOMParser API as Wladimir suggests, I edited my previous answer since the function posted introduced a security vulnerability.

The following snippet is the old answer's code with a small modification: using a textarea instead of a div reduces the XSS vulnerability, but it is still problematic in IE9 and Firefox.

function htmlDecode(input){
  var e = document.createElement('textarea');
  e.innerHTML = input;
  // handle case of empty input
  return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}

htmlDecode("&lt;img src='myimage.jpg'&gt;"); 
// returns "<img src='myimage.jpg'>"

Basically I create a DOM element programmatically, assign the encoded HTML to its innerHTML and retrieve the nodeValue from the text node created on the innerHTML insertion. Since it just creates an element but never adds it, no site HTML is modified.

It will work cross-browser (including older browsers) and accept all the HTML Character Entities.

EDIT: The old version of this code did not work on IE with blank inputs, as evidenced here on jsFiddle (view in IE). The version above works with all inputs.

UPDATE: appears this doesn't work with large string, and it also introduces a security vulnerability, see comments.

Answer

Most answers given here have a huge disadvantage: if the string you are trying to convert isn't trusted then you will end up with a Cross-Site Scripting (XSS) vulnerability. For the function in the accepted answer, consider the following:

htmlDecode("<img src='dummy' onerror='alert(/xss/)'>");

The string here contains an unescaped HTML tag, so instead of decoding anything the htmlDecode function will actually run JavaScript code specified inside the string.

This can be avoided by using DOMParser which is supported in all modern browsers:

function htmlDecode(input)
{
  var doc = new DOMParser().parseFromString(input, "text/html");
  return doc.documentElement.textContent;
}

// This returns "<img src='myimage.jpg'>"
htmlDecode("&lt;img src='myimage.jpg'&gt;");

// This returns ""
htmlDecode("<img src='dummy' onerror='alert(/xss/)'>");

This function is guaranteed to not run any JavaScript code as a side-effect. Any HTML tags will be ignored, only text content will be returned.

Compatibility note: Parsing HTML with DOMParser requires at least Chrome 30, Firefox 12, Opera 17, Internet Explorer 10, Safari 7.1 or Microsoft Edge. So all browsers without support are way past their EOL and as of 2017 the only ones that can still be seen in the wild occasionally are older Internet Explorer and Safari versions (usually these still aren't numerous enough to bother).

Answer

If you're using jQuery:

function htmlDecode(value){ 
  return $('<div/>').html(value).text(); 
}

Otherwise, use Strictly Software's Encoder Object, which has an excellent htmlDecode() function.

Answer

The trick is to use the power of the browser to decode the special HTML characters, but not allow the browser to execute the results as if it was actual html... This function uses a regex to identify and replace encoded HTML characters, one character at a time.

function unescapeHtml(html) {
    var el = document.createElement('div');
    return html.replace(/\&[#0-9a-z]+;/gi, function (enc) {
        el.innerHTML = enc;
        return el.innerText
    });
}
Answer

CMS' answer works fine, unless the HTML you want to unescape is very long, longer than 65536 chars. Because then in Chrome the inner HTML gets split into many child nodes, each one at most 65536 long, and you need to concatenate them. This function works also for very long strings:

function unencodeHtmlContent(escapedHtml) {
  var elem = document.createElement('div');
  elem.innerHTML = escapedHtml;
  var result = '';
  // Chrome splits innerHTML into many child nodes, each one at most 65536.
  // Whereas FF creates just one single huge child node.
  for (var i = 0; i < elem.childNodes.length; ++i) {
    result = result + elem.childNodes[i].nodeValue;
  }
  return result;
}

See this answer about innerHTML max length for more info: https://stackoverflow.com/a/27545633/694469

Answer

Not a direct response to your question, but wouldn't it be better for your RPC to return some structure (be it XML or JSON or whatever) with those image data (urls in your example) inside that structure?

Then you could just parse it in your javascript and build the <img> using javascript itself.

The structure you recieve from RPC could look like:

{"img" : ["myimage.jpg", "myimage2.jpg"]}

I think it's better this way, as injecting a code that comes from external source into your page doesn't look very secure. Imaging someone hijacking your XML-RPC script and putting something you wouldn't want in there (even some javascript...)

Answer

Chris answer is nice & elegant but it fails if value is undefined. Just simple improvement makes it solid:

function htmlDecode(value) {
   return (typeof value === 'undefined') ? '' : $('<div/>').html(value).text();
}
Answer

You're welcome...just a messenger...full credit goes to ourcodeworld.com, link below.

window.htmlentities = {
        /**
         * Converts a string to its html characters completely.
         *
         * @param {String} str String with unescaped HTML characters
         **/
        encode : function(str) {
            var buf = [];

            for (var i=str.length-1;i>=0;i--) {
                buf.unshift(['&#', str[i].charCodeAt(), ';'].join(''));
            }

            return buf.join('');
        },
        /**
         * Converts an html characterSet into its original character.
         *
         * @param {String} str htmlSet entities
         **/
        decode : function(str) {
            return str.replace(/&#(\d+);/g, function(match, dec) {
                return String.fromCharCode(dec);
            });
        }
    };

Full Credit: https://ourcodeworld.com/articles/read/188/encode-and-decode-html-entities-using-pure-javascript

Answer

This is a better:

String::decode = ->
   $('<textarea />').html(this).text()

use:

"&lt;img src='myimage.jpg'&gt;".decode();

from: HTML Entity Decode

Answer

I use this in my project: inspired by other answers but with an extra secure parameter, can be useful when you deal with decorated characters

var decodeEntities=(function(){

    var el=document.createElement('div');
    return function(str, safeEscape){

        if(str && typeof str === 'string'){

            str=str.replace(/\</g, '&lt;');

            el.innerHTML=str;
            if(el.innerText){

                str=el.innerText;
                el.innerText='';
            }
            else if(el.textContent){

                str=el.textContent;
                el.textContent='';
            }

            if(safeEscape)
                str=str.replace(/\</g, '&lt;');
        }
        return str;
    }
})();

And it's usable like:

var label='safe <b> character &eacute;ntity</b>';
var safehtml='<div title="'+decodeEntities(label)+'">'+decodeEntities(label, true)+'</div>';
Answer

All of the other answers here have problems.

The document.createElement('div') methods (including those using jQuery) execute any javascript passed into it (a security issue) and the DOMParser.parseFromString() method trims whitespace. Here is a pure javascript solution that has neither problem:

function htmlDecode(html) {
    var textarea = document.createElement("textarea");
    html= html.replace(/\r/g, String.fromCharCode(0xe000)); // Replace "\r" with reserved unicode character.
    textarea.innerHTML = html;
    var result = textarea.value;
    return result.replace(new RegExp(String.fromCharCode(0xe000), 'g'), '\r');
}

TextArea is used specifically to avoid executig js code. It passes these:

htmlDecode('&lt;&amp;&nbsp;&gt;'); // returns "<& >" with non-breaking space.
htmlDecode('  '); // returns "  "
htmlDecode('<img src="dummy" onerror="alert(\'xss\')">'); // Does not execute alert()
htmlDecode('\r\n') // returns "\r\n", doesn't lose the \r like other solutions.
Answer
var encodedStr = 'hello &amp; world';

var parser = new DOMParser;
var dom = parser.parseFromString(
    '<!doctype html><body>' + encodedStr,
    'text/html');
var decodedString = dom.body.textContent;

console.log(decodedString);
Answer

I was crazy enough to go through and make this function that should be pretty, if not completely, exhaustive:

function removeEncoding(string) {
    return string.replace(/&Agrave;/g, "À").replace(/&Aacute;/g, "Á").replace(/&Acirc;/g, "Â").replace(/&Atilde;/g, "Ã").replace(/&Auml;/g, "Ä").replace(/&Aring;/g, "Å").replace(/&agrave;/g, "à").replace(/&acirc;/g, "â").replace(/&atilde;/g, "ã").replace(/&auml;/g, "ä").replace(/&aring;/g, "å").replace(/&AElig;/g, "Æ").replace(/&aelig;/g, "æ").replace(/&szlig;/g, "ß").replace(/&Ccedil;/g, "Ç").replace(/&ccedil;/g, "ç").replace(/&Egrave;/g, "È").replace(/&Eacute;/g, "É").replace(/&Ecirc;/g, "Ê").replace(/&Euml;/g, "Ë").replace(/&egrave;/g, "è").replace(/&eacute;/g, "é").replace(/&ecirc;/g, "ê").replace(/&euml;/g, "ë").replace(/&#131;/g, "ƒ").replace(/&Igrave;/g, "Ì").replace(/&Iacute;/g, "Í").replace(/&Icirc;/g, "Î").replace(/&Iuml;/g, "Ï").replace(/&igrave;/g, "ì").replace(/&iacute;/g, "í").replace(/&icirc;/g, "î").replace(/&iuml;/g, "ï").replace(/&Ntilde;/g, "Ñ").replace(/&ntilde;/g, "ñ").replace(/&Ograve;/g, "Ò").replace(/&Oacute;/g, "Ó").replace(/&Ocirc;/g, "Ô").replace(/&Otilde;/g, "Õ").replace(/&Ouml;/g, "Ö").replace(/&ograve;/g, "ò").replace(/&oacute;/g, "ó").replace(/&ocirc;/g, "ô").replace(/&otilde;/g, "õ").replace(/&ouml;/g, "ö").replace(/&Oslash;/g, "Ø").replace(/&oslash;/g, "ø").replace(/&#140;/g, "Œ").replace(/&#156;/g, "œ").replace(/&#138;/g, "Š").replace(/&#154;/g, "š").replace(/&Ugrave;/g, "Ù").replace(/&Uacute;/g, "Ú").replace(/&Ucirc;/g, "Û").replace(/&Uuml;/g, "Ü").replace(/&ugrave;/g, "ù").replace(/&uacute;/g, "ú").replace(/&ucirc;/g, "û").replace(/&uuml;/g, "ü").replace(/&#181;/g, "µ").replace(/&#215;/g, "×").replace(/&Yacute;/g, "Ý").replace(/&#159;/g, "Ÿ").replace(/&yacute;/g, "ý").replace(/&yuml;/g, "ÿ").replace(/&#176;/g, "°").replace(/&#134;/g, "†").replace(/&#135;/g, "‡").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&#177;/g, "±").replace(/&#171;/g, "«").replace(/&#187;/g, "»").replace(/&#191;/g, "¿").replace(/&#161;/g, "¡").replace(/&#183;/g, "·").replace(/&#149;/g, "•").replace(/&#153;/g, "™").replace(/&copy;/g, "©").replace(/&reg;/g, "®").replace(/&#167;/g, "§").replace(/&#182;/g, "¶").replace(/&Alpha;/g, "?").replace(/&Beta;/g, "?").replace(/&Gamma;/g, "?").replace(/&Delta;/g, "?").replace(/&Epsilon;/g, "?").replace(/&Zeta;/g, "?").replace(/&Eta;/g, "?").replace(/&Theta;/g, "?").replace(/&Iota;/g, "?").replace(/&Kappa;/g, "?").replace(/&Lambda;/g, "?").replace(/&Mu;/g, "?").replace(/&Nu;/g, "?").replace(/&Xi;/g, "?").replace(/&Omicron;/g, "?").replace(/&Pi;/g, "?").replace(/&Rho;/g, "?").replace(/&Sigma;/g, "?").replace(/&Tau;/g, "?").replace(/&Upsilon;/g, "?").replace(/&Phi;/g, "?").replace(/&Chi;/g, "?").replace(/&Psi;/g, "?").replace(/&Omega;/g, "?").replace(/&alpha;/g, "?").replace(/&beta;/g, "?").replace(/&gamma;/g, "?").replace(/&delta;/g, "?").replace(/&epsilon;/g, "?").replace(/&zeta;/g, "?").replace(/&eta;/g, "?").replace(/&theta;/g, "?").replace(/&iota;/g, "?").replace(/&kappa;/g, "?").replace(/&lambda;/g, "?").replace(/&mu;/g, "?").replace(/&nu;/g, "?").replace(/&xi;/g, "?").replace(/&omicron;/g, "?").replace(/&pi?;/g, "?").replace(/&rho;/g, "?").replace(/&sigmaf;/g, "?").replace(/&sigma;/g, "?").replace(/&tau;/g, "?").replace(/&phi;/g, "?").replace(/&chi;/g, "?").replace(/&psi;/g, "?").replace(/&omega;/g, "?").replace(/&bull;/g, "•").replace(/&hellip;/g, "…").replace(/&prime;/g, "?").replace(/&Prime;/g, "?").replace(/&oline;/g, "?").replace(/&frasl;/g, "?").replace(/&weierp;/g, "?").replace(/&image;/g, "?").replace(/&real;/g, "?").replace(/&trade;/g, "™").replace(/&alefsym;/g, "?").replace(/&larr;/g, "?").replace(/&uarr;/g, "?").replace(/&rarr;/g, "?").replace(/&darr;/g, "?").replace(/&barr;/g, "?").replace(/&crarr;/g, "?").replace(/&lArr;/g, "?").replace(/&uArr;/g, "?").replace(/&rArr;/g, "?").replace(/&dArr;/g, "?").replace(/&hArr;/g, "?").replace(/&forall;/g, "?").replace(/&part;/g, "?").replace(/&exist;/g, "?").replace(/&empty;/g, "?").replace(/&nabla;/g, "?").replace(/&isin;/g, "?").replace(/&notin;/g, "?").replace(/&ni;/g, "?").replace(/&prod;/g, "?").replace(/&sum;/g, "?").replace(/&minus;/g, "?").replace(/&lowast;/g, "?").replace(/&radic;/g, "?").replace(/&prop;/g, "?").replace(/&infin;/g, "?").replace(/&OEig;/g, "Œ").replace(/&oelig;/g, "œ").replace(/&Yuml;/g, "Ÿ").replace(/&spades;/g, "?").replace(/&clubs;/g, "?").replace(/&hearts;/g, "?").replace(/&diams;/g, "?").replace(/&thetasym;/g, "?").replace(/&upsih;/g, "?").replace(/&piv;/g, "?").replace(/&Scaron;/g, "Š").replace(/&scaron;/g, "š").replace(/&ang;/g, "?").replace(/&and;/g, "?").replace(/&or;/g, "?").replace(/&cap;/g, "?").replace(/&cup;/g, "?").replace(/&int;/g, "?").replace(/&there4;/g, "?").replace(/&sim;/g, "?").replace(/&cong;/g, "?").replace(/&asymp;/g, "?").replace(/&ne;/g, "?").replace(/&equiv;/g, "?").replace(/&le;/g, "?").replace(/&ge;/g, "?").replace(/&sub;/g, "?").replace(/&sup;/g, "?").replace(/&nsub;/g, "?").replace(/&sube;/g, "?").replace(/&supe;/g, "?").replace(/&oplus;/g, "?").replace(/&otimes;/g, "?").replace(/&perp;/g, "?").replace(/&sdot;/g, "?").replace(/&lcell;/g, "?").replace(/&rcell;/g, "?").replace(/&lfloor;/g, "?").replace(/&rfloor;/g, "?").replace(/&lang;/g, "?").replace(/&rang;/g, "?").replace(/&loz;/g, "?").replace(/&#039;/g, "'").replace(/&amp;/g, "&").replace(/&quot;/g, "\"");
}

Used like so:

let decodedText = removeEncoding("Ich hei&szlig;e David");
console.log(decodedText);

Prints: Ich Heiße David

P.S. this took like an hour and a half to make.

Answer

There is an variant that 80% as productive as the answers at the very top.

See the benchmark: https://jsperf.com/decode-html12345678/1

performance test

console.log(decodeEntities('test: &gt'));

function decodeEntities(str) {
  // this prevents any overhead from creating the object each time
  const el = decodeEntities.element || document.createElement('textarea')

  // strip script/html tags
  el.innerHTML = str
    .replace(/<script[^>]*>([\S\s]*?)<\/script>/gmi, '')
    .replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');

  return el.value;
}

If you need to leave tags, then remove the two .replace(...) calls (you can leave the first one if you do not need scripts).

Answer

This is the most comprehensive solution I've tried so far:

const STANDARD_HTML_ENTITIES = {
    nbsp: String.fromCharCode(160),
    amp: "&",
    quot: '"',
    lt: "<",
    gt: ">"
};

const replaceHtmlEntities = plainTextString => {
    return plainTextString
        .replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
        .replace(
            /&(nbsp|amp|quot|lt|gt);/g,
            (a, b) => STANDARD_HTML_ENTITIES[b]
        );
};
Answer

To unescape HTML entities* in JavaScript you can use small library html-escaper: npm install html-escaper

import {unescape} from 'html-escaper';

unescape('escaped string');

Or unescape function from Lodash or Underscore, if you are using it.


*) please note that these functions don't cover all HTML entities, but only the most common ones, i.e. &, <, >, ', ". To unescape all HTML entities you can use he library.

Answer

function decodeHTMLContent(htmlText) {
  var txt = document.createElement("span");
  txt.innerHTML = htmlText;
  return txt.innerText;
}

var result = decodeHTMLContent('One &amp; two &amp; three');
console.log(result);

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.