diff options
-rw-r--r-- | col.go | 3 | ||||
-rw-r--r-- | date.go | 13 | ||||
-rw-r--r-- | excelize.go | 40 | ||||
-rw-r--r-- | excelize_test.go | 53 | ||||
-rw-r--r-- | go.mod | 1 | ||||
-rw-r--r-- | logo.png | bin | 5207 -> 7085 bytes | |||
-rw-r--r-- | sheet.go | 95 | ||||
-rw-r--r-- | sheetpr.go | 20 | ||||
-rw-r--r-- | sheetpr_test.go | 8 | ||||
-rw-r--r-- | sheetview.go | 10 | ||||
-rw-r--r-- | sheetview_test.go | 18 | ||||
-rw-r--r-- | test/Book1.xlsx | bin | 23099 -> 23153 bytes | |||
-rw-r--r-- | test/MergeCell.xlsx | bin | 0 -> 8583 bytes | |||
-rw-r--r-- | xmlWorkbook.go | 2 | ||||
-rw-r--r-- | xmlWorksheet.go | 7 |
15 files changed, 212 insertions, 58 deletions
@@ -337,7 +337,8 @@ func (f *File) RemoveCol(sheet, column string) { f.adjustHelper(sheet, col, -1, -1) } -// Completion column element tags of XML in a sheet. +// completeCol provieds function to completion column element tags of XML in a +// sheet. func completeCol(xlsx *xlsxWorksheet, row, cell int) { buffer := bytes.Buffer{} for r := range xlsx.SheetData.Row { @@ -90,12 +90,13 @@ func julianDateToGregorianTime(part1, part2 float64) time.Time { return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC) } -// By this point generations of programmers have repeated the algorithm sent -// to the editor of "Communications of the ACM" in 1968 (published in CACM, -// volume 11, number 10, October 1968, p.657). None of those programmers seems -// to have found it necessary to explain the constants or variable names set -// out by Henry F. Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy -// that jounal and expand an explanation here - that day is not today. +// doTheFliegelAndVanFlandernAlgorithm; By this point generations of +// programmers have repeated the algorithm sent to the editor of +// "Communications of the ACM" in 1968 (published in CACM, volume 11, number +// 10, October 1968, p.657). None of those programmers seems to have found it +// necessary to explain the constants or variable names set out by Henry F. +// Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that jounal and +// expand an explanation here - that day is not today. func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) { l := jd + 68569 n := (4 * l) / 146097 diff --git a/excelize.go b/excelize.go index 309a8d8..4b4aa32 100644 --- a/excelize.go +++ b/excelize.go @@ -411,3 +411,43 @@ func (f *File) adjustAutoFilterHelper(xlsx *xlsxWorksheet, column, rowIndex, off } } } + +// GetMergeCells provides a function to get all merged cells from a worksheet currently. +func (f *File) GetMergeCells(sheet string) []MergeCell { + mergeCells := []MergeCell{} + + xlsx := f.workSheetReader(sheet) + if xlsx.MergeCells != nil { + for i := 0; i < len(xlsx.MergeCells.Cells); i++ { + ref := xlsx.MergeCells.Cells[i].Ref + axis := strings.Split(ref, ":")[0] + mergeCells = append(mergeCells, []string{ref, f.GetCellValue(sheet, axis)}) + } + } + + return mergeCells +} + +// MergeCell define a merged cell data. +// It consists of the following structure. +// example: []string{"D4:E10", "cell value"} +type MergeCell []string + +// GetCellValue returns merged cell value. +func (m *MergeCell) GetCellValue() string { + return (*m)[1] +} + +// GetStartAxis returns the merge start axis. +// example: "C2" +func (m *MergeCell) GetStartAxis() string { + axis := strings.Split((*m)[0], ":") + return axis[0] +} + +// GetEndAxis returns the merge end axis. +// example: "D4" +func (m *MergeCell) GetEndAxis() string { + axis := strings.Split((*m)[0], ":") + return axis[1] +} diff --git a/excelize_test.go b/excelize_test.go index 89d2e98..6eb3692 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -374,6 +374,59 @@ func TestMergeCell(t *testing.T) { } } +func TestGetMergeCells(t *testing.T) { + wants := []struct { + value string + start string + end string + }{ + { + value: "A1", + start: "A1", + end: "B1", + }, + { + value: "A2", + start: "A2", + end: "A3", + }, + { + value: "A4", + start: "A4", + end: "B5", + }, + { + value: "A7", + start: "A7", + end: "C10", + }, + } + + xlsx, err := OpenFile("./test/MergeCell.xlsx") + if err != nil { + t.Error(err) + } + + mergeCells := xlsx.GetMergeCells("Sheet1") + if len(mergeCells) != len(wants) { + t.Fatalf("Expected count of merge cells %d, but got %d\n", len(wants), len(mergeCells)) + } + + for i, m := range mergeCells { + if wants[i].value != m.GetCellValue() { + t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].value, m.GetCellValue()) + } + + if wants[i].start != m.GetStartAxis() { + t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].start, m.GetStartAxis()) + } + + if wants[i].end != m.GetEndAxis() { + t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].end, m.GetEndAxis()) + } + } +} + func TestSetCellStyleAlignment(t *testing.T) { xlsx, err := OpenFile("./test/Book2.xlsx") if err != nil { @@ -0,0 +1 @@ +module github.com/360EntSecGroup-Skylar/excelize Binary files differ@@ -33,18 +33,27 @@ func (f *File) NewSheet(name string) int { if f.GetSheetIndex(name) != 0 { return f.SheetCount } + f.DeleteSheet(name) f.SheetCount++ + wb := f.workbookReader() + sheetID := 0 + for _, v := range wb.Sheets.Sheet { + if v.SheetID > sheetID { + sheetID = v.SheetID + } + } + sheetID++ // Update docProps/app.xml f.setAppXML() // Update [Content_Types].xml - f.setContentTypes(f.SheetCount) + f.setContentTypes(sheetID) // Create new sheet /xl/worksheets/sheet%d.xml - f.setSheet(f.SheetCount, name) + f.setSheet(sheetID, name) // Update xl/_rels/workbook.xml.rels - rID := f.addXlsxWorkbookRels(f.SheetCount) + rID := f.addXlsxWorkbookRels(sheetID) // Update xl/workbook.xml - f.setWorkbook(name, rID) - return f.SheetCount + f.setWorkbook(name, sheetID, rID) + return sheetID } // contentTypesReader provides a function to get the pointer to the @@ -118,7 +127,8 @@ func trimCell(column []xlsxC) []xlsxC { return col[0:i] } -// Read and update property of contents type of XLSX. +// setContentTypes provides a function to read and update property of contents +// type of XLSX. func (f *File) setContentTypes(index int) { content := f.contentTypesReader() content.Overrides = append(content.Overrides, xlsxOverride{ @@ -127,7 +137,7 @@ func (f *File) setContentTypes(index int) { }) } -// Update sheet property by given index. +// setSheet provides a function to update sheet property by given index. func (f *File) setSheet(index int, name string) { var xlsx xlsxWorksheet xlsx.Dimension.Ref = "A1" @@ -141,19 +151,11 @@ func (f *File) setSheet(index int, name string) { // setWorkbook update workbook property of XLSX. Maximum 31 characters are // allowed in sheet title. -func (f *File) setWorkbook(name string, rid int) { +func (f *File) setWorkbook(name string, sheetID, rid int) { content := f.workbookReader() - rID := 0 - for _, v := range content.Sheets.Sheet { - t, _ := strconv.Atoi(v.SheetID) - if t > rID { - rID = t - } - } - rID++ content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{ Name: trimSheetName(name), - SheetID: strconv.Itoa(rID), + SheetID: sheetID, ID: "rId" + strconv.Itoa(rid), }) } @@ -209,13 +211,13 @@ func (f *File) setAppXML() { f.saveFileList("docProps/app.xml", []byte(templateDocpropsApp)) } -// Some tools that read XLSX files have very strict requirements about the -// structure of the input XML. In particular both Numbers on the Mac and SAS -// dislike inline XML namespace declarations, or namespace prefixes that don't -// match the ones that Excel itself uses. This is a problem because the Go XML -// library doesn't multiple namespace declarations in a single element of a -// document. This function is a horrible hack to fix that after the XML -// marshalling is completed. +// replaceRelationshipsNameSpaceBytes; Some tools that read XLSX files have +// very strict requirements about the structure of the input XML. In +// particular both Numbers on the Mac and SAS dislike inline XML namespace +// declarations, or namespace prefixes that don't match the ones that Excel +// itself uses. This is a problem because the Go XML library doesn't multiple +// namespace declarations in a single element of a document. This function is +// a horrible hack to fix that after the XML marshalling is completed. func replaceRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte { oldXmlns := []byte(`<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`) newXmlns := []byte(`<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main">`) @@ -230,18 +232,23 @@ func (f *File) SetActiveSheet(index int) { if index < 1 { index = 1 } - index-- - content := f.workbookReader() - if len(content.BookViews.WorkBookView) > 0 { - content.BookViews.WorkBookView[0].ActiveTab = index - } else { - content.BookViews.WorkBookView = append(content.BookViews.WorkBookView, xlsxWorkBookView{ - ActiveTab: index, - }) + wb := f.workbookReader() + for activeTab, sheet := range wb.Sheets.Sheet { + if sheet.SheetID == index { + if len(wb.BookViews.WorkBookView) > 0 { + wb.BookViews.WorkBookView[0].ActiveTab = activeTab + } else { + wb.BookViews.WorkBookView = append(wb.BookViews.WorkBookView, xlsxWorkBookView{ + ActiveTab: activeTab, + }) + } + } } - index++ for idx, name := range f.GetSheetMap() { xlsx := f.workSheetReader(name) + if len(xlsx.SheetViews.SheetView) > 0 { + xlsx.SheetViews.SheetView[0].TabSelected = false + } if index == idx { if len(xlsx.SheetViews.SheetView) > 0 { xlsx.SheetViews.SheetView[0].TabSelected = true @@ -250,10 +257,6 @@ func (f *File) SetActiveSheet(index int) { TabSelected: true, }) } - } else { - if len(xlsx.SheetViews.SheetView) > 0 { - xlsx.SheetViews.SheetView[0].TabSelected = false - } } } } @@ -261,21 +264,13 @@ 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 { - buffer := bytes.Buffer{} - content := f.workbookReader() - for _, v := range content.Sheets.Sheet { - xlsx := xlsxWorksheet{} - buffer.WriteString("xl/worksheets/sheet") - buffer.WriteString(strings.TrimPrefix(v.ID, "rId")) - buffer.WriteString(".xml") - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(buffer.String())), &xlsx) + for idx, name := range f.GetSheetMap() { + xlsx := f.workSheetReader(name) for _, sheetView := range xlsx.SheetViews.SheetView { if sheetView.TabSelected { - ID, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId")) - return ID + return idx } } - buffer.Reset() } return 0 } @@ -404,8 +399,8 @@ func (f *File) DeleteSheet(name string) { 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" + strings.TrimPrefix(v.ID, "rId") + ".xml" - rels := "xl/worksheets/_rels/sheet" + strings.TrimPrefix(v.ID, "rId") + ".xml.rels" + 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) f.deleteSheetFromContentTypes(target) delete(f.sheetMap, name) @@ -31,8 +31,26 @@ type ( FitToPage bool // AutoPageBreaks is a SheetPrOption AutoPageBreaks bool + // OutlineSummaryBelow is an outlinePr, within SheetPr option + OutlineSummaryBelow bool ) +func (o OutlineSummaryBelow) setSheetPrOption(pr *xlsxSheetPr) { + if pr.OutlinePr == nil { + pr.OutlinePr = new(xlsxOutlinePr) + } + pr.OutlinePr.SummaryBelow = bool(o) +} + +func (o *OutlineSummaryBelow) getSheetPrOption(pr *xlsxSheetPr) { + // Excel default: true + if pr == nil || pr.OutlinePr == nil { + *o = true + return + } + *o = OutlineSummaryBelow(defaultTrue(&pr.OutlinePr.SummaryBelow)) +} + func (o CodeName) setSheetPrOption(pr *xlsxSheetPr) { pr.CodeName = string(o) } @@ -115,6 +133,7 @@ func (o *AutoPageBreaks) getSheetPrOption(pr *xlsxSheetPr) { // Published(bool) // FitToPage(bool) // AutoPageBreaks(bool) +// OutlineSummaryBelow(bool) func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error { sheet := f.workSheetReader(name) pr := sheet.SheetPr @@ -137,6 +156,7 @@ func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error { // Published(bool) // FitToPage(bool) // AutoPageBreaks(bool) +// OutlineSummaryBelow(bool) func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error { sheet := f.workSheetReader(name) pr := sheet.SheetPr diff --git a/sheetpr_test.go b/sheetpr_test.go index e7e7482..d9f5059 100644 --- a/sheetpr_test.go +++ b/sheetpr_test.go @@ -15,6 +15,7 @@ var _ = []excelize.SheetPrOption{ excelize.Published(false), excelize.FitToPage(true), excelize.AutoPageBreaks(true), + excelize.OutlineSummaryBelow(true), } var _ = []excelize.SheetPrOptionPtr{ @@ -23,6 +24,7 @@ var _ = []excelize.SheetPrOptionPtr{ (*excelize.Published)(nil), (*excelize.FitToPage)(nil), (*excelize.AutoPageBreaks)(nil), + (*excelize.OutlineSummaryBelow)(nil), } func ExampleFile_SetSheetPrOptions() { @@ -35,6 +37,7 @@ func ExampleFile_SetSheetPrOptions() { excelize.Published(false), excelize.FitToPage(true), excelize.AutoPageBreaks(true), + excelize.OutlineSummaryBelow(false), ); err != nil { panic(err) } @@ -51,6 +54,7 @@ func ExampleFile_GetSheetPrOptions() { published excelize.Published fitToPage excelize.FitToPage autoPageBreaks excelize.AutoPageBreaks + outlineSummaryBelow excelize.OutlineSummaryBelow ) if err := xl.GetSheetPrOptions(sheet, @@ -59,6 +63,7 @@ func ExampleFile_GetSheetPrOptions() { &published, &fitToPage, &autoPageBreaks, + &outlineSummaryBelow, ); err != nil { panic(err) } @@ -68,6 +73,7 @@ func ExampleFile_GetSheetPrOptions() { fmt.Println("- published:", published) fmt.Println("- fitToPage:", fitToPage) fmt.Println("- autoPageBreaks:", autoPageBreaks) + fmt.Println("- outlineSummaryBelow:", outlineSummaryBelow) // Output: // Defaults: // - codeName: "" @@ -75,6 +81,7 @@ func ExampleFile_GetSheetPrOptions() { // - published: true // - fitToPage: false // - autoPageBreaks: false + // - outlineSummaryBelow: true } func TestSheetPrOptions(t *testing.T) { @@ -88,6 +95,7 @@ func TestSheetPrOptions(t *testing.T) { {new(excelize.Published), excelize.Published(false)}, {new(excelize.FitToPage), excelize.FitToPage(true)}, {new(excelize.AutoPageBreaks), excelize.AutoPageBreaks(true)}, + {new(excelize.OutlineSummaryBelow), excelize.OutlineSummaryBelow(false)}, } { opt := test.nonDefault t.Logf("option %T", opt) diff --git a/sheetview.go b/sheetview.go index e76325c..37a0c39 100644 --- a/sheetview.go +++ b/sheetview.go @@ -35,6 +35,8 @@ type ( ShowRowColHeaders bool // ZoomScale is a SheetViewOption. ZoomScale float64 + // TopLeftCell is a SheetViewOption. + TopLeftCell string /* TODO // ShowWhiteSpace is a SheetViewOption. ShowWhiteSpace bool @@ -47,6 +49,14 @@ type ( // Defaults for each option are described in XML schema for CT_SheetView +func (o TopLeftCell) setSheetViewOption(view *xlsxSheetView) { + view.TopLeftCell = string(o) +} + +func (o *TopLeftCell) getSheetViewOption(view *xlsxSheetView) { + *o = TopLeftCell(string(view.TopLeftCell)) +} + func (o DefaultGridColor) setSheetViewOption(view *xlsxSheetView) { view.DefaultGridColor = boolPtr(bool(o)) } diff --git a/sheetview_test.go b/sheetview_test.go index c580906..ee81d5b 100644 --- a/sheetview_test.go +++ b/sheetview_test.go @@ -13,12 +13,14 @@ var _ = []excelize.SheetViewOption{ excelize.ShowFormulas(false), excelize.ShowGridLines(true), excelize.ShowRowColHeaders(true), + excelize.TopLeftCell("B2"), // SheetViewOptionPtr are also SheetViewOption new(excelize.DefaultGridColor), new(excelize.RightToLeft), new(excelize.ShowFormulas), new(excelize.ShowGridLines), new(excelize.ShowRowColHeaders), + new(excelize.TopLeftCell), } var _ = []excelize.SheetViewOptionPtr{ @@ -27,6 +29,7 @@ var _ = []excelize.SheetViewOptionPtr{ (*excelize.ShowFormulas)(nil), (*excelize.ShowGridLines)(nil), (*excelize.ShowRowColHeaders)(nil), + (*excelize.TopLeftCell)(nil), } func ExampleFile_SetSheetViewOptions() { @@ -40,6 +43,7 @@ func ExampleFile_SetSheetViewOptions() { excelize.ShowGridLines(true), excelize.ShowRowColHeaders(true), excelize.ZoomScale(80), + excelize.TopLeftCell("C3"), ); err != nil { panic(err) } @@ -91,6 +95,7 @@ func ExampleFile_GetSheetViewOptions() { showGridLines excelize.ShowGridLines showRowColHeaders excelize.ShowRowColHeaders zoomScale excelize.ZoomScale + topLeftCell excelize.TopLeftCell ) if err := xl.GetSheetViewOptions(sheet, 0, @@ -100,6 +105,7 @@ func ExampleFile_GetSheetViewOptions() { &showGridLines, &showRowColHeaders, &zoomScale, + &topLeftCell, ); err != nil { panic(err) } @@ -111,6 +117,15 @@ func ExampleFile_GetSheetViewOptions() { fmt.Println("- showGridLines:", showGridLines) fmt.Println("- showRowColHeaders:", showRowColHeaders) fmt.Println("- zoomScale:", zoomScale) + fmt.Println("- topLeftCell:", `"`+topLeftCell+`"`) + + if err := xl.SetSheetViewOptions(sheet, 0, excelize.TopLeftCell("B2")); err != nil { + panic(err) + } + + if err := xl.GetSheetViewOptions(sheet, 0, &topLeftCell); err != nil { + panic(err) + } if err := xl.SetSheetViewOptions(sheet, 0, excelize.ShowGridLines(false)); err != nil { panic(err) @@ -122,6 +137,7 @@ func ExampleFile_GetSheetViewOptions() { fmt.Println("After change:") fmt.Println("- showGridLines:", showGridLines) + fmt.Println("- topLeftCell:", topLeftCell) // Output: // Default: @@ -131,8 +147,10 @@ func ExampleFile_GetSheetViewOptions() { // - showGridLines: true // - showRowColHeaders: true // - zoomScale: 0 + // - topLeftCell: "" // After change: // - showGridLines: false + // - topLeftCell: B2 } func TestSheetViewOptionsErrors(t *testing.T) { diff --git a/test/Book1.xlsx b/test/Book1.xlsx Binary files differindex 84c43d1..2ef1121 100644 --- a/test/Book1.xlsx +++ b/test/Book1.xlsx diff --git a/test/MergeCell.xlsx b/test/MergeCell.xlsx Binary files differnew file mode 100644 index 0000000..d4dad18 --- /dev/null +++ b/test/MergeCell.xlsx diff --git a/xmlWorkbook.go b/xmlWorkbook.go index f00a0b8..6572033 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -151,7 +151,7 @@ type xlsxSheets struct { // 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"` + SheetID int `xml:"sheetId,attr,omitempty"` ID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` State string `xml:"state,attr,omitempty"` } diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 42f8ddb..d35b40e 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -211,6 +211,13 @@ type xlsxSheetPr struct { TransitionEntry bool `xml:"transitionEntry,attr,omitempty"` TabColor *xlsxTabColor `xml:"tabColor,omitempty"` PageSetUpPr *xlsxPageSetUpPr `xml:"pageSetUpPr,omitempty"` + OutlinePr *xlsxOutlinePr `xml:"outlinePr,omitempty"` +} + +// xlsxOutlinePr maps to the outlinePr element +// SummaryBelow allows you to adjust the direction of grouper controls +type xlsxOutlinePr struct { + SummaryBelow bool `xml:"summaryBelow,attr"` } // xlsxPageSetUpPr directly maps the pageSetupPr element in the namespace |