diff --git a/docs/csv.js b/docs/csv.js new file mode 100644 index 0000000..6854ff2 --- /dev/null +++ b/docs/csv.js @@ -0,0 +1,95 @@ +// Account transactions CSV export format parsing + +/* eslint-disable import/extensions */ + +import { stripCommasAndSpaces } from './util.js'; + +export function csv2csv(inputCSV) { + // Split the input CSV into lines + const lines = inputCSV.split('\n'); + lines.shift(); + + // Process the CSV data + const processedLines = lines.map(line => { + const row = [] + const parts = csvToArray(line); + + if (parts.length > 0) { + const infoColumnParts = parts[13].split(' '); + let payee = null; + let memo = getCard(infoColumnParts); + if (memo !== null) { + payee = getPayee(infoColumnParts); + } else { + payee = parts[8]; + memo = parts[13] + " | " + parts[12]; + } + + row.push(parts[1]); + row.push(stripCommasAndSpaces(payee)); + row.push(stripCommasAndSpaces(memo)); + row.push(parts[2].replace(/,/g, '.')); + } + + return row; + }); + + // Convert the processed lines back to a CSV string + const processedCSV = processedLines.map(row => row.join(',')).join('\n'); + return 'Date,Payee,Memo,Amount\n' + processedCSV; +} + +function getCard(parts) { + if (isValidCardFormat(parts[0])) { + return parts[0]; + } + return null; +} + +function getPayee(parts) { + const payee = [] + for (let i = parts.length - 1; i >= 0; i--) { + if (isAmountWithCurrency(parts[i]) || isTime(parts[i])) { + return payee.join(' '); + } + payee.unshift(parts[i]); + } + return null; +} + +function isValidCardFormat(str) { + // Regex to match the pattern: starting with 6 digits, followed by 6 asterisks, and ending with 4 digits + const regex = /^\d{6}\*{6}\d{4}$/; + return regex.test(str); +} + +function isAmountWithCurrency(str) { + const regex = /^\d+(\.\d{1,2})?[A-Z]{3}$/; + return regex.test(str); +} + +function isTime(str) { + const regex = /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/; + return regex.test(str); +} + +// https://stackoverflow.com/a/8497474 +function csvToArray(text) { + var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/; + var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g; + // Return NULL if input string is not well formed CSV string. + if (!re_valid.test(text)) return null; + var a = []; // Initialize array to receive values. + text.replace(re_value, // "Walk" the string using replace with callback. + function(m0, m1, m2, m3) { + // Remove backslash from \' in single quoted values. + if (m1 !== undefined) a.push(m1.replace(/\\'/g, "'")); + // Remove backslash from \" in double quoted values. + else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"')); + else if (m3 !== undefined) a.push(m3); + return ''; // Return empty string. + }); + // Handle special case of empty last value. + if (/,\s*$/.test(text)) a.push(''); + return a; +}; diff --git a/docs/index.html b/docs/index.html index 17bf9a8..4fa539f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -41,8 +41,8 @@
CSV bude stiahnuté automaticky.
diff --git a/docs/tb2ynab.js b/docs/tb2ynab.js index d703670..d32304b 100644 --- a/docs/tb2ynab.js +++ b/docs/tb2ynab.js @@ -3,6 +3,7 @@ // https://medium.com/@mattlag/es6-modules-getting-started-gotchas-2ad154f38e2e import { xml2csv } from './camt053sk.js'; +import { csv2csv } from './csv.js'; // FIXME poor man's error handling function error(text) { @@ -10,14 +11,25 @@ function error(text) { console.error(text); } -function parse(xmlText, fileName) { - const csv = xml2csv(xmlText); +function parse(text, file) { + let fileName; + let csv; + if (file.type == "text/xml") { + fileName = file.name.replace('.xml', '.ynab.csv'); + csv = xml2csv(text) + } else if (file.type == "text/csv") { + fileName = file.name.replace('.csv', '.ynab.csv'); + csv = csv2csv(text); + } else { + error('Incorrect file type'); + return; + } // https://code-maven.com/create-and-download-csv-with-javascript const hiddenElement = document.createElement('a'); hiddenElement.href = `data:text/csv;charset=utf-8,${encodeURI(csv)}`; hiddenElement.target = '_blank'; - hiddenElement.download = fileName.replace('.xml', '.csv'); + hiddenElement.download = fileName; hiddenElement.click(); } @@ -27,12 +39,15 @@ function fileListener() { if (allFiles.length === 0) { error('No file selected'); return; } const file = allFiles[0]; - if (file.type !== 'text/xml') { error('Incorrect file type'); return; } + if (file.type !== 'text/xml' && file.type !== 'text/csv' ) { + error('Incorrect file type'); + return; + } if (file.size > 2 * 1024 * 1024) { error('Exceeded size 2MB'); return; } const reader = new FileReader(); reader.addEventListener('load', (e) => { - parse(e.target.result, file.name); + parse(e.target.result, file); }); reader.addEventListener('error', () => { error('Failed to read file'); }); reader.readAsText(file);