Node.js 将 ISO8859-1 编码转换为 UTF-8

9

我有一个应用程序允许用户将字符串保存到数据库中,这些字符串可能包含表情符号。我的问题是,例如 这样的表情符号会被存储在MySQL中为 😊

当我使用PHP MySQL客户端检索此字符串并在Web浏览器中呈现时,它可以正常呈现,这可能是因为 Content-Type 设置为UTF-8。当我尝试在node.js中读取该字符串时,我得到的是ISO8859-1编码的字面量 😊。表上的字符集设置为latin1,这就是我从中获取ISO8859-1的地方。

在node.js中正确的编码字符串的方式是什么,以便我可以看到表情符号而不是MySQL设置的编码?当我通过 console.log 输出该字符串时。


3
是否考虑直接修复列的字符集呢? - zerkms
@zerkms 很遗憾,目前这不是一个选项,理想情况下我希望能在客户端解决这个问题。不过我确实明白你的建议是最理想的解决方案。 - randombits
看起来您需要将Unicode Codepoints转换为相应的ISO8859-1字符。然后使用数组缓冲区从原始字节组装UTF字符串。后者可以在https://dev59.com/fWw05IYBdhLWcg3w41wp#11058858找到,而前者可以尝试https://github.com/ashtuchkin/iconv-lite。此外,您还可以手动创建转换表格,因为它很小,所以可以简单地处理。 - zerkms
@zerkms - ISO8859-1没有表情符号的编码,因此无法将其塞入该字符集。 - Rick James
@RickJames 我并没有说它有,我的回答与你在mysql中建议的一样,只不过是在js中实现的。 - zerkms
6个回答

13

😊Mojibake,表示笑脸表情。如果将它当作latin1解释,你可以得到十六进制值F09F988A,这是该Emoji的UTF-8十六进制值。

注意:在MySQL外部使用UTF-8等同于在MySQL内部使用utf8mb4

在MySQL中,你必须使用CHARACTER SET utf8mb4声明列/表,同时还必须指定数据存储/获取时的编码为utf8mb4。注意:使用utf8不足以满足需求。

执行SELECT HEX(col) FROM ...命令,查看是否得到该Emoji的十六进制值。如果是并且该列当前为latin1,则需要小心地将该列转换为utf8mb4。也就是说,你有一个CHARACTER SET latin1列,但其中包含UTF-8字节;这会保持字节不变,同时修复字符集。假设该列已经是VARCHAR(111) CHARACTER SET latin1 NOT NULL,那么请按以下两步进行更改:

ALTER TABLE tbl MODIFY COLUMN col VARBINARY(111) NOT NULL;
ALTER TABLE tbl MODIFY COLUMN col VARCHAR(111) CHARACTER SET utf8mb4 NOT NULL;

几乎任何其他转换机制都会造成更糟糕的混乱。

至于正确建立连接,对于node.js来说大致如下:

var connection = mysql.createConnection({ ... , charset : 'utf8mb4'});

如果列是CHAR类型(而不是VARCHAR),这会有任何变化吗?我想答案是否定的,但当我转换所有的乱码字节时,它们会变成字面上的问号? - randombits
再查看一下链接 -- 问号是由其他原因造成的。检查“最佳实践”列表。除非字符串长度固定,否则CHAR往往会浪费空间。 - Rick James

3
你不需要,也不应该转换编码。只需使用正确的协议。如果你将HTML页面发送为UTF-8,则浏览器会以UTF-8的形式将数据发送回你的服务器。
然后你想要将数据存储到你的数据库中,而该数据库是使用latin1编码的,这根本行不通。你必须将你的数据库转换为UTF-8编码。这包括数据库、表和最终列本身。还要确保你的数据库客户端配置为以UTF-8连接,因为客户端本身必须声明其编码。
一旦你的整个数据流都是UTF-8,一切都将无缝运行。
服务器 -> 获取HTML -> 发送POST -> 服务器 -> SQL客户端 -> 数据库 -> 表 -> 列

2
如果您阅读原帖中的评论,您会发现目前无法将数据库更改为UTF-8。 - randombits
4
我明白了,但是使用肮脏的技巧来转换编码只会打开通往地狱之门的选项。OP的团队应该紧急讨论这个问题,并做出相应的决定。 - Guillaume F.

3

建议使用iconv(一种简单的ISO-8859-1到UTF-8转换)

来源于这个代码片段

var iconv = require('iconv');

function toUTF8(body) {
  // convert from iso-8859-1 to utf-8
  var ic = new iconv.Iconv('iso-8859-1', 'utf-8');
  var buf = ic.convert(body);
  return buf.toString('utf-8');
}

如果您传递任何ISO-8859-1编码的内容,它将返回其UTF-8编码。

例如,

toUTF8("😊");

将返回


1
你真的尝试过这个吗?我得到了ðŸË的返回。 - randombits

2
我找到了一种非常不正规的方法来将其转换回去:

    const isoToUtfTable = {
      'ð': 0xf0,
      'Ÿ': 0x9f,
      '˜': 0x98,
      'Š': 0x8a
    };
    
    function convertISO8859ToUtf8(s) {
      const buf = new Uint8Array([...s].map(c => isoToUtfTable[c]));
      return String.fromCharCode(...buf)
    }
    
    function decode_utf8(s) {
      return decodeURIComponent(escape(s));
    }
    
    console.log(decode_utf8(convertISO8859ToUtf8('😊')))

