Go-Excelize API源码阅读(二十八)—— Cols(sheet string)

2022-10-25 19:45:05 浏览数 (1)

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)来进行类型断言。 前面介绍了此功能是并发安全的,为什么并发安全呢,其中重要的一个因素在于此处使用了锁。

代码语言:javascript复制
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:

代码语言:javascript复制
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,然后直接得到迭代器。

0 人点赞