Skip to content

Commit

Permalink
- New feature: border setting support (Related issue qax-os#21);
Browse files Browse the repository at this point in the history
- Function parameter code is simplified;
- Fix element `Tint` value parsing error in worksheet;
- Update go test
  • Loading branch information
xuri committed Mar 6, 2017
1 parent 48722e6 commit 1f73f08
Show file tree
Hide file tree
Showing 9 changed files with 519 additions and 18 deletions.
8 changes: 4 additions & 4 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// GetCellValue provides function to get value from cell by given sheet index
// and axis in XLSX file.
func (f *File) GetCellValue(sheet string, axis string) string {
func (f *File) GetCellValue(sheet, axis string) string {
axis = strings.ToUpper(axis)
var xlsx xlsxWorksheet
name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
Expand Down Expand Up @@ -59,7 +59,7 @@ func (f *File) GetCellValue(sheet string, axis string) string {

// GetCellFormula provides function to get formula from cell by given sheet
// index and axis in XLSX file.
func (f *File) GetCellFormula(sheet string, axis string) string {
func (f *File) GetCellFormula(sheet, axis string) string {
axis = strings.ToUpper(axis)
var xlsx xlsxWorksheet
name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
Expand Down Expand Up @@ -182,8 +182,8 @@ func (f *File) SetCellHyperLink(sheet, axis, link string) {
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpace(string(output)))
}

// MergeCell provides function to merge cells by given axis and sheet name.
// For example create a merged cell of D3:E9 on Sheet1:
// MergeCell provides function to merge cells by given coordinate area and sheet
// name. For example create a merged cell of D3:E9 on Sheet1:
//
// xlsx.MergeCell("sheet1", "D3", "E9")
//
Expand Down
10 changes: 5 additions & 5 deletions excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func OpenReader(r io.Reader) (*File, error) {
}

// SetCellValue provides function to set int or string type value of a cell.
func (f *File) SetCellValue(sheet string, axis string, value interface{}) {
func (f *File) SetCellValue(sheet, axis string, value interface{}) {
switch t := value.(type) {
case int:
f.SetCellInt(sheet, axis, value.(int))
Expand All @@ -86,7 +86,7 @@ func (f *File) SetCellValue(sheet string, axis string, value interface{}) {
}

// SetCellInt provides function to set int type value of a cell.
func (f *File) SetCellInt(sheet string, axis string, value int) {
func (f *File) SetCellInt(sheet, axis string, value int) {
axis = strings.ToUpper(axis)
var xlsx xlsxWorksheet
name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
Expand Down Expand Up @@ -127,7 +127,7 @@ func (f *File) SetCellInt(sheet string, axis string, value int) {

// SetCellStr provides function to set string type value of a cell. Total number
// of characters that a cell can contain 32767 characters.
func (f *File) SetCellStr(sheet string, axis string, value string) {
func (f *File) SetCellStr(sheet, axis, value string) {
axis = strings.ToUpper(axis)
var xlsx xlsxWorksheet
name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
Expand Down Expand Up @@ -170,7 +170,7 @@ func (f *File) SetCellStr(sheet string, axis string, value string) {

// SetCellDefault provides function to set string type value of a cell as
// default format without escaping the cell.
func (f *File) SetCellDefault(sheet string, axis string, value string) {
func (f *File) SetCellDefault(sheet, axis, value string) {
axis = strings.ToUpper(axis)
var xlsx xlsxWorksheet
name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
Expand Down Expand Up @@ -235,7 +235,7 @@ func completeCol(xlsx xlsxWorksheet, row int, cell int) xlsxWorksheet {
}

// Completion row element tags of XML in a sheet.
func completeRow(xlsx xlsxWorksheet, row int, cell int) xlsxWorksheet {
func completeRow(xlsx xlsxWorksheet, row, cell int) xlsxWorksheet {
currentRows := len(xlsx.SheetData.Row)
if currentRows > 1 {
lastRow := xlsx.SheetData.Row[currentRows-1].R
Expand Down
35 changes: 35 additions & 0 deletions excelize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,38 @@ func TestSetRowHeight(t *testing.T) {
t.Log(err)
}
}

func TestSetBorder(t *testing.T) {
xlsx, err := OpenFile("./test/Workbook_2.xlsx")
if err != nil {
t.Log(err)
}
// Test set border with invalid style parameter.
err = xlsx.SetBorder("Sheet1", "J21", "L25", "")
if err != nil {
t.Log(err)
}
// Test set border with invalid style index number.
err = xlsx.SetBorder("Sheet1", "J21", "L25", "")
if err != nil {
t.Log(err)
}
// Test set border on overlapping area.
err = xlsx.SetBorder("Sheet1", "J21", "L25", `{"border":[{"type":"left","color":"0000FF","style":-1},{"type":"top","color":"00FF00","style":14},{"type":"bottom","color":"FFFF00","style":5},{"type":"right","color":"FF0000","style":6},{"type":"diagonalDown","color":"A020F0","style":9},{"type":"diagonalUp","color":"A020F0","style":8}]}`)
if err != nil {
t.Log(err)
}
err = xlsx.SetBorder("Sheet1", "M28", "K24", `{"border":[{"type":"left","color":"0000FF","style":2},{"type":"top","color":"00FF00","style":3},{"type":"bottom","color":"FFFF00","style":4},{"type":"right","color":"FF0000","style":5},{"type":"diagonalDown","color":"A020F0","style":6},{"type":"diagonalUp","color":"A020F0","style":7}]}`)
if err != nil {
t.Log(err)
}
// Test set border for a single cell.
err = xlsx.SetBorder("Sheet1", "O22", "O22", `{"border":[{"type":"left","color":"0000FF","style":8},{"type":"top","color":"00FF00","style":9},{"type":"bottom","color":"FFFF00","style":10},{"type":"right","color":"FF0000","style":11},{"type":"diagonalDown","color":"A020F0","style":12},{"type":"diagonalUp","color":"A020F0","style":13}]}`)
if err != nil {
t.Log(err)
}
err = xlsx.Save()
if err != nil {
t.Log(err)
}
}
2 changes: 1 addition & 1 deletion lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (f *File) readXML(name string) string {

// saveFileList provides function to update given file content in file list of
// XLSX.
func (f *File) saveFileList(name string, content string) {
func (f *File) saveFileList(name, content string) {
f.XLSX[name] = XMLHeader + content
}

Expand Down
6 changes: 3 additions & 3 deletions picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (

// parseFormatPictureSet provides function to parse the format settings of the
// picture with default value.
func parseFormatPictureSet(formatSet string) *xlsxFormatPicture {
format := xlsxFormatPicture{
func parseFormatPictureSet(formatSet string) *formatPicture {
format := formatPicture{
FPrintsWithSheet: true,
FLocksWithSheet: false,
NoChangeAspect: false,
Expand Down Expand Up @@ -194,7 +194,7 @@ func (f *File) countDrawings() int {
// yAxis, file name and relationship index. In order to solve the problem that
// the label structure is changed after serialization and deserialization, two
// different structures: decodeWsDr and encodeWsDr are defined.
func (f *File) addDrawing(sheet, drawingXML, cell, file string, width, height, rID int, formatSet *xlsxFormatPicture) {
func (f *File) addDrawing(sheet, drawingXML, cell, file string, width, height, rID int, formatSet *formatPicture) {
cell = strings.ToUpper(cell)
fromCol := string(strings.Map(letterOnlyMapF, cell))
fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
Expand Down
223 changes: 223 additions & 0 deletions styles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package excelize

import (
"encoding/json"
"encoding/xml"
"strconv"
"strings"
)

// parseFormatBordersSet provides function to parse the format settings of the
// borders.
func parseFormatBordersSet(bordersSet string) (*formatBorder, error) {
var format formatBorder
err := json.Unmarshal([]byte(bordersSet), &format)
return &format, err
}

// SetBorder provides function to get value from cell by given sheet index and
// coordinate area in XLSX file. Note that the color field uses RGB color code
// and diagonalDown and diagonalUp type border should be use same color in the
// same coordinate area.
//
// For example create a borders of cell H9 on
// Sheet1:
//
// err := xlsx.SetBorder("Sheet1", "H9", "H9", `{"border":[{"type":"left","color":"0000FF","style":3},{"type":"top","color":"00FF00","style":4},{"type":"bottom","color":"FFFF00","style":5},{"type":"right","color":"FF0000","style":6},{"type":"diagonalDown","color":"A020F0","style":7},{"type":"diagonalUp","color":"A020F0","style":8}]}`)
// if err != nil {
// fmt.Println(err)
// }
//
// The following shows the border styles sorted by excelize index number:
//
// +-------+---------------+--------+-----------------+
// | Index | Name | Weight | Style |
// +=======+===============+========+=================+
// | 0 | None | 0 | |
// +-------+---------------+--------+-----------------+
// | 1 | Continuous | 1 | ``-----------`` |
// +-------+---------------+--------+-----------------+
// | 2 | Continuous | 2 | ``-----------`` |
// +-------+---------------+--------+-----------------+
// | 3 | Dash | 1 | ``- - - - - -`` |
// +-------+---------------+--------+-----------------+
// | 4 | Dot | 1 | ``. . . . . .`` |
// +-------+---------------+--------+-----------------+
// | 5 | Continuous | 3 | ``-----------`` |
// +-------+---------------+--------+-----------------+
// | 6 | Double | 3 | ``===========`` |
// +-------+---------------+--------+-----------------+
// | 7 | Continuous | 0 | ``-----------`` |
// +-------+---------------+--------+-----------------+
// | 8 | Dash | 2 | ``- - - - - -`` |
// +-------+---------------+--------+-----------------+
// | 9 | Dash Dot | 1 | ``- . - . - .`` |
// +-------+---------------+--------+-----------------+
// | 10 | Dash Dot | 2 | ``- . - . - .`` |
// +-------+---------------+--------+-----------------+
// | 11 | Dash Dot Dot | 1 | ``- . . - . .`` |
// +-------+---------------+--------+-----------------+
// | 12 | Dash Dot Dot | 2 | ``- . . - . .`` |
// +-------+---------------+--------+-----------------+
// | 13 | SlantDash Dot | 2 | ``/ - . / - .`` |
// +-------+---------------+--------+-----------------+
//
// The following shows the borders in the order shown in the Excel dialog:
//
// +-------+-----------------+-------+-----------------+
// | Index | Style | Index | Style |
// +=======+=================+=======+=================+
// | 0 | None | 12 | ``- . . - . .`` |
// +-------+-----------------+-------+-----------------+
// | 7 | ``-----------`` | 13 | ``/ - . / - .`` |
// +-------+-----------------+-------+-----------------+
// | 4 | ``. . . . . .`` | 10 | ``- . - . - .`` |
// +-------+-----------------+-------+-----------------+
// | 11 | ``- . . - . .`` | 8 | ``- - - - - -`` |
// +-------+-----------------+-------+-----------------+
// | 9 | ``- . - . - .`` | 2 | ``-----------`` |
// +-------+-----------------+-------+-----------------+
// | 3 | ``- - - - - -`` | 5 | ``-----------`` |
// +-------+-----------------+-------+-----------------+
// | 1 | ``-----------`` | 6 | ``===========`` |
// +-------+-----------------+-------+-----------------+
//
func (f *File) SetBorder(sheet, hcell, vcell, style string) error {
var styleSheet xlsxStyleSheet
xml.Unmarshal([]byte(f.readXML("xl/styles.xml")), &styleSheet)
formatBorder, err := parseFormatBordersSet(style)
if err != nil {
return err
}
borderID := setBorders(&styleSheet, formatBorder)
cellXfsID := setCellXfs(&styleSheet, borderID)
output, err := xml.Marshal(styleSheet)
if err != nil {
return err
}
f.saveFileList("xl/styles.xml", replaceWorkSheetsRelationshipsNameSpace(string(output)))
f.setCellStyle(sheet, hcell, vcell, cellXfsID)
return err
}

// setBorders provides function to add border elements in the styles.xml by
// given borders format settings.
func setBorders(style *xlsxStyleSheet, formatBorder *formatBorder) int {
var styles = []string{
"none",
"thin",
"medium",
"dashed",
"dotted",
"thick",
"double",
"hair",
"mediumDashed",
"dashDot",
"mediumDashDot",
"dashDotDot",
"mediumDashDotDot",
"slantDashDot",
}

var border xlsxBorder
for _, v := range formatBorder.Border {
if v.Style > 13 || v.Style < 0 {
continue
}
var color xlsxColor
color.RGB = v.Color
switch v.Type {
case "left":
border.Left.Style = styles[v.Style]
border.Left.Color = &color
case "right":
border.Right.Style = styles[v.Style]
border.Right.Color = &color
case "top":
border.Top.Style = styles[v.Style]
border.Top.Color = &color
case "bottom":
border.Bottom.Style = styles[v.Style]
border.Bottom.Color = &color
case "diagonalUp":
border.Diagonal.Style = styles[v.Style]
border.Diagonal.Color = &color
border.DiagonalUp = true
case "diagonalDown":
border.Diagonal.Style = styles[v.Style]
border.Diagonal.Color = &color
border.DiagonalDown = true
}
}
style.Borders.Count++
style.Borders.Border = append(style.Borders.Border, &border)
return style.Borders.Count - 1
}

// setCellXfs provides function to set describes all of the formatting for a
// cell.
func setCellXfs(style *xlsxStyleSheet, borderID int) int {
var xf xlsxXf
xf.BorderID = borderID
style.CellXfs.Count++
style.CellXfs.Xf = append(style.CellXfs.Xf, xf)
return style.CellXfs.Count - 1
}

// setCellStyle provides function to add style attribute for cells by given
// sheet index, coordinate area and style ID.
func (f *File) setCellStyle(sheet, hcell, vcell string, styleID int) {
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
}

// Correct the coordinate area, such correct C1:B3 to B1:C3.
hcell = toAlphaString(hxAxis+1) + strconv.Itoa(hyAxis+1)
vcell = toAlphaString(vxAxis+1) + strconv.Itoa(vyAxis+1)

var xlsx xlsxWorksheet
name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
xml.Unmarshal([]byte(f.readXML(name)), &xlsx)
if f.checked == nil {
f.checked = make(map[string]bool)
}
ok := f.checked[name]
if !ok {
xlsx = checkRow(xlsx)
f.checked[name] = true
}

xlsx = completeRow(xlsx, vxAxis+1, vyAxis+1)
xlsx = completeCol(xlsx, vxAxis+1, vyAxis+1)

for r, row := range xlsx.SheetData.Row {
for k, c := range row.C {
if checkCellInArea(c.R, hcell+":"+vcell) {
xlsx.SheetData.Row[r].C[k].S = styleID
}
}
}
output, _ := xml.Marshal(xlsx)
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpace(string(output)))
}
4 changes: 2 additions & 2 deletions xmlDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ type encodeWsDr struct {
WsDr xlsxWsDr `xml:"xdr:wsDr"`
}

// xlsxFormatPicture directly maps the format settings of the picture.
type xlsxFormatPicture struct {
// formatPicture directly maps the format settings of the picture.
type formatPicture struct {
FPrintsWithSheet bool `json:"print_obj"`
FLocksWithSheet bool `json:"locked"`
NoChangeAspect bool `json:"lock_aspect_ratio"`
Expand Down
Loading

0 comments on commit 1f73f08

Please sign in to comment.