【问题标题】:How to escape HTML如何转义 HTML
【发布时间】:2011-03-03 21:27:40
【问题描述】:

我有一个包含 HTML 文本的字符串。我只需要转义字符串而不是标签。 例如,我有一个字符串,其中包含,

<ul class="main_nav">
  <li>
    <a class="className1" id="idValue1" tabindex="2">Test & Sample</a>
  </li>
 <li>
  <a class="className2" id="idValue2" tabindex="2">Test & Sample2</a>
  </li>
</ul>

如何将文本转义为,

<ul class="main_nav">
  <li>
    <a class="className1" id="idValue1" tabindex="2">Test &amp; Sample</a>
  </li>
  <li>
    <a class="className2" id="idValue2" tabindex="2">Test &amp; Sample2</a>
  </li>
</ul>

无需修改标签。

这可以用 HTML DOM 和 javascript 处理吗?

【问题讨论】:

标签: javascript html dom


【解决方案1】:

(请参阅下方 OP 中 cmets 更新的问题答案)

这可以用 HTML DOM 和 javascript 处理吗?

不,一旦文本在 DOM 中,“转义”的概念就不适用了。 HTML 源文本 需要转义,以便正确解析到 DOM 中;一旦它在 DOM 中,它就不会被转义。

这可能有点难以理解,所以让我们举个例子。以下是一些 HTML 源文本(例如在您可以使用浏览器查看的 HTML 文件中):

<div>This &amp; That</div>

浏览器将其解析为 DOM 后,div 中的文本为 This &amp; That,因为此时 &amp;amp;amp; 已被解释。

因此,您需要在浏览器将文本解析到 DOM 之前更早地捕捉到这一点。事后处理不了,为时已晚。

另外,如果您开头的字符串中包含&lt;div&gt;This &amp; That&lt;/div&gt; 之类的内容,则该字符串无效。对无效字符串进行预处理会很棘手。您不能只使用环境的内置功能(PHP 或您使用的任何服务器端功能),因为它们也会转义标签。您需要进行文本处理,仅提取您想要处理的部分,然后通过转义过程运行这些部分。这个过程会很棘手。 &amp;amp;amp; 后跟空格很容易,但是如果源文本中有未转义的实体,你怎么知道是否要转义它们?您是否假设如果字符串包含&amp;amp;amp;,您就不管它了吗?还是转成&amp;amp;amp;? (这是完全有效的;这是您在 HTML 页面中显示实际字符串 &amp;amp;amp; 的方式。)

您真正需要做的是纠正根本问题:创建这些无效、半编码字符串的原因。


编辑:从我们下面的评论流来看,这个问题与您的示例中看起来完全不同(这不是批判性的)。回顾一下那些刚接触到这个新鲜事物的 cmets,你说你是从 WebKit 的innerHTML 得到这些字符串的,我说这很奇怪,innerHTML 应该正确编码&amp;amp;amp;(并指出你在a coupletest pages 建议这样做)。您的回复是:

这适用于 &。但同一测试页不适用于 ©、®、« 等实体。

这改变了问题的性质。您想用字符创建实体,虽然在字面上使用时完全有效(前提是您有正确的文本编码),但可以改为实体表示,因此对文本编码更改更具弹性。

我们可以做到。根据the spec,JavaScript 字符串中的字符值为UTF-16(使用Unicode Normalized Form C),并且从源字符编码(ISO 8859-1Windows-1252、UTF-8 等)的任何转换都在之前执行JavaScript 运行时会看到它。 (如果您不是 100% 确定您知道我所说的字符编码是什么意思,那么现在值得停下来,阅读 Joel Spolsky 的 The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!),然后再回来。)这就是输入端。在输出端,HTML 实体识别 Unicode 代码点。所以我们可以可靠地将 JavaScript 字符串转换为 HTML 实体。

