forked from p30928647/excelize
Speed up merge cells
This commit is contained in:
parent
61d0ed1ff2
commit
f6f14f507e
|
@ -145,7 +145,7 @@ func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, off
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
coordinates, err := f.areaRefToCoordinates(ws.AutoFilter.Ref)
|
coordinates, err := areaRefToCoordinates(ws.AutoFilter.Ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, off
|
||||||
|
|
||||||
for i := 0; i < len(ws.MergeCells.Cells); i++ {
|
for i := 0; i < len(ws.MergeCells.Cells); i++ {
|
||||||
areaData := ws.MergeCells.Cells[i]
|
areaData := ws.MergeCells.Cells[i]
|
||||||
coordinates, err := f.areaRefToCoordinates(areaData.Ref)
|
coordinates, err := areaRefToCoordinates(areaData.Ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -219,7 +219,7 @@ func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, off
|
||||||
x1 = f.adjustMergeCellsHelper(x1, num, offset)
|
x1 = f.adjustMergeCellsHelper(x1, num, offset)
|
||||||
x2 = f.adjustMergeCellsHelper(x2, num, offset)
|
x2 = f.adjustMergeCellsHelper(x2, num, offset)
|
||||||
}
|
}
|
||||||
if x1 == x2 && y1 == y2 {
|
if x1 == x2 && y1 == y2 && i >= 0 {
|
||||||
f.deleteMergeCell(ws, i)
|
f.deleteMergeCell(ws, i)
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, off
|
||||||
// compare and calculate cell axis by the given pivot, operation axis and
|
// compare and calculate cell axis by the given pivot, operation axis and
|
||||||
// offset.
|
// offset.
|
||||||
func (f *File) adjustMergeCellsHelper(pivot, num, offset int) int {
|
func (f *File) adjustMergeCellsHelper(pivot, num, offset int) int {
|
||||||
if pivot >= num {
|
if pivot > num {
|
||||||
pivot += offset
|
pivot += offset
|
||||||
if pivot < 1 {
|
if pivot < 1 {
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -84,6 +84,10 @@ func TestAdjustHelper(t *testing.T) {
|
||||||
assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN is not exist")
|
assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN is not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdjustMergeCellsHelper(t *testing.T) {
|
||||||
|
assert.Equal(t, 1, NewFile().adjustMergeCellsHelper(1, 0, -2))
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdjustCalcChain(t *testing.T) {
|
func TestAdjustCalcChain(t *testing.T) {
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
f.CalcChain = &xlsxCalcChain{
|
f.CalcChain = &xlsxCalcChain{
|
||||||
|
|
12
calc.go
12
calc.go
|
@ -5103,11 +5103,11 @@ func (fn *formulaFuncs) kth(name string, argsList *list.List) formulaArg {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
|
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
|
||||||
}
|
}
|
||||||
array := argsList.Front().Value.(formulaArg).ToList()
|
array := argsList.Front().Value.(formulaArg).ToList()
|
||||||
kArg := argsList.Back().Value.(formulaArg).ToNumber()
|
argK := argsList.Back().Value.(formulaArg).ToNumber()
|
||||||
if kArg.Type != ArgNumber {
|
if argK.Type != ArgNumber {
|
||||||
return kArg
|
return argK
|
||||||
}
|
}
|
||||||
k := int(kArg.Number)
|
k := int(argK.Number)
|
||||||
if k < 1 {
|
if k < 1 {
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "k should be > 0")
|
return newErrorFormulaArg(formulaErrorNUM, "k should be > 0")
|
||||||
}
|
}
|
||||||
|
@ -7177,7 +7177,7 @@ func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg {
|
||||||
func vlookupBinarySearch(tableArray, lookupValue formulaArg) (matchIdx int, wasExact bool) {
|
func vlookupBinarySearch(tableArray, lookupValue formulaArg) (matchIdx int, wasExact bool) {
|
||||||
var low, high, lastMatchIdx int = 0, len(tableArray.Matrix) - 1, -1
|
var low, high, lastMatchIdx int = 0, len(tableArray.Matrix) - 1, -1
|
||||||
for low <= high {
|
for low <= high {
|
||||||
var mid int = low + (high-low)/2
|
mid := low + (high-low)/2
|
||||||
mtx := tableArray.Matrix[mid]
|
mtx := tableArray.Matrix[mid]
|
||||||
lhs := mtx[0]
|
lhs := mtx[0]
|
||||||
switch lookupValue.Type {
|
switch lookupValue.Type {
|
||||||
|
@ -7216,7 +7216,7 @@ func vlookupBinarySearch(tableArray, lookupValue formulaArg) (matchIdx int, wasE
|
||||||
func hlookupBinarySearch(row []formulaArg, lookupValue formulaArg) (matchIdx int, wasExact bool) {
|
func hlookupBinarySearch(row []formulaArg, lookupValue formulaArg) (matchIdx int, wasExact bool) {
|
||||||
var low, high, lastMatchIdx int = 0, len(row) - 1, -1
|
var low, high, lastMatchIdx int = 0, len(row) - 1, -1
|
||||||
for low <= high {
|
for low <= high {
|
||||||
var mid int = low + (high-low)/2
|
mid := low + (high-low)/2
|
||||||
mtx := row[mid]
|
mtx := row[mid]
|
||||||
result := compareFormulaArg(mtx, lookupValue, false, false)
|
result := compareFormulaArg(mtx, lookupValue, false, false)
|
||||||
if result == criteriaEq {
|
if result == criteriaEq {
|
||||||
|
|
31
cell.go
31
cell.go
|
@ -101,6 +101,28 @@ func (f *File) SetCellValue(sheet, axis string, value interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String extracts characters from a string item.
|
||||||
|
func (x xlsxSI) String() string {
|
||||||
|
if len(x.R) > 0 {
|
||||||
|
var rows strings.Builder
|
||||||
|
for _, s := range x.R {
|
||||||
|
if s.T != nil {
|
||||||
|
rows.WriteString(s.T.Val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bstrUnmarshal(rows.String())
|
||||||
|
}
|
||||||
|
if x.T != nil {
|
||||||
|
return bstrUnmarshal(x.T.Val)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasValue determine if cell non-blank value.
|
||||||
|
func (c *xlsxC) hasValue() bool {
|
||||||
|
return c.S != 0 || c.V != "" || c.F != nil || c.T != ""
|
||||||
|
}
|
||||||
|
|
||||||
// setCellIntFunc is a wrapper of SetCellInt.
|
// setCellIntFunc is a wrapper of SetCellInt.
|
||||||
func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error {
|
func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -431,13 +453,11 @@ func (f *File) GetCellHyperLink(sheet, axis string) (bool, string, error) {
|
||||||
if _, _, err := SplitCellName(axis); err != nil {
|
if _, _, err := SplitCellName(axis); err != nil {
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ws, err := f.workSheetReader(sheet)
|
ws, err := f.workSheetReader(sheet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
axis, err = f.mergeCellsParser(ws, axis)
|
if axis, err = f.mergeCellsParser(ws, axis); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
if ws.Hyperlinks != nil {
|
if ws.Hyperlinks != nil {
|
||||||
|
@ -485,8 +505,7 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string, opts ...Hype
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
axis, err = f.mergeCellsParser(ws, axis)
|
if axis, err = f.mergeCellsParser(ws, axis); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -932,7 +951,7 @@ func (f *File) checkCellInArea(cell, area string) (bool, error) {
|
||||||
if len(rng) != 2 {
|
if len(rng) != 2 {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
coordinates, err := f.areaRefToCoordinates(area)
|
coordinates, err := areaRefToCoordinates(area)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
6
col.go
6
col.go
|
@ -616,10 +616,10 @@ func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, heigh
|
||||||
// getColWidth provides a function to get column width in pixels by given
|
// getColWidth provides a function to get column width in pixels by given
|
||||||
// sheet name and column number.
|
// sheet name and column number.
|
||||||
func (f *File) getColWidth(sheet string, col int) int {
|
func (f *File) getColWidth(sheet string, col int) int {
|
||||||
xlsx, _ := f.workSheetReader(sheet)
|
ws, _ := f.workSheetReader(sheet)
|
||||||
if xlsx.Cols != nil {
|
if ws.Cols != nil {
|
||||||
var width float64
|
var width float64
|
||||||
for _, v := range xlsx.Cols.Col {
|
for _, v := range ws.Cols.Col {
|
||||||
if v.Min <= col && col <= v.Max {
|
if v.Min <= col && col <= v.Max {
|
||||||
width = v.Width
|
width = v.Width
|
||||||
}
|
}
|
||||||
|
|
5
lib.go
5
lib.go
|
@ -221,12 +221,11 @@ func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
|
||||||
|
|
||||||
// areaRefToCoordinates provides a function to convert area reference to a
|
// areaRefToCoordinates provides a function to convert area reference to a
|
||||||
// pair of coordinates.
|
// pair of coordinates.
|
||||||
func (f *File) areaRefToCoordinates(ref string) ([]int, error) {
|
func areaRefToCoordinates(ref string) ([]int, error) {
|
||||||
rng := strings.Split(strings.Replace(ref, "$", "", -1), ":")
|
rng := strings.Split(strings.Replace(ref, "$", "", -1), ":")
|
||||||
if len(rng) < 2 {
|
if len(rng) < 2 {
|
||||||
return nil, ErrParameterInvalid
|
return nil, ErrParameterInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
return areaRangeToCoordinates(rng[0], rng[1])
|
return areaRangeToCoordinates(rng[0], rng[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +289,7 @@ func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) {
|
||||||
}
|
}
|
||||||
cells[col] = append(cells[col], []int{col, row})
|
cells[col] = append(cells[col], []int{col, row})
|
||||||
case 2:
|
case 2:
|
||||||
if coordinates, err = f.areaRefToCoordinates(ref); err != nil {
|
if coordinates, err = areaRefToCoordinates(ref); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = sortCoordinates(coordinates)
|
_ = sortCoordinates(coordinates)
|
||||||
|
|
217
merge.go
217
merge.go
|
@ -11,10 +11,16 @@
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
import "strings"
|
||||||
"fmt"
|
|
||||||
"strings"
|
// Rect gets merged cell rectangle coordinates sequence.
|
||||||
)
|
func (mc *xlsxMergeCell) Rect() ([]int, error) {
|
||||||
|
var err error
|
||||||
|
if mc.rect == nil {
|
||||||
|
mc.rect, err = areaRefToCoordinates(mc.Ref)
|
||||||
|
}
|
||||||
|
return mc.rect, err
|
||||||
|
}
|
||||||
|
|
||||||
// MergeCell provides a function to merge cells by given coordinate area and
|
// MergeCell provides a function to merge cells by given coordinate area and
|
||||||
// sheet name. Merging cells only keeps the upper-left cell value, and
|
// sheet name. Merging cells only keeps the upper-left cell value, and
|
||||||
|
@ -24,7 +30,9 @@ import (
|
||||||
// err := f.MergeCell("Sheet1", "D3", "E9")
|
// err := f.MergeCell("Sheet1", "D3", "E9")
|
||||||
//
|
//
|
||||||
// If you create a merged cell that overlaps with another existing merged cell,
|
// If you create a merged cell that overlaps with another existing merged cell,
|
||||||
// those merged cells that already exist will be removed.
|
// those merged cells that already exist will be removed. The cell coordinates
|
||||||
|
// tuple after merging in the following range will be: A1(x3,y1) D1(x2,y1)
|
||||||
|
// A8(x3,y4) D8(x2,y4)
|
||||||
//
|
//
|
||||||
// B1(x1,y1) D1(x2,y1)
|
// B1(x1,y1) D1(x2,y1)
|
||||||
// +------------------------+
|
// +------------------------+
|
||||||
|
@ -39,15 +47,15 @@ import (
|
||||||
// +------------------------+
|
// +------------------------+
|
||||||
//
|
//
|
||||||
func (f *File) MergeCell(sheet, hcell, vcell string) error {
|
func (f *File) MergeCell(sheet, hcell, vcell string) error {
|
||||||
rect1, err := f.areaRefToCoordinates(hcell + ":" + vcell)
|
rect, err := areaRefToCoordinates(hcell + ":" + vcell)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Correct the coordinate area, such correct C1:B3 to B1:C3.
|
// Correct the coordinate area, such correct C1:B3 to B1:C3.
|
||||||
_ = sortCoordinates(rect1)
|
_ = sortCoordinates(rect)
|
||||||
|
|
||||||
hcell, _ = CoordinatesToCellName(rect1[0], rect1[1])
|
hcell, _ = CoordinatesToCellName(rect[0], rect[1])
|
||||||
vcell, _ = CoordinatesToCellName(rect1[2], rect1[3])
|
vcell, _ = CoordinatesToCellName(rect[2], rect[3])
|
||||||
|
|
||||||
ws, err := f.workSheetReader(sheet)
|
ws, err := f.workSheetReader(sheet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -55,49 +63,9 @@ func (f *File) MergeCell(sheet, hcell, vcell string) error {
|
||||||
}
|
}
|
||||||
ref := hcell + ":" + vcell
|
ref := hcell + ":" + vcell
|
||||||
if ws.MergeCells != nil {
|
if ws.MergeCells != nil {
|
||||||
for i := 0; i < len(ws.MergeCells.Cells); i++ {
|
ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: rect})
|
||||||
cellData := ws.MergeCells.Cells[i]
|
|
||||||
if cellData == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cc := strings.Split(cellData.Ref, ":")
|
|
||||||
if len(cc) != 2 {
|
|
||||||
return fmt.Errorf("invalid area %q", cellData.Ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
rect2, err := f.areaRefToCoordinates(cellData.Ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the merged cells of the overlapping area.
|
|
||||||
if isOverlap(rect1, rect2) {
|
|
||||||
ws.MergeCells.Cells = append(ws.MergeCells.Cells[:i], ws.MergeCells.Cells[i+1:]...)
|
|
||||||
i--
|
|
||||||
|
|
||||||
if rect1[0] > rect2[0] {
|
|
||||||
rect1[0], rect2[0] = rect2[0], rect1[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if rect1[2] < rect2[2] {
|
|
||||||
rect1[2], rect2[2] = rect2[2], rect1[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if rect1[1] > rect2[1] {
|
|
||||||
rect1[1], rect2[1] = rect2[1], rect1[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if rect1[3] < rect2[3] {
|
|
||||||
rect1[3], rect2[3] = rect2[3], rect1[3]
|
|
||||||
}
|
|
||||||
hcell, _ = CoordinatesToCellName(rect1[0], rect1[1])
|
|
||||||
vcell, _ = CoordinatesToCellName(rect1[2], rect1[3])
|
|
||||||
ref = hcell + ":" + vcell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
|
|
||||||
} else {
|
} else {
|
||||||
ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref}}}
|
ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref, rect: rect}}}
|
||||||
}
|
}
|
||||||
ws.MergeCells.Count = len(ws.MergeCells.Cells)
|
ws.MergeCells.Count = len(ws.MergeCells.Cells)
|
||||||
return err
|
return err
|
||||||
|
@ -114,7 +82,7 @@ func (f *File) UnmergeCell(sheet string, hcell, vcell string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rect1, err := f.areaRefToCoordinates(hcell + ":" + vcell)
|
rect1, err := areaRefToCoordinates(hcell + ":" + vcell)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -126,26 +94,19 @@ func (f *File) UnmergeCell(sheet string, hcell, vcell string) error {
|
||||||
if ws.MergeCells == nil {
|
if ws.MergeCells == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err = f.mergeOverlapCells(ws); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
i := 0
|
i := 0
|
||||||
for _, cellData := range ws.MergeCells.Cells {
|
for _, mergeCell := range ws.MergeCells.Cells {
|
||||||
if cellData == nil {
|
if mergeCell == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cc := strings.Split(cellData.Ref, ":")
|
rect2, _ := areaRefToCoordinates(mergeCell.Ref)
|
||||||
if len(cc) != 2 {
|
|
||||||
return fmt.Errorf("invalid area %q", cellData.Ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
rect2, err := f.areaRefToCoordinates(cellData.Ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if isOverlap(rect1, rect2) {
|
if isOverlap(rect1, rect2) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ws.MergeCells.Cells[i] = cellData
|
ws.MergeCells.Cells[i] = mergeCell
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
ws.MergeCells.Cells = ws.MergeCells.Cells[:i]
|
ws.MergeCells.Cells = ws.MergeCells.Cells[:i]
|
||||||
|
@ -165,8 +126,10 @@ func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
|
||||||
return mergeCells, err
|
return mergeCells, err
|
||||||
}
|
}
|
||||||
if ws.MergeCells != nil {
|
if ws.MergeCells != nil {
|
||||||
|
if err = f.mergeOverlapCells(ws); err != nil {
|
||||||
|
return mergeCells, err
|
||||||
|
}
|
||||||
mergeCells = make([]MergeCell, 0, len(ws.MergeCells.Cells))
|
mergeCells = make([]MergeCell, 0, len(ws.MergeCells.Cells))
|
||||||
|
|
||||||
for i := range ws.MergeCells.Cells {
|
for i := range ws.MergeCells.Cells {
|
||||||
ref := ws.MergeCells.Cells[i].Ref
|
ref := ws.MergeCells.Cells[i].Ref
|
||||||
axis := strings.Split(ref, ":")[0]
|
axis := strings.Split(ref, ":")[0]
|
||||||
|
@ -174,10 +137,128 @@ func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
|
||||||
mergeCells = append(mergeCells, []string{ref, val})
|
mergeCells = append(mergeCells, []string{ref, val})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mergeCells, err
|
return mergeCells, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// overlapRange calculate overlap range of merged cells, and returns max
|
||||||
|
// column and rows of the range.
|
||||||
|
func overlapRange(ws *xlsxWorksheet) (row, col int, err error) {
|
||||||
|
var rect []int
|
||||||
|
for _, mergeCell := range ws.MergeCells.Cells {
|
||||||
|
if mergeCell == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rect, err = mergeCell.Rect(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x1, y1, x2, y2 := rect[0], rect[1], rect[2], rect[3]
|
||||||
|
if x1 > col {
|
||||||
|
col = x1
|
||||||
|
}
|
||||||
|
if x2 > col {
|
||||||
|
col = x2
|
||||||
|
}
|
||||||
|
if y1 > row {
|
||||||
|
row = y1
|
||||||
|
}
|
||||||
|
if y2 > row {
|
||||||
|
row = y2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatMergedCells convert merged cells range reference to cell-matrix.
|
||||||
|
func flatMergedCells(ws *xlsxWorksheet, matrix [][]*xlsxMergeCell) error {
|
||||||
|
for i, cell := range ws.MergeCells.Cells {
|
||||||
|
rect, err := cell.Rect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x1, y1, x2, y2 := rect[0]-1, rect[1]-1, rect[2]-1, rect[3]-1
|
||||||
|
var overlapCells []*xlsxMergeCell
|
||||||
|
for x := x1; x <= x2; x++ {
|
||||||
|
for y := y1; y <= y2; y++ {
|
||||||
|
if matrix[x][y] != nil {
|
||||||
|
overlapCells = append(overlapCells, matrix[x][y])
|
||||||
|
}
|
||||||
|
matrix[x][y] = cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(overlapCells) != 0 {
|
||||||
|
newCell := cell
|
||||||
|
for _, overlapCell := range overlapCells {
|
||||||
|
newCell = mergeCell(cell, overlapCell)
|
||||||
|
}
|
||||||
|
newRect, _ := newCell.Rect()
|
||||||
|
x1, y1, x2, y2 := newRect[0]-1, newRect[1]-1, newRect[2]-1, newRect[3]-1
|
||||||
|
for x := x1; x <= x2; x++ {
|
||||||
|
for y := y1; y <= y2; y++ {
|
||||||
|
matrix[x][y] = newCell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws.MergeCells.Cells[i] = newCell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeOverlapCells merge overlap cells.
|
||||||
|
func (f *File) mergeOverlapCells(ws *xlsxWorksheet) error {
|
||||||
|
rows, cols, err := overlapRange(ws)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rows == 0 || cols == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
matrix := make([][]*xlsxMergeCell, cols)
|
||||||
|
for i := range matrix {
|
||||||
|
matrix[i] = make([]*xlsxMergeCell, rows)
|
||||||
|
}
|
||||||
|
_ = flatMergedCells(ws, matrix)
|
||||||
|
mergeCells := ws.MergeCells.Cells[:0]
|
||||||
|
for _, cell := range ws.MergeCells.Cells {
|
||||||
|
rect, _ := cell.Rect()
|
||||||
|
x1, y1, x2, y2 := rect[0]-1, rect[1]-1, rect[2]-1, rect[3]-1
|
||||||
|
if matrix[x1][y1] == cell {
|
||||||
|
mergeCells = append(mergeCells, cell)
|
||||||
|
for x := x1; x <= x2; x++ {
|
||||||
|
for y := y1; y <= y2; y++ {
|
||||||
|
matrix[x][y] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws.MergeCells.Count, ws.MergeCells.Cells = len(mergeCells), mergeCells
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeCell merge two cells.
|
||||||
|
func mergeCell(cell1, cell2 *xlsxMergeCell) *xlsxMergeCell {
|
||||||
|
rect1, _ := cell1.Rect()
|
||||||
|
rect2, _ := cell2.Rect()
|
||||||
|
|
||||||
|
if rect1[0] > rect2[0] {
|
||||||
|
rect1[0], rect2[0] = rect2[0], rect1[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if rect1[2] < rect2[2] {
|
||||||
|
rect1[2], rect2[2] = rect2[2], rect1[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
if rect1[1] > rect2[1] {
|
||||||
|
rect1[1], rect2[1] = rect2[1], rect1[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if rect1[3] < rect2[3] {
|
||||||
|
rect1[3], rect2[3] = rect2[3], rect1[3]
|
||||||
|
}
|
||||||
|
hcell, _ := CoordinatesToCellName(rect1[0], rect1[1])
|
||||||
|
vcell, _ := CoordinatesToCellName(rect1[2], rect1[3])
|
||||||
|
return &xlsxMergeCell{rect: rect1, Ref: hcell + ":" + vcell}
|
||||||
|
}
|
||||||
|
|
||||||
// MergeCell define a merged cell data.
|
// MergeCell define a merged cell data.
|
||||||
// It consists of the following structure.
|
// It consists of the following structure.
|
||||||
// example: []string{"D4:E10", "cell value"}
|
// example: []string{"D4:E10", "cell value"}
|
||||||
|
|
|
@ -27,7 +27,7 @@ func TestMergeCell(t *testing.T) {
|
||||||
assert.NoError(t, f.SetCellHyperLink("Sheet1", "J11", "https://github.com/xuri/excelize", "External"))
|
assert.NoError(t, f.SetCellHyperLink("Sheet1", "J11", "https://github.com/xuri/excelize", "External"))
|
||||||
assert.NoError(t, f.SetCellFormula("Sheet1", "G12", "SUM(Sheet1!B19,Sheet1!C19)"))
|
assert.NoError(t, f.SetCellFormula("Sheet1", "G12", "SUM(Sheet1!B19,Sheet1!C19)"))
|
||||||
value, err := f.GetCellValue("Sheet1", "H11")
|
value, err := f.GetCellValue("Sheet1", "H11")
|
||||||
assert.Equal(t, "0.5", value)
|
assert.Equal(t, "100", value)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
value, err = f.GetCellValue("Sheet2", "A6") // Merged cell ref is single coordinate.
|
value, err = f.GetCellValue("Sheet2", "A6") // Merged cell ref is single coordinate.
|
||||||
assert.Equal(t, "", value)
|
assert.Equal(t, "", value)
|
||||||
|
@ -75,16 +75,24 @@ func TestMergeCell(t *testing.T) {
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
|
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
|
||||||
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
|
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
|
||||||
|
}
|
||||||
|
|
||||||
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
func TestMergeCellOverlap(t *testing.T) {
|
||||||
assert.True(t, ok)
|
f := NewFile()
|
||||||
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
|
assert.NoError(t, f.MergeCell("Sheet1", "A1", "C2"))
|
||||||
assert.EqualError(t, f.MergeCell("Sheet1", "A2", "B3"), `invalid area "A1"`)
|
assert.NoError(t, f.MergeCell("Sheet1", "B2", "D3"))
|
||||||
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCellOverlap.xlsx")))
|
||||||
|
|
||||||
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
f, err := OpenFile(filepath.Join("test", "TestMergeCellOverlap.xlsx"))
|
||||||
assert.True(t, ok)
|
if !assert.NoError(t, err) {
|
||||||
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
|
t.FailNow()
|
||||||
assert.EqualError(t, f.MergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
}
|
||||||
|
mc, err := f.GetMergeCells("Sheet1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(mc))
|
||||||
|
assert.Equal(t, "A1", mc[0].GetStartAxis())
|
||||||
|
assert.Equal(t, "D3", mc[0].GetEndAxis())
|
||||||
|
assert.Equal(t, "", mc[0].GetCellValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMergeCells(t *testing.T) {
|
func TestGetMergeCells(t *testing.T) {
|
||||||
|
@ -173,11 +181,15 @@ func TestUnmergeCell(t *testing.T) {
|
||||||
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
|
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
|
||||||
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `invalid area "A1"`)
|
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), "parameter is invalid")
|
||||||
|
|
||||||
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
|
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
|
||||||
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlatMergedCells(t *testing.T) {
|
||||||
|
ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}}
|
||||||
|
assert.EqualError(t, flatMergedCells(ws, [][]*xlsxMergeCell{}), "parameter is invalid")
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ws.Lock()
|
||||||
// Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
|
// Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
|
||||||
drawingID := f.countDrawings() + 1
|
drawingID := f.countDrawings() + 1
|
||||||
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
|
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
|
||||||
|
@ -162,6 +163,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
|
||||||
}
|
}
|
||||||
drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType)
|
drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType)
|
||||||
}
|
}
|
||||||
|
ws.Unlock()
|
||||||
err = f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet)
|
err = f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -198,7 +198,7 @@ func (f *File) adjustRange(rangeStr string) (string, []int, error) {
|
||||||
return "", []int{}, ErrParameterInvalid
|
return "", []int{}, ErrParameterInvalid
|
||||||
}
|
}
|
||||||
trimRng := strings.Replace(rng[1], "$", "", -1)
|
trimRng := strings.Replace(rng[1], "$", "", -1)
|
||||||
coordinates, err := f.areaRefToCoordinates(trimRng)
|
coordinates, err := areaRefToCoordinates(trimRng)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rng[0], []int{}, err
|
return rng[0], []int{}, err
|
||||||
}
|
}
|
||||||
|
|
4
rows.go
4
rows.go
|
@ -614,7 +614,7 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in
|
||||||
row++
|
row++
|
||||||
}
|
}
|
||||||
for _, rng := range ws.MergeCells.Cells {
|
for _, rng := range ws.MergeCells.Cells {
|
||||||
coordinates, err := f.areaRefToCoordinates(rng.Ref)
|
coordinates, err := areaRefToCoordinates(rng.Ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -624,7 +624,7 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in
|
||||||
}
|
}
|
||||||
for i := 0; i < len(ws.MergeCells.Cells); i++ {
|
for i := 0; i < len(ws.MergeCells.Cells); i++ {
|
||||||
areaData := ws.MergeCells.Cells[i]
|
areaData := ws.MergeCells.Cells[i]
|
||||||
coordinates, _ := f.areaRefToCoordinates(areaData.Ref)
|
coordinates, _ := areaRefToCoordinates(areaData.Ref)
|
||||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||||
if y1 == y2 && y1 == row {
|
if y1 == y2 && y1 == row {
|
||||||
from, _ := CoordinatesToCellName(x1, row2)
|
from, _ := CoordinatesToCellName(x1, row2)
|
||||||
|
|
3
sheet.go
3
sheet.go
|
@ -158,6 +158,9 @@ func (f *File) workSheetWriter() {
|
||||||
f.Sheet.Range(func(p, ws interface{}) bool {
|
f.Sheet.Range(func(p, ws interface{}) bool {
|
||||||
if ws != nil {
|
if ws != nil {
|
||||||
sheet := ws.(*xlsxWorksheet)
|
sheet := ws.(*xlsxWorksheet)
|
||||||
|
if sheet.MergeCells != nil && len(sheet.MergeCells.Cells) > 0 {
|
||||||
|
_ = f.mergeOverlapCells(sheet)
|
||||||
|
}
|
||||||
for k, v := range sheet.SheetData.Row {
|
for k, v := range sheet.SheetData.Row {
|
||||||
sheet.SheetData.Row[k].C = trimCell(v.C)
|
sheet.SheetData.Row[k].C = trimCell(v.C)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,7 @@
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
import "encoding/xml"
|
||||||
"encoding/xml"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// xlsxSST directly maps the sst element from the namespace
|
// xlsxSST directly maps the sst element from the namespace
|
||||||
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may
|
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may
|
||||||
|
@ -44,23 +41,6 @@ type xlsxSI struct {
|
||||||
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
|
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// String extracts characters from a string item.
|
|
||||||
func (x xlsxSI) String() string {
|
|
||||||
if len(x.R) > 0 {
|
|
||||||
var rows strings.Builder
|
|
||||||
for _, s := range x.R {
|
|
||||||
if s.T != nil {
|
|
||||||
rows.WriteString(s.T.Val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bstrUnmarshal(rows.String())
|
|
||||||
}
|
|
||||||
if x.T != nil {
|
|
||||||
return bstrUnmarshal(x.T.Val)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// xlsxR represents a run of rich text. A rich text run is a region of text
|
// xlsxR represents a run of rich text. A rich text run is a region of text
|
||||||
// that share a common set of properties, such as formatting properties. The
|
// that share a common set of properties, such as formatting properties. The
|
||||||
// properties are defined in the rPr element, and the text displayed to the
|
// properties are defined in the rPr element, and the text displayed to the
|
||||||
|
|
|
@ -399,7 +399,8 @@ type xlsxCustomSheetView struct {
|
||||||
|
|
||||||
// xlsxMergeCell directly maps the mergeCell element. A single merged cell.
|
// xlsxMergeCell directly maps the mergeCell element. A single merged cell.
|
||||||
type xlsxMergeCell struct {
|
type xlsxMergeCell struct {
|
||||||
Ref string `xml:"ref,attr,omitempty"`
|
Ref string `xml:"ref,attr,omitempty"`
|
||||||
|
rect []int
|
||||||
}
|
}
|
||||||
|
|
||||||
// xlsxMergeCells directly maps the mergeCells element. This collection
|
// xlsxMergeCells directly maps the mergeCells element. This collection
|
||||||
|
@ -468,10 +469,6 @@ type xlsxC struct {
|
||||||
IS *xlsxSI `xml:"is"`
|
IS *xlsxSI `xml:"is"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xlsxC) hasValue() bool {
|
|
||||||
return c.S != 0 || c.V != "" || c.F != nil || c.T != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// xlsxF represents a formula for the cell. The formula expression is
|
// xlsxF represents a formula for the cell. The formula expression is
|
||||||
// contained in the character node of this element.
|
// contained in the character node of this element.
|
||||||
type xlsxF struct {
|
type xlsxF struct {
|
||||||
|
|
Loading…
Reference in New Issue