REBOL [
    Title: "Read a Remote Payment and Presentation System file"
    Author: "Steven White"
    File: %rpps.r
    Date: 7-Nov-2011
    Purpose: {This is a module for reading and taking apart a modified
    NACHA file used in the Remote Payment and Presentation System.      
    If those terms mean anything, this could be a useful module.            
    If those terms mean nothing, then at least this module could be an    
    example of a way to handle a text file of fixed-format records.
    In 25 words or less, a NACHA file is a file of fixed-format text
    records containing information about bank transfers.  The records
    are of several different types (headers, detail, trailers)
    differentiated by a code in the first character of each record.}
    library: [
        level: 'beginner
        platform: 'all
        type: [tutorial tool]
        domain: [text-processing file-handling]
        tested-under: none
        support: none
        license: none
        see-also: none
    ]
]

;; [---------------------------------------------------------------------------]
;; [ This module defines a text file in the RPPS format for                    ]
;; [ getting money electronically from the bank.                               ]
;; [ RPPS stands for Remote Payment and Presentment System.                    ] 
;; [ This is a "modified NACHA" format and is not quite the same               ]
;; [ as the file that WE send TO the bank for automatic payments.              ]
;; [---------------------------------------------------------------------------]

;; [---------------------------------------------------------------------------]
;; [ This function accepts a string, a starting position, and an               ]
;; [ ending position, and returns a substring from the starting                ]
;; [ position to the ending position.  If the ending position is -1,           ]
;; [ the procedure returns the substring from the starting position            ]
;; [ to the end of the string.                                                 ]
;; [ This technique was "borrowed" from the REBOL library.                     ]
;; [---------------------------------------------------------------------------]

GLB-SUBSTRING: func [
    "Return a substring from the start position to the end position"
    INPUT-STRING [series!] "Full input string"
    START-POS    [number!] "Starting position of substring"
    END-POS      [number!] "Ending position of substring"
] [
    if END-POS = -1 [END-POS: length? INPUT-STRING]
    return skip (copy/part INPUT-STRING END-POS) (START-POS - 1)
]

;; [---------------------------------------------------------------------------]
;; [ Various data items used in processing the file.                           ]
;; [---------------------------------------------------------------------------]

RPPS-FILE: []                 ;; Holds the whole file in memory
RPPS-FILE-ID: %ACH.txt        ;; Default name of the file
RPPS-EOF: false               ;; End-of-file flag for reading
RPPS-REC: ""                  ;; One record, for reading or writing 
RPPS-REC-COUNT: 0             ;; Counter, upped by 1 as we read or write 

RPPS-CURRENT-TYPE: ""         ;; Type code of record in memory

;; [---------------------------------------------------------------------------]
;; [ When we read a record, we will take it apart in to its                    ]
;; [ fields and store the fields below.  The CURRENT-TYPE field                ]
;; [ indicates which record we currently have our hands on.                    ]
;; [ For those record that contain numbers or amounts, some of the             ]
;; [ numbers or amounts are converted to appropriate data types                ]
;; [ so they can be used in appropriate ways (currency, for example).          ]
;; [---------------------------------------------------------------------------]

RPPS-1-RECORD-TYPE-CODE: ""
RPPS-1-PRIORITY-CODE: ""
RPPS-1-IMMEDIATE-DESTINATION: ""
RPPS-1-IMMEDIATE-ORIGIN: ""
RPPS-1-TRANSMISSION-DATE: ""
RPPS-1-TRANSMISSION-TIME: ""
RPPS-1-FILE-ID-MODIFIER: ""
RPPS-1-RECORD-SIZE: ""
RPPS-1-BLOCKING-FACTOR: ""
RPPS-1-FORMAT-CODE: ""
RPPS-1-DESTINATION-NAME: ""
RPPS-1-ORIGIN: ""
RPPS-1-REFERENCE-CODE: ""

