xlsx文件如何批量转换成xlsx(批量转换xls文件到xlsx格式)
xlsx文件如何批量转换成xlsx(批量转换xls文件到xlsx格式)GB18030编码:2000年3月17日发布的汉字编码国家标准,是对GBK编码的扩充,覆盖中文、日文、朝鲜语和中国少数民族文字,其中收录27484个汉字。GB18030字符集采用单字节、双字节和四字节三种方式对字符编码。兼容GBK和GB2312字符集。GBK编码:1995年12月发布的汉字编码国家标准,是对GB2312编码的扩充,对汉字采用双字节编码。GBK字符集共收录21003个汉字,包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编码中的所有汉字。如果出现乱码,说明读取文件时,没有按写入的编码格式来解码,导致在字符集中查不到应有的字符导致不可理解--即乱码。GB2312编码:1981年5月1日发布的简体中文汉字编码国家标准。GB2312对汉字采用双字节编码,收录7445个图形字符,其中包括6763个汉字。BIG5编码:台湾地区繁体中文标准字符集,采用双字节编码,共收录130
概要:xls文件转xlsx,需要支持GBK编码,UTF8编码。本篇文章主要记录一下开发经过和一些思考。
这些天,工作上遇到需要转换xls文件到xlsx格式,于是就想着写个小工具。但是,开始使用一个xls 第三方package包,却对GBK支持的不好,表现就是对有些文件转换后全是乱码。
于是,就想到需要支持windows 和MacOS,就需要兼容GBK和UTF8。
说到编码,可以去头条搜索一下,大概是:我们在windows下经常用到GBK编码,GB2312,BIG5,他们都属于UTF16即2字节编码格式。而在MacOS和Linux系统下经常用到UTF8编码。按不同编码写就的文件,读取时也需要按其编码转成系统支持的字符。
如果出现乱码,说明读取文件时,没有按写入的编码格式来解码,导致在字符集中查不到应有的字符导致不可理解--即乱码。
GB2312编码:1981年5月1日发布的简体中文汉字编码国家标准。GB2312对汉字采用双字节编码,收录7445个图形字符,其中包括6763个汉字。
BIG5编码:台湾地区繁体中文标准字符集,采用双字节编码,共收录13053个中文字,1984年实施。
GBK编码:1995年12月发布的汉字编码国家标准,是对GB2312编码的扩充,对汉字采用双字节编码。GBK字符集共收录21003个汉字,包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编码中的所有汉字。
GB18030编码:2000年3月17日发布的汉字编码国家标准,是对GBK编码的扩充,覆盖中文、日文、朝鲜语和中国少数民族文字,其中收录27484个汉字。GB18030字符集采用单字节、双字节和四字节三种方式对字符编码。兼容GBK和GB2312字符集。
Unicode编码:国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。
如果用GO语言编写这个转换工具,需要用到第三方包,xls和xlsx相关的。在pkg.go.dev上查找这俩关键字,搜索已经实现好的包。
"github.com/IntelligenceX/fileconversion/xls" //对GBK支持不够完善
"github.com/xuancanh/xls" //偶尔无法正常读取数据
"github.com/extrame/xls" //偶尔无法正常读取数据
github.com/shakinm/xlsReader/xls //最后才发现这个包完美支持GBK,GB2312
搜索显示最流行的包排在最前面,但是,这些包都支持的不够好,翻到第4页才找到xlsReader,竟然能解码先前一直乱码的文件。
debug进去看到解码方式果然不同。
IntelligenceX/fileconversion/xls 使用windows1251解码,对GBK这种UTF16编码的直接就认成了UTF8格式。即原先7个汉字的sheet名,14个字节,被解码出来前7个字节。
window1251 并不支持汉字
windows-1251(CP1251)是一种流行的8位字符编码,设计涵盖语言使用西里尔如俄罗斯,保加利亚,塞尔维亚西里尔和其他语言。它是最广泛使用的编码保加利亚,塞尔维亚和马其顿语言。
7个汉字,最后被解码前7个字节
我们debug进去看看,怎么实现的?
func xls2xlsx(xlsfile string pth string) {
wb1 err :=xls.OpenFile(xlsfile);logoute(err)
openfile 跳转到xls.go文件:
package xls
import (
"encoding/binary"
"github.com/shakinm/xlsReader/cfb"
"io"
)
// OpenFile - Open document from the file
func OpenFile(fileName string) (workbook Workbook err error) {
adaptor err := cfb.OpenFile(fileName)
if err != nil {
return workbook err
}
return openCfb(adaptor)
}
cfb模块再打开文件:
// OpenFile - Open document from the file
func OpenFile(filename string) (cfb Cfb err error) {
cfb.file err = os.Open(filepath.Clean(filename))
if err != nil {
return cfb err
}
err = open(&cfb)
return cfb err
}
而cfb是xls文件头结构的实现:
package cfb
import (
"bytes"
"encoding/binary"
"github.com/shakinm/xlsReader/helpers"
"io"
"os"
"path/filepath"
)
// Cfb - Compound File Binary
type Cfb struct {
header Header
file io.ReadSeeker
difatPositions []uint32
miniFatPositions []uint32
dirs []*Directory
}
// EntrySize - Directory array entry length
var EntrySize = 128
// DefaultDIFATEntries -Number FAT locations in DIFAT
var DefaultDIFATEntries = uint32(109)
继续对Header解码,就是二进制读,解码成struct结构的数据:
// OpenFile - Open document from the file
func openCfb(adaptor cfb.Cfb) (workbook Workbook err error) {
var book *cfb.Directory
var root *cfb.Directory
for _ dir := range adaptor.GetDirs() {
fn := dir.Name()
if fn == "Workbook" {
if book == nil {
book = dir
}
}
if fn == "Book" {
book = dir
}
if fn == "Root Entry" {
root = dir
}
}
if book != nil {
size := binary.LittleEndian.Uint32(book.StreamSize[:])
reader err := adaptor.OpenObject(book root)
。。。
return readStream(reader size)
}
return workbook err
}
读完Header也就知道了xls文件各个结构的大小,和位置--偏移量。按结构读出二进制字节,解码出来可供显示的数据。
func readStream(reader io.ReadSeeker streamSize uint32) (workbook Workbook err error) {
stream := make([]byte streamSize)
_ err = reader.Read(stream)
if err != nil {
return workbook nil
}
if err != nil {
return workbook nil
}
err = workbook.read(stream)
if err != nil {
return workbook nil
}
for k := range workbook.sheets {
sheet err := workbook.GetSheet(k)
if err != nil {
return workbook nil
}
err = sheet.read(stream)
if err != nil {
return workbook nil
}
}
return
}
下面这段代码有点长,是一个sheet的关键解码代码:
func (s *Sheet) read(stream []byte) (err error) { // nolint: gocyclo
var point int64
point = int64(helpers.BytesToUint32(s.boundSheet.LbPlyPos[:]))
var sPoint int64
eof := false
records := make(map[string]string )
Next:
recordNumber := stream[point : point 2]
recordDataLength := int64(helpers.BytesToUint16(stream[point 2 : point 4]))
sPoint = point 4
records[fmt.Sprintf("%x" recordNumber)]=fmt.Sprintf("%x" recordNumber)
if bytes.Compare(recordNumber record.AutofilterInfoRecord[:]) == 0 {
c := new(record.AutofilterInfo)
c.Read(stream[sPoint : sPoint recordDataLength])
if c.GetCountEntries() > 0 {
s.hasAutofilter = true
} else {
s.hasAutofilter = false
}
goto EIF
}
//LABELSST - String constant that uses BIFF8 shared string table (new to BIFF8)
if bytes.Compare(recordNumber record.LabelSStRecord[:]) == 0 {
c := new(record.LabelSSt)
c.Read(stream[sPoint:sPoint recordDataLength] &s.wb.sst)
s.addCell(c c.GetRow() c.GetCol())
goto EIF
}
//LABEL - Cell Value String Constant
if bytes.Compare(recordNumber record.LabelRecord[:]) == 0 {
if bytes.Compare(s.wb.vers[:] record.FlagBIFF8) == 0 {
c := new(record.LabelBIFF8)
c.Read(stream[sPoint : sPoint recordDataLength])
s.addCell(c c.GetRow() c.GetCol())
} else {
c := new(record.LabelBIFF5)
c.Read(stream[sPoint : sPoint recordDataLength])
s.addCell(c c.GetRow() c.GetCol())
}
goto EIF
}
。。。。省略
return
}
就是这个关键代码,正常解码了汉字 ---helpers.BytesToUint16
func BytesToUint16(b []byte) uint16 {
return binary.LittleEndian.Uint16(b)
}
func (littleEndian) Uint16(b []byte) uint16 {
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
return uint16(b[0]) | uint16(b[1])<<8
}
label.go中:
Microsoft Office Excel 97-2003 Binary File Format (.xls BIFF8)//Excel 97 - Excel 2003 二进制文件格式 (BIFF8)
func (r *LabelBIFF8) GetString() string {
if int(r.grbit[0]) == 1 {
name := helpers.BytesToUints16(r.rgb[:])
runes := utf16.Decode(name)
return string(runes)
} else {
return string(decodeWindows1251(r.rgb[:]))
}
}
func (r *LabelBIFF5) GetString() string {
strLen := helpers.BytesToUint16(r.cch[:])
return strings.TrimSpace(string(decodeWindows1251(r.rgb[:int(strLen)])))
}
请参考:https://support.microsoft.com/zh-cn/office/excel-支持的文件格式-0943ff2c-6014-4e8d-aaea-b83d51d46247
总结:
1、尽量使用第三方包,但要多测试。自己改进还是需要很深的功底。。。。
2、一个包不行,尽量换。
3、需要了解文件系统。debug过程中文件读都需要mutex lock 和引用计数。
4、xls有官方文件格式文档。可以参考。方便读懂xls包。
我是程序员黑洞,正在学习go语言,工作中所有需要手工的地方都在做成工具。
欢迎访问我的gitee(https://gitee.com/laogg)
用了go后,越发喜欢go的多平台特性。太方便了。我以前的python脚本都可以切换过来了。