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
import (
"encoding/xml"
"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
type ofxFile struct {
XMLName xml.Name `xml:"OFX"`
FileHeader *ofxFileHeader
BankMessageResponseV1 *ofxBankMessageResponseV1
CreditCardMessageResponseV1 *ofxCreditCardMessageResponseV1
BankMessageResponseV1 *ofxBankMessageResponseV1 `xml:"BANKMSGSRSV1"`
CreditCardMessageResponseV1 *ofxCreditCardMessageResponseV1 `xml:"CREDITCARDMSGSRSV1"`
}
// 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
type ofxBankMessageResponseV1 struct {
StatementTransactionResponse *ofxBankStatementTransactionResponse
StatementTransactionResponse *ofxBankStatementTransactionResponse `xml:"STMTTRNRS"`
}
// ofxCreditCardMessageResponseV1 represents the struct of open financial exchange (ofx) credit card message response v1
type ofxCreditCardMessageResponseV1 struct {
StatementTransactionResponse *ofxCreditCardStatementTransactionResponse
StatementTransactionResponse *ofxCreditCardStatementTransactionResponse `xml:"CCSTMTTRNRS"`
}
// ofxBankStatementTransactionResponse represents the struct of open financial exchange (ofx) bank statement transaction response
type ofxBankStatementTransactionResponse struct {
StatementResponse *ofxBankStatementResponse
StatementResponse *ofxBankStatementResponse `xml:"STMTRS"`
}
// ofxCreditCardStatementTransactionResponse represents the struct of open financial exchange (ofx) credit card statement transaction response
type ofxCreditCardStatementTransactionResponse struct {
StatementResponse *ofxCreditCardStatementResponse
StatementResponse *ofxCreditCardStatementResponse `xml:"CCSTMTRS"`
}
// ofxBankStatementResponse represents the struct of open financial exchange (ofx) bank statement response
type ofxBankStatementResponse struct {
DefaultCurrency string
AccountFrom *ofxBankAccount
TransactionList *ofxBankTransactionList
DefaultCurrency string `xml:"CURDEF"`
AccountFrom *ofxBankAccount `xml:"BANKACCTFROM"`
TransactionList *ofxBankTransactionList `xml:"BANKTRANLIST"`
}
// ofxCreditCardStatementResponse represents the struct of open financial exchange (ofx) credit card statement response
type ofxCreditCardStatementResponse struct {
DefaultCurrency string
AccountFrom *ofxCreditCardAccount
TransactionList *ofxCreditCardTransactionList
DefaultCurrency string `xml:"CURDEF"`
AccountFrom *ofxCreditCardAccount `xml:"CCACCTFROM"`
TransactionList *ofxCreditCardTransactionList `xml:"BANKTRANLIST"`
}
// ofxBankAccount represents the struct of open financial exchange (ofx) bank account
type ofxBankAccount struct {
BankId string
BranchId string
AccountId string
AccountType ofxAccountType
AccountKey string
BankId string `xml:"BANKID"`
BranchId string `xml:"BRANCHID"`
AccountId string `xml:"ACCTID"`
AccountType ofxAccountType `xml:"ACCTTYPE"`
AccountKey string `xml:"ACCTKEY"`
}
// ofxCreditCardAccount represents the struct of open financial exchange (ofx) credit card account
type ofxCreditCardAccount struct {
AccountId string
AccountKey string
AccountId string `xml:"ACCTID"`
AccountKey string `xml:"ACCTKEY"`
}
// ofxBankTransactionList represents the struct of open financial exchange (ofx) bank transaction list
type ofxBankTransactionList struct {
StartDate string
EndDate string
StatementTransactions []*ofxBankStatementTransaction
StartDate string `xml:"DTSTART"`
EndDate string `xml:"DTEND"`
StatementTransactions []*ofxBankStatementTransaction `xml:"STMTTRN"`
}
// ofxCreditCardTransactionList represents the struct of open financial exchange (ofx) credit card transaction list
type ofxCreditCardTransactionList struct {
StartDate string
EndDate string
StatementTransactions []*ofxCreditCardStatementTransaction
StartDate string `xml:"DTSTART"`
EndDate string `xml:"DTEND"`
StatementTransactions []*ofxCreditCardStatementTransaction `xml:"STMTTRN"`
}
// ofxBaseStatementTransaction represents the struct of open financial exchange (ofx) base statement transaction
type ofxBaseStatementTransaction struct {
TransactionId string
TransactionType ofxTransactionType
PostedDate string
Amount string
Name string
Payee *ofxPayee
Memo string
Currency string
OriginalCurrency string
TransactionId string `xml:"FITID"`
TransactionType ofxTransactionType `xml:"TRNTYPE"`
PostedDate string `xml:"DTPOSTED"`
Amount string `xml:"TRNAMT"`
Name string `xml:"NAME"`
Payee *ofxPayee `xml:"PAYEE"`
Memo string `xml:"MEMO"`
Currency string `xml:"CURRENCY"`
OriginalCurrency string `xml:"ORIGCURRENCY"`
}
// ofxBankStatementTransaction represents the struct of open financial exchange (ofx) bank statement transaction
type ofxBankStatementTransaction struct {
ofxBaseStatementTransaction
AccountTo *ofxBankAccount
AccountTo *ofxBankAccount `xml:"BANKACCTTO"`
}
// ofxCreditCardStatementTransaction represents the struct of open financial exchange (ofx) credit card statement transaction
type ofxCreditCardStatementTransaction struct {
ofxBaseStatementTransaction
AccountTo *ofxCreditCardAccount
AccountTo *ofxCreditCardAccount `xml:"CCACCTTO"`
}
// ofxPayee represents the struct of open financial exchange (ofx) payee info
type ofxPayee struct {
Name string
Address1 string
Address2 string
Address3 string
City string
State string
PostalCode string
Country string
Phone string
Name string `xml:"NAME"`
Address1 string `xml:"ADDR1"`
Address2 string `xml:"ADDR2"`
Address3 string `xml:"ADDR3"`
City string `xml:"CITY"`
State string `xml:"STATE"`
PostalCode string `xml:"POSTALCODE"`
Country string `xml:"COUNTRY"`
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)
}
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()
reader, err := createNewOFXFileReader(context, []byte(
"\n"+
@@ -194,6 +559,104 @@ func TestCreateNewOFXFileReader_OFX1WithBlanklines(t *testing.T) {
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) {
context := core.NewNullContext()
reader, err := createNewOFXFileReader(context, []byte(
@@ -328,14 +791,15 @@ func TestCreateNewOFXFileReader_OFX2WithInvalidHeader(t *testing.T) {
"<?OFX?>"+
"<OFX>"+
"</OFX>"))
assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message)
_, err = createNewOFXFileReader(context, []byte(
"<?xml version=\"1.0\" encoding=\"US-ASCII\"?>"+
"<?OFX OFXHEADER=200?>"+
"<OFX>"+
"</OFX>"))
assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message)
_, err = createNewOFXFileReader(context, []byte(
"<?xml version=\"1.0\" encoding=\"US-ASCII\"?>"+
"<?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)
}
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) {
context := core.NewNullContext()
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")
ErrInvalidIIFFile = NewNormalError(NormalSubcategoryConverter, 18, http.StatusBadRequest, "invalid iif file")
ErrInvalidOFXFile = NewNormalError(NormalSubcategoryConverter, 19, http.StatusBadRequest, "invalid ofx file")
ErrInvalidSGMLFile = NewNormalError(NormalSubcategoryConverter, 20, http.StatusBadRequest, "invalid sgml file")
)