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 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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user