Golang - tealeg/xlsx Sheet Hidden
概述
本文描述使用tealeg/xlsx时,如何隐藏一个工作表。
问题描述
这个库的Sheet定义如下:
// Sheet is a high level structure intended to provide user access to
// the contents of a particular sheet within an XLSX file.
type Sheet struct {
Name string
File *File
Rows []*Row
Cols []*Col
MaxRow int
MaxCol int
Hidden bool
Selected bool
SheetViews []SheetView
SheetFormat SheetFormat
AutoFilter *AutoFilter
}
其中的Hidden字段,从字面上来讲,表示是否隐藏该工作表。但实际验证来看,存储文件时,下面的调用并不起作用:
mySheet.Hidden = true
分析
Hidden字段
通过搜索整个源代码,发现在写Excel文件时,并没有用到Sheet的Hidden属性。因此,应用程序设置该字段并不会起任何作用。
针对xlsx文件进行验证
首先验证excel文件中,修改哪个地方可以隐藏工作表。为此,解压缩xlsx文件:
unzip my_file.xlsx -dabc
找到abc/xl/workbook.xml文件,其中某个工作表的定义如下:
<sheet name="my_sheet" sheetId="2" r:id="rId2" state="visible"></sheet><
这里的state是visible,由此导致工作表没有被隐藏。
为了验证,把这里的visible改为hidden,再把整个abc压缩成另外一个Excel xlsx文件:
cd abc
zip -ry ../my_file2.xlsx .
用Office Excel打开,发现工作表被隐藏了。
接下来分析如何修改源码而支持工作表的隐藏功能。
file.go/MarshallParts()
顺着xl/workbook.xml文件,找到存储xlsx文件的一个主要函数。如下:
// Construct a map of file name to XML content representing the file
// in terms of the structure of an XLSX file.
func (f *File) MarshallParts() (map[string]string, error) {
...
if len(f.Sheets)==0 {
err:= errors.New("Workbook must contains atleast one worksheet")
return nil, err
}
for _, sheet := range f.Sheets {
...
workbook.Sheets.Sheet[sheetIndex-1] = xlsxSheet{
Name: sheet.Name,
SheetId: sheetId,
Id: rId,
State: "visible"}
parts[partName], err = marshal(xSheet)
if err != nil {
return parts, err
}
sheetIndex++
}
其中的for循环遍历每个工作表,设置相关属性。注意到xlsxSheet这个数据结构,其中的State正是设置隐藏与否这个属性的地方。其定义:
// xlsxSheet directly maps the sheet element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
// currently I have not checked it for completeness - it does as much
// as I need.
type xlsxSheet struct {
Name string `xml:"name,attr,omitempty"`
SheetId string `xml:"sheetId,attr,omitempty"`
Id string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
State string `xml:"state,attr,omitempty"`
}
这些信息和workbook.xml中是完全一致的。
基于这些分析,即可确定代码的修改方法。
方案一
一共三个地方:
- file.go/MarshallParts(): State = sheet.State
- sheet.go/Sheet struct: 增加State成员
- 应用程序定义sheet State属性值
file.go/MarshallParts()
func (f *File) MarshallParts() (map[string]string, error) {
...
for _, sheet := range f.Sheets {
...
workbook.Sheets.Sheet[sheetIndex-1] = xlsxSheet{
Name: sheet.Name,
SheetId: sheetId,
Id: rId,
State: sheet.State/*"visible"*/} // Modify here.
...
}
sheet.go/Sheet struct
// Sheet is a high level structure intended to provide user access to
// the contents of a particular sheet within an XLSX file.
type Sheet struct {
Name string
File *File
Rows []*Row
Cols []*Col
MaxRow int
MaxCol int
Hidden bool
State string // New Added
Selected bool
SheetViews []SheetView
SheetFormat SheetFormat
AutoFilter *AutoFilter
}
应用程序
//ef.shtFail.Hidden = true // Discard 'Hidden' and set 'State'
ef.shtFail.State = "hidden"
遗留问题
以上是尚可使用的解决办法之一。几个遗留问题:
-
如果用户没有设置Sheet的(新增的)State属性,那么工作表也仍然是Visible状态。对应xml无state属性:
-
官方定义的三个state值如下。如果用户指定了其他非法值,那么结果未定义。
// see xlsx/xmlWorkbook.go
const (
// sheet state values as defined by
// http://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.sheetstatevalues.aspx
sheetStateVisible = “visible”
sheetStateHidden = “hidden”
sheetStateVeryHidden = “veryHidden”
)
为此,考虑另外一种修改方法。
方案二
仍然使用Sheet的Hidden属性。但在序列化的地方,增加一个if处理:
func (f *File) MarshallParts() (map[string]string, error) {
...
for _, sheet := range f.Sheets {
...
workbook.Sheets.Sheet[sheetIndex-1] = xlsxSheet{
Name: sheet.Name,
SheetId: sheetId,
Id: rId,
State: "visible"}
if sheet.Hidden {
workbook.Sheets.Sheet[sheetIndex-1].State = sheetStateHidden
}
parts[partName], err = marshal(xSheet)
...
}