From 46b8c46d91be522c3dff1044a23810ecb1a81c96 Mon Sep 17 00:00:00 2001 From: Ri Xu Date: Fri, 28 Apr 2017 15:49:41 +0800 Subject: [PATCH] - Initialize table support; - go test updated --- README.md | 2 +- chart.go | 2 +- excelize_test.go | 14 ++++ picture.go | 2 +- table.go | 170 +++++++++++++++++++++++++++++++++++++++++++++++ xmlDrawing.go | 4 +- xmlTable.go | 83 +++++++++++++++++++++++ 7 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 table.go create mode 100644 xmlTable.go diff --git a/README.md b/README.md index 0e13138..2d77950 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ## Introduction -Excelize is a library written in pure Golang and providing a set of functions that allow you to write to and read from XLSX files. Support reads and writes XLSX file generated by Office Excel 2007 and later. Support save file without losing original charts of XLSX. This library needs Go version 1.8 or later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/Luxurioust/excelize). +Excelize is a library written in pure Golang and providing a set of functions that allow you to write to and read from XLSX files. Support reads and writes XLSX file generated by Microsoft Excelâ„¢ 2007 and later. Support save file without losing original charts of XLSX. This library needs Go version 1.8 or later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/Luxurioust/excelize). ## Basic Usage diff --git a/chart.go b/chart.go index 526544b..ed2efb6 100644 --- a/chart.go +++ b/chart.go @@ -848,7 +848,7 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) content := encodeWsDr{} content.WsDr.A = NameSpaceDrawingML - content.WsDr.Xdr = NameSpaceSpreadSheetDrawing + content.WsDr.Xdr = NameSpaceDrawingMLSpreadSheet cNvPrID := 1 _, ok := f.XLSX[drawingXML] if ok { // Append Model diff --git a/excelize_test.go b/excelize_test.go index 813cef2..e8c3cc3 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -472,6 +472,20 @@ func TestCopySheet(t *testing.T) { } } +func TestAddTable(t *testing.T) { + xlsx, err := OpenFile("./test/Workbook_2.xlsx") + if err != nil { + t.Log(err) + } + xlsx.AddTable("Sheet1", "B26", "A21", ``) + xlsx.AddTable("Sheet2", "A2", "B5", `{"table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`) + xlsx.AddTable("Sheet2", "F1", "F1", `{"table_style":"TableStyleMedium8"}`) + err = xlsx.Save() + if err != nil { + t.Log(err) + } +} + func TestAddChart(t *testing.T) { xlsx, err := OpenFile("./test/Workbook1.xlsx") if err != nil { diff --git a/picture.go b/picture.go index 61b489a..4f650fb 100644 --- a/picture.go +++ b/picture.go @@ -189,7 +189,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) content := encodeWsDr{} content.WsDr.A = NameSpaceDrawingML - content.WsDr.Xdr = NameSpaceSpreadSheetDrawing + content.WsDr.Xdr = NameSpaceDrawingMLSpreadSheet cNvPrID := 1 _, ok := f.XLSX[drawingXML] if ok { // Append Model diff --git a/table.go b/table.go new file mode 100644 index 0000000..d472ea0 --- /dev/null +++ b/table.go @@ -0,0 +1,170 @@ +package excelize + +import ( + "encoding/json" + "encoding/xml" + "strconv" + "strings" +) + +// parseFormatTableSet provides function to parse the format settings of the +// table with default value. +func parseFormatTableSet(formatSet string) *formatTable { + format := formatTable{ + TableStyle: "", + ShowRowStripes: true, + } + json.Unmarshal([]byte(formatSet), &format) + return &format +} + +// AddTable provides the method to add table in a worksheet by given sheet +// index, coordinate area and format set. For example, create a table of A1:D5 +// on Sheet1: +// +// xlsx.AddTable("Sheet1", "A1", "D5", ``) +// +// Create a table of F2:H6 on Sheet2 with format set: +// +// xlsx.AddTable("Sheet2", "F2", "H6", `{"table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`) +// +// Note that the table at least two lines include string type header. The two +// chart coordinate areas can not have an intersection. +// +// table_style: The built-in table style names +// +// TableStyleLight1 - TableStyleLight21 +// TableStyleMedium1 - TableStyleMedium28 +// TableStyleDark1 - TableStyleDark11 +// +func (f *File) AddTable(sheet, hcell, vcell, format string) { + formatSet := parseFormatTableSet(format) + hcell = strings.ToUpper(hcell) + vcell = strings.ToUpper(vcell) + // Coordinate conversion, convert C1:B3 to 2,0,1,2. + hcol := string(strings.Map(letterOnlyMapF, hcell)) + hrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, hcell)) + hyAxis := hrow - 1 + hxAxis := titleToNumber(hcol) + + vcol := string(strings.Map(letterOnlyMapF, vcell)) + vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell)) + vyAxis := vrow - 1 + vxAxis := titleToNumber(vcol) + if vxAxis < hxAxis { + hcell, vcell = vcell, hcell + vxAxis, hxAxis = hxAxis, vxAxis + } + if vyAxis < hyAxis { + hcell, vcell = vcell, hcell + vyAxis, hyAxis = hyAxis, vyAxis + } + tableID := f.countTables() + 1 + sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml" + tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1) + // Add first table for given sheet. + rID := f.addSheetRelationships(sheet, SourceRelationshipTable, sheetRelationshipsTableXML, "") + f.addSheetTable(sheet, rID) + f.addTable(sheet, tableXML, hxAxis, hyAxis, vxAxis, vyAxis, tableID, formatSet) + f.addTableContentTypePart(tableID) +} + +// countTables provides function to get table files count storage in the folder +// xl/tables. +func (f *File) countTables() int { + count := 0 + for k := range f.XLSX { + if strings.Contains(k, "xl/tables/table") { + count++ + } + } + return count +} + +// addSheetTable provides function to add tablePart element to +// xl/worksheets/sheet%d.xml by given sheet name and relationship index. +func (f *File) addSheetTable(sheet string, rID int) { + xlsx := f.workSheetReader(sheet) + table := &xlsxTablePart{ + RID: "rId" + strconv.Itoa(rID), + } + if xlsx.TableParts != nil { + xlsx.TableParts.Count++ + xlsx.TableParts.TableParts = append(xlsx.TableParts.TableParts, table) + } else { + xlsx.TableParts = &xlsxTableParts{ + Count: 1, + TableParts: []*xlsxTablePart{table}, + } + } + +} + +// addTable provides function to add table by given sheet index, coordinate area +// and format set. +func (f *File) addTable(sheet, tableXML string, hxAxis, hyAxis, vxAxis, vyAxis, i int, formatSet *formatTable) { + // Correct the minimum number of rows, the table at least two lines. + if hyAxis == vyAxis { + vyAxis++ + } + // Correct table reference coordinate area, such correct C1:B3 to B1:C3. + ref := toAlphaString(hxAxis+1) + strconv.Itoa(hyAxis+1) + ":" + toAlphaString(vxAxis+1) + strconv.Itoa(vyAxis+1) + tableColumn := []*xlsxTableColumn{} + idx := 0 + for i := hxAxis; i <= vxAxis; i++ { + idx++ + cell := toAlphaString(i+1) + strconv.Itoa(hyAxis+1) + name := f.GetCellValue(sheet, cell) + if _, err := strconv.Atoi(name); err == nil { + f.SetCellStr(sheet, cell, name) + } + if name == "" { + name = "Column" + strconv.Itoa(idx) + f.SetCellStr(sheet, cell, name) + } + tableColumn = append(tableColumn, &xlsxTableColumn{ + ID: idx, + Name: name, + }) + } + name := "Table" + strconv.Itoa(i) + t := xlsxTable{ + XMLNS: NameSpaceSpreadSheet, + ID: i, + Name: name, + DisplayName: name, + Ref: ref, + AutoFilter: &xlsxAutoFilter{ + Ref: ref, + }, + TableColumns: &xlsxTableColumns{ + Count: idx, + TableColumn: tableColumn, + }, + TableStyleInfo: &xlsxTableStyleInfo{ + Name: formatSet.TableStyle, + ShowFirstColumn: formatSet.ShowFirstColumn, + ShowLastColumn: formatSet.ShowLastColumn, + ShowRowStripes: formatSet.ShowRowStripes, + ShowColumnStripes: formatSet.ShowColumnStripes, + }, + } + table, _ := xml.Marshal(t) + f.saveFileList(tableXML, string(table)) +} + +// addTableContentTypePart provides function to add image part relationships +// in the file [Content_Types].xml by given drawing index. +func (f *File) addTableContentTypePart(index int) { + f.setContentTypePartImageExtensions() + content := f.contentTypesReader() + for _, v := range content.Overrides { + if v.PartName == "/xl/tables/table"+strconv.Itoa(index)+".xml" { + return + } + } + content.Overrides = append(content.Overrides, xlsxOverride{ + PartName: "/xl/tables/table" + strconv.Itoa(index) + ".xml", + ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml", + }) +} diff --git a/xmlDrawing.go b/xmlDrawing.go index 156b2ac..baab798 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -5,6 +5,7 @@ const ( SourceRelationship = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" @@ -14,7 +15,8 @@ const ( SourceRelationshipCompatibility = "http://schemas.openxmlformats.org/markup-compatibility/2006" NameSpaceDrawingML = "http://schemas.openxmlformats.org/drawingml/2006/main" NameSpaceDrawingMLChart = "http://schemas.openxmlformats.org/drawingml/2006/chart" - NameSpaceSpreadSheetDrawing = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" + NameSpaceDrawingMLSpreadSheet = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" + NameSpaceSpreadSheet = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" NameSpaceXML = "http://www.w3.org/XML/1998/namespace" ) diff --git a/xmlTable.go b/xmlTable.go new file mode 100644 index 0000000..c79a2ed --- /dev/null +++ b/xmlTable.go @@ -0,0 +1,83 @@ +package excelize + +import "encoding/xml" + +// xlsxTable directly maps the table element. A table helps organize and provide +// structure to lists of information in a worksheet. Tables have clearly labeled +// columns, rows, and data regions. Tables make it easier for users to sort, +// analyze, format, manage, add, and delete information. This element is the +// root element for a table that is not a single cell XML table. +type xlsxTable struct { + XMLName xml.Name `xml:"table"` + XMLNS string `xml:"xmlns,attr"` + DataCellStyle string `xml:"dataCellStyle,attr,omitempty"` + DataDxfID int `xml:"dataDxfId,attr,omitempty"` + DisplayName string `xml:"displayName,attr,omitempty"` + HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"` + HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"` + HeaderRowCount int `xml:"headerRowCount,attr,omitempty"` + HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"` + ID int `xml:"id,attr"` + InsertRow bool `xml:"insertRow,attr,omitempty"` + InsertRowShift bool `xml:"insertRowShift,attr,omitempty"` + Name string `xml:"name,attr"` + Published bool `xml:"published,attr,omitempty"` + Ref string `xml:"ref,attr"` + TotalsRowCount int `xml:"totalsRowCount,attr,omitempty"` + TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"` + TotalsRowShown bool `xml:"totalsRowShown,attr"` + AutoFilter *xlsxAutoFilter `xml:"autoFilter"` + TableColumns *xlsxTableColumns `xml:"tableColumns"` + TableStyleInfo *xlsxTableStyleInfo `xml:"tableStyleInfo"` +} + +// xlsxAutoFilter temporarily hides rows based on a filter criteria, which is +// applied column by column to a table of data in the worksheet. This collection +// expresses AutoFilter settings. +type xlsxAutoFilter struct { + Ref string `xml:"ref,attr"` +} + +// xlsxTableColumns directly maps the element representing the collection of all +// table columns for this table. +type xlsxTableColumns struct { + Count int `xml:"count,attr"` + TableColumn []*xlsxTableColumn `xml:"tableColumn"` +} + +// xlsxTableColumn directly maps the element representing a single column for +// this table. +type xlsxTableColumn struct { + DataCellStyle string `xml:"dataCellStyle,attr,omitempty"` + DataDxfID int `xml:"dataDxfId,attr,omitempty"` + HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"` + HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"` + ID int `xml:"id,attr"` + Name string `xml:"name,attr"` + QueryTableFieldID int `xml:"queryTableFieldId,attr,omitempty"` + TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"` + TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"` + TotalsRowFunction string `xml:"totalsRowFunction,attr,omitempty"` + TotalsRowLabel string `xml:"totalsRowLabel,attr,omitempty"` + UniqueName string `xml:"uniqueName,attr,omitempty"` +} + +// xlsxTableStyleInfo directly maps the tableStyleInfo element. This element +// describes which style is used to display this table, and specifies which +// portions of the table have the style applied. +type xlsxTableStyleInfo struct { + Name string `xml:"name,attr,omitempty"` + ShowFirstColumn bool `xml:"showFirstColumn,attr"` + ShowLastColumn bool `xml:"showLastColumn,attr"` + ShowRowStripes bool `xml:"showRowStripes,attr"` + ShowColumnStripes bool `xml:"showColumnStripes,attr"` +} + +// formatTable directly maps the format settings of the table. +type formatTable struct { + TableStyle string `json:"table_style"` + ShowFirstColumn bool `json:"show_first_column"` + ShowLastColumn bool `json:"show_last_column"` + ShowRowStripes bool `json:"show_row_stripes"` + ShowColumnStripes bool `json:"show_column_stripes"` +}