云开发中导入excel/cvs文件乱码的原因,以及ANSI编码和带BOM的utf-8是什么


前言


在做项目的时候发现支付宝账单是ANSI编码;微信账单是带BOM的utf-8编码。
这两个账单都是CVS文件,但在读取的时候ANSI发生了乱码。记录一下解决过程。

1. 了解 ANSI 与 utf-8 with BOM

(1)什么是 ANSI 编码?

不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准。这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统中,ANSI 编码代表 GB2312编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 JIS 编码。

(2)什么是 带BOM的utf-8 编码?

utf-8我们知道是基于UTF万国码的一种编码,那么BOM呢?

直接看wiki的定义:

字节顺序标记(英语:byte-order mark,BOM)是位于码点U+FEFF的统一码字符的名称。当以UTF-16或UTF-32来将UCS/统一码字符所组成的字符串编码时,这个字符被用来标示其字节序。它常被用来当做标示文件是以UTF-8、UTF-16或UTF-32编码的标记。

字节顺序标记通常有几种涵义:

  1. 在16位和32位的情况下,文字流的字节顺序。

  2. 表示文字流非常有可能是Unicode编码。

  3. 使用的是哪一种Unicode字符编码。

每个Unicode编码(包括Unicode标准以外的编码,如UTF-7,见下表)的BOM字节序列都不一样,而且这些序列都不可能出现在以其他编码存储的文字流的开头。因此,在文字流的开头放置一个编码的BOM,可以表明文本是Unicode,并识别所使用的编码方案。这种对BOM字符的使用被称为“Unicode签名”。

2.解决导入cvs乱码

现在回过来看为什么导入的cvs会乱码

云存储在存储文件的时候是不会改变其文件编码的,而在云函数的 node 中,我使用的是 node-xlsx 来进行 excel 导入。从 node-xlsx 给出的例子来看,node-xlsx 是只接收 utf-8 文件的。那么,一个 ANSI (简中为GB2312)编码必然不能被 node-xlsx 解析,这就需要转成 utf-8 。

我们很自然的会想到用toString方法,再 Buffer.form 回去,但是 node 中这些方法都没有 GB2312 编码。

switch(encoding){
      case 'hex':
      case 'utf8':
      case 'utf-8':
      case 'ascii':
      case 'binary':
      case 'base64':
      case 'ucs2':
      case 'ucs-2':
      case 'utf16le':
      case 'utf-16le':
}

所以我们需要另一个插件 iconv-lite 转换。

核心代码:

//buffer根据原编码GB2312解码成字符串,不舍弃BOM
let sourceString = iconv.decode(sourceBuffer,"GB2312",{stripBOM: false})
//字符串转码成utf-8的buffer,添加BOM
let buff = iconv.encode(sourceString,"utf8",{addBOM: true});

代码片段:

const cloud = require('wx-server-sdk')
const xlsx = require('node-xlsx')
const iconv = require('iconv-lite');
cloud.init({ // 初始化云开发环境
    env: cloud.DYNAMIC_CURRENT_ENV // 当前环境的常量
})

// 云函数入口函数
exports.main = async (event, context) => {
    const { fileID } = event;

    // 下载文件
    const res = await cloud.downloadFile({ fileID })
    let sourceBuffer = res.fileContent;
    
    //buffer根据原编码GB2312解码成字符串,不舍弃BOM
    let sourceString = iconv.decode(sourceBuffer,"GB2312",{stripBOM: false})
    //字符串转码成utf-8的buffer,添加BOM
    let buff = iconv.encode(sourceString,"utf8",{addBOM: true});
    
    //开始解析
    let sheet = xlsx.parse(buff,{ cellDates: true })[0];

    return sheet;
}

至于为什么需要BOM,其实即使不使用BOM,字符串的输出也没有乱码了,但是excel插件没有BOM,解析不行。大胆推测一下,插件需要BOM来正确的解析excel。

3. 注意

我看网上很多人的代码在字符串已经出现乱码的情况下,还要进行转码,这就非常离谱了,你把乱码转成另外一种编码有什么用呢?解码出来不还是乱码吗?这完全是对编码解码与buffer之间关系的不理解。

4. 参考

https://zh.wikipedia.org/wiki/%E4%BD%8D%E5%85%83%E7%B5%84%E9%A0%86%E5%BA%8F%E8%A8%98%E8%99%9F

https://gitee.com/mihome/iconv-lite#bom-handling

https://github.com/mgcrea/node-xlsx


评论
  目录