RPPS-5-RECORD-TYPE-CODE: ""
RPPS-5-SERVICE-CLASS-CODE: ""
RPPS-5-BILLER-NAME: ""
RPPS-5-RESERVED: ""
RPPS-5-BILLER-ID-NUMBER: ""
RPPS-5-ENTRY-CLASS: ""
RPPS-5-ENTRY-DESCRIPTION: ""
RPPS-5-DESCRIPTIVE-DATE: ""
RPPS-5-EFFECTIVE-DATE: ""
RPPS-5-SETTLEMENT-DATE: ""
RPPS-5-CONCENTRATOR-STATUS: ""
RPPS-5-RPPS-ID-NUMBER: ""
RPPS-5-BATCH-NUMBER: ""

RPPS-5-EFFECTIVE-DATE-E: ""

RPPS-6-RECORD-TYPE-CODE: ""
RPPS-6-TRANSACTION-CODE: ""
RPPS-6-RPPS-ID-NUMBER: ""
RPPS-6-MERCHANT-ACCOUNT-NBR: ""
RPPS-6-AMOUNT: ""
RPPS-6-CONSUMER-ACCOUNT-NBR: ""
RPPS-6-CONSUMER-NAME: ""
RPPS-6-RPPS-FLAG: ""
RPPS-6-ADDENDUM-RECORD-IND: ""
RPPS-6-TRACE-NUMBER: ""
RPPS-6-SEQUENCE-NUMBER: ""

RPPS-6-AMOUNT-N: ""

RPPS-8-RECORD-TYPE-CODE: ""
RPPS-8-SERVICE-CLASS-CODE: ""
RPPS-8-ENTRY-ADDENDA-COUNT: ""
RPPS-8-ENTRY-HASH: ""
RPPS-8-TOTAL-DEBIT: ""
RPPS-8-TOTAL-CREDIT: ""
RPPS-8-BILLER-ID-NUMBER: ""
RPPS-8-MAC: ""
RPPS-8-FILLER-1: ""
RPPS-8-RPPS-ID-NUMBER: ""
RPPS-8-BATCH-NUMBER: ""

RPPS-8-TOTAL-DEBIT-N: ""
RPPS-8-TOTAL-CREDIT-N: ""

RPPS-9-RECORD-TYPE-CODE: ""
RPPS-9-BATCH-COUNT: ""
RPPS-9-BLOCK-COUNT: ""
RPPS-9-ENTRY-ADDENDA-COUNT: ""
RPPS-9-ENTRY-HASH: ""
RPPS-9-TOTAL-DEBIT: ""
RPPS-9-TOTAL-CREDIT: ""
RPPS-9-FILLER-1: ""

;; [---------------------------------------------------------------------------]
;; [ This procedure reads the whole file into memory and takes off any         ]
;; [ records at the end that might be all "9" (which would be padding          ]
;; [ records).                                                                 ]
;; [---------------------------------------------------------------------------]

RPPS-OPEN-INPUT: does [
    RPPS-FILE: copy []
    RPPS-FILE: read/lines RPPS-FILE-ID
    RPPS-EOF: false
    RPPS-REC-COUNT: 0
    remove-each TEST-STRING RPPS-FILE [= GLB-SUBSTRING TEST-STRING 1 10 "9999999999"]
]

;; [---------------------------------------------------------------------------]
;; [ This procedure "reads" the "file," which, in this situation, means that   ]
;; [ it locates the next line in the in-memory copy of the file, and takes     ]
;; [ that fixed-format line apart into its various data items.                 ]
;; [ If there are no more lines to "read," it sets an end-of-file flag.        ]
;; [---------------------------------------------------------------------------]

RPPS-READ: does [
    RPPS-REC-COUNT: RPPS-REC-COUNT + 1
    RPPS-REC: copy ""
    RPPS-CURRENT-TYPE: copy ""
    RPPS-REC: pick RPPS-FILE RPPS-REC-COUNT  
    if none? RPPS-REC [
        RPPS-EOF: true
    ]
    if not RPPS-EOF [
        RPPS-UNSTRING-RECORD
    ]
]

;; [---------------------------------------------------------------------------]
;; [ This procedure "closes" the file, which means it clears out the           ]
;; [ memory area that held the data.                                           ]
;; [---------------------------------------------------------------------------]

RPPS-CLOSE-INPUT: does [
    RPPS-FILE: copy []
    RPPS-REC: copy ""
]

