Detect True Encoding of File and Convert

JavaScript

在 Windows 上,很多文本文件并不是以 UTF-8 的格式进行存储的。比如,中文可能的存储格式是 GB2312 或是 BIG5。这导致,在其他系统中,如果直接以 UTF-8 的格式打开对应的文本文件,就会得到一串乱码。

如果不知道之前是以什么格式存储的文件,这时就会有点束手无策了。

一个可行的简单方法是用 VSCode 的“猜测”功能。在 VSCode 中,如果选择 Reopen with Encoding,会得到 VSCode 猜测的当前文本编码格式。如果使用新的编码重新打开文本,看到的不再是乱码,那么很可能 VSCode 就猜测正确了。一般建议再以 UTF-8 的格式保存一下,以后再次打开就不会有乱码的困扰了。

然而,这个方法不适应大规模批量修改的需求。既然如此,不如直接从 VSCode 的源码入手,看看这个文本编码检测的功能是如何实现的。

VSCode 相关的代码,可以在 src/vs/base/node/encoding.ts 中找到,GitHub 的代码在这里

精简后的代码如下:

const fs = require('fs');
const jschardet = require('jschardet');
const iconv = require('iconv-lite');

const JSCHARDET_TO_ICONV_ENCODINGS = {
  'ibm866': 'cp866',
  'big5': 'cp950'
};

const fromPath = '/path/to/read/file';
const toPath = '/path/to/save/file';

const buffer = fs.readFileSync(fromPath);
const { encoding } = jschardet.detect(buffer);
const iconvEncoding = JSCHARDET_TO_ICONV_ENCODINGS[encoding] || encoding;
const content = iconv.decode(buffer, iconvEncoding);
fs.writeFileSync(filename, content, 'utf8');

这里主要用到了两个库,jschardeticonv-lite

jschardet 是 Python chardet 的一个 JavaScript 移植版本,用于检测当前的二进制流(Buffer)是什么类型的编码。检测的大致原理,可以在 chardet 的网站上找到(这里)。

iconv-lite 用于将二进制流转化成指定编码格式的字符串,是一个纯 JavaScript 的 iconv 库。这里 iconv 的全称是 internationalization conversion,在类 Unix 系统中,这是一个用于转换不同编码字符串的命令行工具。

需要注意的是,iconv-lite 并不通过编码来区分 UTF-8 和 UTF-8 with BOM,而是通过第二个参数 { addBOM } 来完成的。因此,转化 UTF-8 with BOM 的时候,需要稍微手动处理一下。(处理的方法可以参考 VSCode 中的相关函数,比如 encode

另外,jschardeticonv-lite 对编码的命名有些不同,使用前需要转化。上面示例代码中的 JSCHARDET_TO_ICONV_ENCODINGS 就是做的这个事情。