不过,一如既往,魔鬼在细节中。 JavaScript 明确假设每个 16 位值都是一个字符(参见规范中的第 8.4 节),即使 UTF-16 实际上并非如此——一个 16 位值可能是一个“代理”(例如 0xD800),它只与下一个值结合时才有意义,这意味着 JavaScript 字符串中的两个“字符”实际上是一个字符。这对于远东语言来说并不少见。

因此,以 JavaScript 字符串开头并生成 HTML 实体的 稳健 转换不能假定 JavaScript“字符”实际上等于文本中的字符,它必须处理代理项。幸运的是,这样做非常容易,因为定义 Unicode 的聪明人让它变得非常容易:第一个代理值始终在 0xD800-0xDBFF 范围内(含),第二个代理值始终在 0xDC00-0xDFFF 范围内(含)。因此,每当您在 JavaScript 字符串中看到与这些范围匹配的一对“字符”时,您就是在处理由代理对定义的单个字符。将代理值对转换为代码点值的公式在上面的链接中给出,尽管相当迟钝;我发现this page 更加平易近人。

有了所有这些信息,我们可以编写一个函数,该函数将接受一个 JavaScript 字符串并搜索您可能想要转换为实体的字符(真实字符,可能是一两个“字符”长),并替换它们使用地图中的命名实体或数字实体(如果我们的命名地图中没有它们):

// A map of the entities we want to handle.
// The numbers on the left are the Unicode code point values; their
// matching named entity strings are on the right.
var entityMap = {
    "160": "&nbsp;",
    "161": "&iexcl;",
    "162": "&#cent;",
    "163": "&#pound;",
    "164": "&#curren;",
    "165": "&#yen;",
    "166": "&#brvbar;",
    "167": "&#sect;",
    "168": "&#uml;",
    "169": "&copy;",
    // ...and lots and lots more, see http://www.w3.org/TR/REC-html40/sgml/entities.html
    "8364": "&euro;"    // Last one must not have a comma after it, IE doesn't like trailing commas
};

// The function to do the work.
// Accepts a string, returns a string with replacements made.
function prepEntities(str) {
    // The regular expression below uses an alternation to look for a surrogate pair _or_
    // a single character that we might want to make an entity out of. The first part of the
    // alternation (the [\uD800-\uDBFF][\uDC00-\uDFFF] before the |), you want to leave
    // alone, it searches for the surrogates. The second part of the alternation you can
    // adjust as you see fit, depending on how conservative you want to be. The example
    // below uses [\u0000-\u001f\u0080-\uFFFF], meaning that it will match and convert any
    // character with a value from 0 to 31 ("control characters") or above 127 -- e.g., if
    // it's not "printable ASCII" (in the old parlance), convert it. That's probably
    // overkill, but you said you wanted to make entities out of things, so... :-)
    return str.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0000-\u001f\u0080-\uFFFF]/g, function(match) {
        var high, low, charValue, rep

        // Get the character value, handling surrogate pairs
        if (match.length == 2) {
            // It's a surrogate pair, calculate the Unicode code point
            high = match.charCodeAt(0) - 0xD800;
            low  = match.charCodeAt(1) - 0xDC00;
            charValue = (high * 0x400) + low + 0x10000;
        }
        else {
            // Not a surrogate pair, the value *is* the Unicode code point
            charValue = match.charCodeAt(0);
        }

        // See if we have a mapping for it
        rep = entityMap[charValue];
        if (!rep) {
            // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
            rep = "&#" + charValue + ";";
        }

        // Return replacement
        return rep;
    });
}

你应该可以通过它传递所有的 HTML,因为如果这些字符出现在属性值中,你几乎肯定也想在那里对它们进行编码。

没有在生产中使用上述内容(我实际上是为这个答案写的,因为这个问题引起了我的兴趣)并且它完全在没有任何形式的保证的情况下提供.我试图确保它能够处理代理对,因为这对于远东语言来说是必要的,并且支持它们是我们现在应该做的事情,因为世界已经变得更小了。

