Skip to content

Instantly share code, notes, and snippets.

@jason-s13r
Last active July 16, 2019 23:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jason-s13r/04805514b1fc07310000e8c770b6486c to your computer and use it in GitHub Desktop.
Save jason-s13r/04805514b1fc07310000e8c770b6486c to your computer and use it in GitHub Desktop.
This is qif2json.ts is a minor refactor to bring Typescript support to the qif2json package available on NPM. It is the basic QIF parsing without any other requirements from Node (fs) or NPM (iconv, jschardet). This means that it is possible to do the QIF parsing in the browser.

This is qif2json.ts is a minor refactor to bring Typescript support to the qif2json package available on NPM. It is the basic QIF parsing without any other requirements from Node (fs) or NPM (iconv, jschardet). This means that it is possible to do the QIF parsing in the browser.

I've written my own QIF parser on StackBlitz which is an improvement in terms of QIF support. Perhaps not as great performance, meh. Can see what it does using this demo.

/* Adapted by Jason Schwarzenberger
* for Typescript from
* qif2json
* https://github.com/spmason/qif2json
*
* Copyright (c) 2012 Steve Mason
* Licensed under the MIT license.
*/
function parseDate(str: string, format: string) {
const [day, month, year]: any[] = str.replace(' ', '').split(/[^0-9]/);
const output: {
year: number;
month: string;
day: string;
} = { year: 0, month: '00', day: '00' };
const yearNow = new Date().getFullYear();
const yearInt = parseInt(year, 10);
const year1900 = 1900 + yearInt;
const year2000 = 2000 + yearInt;
output.day = day.length < 2 ? `0${day}` : day;
output.month = month.length < 2 ? `0${month}` : day;
output.year = year;
if (year.length <= 2) {
output.year = year2000 > yearNow ? year1900 : year2000;
}
if (format === 'us') {
return `${output.year}-${output.day}-${output.month}`;
}
return `${output.year}-${output.month}-${output.day}`;
}
export interface QifOptions {
dateFormat?: string;
ignoreType?: boolean;
}
export interface Qif {
transactions: Transaction[];
type: string;
}
export interface Division {
amount: number;
category: string;
description: string;
subcategory: string;
}
export interface Transaction {
date: string;
amount: number;
number: string;
memo: string;
address: string[];
payee: string;
category: string;
subcategory: string;
clearedStatus: string;
division: Division[];
[key: string]: any;
}
export const parser = (qif: string, options: QifOptions = <QifOptions>{ ignoreType: false }): Qif => {
const lines = qif.split('\n');
let line = lines.shift();
let transaction: Transaction = <Transaction>{};
let division: Division = <Division>{};
const typeArray = /!Type:([^$]*)$/.exec(line!.trim()) || [];
if (!typeArray || !typeArray.length) {
if (!options.ignoreType) {
throw new Error('File does not appear to be a valid qif file: ' + line);
}
typeArray[1] = line!.trim();
}
const type = typeArray[1];
const transactions: Transaction[] = [];
// tslint:disable-next-line: no-conditional-assignment
while ((line = lines.shift())) {
line = line.trim();
if (line === '^') {
transactions.push(transaction);
transaction = <Transaction>{};
continue;
}
switch (line[0]) {
case 'D':
transaction.date = parseDate(line.substring(1), options.dateFormat || '');
break;
case 'T':
transaction.amount = parseFloat(line.substring(1).replace(',', ''));
break;
case 'N':
transaction.number = line.substring(1);
break;
case 'M':
transaction.memo = line.substring(1);
break;
case 'A':
transaction.address = (transaction.address || []).concat(line.substring(1));
break;
case 'P':
transaction.payee = line.substring(1).replace(/&amp;/g, '&');
break;
case 'L':
const lArray = line.substring(1).split(':');
transaction.category = lArray[0];
if (lArray[1] !== undefined) {
transaction.subcategory = lArray[1];
}
break;
case 'C':
transaction.clearedStatus = line.substring(1);
break;
case 'S':
const sArray = line.substring(1).split(':');
division.category = sArray[0];
if (sArray[1] !== undefined) {
division.subcategory = sArray[1];
}
break;
case 'E':
division.description = line.substring(1);
break;
case '$':
division.amount = parseFloat(line.substring(1));
if (!(transaction.division instanceof Array)) {
transaction.division = [];
}
transaction.division.push(division);
division = <Division>{};
break;
default:
throw new Error('Unknown Detail Code: ' + line[0]);
}
}
if (Object.keys(transaction).length) {
transactions.push(transaction);
}
return <Qif>{
transactions,
type
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment