improve the compatibility of qif files
This commit is contained in:
@@ -19,10 +19,12 @@ const qifCreditCardTransactionHeader = "!Type:CCard"
|
||||
const qifAssetAccountTransactionHeader = "!Type:Oth A"
|
||||
const qifLiabilityAccountTransactionHeader = "!Type:Oth L"
|
||||
const qifMemorizedTransactionHeader = "!Type:Memorized"
|
||||
const qifMemorisedTransactionHeader = "!Type:Memorised"
|
||||
const qifInvestmentTransactionHeader = "!Type:Invst"
|
||||
const qifAccountHeader = "!Account"
|
||||
const qifCategoryHeader = "!Type:Cat"
|
||||
const qifClassHeader = "!Type:Class"
|
||||
const qifTypeHeaderPrefix = "!Type:"
|
||||
|
||||
const qifEntryStartRune = '!'
|
||||
const qifEntryEnd = '^'
|
||||
@@ -57,17 +59,24 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
|
||||
return nil, errs.ErrInvalidQIFFile
|
||||
}
|
||||
|
||||
line = strings.TrimRight(line, " ")
|
||||
|
||||
if line == qifBankTransactionHeader ||
|
||||
line == qifCashTransactionHeader ||
|
||||
line == qifCreditCardTransactionHeader ||
|
||||
line == qifAssetAccountTransactionHeader ||
|
||||
line == qifLiabilityAccountTransactionHeader ||
|
||||
line == qifMemorizedTransactionHeader ||
|
||||
line == qifMemorisedTransactionHeader ||
|
||||
line == qifInvestmentTransactionHeader ||
|
||||
line == qifAccountHeader ||
|
||||
line == qifCategoryHeader ||
|
||||
line == qifClassHeader {
|
||||
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 {
|
||||
log.Warnf(ctx, "[qif_data_reader.read] read unsupported entry header line \"%s\" and skip this line", line)
|
||||
continue
|
||||
@@ -81,7 +90,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
|
||||
currentEntryHeader == qifCreditCardTransactionHeader ||
|
||||
currentEntryHeader == qifAssetAccountTransactionHeader ||
|
||||
currentEntryHeader == qifLiabilityAccountTransactionHeader {
|
||||
transactionData, err := r.parseTransaction(ctx, entryData)
|
||||
transactionData, err := r.parseTransaction(ctx, entryData, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -104,7 +113,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
|
||||
} else if currentEntryHeader == qifLiabilityAccountTransactionHeader {
|
||||
data.liabilityAccountTransactions = append(data.liabilityAccountTransactions, transactionData)
|
||||
}
|
||||
} else if currentEntryHeader == qifMemorizedTransactionHeader {
|
||||
} else if currentEntryHeader == qifMemorizedTransactionHeader || currentEntryHeader == qifMemorisedTransactionHeader {
|
||||
transactionData, err := r.parseMemorizedTransaction(ctx, entryData)
|
||||
|
||||
if err != nil {
|
||||
@@ -182,7 +191,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -219,10 +228,12 @@ func (r *qifDataReader) parseTransaction(ctx core.Context, data []string) (*qifT
|
||||
} else if line[0] == '$' {
|
||||
transactionData.subTransactionAmount = append(transactionData.subTransactionAmount, line[1:])
|
||||
} else {
|
||||
if !ignoreUnknown {
|
||||
log.Warnf(ctx, "[qif_data_reader.parseTransaction] read unsupported line \"%s\" and skip this line", line)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transactionData, nil
|
||||
}
|
||||
@@ -232,7 +243,7 @@ func (r *qifDataReader) parseMemorizedTransaction(ctx core.Context, data []strin
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
baseTransactionData, err := r.parseTransaction(ctx, data)
|
||||
baseTransactionData, err := r.parseTransaction(ctx, data, true)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -250,6 +261,13 @@ func (r *qifDataReader) parseMemorizedTransaction(ctx core.Context, data []strin
|
||||
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 == string(qifCheckTransactionType) {
|
||||
transactionData.transactionType = qifCheckTransactionType
|
||||
|
||||
@@ -197,6 +197,54 @@ func TestQifDataReaderParse_EmptyEntry(t *testing.T) {
|
||||
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) {
|
||||
reader := &qifDataReader{
|
||||
allLines: []string{
|
||||
@@ -238,7 +286,7 @@ func TestQifDataReaderParseTransaction_SupportedFields(t *testing.T) {
|
||||
"SPart2 Category",
|
||||
"EPart2 Memo",
|
||||
"$-23.45",
|
||||
})
|
||||
}, false)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "2024/10/12", actualData.date)
|
||||
@@ -439,7 +487,7 @@ func TestQifDataReaderParse_UnsupportedFieldsOrValues(t *testing.T) {
|
||||
"ZTest",
|
||||
"CZ",
|
||||
"",
|
||||
})
|
||||
}, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, qifClearedStatusUnreconciled, actualTransactionData.clearedStatus)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user