如何使用JavaScript将特殊的UTF-8字符转换为它们的iso-8859-1等效字符?

67

我正在制作一个JavaScript应用程序,使用jQuery检索.json文件并将数据注入到所嵌入的网页中。

.json文件采用UTF-8编码,并包含带重音符号的字符,如é、ö和å。

问题在于我无法控制将使用该应用程序的页面的字符集。

有些页面将使用UTF-8,但其他页面将使用iso-8859-1字符集。这当然会破坏.json文件中的特殊字符。

如何使用JavaScript将特殊的UTF-8字符转换为其iso-8859-1等效字符?

7个回答

176

实际上,所有内容通常都以某种Unicode形式内部存储,但是我们不去深究这个。我假设您之所以会看到"åäö"这种类型的字符串,是因为您使用的字符编码是ISO-8859。有一个技巧可以将这些字符转换。用于编码和解码查询字符串的escapeunescape函数是针对ISO字符定义的,而执行相同操作的新encodeURIComponentdecodeURIComponent则是针对UTF8字符定义的。

escape将扩展的ISO-8859-1字符(UTF代码点U+0080-U+00ff)编码为%xx(两位十六进制数),而将UTF代码点U+0100及以上的字符编码为%uxxxx%u后跟四位十六进制数)。例如,escape("å") == "%E5"escape("あ") == "%u3042"

encodeURIComponent将扩展字符作为UTF8字节序列进行百分比编码。例如,encodeURIComponent("å") == "%C3%A5"encodeURIComponent("あ") == "%E3%81%82"

所以您可以这样做:

fixedstring = decodeURIComponent(escape(utfstring));

例如,一个编码不正确的字符“å”会变成“Ã¥”。该命令执行escape("Ã¥") == "%C3%A5",这是将两个不正确的ISO字符编码为单个字节。然后,decodeURIComponent("%C3%A5") == "å",其中两个百分比编码的字节被解释为UTF8序列。
如果出于某种原因需要进行反向操作,也可以这样做:
utfstring = unescape(encodeURIComponent(originalstring));

有没有办法区分坏的UTF8字符串和ISO字符串?事实证明是有的。如上所述,使用decodeURIComponent函数时,如果给定了格式不正确的编码序列,它将抛出一个错误。我们可以利用这一点来很大概率地检测出我们的字符串是UTF8还是ISO。

var fixedstring;

try{
    // If the string is UTF-8, this will work and not throw an error.
    fixedstring=decodeURIComponent(escape(badstring));
}catch(e){
    // If it isn't, an error will be thrown, and we can assume that we have an ISO string.
    fixedstring=badstring;
}

1
我已经在这里引用了您对我的问题的答案:http://stackoverflow.com/questions/18847191/is-there-a-uniform-method-in-both-php-and-js-to-convert-unicode-characters/18863966#18863966 - hsuk
2
escape 函数将扩展的 ISO-8859-1 字符(UTF 代码点 U+0080-U+00ff)编码为 %xx(两位十六进制数),而将 UTF 代码点 U+0100 及以上的字符编码为 %uxxxx%u 后跟四位十六进制数)。例如,escape("å") == "%E5"escape("あ") == "%u3042"encodeURIComponent 函数将扩展字符作为 UTF8 字节序列进行百分号编码。例如,encodeURIComponent("å") == "%C3%A5"encodeURIComponent("あ") == "%E3%81%82"。希望这能解决任何疑问。 - nitro2k01
6
我遇到了一个错误:Uncaught URIError: URI malformed,这是你提出的建议引起的。 - Luis A. Florit
2
转换函数将被弃用!!https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/escape - TheGr8_Nik
2
@Eyewritecode,我很高兴我能帮忙,但我感到难过的是,10年后我们仍需要这个hack... - nitro2k01
显示剩余7条评论

11

问题在于一旦页面被提供,内容将会使用content-type元标签描述的编码呈现。 "错误"编码中的内容已经损坏。

在提供页面之前最好在服务器上进行这种处理。或者像我曾经说过的那样: 使用UTF-8端到端或死亡


