Skip to content

Commit

Permalink
spreadsheet: add support for merged cells
Browse files Browse the repository at this point in the history
  • Loading branch information
tbaliance committed Sep 7, 2017
1 parent 8957cf7 commit 924140c
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ and .pptx).
- [Bar Chart](https://github.com/baliance/gooxml/tree/master/_examples/spreadsheet/bar-chart) Bar Charts
- [Mutiple Charts](https://github.com/baliance/gooxml/tree/master/_examples/spreadsheet/multiple-charts) Multiple charts on a single sheet
- [Named Cell Ranges](https://github.com/baliance/gooxml/tree/master/_examples/spreadsheet/named-ranges) Naming cell ranges
- [Merged Cells](https://github.com/baliance/gooxml/tree/master/_examples/spreadsheet/merged) Merge and unmerge cells.

## Raw Types ##

Expand Down
35 changes: 35 additions & 0 deletions _examples/spreadsheet/merged/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2017 Baliance. All rights reserved.
package main

import (
"fmt"
"log"

"baliance.com/gooxml/spreadsheet"

sml "baliance.com/gooxml/schema/schemas.openxmlformats.org/spreadsheetml"
)

func main() {
ss := spreadsheet.New()
sheet := ss.AddSheet()

sheet.Cell("A1").SetString("Hello World!")
sheet.Cell("B1").SetString("will not be visible") // as it's not the first cell within a merged range Excel warns you when you do this through the UI
sheet.AddMergedCells("A1", "C2")

centered := ss.StyleSheet.AddCellStyle()
centered.SetHorizontalAlignment(sml.ST_HorizontalAlignmentCenter)
centered.SetVerticalAlignment(sml.ST_VerticalAlignmentCenter)
sheet.Cell("A1").SetStyle(centered)

for _, m := range sheet.MergedCells() {
fmt.Println("merged region", m.Reference(), "has contents", m.Cell().GetString())
}

if err := ss.Validate(); err != nil {
log.Fatalf("error validating sheet: %s", err)
}

ss.SaveToFile("merged.xlsx")
}
Binary file added _examples/spreadsheet/merged/merged.xlsx
Binary file not shown.
25 changes: 25 additions & 0 deletions spreadsheet/cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,31 @@ func (c Cell) SetStyleIndex(idx uint32) {
c.x.SAttr = gooxml.Uint32(idx)
}

// GetString returns the string in a cell if it's an inline or string table
// string. Otherwise it returns an empty string.
func (c Cell) GetString() string {
switch c.x.TAttr {
case sml.ST_CellTypeInlineStr:
if c.x.Is == nil || c.x.Is.T == nil {
return ""
}
case sml.ST_CellTypeS:
if c.x.V == nil {
return ""
}
id, err := strconv.Atoi(*c.x.V)
if err != nil {
return ""
}
s, err := c.w.SharedStrings.GetString(id)
if err != nil {
return ""
}
return s
}
return ""
}

func (c Cell) GetValue() (string, error) {
switch c.x.TAttr {
case sml.ST_CellTypeInlineStr:
Expand Down
48 changes: 48 additions & 0 deletions spreadsheet/mergedcell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2017 Baliance. All rights reserved.
//
// Use of this source code is governed by the terms of the Affero GNU General
// Public License version 3.0 as published by the Free Software Foundation and
// appearing in the file LICENSE included in the packaging of this file. A
// commercial license can be purchased by contacting [email protected].

package spreadsheet

import (
"fmt"
"strings"

sml "baliance.com/gooxml/schema/schemas.openxmlformats.org/spreadsheetml"
)

type MergedCell struct {
wb *Workbook
ws *sml.Worksheet
x *sml.CT_MergeCell
}

// X returns the inner wrapped XML type.
func (s MergedCell) X() *sml.CT_MergeCell {
return s.x
}

// SetReference sets the regin of cells that the merged cell applies to.
func (s MergedCell) SetReference(ref string) {
s.x.RefAttr = ref
}

// Reference returns the region of cells that are merged.
func (s MergedCell) Reference() string {
return s.x.RefAttr
}

// Cell returns the actual cell behind the merged region
func (s MergedCell) Cell() Cell {
ref := s.Reference()
if idx := strings.Index(s.Reference(), ":"); idx != -1 {
ref = ref[0:idx]
return Sheet{w: s.wb, x: s.ws}.Cell(ref)
}
fmt.Println("DIE")
// couldn't find it, log an error?
return Cell{}
}
43 changes: 43 additions & 0 deletions spreadsheet/sheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,46 @@ func (s Sheet) SetAutoFilter(rangeRef string) {
}
}
}

// AddMergedCells merges cells within a sheet.
func (s Sheet) AddMergedCells(fromRef, toRef string) MergedCell {
// TODO: we might need to actually create the merged cells if they don't
// exist, but it appears to work fine on both Excel and LibreOffice just
// creating the merged region

if s.x.MergeCells == nil {
s.x.MergeCells = sml.NewCT_MergeCells()
}

merge := sml.NewCT_MergeCell()
merge.RefAttr = fmt.Sprintf("%s:%s", fromRef, toRef)

s.x.MergeCells.MergeCell = append(s.x.MergeCells.MergeCell, merge)
s.x.MergeCells.CountAttr = gooxml.Uint32(uint32(len(s.x.MergeCells.MergeCell)))
return MergedCell{s.w, s.x, merge}
}

// MergedCells returns the merged cell regions within the sheet.
func (s Sheet) MergedCells() []MergedCell {
if s.x.MergeCells == nil {
return nil
}
ret := []MergedCell{}
for _, c := range s.x.MergeCells.MergeCell {
ret = append(ret, MergedCell{s.w, s.x, c})
}
return ret
}

// RemoveMergedCell removes merging from a cell range within a sheet. The cells
// that made up the merged cell remain, but are no lon merged.
func (s Sheet) RemoveMergedCell(mc MergedCell) {
for i, c := range s.x.MergeCells.MergeCell {
if c == mc.X() {
copy(s.x.MergeCells.MergeCell[i:], s.x.MergeCells.MergeCell[i+1:])
s.x.MergeCells.MergeCell[len(s.x.MergeCells.MergeCell)-1] = nil
s.x.MergeCells.MergeCell = s.x.MergeCells.MergeCell[:len(s.x.MergeCells.MergeCell)-1]
}
}

}
31 changes: 31 additions & 0 deletions spreadsheet/sheet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,34 @@ func TestSheetNameLength(t *testing.T) {
t.Errorf("expected validation error with sheet name too long")
}
}

func TestMergedCell(t *testing.T) {
wb := spreadsheet.New()
sheet := wb.AddSheet()

expContent := "testing 123"
sheet.Cell("A1").SetString(expContent)
sheet.Cell("B1").SetString("in range, but not visible")
if len(sheet.MergedCells()) != 0 {
t.Errorf("new sheet should have no merged cells")
}
sheet.AddMergedCells("A1", "C2")
if len(sheet.MergedCells()) != 1 {
t.Errorf("sheet should have a single merged cells")
}

mc := sheet.MergedCells()[0]
expRef := "A1:C2"
if mc.Reference() != expRef {
t.Errorf("expected merged cell reference %s, got %s", expRef, mc.Reference())
}

if mc.Cell().GetString() != expContent {
t.Errorf("expected merged cell content to be '%s', got '%s'", expContent, mc.Cell().GetString())
}

sheet.RemoveMergedCell(mc)
if len(sheet.MergedCells()) != 0 {
t.Errorf("after removal, sheet should have no merged cells")
}
}

0 comments on commit 924140c

Please sign in to comment.