Get all cells in a row with empty ones (#377)

* Get all cells in a row with empty ones
* sheet.MaxColumnIdx() changed
* goimports for all
This commit is contained in:
Vyacheslav Zgordan 2020-03-25 16:40:25 +03:00 committed by GitHub
parent 5f9c94bf84
commit 72ad869a28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 426 additions and 341 deletions

View File

@ -0,0 +1,36 @@
// Copyright 2017 FoxyUtils ehf. All rights reserved.
package main
// This example demonstrates outputing all cells in a row of an excel spreadsheet, including empty cells.
import (
"fmt"
"log"
"github.com/unidoc/unioffice/spreadsheet"
)
func main() {
ss, err := spreadsheet.Open("test.xlsx")
if err != nil {
log.Fatalf("error opening document: %s", err)
}
s := ss.Sheets()[0]
maxColumnIdx := s.MaxColumnIdx()
for _, row := range s.Rows() {
for _, cell := range row.CellsWithEmpty(maxColumnIdx) {
fmt.Println(cell.Reference(), ":", cell.GetFormattedValue())
}
}
fmt.Print("\n\n\n")
s.Cell("F4").SetString("Hello world")
maxColumnIdx = s.MaxColumnIdx()
for _, row := range s.Rows() {
for _, cell := range row.CellsWithEmpty(maxColumnIdx) {
fmt.Println(cell.Reference(), ":", cell.GetFormattedValue())
}
}
}

Binary file not shown.

View File

@ -10,9 +10,9 @@ import (
"github.com/unidoc/unioffice/common"
"github.com/unidoc/unioffice/document"
"github.com/unidoc/unioffice/schema/soo/ofc/docPropsVTypes"
"github.com/unidoc/unioffice/testhelper"
"github.com/unidoc/unioffice/zippkg"
"github.com/unidoc/unioffice/schema/soo/ofc/docPropsVTypes"
)
func TestMarshalCustomProperties(t *testing.T) {

View File

@ -395,7 +395,6 @@ func (p *Presentation) Save(w io.Writer) error {
r.Properties().SetSolidFill(color.Red)
}
dt := unioffice.DocTypePresentation
z := zip.NewWriter(w)

View File

@ -8,6 +8,7 @@
// commercial license can be purchased via https://unidoc.io website.
package custom_properties
import "github.com/unidoc/unioffice"
// init registers constructor functions for dynamically creating elements based off the XML namespace and name

View File

@ -27,7 +27,7 @@ const iso8601Format = "2006-01-02T15:04:05Z07:00"
// Cell is a single cell within a sheet.
type Cell struct {
w *Workbook
s *sml.Worksheet
sheet *Sheet
r *sml.CT_Row
x *sml.CT_Cell
}
@ -110,8 +110,8 @@ func (c Cell) SetFormulaShared(formula string, rows, cols uint32) error {
}
sid := uint32(0)
for _, r := range c.s.SheetData.Row {
for _, c := range r.C {
for _, r := range c.sheet.Rows() {
for _, c := range r.x.C {
if c.F != nil && c.F.SiAttr != nil && *c.F.SiAttr >= sid {
sid = *c.F.SiAttr
}
@ -122,7 +122,7 @@ func (c Cell) SetFormulaShared(formula string, rows, cols uint32) error {
ref := fmt.Sprintf("%s%d:%s%d", cref.Column, cref.RowIdx, reference.IndexToColumn(cref.ColumnIdx+cols), cref.RowIdx+rows)
c.x.F.RefAttr = unioffice.String(ref)
c.x.F.SiAttr = unioffice.Uint32(sid)
sheet := Sheet{c.w, nil, c.s}
sheet := Sheet{c.w, c.sheet.cts, c.sheet.x}
for row := cref.RowIdx; row <= cref.RowIdx+rows; row++ {
for col := cref.ColumnIdx; col <= cref.ColumnIdx+cols; col++ {
if row == cref.RowIdx && col == cref.ColumnIdx {
@ -200,11 +200,16 @@ func (c Cell) getLabelPrefix() string {
sid := *c.x.SAttr
cs := c.w.StyleSheet.GetCellStyle(sid)
switch cs.xf.Alignment.HorizontalAttr {
case sml.ST_HorizontalAlignmentLeft: return "'"
case sml.ST_HorizontalAlignmentRight: return "\""
case sml.ST_HorizontalAlignmentCenter: return "^"
case sml.ST_HorizontalAlignmentFill: return "\\"
default: return ""
case sml.ST_HorizontalAlignmentLeft:
return "'"
case sml.ST_HorizontalAlignmentRight:
return "\""
case sml.ST_HorizontalAlignmentCenter:
return "^"
case sml.ST_HorizontalAlignmentFill:
return "\\"
default:
return ""
}
}
@ -504,22 +509,23 @@ func (c Cell) GetRawValue() (string, error) {
// SetHyperlink sets a hyperlink on a cell.
func (c Cell) SetHyperlink(hl common.Hyperlink) {
if c.s.Hyperlinks == nil {
c.s.Hyperlinks = sml.NewCT_Hyperlinks()
ws := c.sheet.x
if ws.Hyperlinks == nil {
ws.Hyperlinks = sml.NewCT_Hyperlinks()
}
rel := common.Relationship(hl)
hle := sml.NewCT_Hyperlink()
hle.RefAttr = c.Reference()
hle.IdAttr = unioffice.String(rel.ID())
c.s.Hyperlinks.Hyperlink = append(c.s.Hyperlinks.Hyperlink, hle)
ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink, hle)
}
// AddHyperlink creates and sets a hyperlink on a cell.
func (c Cell) AddHyperlink(url string) {
// store the relationships so we don't need to do a lookup here?
for i, ws := range c.w.xws {
if ws == c.s {
if ws == c.sheet.x {
// add a hyperlink relationship in the worksheet relationships file
c.SetHyperlink(c.w.xwsRels[i].AddHyperlink(url))
return

View File

@ -9,10 +9,10 @@ package spreadsheet
import (
"fmt"
"time"
"regexp"
"strconv"
"strings"
"time"
"github.com/unidoc/unioffice/spreadsheet/formula"
"github.com/unidoc/unioffice/spreadsheet/reference"
@ -220,7 +220,7 @@ func (e *evalContext) LastRow(col string) int {
max := 1
for _, r := range sheet.x.SheetData.Row {
if r.RAttr != nil {
row := Row{sheet.w, sheet.x, r}
row := Row{sheet.w, sheet, r}
l := len(row.Cells())
if l > colIdx {
max = int(row.RowNumber())

View File

@ -904,7 +904,7 @@ func feb29Between(date1, date2 time.Time) bool {
date2S := date2.Unix()
year1 := date1.Year()
mar1year1 := makeDateS(year1, time.March, 1)
if (isLeapYear(year1) && date1S < mar1year1 && date2S >= mar1year1) {
if isLeapYear(year1) && date1S < mar1year1 && date2S >= mar1year1 {
return true
}
var year2 = date2.Year()

View File

@ -8,10 +8,10 @@
package formula
import (
"time"
"math"
"strconv"
"strings"
"time"
)
func init() {

View File

@ -11,9 +11,9 @@ import (
"fmt"
"strings"
"github.com/unidoc/unioffice/internal/mergesort"
"github.com/unidoc/unioffice/internal/wildcard"
"github.com/unidoc/unioffice/spreadsheet/reference"
"github.com/unidoc/unioffice/internal/mergesort"
)
func init() {

View File

@ -9,6 +9,7 @@ package formula
import (
"bytes"
"github.com/unidoc/unioffice/spreadsheet/update"
)

View File

@ -366,7 +366,6 @@ func TestCountIfs(t *testing.T) {
sheet.Cell("B9").SetNumber(2)
sheet.Cell("B10").SetNumber(1)
ctx := sheet.FormulaContext()
runTests(t, ctx, td)

View File

@ -15,7 +15,7 @@ import (
type MergedCell struct {
wb *Workbook
ws *sml.Worksheet
sheet *Sheet
x *sml.CT_MergeCell
}
@ -39,7 +39,7 @@ 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)
return s.sheet.Cell(ref)
}
// couldn't find it, log an error?
return Cell{}

View File

@ -12,8 +12,8 @@ import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"os"
"path/filepath"
"github.com/unidoc/unioffice"
"github.com/unidoc/unioffice/zippkg"

View File

@ -19,7 +19,7 @@ import (
// Row is a row within a spreadsheet.
type Row struct {
w *Workbook
s *sml.Worksheet
sheet *Sheet
x *sml.CT_Row
}
@ -91,7 +91,7 @@ func (r Row) AddCell() Cell {
nextCellID = unioffice.Stringf("%s%d", reference.IndexToColumn(nextIdx), r.RowNumber())
}
c.RAttr = nextCellID
return Cell{r.w, r.s, r.x, c}
return Cell{r.w, r.sheet, r.x, c}
}
// Cells returns a slice of cells. The cells can be manipulated, but appending
@ -116,7 +116,18 @@ func (r Row) Cells() []Cell {
}
}
lastIndex = currentIndex
ret = append(ret, Cell{r.w, r.s, r.x, c})
ret = append(ret, Cell{r.w, r.sheet, r.x, c})
}
return ret
}
// CellsWithEmpty returns a slice of cells including empty ones from the first column to the last one used in the sheet.
// The cells can be manipulated, but appending to the slice will have no effect.
func (r Row) CellsWithEmpty(lastColIdx uint32) []Cell {
ret := []Cell{}
for columnIdx := uint32(0); columnIdx <= lastColIdx; columnIdx++ {
c := r.Cell(reference.IndexToColumn(columnIdx))
ret = append(ret, c)
}
return ret
}
@ -147,7 +158,7 @@ func (r Row) AddNamedCell(col string) Cell {
r.x.C = append(r.x.C[:indexToInsert], append([]*sml.CT_Cell{c}, r.x.C[indexToInsert:]...)...)
}
return Cell{r.w, r.s, r.x, c}
return Cell{r.w, r.sheet, r.x, c}
}
// Cell retrieves or adds a new cell to a row. Col is the column (e.g. 'A', 'B')
@ -155,7 +166,7 @@ func (r Row) Cell(col string) Cell {
name := fmt.Sprintf("%s%d", col, r.RowNumber())
for _, c := range r.x.C {
if c.RAttr != nil && *c.RAttr == name {
return Cell{r.w, r.s, r.x, c}
return Cell{r.w, r.sheet, r.x, c}
}
}
return r.AddNamedCell(col)

View File

@ -31,6 +31,22 @@ type Sheet struct {
x *sml.Worksheet
}
// MaxColumnIdx returns the max used column of the sheet.
func (s Sheet) MaxColumnIdx() uint32 {
maxColumnIdx := uint32(0)
for _, r := range s.Rows() {
cells := r.x.C
if len(cells) > 0 {
lastCell := cells[len(cells)-1]
ref, _ := reference.ParseCellReference(*lastCell.RAttr)
if maxColumnIdx < ref.ColumnIdx {
maxColumnIdx = ref.ColumnIdx
}
}
}
return maxColumnIdx
}
func (s Sheet) IsValid() bool {
return s.x != nil
}
@ -42,11 +58,11 @@ func (s Sheet) X() *sml.Worksheet {
// Row will return a row with a given row number, creating a new row if
// necessary.
func (s Sheet) Row(rowNum uint32) Row {
func (s *Sheet) Row(rowNum uint32) Row {
// see if the row exists
for _, r := range s.x.SheetData.Row {
if r.RAttr != nil && *r.RAttr == rowNum {
return Row{s.w, s.x, r}
return Row{s.w, s, r}
}
}
// create a new row
@ -54,7 +70,7 @@ func (s Sheet) Row(rowNum uint32) Row {
}
// Cell creates or returns a cell given a cell reference of the form 'A10'
func (s Sheet) Cell(cellRef string) Cell {
func (s *Sheet) Cell(cellRef string) Cell {
cref, err := reference.ParseCellReference(cellRef)
if err != nil {
unioffice.Log("error parsing cell reference: %s", err)
@ -66,7 +82,7 @@ func (s Sheet) Cell(cellRef string) Cell {
// AddNumberedRow adds a row with a given row number. If you reuse a row number
// the resulting file will fail validation and fail to open in Office programs. Use
// Row instead which creates a new row or returns an existing row.
func (s Sheet) AddNumberedRow(rowNum uint32) Row {
func (s *Sheet) AddNumberedRow(rowNum uint32) Row {
r := sml.NewCT_Row()
r.RAttr = unioffice.Uint32(rowNum)
s.x.SheetData.Row = append(s.x.SheetData.Row, r)
@ -84,22 +100,22 @@ func (s Sheet) AddNumberedRow(rowNum uint32) Row {
return *l < *r
})
return Row{s.w, s.x, r}
return Row{s.w, s, r}
}
// addNumberedRowFast is a fast path that can be used when adding consecutive
// rows and not skipping any.
func (s Sheet) addNumberedRowFast(rowNum uint32) Row {
func (s *Sheet) addNumberedRowFast(rowNum uint32) Row {
r := sml.NewCT_Row()
r.RAttr = unioffice.Uint32(rowNum)
s.x.SheetData.Row = append(s.x.SheetData.Row, r)
return Row{s.w, s.x, r}
return Row{s.w, s, r}
}
// AddRow adds a new row to a sheet. You can mix this with numbered rows,
// however it will get confusing. You should prefer to use either automatically
// numbered rows with AddRow or manually numbered rows with Row/AddNumberedRow
func (s Sheet) AddRow() Row {
func (s *Sheet) AddRow() Row {
maxRowID := uint32(0)
numRows := uint32(len(s.x.SheetData.Row))
@ -120,7 +136,7 @@ func (s Sheet) AddRow() Row {
// InsertRow inserts a new row into a spreadsheet at a particular row number. This
// row will now be the row number specified, and any rows after it will be renumbed.
func (s Sheet) InsertRow(rowNum int) Row {
func (s *Sheet) InsertRow(rowNum int) Row {
rIdx := uint32(rowNum)
// Renumber every row after the row we're inserting
@ -166,7 +182,7 @@ func (s Sheet) Name() string {
}
// SetName sets the sheet name.
func (s Sheet) SetName(name string) {
func (s *Sheet) SetName(name string) {
s.cts.NameAttr = name
}
@ -182,7 +198,7 @@ func (s Sheet) Validate() error {
return err
}
}
if err := s.cts.Validate(); err != nil {
if err := s.x.Validate(); err != nil {
return err
}
return s.x.Validate()
@ -249,21 +265,21 @@ func (s Sheet) validateMergedCells() error {
// ValidateWithPath validates the sheet passing path informaton for a better
// error message
func (s Sheet) ValidateWithPath(path string) error {
return s.cts.ValidateWithPath(path)
return s.x.ValidateWithPath(path)
}
// Rows returns all of the rows in a sheet.
func (s Sheet) Rows() []Row {
func (s *Sheet) Rows() []Row {
ret := []Row{}
for _, r := range s.x.SheetData.Row {
ret = append(ret, Row{s.w, s.x, r})
ret = append(ret, Row{s.w, s, r})
}
return ret
}
// SetDrawing sets the worksheet drawing. A worksheet can have a reference to a
// single drawing, but the drawing can have many charts.
func (s Sheet) SetDrawing(d Drawing) {
func (s *Sheet) SetDrawing(d Drawing) {
var rel common.Relationships
for i, wks := range s.w.xws {
if wks == s.x {
@ -288,7 +304,7 @@ func (s Sheet) SetDrawing(d Drawing) {
// AddHyperlink adds a hyperlink to a sheet. Adding the hyperlink to the sheet
// and setting it on a cell is more efficient than setting hyperlinks directly
// on a cell.
func (s Sheet) AddHyperlink(url string) common.Hyperlink {
func (s *Sheet) AddHyperlink(url string) common.Hyperlink {
// store the relationships so we don't need to do a lookup here?
for i, ws := range s.w.xws {
if ws == s.x {
@ -318,7 +334,7 @@ func (s Sheet) RangeReference(n string) string {
const autoFilterName = "_xlnm._FilterDatabase"
// ClearAutoFilter removes the autofilters from the sheet.
func (s Sheet) ClearAutoFilter() {
func (s *Sheet) ClearAutoFilter() {
s.x.AutoFilter = nil
sn := "'" + s.Name() + "'!"
// see if we have a defined auto filter name for the sheet
@ -336,7 +352,7 @@ func (s Sheet) ClearAutoFilter() {
// filters that are common for a header row. The RangeRef should be of the form
// "A1:C5" and cover the entire range of cells to be filtered, not just the
// header. SetAutoFilter replaces any existing auto filter on the sheet.
func (s Sheet) SetAutoFilter(rangeRef string) {
func (s *Sheet) SetAutoFilter(rangeRef string) {
// this should have no $ in it
rangeRef = strings.Replace(rangeRef, "$", "", -1)
@ -369,7 +385,7 @@ func (s Sheet) SetAutoFilter(rangeRef string) {
}
// AddMergedCells merges cells within a sheet.
func (s Sheet) AddMergedCells(fromRef, toRef string) MergedCell {
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
@ -383,24 +399,24 @@ func (s Sheet) AddMergedCells(fromRef, toRef string) MergedCell {
s.x.MergeCells.MergeCell = append(s.x.MergeCells.MergeCell, merge)
s.x.MergeCells.CountAttr = unioffice.Uint32(uint32(len(s.x.MergeCells.MergeCell)))
return MergedCell{s.w, s.x, merge}
return MergedCell{s.w, s, merge}
}
// MergedCells returns the merged cell regions within the sheet.
func (s Sheet) MergedCells() []MergedCell {
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})
ret = append(ret, MergedCell{s.w, s, 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) {
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:])
@ -443,7 +459,7 @@ func (s Sheet) Extents() string {
}
// AddConditionalFormatting adds conditional formatting to the sheet.
func (s Sheet) AddConditionalFormatting(cellRanges []string) ConditionalFormatting {
func (s *Sheet) AddConditionalFormatting(cellRanges []string) ConditionalFormatting {
cfmt := sml.NewCT_ConditionalFormatting()
s.x.ConditionalFormatting = append(s.x.ConditionalFormatting, cfmt)
@ -460,7 +476,7 @@ func (s Sheet) AddConditionalFormatting(cellRanges []string) ConditionalFormatti
// can span multiple column indices, this method will return the column that
// applies to a column index if it exists or create a new column that only
// applies to the index passed in otherwise.
func (s Sheet) Column(idx uint32) Column {
func (s *Sheet) Column(idx uint32) Column {
// scan for any existing column that covers this index
for _, colSet := range s.x.Cols {
for _, col := range colSet.Col {
@ -487,7 +503,7 @@ func (s Sheet) Column(idx uint32) Column {
}
// Comments returns the comments for a sheet.
func (s Sheet) Comments() Comments {
func (s *Sheet) Comments() Comments {
for i, wks := range s.w.xws {
if wks == s.x {
if s.w.comments[i] == nil {
@ -518,7 +534,7 @@ func (s Sheet) Comments() Comments {
// the cells on the top,left,right,bottom and four corners. This function
// breaks apart a single border into its components and applies it to cells as
// needed to give the effect of a border applying to multiple cells.
func (s Sheet) SetBorder(cellRange string, border Border) error {
func (s *Sheet) SetBorder(cellRange string, border Border) error {
from, to, err := reference.ParseRangeReference(cellRange)
if err != nil {
return err
@ -605,7 +621,7 @@ func (s Sheet) SetBorder(cellRange string, border Border) error {
}
// AddDataValidation adds a data validation rule to a sheet.
func (s Sheet) AddDataValidation() DataValidation {
func (s *Sheet) AddDataValidation() DataValidation {
if s.x.DataValidations == nil {
s.x.DataValidations = sml.NewCT_DataValidations()
}
@ -860,8 +876,8 @@ func (s *Sheet) Sort(column string, firstRow uint32, order SortOrder) {
cmp := Comparer{Order: order}
sort.Slice(sheetData, func(i, j int) bool {
return cmp.LessRows(column,
Row{s.w, s.x, sheetData[i]},
Row{s.w, s.x, sheetData[j]})
Row{s.w, s, sheetData[i]},
Row{s.w, s, sheetData[j]})
})
// since we probably moved some rows, we need to go and fix up their row

View File

@ -369,3 +369,16 @@ func TestRemoveColumn(t *testing.T) {
}
}
}
func TestCellsWithEmpty(t *testing.T) {
wb := spreadsheet.New()
sheet := wb.AddSheet()
sheet.Cell("A1").SetNumber(1)
sheet.Cell("F2").SetNumber(1)
rows := sheet.Rows()
exp := 6
got := len(rows[0].CellsWithEmpty(sheet.MaxColumnIdx()))
if got != exp {
t.Errorf("expected %d cells in row, got %d", exp, got)
}
}

View File

@ -10,6 +10,7 @@ package update
// UpdateAction is the type for update types constants.
type UpdateAction byte
const (
// UpdateActionRemoveColumn means updating references after removing a column.
UpdateActionRemoveColumn UpdateAction = iota

View File

@ -237,7 +237,8 @@ func (wb *Workbook) CopySheet(ind int, copiedSheetName string) (Sheet, error) {
wb.comments = append(wb.comments, &copiedComments)
}
return Sheet{wb, &copiedSheet, &copiedWs}, nil
sheet := Sheet{wb, &copiedSheet, &copiedWs}
return sheet, nil
}
// CopySheetByName copies the existing sheet with the name `name` and puts its copy with the name `copiedSheetName`.
@ -438,7 +439,8 @@ func (wb *Workbook) Sheets() []Sheet {
ret := []Sheet{}
for i, wks := range wb.xws {
r := wb.x.Sheets.Sheet[i]
ret = append(ret, Sheet{wb, r, wks})
sheet := Sheet{wb, r, wks}
ret = append(ret, sheet)
}
return ret
}