-
Notifications
You must be signed in to change notification settings - Fork 4
/
table.go
153 lines (140 loc) · 3.8 KB
/
table.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// table provides a struct to handle tabular data.
package table
import (
"encoding/csv"
"os"
"strconv"
"github.com/grokify/mogo/errors/errorsutil"
)
// Table is useful for working on CSV data. It stores records as `[]string` with typed
// formatting information per-column to facilitate transformations.
type Table struct {
Name string
Columns Columns
Rows [][]string
RowsFloat64 [][]float64
IsFloat64 bool
FormatMap map[int]string
FormatFunc func(val string, colIdx uint) (any, error)
FormatAutoLink bool
BackgroundColorFunc func(colIdx, rowIdx uint) string
ID string
Class string
Style string
}
// NewTable returns a new empty `Table` struct with slices and maps set to empty (non-nil) values.
func NewTable(name string) Table {
return Table{
Name: name,
Columns: []string{},
Rows: [][]string{},
FormatMap: map[int]string{}}
}
// LoadMergedRows is used to load data including both column names and rows from `[][]string` sources
// like `csv.ReadAll()`.
func (tbl *Table) LoadMergedRows(data [][]string) {
if len(data) == 0 {
return
}
tbl.Columns = data[0]
if len(data) > 1 {
tbl.Rows = data[1:]
}
}
func (tbl *Table) UpsertRowColumnValue(rowIdx, colIdx uint, value string) {
rowIdxInt := int(rowIdx)
colIdxInt := int(colIdx)
for rowIdxInt < len(tbl.Rows)-1 {
tbl.Rows = append(tbl.Rows, []string{})
}
row := tbl.Rows[rowIdxInt]
for colIdxInt < len(row)-1 {
row = append(row, "")
}
row[colIdxInt] = value
tbl.Rows[rowIdxInt] = row
}
// IsWellFormed returns true when the number of columns equals
// the length of each row. If columns is empty, the length of the
// first row is used for comparison.
func (tbl *Table) IsWellFormed() (isWellFormed bool, columnCount int, mismatchRows []int) {
isWellFormed = true
columnCount = len(tbl.Columns)
if !tbl.IsFloat64 {
if len(tbl.Rows) == 0 {
return isWellFormed, columnCount, []int{}
}
for i, row := range tbl.Rows {
if i == 0 && columnCount == 0 {
columnCount = len(row)
} else if len(row) != columnCount {
isWellFormed = false
mismatchRows = append(mismatchRows, i)
}
}
} else {
if len(tbl.RowsFloat64) == 0 {
return isWellFormed, columnCount, []int{}
}
for i, row := range tbl.RowsFloat64 {
if i == 0 && columnCount == 0 {
columnCount = len(row)
} else if len(row) != columnCount {
isWellFormed = false
mismatchRows = append(mismatchRows, i)
}
}
}
return
}
// BuildFloat64 populates `RowsFloat64` from `Rows`. It is an experimental feature for machine learning.
// Most features use `Rows`.
func (tbl *Table) BuildFloat64(skipEmpty bool) error {
for i, row := range tbl.Rows {
if skipEmpty && len(row) == 0 {
continue
}
var rowFloat64 []float64
for j, v := range row {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return errorsutil.Wrapf(err, "cannot parse float on row [%d] col [%d] with value [%s]", i, j, v)
}
rowFloat64 = append(rowFloat64, f)
}
tbl.RowsFloat64 = append(tbl.RowsFloat64, rowFloat64)
}
tbl.IsFloat64 = true
return nil
}
func (tbl *Table) WriteXLSX(path, sheetname string) error {
tbl.Name = sheetname
return WriteXLSX(path, []*Table{tbl})
}
func (tbl *Table) WriteCSV(path string) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
writer := csv.NewWriter(file)
if len(tbl.Columns) > 0 {
err = writer.Write(tbl.Columns)
if err != nil {
return err
}
}
err = writer.WriteAll(tbl.Rows)
if err != nil {
return err
}
writer.Flush()
return writer.Error()
}
func (tbl *Table) ToSliceMSS() []map[string]string {
slice := []map[string]string{}
for _, row := range tbl.Rows {
slice = append(slice, tbl.Columns.RowMap(row, false))
}
return slice
}