ofx-0.4.0.0: Parser for OFX data

Safe Haskell | None

Data.OFX

Contents

- Error handling
- The OFX data tree
- Manipulating the OFX tag tree
- Extracting specific data
- Types to represent specific OFX data
- Parsec parsers
- Pretty printers

Description

Parser for downloaded OFX files.

This parser was written based on the OFX version 1.03 specification, which is available at

http://www.ofx.net

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:

     import System.Environment
     import Text.Parsec
     import Text.PrettyPrint
     import Data.OFX
     import System.IO
     import System.Exit

     main :: IO ()
     main = do
       filename:[] <- getArgs
       contents <- readFile filename
       ofx <- case parse ofxFile filename contents of
         Left e -> do
           hPutStrLn stderr . show $ e
           exitFailure
         Right g -> return g
       putStrLn . render . pFile $ ofx
       putStrLn
         . render
         . pExceptional text (pList . map pTransaction)
         . transactions
         $ ofx
       putStrLn . render . pMaybe text . fiName $ ofx
       putStrLn . render . pMaybe text . accountNumber $ ofx

Synopsis

- 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 {
  - fHeader :: [OFXHeader]
  - fTag :: Tag
  }
- 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
  - = TCREDIT
  - | TDEBIT
  - | TINT
  - | TDIV
  - | TFEE
  - | TSRVCHG
  - | TDEP
  - | TATM
  - | TPOS
  - | TXFER
  - | TCHECK
  - | TPAYMENT
  - | TCASH
  - | TDIRECTDEP
  - | TDIRECTDEBIT
  - | TREPEATPMT
  - | TOTHER
- trnType :: TagData -> Maybe TrnType
- data Payee = Payee {
  - peNAME :: String
  - peADDR1 :: String
  - peADDR2 :: Maybe String
  - peADDR3 :: Maybe String
  - peCITY :: String
  - peSTATE :: String
  - pePOSTALCODE :: String
  - peCOUNTRY :: Maybe String
  - pePHONE :: String
  }
- payee :: Tag -> Maybe (Err Payee)
- data CorrectAction
  - = REPLACE
  - | DELETE
- data BankAcctTo = BankAcctTo {
  - btBANKID :: String
  - btBRANCHID :: Maybe String
  - btACCTID :: String
  - btACCTTYPE :: AcctType
  - btACCTKEY :: Maybe String
  }
- bankAcctTo :: Tag -> Maybe (Err BankAcctTo)
- data CCAcctTo = CCAcctTo {
  - ctACCTID :: String
  - ctACCTKEY :: Maybe String
  }
- ccAcctTo :: Tag -> Maybe (Err CCAcctTo)
- data AcctType
  - = ACHECKING
  - | ASAVINGS
  - | AMONEYMRKT
  - | ACREDITLINE
- acctType :: String -> Err AcctType
- data CurrencyData = CurrencyData {
  - cdCURRATE :: String
  - cdCURSYM :: String
  }
- currencyData :: Tag -> Err CurrencyData
- data Currency = Currency CurrencyData
- currency :: Tag -> Maybe (Err Currency)
- data OrigCurrency = OrigCurrency CurrencyData
- origCurrency :: Tag -> Maybe (Err OrigCurrency)
- 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.

data OFXHeader Source

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.

Constructors

OFXHeader HeaderTag HeaderValue |

Instances

Eq OFXHeader |
Show OFXHeader |

type TagName = String Source

The name of an OFX tag

type TagData = String Source

The data accompanying an OFX tag.

data Tag Source

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.

Constructors

Tag TagName (Either TagData [Tag]) |

Instances

Eq Tag |
Show Tag |

data OFXFile Source

All the data from an OFX file.

Constructors

OFXFile |

Fields

fHeader :: [OFXHeader]
fTag :: Tag

  All the data will be contained in a root tag with the TagName `OFX`.

Instances

Eq OFXFile |
Show OFXFile |

# 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

tagData :: Tag -> Maybe TagData Source

Gets the data from a tag, if it is a tag with data.

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.

Constructors

Transaction |

Fields

txTRNTYPE :: TrnType

  Transaction type

txDTPOSTED :: ZonedTime

  Date transaction was posted to account

txDTUSER :: Maybe ZonedTime

  Date user initiated transaction, if known

txDTAVAIL :: Maybe ZonedTime

  Date funds are available

txTRNAMT :: String

  Amount of transaction. This is left as the string that was originally in the download. That means the transaction may include a plus or minus sign (no sign is the same as a plus sign.) According to section 3.2.9.2, amounts are always signed from the perspective of the customer.

  Typically negative amounts:

  - Investment buy amount, investment sell quantity
  - Bank statement debit amounts, checks, fees
  - Credit card purchases
  - Margin balance (unless the institution owes the client money)

  Typically positive amounts:

  - Investment sell amount, investment buy quantity
  - Bank statement credits
  - Credit card payments
  - Ledger balance (unless the account is overdrawn)

  Formats for amounts are described in 3.2.9.1. If there is no decimal point, there is an implied decimal point at the end of the value. Trailing and leading spaces "should" be stripped. Positive or minus is indicated with a leading sign; a plus sign is assumed if there is no sign.

  An amount has a maximum of 32 alphanumeric characters, including digits and punctuation.

  The radix point is indicated with either a period or a comma. Amounts "should" not include any digit grouping characters.