现在你只需要完成 isoToUtfTable 表格(它很小,参见 https://en.wikipedia.org/wiki/ISO/IEC_8859-1)即可。请注意不要删除任何 HTML 标签。

必须有比手动填充查找表更好的方法。如果这是最佳答案,我会标记它,但我会先等待是否有更可靠的方法。 - randombits
@randombits 你可以使用 https://github.com/ashtuchkin/iconv-lite 或其他库来进行转换。这只是一种在不引入依赖项的情况下完成转换的方法。 - zerkms
我一直在尝试使用iconv-lite让它工作,我不介意添加依赖项 - 但似乎无法将其转换回UTF-8。只会显示在数据库中的字符串文字。 - randombits
你能否更新你的答案,展示如何使用iconv-lite来完成这个操作? - randombits
1
@randombits 我尝试使用他们的 https://github.com/ashtuchkin/iconv-lite/blob/master/encodings/sbcs-data-generated.js,但显然 😊 中最新的 4 个字符中有 3 个不是有效的 ISO-8859-1 字符。所以,我不知道除了创建一个小的 255 元素表(如果下半部分相同,则可能甚至只需要 127)之外,还有什么其他的修复方法。 - zerkms

1
也许可以看一下node-iconv
const iconv = new Iconv('ISO-8859-2', 'UTF-8');
const buffer = iconv.convert(something);
console.log(buffer);
console.log(buffer.toString('UTF8'));

1

这是@zerkms解决方案的完整答案

 const isoToUtfTable = {
'€':0x80,
'na':0x81,
'‚':0x82,
'ƒ':0x83,
'„':0x84,
'…':0x85,
'†':0x86,
'‡':0x87,
'ˆ':0x88,
'‰':0x89,
'Š':0x8a,
'‹':0x8b,
'Œ':0x8c,
'na':0x8d,
'Ž':0x8e,
'na':0x8f,
'na':0x90,
'‘':0x91,
'’':0x92,
'“':0x93,
'”':0x94,
'•':0x95,
'–':0x96,
'—':0x97,
'˜':0x98,
'™':0x99,
'š':0x9a,
'›':0x9b,
'œ':0x9c,
'na':0x9d,
'ž':0x9e,
'Ÿ':0x9f,
'NSBP':0xa0,
'¡':0xa1,
'¢':0xa2,
'£':0xa3,
'¤':0xa4,
'¥':0xa5,
'¦':0xa6,
'§':0xa7,
'¨':0xa8,
'©':0xa9,
'ª':0xaa,
'«':0xab,
'¬':0xac,
'SHY':0xad,
'®':0xae,
'¯':0xaf,
'°':0xb0,
'±':0xb1,
'²':0xb2,
'³':0xb3,
'´':0xb4,
'µ':0xb5,
'¶':0xb6,
'·':0xb7,
'¸':0xb8,
'¹':0xb9,
'º':0xba,
'»':0xbb,
'¼':0xbc,
'½':0xbd,
'¾':0xbe,
'¿':0xbf,
'À':0xc0,
'Á':0xc1,
'Â':0xc2,
'Ã':0xc3,
'Ä':0xc4,
'Å':0xc5,
'Æ':0xc6,
'Ç':0xc7,
'È':0xc8,
'É':0xc9,
'Ê':0xca,
'Ë':0xcb,
'Ì':0xcc,
'Í':0xcd,
'Î':0xce,
'Ï':0xcf,
'Ð':0xd0,
'Ñ':0xd1,
'Ò':0xd2,
'Ó':0xd3,
'Ô':0xd4,
'Õ':0xd5,
'Ö':0xd6,
'×':0xd7,
'Ø':0xd8,
'Ù':0xd9,
'Ú':0xda,
'Û':0xdb,
'Ü':0xdc,
'Ý':0xdd,
'Þ':0xde,
'ß':0xdf,
'à':0xe0,
'á':0xe1,
'â':0xe2,
'ã':0xe3,
'ä':0xe4,
'å':0xe5,
'æ':0xe6,
'ç':0xe7,
'è':0xe8,
'é':0xe9,
'ê':0xea,
'ë':0xeb,
'ì':0xec,
'í':0xed,
'î':0xee,
'ï':0xef,
'ð':0xf0,
'ñ':0xf1,
'ò':0xf2,
'ó':0xf3,
'ô':0xf4,
'õ':0xf5,
'ö':0xf6,
'÷':0xf7,
'ø':0xf8,
'ù':0xf9,
'ú':0xfa,
'û':0xfb,
'ü':0xfc,
'ý':0xfd,
'þ':0xfe,
'ÿ':0xff }





let offsetArray = [];
function convertISO8859ToUtf8Simple(s) {
    offsetArray = [];
    const buf = new Uint8Array([...s].map((c, index) => 
        {
            if(isoToUtfTable[c]) {
                if(offsetArray.length > 0 && offsetArray[offsetArray.length -1]+3 < index) {
                    offsetArray.push(index);
                }
                if(offsetArray.length == 0) {
                    offsetArray.push(index);
                }
            }
            
            return isoToUtfTable[c];
        }
        
        ));
      return String.fromCharCode(...buf);
      
    }

    
function decode_utf8(s) {
      return decodeURIComponent(escape(s));
    }


function  emojiStringToArray(str) {
  split = str.split(/([\uD800-\uDBFF][\uDC00-\uDFFF])/);
  arr = [];
  for (var i=0; i<split.length; i++) {
    char = split[i]
    if (char !== "" && !char.includes('\u0000')) {
      arr.push(char);
    }
  }
  return arr;
};


const string = 'hello 😌😌 with some emojis 😊 ';

function finalString(s) {
    const emojis = emojiStringToArray(decode_utf8(convertISO8859ToUtf8Simple(s)));

    for(let i = 0; i<offsetArray.length; i++){ 
        let position = 0;
        if (i == 0) {
            position = offsetArray[i];
        } else {
            position = (i * -3) + offsetArray[i] + (i);
        }
            s =  [s.slice(0, position), emojis[i], s.slice(position+4)].join('');
    }
    return s;
}

console.log(finalString(string));


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