完整的示例页面:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Test Page</title>
<style type='text/css'>
body {
    font-family: sans-serif;
}
#log p {
    margin:     0;
    padding:    0;
}
</style>
<script type='text/javascript'>

// Make the function available as a global, but define it within a scoping
// function so we can have data (the `entityMap`) that only it has access to
var prepEntities = (function() {

    // A map of the entities we want to handle.
    // The numbers on the left are the Unicode code point values; their
    // matching named entity strings are on the right.
    var entityMap = {
        "160": "&nbsp;",
        "161": "&iexcl;",
        "162": "&#cent;",
        "163": "&#pound;",
        "164": "&#curren;",
        "165": "&#yen;",
        "166": "&#brvbar;",
        "167": "&#sect;",
        "168": "&#uml;",
        "169": "&copy;",
        // ...and lots and lots more, see http://www.w3.org/TR/REC-html40/sgml/entities.html
        "8364": "&euro;"    // Last one must not have a comma after it, IE doesn't like trailing commas
    };

    // The function to do the work.
    // Accepts a string, returns a string with replacements made.
    function prepEntities(str) {
        // The regular expression below uses an alternation to look for a surrogate pair _or_
        // a single character that we might want to make an entity out of. The first part of the
        // alternation (the [\uD800-\uDBFF][\uDC00-\uDFFF] before the |), you want to leave
        // alone, it searches for the surrogates. The second part of the alternation you can
        // adjust as you see fit, depending on how conservative you want to be. The example
        // below uses [\u0000-\u001f\u0080-\uFFFF], meaning that it will match and convert any
        // character with a value from 0 to 31 ("control characters") or above 127 -- e.g., if
        // it's not "printable ASCII" (in the old parlance), convert it. That's probably
        // overkill, but you said you wanted to make entities out of things, so... :-)
        return str.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0000-\u001f\u0080-\uFFFF]/g, function(match) {
            var high, low, charValue, rep

            // Get the character value, handling surrogate pairs
            if (match.length == 2) {
                // It's a surrogate pair, calculate the Unicode code point
                high = match.charCodeAt(0) - 0xD800;
                low  = match.charCodeAt(1) - 0xDC00;
                charValue = (high * 0x400) + low + 0x10000;
            }
            else {
                // Not a surrogate pair, the value *is* the Unicode code point
                charValue = match.charCodeAt(0);
            }

            // See if we have a mapping for it
            rep = entityMap[charValue];
            if (!rep) {
                // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
                rep = "&#" + charValue + ";";
            }

            // Return replacement
            return rep;
        });
    }

    // Return the function reference out of the scoping function to publish it
    return prepEntities;
})();

function go() {
    var d = document.getElementById('d1');
    var s = d.innerHTML;
    alert("Before: " + s);
    s = prepEntities(s);
    alert("After: " + s);
}

</script>
</head>
<body>
<div id='d1'>Copyright: &copy; Yen: &yen; Cedilla: &cedil; Surrogate pair: &#65536;</div>
<input type='button' id='btnGo' value='Go' onclick="return go();">
</body>
</html>

在这里,我将 cedilla 作为转换为数字实体而不是命名实体的示例(因为我将 cedil 离开了我非常小的示例地图)。请注意,由于 JavaScript 处理 UTF-16 的方式,最后的代理对在第一个警报中显示为两个“字符”。

