From 2c454f001efb6974b8c8a4fe49cb57d4dc7b7b53 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Wed, 26 Nov 2025 23:57:54 +0800 Subject: [PATCH] support importing amounts that use non-breaking space (NBSP), narrow no-break space (NNBSP) or figure space as digit grouping symbol when importing delimiter-separated values file / data (#361) --- ...transaction_data_dsv_file_importer_test.go | 56 +++++++++++++++++++ ...ustom_transaction_plain_text_data_table.go | 6 ++ src/core/numeral.ts | 4 +- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/pkg/converters/dsv/custom_transaction_data_dsv_file_importer_test.go b/pkg/converters/dsv/custom_transaction_data_dsv_file_importer_test.go index 208d4293..c719bcb4 100644 --- a/pkg/converters/dsv/custom_transaction_data_dsv_file_importer_test.go +++ b/pkg/converters/dsv/custom_transaction_data_dsv_file_importer_test.go @@ -874,6 +874,62 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) { assert.Equal(t, int64(35), allNewTransactions[3].RelatedAccountAmount) } +func TestCustomTransactionDataDsvFileImporter_ParseAmountWithSpaceDigitGroupingSymbol(t *testing.T) { + columnIndexMapping := map[datatable.TransactionDataTableColumn]int{ + datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0, + datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1, + datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2, + } + transactionTypeMapping := map[string]models.TransactionType{ + "E": models.TRANSACTION_TYPE_EXPENSE, + } + importer, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", " ", "", "", "") + assert.Nil(t, err) + + context := core.NewNullContext() + + user := &models.User{ + Uid: 1234567890, + DefaultCurrency: "CNY", + } + + // normal space + allNewTransactions, _, _, _, _, _, err := importer.ParseImportedData(context, user, []byte( + "2024-09-01 00:00:00,E,1 234,\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) + + assert.Nil(t, err) + + assert.Equal(t, 1, len(allNewTransactions)) + assert.Equal(t, int64(123400), allNewTransactions[0].Amount) + + // no-break space (NBSP) + allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( + "2024-09-01 00:00:00,E,1 234,\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) + + assert.Nil(t, err) + + assert.Equal(t, 1, len(allNewTransactions)) + assert.Equal(t, int64(123400), allNewTransactions[0].Amount) + + // narrow no-break space (NNBSP) + allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( + "2024-09-01 00:00:00,E,1 234,\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) + + assert.Nil(t, err) + + assert.Equal(t, 1, len(allNewTransactions)) + assert.Equal(t, int64(123400), allNewTransactions[0].Amount) + + // figure space + allNewTransactions, _, _, _, _, _, err = importer.ParseImportedData(context, user, []byte( + "2024-09-01 00:00:00,E,1 234,\n"), 0, converter.DefaultImporterOptions, nil, nil, nil, nil, nil) + + assert.Nil(t, err) + + assert.Equal(t, 1, len(allNewTransactions)) + assert.Equal(t, int64(123400), allNewTransactions[0].Amount) +} + func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) { columnIndexMapping := map[datatable.TransactionDataTableColumn]int{ datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0, diff --git a/pkg/converters/dsv/custom_transaction_plain_text_data_table.go b/pkg/converters/dsv/custom_transaction_plain_text_data_table.go index c5528f51..f8ac3cfb 100644 --- a/pkg/converters/dsv/custom_transaction_plain_text_data_table.go +++ b/pkg/converters/dsv/custom_transaction_plain_text_data_table.go @@ -215,6 +215,12 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user func (t *customPlainTextDataRowIterator) parseAmount(ctx core.Context, amountValue string) (string, error) { if t.transactionDataTable.amountDigitGroupingSymbol != "" { amountValue = strings.ReplaceAll(amountValue, t.transactionDataTable.amountDigitGroupingSymbol, "") + + if t.transactionDataTable.amountDigitGroupingSymbol == " " { + amountValue = strings.ReplaceAll(amountValue, "\u00A0", "") // No-Break Space (NBSP) + amountValue = strings.ReplaceAll(amountValue, "\u202F", "") // Narrow No-Break Space (NNBSP) + amountValue = strings.ReplaceAll(amountValue, "\u2007", "") // Figure Space + } } if t.transactionDataTable.amountDecimalSeparator != "" && t.transactionDataTable.amountDecimalSeparator != "." { diff --git a/src/core/numeral.ts b/src/core/numeral.ts index 49114cc0..57f6d7c7 100644 --- a/src/core/numeral.ts +++ b/src/core/numeral.ts @@ -363,8 +363,8 @@ export class KnownAmountFormat { public static readonly CommaDecimalSeparator = new KnownAmountFormat('1234,56', DecimalSeparator.Comma, undefined, /^-?[0-9]+(,[0-9]+)?$/); public static readonly DotDecimalSeparatorWithCommaGroupingSymbol = new KnownAmountFormat('1,234.56', DecimalSeparator.Dot, DigitGroupingSymbol.Comma, /^-?([0-9]+,)*[0-9]+(\.[0-9]+)?$/); public static readonly CommaDecimalSeparatorWithDotGroupingSymbol = new KnownAmountFormat('1.234,56', DecimalSeparator.Comma, DigitGroupingSymbol.Dot, /^-?([0-9]+\.)*[0-9]+(,[0-9]+)?$/); - public static readonly DotDecimalSeparatorWithSpaceGroupingSymbol = new KnownAmountFormat('1 234.56', DecimalSeparator.Dot, DigitGroupingSymbol.Space, /^-?([0-9]+ )*[0-9]+(\.[0-9]+)?$/); - public static readonly CommaDecimalSeparatorWithSpaceGroupingSymbol = new KnownAmountFormat('1 234,56', DecimalSeparator.Comma, DigitGroupingSymbol.Space, /^-?([0-9]+ )*[0-9]+(,[0-9]+)?$/); + public static readonly DotDecimalSeparatorWithSpaceGroupingSymbol = new KnownAmountFormat('1 234.56', DecimalSeparator.Dot, DigitGroupingSymbol.Space, /^-?([0-9]+([    ]))*[0-9]+(\.[0-9]+)?$/); + public static readonly CommaDecimalSeparatorWithSpaceGroupingSymbol = new KnownAmountFormat('1 234,56', DecimalSeparator.Comma, DigitGroupingSymbol.Space, /^-?([0-9]+([    ]))*[0-9]+(,[0-9]+)?$/); public static readonly DotDecimalSeparatorWithApostropheGroupingSymbol = new KnownAmountFormat('1\'234.56', DecimalSeparator.Dot, DigitGroupingSymbol.Apostrophe, /^-?([0-9]+')*[0-9]+(\.[0-9]+)?$/); public static readonly CommaDecimalSeparatorWithApostropheGroupingSymbol = new KnownAmountFormat('1\'234,56', DecimalSeparator.Comma, DigitGroupingSymbol.Apostrophe, /^-?([0-9]+')*[0-9]+(,[0-9]+)?$/);