New feature: group and ungroup sheets support

New functions `GroupSheets` and `UngroupSheets` added
Refactor sheet index calculation
This commit is contained in:
xuri 2019-07-03 00:50:10 +08:00 committed by GitHub
parent dc8210d4a7
commit 8b2d4cb697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 109 additions and 46 deletions

155
sheet.go
View File

@ -275,13 +275,20 @@ func (f *File) SetActiveSheet(index int) {
// GetActiveSheetIndex provides a function to get active sheet index of the
// XLSX. If not found the active sheet will be return integer 0.
func (f *File) GetActiveSheetIndex() int {
for idx, name := range f.GetSheetMap() {
xlsx, _ := f.workSheetReader(name)
for _, sheetView := range xlsx.SheetViews.SheetView {
if sheetView.TabSelected {
return idx
wb := f.workbookReader()
if wb != nil {
view := wb.BookViews.WorkBookView
sheets := wb.Sheets.Sheet
var activeTab int
if len(view) > 0 {
activeTab = view[0].ActiveTab
if len(sheets) > activeTab && sheets[activeTab].SheetID != 0 {
return sheets[activeTab].SheetID
}
}
if len(wb.Sheets.Sheet) == 1 {
return wb.Sheets.Sheet[0].SheetID
}
}
return 0
}
@ -308,34 +315,26 @@ func (f *File) SetSheetName(oldName, newName string) {
// worksheet index. If given sheet index is invalid, will return an empty
// string.
func (f *File) GetSheetName(index int) string {
content := f.workbookReader()
rels := f.workbookRelsReader()
for _, rel := range rels.Relationships {
rID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(rel.Target, "worksheets/sheet"), ".xml"))
if rID == index {
for _, v := range content.Sheets.Sheet {
if v.ID == rel.ID {
return v.Name
}
wb := f.workbookReader()
if wb != nil {
for _, sheet := range wb.Sheets.Sheet {
if sheet.SheetID == index {
return sheet.Name
}
}
}
return ""
}
// GetSheetIndex provides a function to get worksheet index of XLSX by given sheet
// name. If given worksheet name is invalid, will return an integer type value
// 0.
// GetSheetIndex provides a function to get worksheet index of XLSX by given
// sheet name. If given worksheet name is invalid, will return an integer type
// value 0.
func (f *File) GetSheetIndex(name string) int {
content := f.workbookReader()
rels := f.workbookRelsReader()
for _, v := range content.Sheets.Sheet {
if v.Name == name {
for _, rel := range rels.Relationships {
if v.ID == rel.ID {
rID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(rel.Target, "worksheets/sheet"), ".xml"))
return rID
}
wb := f.workbookReader()
if wb != nil {
for _, sheet := range wb.Sheets.Sheet {
if sheet.Name == trimSheetName(name) {
return sheet.SheetID
}
}
}
@ -354,16 +353,11 @@ func (f *File) GetSheetIndex(name string) int {
// }
//
func (f *File) GetSheetMap() map[int]string {
content := f.workbookReader()
rels := f.workbookRelsReader()
wb := f.workbookReader()
sheetMap := map[int]string{}
for _, v := range content.Sheets.Sheet {
for _, rel := range rels.Relationships {
relStr := strings.SplitN(rel.Target, "worksheets/sheet", 2)
if rel.ID == v.ID && len(relStr) == 2 {
rID, _ := strconv.Atoi(strings.TrimSuffix(relStr[1], ".xml"))
sheetMap[rID] = v.Name
}
if wb != nil {
for _, sheet := range wb.Sheets.Sheet {
sheetMap[sheet.SheetID] = sheet.Name
}
}
return sheetMap
@ -411,19 +405,31 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
// value of the deleted worksheet, it will cause a file error when you open it.
// This function will be invalid when only the one worksheet is left.
func (f *File) DeleteSheet(name string) {
content := f.workbookReader()
for k, v := range content.Sheets.Sheet {
if v.Name == trimSheetName(name) && len(content.Sheets.Sheet) > 1 {
content.Sheets.Sheet = append(content.Sheets.Sheet[:k], content.Sheets.Sheet[k+1:]...)
sheet := "xl/worksheets/sheet" + strconv.Itoa(v.SheetID) + ".xml"
rels := "xl/worksheets/_rels/sheet" + strconv.Itoa(v.SheetID) + ".xml.rels"
target := f.deleteSheetFromWorkbookRels(v.ID)
if f.SheetCount == 1 || f.GetSheetIndex(name) == 0 {
return
}
sheetName := trimSheetName(name)
wb := f.workbookReader()
wbRels := f.workbookRelsReader()
for idx, sheet := range wb.Sheets.Sheet {
if sheet.Name == sheetName {
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:idx], wb.Sheets.Sheet[idx+1:]...)
var sheetXML, rels string
if wbRels != nil {
for _, rel := range wbRels.Relationships {
if rel.ID == sheet.ID {
sheetXML = fmt.Sprintf("xl/%s", rel.Target)
rels = strings.Replace(fmt.Sprintf("xl/%s.rels", rel.Target), "xl/worksheets/", "xl/worksheets/_rels/", -1)
}
}
}
target := f.deleteSheetFromWorkbookRels(sheet.ID)
f.deleteSheetFromContentTypes(target)
f.deleteCalcChain(v.SheetID, "") // Delete CalcChain
delete(f.sheetMap, name)
delete(f.XLSX, sheet)
f.deleteCalcChain(sheet.SheetID, "") // Delete CalcChain
delete(f.sheetMap, sheetName)
delete(f.XLSX, sheetXML)
delete(f.XLSX, rels)
delete(f.Sheet, sheet)
delete(f.Sheet, sheetXML)
f.SheetCount--
}
}
@ -1285,6 +1291,63 @@ func (f *File) GetDefinedName() []DefinedName {
return definedNames
}
// GroupSheets provides a function to group worksheets by given worksheets
// name. Group worksheets must contain an active worksheet.
func (f *File) GroupSheets(sheets []string) error {
// check an active worksheet in group worksheets
var inActiveSheet bool
activeSheet := f.GetActiveSheetIndex()
sheetMap := f.GetSheetMap()
for idx, sheetName := range sheetMap {
for _, s := range sheets {
if s == sheetName && idx == activeSheet {
inActiveSheet = true
}
}
}
if !inActiveSheet {
return errors.New("group worksheet must contain an active worksheet")
}
// check worksheet exists
ws := []*xlsxWorksheet{}
for _, sheet := range sheets {
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws = append(ws, xlsx)
}
for _, s := range ws {
sheetViews := s.SheetViews.SheetView
if len(sheetViews) > 0 {
for idx := range sheetViews {
s.SheetViews.SheetView[idx].TabSelected = true
}
continue
}
}
return nil
}
// UngroupSheets provides a function to ungroup worksheets.
func (f *File) UngroupSheets() error {
activeSheet := f.GetActiveSheetIndex()
sheetMap := f.GetSheetMap()
for sheetID, sheet := range sheetMap {
if activeSheet == sheetID {
continue
}
xlsx, _ := f.workSheetReader(sheet)
sheetViews := xlsx.SheetViews.SheetView
if len(sheetViews) > 0 {
for idx := range sheetViews {
xlsx.SheetViews.SheetView[idx].TabSelected = false
}
}
}
return nil
}
// workSheetRelsReader provides a function to get the pointer to the structure
// after deserialization of xl/worksheets/_rels/sheet%d.xml.rels.
func (f *File) workSheetRelsReader(path string) *xlsxWorkbookRels {