code refactor

This commit is contained in:
MaysWind
2024-11-02 22:47:33 +08:00
parent f2e89da724
commit 0e062ed065
8 changed files with 1252 additions and 1043 deletions
+48 -45
View File
@@ -1,6 +1,8 @@
package ofx package ofx
import ( import (
"encoding/xml"
"github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/models"
) )
@@ -69,9 +71,10 @@ var ofxTransactionTypeMapping = map[ofxTransactionType]models.TransactionType{
// ofxFile represents the struct of open financial exchange (ofx) file // ofxFile represents the struct of open financial exchange (ofx) file
type ofxFile struct { type ofxFile struct {
XMLName xml.Name `xml:"OFX"`
FileHeader *ofxFileHeader FileHeader *ofxFileHeader
BankMessageResponseV1 *ofxBankMessageResponseV1 BankMessageResponseV1 *ofxBankMessageResponseV1 `xml:"BANKMSGSRSV1"`
CreditCardMessageResponseV1 *ofxCreditCardMessageResponseV1 CreditCardMessageResponseV1 *ofxCreditCardMessageResponseV1 `xml:"CREDITCARDMSGSRSV1"`
} }
// ofxFileHeader represents the struct of open financial exchange (ofx) file header // ofxFileHeader represents the struct of open financial exchange (ofx) file header
@@ -85,101 +88,101 @@ type ofxFileHeader struct {
// ofxBankMessageResponseV1 represents the struct of open financial exchange (ofx) bank message response v1 // ofxBankMessageResponseV1 represents the struct of open financial exchange (ofx) bank message response v1
type ofxBankMessageResponseV1 struct { type ofxBankMessageResponseV1 struct {
StatementTransactionResponse *ofxBankStatementTransactionResponse StatementTransactionResponse *ofxBankStatementTransactionResponse `xml:"STMTTRNRS"`
} }
// ofxCreditCardMessageResponseV1 represents the struct of open financial exchange (ofx) credit card message response v1 // ofxCreditCardMessageResponseV1 represents the struct of open financial exchange (ofx) credit card message response v1
type ofxCreditCardMessageResponseV1 struct { type ofxCreditCardMessageResponseV1 struct {
StatementTransactionResponse *ofxCreditCardStatementTransactionResponse StatementTransactionResponse *ofxCreditCardStatementTransactionResponse `xml:"CCSTMTTRNRS"`
} }
// ofxBankStatementTransactionResponse represents the struct of open financial exchange (ofx) bank statement transaction response // ofxBankStatementTransactionResponse represents the struct of open financial exchange (ofx) bank statement transaction response
type ofxBankStatementTransactionResponse struct { type ofxBankStatementTransactionResponse struct {
StatementResponse *ofxBankStatementResponse StatementResponse *ofxBankStatementResponse `xml:"STMTRS"`
} }
// ofxCreditCardStatementTransactionResponse represents the struct of open financial exchange (ofx) credit card statement transaction response // ofxCreditCardStatementTransactionResponse represents the struct of open financial exchange (ofx) credit card statement transaction response
type ofxCreditCardStatementTransactionResponse struct { type ofxCreditCardStatementTransactionResponse struct {
StatementResponse *ofxCreditCardStatementResponse StatementResponse *ofxCreditCardStatementResponse `xml:"CCSTMTRS"`
} }
// ofxBankStatementResponse represents the struct of open financial exchange (ofx) bank statement response // ofxBankStatementResponse represents the struct of open financial exchange (ofx) bank statement response
type ofxBankStatementResponse struct { type ofxBankStatementResponse struct {
DefaultCurrency string DefaultCurrency string `xml:"CURDEF"`
AccountFrom *ofxBankAccount AccountFrom *ofxBankAccount `xml:"BANKACCTFROM"`
TransactionList *ofxBankTransactionList TransactionList *ofxBankTransactionList `xml:"BANKTRANLIST"`
} }
// ofxCreditCardStatementResponse represents the struct of open financial exchange (ofx) credit card statement response // ofxCreditCardStatementResponse represents the struct of open financial exchange (ofx) credit card statement response
type ofxCreditCardStatementResponse struct { type ofxCreditCardStatementResponse struct {
DefaultCurrency string DefaultCurrency string `xml:"CURDEF"`
AccountFrom *ofxCreditCardAccount AccountFrom *ofxCreditCardAccount `xml:"CCACCTFROM"`
TransactionList *ofxCreditCardTransactionList TransactionList *ofxCreditCardTransactionList `xml:"BANKTRANLIST"`
} }
// ofxBankAccount represents the struct of open financial exchange (ofx) bank account // ofxBankAccount represents the struct of open financial exchange (ofx) bank account
type ofxBankAccount struct { type ofxBankAccount struct {
BankId string BankId string `xml:"BANKID"`
BranchId string BranchId string `xml:"BRANCHID"`
AccountId string AccountId string `xml:"ACCTID"`
AccountType ofxAccountType AccountType ofxAccountType `xml:"ACCTTYPE"`
AccountKey string AccountKey string `xml:"ACCTKEY"`
} }
// ofxCreditCardAccount represents the struct of open financial exchange (ofx) credit card account // ofxCreditCardAccount represents the struct of open financial exchange (ofx) credit card account
type ofxCreditCardAccount struct { type ofxCreditCardAccount struct {
AccountId string AccountId string `xml:"ACCTID"`
AccountKey string AccountKey string `xml:"ACCTKEY"`
} }
// ofxBankTransactionList represents the struct of open financial exchange (ofx) bank transaction list // ofxBankTransactionList represents the struct of open financial exchange (ofx) bank transaction list
type ofxBankTransactionList struct { type ofxBankTransactionList struct {
StartDate string StartDate string `xml:"DTSTART"`
EndDate string EndDate string `xml:"DTEND"`
StatementTransactions []*ofxBankStatementTransaction StatementTransactions []*ofxBankStatementTransaction `xml:"STMTTRN"`
} }
// ofxCreditCardTransactionList represents the struct of open financial exchange (ofx) credit card transaction list // ofxCreditCardTransactionList represents the struct of open financial exchange (ofx) credit card transaction list
type ofxCreditCardTransactionList struct { type ofxCreditCardTransactionList struct {
StartDate string StartDate string `xml:"DTSTART"`
EndDate string EndDate string `xml:"DTEND"`
StatementTransactions []*ofxCreditCardStatementTransaction StatementTransactions []*ofxCreditCardStatementTransaction `xml:"STMTTRN"`
} }
// ofxBaseStatementTransaction represents the struct of open financial exchange (ofx) base statement transaction // ofxBaseStatementTransaction represents the struct of open financial exchange (ofx) base statement transaction
type ofxBaseStatementTransaction struct { type ofxBaseStatementTransaction struct {
TransactionId string TransactionId string `xml:"FITID"`
TransactionType ofxTransactionType TransactionType ofxTransactionType `xml:"TRNTYPE"`
PostedDate string PostedDate string `xml:"DTPOSTED"`
Amount string Amount string `xml:"TRNAMT"`
Name string Name string `xml:"NAME"`
Payee *ofxPayee Payee *ofxPayee `xml:"PAYEE"`
Memo string Memo string `xml:"MEMO"`
Currency string Currency string `xml:"CURRENCY"`
OriginalCurrency string OriginalCurrency string `xml:"ORIGCURRENCY"`
} }
// ofxBankStatementTransaction represents the struct of open financial exchange (ofx) bank statement transaction // ofxBankStatementTransaction represents the struct of open financial exchange (ofx) bank statement transaction
type ofxBankStatementTransaction struct { type ofxBankStatementTransaction struct {
ofxBaseStatementTransaction ofxBaseStatementTransaction
AccountTo *ofxBankAccount AccountTo *ofxBankAccount `xml:"BANKACCTTO"`
} }
// ofxCreditCardStatementTransaction represents the struct of open financial exchange (ofx) credit card statement transaction // ofxCreditCardStatementTransaction represents the struct of open financial exchange (ofx) credit card statement transaction
type ofxCreditCardStatementTransaction struct { type ofxCreditCardStatementTransaction struct {
ofxBaseStatementTransaction ofxBaseStatementTransaction
AccountTo *ofxCreditCardAccount AccountTo *ofxCreditCardAccount `xml:"CCACCTTO"`
} }
// ofxPayee represents the struct of open financial exchange (ofx) payee info // ofxPayee represents the struct of open financial exchange (ofx) payee info
type ofxPayee struct { type ofxPayee struct {
Name string Name string `xml:"NAME"`
Address1 string Address1 string `xml:"ADDR1"`
Address2 string Address2 string `xml:"ADDR2"`
Address3 string Address3 string `xml:"ADDR3"`
City string City string `xml:"CITY"`
State string State string `xml:"STATE"`
PostalCode string PostalCode string `xml:"POSTALCODE"`
Country string Country string `xml:"COUNTRY"`
Phone string Phone string `xml:"PHONE"`
} }
File diff suppressed because it is too large Load Diff
+487 -2
View File
@@ -131,7 +131,372 @@ func TestCreateNewOFXFileReader_OFX1WithoutBreakLine(t *testing.T) {
assert.Equal(t, "123.45", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Amount) assert.Equal(t, "123.45", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Amount)
} }
func TestCreateNewOFXFileReader_OFX1WithBlanklines(t *testing.T) { func TestCreateNewOFXFileReader_OFX1ParseBankAccountFrom(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"<BANKMSGSRSV1>\n"+
"<STMTTRNRS>\n"+
"<STMTRS>\n"+
"<BANKACCTFROM>\n"+
"<BANKID>1234567890\n"+
"<BRANCHID>2345678901\n"+
"<ACCTID>3456789012\n"+
"<ACCTTYPE>CHECKING\n"+
"<ACCTKEY>4567890123\n"+
"</BANKACCTFROM>\n"+
"</STMTRS>\n"+
"</STMTTRNRS>\n"+
"</BANKMSGSRSV1>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.Nil(t, err)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.BankMessageResponseV1)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.AccountFrom)
account := ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.AccountFrom
assert.Equal(t, "1234567890", account.BankId)
assert.Equal(t, "2345678901", account.BranchId)
assert.Equal(t, "3456789012", account.AccountId)
assert.Equal(t, ofxCheckingAccount, account.AccountType)
assert.Equal(t, "4567890123", account.AccountKey)
}
func TestCreateNewOFXFileReader_OFX1ParseCreditCardAccountFrom(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"<CREDITCARDMSGSRSV1>\n"+
"<CCSTMTTRNRS>\n"+
"<CCSTMTRS>\n"+
"<CCACCTFROM>\n"+
"<ACCTID>3456789012\n"+
"<ACCTKEY>4567890123\n"+
"</CCACCTFROM>\n"+
"</CCSTMTRS>\n"+
"</CCSTMTTRNRS>\n"+
"</CREDITCARDMSGSRSV1>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.Nil(t, err)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.CreditCardMessageResponseV1)
assert.NotNil(t, ofxFile.CreditCardMessageResponseV1.StatementTransactionResponse)
assert.NotNil(t, ofxFile.CreditCardMessageResponseV1.StatementTransactionResponse.StatementResponse)
assert.NotNil(t, ofxFile.CreditCardMessageResponseV1.StatementTransactionResponse.StatementResponse.AccountFrom)
account := ofxFile.CreditCardMessageResponseV1.StatementTransactionResponse.StatementResponse.AccountFrom
assert.Equal(t, "3456789012", account.AccountId)
assert.Equal(t, "4567890123", account.AccountKey)
}
func TestCreateNewOFXFileReader_OFX1ParseBankTransactionList(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"<BANKMSGSRSV1>\n"+
"<STMTTRNRS>\n"+
"<STMTRS>\n"+
"<BANKTRANLIST>\n"+
"<DTSTART>20240901012345.000[+8:CST]\n"+
"<DTEND>20240901235959.000[+8:CST]\n"+
"</BANKTRANLIST>\n"+
"</STMTRS>\n"+
"</STMTTRNRS>\n"+
"</BANKMSGSRSV1>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.Nil(t, err)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.BankMessageResponseV1)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList)
transactionList := ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList
assert.Equal(t, "20240901012345.000[+8:CST]", transactionList.StartDate)
assert.Equal(t, "20240901235959.000[+8:CST]", transactionList.EndDate)
}
func TestCreateNewOFXFileReader_OFX1ParseCreditTransactionList(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"<CREDITCARDMSGSRSV1>\n"+
"<CCSTMTTRNRS>\n"+
"<CCSTMTRS>\n"+
"<BANKTRANLIST>\n"+
"<DTSTART>20240901012345.000[+8:CST]\n"+
"<DTEND>20240901235959.000[+8:CST]\n"+
"</BANKTRANLIST>\n"+
"</CCSTMTRS>\n"+
"</CCSTMTTRNRS>\n"+
"</CREDITCARDMSGSRSV1>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.Nil(t, err)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.CreditCardMessageResponseV1)
assert.NotNil(t, ofxFile.CreditCardMessageResponseV1.StatementTransactionResponse)
assert.NotNil(t, ofxFile.CreditCardMessageResponseV1.StatementTransactionResponse.StatementResponse)
assert.NotNil(t, ofxFile.CreditCardMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList)
transactionList := ofxFile.CreditCardMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList
assert.Equal(t, "20240901012345.000[+8:CST]", transactionList.StartDate)
assert.Equal(t, "20240901235959.000[+8:CST]", transactionList.EndDate)
}
func TestCreateNewOFXFileReader_OFX1ParseTransaction(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"<BANKMSGSRSV1>\n"+
"<STMTTRNRS>\n"+
"<STMTRS>\n"+
"<BANKACCTFROM>\n"+
"<ACCTID>123\n"+
"</BANKACCTFROM>\n"+
"<BANKTRANLIST>\n"+
"<STMTTRN>\n"+
"<FITID>1234567890\n"+
"<TRNTYPE>CASH\n"+
"<DTPOSTED>20240901012345.000[+8:CST]\n"+
"<TRNAMT>123.45\n"+
"<NAME>Test Name\n"+
"<MEMO>Some Text\n"+
"<CURRENCY>CNY\n"+
"<ORIGCURRENCY>USD\n"+
"</STMTTRN>\n"+
"</BANKTRANLIST>\n"+
"</STMTRS>\n"+
"</STMTTRNRS>\n"+
"</BANKMSGSRSV1>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.Nil(t, err)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.BankMessageResponseV1)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList)
assert.Equal(t, 1, len(ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions))
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0])
transaction := ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0]
assert.Equal(t, "1234567890", transaction.TransactionId)
assert.Equal(t, ofxCashWithdrawalTransaction, transaction.TransactionType)
assert.Equal(t, "20240901012345.000[+8:CST]", transaction.PostedDate)
assert.Equal(t, "123.45", transaction.Amount)
assert.Equal(t, "Test Name", transaction.Name)
assert.Equal(t, "Some Text", transaction.Memo)
assert.Equal(t, "CNY", transaction.Currency)
assert.Equal(t, "USD", transaction.OriginalCurrency)
}
func TestCreateNewOFXFileReader_OFX1ParseTransactionPayee(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"<BANKMSGSRSV1>\n"+
"<STMTTRNRS>\n"+
"<STMTRS>\n"+
"<BANKACCTFROM>\n"+
"<ACCTID>123\n"+
"</BANKACCTFROM>\n"+
"<BANKTRANLIST>\n"+
"<STMTTRN>\n"+
"<TRNTYPE>DEP\n"+
"<DTPOSTED>20240901012345.000[+8:CST]\n"+
"<TRNAMT>123.45\n"+
"<PAYEE>\n"+
"<NAME>Test Name\n"+
"<ADDR1>Address 1\n"+
"<ADDR2>Address 2\n"+
"<ADDR3>Address 3\n"+
"<CITY>City Name\n"+
"<STATE>State Name\n"+
"<POSTALCODE>10000000\n"+
"<COUNTRY>Country Name\n"+
"<PHONE>11111111111\n"+
"</PAYEE>\n"+
"</STMTTRN>\n"+
"</BANKTRANLIST>\n"+
"</STMTRS>\n"+
"</STMTTRNRS>\n"+
"</BANKMSGSRSV1>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.Nil(t, err)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.BankMessageResponseV1)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList)
assert.Equal(t, 1, len(ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions))
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0])
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Payee)
payee := ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Payee
assert.Equal(t, "Test Name", payee.Name)
assert.Equal(t, "Address 1", payee.Address1)
assert.Equal(t, "Address 2", payee.Address2)
assert.Equal(t, "Address 3", payee.Address3)
assert.Equal(t, "City Name", payee.City)
assert.Equal(t, "State Name", payee.State)
assert.Equal(t, "10000000", payee.PostalCode)
assert.Equal(t, "Country Name", payee.Country)
assert.Equal(t, "11111111111", payee.Phone)
}
func TestCreateNewOFXFileReader_OFX1WithEndElement(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"<BANKMSGSRSV1>\n"+
"<STMTTRNRS>\n"+
"<STMTRS>\n"+
"<CURDEF>CNY</CURDEF>\n"+
"<BANKACCTFROM>\n"+
"<ACCTID>123</ACCTID>\n"+
"</BANKACCTFROM>\n"+
"<BANKTRANLIST>\n"+
"<STMTTRN>\n"+
"<TRNTYPE>DEP</TRNTYPE>\n"+
"<DTPOSTED>20240901012345.000[+8:CST]</DTPOSTED>\n"+
"<TRNAMT>123.45</TRNAMT>\n"+
"</STMTTRN>\n"+
"</BANKTRANLIST>\n"+
"</STMTRS>\n"+
"</STMTTRNRS>\n"+
"</BANKMSGSRSV1>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.Nil(t, err)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.FileHeader)
assert.Equal(t, ofxVersion1, ofxFile.FileHeader.OFXDeclarationVersion)
assert.Equal(t, "103", ofxFile.FileHeader.OFXDataVersion)
assert.Equal(t, "NONE", ofxFile.FileHeader.Security)
assert.Equal(t, "NONE", ofxFile.FileHeader.OldFileUid)
assert.Equal(t, "NONE", ofxFile.FileHeader.NewFileUid)
assert.NotNil(t, ofxFile.BankMessageResponseV1)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse)
assert.Equal(t, "CNY", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.DefaultCurrency)
assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.AccountFrom)
assert.Equal(t, "123", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.AccountFrom.AccountId)
assert.Equal(t, 1, len(ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions))
assert.Equal(t, ofxDepositTransaction, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].TransactionType)
assert.Equal(t, "20240901012345.000[+8:CST]", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].PostedDate)
assert.Equal(t, "123.45", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Amount)
}
func TestCreateNewOFXFileReader_OFX1WithBlanklinesInHeader(t *testing.T) {
context := core.NewNullContext() context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte( reader, err := createNewOFXFileReader(context, []byte(
"\n"+ "\n"+
@@ -194,6 +559,104 @@ func TestCreateNewOFXFileReader_OFX1WithBlanklines(t *testing.T) {
assert.Equal(t, "123.45", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Amount) assert.Equal(t, "123.45", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Amount)
} }
func TestCreateNewOFXFileReader_OFX1WithoutCharset(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"FOO:BAR\n"+
"\n"+
"<OFX>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.FileHeader)
assert.Equal(t, ofxVersion1, ofxFile.FileHeader.OFXDeclarationVersion)
assert.Equal(t, "103", ofxFile.FileHeader.OFXDataVersion)
assert.Equal(t, "NONE", ofxFile.FileHeader.Security)
assert.Equal(t, "NONE", ofxFile.FileHeader.OldFileUid)
assert.Equal(t, "NONE", ofxFile.FileHeader.NewFileUid)
}
func TestCreateNewOFXFileReader_OFX1WithInvalidHeaderVersion(t *testing.T) {
context := core.NewNullContext()
_, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:200\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"</OFX>"))
assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message)
}
func TestCreateNewOFXFileReader_OFX1WithInvalidHeader(t *testing.T) {
context := core.NewNullContext()
_, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:XML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"\n"+
"<OFX>\n"+
"</OFX>"))
assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message)
}
func TestCreateNewOFXFileReader_OFX1WithUnknownHeader(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"OFXHEADER:100\n"+
"DATA:OFXSGML\n"+
"VERSION:103\n"+
"SECURITY:NONE\n"+
"ENCODING:USASCII\n"+
"CHARSET:1252\n"+
"COMPRESSION:NONE\n"+
"OLDFILEUID:NONE\n"+
"NEWFILEUID:NONE\n"+
"FOO:BAR\n"+
"\n"+
"<OFX>\n"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.FileHeader)
assert.Equal(t, ofxVersion1, ofxFile.FileHeader.OFXDeclarationVersion)
assert.Equal(t, "103", ofxFile.FileHeader.OFXDataVersion)
assert.Equal(t, "NONE", ofxFile.FileHeader.Security)
assert.Equal(t, "NONE", ofxFile.FileHeader.OldFileUid)
assert.Equal(t, "NONE", ofxFile.FileHeader.NewFileUid)
}
func TestCreateNewOFXFileReader_OFX2(t *testing.T) { func TestCreateNewOFXFileReader_OFX2(t *testing.T) {
context := core.NewNullContext() context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte( reader, err := createNewOFXFileReader(context, []byte(
@@ -328,14 +791,15 @@ func TestCreateNewOFXFileReader_OFX2WithInvalidHeader(t *testing.T) {
"<?OFX?>"+ "<?OFX?>"+
"<OFX>"+ "<OFX>"+
"</OFX>")) "</OFX>"))
assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message)
_, err = createNewOFXFileReader(context, []byte( _, err = createNewOFXFileReader(context, []byte(
"<?xml version=\"1.0\" encoding=\"US-ASCII\"?>"+ "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>"+
"<?OFX OFXHEADER=200?>"+ "<?OFX OFXHEADER=200?>"+
"<OFX>"+ "<OFX>"+
"</OFX>")) "</OFX>"))
assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message) assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message)
_, err = createNewOFXFileReader(context, []byte( _, err = createNewOFXFileReader(context, []byte(
"<?xml version=\"1.0\" encoding=\"US-ASCII\"?>"+ "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>"+
"<?OFX OFXHEADER=\"200\" VERSION=\"211\" SECURITY=\"NONE\" OLDFILEUID=\"NONE\" NEWFILEUID=\"NONE\" test=\"\"?>"+ "<?OFX OFXHEADER=\"200\" VERSION=\"211\" SECURITY=\"NONE\" OLDFILEUID=\"NONE\" NEWFILEUID=\"NONE\" test=\"\"?>"+
@@ -344,6 +808,27 @@ func TestCreateNewOFXFileReader_OFX2WithInvalidHeader(t *testing.T) {
assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message) assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message)
} }
func TestCreateNewOFXFileReader_OFX2WithUnknownHeader(t *testing.T) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
"<?xml version=\"1.0\" encoding=\"US-ASCII\"?>"+
"<?OFX OFXHEADER=\"200\" VERSION=\"211\" SECURITY=\"NONE\" OLDFILEUID=\"NONE\" NEWFILEUID=\"NONE\" FOO=\"BAR\"?>"+
"<OFX>"+
"</OFX>"))
assert.Nil(t, err)
ofxFile, err := reader.read(context)
assert.NotNil(t, ofxFile)
assert.NotNil(t, ofxFile.FileHeader)
assert.Equal(t, ofxVersion2, ofxFile.FileHeader.OFXDeclarationVersion)
assert.Equal(t, "211", ofxFile.FileHeader.OFXDataVersion)
assert.Equal(t, "NONE", ofxFile.FileHeader.Security)
assert.Equal(t, "NONE", ofxFile.FileHeader.OldFileUid)
assert.Equal(t, "NONE", ofxFile.FileHeader.NewFileUid)
}
func TestCreateNewOFXFileReader_OFX2WithSGML(t *testing.T) { func TestCreateNewOFXFileReader_OFX2WithSGML(t *testing.T) {
context := core.NewNullContext() context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte( reader, err := createNewOFXFileReader(context, []byte(
+321
View File
@@ -0,0 +1,321 @@
package sgml
import (
"encoding/xml"
"io"
"reflect"
"sync"
"github.com/mayswind/ezbookkeeping/pkg/errs"
)
const sgmlTagName = "sgml"
const sgmlNameFieldName = "SGMLName"
const xmlTagName = "xml" // reuse xml tag
const xmlNameFieldName = "XMLName" // reuse xml tag
// sgmlFieldType represents SGML field type
type sgmlFieldType byte
// Transaction template types
const (
sgmlNotSupportedField sgmlFieldType = 0
sgmlTextualField sgmlFieldType = 1
sgmlStructField sgmlFieldType = 2
sgmlStructSliceField sgmlFieldType = 3
)
// sgmlTypeInfo represents the struct of SGML type reflection info
type sgmlTypeInfo struct {
supportedFields map[string]*sgmlFieldInfo
}
// sgmlFieldInfo represents the struct of SGML field info
type sgmlFieldInfo struct {
sgmlFieldName string
sgmlFieldType sgmlFieldType
structFieldName string
}
type Decoder struct {
xmlDecoder *xml.Decoder
}
var sgmlTypeInfoMap sync.Map // map[reflect.Type]*typeInfo
// Decode unmarshal the specified struct instance and returns whether error occurs
func (d *Decoder) Decode(v any) error {
value := reflect.ValueOf(v).Elem()
finalValue := value
finalType := value.Type()
for finalValue.Kind() == reflect.Pointer {
finalValue = value.Elem()
finalType = finalValue.Type()
}
rootNameField, exists := finalType.FieldByName(sgmlNameFieldName)
if !exists {
rootNameField, exists = finalType.FieldByName(xmlNameFieldName)
}
if !exists {
return nil
}
rootElementName := rootNameField.Tag.Get(sgmlTagName)
if rootElementName == "" {
rootElementName = rootNameField.Tag.Get(xmlTagName)
}
for {
token, err := d.xmlDecoder.RawToken()
if err == io.EOF {
break
}
switch token := token.(type) {
case xml.StartElement:
if token.Name.Local == rootElementName {
return d.unmarshal(value.Elem(), rootElementName)
}
}
}
return nil
}
func (d *Decoder) unmarshal(element reflect.Value, elementName string) error {
typeInfo, err := d.getStructTypeInfo(element.Type())
if err != nil {
return err
}
if typeInfo == nil {
return errs.ErrInvalidSGMLFile
}
textualFieldWithoutEndElementNames := make(map[string]bool)
textualFieldValues := make(map[string]string)
hasEndElement := false
currentSGMLFieldName := ""
for {
token, err := d.xmlDecoder.RawToken()
if err == io.EOF {
break
}
switch token := token.(type) {
case xml.StartElement:
if fieldInfo, exists := typeInfo.supportedFields[token.Name.Local]; exists {
if fieldInfo.sgmlFieldType == sgmlStructField || fieldInfo.sgmlFieldType == sgmlStructSliceField {
field := element.FieldByName(fieldInfo.structFieldName)
childElementType := field.Type()
childElementKind := field.Kind()
var childElement reflect.Value
if fieldInfo.sgmlFieldType == sgmlStructSliceField {
childElementType = childElementType.Elem()
childElementKind = childElementType.Kind()
}
if childElementKind == reflect.Pointer {
childElement = reflect.New(childElementType.Elem())
} else if childElementKind == reflect.Struct {
childElement = reflect.New(childElementType)
}
err := d.unmarshal(childElement.Elem(), fieldInfo.sgmlFieldName)
if err != nil {
return err
}
if childElementKind == reflect.Struct {
childElement = childElement.Elem()
}
if fieldInfo.sgmlFieldType == sgmlStructField {
field.Set(childElement)
} else if fieldInfo.sgmlFieldType == sgmlStructSliceField {
if field.Len() == 0 {
slice := reflect.MakeSlice(reflect.SliceOf(childElement.Type()), 0, 0)
field.Set(reflect.Append(slice, childElement))
} else {
field.Set(reflect.Append(field.Addr().Elem(), childElement))
}
}
} else if fieldInfo.sgmlFieldType == sgmlTextualField {
currentSGMLFieldName = token.Name.Local
textualFieldWithoutEndElementNames[token.Name.Local] = true
}
}
case xml.EndElement:
if fieldInfo, exists := typeInfo.supportedFields[token.Name.Local]; exists {
if fieldInfo.sgmlFieldType == sgmlTextualField {
delete(textualFieldWithoutEndElementNames, token.Name.Local)
}
} else if token.Name.Local == elementName {
hasEndElement = true
break
}
case xml.CharData:
if currentSGMLFieldName != "" {
if fieldInfo, exists := typeInfo.supportedFields[currentSGMLFieldName]; exists {
if fieldInfo.sgmlFieldType == sgmlTextualField {
textualFieldValues[currentSGMLFieldName] = string(token)
}
}
}
currentSGMLFieldName = ""
}
if hasEndElement {
break
}
}
if !hasEndElement {
return errs.ErrInvalidSGMLFile
}
for sgmlFieldName, fieldValue := range textualFieldValues {
finalValue := d.getActualFieldValue(sgmlFieldName, fieldValue, textualFieldWithoutEndElementNames)
fieldInfo, exists := typeInfo.supportedFields[sgmlFieldName]
if !exists {
continue
}
field := element.FieldByName(fieldInfo.structFieldName)
field.SetString(finalValue)
}
return nil
}
func (d *Decoder) getStructTypeInfo(reflectType reflect.Type) (*sgmlTypeInfo, error) {
if reflectType.Kind() != reflect.Struct {
return nil, nil
}
typeInfo, exists := sgmlTypeInfoMap.Load(reflectType)
if exists {
return typeInfo.(*sgmlTypeInfo), nil
}
newTypeInfo := &sgmlTypeInfo{
supportedFields: make(map[string]*sgmlFieldInfo),
}
for i := 0; i < reflectType.NumField(); i++ {
field := reflectType.Field(i)
if field.Anonymous {
fieldType := field.Type
if fieldType.Kind() == reflect.Struct {
fieldSgmlTypeInfo, err := d.getStructTypeInfo(fieldType)
if err != nil {
return nil, err
}
for sgmlFieldName, fieldInfo := range fieldSgmlTypeInfo.supportedFields {
newTypeInfo.supportedFields[sgmlFieldName] = fieldInfo
}
}
continue
} else if !field.IsExported() {
continue
}
sgmlFieldName := field.Tag.Get(sgmlTagName)
if sgmlFieldName == "" {
sgmlFieldName = field.Tag.Get(xmlTagName)
}
if sgmlFieldName == "" || field.Name == sgmlNameFieldName || field.Name == xmlNameFieldName {
continue
}
sgmlFieldType := sgmlNotSupportedField
finalFieldType := field.Type
for finalFieldType.Kind() == reflect.Pointer {
finalFieldType = finalFieldType.Elem()
}
switch finalFieldType.Kind() {
case reflect.String:
sgmlFieldType = sgmlTextualField
case reflect.Struct:
sgmlFieldType = sgmlStructField
case reflect.Slice:
childFinalFieldType := finalFieldType.Elem()
for childFinalFieldType.Kind() == reflect.Pointer {
childFinalFieldType = childFinalFieldType.Elem()
}
if childFinalFieldType.Kind() == reflect.Struct {
sgmlFieldType = sgmlStructSliceField
}
default:
sgmlFieldType = sgmlNotSupportedField
}
if sgmlFieldType == sgmlNotSupportedField {
return nil, errs.ErrInvalidSGMLFile
}
newTypeInfo.supportedFields[sgmlFieldName] = &sgmlFieldInfo{
sgmlFieldName: sgmlFieldName,
sgmlFieldType: sgmlFieldType,
structFieldName: field.Name,
}
}
typeInfo, _ = sgmlTypeInfoMap.LoadOrStore(reflectType, newTypeInfo)
return typeInfo.(*sgmlTypeInfo), nil
}
func (d *Decoder) getActualFieldValue(fieldName string, fieldValue string, textualFieldWithoutEndElementNames map[string]bool) string {
_, notHasEndElement := textualFieldWithoutEndElementNames[fieldName]
if !notHasEndElement {
return fieldValue
}
for i := 0; i < len(fieldValue); i++ {
if fieldValue[i] == '\r' || fieldValue[i] == '\n' {
return fieldValue[0:i]
}
}
return fieldValue
}
// NewDecoder creates a new SGML parser reading from specified io reader
func NewDecoder(reader io.Reader) *Decoder {
xmlDecoder := xml.NewDecoder(reader)
xmlDecoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
return input, nil
}
return &Decoder{
xmlDecoder: xmlDecoder,
}
}
+359
View File
@@ -0,0 +1,359 @@
package sgml
import (
"encoding/xml"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/mayswind/ezbookkeeping/pkg/errs"
)
type TestSimpleStruct struct {
SGMLName string `sgml:"Root"`
Text1 string `sgml:"Text1"`
Text2 string `sgml:"Text2"`
}
type TestNestedStruct1 struct {
SGMLName string `sgml:"Root"`
Child TestSimpleStruct `sgml:"Child"`
Text3 string `sgml:"Text3"`
Text4 string `sgml:"Text4"`
}
type TestNestedStruct2 struct {
SGMLName string `sgml:"Root"`
Child *TestSimpleStruct `sgml:"Child"`
Text3 string `sgml:"Text3"`
Text4 string `sgml:"Text4"`
}
type TestEmbeddedStruct struct {
TestSimpleStruct
Text5 string `sgml:"Text5"`
Text6 string `sgml:"Text6"`
}
type TestSliceStruct1 struct {
SGMLName string `sgml:"Root"`
Children []TestSimpleStruct `sgml:"Child"`
Text7 string `sgml:"Text7"`
}
type TestSliceStruct2 struct {
SGMLName string `sgml:"Root"`
Children []*TestSimpleStruct `sgml:"Child"`
Text7 string `sgml:"Text7"`
}
type TestSimpleStructWithXMLTag struct {
XMLName xml.Name `xml:"Root"`
Text1 string `xml:"Text1"`
Text2 string `xml:"Text2"`
}
type TestStructWithXMLTag struct {
XMLName xml.Name `xml:"Root"`
Child TestSimpleStructWithXMLTag `xml:"Child"`
Text3 string `xml:"Text3"`
Text4 string `xml:"Text4"`
}
type TestNotExportedFieldStruct struct {
SGMLName string `sgml:"Root"`
Text1 string `sgml:"Text1"`
Text2 string
text3 string `sgml:"Text3"`
}
type TestUnsupportedStruct struct {
SGMLName string `sgml:"Root"`
Number int `sgml:"Number"`
}
type TestEmbeddedUnsupportedStruct struct {
TestUnsupportedStruct
Text1 string `sgml:"Text1"`
}
func TestDecoderDecode(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Text1>Foo\n" +
"<Text2>Bar\n" +
"</Root>\n"))
testStruct := &TestSimpleStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.Equal(t, "Foo", testStruct.Text1)
assert.Equal(t, "Bar", testStruct.Text2)
}
func TestDecoderDecode_WithRedundantFields(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Text1>Foo\n" +
"<Text2>Bar\n" +
"<Text3>Hello\n" +
"<Child>\n" +
"<Text4>World\n" +
"</Child>\n" +
"</Root>\n"))
testStruct := &TestSimpleStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.Equal(t, "Foo", testStruct.Text1)
assert.Equal(t, "Bar", testStruct.Text2)
}
func TestDecoderDecode_WithEndElement(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Text1>Foo</Text1>\n" +
"<Text2>Bar</Text2>\n" +
"</Root>\n"))
testStruct := &TestSimpleStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.Equal(t, "Foo", testStruct.Text1)
assert.Equal(t, "Bar", testStruct.Text2)
}
func TestDecoderDecode_WithoutBreakLine(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>" +
"<Text1>Foo" +
"<Text2>Bar" +
"</Root>"))
testStruct := &TestSimpleStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.Equal(t, "Foo", testStruct.Text1)
assert.Equal(t, "Bar", testStruct.Text2)
}
func TestDecoderDecode_NestedStruct(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Child>\n" +
"<Text1>Hello\n" +
"<Text2>World\n" +
"</Child>\n" +
"<Text3>Foo\n" +
"<Text4>Bar\n" +
"</Root>\n"))
testStruct := &TestNestedStruct1{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.NotNil(t, testStruct.Child)
assert.Equal(t, "Hello", testStruct.Child.Text1)
assert.Equal(t, "World", testStruct.Child.Text2)
assert.Equal(t, "Foo", testStruct.Text3)
assert.Equal(t, "Bar", testStruct.Text4)
}
func TestDecoderDecode_NestedStructUsingPointer(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Child>\n" +
"<Text1>Hello\n" +
"<Text2>World\n" +
"</Child>\n" +
"<Text3>Foo\n" +
"<Text4>Bar\n" +
"</Root>\n"))
testStruct := &TestNestedStruct2{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.NotNil(t, testStruct.Child)
assert.Equal(t, "Hello", testStruct.Child.Text1)
assert.Equal(t, "World", testStruct.Child.Text2)
assert.Equal(t, "Foo", testStruct.Text3)
assert.Equal(t, "Bar", testStruct.Text4)
}
func TestDecoderDecode_EmbeddedStruct(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Text1>Hello\n" +
"<Text2>World\n" +
"<Text5>Foo\n" +
"<Text6>Bar\n" +
"</Root>\n"))
testStruct := &TestEmbeddedStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.Equal(t, "Hello", testStruct.Text1)
assert.Equal(t, "World", testStruct.Text2)
assert.Equal(t, "Foo", testStruct.Text5)
assert.Equal(t, "Bar", testStruct.Text6)
}
func TestDecoderDecode_StructSlice(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Child>\n" +
"<Text1>Hello\n" +
"<Text2>World\n" +
"</Child>\n" +
"<Child>\n" +
"<Text1>Hello2\n" +
"<Text2>World2\n" +
"</Child>\n" +
"<Text7>Foo\n" +
"</Root>\n"))
testStruct := &TestSliceStruct1{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.Equal(t, 2, len(testStruct.Children))
assert.Equal(t, "Hello", testStruct.Children[0].Text1)
assert.Equal(t, "World", testStruct.Children[0].Text2)
assert.Equal(t, "Hello2", testStruct.Children[1].Text1)
assert.Equal(t, "World2", testStruct.Children[1].Text2)
assert.Equal(t, "Foo", testStruct.Text7)
}
func TestDecoderDecode_StructSliceUsingPointer(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Child>\n" +
"<Text1>Hello\n" +
"<Text2>World\n" +
"</Child>\n" +
"<Child>\n" +
"<Text1>Hello2\n" +
"<Text2>World2\n" +
"</Child>\n" +
"<Text7>Foo\n" +
"</Root>\n"))
testStruct := &TestSliceStruct2{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.Equal(t, 2, len(testStruct.Children))
assert.Equal(t, "Hello", testStruct.Children[0].Text1)
assert.Equal(t, "World", testStruct.Children[0].Text2)
assert.Equal(t, "Hello2", testStruct.Children[1].Text1)
assert.Equal(t, "World2", testStruct.Children[1].Text2)
assert.Equal(t, "Foo", testStruct.Text7)
}
func TestDecoderDecode_UsingXMLTag(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Child>\n" +
"<Text1>Hello\n" +
"<Text2>World\n" +
"</Child>\n" +
"<Text3>Foo\n" +
"<Text4>Bar\n" +
"</Root>\n"))
testStruct := &TestStructWithXMLTag{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.NotNil(t, testStruct.Child)
assert.Equal(t, "Hello", testStruct.Child.Text1)
assert.Equal(t, "World", testStruct.Child.Text2)
assert.Equal(t, "Foo", testStruct.Text3)
assert.Equal(t, "Bar", testStruct.Text4)
}
func TestDecoderDecode_WithNotExportedFields(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Text1>Foo\n" +
"<Text2>Bar\n" +
"<Text3>Hello World\n" +
"</Root>\n"))
testStruct := &TestNotExportedFieldStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.Nil(t, err)
assert.NotNil(t, testStruct)
assert.Equal(t, "Foo", testStruct.Text1)
assert.Equal(t, "", testStruct.Text2)
assert.Equal(t, "", testStruct.text3)
}
func TestDecoderDecode_StructWithoutEndElement(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Text1>Foo\n" +
"<Text2>Bar\n"))
testStruct := &TestSimpleStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.EqualError(t, err, errs.ErrInvalidSGMLFile.Message)
sgmlDecoder = NewDecoder(strings.NewReader(
"<Root>\n" +
"<Child>\n" +
"<Text1>Hello\n" +
"<Text2>World\n" +
"<Text3>Foo\n" +
"<Text4>Bar\n" +
"</Root>\n"))
testStruct2 := &TestNestedStruct2{}
err = sgmlDecoder.Decode(&testStruct2)
assert.EqualError(t, err, errs.ErrInvalidSGMLFile.Message)
}
func TestDecoderDecode_WithNotSupportedField(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Number>1234\n" +
"</Root>\n"))
testStruct := &TestUnsupportedStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.EqualError(t, err, errs.ErrInvalidSGMLFile.Message)
}
func TestDecoderDecode_WithEmbeddedNotSupportedField(t *testing.T) {
sgmlDecoder := NewDecoder(strings.NewReader(
"<Root>\n" +
"<Number>1234\n" +
"<Text1>Foo\n" +
"</Root>\n"))
testStruct := &TestEmbeddedUnsupportedStruct{}
err := sgmlDecoder.Decode(&testStruct)
assert.EqualError(t, err, errs.ErrInvalidSGMLFile.Message)
}
+1
View File
@@ -24,4 +24,5 @@ var (
ErrThereAreNotSupportedTransactionType = NewNormalError(NormalSubcategoryConverter, 17, http.StatusBadRequest, "there are not supported transaction type") ErrThereAreNotSupportedTransactionType = NewNormalError(NormalSubcategoryConverter, 17, http.StatusBadRequest, "there are not supported transaction type")
ErrInvalidIIFFile = NewNormalError(NormalSubcategoryConverter, 18, http.StatusBadRequest, "invalid iif file") ErrInvalidIIFFile = NewNormalError(NormalSubcategoryConverter, 18, http.StatusBadRequest, "invalid iif file")
ErrInvalidOFXFile = NewNormalError(NormalSubcategoryConverter, 19, http.StatusBadRequest, "invalid ofx file") ErrInvalidOFXFile = NewNormalError(NormalSubcategoryConverter, 19, http.StatusBadRequest, "invalid ofx file")
ErrInvalidSGMLFile = NewNormalError(NormalSubcategoryConverter, 20, http.StatusBadRequest, "invalid sgml file")
) )
+1
View File
@@ -1135,6 +1135,7 @@
"there are not supported transaction type": "There are not supported transaction type in import file", "there are not supported transaction type": "There are not supported transaction type in import file",
"invalid iif file": "Invalid IIF file", "invalid iif file": "Invalid IIF file",
"invalid ofx file": "Invalid OFX file", "invalid ofx file": "Invalid OFX file",
"invalid sgml file": "Invalid SGML file",
"query items cannot be blank": "There are no query items", "query items cannot be blank": "There are no query items",
"query items too much": "There are too many query items", "query items too much": "There are too many query items",
"query items have invalid item": "There is invalid item in query items", "query items have invalid item": "There is invalid item in query items",
+1
View File
@@ -1135,6 +1135,7 @@
"there are not supported transaction type": "导入文件中有不支持的交易类型", "there are not supported transaction type": "导入文件中有不支持的交易类型",
"invalid iif file": "无效的 IIF 文件", "invalid iif file": "无效的 IIF 文件",
"invalid ofx file": "无效的 OFX 文件", "invalid ofx file": "无效的 OFX 文件",
"invalid sgml file": "无效的 SGML 文件",
"query items cannot be blank": "请求项目不能为空", "query items cannot be blank": "请求项目不能为空",
"query items too much": "请求项目过多", "query items too much": "请求项目过多",
"query items have invalid item": "请求项目中有非法项目", "query items have invalid item": "请求项目中有非法项目",