From 69b38ddcd60f7cf4c158c706ddbbeb89a8ff2108 Mon Sep 17 00:00:00 2001 From: xuri Date: Sun, 5 May 2019 16:25:57 +0800 Subject: [PATCH] Resolve #394, init set header and footer support --- datavalidation_test.go | 30 ++++---- excelize_test.go | 8 +-- sheet.go | 155 ++++++++++++++++++++++++++++++++++++++++- sheet_test.go | 57 ++++++++++----- 4 files changed, 213 insertions(+), 37 deletions(-) diff --git a/datavalidation_test.go b/datavalidation_test.go index afb659c2..0fee0920 100644 --- a/datavalidation_test.go +++ b/datavalidation_test.go @@ -19,7 +19,7 @@ import ( func TestDataValidation(t *testing.T) { resultFile := filepath.Join("test", "TestDataValidation.xlsx") - xlsx := NewFile() + f := NewFile() dvRange := NewDataValidation(true) dvRange.Sqref = "A1:B2" @@ -27,8 +27,8 @@ func TestDataValidation(t *testing.T) { dvRange.SetError(DataValidationErrorStyleStop, "error title", "error body") dvRange.SetError(DataValidationErrorStyleWarning, "error title", "error body") dvRange.SetError(DataValidationErrorStyleInformation, "error title", "error body") - xlsx.AddDataValidation("Sheet1", dvRange) - if !assert.NoError(t, xlsx.SaveAs(resultFile)) { + f.AddDataValidation("Sheet1", dvRange) + if !assert.NoError(t, f.SaveAs(resultFile)) { t.FailNow() } @@ -36,16 +36,16 @@ func TestDataValidation(t *testing.T) { dvRange.Sqref = "A3:B4" dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan) dvRange.SetInput("input title", "input body") - xlsx.AddDataValidation("Sheet1", dvRange) - if !assert.NoError(t, xlsx.SaveAs(resultFile)) { + f.AddDataValidation("Sheet1", dvRange) + if !assert.NoError(t, f.SaveAs(resultFile)) { t.FailNow() } dvRange = NewDataValidation(true) dvRange.Sqref = "A5:B6" dvRange.SetDropList([]string{"1", "2", "3"}) - xlsx.AddDataValidation("Sheet1", dvRange) - if !assert.NoError(t, xlsx.SaveAs(resultFile)) { + f.AddDataValidation("Sheet1", dvRange) + if !assert.NoError(t, f.SaveAs(resultFile)) { t.FailNow() } } @@ -53,10 +53,10 @@ func TestDataValidation(t *testing.T) { func TestDataValidationError(t *testing.T) { resultFile := filepath.Join("test", "TestDataValidationError.xlsx") - xlsx := NewFile() - xlsx.SetCellStr("Sheet1", "E1", "E1") - xlsx.SetCellStr("Sheet1", "E2", "E2") - xlsx.SetCellStr("Sheet1", "E3", "E3") + f := NewFile() + f.SetCellStr("Sheet1", "E1", "E1") + f.SetCellStr("Sheet1", "E2", "E2") + f.SetCellStr("Sheet1", "E3", "E3") dvRange := NewDataValidation(true) dvRange.SetSqref("A7:B8") @@ -66,8 +66,8 @@ func TestDataValidationError(t *testing.T) { err := dvRange.SetSqrefDropList("$E$1:$E$3", false) assert.EqualError(t, err, "cross-sheet sqref cell are not supported") - xlsx.AddDataValidation("Sheet1", dvRange) - if !assert.NoError(t, xlsx.SaveAs(resultFile)) { + f.AddDataValidation("Sheet1", dvRange) + if !assert.NoError(t, f.SaveAs(resultFile)) { t.FailNow() } @@ -81,8 +81,8 @@ func TestDataValidationError(t *testing.T) { dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan) dvRange.SetSqref("A9:B10") - xlsx.AddDataValidation("Sheet1", dvRange) - if !assert.NoError(t, xlsx.SaveAs(resultFile)) { + f.AddDataValidation("Sheet1", dvRange) + if !assert.NoError(t, f.SaveAs(resultFile)) { t.FailNow() } } diff --git a/excelize_test.go b/excelize_test.go index c76aa92b..3509cb80 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -789,9 +789,9 @@ func TestCopySheet(t *testing.T) { } idx := f.NewSheet("CopySheet") - assert.EqualError(t, f.CopySheet(1, idx), "sheet sheet1 is not exist") + assert.NoError(t, f.CopySheet(1, idx)) - f.SetCellValue("Sheet4", "F1", "Hello") + f.SetCellValue("CopySheet", "F1", "Hello") val, err := f.GetCellValue("Sheet1", "F1") assert.NoError(t, err) assert.NotEqual(t, "Hello", val) @@ -805,8 +805,8 @@ func TestCopySheetError(t *testing.T) { t.FailNow() } - err = f.CopySheet(0, -1) - if !assert.EqualError(t, err, "invalid worksheet index") { + assert.EqualError(t, f.copySheet(0, -1), "sheet is not exist") + if !assert.EqualError(t, f.CopySheet(0, -1), "invalid worksheet index") { t.FailNow() } diff --git a/sheet.go b/sheet.go index 5e9fcae9..b22592db 100644 --- a/sheet.go +++ b/sheet.go @@ -14,9 +14,11 @@ import ( "encoding/json" "encoding/xml" "errors" + "fmt" "io/ioutil" "os" "path" + "reflect" "regexp" "strconv" "strings" @@ -466,7 +468,7 @@ func (f *File) CopySheet(from, to int) error { // copySheet provides a function to duplicate a worksheet by gave source and // target worksheet name. func (f *File) copySheet(from, to int) error { - sheet, err := f.workSheetReader("sheet" + strconv.Itoa(from)) + sheet, err := f.workSheetReader(f.GetSheetName(from)) if err != nil { return err } @@ -761,6 +763,155 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) { return result, nil } +// SetHeaderFooter provides a function to set headers and footers by given +// worksheet name and the control characters. +// +// Headers and footers are specified using the following settings fields: +// +// Fields | Description +// ------------------+----------------------------------------------------------- +// AlignWithMargins | Align header footer margins with page margins +// DifferentFirst | Different first-page header and footer indicator +// DifferentOddEven | Different odd and even page headers and footers indicator +// ScaleWithDoc | Scale header and footer with document scaling +// OddFooter | Odd Page Footer +// OddHeader | Odd Header +// EvenFooter | Even Page Footer +// EvenHeader | Even Page Header +// FirstFooter | First Page Footer +// FirstHeader | First Page Header +// +// The following formatting codes can be used in 6 string type fields: +// OddHeader, OddFooter, EvenHeader, EvenFooter, FirstFooter, FirstHeader +// +// Formatting Code | Description +// ------------------------+------------------------------------------------------------------------- +// && | The character "&" +// | +// &font-size | Size of the text font, where font-size is a decimal font size in points +// | +// &"font name,font type" | A text font-name string, font name, and a text font-type string, +// | font type +// | +// &"-,Regular" | Regular text format. Toggles bold and italic modes to off +// | +// &A | Current worksheet's tab name +// | +// &B or &"-,Bold" | Bold text format, from off to on, or vice versa. The default mode is off +// | +// &D | Current date +// | +// &C | Center section +// | +// &E | Double-underline text format +// | +// &F | Current workbook's file name +// | +// &G | Drawing object as background +// | +// &H | Shadow text format +// | +// &I or &"-,Italic" | Italic text format +// | +// &K | Text font color +// | +// | An RGB Color is specified as RRGGBB +// | +// | A Theme Color is specified as TTSNNN where TT is the theme color Id, +// | S is either "+" or "-" of the tint/shade value, and NNN is the +// | tint/shade value +// | +// &L | Left section +// | +// &N | Total number of pages +// | +// &O | Outline text format +// | +// &P[[+|-]n] | Without the optional suffix, the current page number in decimal +// | +// &R | Right section +// | +// &S | Strikethrough text format +// | +// &T | Current time +// | +// &U | Single-underline text format. If double-underline mode is on, the next +// | occurrence in a section specifier toggles double-underline mode to off; +// | otherwise, it toggles single-underline mode, from off to on, or vice +// | versa. The default mode is off +// | +// &X | Superscript text format +// | +// &Y | Subscript text format +// | +// &Z | Current workbook's file path +// +// For example: +// +// err := f.SetHeaderFooter("Sheet1", &excelize.FormatHeaderFooter{ +// DifferentFirst: true, +// DifferentOddEven: true, +// OddHeader: "&R&P", +// OddFooter: "&C&F", +// EvenHeader: "&L&P", +// EvenFooter: "&L&D&R&T", +// FirstHeader: `&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D`, +// }) +// +// This example shows: +// +// - The first page has its own header and footer +// +// - Odd and even-numbered pages have different headers and footers +// +// - Current page number in the right section of odd-page headers +// +// - Current workbook's file name in the center section of odd-page footers +// +// - Current page number in the left section of even-page headers +// +// - Current date in the left section and the current time in the right section +// of even-page footers +// +// - The text "Center Bold Header" on the first line of the center section of +// the first page, and the date on the second line of the center section of +// that same page +// +// - No footer on the first page +// +func (f *File) SetHeaderFooter(sheet string, settings *FormatHeaderFooter) error { + xlsx, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if settings == nil { + xlsx.HeaderFooter = nil + return err + } + + v := reflect.ValueOf(*settings) + // Check 6 string type fields: OddHeader, OddFooter, EvenHeader, EvenFooter, + // FirstFooter, FirstHeader + for i := 4; i < v.NumField()-1; i++ { + if v.Field(i).Len() >= 255 { + return fmt.Errorf("field %s must be less than 255 characters", v.Type().Field(i).Name) + } + } + xlsx.HeaderFooter = &xlsxHeaderFooter{ + AlignWithMargins: settings.AlignWithMargins, + DifferentFirst: settings.DifferentFirst, + DifferentOddEven: settings.DifferentOddEven, + ScaleWithDoc: settings.ScaleWithDoc, + OddHeader: settings.OddHeader, + OddFooter: settings.OddFooter, + EvenHeader: settings.EvenHeader, + EvenFooter: settings.EvenFooter, + FirstFooter: settings.FirstFooter, + FirstHeader: settings.FirstHeader, + } + return err +} + // ProtectSheet provides a function to prevent other users from accidentally // or deliberately changing, moving, or deleting data in a worksheet. For // example, protect Sheet1 with protection settings: @@ -898,7 +1049,7 @@ func (p *PageLayoutPaperSize) getPageLayout(ps *xlsxPageSetUp) { // // Available options: // PageLayoutOrientation(string) -// PageLayoutPaperSize(int) +// PageLayoutPaperSize(int) // // The following shows the paper size sorted by excelize index number: // diff --git a/sheet_test.go b/sheet_test.go index f0a1963e..beee10bb 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -2,6 +2,8 @@ package excelize_test import ( "fmt" + "path/filepath" + "strings" "testing" "github.com/360EntSecGroup-Skylar/excelize/v2" @@ -10,15 +12,15 @@ import ( ) func ExampleFile_SetPageLayout() { - xl := excelize.NewFile() + f := excelize.NewFile() - if err := xl.SetPageLayout( + if err := f.SetPageLayout( "Sheet1", excelize.PageLayoutOrientation(excelize.OrientationLandscape), ); err != nil { panic(err) } - if err := xl.SetPageLayout( + if err := f.SetPageLayout( "Sheet1", excelize.PageLayoutPaperSize(10), ); err != nil { @@ -28,15 +30,15 @@ func ExampleFile_SetPageLayout() { } func ExampleFile_GetPageLayout() { - xl := excelize.NewFile() + f := excelize.NewFile() var ( orientation excelize.PageLayoutOrientation paperSize excelize.PageLayoutPaperSize ) - if err := xl.GetPageLayout("Sheet1", &orientation); err != nil { + if err := f.GetPageLayout("Sheet1", &orientation); err != nil { panic(err) } - if err := xl.GetPageLayout("Sheet1", &paperSize); err != nil { + if err := f.GetPageLayout("Sheet1", &paperSize); err != nil { panic(err) } fmt.Println("Defaults:") @@ -69,26 +71,26 @@ func TestPageLayoutOption(t *testing.T) { val1 := deepcopy.Copy(def).(excelize.PageLayoutOptionPtr) val2 := deepcopy.Copy(def).(excelize.PageLayoutOptionPtr) - xl := excelize.NewFile() + f := excelize.NewFile() // Get the default value - assert.NoError(t, xl.GetPageLayout(sheet, def), opt) + assert.NoError(t, f.GetPageLayout(sheet, def), opt) // Get again and check - assert.NoError(t, xl.GetPageLayout(sheet, val1), opt) + assert.NoError(t, f.GetPageLayout(sheet, val1), opt) if !assert.Equal(t, val1, def, opt) { t.FailNow() } // Set the same value - assert.NoError(t, xl.SetPageLayout(sheet, val1), opt) + assert.NoError(t, f.SetPageLayout(sheet, val1), opt) // Get again and check - assert.NoError(t, xl.GetPageLayout(sheet, val1), opt) + assert.NoError(t, f.GetPageLayout(sheet, val1), opt) if !assert.Equal(t, val1, def, "%T: value should not have changed", opt) { t.FailNow() } // Set a different value - assert.NoError(t, xl.SetPageLayout(sheet, test.nonDefault), opt) - assert.NoError(t, xl.GetPageLayout(sheet, val1), opt) + assert.NoError(t, f.SetPageLayout(sheet, test.nonDefault), opt) + assert.NoError(t, f.GetPageLayout(sheet, val1), opt) // Get again and compare - assert.NoError(t, xl.GetPageLayout(sheet, val2), opt) + assert.NoError(t, f.GetPageLayout(sheet, val2), opt) if !assert.Equal(t, val1, val2, "%T: value should not have changed", opt) { t.FailNow() } @@ -97,8 +99,8 @@ func TestPageLayoutOption(t *testing.T) { t.FailNow() } // Restore the default value - assert.NoError(t, xl.SetPageLayout(sheet, def), opt) - assert.NoError(t, xl.GetPageLayout(sheet, val1), opt) + assert.NoError(t, f.SetPageLayout(sheet, def), opt) + assert.NoError(t, f.GetPageLayout(sheet, val1), opt) if !assert.Equal(t, def, val1) { t.FailNow() } @@ -117,3 +119,26 @@ func TestGetPageLayout(t *testing.T) { // Test get page layout on not exists worksheet. assert.EqualError(t, f.GetPageLayout("SheetN"), "sheet SheetN is not exist") } + +func TestSetHeaderFooter(t *testing.T) { + f := excelize.NewFile() + f.SetCellStr("Sheet1", "A1", "Test SetHeaderFooter") + // Test set header and footer on not exists worksheet. + assert.EqualError(t, f.SetHeaderFooter("SheetN", nil), "sheet SheetN is not exist") + // Test set header and footer with illegal setting. + assert.EqualError(t, f.SetHeaderFooter("Sheet1", &excelize.FormatHeaderFooter{ + OddHeader: strings.Repeat("c", 256), + }), "field OddHeader must be less than 255 characters") + + assert.NoError(t, f.SetHeaderFooter("Sheet1", nil)) + assert.NoError(t, f.SetHeaderFooter("Sheet1", &excelize.FormatHeaderFooter{ + DifferentFirst: true, + DifferentOddEven: true, + OddHeader: "&R&P", + OddFooter: "&C&F", + EvenHeader: "&L&P", + EvenFooter: "&L&D&R&T", + FirstHeader: `&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D`, + })) + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetHeaderFooter.xlsx"))) +}