如何批量将xls转换成xlsx(批量转换xls文件到xlsx格式)

概要: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个字节。

如何批量将xls转换成xlsx(批量转换xls文件到xlsx格式)(1)

window1251 并不支持汉字

windows-1251(CP1251)是一种流行的8位字符编码,设计涵盖语言使用西里尔如俄罗斯,保加利亚,塞尔维亚西里尔和其他语言。它是最广泛使用的编码保加利亚,塞尔维亚和马其顿语言。

如何批量将xls转换成xlsx(批量转换xls文件到xlsx格式)(2)

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脚本都可以切换过来了。

如何批量将xls转换成xlsx(批量转换xls文件到xlsx格式)(3)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页