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)
}
}
Related
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.
I want to split a string on a regular expresion, but preserve the matches.
I have tried splitting the string on a regex, but it throws away the matches. I have also tried using this, but I am not very good at translating code from language to language, let alone C#.
re := regexp.MustCompile(`\d`)
array := re.Split("ab1cd2ef3", -1)
I need the value of array to be ["ab", "1", "cd", "2", "ef", "3"], but the value of array is ["ab", "cd", "ef"]. No errors.
The kind of regex support in the link you have pointed out is NOT available in Go regex package. You can read the related discussion.
What you want to achieve (as per the sample given) can be done using regex to match digits or non-digits.
package main
import (
"fmt"
"regexp"
)
func main() {
str := "ab1cd2ef3"
r := regexp.MustCompile(`(\d|[^\d]+)`)
fmt.Println(r.FindAllStringSubmatch(str, -1))
}
Playground: https://play.golang.org/p/L-ElvkDky53
Output:
[[ab ab] [1 1] [cd cd] [2 2] [ef ef] [3 3]]
I don't think this is possible with the current regexp package, but the Split could be easily extended to such behavior.
This should work for your case:
func Split(re *regexp.Regexp, s string, n int) []string {
if n == 0 {
return nil
}
matches := re.FindAllStringIndex(s, n)
strings := make([]string, 0, len(matches))
beg := 0
end := 0
for _, match := range matches {
if n > 0 && len(strings) >= n-1 {
break
}
end = match[0]
if match[1] != 0 {
strings = append(strings, s[beg:end])
}
beg = match[1]
// This also appends the current match
strings = append(strings, s[match[0]:match[1]])
}
if end != len(s) {
strings = append(strings, s[beg:])
}
return strings
}
Dumb solutions. Add separator in the string and split with separator.
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
re := regexp.MustCompile(`\d+`)
input := "ab1cd2ef3"
sep := "|"
indexes := re.FindAllStringIndex(input, -1)
fmt.Println(indexes)
move := 0
for _, v := range indexes {
p1 := v[0] + move
p2 := v[1] + move
input = input[:p1] + sep + input[p1:p2] + sep + input[p2:]
move += 2
}
result := strings.Split(input, sep)
fmt.Println(result)
}
You can use a bufio.Scanner:
package main
import (
"bufio"
"strings"
)
func digit(data []byte, eof bool) (int, []byte, error) {
for i, b := range data {
if '0' <= b && b <= '9' {
if i > 0 {
return i, data[:i], nil
}
return 1, data[:1], nil
}
}
return 0, nil, nil
}
func main() {
s := bufio.NewScanner(strings.NewReader("ab1cd2ef3"))
s.Split(digit)
for s.Scan() {
println(s.Text())
}
}
https://golang.org/pkg/bufio#Scanner.Split
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
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
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.