(请参阅下方 OP 中 cmets 更新的问题答案)
这可以用 HTML DOM 和 javascript 处理吗?
不,一旦文本在 DOM 中,“转义”的概念就不适用了。 HTML 源文本 需要转义,以便正确解析到 DOM 中;一旦它在 DOM 中,它就不会被转义。
这可能有点难以理解,所以让我们举个例子。以下是一些 HTML 源文本(例如在您可以使用浏览器查看的 HTML 文件中):
<div>This & That</div>
浏览器将其解析为 DOM 后,div 中的文本为 This & That,因为此时 &amp;amp; 已被解释。
因此,您需要在浏览器将文本解析到 DOM 之前更早地捕捉到这一点。事后处理不了,为时已晚。
另外,如果您开头的字符串中包含<div>This & That</div> 之类的内容,则该字符串无效。对无效字符串进行预处理会很棘手。您不能只使用环境的内置功能(PHP 或您使用的任何服务器端功能),因为它们也会转义标签。您需要进行文本处理,仅提取您想要处理的部分,然后通过转义过程运行这些部分。这个过程会很棘手。 &amp;amp; 后跟空格很容易,但是如果源文本中有未转义的实体,你怎么知道是否要转义它们?您是否假设如果字符串包含&amp;amp;,您就不管它了吗?还是转成&amp;amp;? (这是完全有效的;这是您在 HTML 页面中显示实际字符串 &amp;amp; 的方式。)
您真正需要做的是纠正根本问题:创建这些无效、半编码字符串的原因。
编辑:从我们下面的评论流来看,这个问题与您的示例中看起来完全不同(这不是批判性的)。回顾一下那些刚接触到这个新鲜事物的 cmets,你说你是从 WebKit 的innerHTML 得到这些字符串的,我说这很奇怪,innerHTML 应该正确编码&amp;amp;(并指出你在a couple 的test pages 建议这样做)。您的回复是:
这适用于 &。但同一测试页不适用于 ©、®、« 等实体。
这改变了问题的性质。您想用字符创建实体,虽然在字面上使用时完全有效(前提是您有正确的文本编码),但可以改为实体表示,因此对文本编码更改更具弹性。
我们可以做到。根据the spec,JavaScript 字符串中的字符值为UTF-16(使用Unicode Normalized Form C),并且从源字符编码(ISO 8859-1、Windows-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": " ",
"161": "¡",
"162": "&#cent;",
"163": "&#pound;",
"164": "&#curren;",
"165": "&#yen;",
"166": "&#brvbar;",
"167": "&#sect;",
"168": "&#uml;",
"169": "©",
// ...and lots and lots more, see http://www.w3.org/TR/REC-html40/sgml/entities.html
"8364": "€" // 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": " ",
"161": "¡",
"162": "&#cent;",
"163": "&#pound;",
"164": "&#curren;",
"165": "&#yen;",
"166": "&#brvbar;",
"167": "&#sect;",
"168": "&#uml;",
"169": "©",
// ...and lots and lots more, see http://www.w3.org/TR/REC-html40/sgml/entities.html
"8364": "€" // 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: © Yen: ¥ Cedilla: ¸ Surrogate pair: 𐀀</div>
<input type='button' id='btnGo' value='Go' onclick="return go();">
</body>
</html>
在这里,我将 cedilla 作为转换为数字实体而不是命名实体的示例(因为我将 cedil 离开了我非常小的示例地图)。请注意,由于 JavaScript 处理 UTF-16 的方式,最后的代理对在第一个警报中显示为两个“字符”。