txFITID :: String

  Transaction ID issued by financial institution. Used to detect duplicate downloads.

txCORRECTFITID :: Maybe String

  If present, this indicates the FITID of a previously sent transaction that is corrected by this record. This transaction replaces or deletes the transaction that it corrects, based on the value of CORRECTACTION below.

txCORRECTACTION :: Maybe CorrectAction

  See `CorrectAction` and `txCORRECTFITID`

txSRVRTID :: Maybe String

  Server assigned transaction ID; used for transactions initiated by client, such as payment or funds transfer

txCHECKNUM :: Maybe String

  Check or other reference number

txREFNUM :: Maybe String

  Reference number that uniquely identifies the transaction. Can be used in addition to or instead of a CHECKNUM.

txSIC :: Maybe String

  Standard Industrial Code

txPAYEEID :: Maybe String

  Payee identifier if available

txPayeeInfo :: Maybe (Either String Payee)

  Information on the payee. The OFX spec seems to be saying that every transaction must have either NAME, wich is "name of payee or description of transaction", or the Payee aggregate. The former is indicated with a Left, the latter with a Right.

txAccountTo :: Maybe (Either BankAcctTo CCAcctTo)

  Information on a transfer. If this transaction wa sa transfer, this may contain information about the account being transferred to.

txMEMO :: Maybe String

  Extra information not in NAME

txCurrency :: Maybe (Either Currency OrigCurrency)

  Currency option. OFX spec says to choose either CURRENCY or ORIGCURRENCY.

Instances

Show 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 :-)

data TrnType Source

OFX transaction types. These are used in STMTTRN aggregates, see OFX spec section 11.4.2.3.1.1.

Constructors

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 |

Instances

Eq TrnType |
Ord TrnType |
Show TrnType |

trnType :: TagData -> Maybe TrnType Source

data Payee Source

Constructors

Payee |

Fields

peNAME :: String
peADDR1 :: String
peADDR2 :: Maybe String
peADDR3 :: Maybe String
peCITY :: String
peSTATE :: String
pePOSTALCODE :: String
peCOUNTRY :: Maybe String
pePHONE :: String

Instances

Eq Payee |
Show Payee |

payee Source

Arguments

:: 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.

Constructors

REPLACE | Replaces the transaction referenced by the CORRECTFITID
DELETE | Deletes the transaction referenced by the CORRECTFITID

Instances

Eq CorrectAction |
Read CorrectAction |
Show CorrectAction |

data BankAcctTo Source

Constructors

BankAcctTo |

Fields

btBANKID :: String

  Routing and transit number

btBRANCHID :: Maybe String

  Bank identifier for international banks

btACCTID :: String

  Account number

btACCTTYPE :: AcctType

  Type of account

btACCTKEY :: Maybe String

  Checksum for international banks

Instances

Show BankAcctTo |

bankAcctTo :: Tag -> Maybe (Err BankAcctTo)Source

data CCAcctTo Source

Constructors

CCAcctTo |

Fields

ctACCTID :: String

  Account number

ctACCTKEY :: Maybe String

  Checksum for international banks

Instances

Eq CCAcctTo |
Show CCAcctTo |

ccAcctTo :: Tag -> Maybe (Err CCAcctTo)Source

data AcctType Source

Constructors

ACHECKING |
ASAVINGS |
AMONEYMRKT |
ACREDITLINE |

Instances

Eq AcctType |
Ord AcctType |
Show AcctType |

acctType :: String -> Err AcctType Source

data CurrencyData Source

Holds all data both for CURRENCY and for ORIGCURRENCY.

Constructors

CurrencyData |

Fields

cdCURRATE :: String

  Ratio of CURDEF currency to CURSYM currency, in decimal form

cdCURSYM :: String

  ISO-4217 3-letter currency identifier

Instances

Eq CurrencyData |
Show CurrencyData |

currencyData Source

Arguments

:: Tag | The tag that contains the data, e.g. CURRENCY or ORIGCURRENCY.
-> Err CurrencyData |

Parses currency data.

data Currency Source

Constructors

Currency CurrencyData |

Instances

Eq Currency |
Show Currency |

currency :: Tag -> Maybe (Err Currency)Source

data OrigCurrency Source

Constructors

OrigCurrency CurrencyData |

Instances

Eq OrigCurrency |
Show OrigCurrency |

origCurrency :: Tag -> Maybe (Err OrigCurrency)Source

# Parsec parsers

ofxFile :: Parser OFXFile Source

Parses an entire OFX file, including headers.

newline :: Parser ()Source

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.

header :: Parser OFXHeader Source

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.

tag :: Parser Tag Source

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

pPayee :: Payee -> Doc Source

pTransaction :: Transaction -> Doc Source

pTag :: Tag -> Doc Source

pHeader :: OFXHeader -> Doc Source

pFile :: OFXFile -> Doc Source

pEither :: (a -> Doc) -> (b -> Doc) -> Either a b -> Doc Source

pMaybe :: (a -> Doc) -> Maybe a -> Doc Source

pList :: [Doc] -> Doc Source

label :: String -> Doc -> Doc Source

pExceptional :: (e -> Doc) -> (a -> Doc) -> Either e a -> Doc Source