Hi, I'm going to share some code with you that you wrote.  There are three input file types:  canada helps, efts, and etransfers, and one output file type.  the canada helps and efts i've tested and they work fine.  the etransfers however, when i look at the output file, it appears that there is still some trouble parsing out the name from the "Description" column of the input file.  From what i can tell by looking at the output file, the code seems to do a good job of ignoring any rows until it gets to the headers, and it processes all lines from the input file that in the "Description" column, begin with "Telephone/Online banking".  The code also parses out the correct "Last Name" in the output file column, however in the "First Name" column of the output file, all it shows is "Telephone/Online".  The format of the data "Description" column from the input file looks like this: Telephone/Online banking , INTERAC ETRNSFR AD RECVD "First Name" "Last Name"
I think I've described things correctly, let me know if you have any questions.  Here is the code: <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Bank Statement Parser</title>
    <!-- Include Papa Parse CDN -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
    <!-- Include Bootstrap for Styling -->
    <link 
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" 
        rel="stylesheet">
    <style>
        body {
            padding: 20px;
        }
        .table-responsive {
            max-height: 500px;
            overflow-y: auto;
        }
        .exception-row {
            background-color: #f8d7da;
        }
        .fixed-header th {
            position: sticky;
            top: 0;
            background: #fff;
            z-index: 2;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1 class="mb-4">Bank Statement Parser</h1>
        
        <!-- File Type Selection -->
        <div class="mb-3">
            <label for="fileType" class="form-label">Select File Type:</label>
            <select class="form-select" id="fileType">
                <option value="">-- Select File Type --</option>
                <option value="canadaHelps">Canada Helps</option>
                <option value="efts">EFTs</option>
                <option value="etransfers">eTransfers</option>
            </select>
        </div>
        
        <!-- File Upload -->
        <div class="mb-3">
            <label for="csvFile" class="form-label">Upload CSV File:</label>
            <input class="form-control" type="file" id="csvFile" accept=".csv">
        </div>
        
        <!-- Parsed Data Table -->
        <div id="tableContainer" class="mb-3" style="display: none;">
            <h3>Parsed Data</h3>
            <div class="table-responsive">
                <table class="table table-bordered" id="dataTable">
                    <thead class="fixed-header">
                        <tr>
                            <th>#</th>
                            <th>First Name</th>
                            <th>Last Name</th>
                            <th>Date</th>
                            <th>Fund</th>
                            <th>Source</th>
                            <th>Amount</th>
                            <th>Method</th>
                            <th>Source Detail</th>
                            <th>Check Number</th>
                            <th>CC Last Four</th>
                            <th>Notes</th>
                            <th>Batch Number</th>
                            <th>ID</th>
                        </tr>
                    </thead>
                    <tbody id="dataBody">
                        <!-- Rows will be populated here -->
                    </tbody>
                </table>
            </div>
        </div>
        
        <!-- Download Button -->
        <button id="downloadBtn" class="btn btn-primary" style="display: none;">Download Output CSV</button>
    </div>

    <script>
        document.getElementById('csvFile').addEventListener('change', function(e) {
            const file = e.target.files[0];
            const fileType = document.getElementById('fileType').value;

            if (!fileType) {
                alert('Please select a file type before uploading.');
                return;
            }

            if (!file) {
                alert('No file selected.');
                return;
            }

            console.log(`File selected: ${file.name}, File Type: ${fileType}`);

            Papa.parse(file, {
                header: true,
                skipEmptyLines: true,
                complete: function(results) {
                    console.log('Parsing completed. Data:', results.data);
                    processData(results.data, fileType);
                },
                error: function(err) {
                    console.error('Error parsing CSV file:', err);
                    alert('Error parsing CSV file: ' + err.message);
                },
                beforeFirstChunk: function(chunk) {
                    if (fileType === 'etransfers') {
                        // Remove any lines before the header for eTransfers
                        const lines = chunk.split(/\r\n|\n/);
                        let headerIndex = lines.findIndex(line => 
                            line.includes('Posted') && 
                            line.includes('Value Date') && 
                            line.includes('Description') && 
                            line.includes('Debit') && 
                            line.includes('Credit')
                        );
                        if (headerIndex !== -1) {
                            // Return the chunk starting from header
                            return lines.slice(headerIndex).join('\n');
                        } else {
                            // If header not found, return original chunk
                            return chunk;
                        }
                    }
                    return chunk;
                }
            });
        });

        function processData(data, fileType) {
            if (!data || data.length === 0) {
                alert('No data found in CSV.');
                return;
            }

            const dataBody = document.getElementById('dataBody');
            dataBody.innerHTML = ''; // Clear previous data

            let processedData = [];

            switch(fileType) {
                case 'canadaHelps':
                    processedData = processCanadaHelps(data);
                    break;
                case 'efts':
                    processedData = processEFTs(data);
                    break;
                case 'etransfers':
                    processedData = processETransfers(data);
                    break;
                default:
                    alert('Unsupported file type selected.');
                    return;
            }

            if (processedData.length === 0) {
                alert('No valid data to display.');
                return;
            }

            // Populate the table
            processedData.forEach((row, index) => {
                const isException = !row.nameParsed || !row.firstName || !row.lastName;
                const tr = document.createElement('tr');
                if (isException) tr.classList.add('exception-row');

                // Row Number
                const tdNum = document.createElement('td');
                tdNum.textContent = index + 1;
                tr.appendChild(tdNum);

                // First Name
                const tdFirst = document.createElement('td');
                if (row.nameParsed && row.firstName) {
                    tdFirst.textContent = row.firstName;
                } else {
                    const inputFirst = document.createElement('input');
                    inputFirst.type = 'text';
                    inputFirst.className = 'form-control';
                    inputFirst.placeholder = 'First Name';
                    tdFirst.appendChild(inputFirst);
                }
                tr.appendChild(tdFirst);

                // Last Name
                const tdLast = document.createElement('td');
                if (row.nameParsed && row.lastName) {
                    tdLast.textContent = row.lastName;
                } else {
                    const inputLast = document.createElement('input');
                    inputLast.type = 'text';
                    inputLast.className = 'form-control';
                    inputLast.placeholder = 'Last Name';
                    tdLast.appendChild(inputLast);
                }
                tr.appendChild(tdLast);

                // Date
                const tdDate = document.createElement('td');
                tdDate.textContent = row.date || '';
                tr.appendChild(tdDate);

                // Fund Dropdown
                const tdFund = document.createElement('td');
                const selectFund = document.createElement('select');
                selectFund.className = 'form-select';
                ['General', 'Youth', 'Benevolent', 'Stewardship', 'Ministry'].forEach(fund => {
                    const option = document.createElement('option');
                    option.value = fund;
                    option.textContent = fund;
                    selectFund.appendChild(option);
                });
                tdFund.appendChild(selectFund);
                tr.appendChild(tdFund);

                // Source Dropdown
                const tdSource = document.createElement('td');
                const selectSource = document.createElement('select');
                selectSource.className = 'form-select';
                ['ACH', 'Cash', 'Cheque', 'EFT'].forEach(source => {
                    const option = document.createElement('option');
                    option.value = source;
                    option.textContent = source;
                    selectSource.appendChild(option);
                });
                tdSource.appendChild(selectSource);
                tr.appendChild(tdSource);

                // Amount
                const tdAmount = document.createElement('td');
                tdAmount.textContent = row.amount ? parseFloat(row.amount).toFixed(2) : '0.00';
                tr.appendChild(tdAmount);

                // Method
                const tdMethod = document.createElement('td');
                tdMethod.textContent = row.method || '';
                tr.appendChild(tdMethod);

                // Source Detail
                const tdSourceDetail = document.createElement('td');
                tdSourceDetail.textContent = ''; // Empty as per requirement
                tr.appendChild(tdSourceDetail);

                // Check Number
                const tdCheckNumber = document.createElement('td');
                tdCheckNumber.textContent = ''; // Empty as per requirement
                tr.appendChild(tdCheckNumber);

                // CC Last Four
                const tdCCLastFour = document.createElement('td');
                tdCCLastFour.textContent = ''; // Empty as per requirement
                tr.appendChild(tdCCLastFour);

                // Notes
                const tdNotes = document.createElement('td');
                tdNotes.textContent = ''; // Empty as per requirement
                tr.appendChild(tdNotes);

                // Batch Number
                const tdBatch = document.createElement('td');
                tdBatch.textContent = ''; // Empty as per requirement
                tr.appendChild(tdBatch);

                // ID
                const tdID = document.createElement('td');
                tdID.textContent = ''; // Empty as per requirement
                tr.appendChild(tdID);

                dataBody.appendChild(tr);
            });

            document.getElementById('tableContainer').style.display = 'block';
            document.getElementById('downloadBtn').style.display = 'block';
        }

        function processCanadaHelps(data) {
            // Required headers for Canada Helps
            const requiredHeaders = ['DONOR FIRST NAME', 'DONOR LAST NAME', 'AMOUNT', 'DONATION DATE'];
            const headers = Object.keys(data[0]);

            // Normalize headers to handle case insensitivity and trim spaces
            const normalizedHeaders = headers.map(header => header.trim().toUpperCase());
            const normalizedRequired = requiredHeaders.map(header => header.trim().toUpperCase());

            const missingHeaders = normalizedRequired.filter(header => !normalizedHeaders.includes(header));

            if (missingHeaders.length > 0) {
                alert('Missing required headers for Canada Helps: ' + missingHeaders.join(', '));
                console.error('Missing required headers for Canada Helps:', missingHeaders);
                return [];
            }

            // Create a mapping from normalized header to actual header for case-insensitive access
            const headerMap = {};
            headers.forEach((header, index) => {
                headerMap[header.trim().toUpperCase()] = header;
            });

            return data.map(row => {
                return {
                    nameParsed: true, // Assuming name is always parsed correctly for Canada Helps
                    firstName: row[headerMap['DONOR FIRST NAME']].trim(),
                    lastName: row[headerMap['DONOR LAST NAME']].trim(),
                    date: formatDate(row[headerMap['DONATION DATE']].trim()),
                    amount: sanitizeAmount(row[headerMap['AMOUNT']].trim()),
                    method: 'Credit' // Assuming Canada Helps donations are credits
                };
            });
        }

        function processEFTs(data) {
            // Required headers for EFTs
            const requiredHeaders = ['Payee/Payer Name', 'Amount', 'Value Date'];
            const headers = Object.keys(data[0]);

            // Normalize headers to handle case insensitivity and trim spaces
            const normalizedHeaders = headers.map(header => header.trim().toUpperCase());
            const normalizedRequired = requiredHeaders.map(header => header.trim().toUpperCase());

            // Check for missing headers
            const missingHeaders = normalizedRequired.filter(header => !normalizedHeaders.includes(header));

            if (missingHeaders.length > 0) {
                const originalMissing = missingHeaders.map(missing => {
                    // Attempt to find the original header (case-insensitive)
                    const originalHeader = headers.find(header => header.trim().toUpperCase() === missing);
                    return originalHeader || missing;
                });
                alert('Missing required headers for EFTs: ' + originalMissing.join(', '));
                console.error('Missing required headers for EFTs:', originalMissing);
                return [];
            }

            // Create a mapping from normalized header to actual header for case-insensitive access
            const headerMap = {};
            headers.forEach((header, index) => {
                headerMap[header.trim().toUpperCase()] = header;
            });

            return data.map(row => {
                const payeePayerName = row[headerMap['PAYEE/PAYER NAME']].trim();
                const amountRaw = row[headerMap['AMOUNT']].trim();
                const valueDate = formatDate(row[headerMap['VALUE DATE']].trim());

                const parsedName = parseName(payeePayerName);

                // Sanitize amount to remove any non-numeric characters
                const amount = sanitizeAmount(amountRaw);

                return {
                    nameParsed: parsedName.success,
                    firstName: parsedName.firstName,
                    lastName: parsedName.lastName,
                    date: valueDate,
                    amount: amount,
                    method: 'EFT' // Assuming EFT method
                };
            }).filter(row => row && row.amount !== ''); // Filter out null entries and rows without amount
        }

        function processETransfers(data) {
            // Required headers for eTransfers
            const requiredHeaders = ['Posted', 'Value Date', 'Description', 'Debit', 'Credit'];
            const headers = Object.keys(data[0]);

            // Normalize headers to handle case insensitivity and trim spaces
            const normalizedHeaders = headers.map(header => header.trim().toUpperCase());
            const normalizedRequired = requiredHeaders.map(header => header.trim().toUpperCase());

            const missingHeaders = normalizedRequired.filter(header => !normalizedHeaders.includes(header));

            if (missingHeaders.length > 0) {
                const originalMissing = missingHeaders.map(missing => {
                    const originalHeader = headers.find(header => header.trim().toUpperCase() === missing);
                    return originalHeader || missing;
                });
                alert('Missing required headers for eTransfers: ' + originalMissing.join(', '));
                console.error('Missing required headers for eTransfers:', originalMissing);
                return [];
            }

            // Create a mapping from normalized header to actual header for case-insensitive access
            const headerMap = {};
            headers.forEach((header, index) => {
                headerMap[header.trim().toUpperCase()] = header;
            });

            return data.map(row => {
                const description = row[headerMap['DESCRIPTION']].trim();
                const creditRaw = row[headerMap['CREDIT']].trim();
                const valueDate = formatDate(row[headerMap['VALUE DATE']].trim());

                // Define flexible keywords to match various description formats
                const keywords = ['INTERAC ETRNSFR AD RECV', 'INTERAC ETRNSFR AD RECVD'];

                // Find which keyword is present in the description
                let matchedKeyword = '';
                for (let keyword of keywords) {
                    if (description.toUpperCase().includes(keyword)) {
                        matchedKeyword = keyword;
                        break;
                    }
                }

                if (!matchedKeyword) {
                    // Description does not contain any of the keywords; ignore this row
                    return null;
                }

                // Extract the portion of the description after the matched keyword
                const regex = new RegExp(`.*${matchedKeyword}\\s+`, 'i');
                const namePart = description.replace(regex, '').trim();

                if (!namePart) {
                    console.warn('No name found after keyword in Description:', description);
                    return null;
                }

                const parsedName = parseName(namePart);

                // Sanitize amount to remove any non-numeric characters
                const amount = sanitizeAmount(creditRaw || '0.00');

                return {
                    nameParsed: parsedName.success,
                    firstName: parsedName.firstName,
                    lastName: parsedName.lastName,
                    date: valueDate,
                    amount: amount,
                    method: 'Credit' // Assuming eTransfers are credits
                };
            }).filter(row => row !== null); // Remove null entries
        }

        /**
         * Parses a full name and attempts to extract first and last names.
         * Supports various formats: 
         * - Firstname Lastname
         * - Firstname Middlename Lastname
         * - Firstname M. Lastname
         * - Stefano di Matteo
         * - Names with hyphens (e.g., Anne-Marie Smith-Jones)
         * 
         * If the name cannot be reliably parsed, returns success: false.
         * 
         * @param {string} fullName 
         * @returns {Object} { success: boolean, firstName: string, lastName: string }
         */
        function parseName(fullName) {
            // Trim any extra spaces
            const name = fullName.trim();

            // Use Regular Expressions to capture names with various formats
            // This regex captures first name, optional middle name/initial, and last name
            const nameRegex = /^([A-Za-z\-]+)\s+(?:[A-Za-z]\.\s+)?([A-Za-z\-]+)$/;

            const match = name.match(nameRegex);
            if (match) {
                return {
                    success: true,
                    firstName: match[1],
                    lastName: match[2]
                };
            } else {
                // If regex does not match, attempt to split by space and assign accordingly
                const nameParts = name.split(/\s+/);
                if (nameParts.length >= 2) {
                    const firstName = nameParts[0];
                    const lastName = nameParts.slice(-1)[0];
                    return {
                        success: true,
                        firstName: firstName,
                        lastName: lastName
                    };
                } else {
                    // Unable to parse the name reliably
                    console.warn('Unable to parse name:', fullName);
                    return { success: false };
                }
            }
        }

        /**
         * Sanitizes the amount string by removing any non-numeric characters except '.' and '-'
         * @param {string} amountStr 
         * @returns {string} Cleaned amount string
         */
        function sanitizeAmount(amountStr) {
            return amountStr.replace(/[^0-9.-]+/g, '');
        }

        /**
         * Formats a date string into YYYY-MM-DD using UTC to prevent timezone shifts.
         * If invalid, returns the original string.
         * @param {string} dateStr 
         * @returns {string}
         */
        function formatDate(dateStr) {
            const date = new Date(dateStr);
            if (isNaN(date)) {
                console.warn('Invalid date format:', dateStr);
                return dateStr; // Return original if invalid
            }
            const year = date.getUTCFullYear();
            const month = String(date.getUTCMonth() + 1).padStart(2, '0');
            const day = String(date.getUTCDate()).padStart(2, '0');
            return `${year}-${month}-${day}`;
        }

        document.getElementById('downloadBtn').addEventListener('click', function() {
            const table = document.getElementById('dataTable');
            const rows = table.querySelectorAll('tr');
            const output = [];

            // Define the headers for the output CSV
            const headers = ["First Name","Last Name","", "Date","Fund","Source","Amount","Method","Source Detail","Check Number","CC Last Four","Notes","Batch Number","ID"];
            output.push(headers);

            // Iterate over table rows
            for (let i = 1; i < rows.length; i++) { // Start from 1 to skip header
                const cols = rows[i].querySelectorAll('td');
                const rowData = [];

                // First Name
                const firstNameInput = cols[1].querySelector('input');
                const firstName = firstNameInput ? firstNameInput.value.trim() : cols[1].textContent.trim();
                rowData.push(firstName);

                // Last Name
                const lastNameInput = cols[2].querySelector('input');
                const lastName = lastNameInput ? lastNameInput.value.trim() : cols[2].textContent.trim();
                rowData.push(lastName);

                rowData.push(""); // Empty column for third field

                // Date
                rowData.push(cols[3].textContent.trim());

                // Fund
                const fundSelect = cols[4].querySelector('select');
                const fund = fundSelect ? fundSelect.value : '';
                rowData.push(fund);

                rowData.push(""); // Fund Number

                // Amount
                rowData.push(cols[6].textContent.trim());

                // Method
                rowData.push(cols[7].textContent.trim());

                // Source
                const sourceSelect = cols[5].querySelector('select');
                const source = sourceSelect ? sourceSelect.value : '';
                rowData.push(source);

                // Check Number
                rowData.push(cols[8].textContent.trim());

                // CC Last Four
                rowData.push(cols[9].textContent.trim());

                // Notes
                rowData.push(cols[10].textContent.trim());

                // Batch Number
                rowData.push(cols[11].textContent.trim());

                // ID
                rowData.push(cols[12].textContent.trim());

                output.push(rowData);
            }

            // Convert to CSV
            const csv = Papa.unparse(output);

            // Create a blob and download link
            const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'output.csv';
            a.click();
            URL.revokeObjectURL(url);
        });
    </script>
</body>
</html>