improve the compatibility of qif files

This commit is contained in:
MaysWind
2024-10-20 15:56:19 +08:00
parent 7162ce4a77
commit 4b239030c5
2 changed files with 74 additions and 8 deletions
+24 -6
View File
@@ -19,10 +19,12 @@ const qifCreditCardTransactionHeader = "!Type:CCard"
const qifAssetAccountTransactionHeader = "!Type:Oth A" const qifAssetAccountTransactionHeader = "!Type:Oth A"
const qifLiabilityAccountTransactionHeader = "!Type:Oth L" const qifLiabilityAccountTransactionHeader = "!Type:Oth L"
const qifMemorizedTransactionHeader = "!Type:Memorized" const qifMemorizedTransactionHeader = "!Type:Memorized"
const qifMemorisedTransactionHeader = "!Type:Memorised"
const qifInvestmentTransactionHeader = "!Type:Invst" const qifInvestmentTransactionHeader = "!Type:Invst"
const qifAccountHeader = "!Account" const qifAccountHeader = "!Account"
const qifCategoryHeader = "!Type:Cat" const qifCategoryHeader = "!Type:Cat"
const qifClassHeader = "!Type:Class" const qifClassHeader = "!Type:Class"
const qifTypeHeaderPrefix = "!Type:"
const qifEntryStartRune = '!' const qifEntryStartRune = '!'
const qifEntryEnd = '^' const qifEntryEnd = '^'
@@ -57,17 +59,24 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
return nil, errs.ErrInvalidQIFFile return nil, errs.ErrInvalidQIFFile
} }
line = strings.TrimRight(line, " ")
if line == qifBankTransactionHeader || if line == qifBankTransactionHeader ||
line == qifCashTransactionHeader || line == qifCashTransactionHeader ||
line == qifCreditCardTransactionHeader || line == qifCreditCardTransactionHeader ||
line == qifAssetAccountTransactionHeader || line == qifAssetAccountTransactionHeader ||
line == qifLiabilityAccountTransactionHeader || line == qifLiabilityAccountTransactionHeader ||
line == qifMemorizedTransactionHeader || line == qifMemorizedTransactionHeader ||
line == qifMemorisedTransactionHeader ||
line == qifInvestmentTransactionHeader || line == qifInvestmentTransactionHeader ||
line == qifAccountHeader || line == qifAccountHeader ||
line == qifCategoryHeader || line == qifCategoryHeader ||
line == qifClassHeader { line == qifClassHeader {
currentEntryHeader = line currentEntryHeader = line
} else if strings.Index(line, qifTypeHeaderPrefix) == 0 {
currentEntryHeader = line
log.Warnf(ctx, "[qif_data_reader.read] read unsupported entry header line \"%s\" and skip the following entries", line)
continue
} else { } else {
log.Warnf(ctx, "[qif_data_reader.read] read unsupported entry header line \"%s\" and skip this line", line) log.Warnf(ctx, "[qif_data_reader.read] read unsupported entry header line \"%s\" and skip this line", line)
continue continue
@@ -81,7 +90,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
currentEntryHeader == qifCreditCardTransactionHeader || currentEntryHeader == qifCreditCardTransactionHeader ||
currentEntryHeader == qifAssetAccountTransactionHeader || currentEntryHeader == qifAssetAccountTransactionHeader ||
currentEntryHeader == qifLiabilityAccountTransactionHeader { currentEntryHeader == qifLiabilityAccountTransactionHeader {
transactionData, err := r.parseTransaction(ctx, entryData) transactionData, err := r.parseTransaction(ctx, entryData, false)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -104,7 +113,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
} else if currentEntryHeader == qifLiabilityAccountTransactionHeader { } else if currentEntryHeader == qifLiabilityAccountTransactionHeader {
data.liabilityAccountTransactions = append(data.liabilityAccountTransactions, transactionData) data.liabilityAccountTransactions = append(data.liabilityAccountTransactions, transactionData)
} }
} else if currentEntryHeader == qifMemorizedTransactionHeader { } else if currentEntryHeader == qifMemorizedTransactionHeader || currentEntryHeader == qifMemorisedTransactionHeader {
transactionData, err := r.parseMemorizedTransaction(ctx, entryData) transactionData, err := r.parseMemorizedTransaction(ctx, entryData)
if err != nil { if err != nil {
@@ -182,7 +191,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
return data, nil return data, nil
} }
func (r *qifDataReader) parseTransaction(ctx core.Context, data []string) (*qifTransactionData, error) { func (r *qifDataReader) parseTransaction(ctx core.Context, data []string, ignoreUnknown bool) (*qifTransactionData, error) {
if len(data) < 1 { if len(data) < 1 {
return nil, nil return nil, nil
} }
@@ -219,8 +228,10 @@ func (r *qifDataReader) parseTransaction(ctx core.Context, data []string) (*qifT
} else if line[0] == '$' { } else if line[0] == '$' {
transactionData.subTransactionAmount = append(transactionData.subTransactionAmount, line[1:]) transactionData.subTransactionAmount = append(transactionData.subTransactionAmount, line[1:])
} else { } else {
log.Warnf(ctx, "[qif_data_reader.parseTransaction] read unsupported line \"%s\" and skip this line", line) if !ignoreUnknown {
continue log.Warnf(ctx, "[qif_data_reader.parseTransaction] read unsupported line \"%s\" and skip this line", line)
continue
}
} }
} }
@@ -232,7 +243,7 @@ func (r *qifDataReader) parseMemorizedTransaction(ctx core.Context, data []strin
return nil, nil return nil, nil
} }
baseTransactionData, err := r.parseTransaction(ctx, data) baseTransactionData, err := r.parseTransaction(ctx, data, true)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -250,6 +261,13 @@ func (r *qifDataReader) parseMemorizedTransaction(ctx core.Context, data []strin
continue continue
} }
// these lines has been already processed in parseTransaction
if line[0] == 'D' || line[0] == 'T' || line[0] == 'C' || line[0] == 'N' ||
line[0] == 'P' || line[0] == 'M' || line[0] == 'A' || line[0] == 'L' ||
line[0] == 'S' || line[0] == 'E' || line[0] == '$' {
continue
}
if line[0] == 'K' { if line[0] == 'K' {
if line == string(qifCheckTransactionType) { if line == string(qifCheckTransactionType) {
transactionData.transactionType = qifCheckTransactionType transactionData.transactionType = qifCheckTransactionType
+50 -2
View File
@@ -197,6 +197,54 @@ func TestQifDataReaderParse_EmptyEntry(t *testing.T) {
assert.Equal(t, 0, len(actualData.classes)) assert.Equal(t, 0, len(actualData.classes))
} }
func TestQifDataReaderParse_UnsupportedEntryHeader(t *testing.T) {
reader := &qifDataReader{
allLines: []string{
"!Type:Bank",
"D2024/10/9",
"T-123.45",
"^",
"!Type:Unknown",
"D2024/10/11",
"T100.00",
"^",
},
}
context := core.NewNullContext()
actualData, err := reader.read(context)
assert.Nil(t, err)
assert.Equal(t, 1, len(actualData.bankAccountTransactions))
assert.Equal(t, "2024/10/9", actualData.bankAccountTransactions[0].date)
assert.Equal(t, "-123.45", actualData.bankAccountTransactions[0].amount)
}
func TestQifDataReaderParse_UnsupportedLine(t *testing.T) {
reader := &qifDataReader{
allLines: []string{
"!Type:Bank",
"D2024/10/9",
"T-123.45",
"^",
"!Option:Unknown",
"D2024/10/11",
"T100.00",
"^",
},
}
context := core.NewNullContext()
actualData, err := reader.read(context)
assert.Nil(t, err)
assert.Equal(t, 2, len(actualData.bankAccountTransactions))
assert.Equal(t, "2024/10/9", actualData.bankAccountTransactions[0].date)
assert.Equal(t, "-123.45", actualData.bankAccountTransactions[0].amount)
assert.Equal(t, "2024/10/11", actualData.bankAccountTransactions[1].date)
assert.Equal(t, "100.00", actualData.bankAccountTransactions[1].amount)
}
func TestQifDataReaderParse_NewEntryHeaderAfterUnclosedEntry(t *testing.T) { func TestQifDataReaderParse_NewEntryHeaderAfterUnclosedEntry(t *testing.T) {
reader := &qifDataReader{ reader := &qifDataReader{
allLines: []string{ allLines: []string{
@@ -238,7 +286,7 @@ func TestQifDataReaderParseTransaction_SupportedFields(t *testing.T) {
"SPart2 Category", "SPart2 Category",
"EPart2 Memo", "EPart2 Memo",
"$-23.45", "$-23.45",
}) }, false)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "2024/10/12", actualData.date) assert.Equal(t, "2024/10/12", actualData.date)
@@ -439,7 +487,7 @@ func TestQifDataReaderParse_UnsupportedFieldsOrValues(t *testing.T) {
"ZTest", "ZTest",
"CZ", "CZ",
"", "",
}) }, false)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, qifClearedStatusUnreconciled, actualTransactionData.clearedStatus) assert.Equal(t, qifClearedStatusUnreconciled, actualTransactionData.clearedStatus)