;; [---------------------------------------------------------------------------]
;; [ This module is incomplete.  It does not include any procedure for         ]
;; [ creating a NACHA file.  The Remote Payment and Presentation System        ]
;; [ seems to be for getting money from the bank anyway, and not going         ]
;; [ after it ourselves.                                                       ]
;; [ If one wanted to create a file, one could reverse the reading process     ]
;; [ by converting the data items to strings of appropriate lengths, and       ]
;; [ joining them all together, with a newline at the end of each.             ]
;; [---------------------------------------------------------------------------]

RPPS-OPEN-OUTPUT: does [
    RPPS-FILE: copy []
    RPPS-REC: copy ""
    RPPS-REC-COUNT: 0
]

RPPS-WRITE: does [
    append RPPS-FILE RPPS-REC
    RPPS-REC-COUNT: RPPS-REC-COUNT + 1
]

RPPS-CLOSE-OUTPUT: does [
    write/lines RPPS-FILE-ID RPPS-FILE
    RPPS-FILE: copy []
    RPPS-REC: copy ""
]

;; [---------------------------------------------------------------------------]
;; [ This procedure is performed after reading each record.                    ]
;; [ It takes apart the fixed-format record into individual data items         ]
;; [ that can be used in whatever ways the caller wants.                       ]
;; [ Records are differentiated by a type code in the first character.         ]
;; [---------------------------------------------------------------------------]

RPPS-UNSTRING-RECORD: does [
    RPPS-CURRENT-TYPE: GLB-SUBSTRING RPPS-REC 1 1
    if RPPS-CURRENT-TYPE = "1" [
        RPPS-UNSTRING-1
    ]
    if RPPS-CURRENT-TYPE = "5" [
        RPPS-UNSTRING-5
    ]
    if RPPS-CURRENT-TYPE = "6" [
        RPPS-UNSTRING-6
    ]
    if RPPS-CURRENT-TYPE = "8" [
        RPPS-UNSTRING-8
    ]
    if RPPS-CURRENT-TYPE = "9" [
        RPPS-UNSTRING-9
    ] 
]

RPPS-UNSTRING-1: does [
    RPPS-1-RECORD-TYPE-CODE: copy ""
    RPPS-1-PRIORITY-CODE: copy ""
    RPPS-1-IMMEDIATE-DESTINATION: copy ""
    RPPS-1-IMMEDIATE-ORIGIN: copy ""
    RPPS-1-TRANSMISSION-DATE: copy ""
    RPPS-1-TRANSMISSION-TIME: copy ""
    RPPS-1-FILE-ID-MODIFIER: copy ""
    RPPS-1-RECORD-SIZE: copy ""
    RPPS-1-BLOCKING-FACTOR: copy ""
    RPPS-1-FORMAT-CODE: copy ""
    RPPS-1-DESTINATION-NAME: copy ""
    RPPS-1-ORIGIN: copy ""
    RPPS-1-REFERENCE-CODE: copy ""
    RPPS-1-RECORD-TYPE-CODE: GLB-SUBSTRING RPPS-REC 1 1
    RPPS-1-PRIORITY-CODE: GLB-SUBSTRING RPPS-REC 2 3
    RPPS-1-IMMEDIATE-DESTINATION: GLB-SUBSTRING RPPS-REC 4 13
    RPPS-1-IMMEDIATE-ORIGIN: GLB-SUBSTRING RPPS-REC 14 23
    RPPS-1-TRANSMISSION-DATE: GLB-SUBSTRING RPPS-REC 24 29
    RPPS-1-TRANSMISSION-TIME: GLB-SUBSTRING RPPS-REC 30 33
    RPPS-1-FILE-ID-MODIFIER: GLB-SUBSTRING RPPS-REC 34 34
    RPPS-1-RECORD-SIZE: GLB-SUBSTRING RPPS-REC 35 37
    RPPS-1-BLOCKING-FACTOR: GLB-SUBSTRING RPPS-REC 38 39
    RPPS-1-FORMAT-CODE: GLB-SUBSTRING RPPS-REC 40 40
    RPPS-1-DESTINATION-NAME: GLB-SUBSTRING RPPS-REC 41 63
    RPPS-1-ORIGIN: GLB-SUBSTRING RPPS-REC 64 86
    RPPS-1-REFERENCE-CODE: GLB-SUBSTRING RPPS-REC 87 94
]