【讨论】:

  • @T.J.克劳德:谢谢你的解释。让我详细说明我的确切问题。我想将 HTML 源文件保存到磁盘。我尝试使用 document.childNodes[i].outerHTML; 获取内容。还有,document.getElementsByTagName('html')[0].innerHTML;一些实体将被 DOM 解码。我想将实体保留为原始实体。我不知道获取 html 内容(同时保持当前状态)和保留实体的替代方法。我正在使用 QT webkit 和 javascript。有关此的任何帮助。谢谢
  • @kokila:如果您使用innerHTMLouterHTML 从WebKit 获取字符串,它们应该已经被正确转义。此测试页面 (pastie.org/1005105) 在 Chrome 和 Safari(基于 WebKit 的浏览器)中都产生了预期的结果。即使我使用 &amp;amp;amp; 而不是 &amp;amp;amp; (pastie.org/1005109) 给他们一个无效的 HTML 文件,他们也会在解析期间更正它,并在为 innerHTMLouterHTML 重构 HTML 时给我实体版本。跨度>
  • @T.J.克劳德:这适用于 &.但是相同的测试页不适用于 ©、®、« 等实体。还有很多。示例
    测试这个 ©那个
    。我也有同样的问题。
  • @kokila:啊,现在,这完全改变了问题的性质。完全有可能解决这些问题,我会更新答案以向您展示如何处理(这很棘手)。
  • @T.J.克劳德:希望我能得到一些解决方案。我猜 innerHTML 或 outerHTML 不属于 DOM 标准。即使我尝试使用“d.firstChild.nodeValue”,它应该是 DOM 标准的一部分。甚至它也能解码实体。
【解决方案2】:

您使用什么服务器端语言?

如果你使用 PHP,你可以使用 htmlentities

例子:

<?php

$myHTML = "<h1>Some HTML Tags</h1><br />";
echo htmlentities($myHTML);

?>

【讨论】:

  • 不,这也会转义标签。 OP 说他/她只想转义标签 之间 的位(一个非常棘手且容易出错的操作)。
  • 问题被标记为javascript。没有迹象表明涉及任何服务器端语言。
【解决方案3】:

你试过 Javascript 中的 escape() 函数吗? JavaScript escape() Function

【讨论】:

  • 此函数用于转义 URL 参数,而不是 HTML。甚至在 URL 参数中使用它也是错误的。
  • @Tomalek: escape 不是专门用于转义 URL 参数(它不进行 URL 编码),它只是用于转义要作为 GET 或 POST 的一部分发送的数据一种特定的、控制良好的格式(不同于 URL 编码)。是的,它与转义 HTML 完全无关。
【解决方案4】:

我很惊讶没有人回答这个问题。您可以只使用浏览器本身为您进行转义。没有任何正则表达式比让浏览器做它最擅长的事情,处理 HTML 更好或更安全。

function escapeHTML(str){
    var p = document.createElement("p");
    p.appendChild(document.createTextNode(str));
    return p.innerHTML;
}

或使用Option() 构造函数的简短替代方法

function escapeHTML(str){
    return new Option(str).innerHTML;
}

【讨论】:

  • 我看到了很多解决方案,但没有比这更好的了。简单而美丽。
  • 当你可以做var p = document.createElement('p');p.innerText = str;return p.innerHTML;时,为什么要创建文本节点?
  • 为什么要专门使用&lt;option&gt; 元素?
  • @Yeti 当时的 innerText 不符合规范,仅在 2017 年才标准化。请参阅:github.com/rocallahan/innerText-spec
【解决方案5】:

您可以对字符串中的所有字符进行编码:

function encode(e){return e.replace(/[^]/g,function(e){return"&#"+e.charCodeAt(0)+";"})}

或者只针对主要角色担心(&、inebreaks、、“和'),例如:

function encode(r){
return r.replace(/[\x26\x0A\<>'"]/g,function(r){return"&#"+r.charCodeAt(0)+";"})
}

var myString='Encode HTML entities!\n"Safe" escape <script></'+'script> & other tags!';

test.value=encode(myString);

testing.innerHTML=encode(myString);

/*************
* \x26 is &ampersand (it has to be first),
* \x0A is newline,
*************/
<p><b>What JavaScript Sees:</b></p>

<textarea id=test rows="3" cols="55"></textarea>

<p><b>What It Renders Too In HTML:</b></p>

<div id="testing">www.WHAK.com</div>

【讨论】:

    猜你喜欢
    • 2021-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-12
    • 2019-03-02
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多