mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-19 17:24:26 +08:00
import transactions from Feidee Mymoney (Elecloud)
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
// excelOOXMLSheet defines the structure of excel (Office Open XML) file sheet
|
||||
type excelOOXMLSheet struct {
|
||||
sheetName string
|
||||
allData [][]string
|
||||
}
|
||||
|
||||
// ExcelOOXMLFileImportedDataTable defines the structure of excel (Office Open XML) file data table
|
||||
type ExcelOOXMLFileImportedDataTable struct {
|
||||
sheets []*excelOOXMLSheet
|
||||
headerLineColumnNames []string
|
||||
}
|
||||
|
||||
// ExcelOOXMLFileDataRow defines the structure of excel (Office Open XML) file data table row
|
||||
type ExcelOOXMLFileDataRow struct {
|
||||
sheet *excelOOXMLSheet
|
||||
rowData []string
|
||||
rowIndex int
|
||||
}
|
||||
|
||||
// ExcelOOXMLFileDataRowIterator defines the structure of excel (Office Open XML) file data table row iterator
|
||||
type ExcelOOXMLFileDataRowIterator struct {
|
||||
dataTable *ExcelOOXMLFileImportedDataTable
|
||||
currentSheetIndex int
|
||||
currentRowIndexInSheet int
|
||||
}
|
||||
|
||||
// DataRowCount returns the total count of data row
|
||||
func (t *ExcelOOXMLFileImportedDataTable) DataRowCount() int {
|
||||
totalDataRowCount := 0
|
||||
|
||||
for i := 0; i < len(t.sheets); i++ {
|
||||
sheet := t.sheets[i]
|
||||
|
||||
if len(sheet.allData) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
totalDataRowCount += len(sheet.allData) - 1
|
||||
}
|
||||
|
||||
return totalDataRowCount
|
||||
}
|
||||
|
||||
// HeaderColumnNames returns the header column name list
|
||||
func (t *ExcelOOXMLFileImportedDataTable) HeaderColumnNames() []string {
|
||||
return t.headerLineColumnNames
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *ExcelOOXMLFileImportedDataTable) DataRowIterator() datatable.ImportedDataRowIterator {
|
||||
return &ExcelOOXMLFileDataRowIterator{
|
||||
dataTable: t,
|
||||
currentSheetIndex: 0,
|
||||
currentRowIndexInSheet: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
func (r *ExcelOOXMLFileDataRow) ColumnCount() int {
|
||||
return len(r.rowData)
|
||||
}
|
||||
|
||||
// GetData returns the data in the specified column index
|
||||
func (r *ExcelOOXMLFileDataRow) GetData(columnIndex int) string {
|
||||
if columnIndex < 0 || columnIndex >= len(r.rowData) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return r.rowData[columnIndex]
|
||||
}
|
||||
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
func (t *ExcelOOXMLFileDataRowIterator) HasNext() bool {
|
||||
sheets := t.dataTable.sheets
|
||||
|
||||
if t.currentSheetIndex >= len(sheets) {
|
||||
return false
|
||||
}
|
||||
|
||||
currentSheet := sheets[t.currentSheetIndex]
|
||||
|
||||
if t.currentRowIndexInSheet+1 < len(currentSheet.allData) {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := t.currentSheetIndex + 1; i < len(sheets); i++ {
|
||||
sheet := sheets[i]
|
||||
|
||||
if len(sheet.allData) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CurrentRowId returns current index
|
||||
func (t *ExcelOOXMLFileDataRowIterator) CurrentRowId() string {
|
||||
return fmt.Sprintf("table#%d-row#%d", t.currentSheetIndex, t.currentRowIndexInSheet)
|
||||
}
|
||||
|
||||
// Next returns the next imported data row
|
||||
func (t *ExcelOOXMLFileDataRowIterator) Next() datatable.ImportedDataRow {
|
||||
sheets := t.dataTable.sheets
|
||||
currentRowIndexInTable := t.currentRowIndexInSheet
|
||||
|
||||
for i := t.currentSheetIndex; i < len(sheets); i++ {
|
||||
sheet := sheets[i]
|
||||
|
||||
if currentRowIndexInTable+1 < len(sheet.allData) {
|
||||
t.currentRowIndexInSheet++
|
||||
currentRowIndexInTable = t.currentRowIndexInSheet
|
||||
break
|
||||
}
|
||||
|
||||
t.currentSheetIndex++
|
||||
t.currentRowIndexInSheet = 0
|
||||
currentRowIndexInTable = 0
|
||||
}
|
||||
|
||||
if t.currentSheetIndex >= len(sheets) {
|
||||
return nil
|
||||
}
|
||||
|
||||
currentSheet := sheets[t.currentSheetIndex]
|
||||
|
||||
if t.currentRowIndexInSheet >= len(currentSheet.allData) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ExcelOOXMLFileDataRow{
|
||||
sheet: currentSheet,
|
||||
rowData: currentSheet.allData[t.currentRowIndexInSheet],
|
||||
rowIndex: t.currentRowIndexInSheet,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewExcelOOXMLFileImportedDataTable returns excel (Office Open XML) data table by file binary data
|
||||
func CreateNewExcelOOXMLFileImportedDataTable(data []byte) (*ExcelOOXMLFileImportedDataTable, error) {
|
||||
reader := bytes.NewReader(data)
|
||||
file, err := excelize.OpenReader(reader)
|
||||
|
||||
defer file.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sheetNames := file.GetSheetList()
|
||||
var headerRowItems []string
|
||||
var sheets []*excelOOXMLSheet
|
||||
|
||||
for i := 0; i < len(sheetNames); i++ {
|
||||
sheetName := sheetNames[i]
|
||||
allData, err := file.GetRows(sheetName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if allData == nil || len(allData) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
row := allData[0]
|
||||
|
||||
if i == 0 {
|
||||
for j := 0; j < len(row); j++ {
|
||||
headerItem := row[j]
|
||||
|
||||
if headerItem == "" {
|
||||
break
|
||||
}
|
||||
|
||||
headerRowItems = append(headerRowItems, headerItem)
|
||||
}
|
||||
} else {
|
||||
for j := 0; j < min(len(row), len(headerRowItems)); j++ {
|
||||
headerItem := row[j]
|
||||
|
||||
if headerItem != headerRowItems[j] {
|
||||
return nil, errs.ErrFieldsInMultiTableAreDifferent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sheets = append(sheets, &excelOOXMLSheet{
|
||||
sheetName: sheetName,
|
||||
allData: allData,
|
||||
})
|
||||
}
|
||||
|
||||
return &ExcelOOXMLFileImportedDataTable{
|
||||
sheets: sheets,
|
||||
headerLineColumnNames: headerRowItems,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
func TestExcelOOXMLFileImportedDataTableDataRowCount(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileImportedDataTableDataRowCount_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 5, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileImportedDataTableDataRowCount_OnlyHeaderLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/only_one_row_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileImportedDataTableDataRowCount_EmptyContent(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, datatable.DataRowCount())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileImportedDataTableHeaderColumnNames(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
assert.EqualValues(t, []string{"A1", "B1", "C1"}, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileImportedDataTableHeaderColumnNames_EmptyContent(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
assert.Nil(t, datatable.HeaderColumnNames())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileDataRowIterator(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 3
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 4
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileDataRowIterator_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 1 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 1 data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 3 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 5 data row 1
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.True(t, iterator.HasNext())
|
||||
|
||||
// sheet 5 data row 2
|
||||
assert.NotNil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileDataRowIterator_OnlyHeaderLine(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/only_one_row_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 1
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 2
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileDataRowIterator_EmptyContent(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
iterator := datatable.DataRowIterator()
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 1
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
|
||||
// not existed data row 2
|
||||
assert.Nil(t, iterator.Next())
|
||||
assert.False(t, iterator.HasNext())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileDataRowColumnCount(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
assert.EqualValues(t, 3, row1.ColumnCount())
|
||||
|
||||
row2 := iterator.Next()
|
||||
assert.EqualValues(t, 3, row2.ColumnCount())
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileDataRowGetData(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
assert.Equal(t, "A2", row1.GetData(0))
|
||||
assert.Equal(t, "B2", row1.GetData(1))
|
||||
assert.Equal(t, "C2", row1.GetData(2))
|
||||
|
||||
row2 := iterator.Next()
|
||||
assert.Equal(t, "A3", row2.GetData(0))
|
||||
assert.Equal(t, "B3", row2.GetData(1))
|
||||
assert.Equal(t, "C3", row2.GetData(2))
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileDataRowGetData_GetNotExistedColumnData(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
row1 := iterator.Next()
|
||||
assert.Equal(t, "", row1.GetData(3))
|
||||
}
|
||||
|
||||
func TestExcelOOXMLFileDataRowGetData_MultipleSheets(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
iterator := datatable.DataRowIterator()
|
||||
|
||||
sheet1Row1 := iterator.Next()
|
||||
assert.Equal(t, "1-A2", sheet1Row1.GetData(0))
|
||||
assert.Equal(t, "1-B2", sheet1Row1.GetData(1))
|
||||
assert.Equal(t, "1-C2", sheet1Row1.GetData(2))
|
||||
|
||||
sheet1Row2 := iterator.Next()
|
||||
assert.Equal(t, "1-A3", sheet1Row2.GetData(0))
|
||||
assert.Equal(t, "1-B3", sheet1Row2.GetData(1))
|
||||
assert.Equal(t, "1-C3", sheet1Row2.GetData(2))
|
||||
|
||||
// skip empty sheet2
|
||||
|
||||
sheet3Row1 := iterator.Next()
|
||||
assert.Equal(t, "3-A2", sheet3Row1.GetData(0))
|
||||
assert.Equal(t, "3-B2", sheet3Row1.GetData(1))
|
||||
assert.Equal(t, "", sheet3Row1.GetData(2))
|
||||
|
||||
// skip no data row sheet4
|
||||
|
||||
sheet5Row1 := iterator.Next()
|
||||
assert.Equal(t, "5-A2", sheet5Row1.GetData(0))
|
||||
assert.Equal(t, "5-B2", sheet5Row1.GetData(1))
|
||||
assert.Equal(t, "5-C2", sheet5Row1.GetData(2))
|
||||
|
||||
sheet5Row2 := iterator.Next()
|
||||
assert.Equal(t, "5-A3", sheet5Row2.GetData(0))
|
||||
assert.Equal(t, "5-B3", sheet5Row2.GetData(1))
|
||||
assert.Equal(t, "5-C3", sheet5Row2.GetData(2))
|
||||
}
|
||||
|
||||
func TestCreateNewExcelOOXMLFileImportedDataTable_MultipleSheetsWithDifferentHeaders(t *testing.T) {
|
||||
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_with_different_header_row_excel_file.xlsx")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = CreateNewExcelOOXMLFileImportedDataTable(testdata)
|
||||
assert.EqualError(t, err, errs.ErrFieldsInMultiTableAreDifferent.Message)
|
||||
}
|
||||
Reference in New Issue
Block a user