RPPS-UNSTRING-5: does [
    RPPS-5-RECORD-TYPE-CODE: copy ""
    RPPS-5-SERVICE-CLASS-CODE: copy ""
    RPPS-5-BILLER-NAME: copy ""
    RPPS-5-RESERVED: copy ""
    RPPS-5-BILLER-ID-NUMBER: copy ""
    RPPS-5-ENTRY-CLASS: copy ""
    RPPS-5-ENTRY-DESCRIPTION: copy ""
    RPPS-5-DESCRIPTIVE-DATE: copy ""
    RPPS-5-EFFECTIVE-DATE: copy ""
    RPPS-5-SETTLEMENT-DATE: copy ""
    RPPS-5-CONCENTRATOR-STATUS: copy ""
    RPPS-5-RPPS-ID-NUMBER: copy ""
    RPPS-5-BATCH-NUMBER: copy ""
    RPPS-5-RECORD-TYPE-CODE: GLB-SUBSTRING RPPS-REC 1 1
    RPPS-5-SERVICE-CLASS-CODE: GLB-SUBSTRING RPPS-REC 2 4
    RPPS-5-BILLER-NAME: GLB-SUBSTRING RPPS-REC 5 20
    RPPS-5-RESERVED: GLB-SUBSTRING RPPS-REC 21 40
    RPPS-5-BILLER-ID-NUMBER: GLB-SUBSTRING RPPS-REC 41 50
    RPPS-5-ENTRY-CLASS: GLB-SUBSTRING RPPS-REC 51 53
    RPPS-5-ENTRY-DESCRIPTION: GLB-SUBSTRING RPPS-REC 54 63
    RPPS-5-DESCRIPTIVE-DATE: GLB-SUBSTRING RPPS-REC 64 69
    RPPS-5-EFFECTIVE-DATE: GLB-SUBSTRING RPPS-REC 70 75
    RPPS-5-SETTLEMENT-DATE: GLB-SUBSTRING RPPS-REC 76 78
    RPPS-5-CONCENTRATOR-STATUS: GLB-SUBSTRING RPPS-REC 79 79
    RPPS-5-ID-NUMBER: GLB-SUBSTRING RPPS-REC 80 87
    RPPS-5-BATCH-NUMBER: GLB-SUBSTRING RPPS-REC 88 94
    RPPS-5-EFFECTIVE-DATE-E: rejoin [
        GLB-SUBSTRING RPPS-5-EFFECTIVE-DATE 3 4
        "/"
        GLB-SUBSTRING RPPS-5-EFFECTIVE-DATE 5 6
        "/"
        "20"
        GLB-SUBSTRING RPPS-5-EFFECTIVE-DATE 1 2
    ]
]

RPPS-UNSTRING-6: does [
    RPPS-6-RECORD-TYPE-CODE: copy ""
    RPPS-6-TRANSACTION-CODE: copy ""
    RPPS-6-RPPS-ID-NUMBER: copy ""
    RPPS-6-MERCHANT-ACCOUNT-NBR: copy ""
    RPPS-6-AMOUNT: copy ""
    RPPS-6-CONSUMER-ACCOUNT-NBR: copy ""
    RPPS-6-CONSUMER-NAME: copy ""
    RPPS-6-RPPS-FLAG: copy ""
    RPPS-6-ADDENDUM-RECORD-IND: copy ""
    RPPS-6-TRACE-NUMBER: copy ""
    RPPS-6-SEQUENCE-NUMBER: copy ""
    RPPS-6-RECORD-TYPE-CODE: GLB-SUBSTRING RPPS-REC 1 1
    RPPS-6-TRANSACTION-CODE: GLB-SUBSTRING RPPS-REC 2 3
    RPPS-6-RPPS-ID-NUMBER: GLB-SUBSTRING RPPS-REC 4 12
    RPPS-6-MERCHANT-ACCOUNT-NBR: GLB-SUBSTRING RPPS-REC 13 29
    RPPS-6-AMOUNT: GLB-SUBSTRING RPPS-REC 30 39
    RPPS-6-CONSUMER-ACCOUNT-NBR: GLB-SUBSTRING RPPS-REC 40 54
    RPPS-6-CONSUMER-NAME: GLB-SUBSTRING RPPS-REC 55 76
    RPPS-6-RPPS-FLAG: GLB-SUBSTRING RPPS-REC 77 78
    RPPS-6-ADDENDUM-RECORD-IND: GLB-SUBSTRING RPPS-REC 79 79
    RPPS-6-TRACE-NUMBER: GLB-SUBSTRING RPPS-REC 80 87
    RPPS-6-SEQUENCE-NUMBER: GLB-SUBSTRING RPPS-REC 88 94
    RPPS-6-AMOUNT-N: to-decimal divide to-decimal RPPS-6-AMOUNT 100
]