尽管我的网页头已经声明为UTF-8,但我还是必须将其转换为ISO Latin以进行进一步的加密。 - hsuk
那不是回答问题的方法! - Remigius Stalder

5

由于这个问题已经关闭,因此我在这里发布我的解决方案。该问题是如何从ISO-8859-1转换为UTF-8。

问题在于,如果您使用XMLHttpRequest获取任何内容,并且XMLHttpRequest.responseType为"text"或为空,则XMLHttpRequest.response会被转换为DOMString,这就是问题所在。然后,几乎无法可靠地处理该字符串。

现在,如果服务器返回的内容是ISO-8859-1格式,则必须强制响应类型为“Blob”,然后将其转换为DOMString。例如:

var ajax = new XMLHttpRequest();
ajax.open('GET', url, true);
ajax.responseType = 'blob';
ajax.onreadystatechange = function(){
    ...
    if(ajax.responseType === 'blob'){
        // Convert the blob to a string
        var reader = new window.FileReader();
        reader.addEventListener('loadend', function() {
           // For ISO-8859-1 there's no further conversion required
           Promise.resolve(reader.result);
        });
        reader.readAsBinaryString(ajax.response);
    }
}

看起来神奇的事情发生在readAsBinaryString上,也许有人可以解释一下为什么这个方法有效。


1

在内部,Javascript字符串都是Unicode(实际上是UCS-2,UTF-16的子集)。

如果您通过AJAX单独检索JSON文件,则只需要确保JSON文件以正确的Content-Type和charset提供:Content-Type:application/json; charset="utf-8")。 如果这样做,当您访问反序列化对象时,jQuery应该已经正确地解释了它们。

您能否发布一下检索JSON对象所使用的代码示例?


无论是仅设置内容类型还是字符集都是无关紧要的:jQuery会以完全相同的方式解释提供的JSON。这可能是因为规范(http://www.ietf.org/rfc/rfc4627.txt)指出“JSON文本应编码为Unicode。默认编码为UTF-8”。因此,在将从编码为iso-8859-1的文件中获取的变量的JSON编码文本之后,将标头设置为“Content-Type:application / json; charset =” iso-8859-1“并通过ajax发送到一个编码为iso-8859-1的html页面会产生与不指定任何内容相同的结果:浏览器将字符串解释为“NULL”。 - Pere

1
有一些用于在JavaScript中进行字符集转换的库。但如果你想要一个简单的解决方案,下面的函数大致可以满足你的需求:
function stringToBytes(text) {
  const length = text.length;
  const result = new Uint8Array(length);
  for (let i = 0; i < length; i++) {
    const code = text.charCodeAt(i);
    const byte = code > 255 ? 32 : code;
    result[i] = byte;
  }
  return result;
}

如果您想将结果字节数组转换为Blob,可以按照以下方式进行操作:
const originalString = 'ååå';
const bytes = stringToBytes(originalString);
const blob = new Blob([bytes.buffer], { type: 'text/plain; charset=ISO-8859-1' });

现在,请记住,一些应用程序确实接受UTF-8编码,但是除非您在前面添加BOM字符(如这里所解释的那样),否则它们无法猜测编码。


你能为Javascript字符集转换建议一些库的选项吗? - Orestis Kapar
将所有特殊字符转换为空格(32)显然不是“你想要的近似结果” :) - marcelj

0

由于 escape已被弃用(而且实际上对我没有起作用),因此我使用了一个小型库进行编码。 我选择了一个名为iso-8859-15的库。 请注意,ISO-8859-15与ISO-8859-1仅在少数字符上有所不同(comparison),您输入的内容很可能实际上是ISO-8859-15而不是ISO-8859-1。

import {encode} from 'iso-8859-15';

const encodedBytes = new Uint8Array(encode(unicodeString))
const blob = new Blob([encodedBytes])

-4
你应该在你的页面上方添加这一行。
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

2
这个答案是否缺少一些细节? - Nate Barbettini

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