Go-Excelize API源码阅读(二十八)—— Cols(sheet string)
开源摘星计划(WeOpen Star) 是由腾源会 2022 年推出的全新项目,旨在为开源人提供成长激励,为开源项目提供成长支持,助力开发者更好地了解开源,更快地跨越鸿沟,参与到开源的具体贡献与实践中。
不管你是开源萌新,还是希望更深度参与开源贡献的老兵,跟随“开源摘星计划”开启你的开源之旅,从一篇学习笔记、到一段代码的提交,不断挖掘自己的潜能,最终成长为开源社区的“闪亮之星”。
我们将同你一起,探索更多的可能性!
项目地址: WeOpen-Star:https://github.com/weopenprojects/WeOpen-Star
一、Go-Excelize简介
Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写 API,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.15 或更高版本。
二、Cols(sheet string)
代码语言:javascript复制func (f *File) Cols(sheet string) (*Cols, error)
根据给定的工作表名称获取该工作表的列迭代器。此功能是并发安全的。使用列迭代器进行流式读取遍历单元格:
代码语言:javascript复制cols, err := f.Cols("Sheet1")
if err != nil {
fmt.Println(err)
return
}
for cols.Next() {
col, err := cols.Rows()
if err != nil {
fmt.Println(err)
}
for _, rowCell := range col {
fmt.Print(rowCell, "t")
}
fmt.Println()
}
下面直接来看源代码:
代码语言:javascript复制func (f *File) Cols(sheet string) (*Cols, error) {
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
return nil, ErrSheetNotExist{sheet}
}
if ws, ok := f.Sheet.Load(name); ok && ws != nil {
worksheet := ws.(*xlsxWorksheet)
worksheet.Lock()
defer worksheet.Unlock()
output, _ := xml.Marshal(worksheet)
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
}
var colIterator columnXMLIterator
colIterator.cols.sheetXML = f.readBytes(name)
decoder := f.xmlNewDecoder(bytes.NewReader(colIterator.cols.sheetXML))
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch xmlElement := token.(type) {
case xml.StartElement:
columnXMLHandler(&colIterator, &xmlElement)
if colIterator.err != nil {
return &colIterator.cols, colIterator.err
}
case xml.EndElement:
if xmlElement.Name.Local == "sheetData" {
colIterator.cols.f = f
colIterator.cols.sheet = trimSheetName(sheet)
return &colIterator.cols, nil
}
}
}
return &colIterator.cols, nil
}
先来看Cols的第一部分代码:
代码语言:javascript复制 name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
return nil, ErrSheetNotExist{sheet}
}
if ws, ok := f.Sheet.Load(name); ok && ws != nil {
worksheet := ws.(*xlsxWorksheet)
worksheet.Lock()
defer worksheet.Unlock()
output, _ := xml.Marshal(worksheet)
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
}
先从表格文件的表格映射表中查找我们要遍历的那张表是否存在,并获取它的name。
然后加载我们要操作的工作表,因为得到的ws是any类型,所以我们要使用worksheet := ws.(*xlsxWorksheet)
来进行类型断言。
前面介绍了此功能是并发安全的,为什么并发安全呢,其中重要的一个因素在于此处使用了锁。
worksheet.Lock()
defer worksheet.Unlock()
output, _ := xml.Marshal(worksheet)
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
加锁后才进行worksheet的xml序列化操作,并使用replaceNameSpaceBytes
通过给定的组件部分路径和XML内容替换XML根元素属性,然后再更新电子表格的文件列表中的指定文件内容。
再来看看第二部分代码:
代码语言:javascript复制 var colIterator columnXMLIterator
colIterator.cols.sheetXML = f.readBytes(name)
decoder := f.xmlNewDecoder(bytes.NewReader(colIterator.cols.sheetXML))
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch xmlElement := token.(type) {
case xml.StartElement:
columnXMLHandler(&colIterator, &xmlElement)
if colIterator.err != nil {
return &colIterator.cols, colIterator.err
}
case xml.EndElement:
if xmlElement.Name.Local == "sheetData" {
colIterator.cols.f = f
colIterator.cols.sheet = trimSheetName(sheet)
return &colIterator.cols, nil
}
}
}
return &colIterator.cols, nil
columnXMLIterator的定义如下:
将之前获得的name指向的工作表以字节的形式进行读取,然后保存在colIterator.cols.sheetXML
中。
然后让xml decoder读取,其内容放入*xml.Decoder
变量decoder
。
json.Decoder提供了Token方法,用来返回JSON串中的每一个Token。Token包括:json.Delim,基本类型(bool,float64,Number,string)和nil。其中json.Delim包括[ ] { }。每一次调用Token()方法都会返回以上Token中的一个。
同样使用xml包的NewDecoder函数,可以创建XML内容的Decoder。通过在Decoder上调用Token方法,接收xml.Token。 xml.Token是保存令牌类型的接口。 可以根据类型定义代码的行为。
代码语言:javascript复制 token, _ := decoder.Token()
xml.StartElement和xml.EndElement就是json的Token中的[ ] { }
。
如果是xml.StartElement:
columnXMLHandler(&colIterator, &xmlElement)
if colIterator.err != nil {
return &colIterator.cols, colIterator.err
}
columnXMLHandler解析工作表的列XML元素,将xml的工作表放入迭代器。
如果是xml.EndElement:
代码语言:javascript复制if xmlElement.Name.Local == "sheetData" {
colIterator.cols.f = f
colIterator.cols.sheet = trimSheetName(sheet)
return &colIterator.cols, nil
}
一个Name有一个XML名称(Local),并带有一个名称空间标识符(Space)的注释。 在由Decoder.Token返回的令牌中,Space标识符是作为一个规范的URL给出的,而不是在被解析的文档中使用的短前缀。
那么我们就判断其Local是否为sheetData,如果是我们就将f赋给colIterator.cols.f,将sheet赋给colIterator.cols.sheet,然后直接得到迭代器。