Safe Haskell | Safe |
---|---|
Language | Haskell2010 |
Parser for downloaded OFX files.
This parser was written based on the OFX version 1.03 specification, which is available at
It will probably work on earlier versions of OFX without incident. However, it may or may not not work on newer versions of OFX, which are XML based (this version of OFX is SGML based.)
It will also parse QFX files, which are OFX files with minor proprietary additions by Intuit, the maker of Quicken.
An OFX file consists of three parts: the HTTP headers (which this parser does NOT handle because typically they will not show up in files downloaded to disk), the OFX headers, and the OFX data. This parser handles the OFX headers and the OFX data.
The parser in this module simply parses the tags and data into a
tree, which you can manipulate with other functions. Some functions
are provided to find the transactions in the tree and place them
into a Transaction
type, which is the data you are most likely
interested in. If you are interested in other data you can query
the Tag
tree for what you need.
For example, to read in the filename given on the command line and parse it and print it nicely:
The ofx
package includes two executable files that you can use at
the command line to test the library and see how it works. The
renderTransactions
executable reads an OFX file on standard
input, runs it through the prettyRenderTransactions
function, and
prints the result to standard output. The renderOfx
executable
reads an OFX file on standard input, runs it through the
prettyRenderOfxFile
function, and prints the result to standard
output.
- type Err = Either String
- type HeaderTag = String
- type HeaderValue = String
- data OFXHeader = OFXHeader HeaderTag HeaderValue
- type TagName = String
- type TagData = String
- data Tag = Tag TagName (Either TagData [Tag])
- data OFXFile = OFXFile {}
- find :: TagName -> Tag -> [Tag]
- findPath :: [TagName] -> Tag -> Maybe Tag
- tagData :: Tag -> Maybe TagData
- pathData :: [TagName] -> OFXFile -> Maybe TagData
- findData :: TagName -> Tag -> Maybe TagData
- fiName :: OFXFile -> Maybe TagData
- creditCardNumber :: OFXFile -> Maybe TagData
- bankAccountNumber :: OFXFile -> Maybe TagData
- accountNumber :: OFXFile -> Maybe TagData
- data Transaction = Transaction {
- txTRNTYPE :: TrnType
- txDTPOSTED :: ZonedTime
- txDTUSER :: Maybe ZonedTime
- txDTAVAIL :: Maybe ZonedTime
- txTRNAMT :: String
- txFITID :: String
- txCORRECTFITID :: Maybe String
- txCORRECTACTION :: Maybe CorrectAction
- txSRVRTID :: Maybe String
- txCHECKNUM :: Maybe String
- txREFNUM :: Maybe String
- txSIC :: Maybe String
- txPAYEEID :: Maybe String
- txPayeeInfo :: Maybe (Either String Payee)
- txAccountTo :: Maybe (Either BankAcctTo CCAcctTo)
- txMEMO :: Maybe String
- txCurrency :: Maybe (Either Currency OrigCurrency)
- transaction :: Tag -> Err Transaction
- transactions :: OFXFile -> Err [Transaction]
- data TrnType
- trnType :: TagData -> Maybe TrnType
- data Payee = Payee {}
- payee :: Tag -> Maybe (Err Payee)
- data CorrectAction
- data BankAcctTo = BankAcctTo {}
- bankAcctTo :: Tag -> Maybe (Err BankAcctTo)
- data CCAcctTo = CCAcctTo {}
- ccAcctTo :: Tag -> Maybe (Err CCAcctTo)
- data AcctType
- acctType :: String -> Err AcctType
- data CurrencyData = CurrencyData {}
- currencyData :: Tag -> Err CurrencyData
- data Currency = Currency CurrencyData
- currency :: Tag -> Maybe (Err Currency)
- data OrigCurrency = OrigCurrency CurrencyData
- origCurrency :: Tag -> Maybe (Err OrigCurrency)
- parseOfxFile :: String -> Err OFXFile
- parseTransactions :: String -> Err [Transaction]
- prettyRenderOfxFile :: String -> String
- prettyRenderTransactions :: String -> String
- ofxFile :: Parser OFXFile
- newline :: Parser ()
- escChar :: Parser Char
- header :: Parser OFXHeader
- openingTag :: Parser TagName
- closingTag :: TagName -> Parser ()
- tag :: Parser Tag
- date :: Parser ZonedTime
- time :: Parser (TimeOfDay, TimeZone)
- tzOffset :: Parser TimeZone
- pPayee :: Payee -> Doc
- pTransaction :: Transaction -> Doc
- pTag :: Tag -> Doc
- pHeader :: OFXHeader -> Doc
- pFile :: OFXFile -> Doc
- pEither :: (a -> Doc) -> (b -> Doc) -> Either a b -> Doc
- pMaybe :: (a -> Doc) -> Maybe a -> Doc
- pList :: [Doc] -> Doc
- label :: String -> Doc -> Doc
- pExceptional :: (e -> Doc) -> (a -> Doc) -> Either e a -> Doc
Error handling
type Err = Either String Source #
Error handling. Errors are indicated with a Left String; successes with a Right.
The OFX data tree
type HeaderTag = String Source #
Headers consists of simple tag:value
pairs; this represents the
tag.
type HeaderValue = String Source #
The value in an OFX header.
An OFX file starts with a number of headers, which take the form
tag:value
followed by a newline. These are followed by a blank
line.
The main OFX data consists of a series of tags. OFX 1.03 is SGML, not XML. This means that opening tags need not have closing tags. In OFX, a tag either has data and no child elements, or it has no data and it has child elements.
All the data from an OFX file.
Manipulating the OFX tag tree
find :: TagName -> Tag -> [Tag] Source #
Finds child tags with the given name. When a tag is found, that tag is not searched for further children with the same name.
findPath :: [TagName] -> Tag -> Maybe Tag Source #
Descends through a tree of tags to find a tag at a specific
location in the tree. Fails if any part of the search fails. For
example, to find the financial institution ORG tag, where t
is
the root OFX
tag:
findPath ["SIGNONMSGSRSV1", "SONRS", "FI", "ORG"] t
pathData :: [TagName] -> OFXFile -> Maybe TagData Source #
Goes to a certain path in the tag hierarchy and pulls the requested data, if the tag is present and it is a data tag. For example, to get the name of the financial institution:
pathData ["SIGNONMSGSRSV1", "SONRS", "FI", "ORG"] f
findData :: TagName -> Tag -> Maybe TagData Source #
Finds the first tag (either this tag or any children) that has the given name and that is a data tag (not an aggregate tag.) If no data tag with the given name is found, returns Nothing.
Extracting specific data
fiName :: OFXFile -> Maybe TagData Source #
Gets the name of the financial institution from the FI tag, if available. The OFX spec does not require this tag to be present.
creditCardNumber :: OFXFile -> Maybe TagData Source #
Gets the credit card number, if available. The OFX spec does not require this tag to be present.
bankAccountNumber :: OFXFile -> Maybe TagData Source #
Gets the bank account number, if available. The OFX spec does not require this tag to be present.
accountNumber :: OFXFile -> Maybe TagData Source #
Gets either the credit card or bank account number, if available.
Types to represent specific OFX data
data Transaction Source #
A single STMTTRN, see OFX spec section 11.4.2.3.1. This is most likely what you are interested in after downloading a statement from a bank.
Transaction | |
|
transaction :: Tag -> Err Transaction Source #
Gets a single Transaction from a tag. The tag should be the one named STMTTRN. Fails with an error message if any required field was not present.
transactions :: OFXFile -> Err [Transaction] Source #
Pulls all Transactions from a file. Might fail if the OFX file
does not conform to the specification (or if there are bugs in this
library.) In case of the former, you can manually parse the
transaction information yourself using functions like
pathData
. In case of the latter, please send bugreports :-)
OFX transaction types. These are used in STMTTRN aggregates, see OFX spec section 11.4.2.3.1.1.
TCREDIT | |
TDEBIT | |
TINT | Interest earned or paid (which it is depends on sign of amount) |
TDIV | Dividend |
TFEE | |
TSRVCHG | |
TDEP | Deposit |
TATM | ATM debit or credit (which it is depends on sign of amount) |
TPOS | Point of sale debit or credit (which it is depends on sign of amount) |
TXFER | Transfer |
TCHECK | |
TPAYMENT | Electronic payment |
TCASH | Cash withdrawal |
TDIRECTDEP | Direct deposit |
TDIRECTDEBIT | Merchant initiated debit |
TREPEATPMT | Repeating payment / standing order |
TOTHER |
:: Tag | The tag which contains the PAYEE tag, if there is one. This would typically be a STMTTRN tag. |
-> Maybe (Err Payee) | Nothing if there is no PAYEE tag. Just if a PAYEE tag is found, with a Left if the tag is lacking a required element, or a Right if the tag is successfully parsed. If there is more than one PAYEE tag, only the first one is considered. |
Parses a Payee record from its parent tag.
data CorrectAction Source #
Can be either REPLACE or DELETE.
data BankAcctTo Source #
bankAcctTo :: Tag -> Maybe (Err BankAcctTo) Source #
:: Tag | The tag that contains the data, e.g. CURRENCY or ORIGCURRENCY. |
-> Err CurrencyData |
Parses currency data.
data OrigCurrency Source #
origCurrency :: Tag -> Maybe (Err OrigCurrency) Source #
Running parsers
parseOfxFile :: String -> Err OFXFile Source #
Parses an input file. Returns either an error message or the
resulting OFXFile
.
parseTransactions :: String -> Err [Transaction] Source #
Parses an OFX file and gets the list of Tranasction
.
:: String | File contents to parse |
-> String | Pretty printed result of rending the result of the parse, which
is either an error message or an |
Parses an input file to an OfxFile. Returns a pretty-printed string with the results of the parse.
prettyRenderTransactions Source #
:: String | File contents to parse |
-> String | Pretty printed result of rendering the result of the parse,
which is either an error message or a list of |
Parses an input file to an OfxFile, and then to a list of
Transaction
. Returns a pretty-printed string with the results.
Parsec parsers
Parses either a UNIX or an MS-DOS newline. According to 1.2.2, OFX does not contain any white space between tags. However, since I have seen OFX files that do have whitespace between tags, the parser makes allowance for this.
escChar :: Parser Char Source #
Parses a character, possibly with an escape sequence. The greater-than sign, less-than sign, and ampersand must be entered with escape sequences.
According to OFX spec section 2.3.2.1, ampersands, less-than signs, and greater-than signs must appear as entities. However some banks deliver broken OFX files that do not use entities for ampersands (and possibly for less-than or greater-than signs too, although I have not yet observed such behavior.) There is now an error message that reflects this problem. Client code can filter the OFX data for known offenders before passing it to this library.
openingTag :: Parser TagName Source #
Parses any opening tag. Returns the name of the tag.
closingTag :: TagName -> Parser () Source #
Parses a closing tag with the given name.
Parses any tag. The tag itself must be followed by at least one character: either the next tag if this is an aggregate tag, or the data if this is a data tag. OFX does not allow empty tags.
The OFX spec seems to say that OFX files do not include trailing newlines after tags or data, but I have seen these newlines in QFX files, so this parses optional trailing newlines and spaces.
date :: Parser ZonedTime Source #
Parses an OFX date. Fails if the date is not valid or if there is no date to be parsed.
time :: Parser (TimeOfDay, TimeZone) Source #
Parses an OFX time. Fails if the time is not valid or if there is no time to parse. Fails if there is no time to parse; however, if there is a time but no zone, returns the time and UTC for the zone.
tzOffset :: Parser TimeZone Source #
Parses a time zone offset. Fails if there is no time zone offset to parse.
Pretty printers
pTransaction :: Transaction -> Doc Source #