在JavaScript中如何取消转义HTML实体?

293

我有一些JavaScript代码,用它来与XML-RPC后端进行通信。 XML-RPC返回以下格式的字符串:

<img src='myimage.jpg'>

然而,当我使用 JavaScript 将字符串插入 HTML 时,它们会直接渲染为字符串。我看不到图像,我只看到字面的字符串:

<img src='myimage.jpg'>

我猜测HTML在XML-RPC通道上被转义了。

我该如何在JavaScript中取消转义字符串?我尝试了这个页面上的技术,但没有成功:http://paulschreiber.com/blog/2008/09/20/javascript-how-to-unescape-html-entities/

还有哪些方法可以诊断问题?


这篇文章中包含的庞大函数似乎运行良好:http://blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx 我认为这不是最聪明的解决方案,但它能够工作。 - mati
2
作为包含HTML实体的字符串与escapeURI编码字符串不同,因此这些函数无法使用。 - Marcel Korpel
2
@Matias 注意,自2003年编写该函数以来,HTML(例如通过HTML 5规范)已添加了新的命名实体-例如,它无法识别“𝕫”。这是一个不断发展的规范问题;因此,您应选择一个实际正在维护的工具来解决它。 - Mark Amery
可能是如何使用jQuery解码HTML实体?的重复问题。 - lucascaro
我刚刚意识到很容易将这个问题与编码HTML实体混淆。我刚刚意识到我在这个问题上不小心发布了一个错误的答案!不过我已经删除了它。 - shreyasm-dev
34个回答

1

Chris的回答很好,很优雅,但如果值为undefined,它就会失败。只需简单改进即可使其更加稳定:

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

1
如果可以改进,那么就这样做: return (typeof value !== 'string') ? '' : $('<div/>').html(value).text(); - SynCap

1

一个JavaScript解决方案,可以捕获常见问题:

var map = {amp: '&', lt: '<', gt: '>', quot: '"', '#039': "'"}
str = str.replace(/&([^;]+);/g, (m, c) => map[c])

这是https://dev59.com/SnI-5IYBdhLWcg3wkJQA#4835406的反义词


如果您使用 map[c] || '',未被识别的元素将不会显示为 undefined - Eldelshell
1
非常有限的覆盖范围;-1。 - Mark Amery
2
+1,更多是unescapeHtml(str){ var map = {amp: '&', lt: '<', le: '≤', gt: '>', ge: '≥', quot: '"', '#039': "'"} return str.replace(/&([^;]+);/g, (m, c) => map[c]|| '') } - Trần Quốc Hoài new 2015
1
手动覆盖。不建议使用。 - Sergio A.

1
我尝试了各种方法来从JSON数组中移除&。以上示例都无法解决问题,但https://stackoverflow.com/users/2030321/chris提供了一个很好的解决方案,帮助我解决了问题。
var stringtodecode="<B>Hello</B> world<br>";
document.getElementById("decodeIt").innerHTML=stringtodecode;
stringtodecode=document.getElementById("decodeIt").innerText

我没有使用它,因为我不知道如何将它插入到拉取JSON数据到数组中的模态窗口中,但是我根据示例尝试了一下,它起作用了:

var modal = document.getElementById('demodal');
$('#ampersandcontent').text(replaceAll(data[0],"&amp;", "&"));

我喜欢它因为它很简单,而且它有效,但不确定为什么它没有被广泛使用。我搜索了很久才找到一个简单的解决方案。我继续寻求对语法的理解,并且如果使用它有任何风险。目前还没有发现任何问题。


你的第一个提议有点棘手,但不需要太多的努力就可以很好地解决问题。另一方面,第二个提议只使用了暴力破解字符;这意味着要完成完整的解码功能可能需要大量的努力和时间。这就是为什么没有人使用那种方式来解决 OP 的问题。 - Sergio A.

0

我疯狂到足以编写这个函数,它应该是相当全面的,如果不是完全全面的话:

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, "\"");
}

用法如下:

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

输出:Ich Heiße David

顺便说一句,这个花了我一个半小时的时间。


无法处理“Ich Heiße David”。 - gre_gor
“&”需要最后替换;否则,“&quot;”将被错误地解码两次为“"”,而不是一次为“"”。 - Anders Kaseorg

0

闭包可以避免创建不必要的对象。

const decodingHandler = (() => {
  const element = document.createElement('div');
  return text => {
    element.innerHTML = text;
    return element.textContent;
  };
})();

更简洁的方式

const decodingHandler = (() => {
  const element = document.createElement('div');
  return text => ((element.innerHTML = text), element.textContent);
})();

在这里使用 innerHTML 会不会引入 XSS 漏洞,因为字符串被传递给它了?最好使用 innerText - shwz

0
使用Dentity!我发现以上答案都不令人满意,所以我从这里挑选了一些内容,修复了它们的问题,并添加了完整的W3C实体定义和一些额外功能。我还尽可能地将其压缩到最小,现在只有31KB的最小化版本和14KB的gzip压缩版本。您可以从https://github.com/arashkazemi/dentity下载它。
它包括解码器和编码器函数,并且在浏览器和Node环境中都可以正常工作。希望它能高效地解决问题!

0

这是我迄今为止尝试过的最全面的解决方案:

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]
        );
};

2
“最全面的”?你是否尝试过对其运行一个真正全面的测试套件 - Dan Dascalescu

-1
// decode-html.js v1
function decodeHtml(html) {
    const textarea = document.createElement('textarea');
    textarea.innerHTML = html;
    const decodedHtml = textarea.textContent;
    textarea.remove();
    return decodedHtml;
};

// encode-html.js v1
function encodeHtml(html) {
    const textarea = document.createElement('textarea');
    textarea.textContent = html;
    const encodedHtml = textarea.innerHTML;
    textarea.remove();
    return encodedHtml;
};

// example of use:
let htmlDecoded = 'one & two & three';
let htmlEncoded = 'one &amp; two &amp; three';

console.log(1, htmlDecoded);
console.log(2, encodeHtml(htmlDecoded));

console.log(3, htmlEncoded);
console.log(4, decodeHtml(htmlEncoded));

-1

我在我的项目中使用这个:受到其他答案的启发,但增加了一个额外的安全参数,当你处理装饰字符时非常有用

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;
    }
})();

它可以像这样使用:

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

-2

这里的其他答案都有问题。

document.createElement('div')方法(包括使用jQuery的方法)会执行传入的任何javascript代码(存在安全问题),而DOMParser.parseFromString()方法会删除空白字符。下面是一个纯javascript的解决方案,既没有这个问题:

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 专门用于避免执行 js 代码。它传递了这些内容:

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.

1
不,使用不同的标签并不能解决这个问题。这仍然是一个XSS漏洞,请尝试使用htmlDecode("</textarea><img src=x onerror=alert(1)>")。我在Sergio Belevskij的答案中指出了这个问题后,你才发布了这个内容。 - Wladimir Palant
我无法重现您描述的问题。我已经将您的代码放在这个 JsFiddle 中运行,没有警报显示。http://jsfiddle.net/edsjt15g/1/ 你能看一下吗?您使用的是哪个浏览器? - EricP
2
我正在使用Firefox。Chrome确实以不同的方式处理这种情况,因此代码不会执行 - 但这并不是您应该依赖的东西。 - Wladimir Palant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接