Skip to content

Commit

Permalink
feat: add IBM1047 ASCII compatibility when in FRB_COMPATIBILITY_MODE
Browse files Browse the repository at this point in the history
Add support for IBM1047 EBCDIC encoding compatibility with IBM037 for the
three ASCII characters that differ between the encodings: [ ] ^

When FRB_COMPATIBILITY_MODE is enabled, converts IBM037 byte values to their
IBM1047 equivalents before decoding:
- 0xAD (Ý) -> 0xBA ([)
- 0xBD (¨) -> 0xBB (])
- 0x5F (¬) -> 0xB0 (^)
  • Loading branch information
Hillrunner2008 committed Dec 4, 2024
1 parent 4de4fa9 commit 9c764ff
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 2 deletions.
56 changes: 55 additions & 1 deletion checkDetailAddendumA_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func TestCDAddendumATruncationIndicatorFRB(t *testing.T) {
var e *FieldError
require.ErrorAs(t, err, &e)
require.Equal(t, "TruncationIndicator", e.FieldName)
t.Setenv(FRBCompatibilityMode, "")
t.Setenv(FRBCompatibilityMode, "true")
require.NoError(t, cdAddendumA.Validate())
}

Expand Down Expand Up @@ -308,3 +308,57 @@ func TestStringFieldTrim(t *testing.T) {
cdAddendumA.ReturnLocationRoutingNumber = "12345678912345"
require.Len(t, cdAddendumA.ReturnLocationRoutingNumberField(), 9)
}

func TestParseCheckDetailAddendumA_BOFDAccountEBCDIC(t *testing.T) {
t.Setenv("FRB_COMPATIBILITY_MODE", "true")
line := "\xf2\xf6" + // Record Type 26
strings.Repeat("\xf1", 33) + // Fill with '1's
"@@@@@@@@@@@" + // Spaces
"\xad\x85\x94\x97\xa3\xa8\xbd" + // [empty] in IBM1047
strings.Repeat("@", 20) + // More spaces
"\xe8\xf2\xf0@@@@" // End padding
r := NewReader(strings.NewReader(line), ReadEbcdicEncodingOption())
r.line = line

clh := mockCashLetterHeader()
r.addCurrentCashLetter(NewCashLetter(clh))
bh := mockBundleHeader()
b := NewBundle(bh)
r.currentCashLetter.AddBundle(b)
r.addCurrentBundle(b)
cd := mockCheckDetail()
r.currentCashLetter.currentBundle.AddCheckDetail(cd)

err := r.parseCheckDetailAddendumA()
require.NoError(t, err)

record := r.currentCashLetter.currentBundle.GetChecks()[0].CheckDetailAddendumA[0]
require.Equal(t, "[empty]", record.BOFDAccountNumber)
t.Setenv("FRB_COMPATIBILITY_MODE", "")
}

func TestParseCheckDetailAddendumA_BOFDAccountEBCDIC_NoFlag(t *testing.T) {
t.Setenv("FRB_COMPATIBILITY_MODE", "false")
line := "\xf2\xf6" +
strings.Repeat("\xf1", 33) +
"@@@@@@@@@@@" +
"\xad\x85\x94\x97\xa3\xa8\xbd" +
strings.Repeat("@", 20) +
"\xe8\xf2\xf0@@@@"

r := NewReader(strings.NewReader(line), ReadEbcdicEncodingOption())
r.line = line

clh := mockCashLetterHeader()
r.addCurrentCashLetter(NewCashLetter(clh))
bh := mockBundleHeader()
b := NewBundle(bh)
r.currentCashLetter.AddBundle(b)
r.addCurrentBundle(b)
cd := mockCheckDetail()
r.currentCashLetter.currentBundle.AddCheckDetail(cd)

err := r.parseCheckDetailAddendumA()
require.Error(t, err, "Expected an error when FRB_COMPATIBILITY_MODE is false")
t.Setenv("FRB_COMPATIBILITY_MODE", "")
}
28 changes: 27 additions & 1 deletion reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,10 +421,13 @@ func (r *Reader) parseCheckDetailAddendumA() error {
msg := fmt.Sprint(msgFileBundleOutside)
return r.error(&FileError{FieldName: "CheckDetailAddendumA", Msg: msg})
}
lineOut, err := r.decodeLine(r.line)
inputBytes := []byte(r.line)
adjustedBytes := handleIBM1047Compatibility(inputBytes)
lineOut, err := r.decodeLine(string(adjustedBytes))
if err != nil {
return err
}

cdAddendumA := NewCheckDetailAddendumA()
cdAddendumA.Parse(lineOut)
if err := cdAddendumA.Validate(); err != nil {
Expand All @@ -436,6 +439,29 @@ func (r *Reader) parseCheckDetailAddendumA() error {
return nil
}

func handleIBM1047Compatibility(input []byte) []byte {
if !IsFRBCompatibilityModeEnabled() {
return input
}

output := make([]byte, len(input))
copy(output, input)

// Replace bytes that map differently between IBM037 and IBM1047
// but only for the ascii subset see https://en.wikibooks.org/wiki/Character_Encodings/Code_Tables/EBCDIC/EBCDIC_1047
for i, b := range output {
switch b {
case 0xAD: // Ý -> [
output[i] = 0xBA
case 0xBD: // ¨ -> ]
output[i] = 0xBB
case 0x5F: // ¬ -> ^
output[i] = 0xB0
}
}
return output
}

// parseCheckDetailAddendumB takes the input record string and parses the CheckDetailAddendumB values
func (r *Reader) parseCheckDetailAddendumB() error {
r.recordName = "CheckDetailAddendumB"
Expand Down

0 comments on commit 9c764ff

Please sign in to comment.