golang excelize how to copy the cell and it's format - excel

I use github.com/xuri/excelize/v2 to process the excel file.
I append many sheets in many excel files into one sheet in one excel.
Below is the sample code.
var mergedRows [][]string
for _, f := range files {
excelPath := folder + "/" + f.Name()
rows := loadXlsx(excelPath, sheetName)
for _, row := range rows[rowOffset:] {
mergedRows = append(mergedRows, row)
}
}
saveXlsx(aggregatedFilePath, sheetName, mergedRows, rowOffset)
...
func loadXlsx(xlsxPath string, sheetName string) [][]string {
f, err := excelize.OpenFile(xlsxPath)
if err != nil {
log.Fatal(err)
}
defer func() {
if err := f.Close(); err != nil{
fmt.Println(err)
}
}()
rows, err := f.GetRows(sheetName)
if err != nil {
log.Fatal(err)
}
return rows
}
func saveXlsx(path string, sheetName string, rows [][]string, rowOffset int) {
f, err := excelize.OpenFile(path)
if err != nil {
log.Fatal(err)
}
defer func() {
if err := f.Close(); err != nil{
fmt.Println(err)
}
}()
index := f.GetSheetIndex(sheetName)
offset := 1
sequence := 1
for _, row := range rows{
row[0] = strconv.Itoa(sequence)
sequence = sequence + 1
offset = offset + 1
axis := "A" + strconv.Itoa(offset)
f.SetSheetRow(sheetName, axis, &row)
}
for index, _ := range rows[0] {
axis, _ := excelize.CoordinatesToCellName(index, 2)
column, _ := excelize.ColumnNumberToName(index)
styleId, _ := f.GetCellStyle(sheetName, axis)
cellType, _ := f.GetCellType(sheetName, axis)
fmt.Println(styleId)
fmt.Println(cellType)
f.SetColStyle(sheetName, column, styleId)
}
f.SetActiveSheet(index)
if err := f.Save(); err != nil {
fmt.Println(err)
}
}
This works, except some data format issues. the number's style is copyed, but not works; the date is copyed, but with wrong value.
In the source file, there has some number with 2 decimal format and shows like 70.12, while in the output file the format is the same but shows like 70.119.
In the source file, there has some date with Y/m/d format and shows like 2022/1/12, while in the output file the format is the same but shows like 01-12-22.

From the manual
func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error)
If the cell format can be applied to the value of the cell, the
applied value will be used, otherwise the original value will be used.
So in my question, rows, err := f.GetRows(sheetName) will copy the date and number value with format, not the original number. The formated value may be convert to non equal value.
The solution is just read the raw value with RawCellValue option true,
rows, err := f.GetRows(sheetName, excelize.Options{RawCellValue:true})
If the format is changed, just apply the style from the original file to the new file.

Related

Golang: how to sum an array of integers as strings and numbers

How to sum an array of integers with mixed types of strings and numbers in GoLang?
Code below errors with "mismatched types int and any" and "cannot initialize 1 variables with 2 values".
Is there something like this JavaScript solution?
Function that sums array numbers (including numbers as strings)
errored code:
import (
"fmt"
"strconv"
)
func main() {
fmt.Println(sum([]any{9, 1, "8", "2"})) // this should output 20
}
func sum(arr []any) int {
n:=0
for _, v := range arr{
temp:=strconv.Atoi(v) //err: cannot initialize 1 variables with 2 values
n+=temp //err: mismatched types int and any
}
return n
}
This also errors:
n:=0
for _, v := range arr{
temp:=0
if reflect.TypeOf(v)=="string"{
temp=strconv.Atoi(v)
} else {
temp=v
}
n+=temp
}
return n
count+=temp //err: mismatched types int and any
Use a type switch to handle integer and string values as appropriate.
temp:=strconv.Atoi(v) //err: cannot initialize 1 variables with 2 values
strconv.Atoi returns two values. Assign the result to two variables. Handle the error return.
Here's the code with the fixes:
func sum(arr []any) int {
n := 0
for _, v := range arr {
switch v := v.(type) {
case int:
n += v
case string:
i, err := strconv.Atoi(v)
if err != nil {
panic(err)
}
n += i
default:
panic(fmt.Sprintf("unsupported type %T", v))
}
}
return n
}
For completeness, here's a version of the function that uses reflection. The type switch version of the function is preferred over reflection.
func sum(arr []any) int {
n := 0
for _, v := range arr {
v := reflect.ValueOf(v)
if v.Kind() == reflect.Int {
n += int(v.Int())
} else if v.Kind() == reflect.String {
i, err := strconv.Atoi(v.String())
if err != nil {
panic(err)
}
n += i
} else {
panic(fmt.Sprintf("unsupported type %s", v.Type()))
}
}
return n
}

Loop through column cells in golang and excelize

I was wondered how to loop over column cells of excel sheet in golang, here is my excel file:
I have tried this piece of code for other reason
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
f, err := excelize.OpenFile("pricematching.xlsx")
if err != nil {
fmt.Println(err)
return
}
// Get all the rows in the sheet1 section.
rows, err := f.GetCellValue("sheet1", "A2")
fmt.Print(rows, "\t")
}
No matter how's your excel file, this is the way to read each cell:
xlsxFile, error := excelize.OpenFile(filePath)
for _, sheetName := range xlsxFile.GetSheetMap() {
for rowIndex, rowValues := range xlsxFile.GetRows(sheetName) {
for columnIndex, columnValue := range rowValues {
// do what ever you want here
}
}
}
Not sure what exactly you need, but this is a simple way to get all cells in a column (if you know how many rows you want to read):
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
f, err := excelize.OpenFile("pricematching.xlsx")
if err != nil {
fmt.Println(err)
return
}
columnName := "A"
sheetName := "sheet1"
totalNumberOfRows := 20
for i := 1; i < totalNumberOfRows; i++ {
cellName := fmt.Sprintf("%s%d", columnName, i)
// fmt.Println(cellName)
cellValue, err := f.GetCellValue(sheetName, cellName)
fmt.Printf("%s\t", cellValue)
}
}

