github.com/moov-io/imagecashletter@v0.10.1/reader.go (about)

     1  // Copyright 2020 The Moov Authors
     2  // Use of this source code is governed by an Apache License
     3  // license that can be found in the LICENSE file.
     4  
     5  package imagecashletter
     6  
     7  import (
     8  	"bufio"
     9  	"encoding/binary"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"strconv"
    14  
    15  	"github.com/gdamore/encoding"
    16  )
    17  
    18  // ParseError is returned for parsing reader errors.
    19  // The first line is 1.
    20  type ParseError struct {
    21  	Line   int    // Line number where the error occurred
    22  	Record string // Name of the record type being parsed
    23  	Err    error  // The actual error
    24  }
    25  
    26  func (e *ParseError) Error() string {
    27  	if e.Record == "" {
    28  		return fmt.Sprintf("line:%d %T %s", e.Line, e.Err, e.Err)
    29  	}
    30  	return fmt.Sprintf("line:%d record:%s %T %s", e.Line, e.Record, e.Err, e.Err)
    31  }
    32  
    33  func (e *ParseError) Unwrap() error {
    34  	return e.Err
    35  }
    36  
    37  // Reader reads records from a ACH-encoded file.
    38  type Reader struct {
    39  	// r handles the IO.Reader sent to be parser.
    40  	scanner *bufio.Scanner
    41  	// file is ach.file model being built as r is parsed.
    42  	File File
    43  	// func used to decode line to desired encoding ie. ASCII,EBCDIC
    44  	decodeLine DecodeLineFn
    45  	// line is the current line being parsed from the input r
    46  	line string
    47  	// currentCashLetter is the current CashLetter being parsed
    48  	currentCashLetter CashLetter
    49  	// line number of the file being parsed
    50  	lineNum int
    51  	// recordName holds the current record name being parsed.
    52  	recordName string
    53  }
    54  
    55  // error creates a new ParseError based on err.
    56  func (r *Reader) error(err error) error {
    57  	return &ParseError{
    58  		Line:   r.lineNum,
    59  		Record: r.recordName,
    60  		Err:    err,
    61  	}
    62  }
    63  
    64  // addCurrentCashLetter creates the current cash letter for the file being read. A successful
    65  // currentCashLetter will be added to r.File once parsed.
    66  func (r *Reader) addCurrentCashLetter(cashLetter CashLetter) {
    67  	r.currentCashLetter = cashLetter
    68  }
    69  
    70  // addCurrentBundle creates the CurrentBundle for the file being read. A successful
    71  // currentBundle will be added to r.File once parsed.
    72  func (r *Reader) addCurrentBundle(bundle *Bundle) {
    73  	r.currentCashLetter.currentBundle = bundle
    74  }
    75  
    76  // addCurrentRoutingNumberSummary creates the CurrentRoutingNumberSummary for the file being read. A successful
    77  // currentRoutingNumberSummary will be added to r.File once parsed.
    78  func (r *Reader) addCurrentRoutingNumberSummary(rns *RoutingNumberSummary) {
    79  	r.currentCashLetter.currentRoutingNumberSummary = rns
    80  }
    81  
    82  // NewReader returns a new ACH Reader that reads from r.
    83  func NewReader(r io.Reader, opts ...ReaderOption) *Reader {
    84  	f := NewFile()
    85  	f.Control = FileControl{}
    86  	reader := &Reader{
    87  		File:       *f,
    88  		scanner:    bufio.NewScanner(r),
    89  		decodeLine: Passthrough,
    90  	}
    91  	for _, opt := range opts {
    92  		opt(reader)
    93  	}
    94  	return reader
    95  }
    96  
    97  // DecodeLineFn is used to decode a scanned line into desired encoding.
    98  // Depending on X9 spec, cashletter could be encoded as ASCII or EBCDIC
    99  type DecodeLineFn func(lineIn string) (lineOut string, err error)
   100  
   101  // Passthrough will return line as is
   102  func Passthrough(lineIn string) (lineOut string, err error) {
   103  	return lineIn, nil
   104  }
   105  
   106  // DecodeEBCDIC will decode a line from EBCDIC-0037 to UTF-8
   107  func DecodeEBCDIC(lineIn string) (string, error) {
   108  	lineOut, err := encoding.EBCDIC.NewDecoder().String(lineIn)
   109  	if err != nil {
   110  		return "", fmt.Errorf("error decoding '%X' as EBCDIC: %v\n", lineIn, err)
   111  	}
   112  	return lineOut, nil
   113  }
   114  
   115  // ReaderOption can be used to change default behavior of Reader
   116  type ReaderOption func(*Reader)
   117  
   118  // ReadVariableLineLengthOption allows Reader to split imagecashletter files based on encoded line lengths
   119  func ReadVariableLineLengthOption() ReaderOption {
   120  	scanVariableLengthLines := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
   121  		if atEOF && len(data) == 0 {
   122  			return 0, nil, nil
   123  		} else if len(data) < 4 && atEOF {
   124  			// we ran out of bytes and we're at the end of the file
   125  			return 0, nil, io.ErrUnexpectedEOF
   126  		} else if len(data) < 4 {
   127  			// we need at least the control bytes
   128  			return 0, nil, nil
   129  		}
   130  		// line length can be variable
   131  		// use the 4 control bytes at the beginning of a line to determine its length
   132  		ctrl := data[0:4]
   133  		dataLen := int(binary.BigEndian.Uint32(ctrl))
   134  		lineLen := 4 + dataLen
   135  		if lineLen <= len(data) {
   136  			// return line while accounting for control bytes
   137  			return lineLen, data[4:lineLen], nil
   138  		} else if lineLen > len(data) && atEOF {
   139  			// we need more data, but there is no more data to read
   140  			return 0, nil, io.ErrUnexpectedEOF
   141  		}
   142  		// request more data.
   143  		return 0, nil, nil
   144  	}
   145  
   146  	return func(r *Reader) {
   147  		r.scanner.Split(scanVariableLengthLines)
   148  	}
   149  }
   150  
   151  // ReadEbcdicEncodingOption allows Reader to decode scanned lines from EBCDIC to UTF-8
   152  func ReadEbcdicEncodingOption() ReaderOption {
   153  	return func(r *Reader) {
   154  		r.decodeLine = DecodeEBCDIC
   155  	}
   156  }
   157  
   158  // BufferSizeOption creates a byte slice of the specified size and uses it as the buffer
   159  // for the Reader's internal scanner. You may need to set this when processing files that
   160  // contain check details exceeding bufio.MaxScanTokenSize (64 kB).
   161  func BufferSizeOption(size int) ReaderOption {
   162  	return func(r *Reader) {
   163  		r.scanner.Buffer(make([]byte, size), size)
   164  	}
   165  }
   166  
   167  // Read reads each line of the imagecashletter file and defines which parser to use based
   168  // on the first character of each line. It also enforces imagecashletter formatting rules and returns
   169  // the appropriate error if issues are found.
   170  func (r *Reader) Read() (File, error) {
   171  	r.lineNum = 0
   172  	// read through the entire file
   173  	for r.scanner.Scan() {
   174  		r.line = r.scanner.Text()
   175  		r.lineNum++
   176  
   177  		lineLength := len(r.line)
   178  
   179  		if lineLength < 80 {
   180  			msg := fmt.Sprintf(msgRecordLength, lineLength)
   181  			err := &FileError{FieldName: "RecordLength", Value: strconv.Itoa(lineLength), Msg: msg}
   182  			return r.File, r.error(err)
   183  		}
   184  		if err := r.parseLine(); err != nil {
   185  			return r.File, err
   186  		}
   187  	}
   188  
   189  	if scanErr := r.scanner.Err(); scanErr != nil {
   190  		err := &FileError{FieldName: "LineNumber", Value: strconv.Itoa(r.lineNum), Msg: scanErr.Error()}
   191  		return r.File, r.error(err)
   192  	}
   193  
   194  	if (FileHeader{}) == r.File.Header {
   195  		// There must be at least one File Header
   196  		r.recordName = "FileHeader"
   197  		return r.File, r.error(&FileError{Msg: msgFileHeader})
   198  	}
   199  	if (FileControl{}) == r.File.Control {
   200  		// There must be at least one File Control
   201  		r.recordName = "FileControl"
   202  		return r.File, r.error(&FileError{Msg: msgFileControl})
   203  	}
   204  	return r.File, nil
   205  }
   206  
   207  func (r *Reader) parseLine() error { //nolint:gocyclo
   208  	switch r.line[:2] {
   209  	case fileHeaderPos, fileHeaderEbcPos:
   210  		if err := r.parseFileHeader(); err != nil {
   211  			return err
   212  		}
   213  	case cashLetterHeaderPos, cashLetterHeaderEbcPos:
   214  		if err := r.parseCashLetterHeader(); err != nil {
   215  			return err
   216  		}
   217  	case bundleHeaderPos, bundleHeaderEbcPos:
   218  		if err := r.parseBundleHeader(); err != nil {
   219  			return err
   220  		}
   221  	case checkDetailPos, checkDetailEbcPos:
   222  		if err := r.parseCheckDetail(); err != nil {
   223  			return err
   224  		}
   225  	case checkDetailAddendumAPos, checkDetailAddendumAEbcPos:
   226  		if err := r.parseCheckDetailAddendumA(); err != nil {
   227  			return err
   228  		}
   229  	case checkDetailAddendumBPos, checkDetailAddendumBEbcPos:
   230  		if err := r.parseCheckDetailAddendumB(); err != nil {
   231  			return err
   232  		}
   233  	case checkDetailAddendumCPos, checkDetailAddendumCEbcPos:
   234  		if err := r.parseCheckDetailAddendumC(); err != nil {
   235  			return err
   236  		}
   237  	case imageViewDetailPos, imageViewDetailEbcPos:
   238  		if err := r.parseImageViewDetail(); err != nil {
   239  			return err
   240  		}
   241  	case imageViewDataPos, imageViewDataEbcPos:
   242  		if err := r.parseImageViewData(); err != nil {
   243  			return err
   244  		}
   245  	case imageViewAnalysisPos, imageViewAnalysisEbcPos:
   246  		if err := r.parseImageViewAnalysis(); err != nil {
   247  			return err
   248  		}
   249  	case returnDetailPos, returnDetailEbcPos:
   250  		if err := r.parseReturnDetail(); err != nil {
   251  			return err
   252  		}
   253  	case returnAddendumAPos, returnAddendumAPEbcos:
   254  		if err := r.parseReturnDetailAddendumA(); err != nil {
   255  			return err
   256  		}
   257  	case returnAddendumBPos, returnAddendumBEbcPos:
   258  		if err := r.parseReturnDetailAddendumB(); err != nil {
   259  			return err
   260  		}
   261  	case returnAddendumCPos, returnAddendumCEbcPos:
   262  		if err := r.parseReturnDetailAddendumC(); err != nil {
   263  			return err
   264  		}
   265  	case returnAddendumDPos, returnAddendumDEbcPos:
   266  		if err := r.parseReturnDetailAddendumD(); err != nil {
   267  			return err
   268  		}
   269  	case creditPos, creditEbcPos:
   270  		if err := r.parseCredit(); err != nil {
   271  			return err
   272  		}
   273  	case creditItemPos, creditItemEbcPos:
   274  		if err := r.parseCreditItem(); err != nil {
   275  			return err
   276  		}
   277  	case bundleControlPos, bundleControlEbcPos:
   278  		if err := r.parseBundleControl(); err != nil {
   279  			return err
   280  		}
   281  		if r.currentCashLetter.currentBundle == nil {
   282  			r.error(&FileError{Msg: msgFileBundleControl})
   283  		}
   284  		// Add Bundle or ReturnBundle to CashLetter
   285  		if r.currentCashLetter.currentBundle != nil {
   286  			if err := r.currentCashLetter.currentBundle.Validate(); err != nil {
   287  				r.recordName = "Bundles"
   288  				return r.error(err)
   289  			}
   290  			r.currentCashLetter.AddBundle(r.currentCashLetter.currentBundle)
   291  			r.currentCashLetter.currentBundle = new(Bundle)
   292  		}
   293  	case routingNumberSummaryPos, routingNumberSummaryEbcPos:
   294  		if err := r.parseRoutingNumberSummary(); err != nil {
   295  			return err
   296  		}
   297  		r.currentCashLetter.AddRoutingNumberSummary(r.currentCashLetter.currentRoutingNumberSummary)
   298  		r.currentCashLetter.currentRoutingNumberSummary = new(RoutingNumberSummary)
   299  	case cashLetterControlPos, cashLetterControlEbcPos:
   300  		// This is needed for validation od CashLetterControl since SettlementDate
   301  		// is a conditional field and is only available for certain types of CashLetters.
   302  		header := r.currentCashLetter.CashLetterHeader
   303  		if header == nil {
   304  			return errors.New("missing CashLetterHeader")
   305  		}
   306  		if err := r.parseCashLetterControl(); err != nil {
   307  			return err
   308  		}
   309  		if err := r.currentCashLetter.Validate(); err != nil {
   310  			r.recordName = "CashLetters"
   311  			return r.error(err)
   312  		}
   313  		r.File.AddCashLetter(r.currentCashLetter)
   314  		r.currentCashLetter = CashLetter{}
   315  	case fileControlPos, fileControlEbcPos:
   316  		if err := r.parseFileControl(); err != nil {
   317  			return err
   318  		}
   319  	default:
   320  		msg := fmt.Sprintf(msgUnknownRecordType, r.line[:2])
   321  		return r.error(&FileError{FieldName: "recordType", Value: r.line[:2], Msg: msg})
   322  	}
   323  	return nil
   324  }
   325  
   326  // parseFileHeader takes the input record string and parses the FileHeader values
   327  func (r *Reader) parseFileHeader() error {
   328  	r.recordName = "FileHeader"
   329  	if (FileHeader{}) != r.File.Header {
   330  		// There can only be one File Header per File
   331  		r.error(&FileError{Msg: msgFileHeader})
   332  	}
   333  	lineOut, err := r.decodeLine(r.line)
   334  	if err != nil {
   335  		return err
   336  	}
   337  	r.File.Header.Parse(lineOut)
   338  	// Ensure valid FileHeader
   339  	if err := r.File.Header.Validate(); err != nil {
   340  		return r.error(err)
   341  	}
   342  	return nil
   343  }
   344  
   345  // parseCashLetterHeader takes the input record string and parses the CashLetterHeader values
   346  func (r *Reader) parseCashLetterHeader() error {
   347  	r.recordName = "CashLetterHeader"
   348  	if r.currentCashLetter.CashLetterHeader != nil {
   349  		// CashLetterHeader inside of current cash letter
   350  		return r.error(&FileError{Msg: msgFileCashLetterInside})
   351  	}
   352  	lineOut, err := r.decodeLine(r.line)
   353  	if err != nil {
   354  		return err
   355  	}
   356  	clh := NewCashLetterHeader()
   357  	clh.Parse(lineOut)
   358  	// Ensure we have a valid CashLetterHeader
   359  	if err := clh.Validate(); err != nil {
   360  		return r.error(err)
   361  	}
   362  	// Passing CashLetterHeader into NewCashLetter creates a CashLetter
   363  	cl := NewCashLetter(clh)
   364  	r.addCurrentCashLetter(cl)
   365  	return nil
   366  }
   367  
   368  // parseBundleHeader takes the input record string and parses the BundleHeader values
   369  func (r *Reader) parseBundleHeader() error {
   370  	r.recordName = "BundleHeader"
   371  	if r.currentCashLetter.currentBundle != nil {
   372  		// BundleHeader inside of current Bundle
   373  		if r.currentCashLetter.currentBundle.BundleHeader != nil {
   374  			return r.error(&FileError{Msg: msgFileBundleInside})
   375  		}
   376  	}
   377  	// Ensure we have a valid bundle header before building a bundle.
   378  	lineOut, err := r.decodeLine(r.line)
   379  	if err != nil {
   380  		return err
   381  	}
   382  	bh := NewBundleHeader()
   383  	bh.Parse(lineOut)
   384  	if err := bh.Validate(); err != nil {
   385  		return r.error(err)
   386  	}
   387  	// Passing BundleHeader into NewBundle creates a Bundle
   388  	bundle := NewBundle(bh)
   389  	r.addCurrentBundle(bundle)
   390  	return nil
   391  
   392  }
   393  
   394  // parseCheckDetail takes the input record string and parses the CheckDetail values
   395  func (r *Reader) parseCheckDetail() error {
   396  	r.recordName = "CheckDetail"
   397  	if r.currentCashLetter.currentBundle == nil {
   398  		return r.error(&FileError{Msg: msgFileBundleOutside})
   399  	}
   400  	lineOut, err := r.decodeLine(r.line)
   401  	if err != nil {
   402  		return err
   403  	}
   404  	cd := new(CheckDetail)
   405  	cd.Parse(lineOut)
   406  	// Ensure valid CheckDetail
   407  	if err := cd.Validate(); err != nil {
   408  		return r.error(err)
   409  	}
   410  	// Add CheckDetail
   411  	if r.currentCashLetter.currentBundle.BundleHeader != nil {
   412  		r.currentCashLetter.currentBundle.AddCheckDetail(cd)
   413  	}
   414  	return nil
   415  }
   416  
   417  // parseCheckDetailAddendumA takes the input record string and parses the CheckDetailAddendumA values
   418  func (r *Reader) parseCheckDetailAddendumA() error {
   419  	r.recordName = "CheckDetailAddendumA"
   420  	if r.currentCashLetter.currentBundle.GetChecks() == nil {
   421  		msg := fmt.Sprint(msgFileBundleOutside)
   422  		return r.error(&FileError{FieldName: "CheckDetailAddendumA", Msg: msg})
   423  	}
   424  	lineOut, err := r.decodeLine(r.line)
   425  	if err != nil {
   426  		return err
   427  	}
   428  	cdAddendumA := NewCheckDetailAddendumA()
   429  	cdAddendumA.Parse(lineOut)
   430  	if err := cdAddendumA.Validate(); err != nil {
   431  		return r.error(err)
   432  	}
   433  	entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1
   434  	// r.currentCashLetter.currentBundle.Checks[entryIndex].CheckDetailAddendumA = cdAddendumA
   435  	r.currentCashLetter.currentBundle.Checks[entryIndex].AddCheckDetailAddendumA(cdAddendumA)
   436  	return nil
   437  }
   438  
   439  // parseCheckDetailAddendumB takes the input record string and parses the CheckDetailAddendumB values
   440  func (r *Reader) parseCheckDetailAddendumB() error {
   441  	r.recordName = "CheckDetailAddendumB"
   442  	if r.currentCashLetter.currentBundle.GetChecks() == nil {
   443  		msg := fmt.Sprint(msgFileBundleOutside)
   444  		return r.error(&FileError{FieldName: "CheckDetailAddendumB", Msg: msg})
   445  	}
   446  	lineOut, err := r.decodeLine(r.line)
   447  	if err != nil {
   448  		return err
   449  	}
   450  	cdAddendumB := NewCheckDetailAddendumB()
   451  	cdAddendumB.Parse(lineOut)
   452  	if err := cdAddendumB.Validate(); err != nil {
   453  		return r.error(err)
   454  	}
   455  	entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1
   456  	r.currentCashLetter.currentBundle.Checks[entryIndex].AddCheckDetailAddendumB(cdAddendumB)
   457  	return nil
   458  }
   459  
   460  // parseCheckDetailAddendumC takes the input record string and parses the CheckDetailAddendumC values
   461  func (r *Reader) parseCheckDetailAddendumC() error {
   462  	r.recordName = "CheckDetailAddendumC"
   463  	if r.currentCashLetter.currentBundle.GetChecks() == nil {
   464  		msg := fmt.Sprint(msgFileBundleOutside)
   465  		return r.error(&FileError{FieldName: "CheckDetailAddendumC", Msg: msg})
   466  	}
   467  	lineOut, err := r.decodeLine(r.line)
   468  	if err != nil {
   469  		return err
   470  	}
   471  	cdAddendumC := NewCheckDetailAddendumC()
   472  	cdAddendumC.Parse(lineOut)
   473  	if err := cdAddendumC.Validate(); err != nil {
   474  		return r.error(err)
   475  	}
   476  	entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1
   477  	r.currentCashLetter.currentBundle.Checks[entryIndex].AddCheckDetailAddendumC(cdAddendumC)
   478  	return nil
   479  }
   480  
   481  // parseReturnDetail takes the input record string and parses the ReturnDetail values
   482  func (r *Reader) parseReturnDetail() error {
   483  	r.recordName = "ReturnDetail"
   484  	if r.currentCashLetter.currentBundle == nil {
   485  		return r.error(&FileError{Msg: msgFileBundleOutside})
   486  	}
   487  	lineOut, err := r.decodeLine(r.line)
   488  	if err != nil {
   489  		return err
   490  	}
   491  	rd := new(ReturnDetail)
   492  	rd.Parse(lineOut)
   493  	if err := rd.Validate(); err != nil {
   494  		return r.error(err)
   495  	}
   496  	if r.currentCashLetter.currentBundle.BundleHeader != nil {
   497  		r.currentCashLetter.currentBundle.AddReturnDetail(rd)
   498  	}
   499  	return nil
   500  }
   501  
   502  // parseReturnDetailAddendumA takes the input record string and parses the ReturnDetailAddendumA values
   503  func (r *Reader) parseReturnDetailAddendumA() error {
   504  	r.recordName = "ReturnDetailAddendumA"
   505  	if r.currentCashLetter.currentBundle.GetReturns() == nil {
   506  		msg := fmt.Sprint(msgFileBundleOutside)
   507  		return r.error(&FileError{FieldName: "ReturnDetailAddendumA", Msg: msg})
   508  	}
   509  	lineOut, err := r.decodeLine(r.line)
   510  	if err != nil {
   511  		return err
   512  	}
   513  	rdAddendumA := NewReturnDetailAddendumA()
   514  	rdAddendumA.Parse(lineOut)
   515  	if err := rdAddendumA.Validate(); err != nil {
   516  		return r.error(err)
   517  	}
   518  	entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1
   519  	// r.currentCashLetter.currentBundle.Returns[entryIndex].ReturnDetailAddendumA = rdAddendumA
   520  	r.currentCashLetter.currentBundle.Returns[entryIndex].AddReturnDetailAddendumA(rdAddendumA)
   521  	return nil
   522  }
   523  
   524  // parseReturnDetailAddendumB takes the input record string and parses the ReturnDetailAddendumB values
   525  func (r *Reader) parseReturnDetailAddendumB() error {
   526  	r.recordName = "ReturnDetailAddendumB"
   527  	if r.currentCashLetter.currentBundle.GetReturns() == nil {
   528  		msg := fmt.Sprint(msgFileBundleOutside)
   529  		return r.error(&FileError{FieldName: "ReturnDetailAddendumB", Msg: msg})
   530  	}
   531  	lineOut, err := r.decodeLine(r.line)
   532  	if err != nil {
   533  		return err
   534  	}
   535  	rdAddendumB := NewReturnDetailAddendumB()
   536  	rdAddendumB.Parse(lineOut)
   537  	if err := rdAddendumB.Validate(); err != nil {
   538  		return r.error(err)
   539  	}
   540  	entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1
   541  	r.currentCashLetter.currentBundle.Returns[entryIndex].AddReturnDetailAddendumB(rdAddendumB)
   542  	return nil
   543  }
   544  
   545  // parseReturnDetailAddendumC takes the input record string and parses the ReturnDetailAddendumC values
   546  func (r *Reader) parseReturnDetailAddendumC() error {
   547  	r.recordName = "ReturnDetailAddendumC"
   548  	if r.currentCashLetter.currentBundle.GetReturns() == nil {
   549  		msg := fmt.Sprint(msgFileBundleOutside)
   550  		return r.error(&FileError{FieldName: "ReturnDetailAddendumC", Msg: msg})
   551  	}
   552  	lineOut, err := r.decodeLine(r.line)
   553  	if err != nil {
   554  		return err
   555  	}
   556  	rdAddendumC := NewReturnDetailAddendumC()
   557  	rdAddendumC.Parse(lineOut)
   558  	if err := rdAddendumC.Validate(); err != nil {
   559  		return r.error(err)
   560  	}
   561  	entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1
   562  	r.currentCashLetter.currentBundle.Returns[entryIndex].AddReturnDetailAddendumC(rdAddendumC)
   563  	return nil
   564  }
   565  
   566  // parseReturnDetail*AddendumD takes the input record string and parses the ReturnDetail*AddendumD values
   567  func (r *Reader) parseReturnDetailAddendumD() error {
   568  	r.recordName = "ReturnDetailAddendumD"
   569  
   570  	if r.currentCashLetter.currentBundle.GetReturns() == nil {
   571  		msg := fmt.Sprint(msgFileBundleOutside)
   572  		return r.error(&FileError{FieldName: "ReturnDetailAddendumD", Msg: msg})
   573  	}
   574  	lineOut, err := r.decodeLine(r.line)
   575  	if err != nil {
   576  		return err
   577  	}
   578  	rdAddendumD := NewReturnDetailAddendumD()
   579  	rdAddendumD.Parse(lineOut)
   580  	if err := rdAddendumD.Validate(); err != nil {
   581  		return r.error(err)
   582  	}
   583  	entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1
   584  	r.currentCashLetter.currentBundle.Returns[entryIndex].AddReturnDetailAddendumD(rdAddendumD)
   585  	return nil
   586  }
   587  
   588  // parseImageViewDetail takes the input record string and parses the ImageViewDetail values
   589  func (r *Reader) parseImageViewDetail() error {
   590  	r.recordName = "ImageViewDetail"
   591  	if err := r.ImageViewDetail(); err != nil {
   592  		return err
   593  	}
   594  	return nil
   595  }
   596  
   597  // ImageViewDetail takes the input record string and parses ImageViewDetail for a check
   598  func (r *Reader) ImageViewDetail() error {
   599  	if r.currentCashLetter.currentBundle.GetChecks() != nil {
   600  		lineOut, err := r.decodeLine(r.line)
   601  		if err != nil {
   602  			return err
   603  		}
   604  		ivDetail := NewImageViewDetail()
   605  		ivDetail.Parse(lineOut)
   606  		if err := ivDetail.Validate(); err != nil {
   607  			return r.error(err)
   608  		}
   609  		entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1
   610  		r.currentCashLetter.currentBundle.Checks[entryIndex].AddImageViewDetail(ivDetail)
   611  
   612  	} else if r.currentCashLetter.currentBundle.GetReturns() != nil {
   613  		lineOut, err := r.decodeLine(r.line)
   614  		if err != nil {
   615  			return err
   616  		}
   617  		ivDetail := NewImageViewDetail()
   618  		ivDetail.Parse(lineOut)
   619  		if err := ivDetail.Validate(); err != nil {
   620  			return r.error(err)
   621  		}
   622  		entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1
   623  		r.currentCashLetter.currentBundle.Returns[entryIndex].AddImageViewDetail(ivDetail)
   624  	} else {
   625  		msg := fmt.Sprint(msgFileBundleOutside)
   626  		return r.error(&FileError{FieldName: "ImageViewDetail", Msg: msg})
   627  	}
   628  
   629  	return nil
   630  }
   631  
   632  // parseImageViewData takes the input record string and parses the ImageViewData values
   633  func (r *Reader) parseImageViewData() error {
   634  	r.recordName = "ImageViewData"
   635  	if err := r.ImageViewData(); err != nil {
   636  		return err
   637  	}
   638  	return nil
   639  }
   640  
   641  // ImageViewData takes the input record string and parses ImageViewData for a check
   642  func (r *Reader) ImageViewData() error {
   643  	if r.currentCashLetter.currentBundle.GetChecks() != nil {
   644  		ivData := NewImageViewData()
   645  		ivData.ParseAndDecode(r.line, r.decodeLine)
   646  		if err := ivData.Validate(); err != nil {
   647  			return r.error(err)
   648  		}
   649  		entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1
   650  		r.currentCashLetter.currentBundle.Checks[entryIndex].AddImageViewData(ivData)
   651  
   652  	} else if r.currentCashLetter.currentBundle.GetReturns() != nil {
   653  		ivData := NewImageViewData()
   654  		ivData.ParseAndDecode(r.line, r.decodeLine)
   655  		if err := ivData.Validate(); err != nil {
   656  			return r.error(err)
   657  		}
   658  		entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1
   659  		r.currentCashLetter.currentBundle.Returns[entryIndex].AddImageViewData(ivData)
   660  	} else {
   661  		msg := fmt.Sprint(msgFileBundleOutside)
   662  		return r.error(&FileError{FieldName: "ImageViewData", Msg: msg})
   663  	}
   664  
   665  	return nil
   666  }
   667  
   668  // parseImageViewAnalysis takes the input record string and parses ImageViewAnalysis values
   669  func (r *Reader) parseImageViewAnalysis() error {
   670  	r.recordName = "ImageViewAnalysis"
   671  	if err := r.ImageViewAnalysis(); err != nil {
   672  		return err
   673  	}
   674  	return nil
   675  }
   676  
   677  // ImageViewAnalysis takes the input record string and parses ImageViewAnalysis for a check
   678  func (r *Reader) ImageViewAnalysis() error {
   679  	if r.currentCashLetter.currentBundle.GetChecks() != nil {
   680  		lineOut, err := r.decodeLine(r.line)
   681  		if err != nil {
   682  			return err
   683  		}
   684  		ivAnalysis := NewImageViewAnalysis()
   685  		ivAnalysis.Parse(lineOut)
   686  		if err := ivAnalysis.Validate(); err != nil {
   687  			return r.error(err)
   688  		}
   689  		entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1
   690  		r.currentCashLetter.currentBundle.Checks[entryIndex].AddImageViewAnalysis(ivAnalysis)
   691  
   692  	} else if r.currentCashLetter.currentBundle.GetReturns() != nil {
   693  		lineOut, err := r.decodeLine(r.line)
   694  		if err != nil {
   695  			return err
   696  		}
   697  		ivAnalysis := NewImageViewAnalysis()
   698  		ivAnalysis.Parse(lineOut)
   699  		if err := ivAnalysis.Validate(); err != nil {
   700  			return r.error(err)
   701  		}
   702  		entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1
   703  		r.currentCashLetter.currentBundle.Returns[entryIndex].AddImageViewAnalysis(ivAnalysis)
   704  	} else {
   705  		msg := fmt.Sprint(msgFileBundleOutside)
   706  		return r.error(&FileError{FieldName: "ImageViewAnalysis", Msg: msg})
   707  	}
   708  
   709  	return nil
   710  }
   711  
   712  // parseCredit takes the input record string and parses the Credit values
   713  func (r *Reader) parseCredit() error {
   714  	// Current implementation has the credit letter outside the bundle but within the cash letter
   715  	r.recordName = "Credit"
   716  	if r.currentCashLetter.CashLetterHeader == nil {
   717  		return r.error(&FileError{Msg: msgFileCredit})
   718  	}
   719  	lineOut, err := r.decodeLine(r.line)
   720  	if err != nil {
   721  		return err
   722  	}
   723  	cr := new(Credit)
   724  	cr.Parse(lineOut)
   725  	if err := cr.Validate(); err != nil {
   726  		return r.error(err)
   727  	}
   728  	r.currentCashLetter.AddCredit(cr)
   729  	return nil
   730  }
   731  
   732  // parseCreditItem takes the input record string and parses the CreditItem values
   733  func (r *Reader) parseCreditItem() error {
   734  	// Current implementation has the credit letter outside the bundle but within the cash letter
   735  	r.recordName = "CreditItem"
   736  	if r.currentCashLetter.CashLetterHeader == nil {
   737  		return r.error(&FileError{Msg: msgFileCreditItem})
   738  	}
   739  	lineOut, err := r.decodeLine(r.line)
   740  	if err != nil {
   741  		return err
   742  	}
   743  	ci := new(CreditItem)
   744  	ci.Parse(lineOut)
   745  	if err := ci.Validate(); err != nil {
   746  		return r.error(err)
   747  	}
   748  	r.currentCashLetter.AddCreditItem(ci)
   749  	return nil
   750  }
   751  
   752  // parseBundleControl takes the input record string and parses the BundleControl values
   753  func (r *Reader) parseBundleControl() error {
   754  	r.recordName = "BundleControl"
   755  	if r.currentCashLetter.currentBundle == nil || r.currentCashLetter.currentBundle.BundleControl == nil {
   756  		return r.error(&FileError{Msg: msgFileBundleControl})
   757  	}
   758  	lineOut, err := r.decodeLine(r.line)
   759  	if err != nil {
   760  		return err
   761  	}
   762  	r.currentCashLetter.currentBundle.GetControl().Parse(lineOut)
   763  	if err := r.currentCashLetter.currentBundle.GetControl().Validate(); err != nil {
   764  		return r.error(err)
   765  	}
   766  	return nil
   767  }
   768  
   769  // parseRoutingNumberSummary takes the input record string and parses the RoutingNumberSummary values
   770  func (r *Reader) parseRoutingNumberSummary() error {
   771  	r.recordName = "RoutingNumberSummary"
   772  	if r.currentCashLetter.CashLetterHeader == nil {
   773  		return r.error(&FileError{Msg: msgFileRoutingNumberSummary})
   774  	}
   775  	lineOut, err := r.decodeLine(r.line)
   776  	if err != nil {
   777  		return err
   778  	}
   779  	rns := NewRoutingNumberSummary()
   780  	rns.Parse(lineOut)
   781  	if err := rns.Validate(); err != nil {
   782  		return r.error(err)
   783  	}
   784  	return nil
   785  }
   786  
   787  // parseCashLetterControl takes the input record string and parses the CashLetterControl values
   788  func (r *Reader) parseCashLetterControl() error {
   789  	r.recordName = "CashLetterControl"
   790  	if r.currentCashLetter.CashLetterHeader == nil {
   791  		// CashLetterControl without a current CashLetter
   792  		return r.error(&FileError{Msg: msgFileCashLetterControl})
   793  	}
   794  	lineOut, err := r.decodeLine(r.line)
   795  	if err != nil {
   796  		return err
   797  	}
   798  	r.currentCashLetter.GetControl().Parse(lineOut)
   799  	// Ensure valid CashLetterControl
   800  	if err := r.currentCashLetter.GetControl().Validate(); err != nil {
   801  		return r.error(err)
   802  	}
   803  	return nil
   804  }
   805  
   806  // parseFileControl takes the input record string and parses the FileControl values
   807  func (r *Reader) parseFileControl() error {
   808  	r.recordName = "FileControl"
   809  	if (FileControl{}) != r.File.Control {
   810  		// Can be only one file control per file
   811  		return r.error(&FileError{Msg: msgFileControl})
   812  	}
   813  	lineOut, err := r.decodeLine(r.line)
   814  	if err != nil {
   815  		return err
   816  	}
   817  	r.File.Control.Parse(lineOut)
   818  	// Ensure valid FileControl
   819  	if err := r.File.Control.Validate(); err != nil {
   820  		return r.error(err)
   821  	}
   822  	return nil
   823  }