Safe Haskell | None |
---|---|
Language | Haskell2010 |
A module for parsing or rendering QIF files.
QIF is a fairly braindead format designed for transfering financial data between applications. If you're writing a new financial application, you might want to find a newer format to share with other applications (if you can find one), and you definitely shouldn't use this as the database. For one thing, this format uses two-digit years, which is just kind of lazy. Also, it enforces absolutely no consistency constraints in terms of cross-references or transaction sums.
To parse a QIF file, I suggest using Data.Attoparsec.Text.Lazy and a lazy
Text
data structure, as follows:
do txt <- Text.pack fmap
readFile "my.qif"
case parse parseQIF txt of
Fail around _ err ->
fail ("Parse error (" ++ err ++ ") around :" ++
show (Text.take 10 around))
Done _ res ->
somethingInteresting res
To render a QIF file, you can run the builders from Data.Text.LazyBuilder directly, as so:
Data.Text.Lazy.IO.writeFile "my.qif" (toLazyText (renderQIF myQIF))
- data QIF
- emptyQIF :: QIF
- qifAccounts :: Lens' QIF [Account]
- qifCategories :: Lens' QIF [Category]
- qifSecurities :: Lens' QIF [Security]
- qifInvestmentTransactions :: Lens' QIF [(Account, [InvTransaction])]
- qifNormalTransactions :: Lens' QIF [(Account, [Transaction])]
- parseQIF :: Parser QIF
- renderQIF :: QIF -> Builder
- parseAccountList :: Parser [Account]
- renderAccountList :: [Account] -> Builder
- parseCategoryList :: Parser [Category]
- renderCategoryList :: [Category] -> Builder
- parseBankEntryList :: Parser [Transaction]
- renderBankEntryList :: [Transaction] -> Builder
- parseInvestmentEntries :: Parser [InvTransaction]
- renderInvestmentEntries :: [InvTransaction] -> Builder
- parseCashEntryList :: Parser [Transaction]
- renderCashEntryList :: [Transaction] -> Builder
- parseCreditCardEntryList :: Parser [Transaction]
- renderCreditCardEntryList :: [Transaction] -> Builder
- parseAssetEntryList :: Parser [Transaction]
- renderAssetEntryList :: [Transaction] -> Builder
- parseLiabilityEntryList :: Parser [Transaction]
- renderLiabilityEntryList :: [Transaction] -> Builder
- parseSecurityList :: Parser [Security]
- renderSecurityList :: [Security] -> Builder
- data Account
- emptyAccount :: Account
- accountName :: Lens' Account Text
- accountType :: Lens' Account AccountType
- accountDescription :: Lens' Account Text
- accountCreditLimit :: Lens' Account (Maybe Currency)
- accountBalanceDate :: Lens' Account (Maybe Day)
- accountBalance :: Lens' Account Currency
- parseAccount :: Parser Account
- renderAccount :: Account -> Builder
- data AccountType
- parseAccountType :: Parser AccountType
- renderAccountType :: AccountType -> Builder
- parseShortAccountType :: Parser AccountType
- renderShortAccountType :: AccountType -> Builder
- parseAccountHeader :: Parser Account
- renderAccountHeader :: Account -> Builder
- data CategoryKind
- data Category
- emptyCategory :: Category
- catName :: Lens' Category Text
- catDescription :: Lens' Category Text
- catKind :: Lens' Category CategoryKind
- catIsTaxRelated :: Lens' Category Bool
- catBudgetAmount :: Lens' Category (Maybe Currency)
- catTaxScheduleInfo :: Lens' Category (Maybe Word)
- parseCategory :: Parser Category
- renderCategory :: Category -> Builder
- data SplitItem
- emptySplitItem :: SplitItem
- entryMemo :: Lens' SplitItem Text
- entryAmount :: Lens' SplitItem Currency
- entryCategory :: Lens' SplitItem Text
- parseSplit :: SplitItem -> Parser SplitItem
- renderSplit :: SplitItem -> Builder
- data Transaction
- emptyTransaction :: Transaction
- entDate :: Lens' Transaction Day
- entParty :: Lens' Transaction Text
- entMemo :: Lens' Transaction Text
- entAmount :: Lens' Transaction Currency
- entNumber :: Lens' Transaction (Maybe Word)
- entCategory :: Lens' Transaction (Maybe Text)
- entCleared :: Lens' Transaction Bool
- entReimbursable :: Lens' Transaction Bool
- entSplits :: Lens' Transaction [SplitItem]
- parseTransaction :: Parser Transaction
- renderTransaction :: Transaction -> Builder
- data TradeInfo
- emptyTrade :: Day -> TradeInfo
- tradeDate :: Lens' TradeInfo Day
- tradeSecurity :: Lens' TradeInfo Text
- tradeSharePrice :: Lens' TradeInfo (Maybe Currency)
- tradeQuantity :: Lens' TradeInfo (Maybe ShareQuantity)
- tradeCommission :: Lens' TradeInfo (Maybe Currency)
- tradeTotalAmount :: Lens' TradeInfo Currency
- data TransferInfo
- emptyTransfer :: Day -> TransferInfo
- transDate :: Lens' TransferInfo Day
- transSummary :: Lens' TransferInfo Text
- transMemo :: Lens' TransferInfo Text
- transAmount :: Lens' TransferInfo Currency
- transCleared :: Lens' TransferInfo Bool
- transAccount :: Lens' TransferInfo Text
- transSplits :: Lens' TransferInfo [SplitItem]
- data InvTransaction
- invEntDate :: Lens' InvTransaction Day
- parseInvTransaction :: Parser InvTransaction
- renderInvTransaction :: InvTransaction -> Builder
- data SecurityType
- = Stock
- | Bond
- | CD
- | MutualFund
- | Index
- | ETF
- | MoneyMarket
- | PreciousMetal
- | Commodity
- | StockOption
- | Other
- parseSecurityType :: Parser SecurityType
- renderSecurityType :: SecurityType -> Builder
- data Security
- emptySecurity :: Security
- secName :: Lens' Security Text
- secTicker :: Lens' Security Text
- secType :: Lens' Security SecurityType
- secGoal :: Lens' Security (Maybe Text)
- parseSecurity :: Parser Security
- renderSecurity :: Security -> Builder
- type Currency = Fixed E2
- parseCurrency :: Parser Currency
- renderCurrency :: Bool -> Currency -> Builder
- type ShareQuantity = Fixed E4
- parseShareQuantity :: Parser ShareQuantity
- renderShareQuantity :: ShareQuantity -> Builder
- parseQuantity :: HasResolution a => Parser (Fixed a)
- renderQuantity :: HasResolution a => Fixed a -> Builder
- parseDate :: Parser Day
- renderDate :: Day -> Builder
Documentation
The semantic content of a QIF file. (Explicitly this: very little semantic processing has gone into this data structure, and it could contain semantic errors in the underlying file. Checking for these things is your job.)
qifAccounts :: Lens' QIF [Account] Source #
The accounts associated with the QIF file. We hope. You might expect that
there would be an invariant that qifAccounts
would be the same as map
fst
qifInvestmentTransactions
++
map
fst
qifNormalTransactions
.
I would, and it'd be nice if you tried to maintain that in your code. But,
unfortuntely, there's nothing in the QIF file format that requires this. So
you should probably be careful, and make sure you handle the case in which
this item mentions accounts not seen anywhere else, and the case in which
qifNormalTransactions
and qifInvestmentTransactions
suddenly invent new
accounts.
qifCategories :: Lens' QIF [Category] Source #
The list of categories saved in this QIF file. Like qifAccounts
, there
doesn't seem to be anything enforcing consistency in the actual QIF file. So
you may find that this list mentions categories not referenced elsewhere --
which is not necessarily too surprising -- but also that there may be
transactions that mention new categories unlisted in this field.
qifSecurities :: Lens' QIF [Security] Source #
A cached list of securities. As with the other fields, be warned, as this is not required to be complete, as far as I can tell.
qifInvestmentTransactions :: Lens' QIF [(Account, [InvTransaction])] Source #
A list of investment accounts and the transactions associated with those
accounts. Typically each Account
should reference an account in
qifAccount
and include exactly the same date, but there's nothing in the
file structure that enforces this invariant.
qifNormalTransactions :: Lens' QIF [(Account, [Transaction])] Source #
A list of non-investment accounts and the transactions associated with them.
Again, one might expect that each Account
here should reference an account
in qifAccount
, and contain exactly the same data, but there's nothing in
the file structure that enforces this constraint.
parseQIF :: Parser QIF Source #
Parse a QIF file. This function is purely a syntactic parse, and makes no attempt to verify that the data it parses makes sense. So please be a bit paranoid with all the numbers and strings you receive, and perform any validation you need on your own. Also, this function assumes that it is parsing only a QIF file, and that it should run to the end of the input.
renderQIF :: QIF -> Builder Source #
Render out a QIF File. Because it's the order I've seen in my early example QIF files, this renders in the following order: account list, category list, investment accounts and their transactions, non-investment accounts and their transactions, and then security lists.
Various lists in QIF
parseAccountList :: Parser [Account] Source #
Parse the list of accounts associated with this QIF file.
renderAccountList :: [Account] -> Builder Source #
Render the list of accounts associated with this QIF file.
parseCategoryList :: Parser [Category] Source #
Parse the list of categories (and the header for said list).
renderCategoryList :: [Category] -> Builder Source #
Render the header for the list of categories followed by each of the categories.
parseBankEntryList :: Parser [Transaction] Source #
Parse a list of bank transactions. You should probably call this directly
after parseAccountHeader
and discovering that it's a Bank
account. You
should also not trust the results of this, as it does no consistency checking
on your behalf.
renderBankEntryList :: [Transaction] -> Builder Source #
Render a list of bank transactions. Please do any consistency checking you
want before calling this. You probably also want to have called
renderAccountHeader
with an appropriate Bank
account before calling this
one.
parseInvestmentEntries :: Parser [InvTransaction] Source #
Parse a list of investment entries. You probably should've called
parseAccountHeader
right before this and found an investment account.
renderInvestmentEntries :: [InvTransaction] -> Builder Source #
Render a list of investment transactions. You should probably have just
called renderAccountHeader
with an investment account.
parseCashEntryList :: Parser [Transaction] Source #
Parse a list of cash transactions. You should probably have just called
parseAccountHeader
and found a Cash
account. You should probably also
be a bit paranoid about checking over the date you read, as we perform no
semantic checks on your behalf.
renderCashEntryList :: [Transaction] -> Builder Source #
Render a list of cash transactions. You should have just called
renderAccountHeader
with a Cash
account.
parseCreditCardEntryList :: Parser [Transaction] Source #
Parse a list of credit card transactions. You should probably have just
called parseAccountHeader
and found a Cash
account. You should probably
also be a bit paranoid about checking over the date you read, as we perform
no semantic checks on your behalf.
renderCreditCardEntryList :: [Transaction] -> Builder Source #
Render a list of credit card transactions. You should have just called
renderAccountHeader
with a CreditCard
account.
parseAssetEntryList :: Parser [Transaction] Source #
Parse a list of transactions in an asset account. Again, you probably should
have just called parseAccountHeader
and found an Asset
account, and you
should make sure to do any data validation you care about. Because this
library just doesn't care.
renderAssetEntryList :: [Transaction] -> Builder Source #
Render a list of transactions on an asset. Did you call
renderAccountHeader
before this with an asset account? You should have!
parseLiabilityEntryList :: Parser [Transaction] Source #
Last one! Parse a list of transactions about a liability. Probably a loan,
which you may or may not regret. You *will* regret it, however, if you didn't
call parseAccountHeader
first and find a Liability
account. You will also
regret it if you don't do some input validation on what you get from this
function.
renderLiabilityEntryList :: [Transaction] -> Builder Source #
Render a list of transactions about a liability, probably right after you
called renderAccountHeader
with a liability account.
parseSecurityList :: Parser [Security] Source #
Parse a list of securities out of the QIF file.
renderSecurityList :: [Security] -> Builder Source #
Render a list of securities.
Account Information
An account in the QIF file. This same structure applies for all the account types.
emptyAccount :: Account Source #
A blank account. Defaults to BankAccount
for the type, with the obvious
zeros, empty strings, and Nothings elsewhere.
accountType :: Lens' Account AccountType Source #
The type of the account
accountDescription :: Lens' Account Text Source #
The description of the account; in my limited experience this can (and most likely will) be empty.
accountCreditLimit :: Lens' Account (Maybe Currency) Source #
For accounts with limits, the credit limit for the account.
accountBalanceDate :: Lens' Account (Maybe Day) Source #
The date at which the balance in the next field was current.
parseAccount :: Parser Account Source #
Parse an account.
renderAccount :: Account -> Builder Source #
Render an account.
data AccountType Source #
The type of an account; should be fairly self-explanatory.
parseAccountType :: Parser AccountType Source #
Parse a fully-rendered account type (e.g, "!Type:Bank"), used for section headings.
renderAccountType :: AccountType -> Builder Source #
Render a fully-rendered account type (e.g., "!Type:Bank"), used for section headings.
parseShortAccountType :: Parser AccountType Source #
Parse the short version of an account type (e.g., Bank), which is used in a couple different places.
renderShortAccountType :: AccountType -> Builder Source #
Render the short version of an account type (e.g., "Bank").
parseAccountHeader :: Parser Account Source #
Sections full of transactions start with the header demarcating what account
the transactions are in regard to. This parses that header, returning the
account. Note that if you were expecting to be a somewhat reasonable
standard, and just reference a previously-defined account, you're in for a
disappointment. This is a completely fresh Account
structure, and you'll
have to match things up (and merge any differences) yourself.
renderAccountHeader :: Account -> Builder Source #
Render the header that should proceed any list of transactions.
Category Information
data CategoryKind Source #
Whether a category is an income category or an expense category.
Information about a category that one might mark a transaction against.
emptyCategory :: Category Source #
A blank category. We default categories to Expense
.
catDescription :: Lens' Category Text Source #
A description of the category in question. Often empty.
catBudgetAmount :: Lens' Category (Maybe Currency) Source #
A budget amount, if a budget has been established and published.
catTaxScheduleInfo :: Lens' Category (Maybe Word) Source #
A number describing the tax schedule to look at.
parseCategory :: Parser Category Source #
Parse a category.
renderCategory :: Category -> Builder Source #
Render a category.
Transaction Information
When a single transaction is split across a couple categories, this is your friend.
emptySplitItem :: SplitItem Source #
An empty SplitItem
. No texts, no money. So sad.
parseSplit :: SplitItem -> Parser SplitItem Source #
Parse a split. Note that some banking programs may end up emitting empty
splits, and we don't do anything about that. So you might want to check if
what you get back is emptyTransaction
, or something morally similar.
renderSplit :: SplitItem -> Builder Source #
Render a split. Please be sensible in what you emit; this function won't check your work for you.
Standard Transactions (Bank, Credit Card, etc.)
data Transaction Source #
A normal transaction, that doesn't include an action in the stock market.
emptyTransaction :: Transaction Source #
A transaction with no real data, that happened to occur on January 1st, 2000. Happy new year!
entCategory :: Lens' Transaction (Maybe Text) Source #
The category associated with the transaction, if provided.
entCleared :: Lens' Transaction Bool Source #
Whether or not this transaction has cleared.
entReimbursable :: Lens' Transaction Bool Source #
Whether or not this transaction is reimbursable.
parseTransaction :: Parser Transaction Source #
Parse a transaction. Note that this function only does parsing, not consistency checking. Thus, you may end up with a transaction whose splits do not sum to the total transaction amount, or is missing a category, etc.
renderTransaction :: Transaction -> Builder Source #
Render a transaction. This function assumes that you have performed any consistency checking you're going to do before writing out this transaction. It won't do any for you.
Investment Account Transactions
Trade Information
Information about a given trade.
emptyTrade :: Day -> TradeInfo Source #
Build an empty trade made on a given day.
tradeSecurity :: Lens' TradeInfo Text Source #
The security this trade was about. Note that while we probably should be doing some input validation on this, we're not. So if you're consuming this value, be a bit paranoid.
tradeSharePrice :: Lens' TradeInfo (Maybe Currency) Source #
The share price of the security during the trade, if provided.
tradeQuantity :: Lens' TradeInfo (Maybe ShareQuantity) Source #
The amount of the share traded, if provided.
tradeCommission :: Lens' TradeInfo (Maybe Currency) Source #
The annoying commission taken out of the trade, if provided. Note that QIF does differentiate between Nothing and (Just 0.00), for some reason.
Transfer Information
data TransferInfo Source #
Information about a transfer into an investment account. This probably looks like a normal transaction in a non-investment account, and each one probably has a sibling that is exactly that.
emptyTransfer :: Day -> TransferInfo Source #
An empty transfer that occurred on the given day.
transSummary :: Lens' TransferInfo Text Source #
A summary of the transfer. Sometimes the other party in the transfer, or just a short name, and sometimes blank.
transMemo :: Lens' TransferInfo Text Source #
A memo or note about the transaction. Often blank, in our limited experience.
transAmount :: Lens' TransferInfo Currency Source #
The amount of the transfer.
transCleared :: Lens' TransferInfo Bool Source #
Whether or not the transfer has cleared.
transAccount :: Lens' TransferInfo Text Source #
The account with which this transaction took place ... usually. Sometimes this is empty. Make of that as you will.
transSplits :: Lens' TransferInfo [SplitItem] Source #
Any splits associated with the transaction.
Actual Investment Actions
data InvTransaction Source #
An action in an investment account. These are the ones I've seen in QIF files shown to me. If you run into other ones, please file a bug or submit a patch.
invEntDate :: Lens' InvTransaction Day Source #
The date of an investment account action, regardless of what kind of transaction it was.
parseInvTransaction :: Parser InvTransaction Source #
Parse an investment transaction. Like it's sister function,
parseTransaction
, this function doesn't do any semantic validation. So it's
possible that the date in the transaction doesn't make any sense. So ...
that's on you.
renderInvTransaction :: InvTransaction -> Builder Source #
Render an investment transaction. As you might expect, this doesn't check your work. So be careful.
Security Types
data SecurityType Source #
The kinds of securities QIF files will reference.
parseSecurityType :: Parser SecurityType Source #
Parse a security type.
renderSecurityType :: SecurityType -> Builder Source #
Render a security type.
The information QIF keeps about a security.
emptySecurity :: Security Source #
An empty security, forlorn and alone, with no name, no ticker, and no goals. Definitely a stock, though.
secTicker :: Lens' Security Text Source #
The ticker symbol for the security. If I was a better person this would do some validation on the input.
secGoal :: Lens' Security (Maybe Text) Source #
The goal for the security. I think this is for things like "Buying a house" or "Saving for college", but I've never actually seen this used in the wild.
parseSecurity :: Parser Security Source #
Parse a security. Performs no validation that the name makes sense, the ticker makes sense, or that the two go together. Good luck with that.
renderSecurity :: Security -> Builder Source #
Render a security. You should probably make sure that your data structure makes sense before you write it, but that's your thing. This function won't judget you.
Fixed-width quantities
type Currency = Fixed E2 Source #
A fixed width implementation of currency, based on the U.S. dollar. Future versions of this library that wish to support other currencies may wish to change this, or to abstract the rest of the library over a currency type.
parseCurrency :: Parser Currency Source #
Parse a currency. This is slightly differentiated from parseQuantity
in
that it will happily ignore a dollar sign placed in the correct location.
Note that this will support negative amounts written as either "-$500" or
as "$-500".
renderCurrency :: Bool -> Currency -> Builder Source #
Render a currency. The boolean state whether or not to include a dollar sign. When dollar signs are included, negatives are written as "-$500" rather than "$-500".
type ShareQuantity = Fixed E4 Source #
A fixed-width implementation of quantities for shares. So far, I have seen sites report share quantities to up to four decimal points, henced the value.
parseShareQuantity :: Parser ShareQuantity Source #
Parse a share quantity. Currently an alias for parseQuantity
.
renderShareQuantity :: ShareQuantity -> Builder Source #
Render a share quantity. Currently an alias for renderQuantity
.
parseQuantity :: HasResolution a => Parser (Fixed a) Source #
Parse a fixed-width number. Should parse negative values, as well. This does support QIF's annoying "5." notation, as well.
renderQuantity :: HasResolution a => Fixed a -> Builder Source #
Render a quantity. As opposed to the parser, this output function will always represent numbers to their full precision.
Old-school dates
parseDate :: Parser Day Source #
Parse a date, using old-school, incredibly unwise, "mmddyy" formats. To simplify my life, this assumes that all dates start in 2000, rather than in 1970 or some other date. Thus, if you have data going back before 2000, you'll need to post-process this to the appropriate date, by subtracting 100 appropriately. Hopefully by 2100 noone will be using QIF anymore, and this won't matter.
renderDate :: Day -> Builder Source #
Render the date in QIF's silly format.