RPPS-UNSTRING-8: does [
    RPPS-8-RECORD-TYPE-CODE: copy ""
    RPPS-8-SERVICE-CLASS-CODE: copy ""
    RPPS-8-ENTRY-ADDENDA-COUNT: copy ""
    RPPS-8-ENTRY-HASH: copy ""
    RPPS-8-TOTAL-DEBIT: copy ""
    RPPS-8-TOTAL-CREDIT: copy ""
    RPPS-8-BILLER-ID-NUMBER: copy ""
    RPPT-8-MAC: copy ""
    RPPS-8-FILLER-1: copy ""
    RPPS-8-RPPS-ID-NUMBER: copy ""
    RPPS-8-BATCH-NUMBER: copy ""
    RPPS-8-RECORD-TYPE-CODE: GLB-SUBSTRING RPPS-REC 1 1
    RPPS-8-SERVICE-CLASS-CODE: GLB-SUBSTRING RPPS-REC 2 4
    RPPS-8-ENTRY-ADDENDA-COUNT: GLB-SUBSTRING RPPS-REC 5 10
    RPPS-8-ENTRY-HASH: GLB-SUBSTRING RPPS-REC 11 20
    RPPS-8-TOTAL-DEBIT: GLB-SUBSTRING RPPS-REC 21 32
    RPPS-8-TOTAL-CREDIT: GLB-SUBSTRING RPPS-REC 33 44
    RPPS-8-BILLER-ID-NUMBER: GLB-SUBSTRING RPPS-REC 45 54
    RPPS-8-MAC: GLB-SUBSTRING RPPS-REC 55 73
    RPPS-8-FILLER-1: GLB-SUBSTRING RPPS-REC 74 79
    RPPS-8-RPPS-ID-NUMBER: GLB-SUBSTRING RPPS-REC 80 87
    RPPS-8-BATCH-NUMBER: GLB-SUBSTRING RPPS-REC 88 94
    RPPS-8-TOTAL-DEBIT-N: divide to-decimal RPPS-8-TOTAL-DEBIT 100
    RPPS-8-TOTAL-CREDIT-N: divide to-decimal RPPS-8-TOTAL-CREDIT 100  
]

RPPS-UNSTRING-9: does [
    RPPS-9-RECORD-TYPE-CODE: copy "" 
    RPPS-9-BATCH-COUNT: copy "" 
    RPPS-9-BLOCK-COUNT: copy "" 
    RPPS-9-ENTRY-ADDENDA-COUNT: copy "" 
    RPPS-9-ENTRY-HASH: copy "" 
    RPPS-9-TOTAL-DEBIT: copy "" 
    RPPS-9-TOTAL-CREDIT: copy "" 
    RPPS-9-FILLER-1: copy "" 
    RPPS-9-RECORD-TYPE-CODE: GLB-SUBSTRING RPPS-REC 1 1
    RPPS-9-BATCH-COUNT: GLB-SUBSTRING RPPS-REC 2 7
    RPPS-9-BLOCK-COUNT: GLB-SUBSTRING RPPS-REC 8 13
    RPPS-9-ENTRY-ADDENDA-COUNT: GLB-SUBSTRING RPPS-REC 14 21
    RPPS-9-ENTRY-HASH: GLB-SUBSTRING RPPS-REC 22 31
    RPPS-9-TOTAL-DEBIT: GLB-SUBSTRING RPPS-REC 32 43
    RPPS-9-TOTAL-CREDIT: GLB-SUBSTRING RPPS-REC 44 55
    RPPS-9-FILLER-1: GLB-SUBSTRING RPPS-REC 56 94
]

;; -----------------------------------------------------------------------------