Reading value from xls file but numeric value are not read properly

Numeric value from xls file are not reading properly but the string values are fine
file, _ := xls.Open("test.xls", "utf-8")
sheet := file.GetSheet(0)
for r := 0; r <= (int(sheet.MaxRow)); r++ {
row := sheet.Row(r)
log.Println("column with numeric value: ", row.Col(0))
log.Println("column with string value: ", row.Col(1))
}
test.xls:
123 | test
456 | testing
output:
column with numeric value: #
column with string value: test
column with numeric value: #
column with string value: testing
How can I get numeric value correctly?
On my Ubuntu 18.04,I can open file and print content of second column
package main
import (
"fmt"
"github.com/extrame/xls"
"log"
)
func main() {
if xlFile, err := xls.Open("test.xls", "utf-8"); err == nil {
for i := 0; i < xlFile.NumSheets(); i++ {
sheet := xlFile.GetSheet(i)
fmt.Println(sheet.Name)
for r := 0; r <= (int(sheet.MaxRow)); r++ {
row := sheet.Row(r)
log.Println("column ", row.Col(1))
}
}
}
}
Pay special attention on Col(1) indexing.
Output
Sheet1
2019/04/03 14:28:29 column test
2019/04/03 14:28:29 column testi
2019/04/03 14:28:29 column testing
But for numerical column I got this
Sheet1
2019/04/03 14:27:46 column General
2019/04/03 14:27:46 column General
2019/04/03 14:27:46 column General
I saved the same file as xlsx. This works with tealeg/xlsx package
import (
"fmt"
"github.com/tealeg/xlsx"
)
func main() {
excelFileName := "test.xlsx"
xlFile, err := xlsx.OpenFile(excelFileName)
if err != nil {
err = fmt.Errorf("can not open file!!!", err)
return
}
for _, sheet := range xlFile.Sheets {
for _, row := range sheet.Rows {
for _, cell := range row.Cells {
text := cell.String()
fmt.Println(text)
}
}
}
}
Output
123
test
788
456
testi
999
789
testing
100

Expand a string of slice by delimiter

I want to expand a string of slice by delimiter "/".
For example, expanding the following slice
s := []string{"5/3","9","5/4/1","6"}
Should produce individual slices :
["5","9","5","6"] ["5","9","4","6"] ["5","9","1","6"]
["3","9","5","6"] ["3","9","4","6"] ["3","9","1","6"]
I am pretty much stuck here
var c [][]string{}
s := []string{"5/3","9","5/4/1","6"}
for _, v := range s {
combos := strings.Split(v, "/")
for _, combo := range combos {
}
}
Running time aside, you can achieve this with recursion.
func Perm(digits [][]string) (perm [][]string) {
if len(digits) == 0 || len(digits) == 1 {
return digits
}
nextDigits := Perm(digits[1:])
for _, digit := range digits[0] {
for _, next := range nextDigits {
cat := append([]string{digit}, next...)
perm = append(perm, cat)
}
}
return perm
}
Playground

Golang Increcementing numbers in strings (using runes)

I have a string mixed with characters and numerals, but i want to increment the last character which happens to be a number, here is what i have, it works, but once i reach 10 rune goes to black since 10 decimal is zero, is there a better way to do this?
package main
import (
"fmt"
)
func main() {
str := "version-1.1.0-8"
rStr := []rune(str)
last := rStr[len(rStr)-1]
rStr[len(rStr)-1] = last + 1
}
So this works for str := "version-1.1.0-8" = version-1.1.0-9
str := version-1.1.0-9 = version-1.1.0-
I understand why it is happening, but I dont know how to fix it
Your intention is to increment the number represented by the last rune, so you should do that: parse out that number, increment it as a number, and "re-encode" it into string.
You can't operate on a single rune, as once the number reaches 10, it can only be represented using 2 runes. Another issue is if the last number is 19, incrementing it needs to alter the previous rune (and not adding a new rune).
Parsing the numbers and re-encoding though is much easier than one might think.
You can take advantage of the fmt package's fmt.Sscanf() and fmt.Sprintf() functions. Parsing and re-encoding is just a single function call.
Let's wrap this functionality into a function:
const format = "version-%d.%d.%d-%d"
func incLast(s string) (string, error) {
var a, b, c, d int
if _, err := fmt.Sscanf(s, format, &a, &b, &c, &d); err != nil {
return "", err
}
d++
return fmt.Sprintf(format, a, b, c, d), nil
}
Testing it:
s := "version-1.1.0-8"
for i := 0; i < 13; i++ {
var err error
if s, err = incLast(s); err != nil {
panic(err)
}
fmt.Println(s)
}
Output (try it on the Go Playground):
version-1.1.0-9
version-1.1.0-10
version-1.1.0-11
version-1.1.0-12
version-1.1.0-13
version-1.1.0-14
version-1.1.0-15
version-1.1.0-16
version-1.1.0-17
version-1.1.0-18
version-1.1.0-19
version-1.1.0-20
version-1.1.0-21
Another option would be to just parse and re-encode the last part, and not the complete version text. This is how it would look like:
func incLast2(s string) (string, error) {
i := strings.LastIndexByte(s, '-')
if i < 0 {
return "", fmt.Errorf("invalid input")
}
d, err := strconv.Atoi(s[i+1:])
if err != nil {
return "", err
}
d++
return s[:i+1] + strconv.Itoa(d), nil
}
Testing and output is the same. Try this one on the Go Playground.

Resources