Task 587: .QIF File Format
Task 587: .QIF File Format
1. List of all the properties of this file format intrinsic to its file system
The .QIF file format is the Quicken Interchange Format, a text-based (ASCII) format for financial data. It is not intrinsically tied to any specific file system properties (such as permissions, timestamps, or metadata like in NTFS or ext4), as it is a portable data format. However, the format itself has structural properties that define its content. These are the key properties (fields/tags) used in the format, which are intrinsic to how the file is structured and parsed:
- !Type: Header indicating the type of data (e.g., !Type:Bank for bank transactions, !Type:CCard for credit card, !Type:Invst for investments, !Type:Cat for categories, !Type:Class for classes, !Type:Memorized for memorized transactions, !Type:Cash for cash accounts, !Type:Oth A for asset accounts, !Type:Oth L for liability accounts, !Type:Invoice for invoices).
- D: Date (format like MM/DD'YY or DD/MM/YYYY, with optional leading zeros).
- T: Amount (numeric value, negative for payments, positive for deposits; no currency symbols).
- U: Amount (identical to T, often present in exported files for compatibility).
- C: Cleared status (blank for unreconciled, * or c for cleared, X or R for reconciled).
- N: Check number, reference, or investment action (e.g., Buy, Sell, Deposit, Transfer, ATM, EFT, or investment-specific like StkSplit, Div, etc.).
- P: Payee or description (for deposits, transfers, etc.).
- M: Memo (arbitrary text).
- A: Address (up to 5 lines for payee address, 6th line for check message).
- L: Category, subcategory (separated by :), class (separated by /), or transfer account (in [brackets]).
- F: Flag for reimbursable business expense.
- S: Split category (same format as L, repeated for each split).
- E: Split memo (repeated for each split).
- $: Split amount (repeated for each split).
- %: Split percentage (optional for percentage-based splits).
- Y: Security name (for investments).
- I: Price (for investments).
- Q: Quantity of shares or split ratio (for investments).
- O: Commission (for investments).
- B: Budget amount (for categories, can be repeated for monthly budgets).
- X: Extended data for Quicken Business (followed by subcode like XA for ship-to address, XI for invoice type, XE for due date, XC for tax account, XR for tax rate, XT for tax amount, XS for line item description, XN for line item category, X# for line item quantity, X$ for line item price, XF for taxable flag).
- ^: End of transaction/record (caret on its own line, signals the end of a transaction block).
The format is line-based, with each field on a separate line starting with the tag character, and transactions separated by ^. Files are plain text, so no binary header or footer; the first line is always the !Type header.
2. Two direct download links for files of format .QIF
- https://www.propersoft.net/test/transactions.qif
- https://www.propersoft.net/test/transactions-split.qif
3. Ghost blog embedded HTML JavaScript for drag and drop .QIF file dump
Here's an HTML page with embedded JavaScript that can be embedded in a Ghost blog post (using the HTML card). It creates a drop area where the user can drag and drop a .QIF file. The script reads the file as text, parses it into transactions, extracts all properties (fields) from each transaction, and dumps them to the screen in a readable format.
4. Python class for .QIF
Here's a Python class QIFHandler that can open a .QIF file, decode/parse it, read the properties, print them to console, and write a new .QIF file from parsed data.
class QIFHandler:
def __init__(self, filename):
self.filename = filename
self.transactions = []
self.header = ''
def read(self):
with open(self.filename, 'r') as f:
content = f.read()
lines = content.splitlines()
current_tx = {}
for line in lines:
line = line.strip()
if line.startswith('!Type:'):
self.header = line
elif line == '^':
if current_tx:
self.transactions.append(current_tx)
current_tx = {}
elif line:
key = line[0]
value = line[1:]
if key in current_tx: # Handle duplicates by overwriting
current_tx[key] = value
else:
current_tx[key] = value
if current_tx:
self.transactions.append(current_tx)
def print_properties(self):
print(f"Header: {self.header}")
for idx, tx in enumerate(self.transactions, 1):
print(f"\nTransaction {idx}:")
for key, value in tx.items():
print(f"{key}: {value}")
def write(self, new_filename):
with open(new_filename, 'w') as f:
f.write(self.header + '\n')
for tx in self.transactions:
for key, value in tx.items():
f.write(f"{key}{value}\n")
f.write('^\n')
# Example usage:
# handler = QIFHandler('sample.qif')
# handler.read()
# handler.print_properties()
# handler.write('output.qif')
5. Java class for .QIF
Here's a Java class QIFHandler that can open a .QIF file, decode/parse it, read the properties, print them to console, and write a new .QIF file.
import java.io.*;
import java.util.*;
public class QIFHandler {
private String filename;
private List<Map<String, String>> transactions = new ArrayList<>();
private String header = "";
public QIFHandler(String filename) {
this.filename = filename;
}
public void read() throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line;
Map<String, String> currentTx = new HashMap<>();
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("!Type:")) {
header = line;
} else if (line.equals("^")) {
if (!currentTx.isEmpty()) {
transactions.add(currentTx);
currentTx = new HashMap<>();
}
} else if (!line.isEmpty()) {
String key = line.substring(0, 1);
String value = line.substring(1);
currentTx.put(key, value); // Overwrite duplicates
}
}
if (!currentTx.isEmpty()) {
transactions.add(currentTx);
}
}
}
public void printProperties() {
System.out.println("Header: " + header);
for (int i = 0; i < transactions.size(); i++) {
System.out.println("\nTransaction " + (i + 1) + ":");
Map<String, String> tx = transactions.get(i);
for (Map.Entry<String, String> entry : tx.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
public void write(String newFilename) throws IOException {
try (PrintWriter writer = new PrintWriter(new FileWriter(newFilename))) {
writer.println(header);
for (Map<String, String> tx : transactions) {
for (Map.Entry<String, String> entry : tx.entrySet()) {
writer.println(entry.getKey() + entry.getValue());
}
writer.println("^");
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// QIFHandler handler = new QIFHandler("sample.qif");
// handler.read();
// handler.printProperties();
// handler.write("output.qif");
// }
}
6. JavaScript class for .QIF
Here's a JavaScript class QIFHandler that can open a .QIF file (using FileReader for browser, or fs for Node), decode/parse it, read the properties, print them to console, and write a new .QIF file (using fs in Node).
class QIFHandler {
constructor(filename) {
this.filename = filename;
this.transactions = [];
this.header = '';
}
async read() {
// For browser, use FileReader; for Node, use fs
const fs = require('fs'); // Node.js
const content = fs.readFileSync(this.filename, 'utf8');
const lines = content.split(/\r?\n/);
let currentTx = {};
lines.forEach(line => {
line = line.trim();
if (line.startsWith('!Type:')) {
this.header = line;
} else if (line === '^') {
if (Object.keys(currentTx).length > 0) {
this.transactions.push(currentTx);
currentTx = {};
}
} else if (line) {
const key = line[0];
const value = line.slice(1);
currentTx[key] = value; // Overwrite duplicates
}
});
if (Object.keys(currentTx).length > 0) {
this.transactions.push(currentTx);
}
}
printProperties() {
console.log(`Header: ${this.header}`);
this.transactions.forEach((tx, index) => {
console.log(`\nTransaction ${index + 1}:`);
for (const key in tx) {
console.log(`${key}: ${tx[key]}`);
}
});
}
write(newFilename) {
const fs = require('fs'); // Node.js
let content = `${this.header}\n`;
this.transactions.forEach(tx => {
for (const key in tx) {
content += `${key}${tx[key]}\n`;
}
content += '^\n';
});
fs.writeFileSync(newFilename, content);
}
}
// Example usage (Node.js):
// const handler = new QIFHandler('sample.qif');
// await handler.read();
// handler.printProperties();
// handler.write('output.qif');
7. C "class" for .QIF
C does not have classes, so here's a struct QIFHandler with functions to open a .QIF file, decode/parse it, read the properties, print them to console, and write a new .QIF file. Compile with gcc file.c -o program.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LINE 256
#define MAX_FIELDS 50
typedef struct {
char key;
char *value;
} Field;
typedef struct {
Field fields[MAX_FIELDS];
int field_count;
} Transaction;
typedef struct {
char *filename;
Transaction *transactions;
int tx_count;
char *header;
} QIFHandler;
QIFHandler* qif_init(const char *filename) {
QIFHandler *handler = malloc(sizeof(QIFHandler));
handler->filename = strdup(filename);
handler->transactions = NULL;
handler->tx_count = 0;
handler->header = NULL;
return handler;
}
void qif_read(QIFHandler *handler) {
FILE *f = fopen(handler->filename, "r");
if (!f) return;
char line[MAX_LINE];
Transaction current_tx = { .field_count = 0 };
int capacity = 0;
while (fgets(line, MAX_LINE, f)) {
line[strcspn(line, "\n")] = 0; // Trim newline
if (strncmp(line, "!Type:", 6) == 0) {
handler->header = strdup(line);
} else if (strcmp(line, "^") == 0) {
if (current_tx.field_count > 0) {
if (handler->tx_count >= capacity) {
capacity = capacity ? capacity * 2 : 4;
handler->transactions = realloc(handler->transactions, capacity * sizeof(Transaction));
}
handler->transactions[handler->tx_count++] = current_tx;
current_tx.field_count = 0;
}
} else if (strlen(line) > 0) {
char key = line[0];
char *value = strdup(line + 1);
current_tx.fields[current_tx.field_count].key = key;
current_tx.fields[current_tx.field_count].value = value;
current_tx.field_count++; // Overwrite not handled; assumes no duplicates
}
}
if (current_tx.field_count > 0) {
handler->transactions[handler->tx_count++] = current_tx;
}
fclose(f);
}
void qif_print_properties(QIFHandler *handler) {
printf("Header: %s\n", handler->header);
for (int i = 0; i < handler->tx_count; i++) {
printf("\nTransaction %d:\n", i + 1);
Transaction tx = handler->transactions[i];
for (int j = 0; j < tx.field_count; j++) {
printf("%c: %s\n", tx.fields[j].key, tx.fields[j].value);
}
}
}
void qif_write(QIFHandler *handler, const char *new_filename) {
FILE *f = fopen(new_filename, "w");
if (!f) return;
fprintf(f, "%s\n", handler->header);
for (int i = 0; i < handler->tx_count; i++) {
Transaction tx = handler->transactions[i];
for (int j = 0; j < tx.field_count; j++) {
fprintf(f, "%c%s\n", tx.fields[j].key, tx.fields[j].value);
}
fprintf(f, "^\n");
}
fclose(f);
}
void qif_free(QIFHandler *handler) {
free(handler->header);
for (int i = 0; i < handler->tx_count; i++) {
for (int j = 0; j < handler->transactions[i].field_count; j++) {
free(handler->transactions[i].fields[j].value);
}
}
free(handler->transactions);
free(handler->filename);
free(handler);
}
// Example usage:
// int main() {
// QIFHandler *handler = qif_init("sample.qif");
// qif_read(handler);
// qif_print_properties(handler);
// qif_write(handler, "output.qif");
// qif_free(handler);
// return 0;
// }