Files
gnucash/gnucash/import-export/qif-imp/qif-dialog-utils.scm

924 lines
40 KiB
Scheme

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; qif-dialog-utils.scm
;;; build qif->gnc account maps and put them in a displayable
;;; form.
;;;
;;; Bill Gribble <grib@billgribble.com> 20 Feb 2000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 2 of
;; the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, contact:
;;
;; Free Software Foundation Voice: +1-617-542-5942
;; 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
;; Boston, MA 02110-1301, USA gnu@gnu.org
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(use-modules (gnucash import-export string))
(define (default-stock-acct brokerage security)
(string-append brokerage (gnc-get-account-separator-string) security))
(define (default-dividend-acct brokerage security)
(string-append (_ "Income") (gnc-get-account-separator-string)
(_ "Dividends") (gnc-get-account-separator-string)
brokerage (gnc-get-account-separator-string)
security))
(define (default-interest-acct brokerage security)
(string-append (_ "Income") (gnc-get-account-separator-string)
(_ "Interest") (gnc-get-account-separator-string)
brokerage
(if (string=? security "")
""
(string-append (gnc-get-account-separator-string)
security))))
(define (default-capital-return-acct brokerage security)
(string-append (_ "Income") (gnc-get-account-separator-string)
(_ "Cap Return") (gnc-get-account-separator-string)
brokerage (gnc-get-account-separator-string)
security))
(define (default-cglong-acct brokerage security)
(string-append (_ "Income") (gnc-get-account-separator-string)
(_ "Cap. gain (long)") (gnc-get-account-separator-string)
brokerage (gnc-get-account-separator-string)
security))
(define (default-cgmid-acct brokerage security)
(string-append (_ "Income") (gnc-get-account-separator-string)
(_ "Cap. gain (mid)") (gnc-get-account-separator-string)
brokerage (gnc-get-account-separator-string)
security))
(define (default-cgshort-acct brokerage security)
(string-append (_ "Income") (gnc-get-account-separator-string)
(_ "Cap. gain (short)") (gnc-get-account-separator-string)
brokerage (gnc-get-account-separator-string)
security))
(define (default-equity-holding security)
(string-append (_ "Equity") (gnc-get-account-separator-string)
(_ "Retained Earnings")))
(define (default-equity-account)
(string-append (_ "Equity") (gnc-get-account-separator-string)
(_ "Retained Earnings")))
(define (default-commission-acct brokerage)
(string-append (_ "Expenses") (gnc-get-account-separator-string)
(_ "Commissions") (gnc-get-account-separator-string)
brokerage))
(define (default-margin-interest-acct brokerage)
(string-append (_ "Expenses") (gnc-get-account-separator-string)
(_ "Margin Interest") (gnc-get-account-separator-string)
brokerage))
(define (default-unspec-acct)
(_ "Unspecified"))
;; The following investment actions implicitly specify
;; the two accounts involved in the transaction.
(define qif-import:actions-implicit
(list 'buy 'cglong 'cgmid 'cgshort 'div 'intinc 'margint 'reinvdiv
'reinvint 'reinvlg 'reinvmd 'reinvsg 'reinvsh 'reminder
'rtrncap 'sell 'shrsin 'shrsout 'stksplit))
(define (qif-import:gnc-account-exists map-entry acct-list)
(let ((retval #f))
(for-each
(lambda (acct)
(if (string=? (qif-map-entry:gnc-name map-entry)
(cadr acct))
(set! retval #t)))
acct-list)
retval))
;; the account-display is a 3-columned list of accounts in the QIF
;; import dialog (the "Account" page of the notebook). Column 1 is
;; the account name in the QIF file, column 2 is the number of QIF
;; xtns with that account name, and column 3 is the guess for the
;; translation. Sorted on # transactions, then alpha.
(define (qif-dialog:make-account-display qif-files acct-hash gnc-acct-info)
;; first, clear the "display" flags in the acct-hash and set up the
;; new-file? flags. If there's nothing to show any more, don't.
(hash-fold
(lambda (k v p)
(qif-map-entry:set-display?! v #f)
#f)
#f acct-hash)
(let ((retval '()))
;; we want to make two passes here. The first pass picks the
;; explicit Account descriptions out of each file. These are the
;; best sources of info because we will have types and so on for
;; them. The second pass picks out account-style L fields and
;; investment security names from the transactions. Hopefully
;; we'll have most of the accounts already located by that point.
;; Otherwise, we have to guess them.
;; acct-hash hashes the qif name to a <qif-map-entry> object.
;; guess-acct returns one.
(for-each
(lambda (file)
;; first, get the explicit account references.
(for-each
(lambda (acct)
(let ((entry (hash-ref acct-hash (qif-acct:name acct))))
(if (not entry)
(set! entry
(qif-import:guess-acct (qif-acct:name acct)
(qif-acct:type acct)
gnc-acct-info)))
(qif-map-entry:set-description! entry (qif-acct:description acct))
(hash-set! acct-hash (qif-acct:name acct) entry)))
(qif-file:accounts file)))
qif-files)
;; now make the second pass through the files, looking at the
;; transactions. Hopefully the accounts are all there already.
;; stock accounts can have both a category/account and another
;; account ref from the security name.
(for-each
(lambda (file)
(for-each
(lambda (xtn)
(let ((stock-acct (qif-xtn:security-name xtn))
(action (qif-xtn:action xtn))
(from-acct (qif-xtn:from-acct xtn))
(qif-account #f)
(qif-account-types #f)
(entry #f))
(if (and stock-acct action)
;; stock transactions are weird. there can be several
;; accounts associated with stock xtns: the security,
;; the brokerage, a dividend account, a long-term CG
;; account, a short-term CG account, an interest
;; account. Make sure all of the right ones get stuck
;; in the map.
(begin
;; first: figure out what the near-end account is.
;; it's generally the security account, but could be
;; an interest, dividend, or CG account.
(case action
((buy buyx sell sellx reinvint reinvdiv reinvsh reinvsg
reinvlg reinvmd shrsin shrsout stksplit)
(set! qif-account
(default-stock-acct from-acct stock-acct))
(set! qif-account-types (list GNC-STOCK-TYPE
GNC-MUTUAL-TYPE)))
((div cgshort cgmid cglong intinc miscinc miscexp
margint rtrncap xin xout)
(set! qif-account from-acct)
(set! qif-account-types (list GNC-BANK-TYPE
GNC-CCARD-TYPE
GNC-CASH-TYPE
GNC-ASSET-TYPE
GNC-LIABILITY-TYPE
GNC-RECEIVABLE-TYPE
GNC-PAYABLE-TYPE)))
((divx cgshortx cgmidx cglongx intincx margintx rtrncapx)
(set! qif-account
(and (qif-split:category-is-account?
(car (qif-xtn:splits xtn)))
(qif-split:category
(car (qif-xtn:splits xtn)))))
(set! qif-account-types (list GNC-BANK-TYPE
GNC-CCARD-TYPE
GNC-CASH-TYPE
GNC-ASSET-TYPE
GNC-LIABILITY-TYPE
GNC-RECEIVABLE-TYPE
GNC-PAYABLE-TYPE)))
((miscincx miscexpx)
(set! qif-account
(qif-split:miscx-category
(car (qif-xtn:splits xtn))))
(set! qif-account-types (list GNC-BANK-TYPE
GNC-CCARD-TYPE
GNC-CASH-TYPE
GNC-ASSET-TYPE
GNC-LIABILITY-TYPE
GNC-RECEIVABLE-TYPE
GNC-PAYABLE-TYPE))))
;; now reference the near-end account
(if qif-account
(begin
(set! entry (hash-ref acct-hash qif-account))
(if (not entry)
(set! entry
(qif-import:guess-acct qif-account
qif-account-types
gnc-acct-info)))
(qif-map-entry:set-display?! entry #t)
(hash-set! acct-hash qif-account entry)))
;; now figure out the other end of the transaction.
;; the far end will be the brokerage for buy, sell,
;; etc, or the "L"-referenced account for buyx,
;; sellx, etc, or an equity account for ShrsIn/ShrsOut
;; miscintx and miscexpx are very, very "special"
;; cases ... I'm not sure this is right.
;; the L line looks like :
;; LCategory/class [Account]/class
;; so I assume near-acct is Account and far acct
;; is Category. This matches the intincx/divx
;; behavior.
(set! qif-account #f)
(case action
((buy sell)
(set! qif-account from-acct)
(set! qif-account-types (list GNC-BANK-TYPE
GNC-CCARD-TYPE
GNC-CASH-TYPE
GNC-ASSET-TYPE
GNC-LIABILITY-TYPE
GNC-RECEIVABLE-TYPE
GNC-PAYABLE-TYPE)))
((buyx sellx xin xout)
(set! qif-account
(and (qif-split:category-is-account?
(car (qif-xtn:splits xtn)))
(qif-split:category
(car (qif-xtn:splits xtn)))))
(set! qif-account-types (list GNC-BANK-TYPE
GNC-CCARD-TYPE
GNC-CASH-TYPE
GNC-ASSET-TYPE
GNC-LIABILITY-TYPE
GNC-RECEIVABLE-TYPE
GNC-PAYABLE-TYPE)))
((stksplit)
(set! qif-account
(default-stock-acct from-acct stock-acct))
(set! qif-account-types (list GNC-STOCK-TYPE
GNC-MUTUAL-TYPE
GNC-ASSET-TYPE
GNC-LIABILITY-TYPE
GNC-RECEIVABLE-TYPE
GNC-PAYABLE-TYPE)))
((cgshort cgshortx reinvsg reinvsh)
(set! qif-account
(default-cgshort-acct from-acct stock-acct))
(set! qif-account-types (list GNC-INCOME-TYPE)))
((cgmid cgmidx reinvmd)
(set! qif-account
(default-cgmid-acct from-acct stock-acct))
(set! qif-account-types (list GNC-INCOME-TYPE)))
((cglong cglongx reinvlg)
(set! qif-account
(default-cglong-acct from-acct stock-acct))
(set! qif-account-types (list GNC-INCOME-TYPE)))
((intinc intincx reinvint)
(set! qif-account
(default-interest-acct from-acct stock-acct))
(set! qif-account-types (list GNC-INCOME-TYPE)))
((div divx reinvdiv)
(set! qif-account
(default-dividend-acct from-acct stock-acct))
(set! qif-account-types (list GNC-INCOME-TYPE)))
((rtrncap rtrncapx)
(set! qif-account
(default-capital-return-acct from-acct stock-acct))
(set! qif-account-types (list GNC-INCOME-TYPE)))
((shrsin shrsout)
(set! qif-account
(default-equity-holding stock-acct))
(set! qif-account-types (list GNC-EQUITY-TYPE)))
((margint margintx)
(set! qif-account
(default-margin-interest-acct from-acct))
(set! qif-account-types (list GNC-EXPENSE-TYPE)))
((miscinc miscexp miscincx miscexpx)
;; these reference a category on the other end
(set! qif-account #f)))
;; now reference the far-end account
(if qif-account
(begin
(set! entry (hash-ref acct-hash qif-account))
(if (not entry)
(set! entry (qif-import:guess-acct
qif-account qif-account-types
gnc-acct-info)))
(qif-map-entry:set-display?! entry #t)
(hash-set! acct-hash qif-account entry)))
;; if there's a commission, reference the
;; commission account
(if (qif-xtn:commission xtn)
(begin
(set! qif-account
(default-commission-acct from-acct))
(set! entry
(hash-ref acct-hash qif-account))
(if (not entry)
(set! entry
(qif-import:guess-acct
qif-account
(list GNC-EXPENSE-TYPE)
gnc-acct-info)))
(qif-map-entry:set-display?! entry #t)
(hash-set! acct-hash qif-account entry))))
;; non-stock transactions. these are a bit easier.
;; the near-end account (from) is always in the
;; transaction, and the far end(s) are in the splits.
(begin
(set! entry (hash-ref acct-hash from-acct))
(if (not entry)
(set! entry (qif-import:guess-acct
from-acct
(list GNC-BANK-TYPE
GNC-CCARD-TYPE
GNC-CASH-TYPE
GNC-ASSET-TYPE
GNC-LIABILITY-TYPE
GNC-RECEIVABLE-TYPE
GNC-PAYABLE-TYPE)
gnc-acct-info)))
(qif-map-entry:set-display?! entry #t)
(hash-set! acct-hash from-acct entry)
;; iterate over the splits doing the same thing.
(for-each
(lambda (split)
(let ((xtn-is-acct (qif-split:category-is-account? split))
(xtn-acct #f)
(entry #f))
(if xtn-is-acct
(begin
(set! xtn-acct (qif-split:category split))
(set! entry (hash-ref acct-hash xtn-acct))
(if (not entry)
(set! entry
(qif-import:guess-acct
xtn-acct
(list GNC-BANK-TYPE
GNC-CCARD-TYPE
GNC-CASH-TYPE
GNC-ASSET-TYPE
GNC-LIABILITY-TYPE
GNC-RECEIVABLE-TYPE
GNC-PAYABLE-TYPE)
gnc-acct-info)))
(qif-map-entry:set-display?! entry #t)
(hash-set! acct-hash xtn-acct entry)))))
(qif-xtn:splits xtn))))))
(qif-file:xtns file)))
qif-files)
;; now that the hash table is filled, make the display list
(hash-fold
(lambda (k v p)
(if (qif-map-entry:display? v)
(begin
(qif-map-entry:set-new-acct?!
v (not (qif-import:gnc-account-exists v gnc-acct-info)))
(set! retval (cons v retval))))
#f)
#f acct-hash)
;; sort by number of transactions with that account so the
;; most important are at the top
(set! retval
(sort retval
(lambda (a b)
(string<? (qif-map-entry:qif-name a)
(qif-map-entry:qif-name b)))))
retval))
;; the category display is similar to the Account display.
;; QIF category name, xtn count, then GNUcash account.
(define (qif-dialog:make-category-display qif-files cat-hash gnc-acct-info)
;; first, clear the "display" flags in the cat-hash. If there's
;; nothing to show any more, don't.
(hash-fold
(lambda (k v p)
(qif-map-entry:set-display?! v #f)
#f)
#f cat-hash)
(let ((retval '())
(entry #f))
;; get the Cat entries from each file
(for-each
(lambda (file)
(for-each
(lambda (cat)
(set! entry (hash-ref cat-hash (qif-cat:name cat)))
(if (not entry)
(set! entry
(qif-import:guess-acct (qif-cat:name cat)
(if (qif-cat:expense-cat cat)
(list GNC-EXPENSE-TYPE
GNC-INCOME-TYPE)
(list GNC-INCOME-TYPE
GNC-EXPENSE-TYPE))
gnc-acct-info)))
(qif-map-entry:set-description!
entry (qif-cat:description cat))
(hash-set! cat-hash (qif-cat:name cat) entry))
(qif-file:cats file)))
qif-files)
;; now look at every transaction and increment the count
;; in the account slot if the string matches, or make a
;; new hash reference if not.
(for-each
(lambda (qif-file)
(for-each
(lambda (xtn)
(let ((action (qif-xtn:action xtn)))
;; Many types of investment transactions implicitly use the
;; brokerage account or a known offshoot. There is no need
;; to consider a category mapping for these.
(if (not (and action
(memv action qif-import:actions-implicit)))
;; iterate over the splits
(for-each
(lambda (split)
(let ((xtn-is-acct (qif-split:category-is-account? split))
(xtn-cat #f)
(entry #f))
(if (not xtn-is-acct)
(begin
(set! xtn-cat (qif-split:category split))
(set! entry (hash-ref cat-hash xtn-cat))
;; NOTE: It would be more robust and efficient if the
;; three "make display" routines below were combined:
;; make-account-display
;; make-category-display
;; make-memo-display
;;
;; This would also require adjusting several callback
;; functions that reference these procedures from C.
;;
;; Until then, the maintainer of this code must make
;; sure that the logic used in the "if" below matches
;; the criteria for making memo/payee mappings (seen
;; in make-memo-display).
;; Add an entry if there isn't one already and either
;; (a) the category is non-blank, or
;; (b) no memo/payee mapping can be applied
(if (and (not entry)
(or (not (and (string? xtn-cat)
(string=? xtn-cat "")))
(and (or (not (qif-split:memo split))
(equal? (qif-split:memo split) ""))
(or (> (length (qif-xtn:splits xtn)) 1)
(not (qif-xtn:payee xtn))
(equal? (qif-xtn:payee xtn) "")))))
(set! entry
(qif-import:guess-acct
xtn-cat
(if (gnc-numeric-positive-p
(qif-split:amount split))
(list GNC-INCOME-TYPE
GNC-EXPENSE-TYPE)
(list GNC-EXPENSE-TYPE
GNC-INCOME-TYPE))
gnc-acct-info)))
(if entry
(begin
(qif-map-entry:set-display?! entry #t)
(hash-set! cat-hash xtn-cat entry)))))))
(qif-xtn:splits xtn)))))
(qif-file:xtns qif-file)))
qif-files)
;; now that the hash table is filled, make the display list
(hash-fold
(lambda (k v p)
(if (qif-map-entry:display? v)
(begin
(qif-map-entry:set-new-acct?!
v (not (qif-import:gnc-account-exists v gnc-acct-info)))
(set! retval (cons v retval))))
#f)
#f cat-hash)
;; sort by qif account name
(set! retval (sort retval
(lambda (a b)
(string<? (qif-map-entry:qif-name a)
(qif-map-entry:qif-name b)))))
retval))
;; this one's like the other display builders, it just looks at the
;; payee and memo too.
(define (qif-dialog:make-memo-display qif-files memo-hash gnc-acct-info)
(let ((retval '()))
;; clear the display flags for existing items
(hash-fold
(lambda (k v p)
(qif-map-entry:set-display?! v #f)
#f)
#f memo-hash)
;; iterate over every imported transaction. If there's no
;; category in the transaction, look at the payee to get a clue.
;; of there's no payee, look at the split memo.
(for-each
(lambda (file)
(for-each
(lambda (xtn)
(let ((payee (qif-xtn:payee xtn))
(action (qif-xtn:action xtn))
(splits (qif-xtn:splits xtn)))
;; Many types of investment transactions implicitly use the
;; brokerage account or a known offshoot. There is no need
;; to consider a memo/payee mapping for these.
(if (not (and action
(memv action qif-import:actions-implicit)))
(for-each
(lambda (split)
(let ((cat (qif-split:category split))
(memo (qif-split:memo split))
(key-string #f))
;; for each split: if there's a category, do nothing.
;; if there's a payee, use that as the
;; key otherwise, use the split memo.
(cond ((and cat
(or (not (string? cat))
(not (string=? cat ""))))
(set! key-string #f))
((and payee (= (length splits) 1))
(set! key-string payee))
(memo
(set! key-string memo)))
(if key-string
(let ((entry (hash-ref memo-hash key-string)))
(if (not entry)
(begin
(set! entry (make-qif-map-entry))
(qif-map-entry:set-qif-name! entry key-string)
(qif-map-entry:set-gnc-name!
entry (default-unspec-acct))
(qif-map-entry:set-allowed-types!
entry
(if (gnc-numeric-positive-p
(qif-split:amount split))
(list GNC-INCOME-TYPE GNC-EXPENSE-TYPE
GNC-BANK-TYPE GNC-CCARD-TYPE
GNC-LIABILITY-TYPE GNC-ASSET-TYPE
GNC-RECEIVABLE-TYPE GNC-PAYABLE-TYPE
GNC-STOCK-TYPE GNC-MUTUAL-TYPE)
(list GNC-EXPENSE-TYPE GNC-INCOME-TYPE
GNC-BANK-TYPE GNC-CCARD-TYPE
GNC-LIABILITY-TYPE GNC-ASSET-TYPE
GNC-RECEIVABLE-TYPE GNC-PAYABLE-TYPE
GNC-STOCK-TYPE GNC-MUTUAL-TYPE)))))
(qif-map-entry:set-display?! entry #t)
(hash-set! memo-hash key-string entry)))))
splits))))
(qif-file:xtns file)))
qif-files)
;; build display list
(hash-fold
(lambda (k v p)
(if (qif-map-entry:display? v)
(begin
(qif-map-entry:set-new-acct?!
v (not (qif-import:gnc-account-exists v gnc-acct-info)))
(set! retval (cons v retval))))
#f)
#f memo-hash)
;; sort by qif memo/payee name
(set! retval (sort retval
(lambda (a b)
(string<? (qif-map-entry:qif-name a)
(qif-map-entry:qif-name b)))))
retval))
(define (qif-dialog:qif-file-loaded? filename list-of-files)
(let ((status (map
(lambda (file)
(string=? filename (qif-file:path file)))
list-of-files)))
(if (memq #t status)
#t
#f)))
(define (qif-dialog:unload-qif-file oldfile list-of-files)
(delq oldfile list-of-files))
(define (qif-import:any-new-accts? hash-table)
(let ((retval #f))
(for-each
(lambda (bin)
(for-each
(lambda (elt)
(if (and (qif-map-entry:new-acct? (cdr elt))
(qif-map-entry:display? (cdr elt)))
(set! retval #t)))
bin))
(vector->list hash-table))
retval))
(define (qif-import:fix-from-acct qif-file new-acct-name)
(for-each
(lambda (xtn)
(if (not (qif-xtn:from-acct xtn))
(qif-xtn:set-from-acct! xtn new-acct-name)))
(qif-file:xtns qif-file)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; qif-import:get-account-name
;;
;; Given an account name, return the rightmost subaccount.
;; For example, given the account name "foo:bar", "bar" is
;; returned (assuming the account separator is ":").
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (qif-import:get-account-name fullname)
(let* ((sep (gnc-get-account-separator-string))
(last-sep (gnc:string-rcontains fullname sep)))
(if last-sep
(substring fullname (+ last-sep (string-length sep)))
fullname)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; qif-dialog:default-namespace
;;
;; Given a security's QIF symbol and type, along with all
;; previously seen security mapping preferences, return a
;; default namespace.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (qif-dialog:default-namespace qif-symbol qif-type prefs)
;; Guess a namespace based on the symbol alone.
(define (guess-by-symbol s)
(if (string? s)
(let ((l (string-length s))
(d (string-index s #\.))
(pref-match
(if (list? prefs)
(find (lambda (elt)
;; Does the symbol match, and is the namespace
;; compatible with the QIF type?
(and (string=? s (caddr elt))
(not (and (string? qif-type)
(string=? GNC_COMMODITY_NS_MUTUAL
(cadr elt))
(or (string-ci=? qif-type "stock")
(string-ci=? qif-type "etf"))))))
prefs)
#f)))
(cond
;; If a preferences match was found, use its namespace.
(pref-match
(cadr pref-match))
;; Guess NYSE for symbols of 1-3 characters.
((< l 4)
GNC_COMMODITY_NS_NYSE)
;; Guess NYSE for symbols of 1-3 characters
;; followed by a dot and 1-2 characters.
((and d
(< l 7)
(< 0 d 4)
(<= 2 (- l d) 3))
GNC_COMMODITY_NS_NYSE)
;; Guess NASDAQ for symbols of 4 characters.
((= l 4)
GNC_COMMODITY_NS_NASDAQ)
;; Otherwise it's probably a fund.
(else
GNC_COMMODITY_NS_MUTUAL)))
;; There's no symbol. Default to a fund.
GNC_COMMODITY_NS_MUTUAL))
;; Was a QIF type given?
(if (string? qif-type)
;; Yes. We might be able to definitely determine the namespace.
(cond
;; Mutual fund
((string-ci=? qif-type "mutual fund")
GNC_COMMODITY_NS_MUTUAL)
;; Index
((string-ci=? qif-type "index")
;; This QIF type must be wrong; indexes aren't tradable!
GNC_COMMODITY_NS_MUTUAL)
(else
(guess-by-symbol qif-symbol)))
;; No QIF type was given, so guess a
;; default namespace by symbol alone.
(guess-by-symbol qif-symbol)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; qif-import:update-security-hash
;;
;; For each QIF security in acct-hash, find a matching
;; GnuCash security or create a new one, then add it to the
;; security-hash table. Return a list of security-hash keys
;; for all newly created GnuCash securities, or #f if none.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (qif-import:update-security-hash security-hash ticker-map
acct-hash sec-prefs)
(let ((names '()))
(hash-fold
(lambda (qif-name map-entry p)
(let ((security-name (qif-import:get-account-name qif-name)))
;; Is this account going to be imported, is it security-denominated,
;; and is the security not already in the security hash table?
(if (and
security-name
(qif-map-entry:display? map-entry)
(or (memv GNC-STOCK-TYPE
(qif-map-entry:allowed-types map-entry))
(memv GNC-MUTUAL-TYPE
(qif-map-entry:allowed-types map-entry)))
(not (hash-ref security-hash security-name)))
(let ((existing-gnc-acct
(gnc-account-lookup-by-full-name
(gnc-get-current-root-account)
(qif-map-entry:gnc-name map-entry)))
(book (gnc-account-get-book (gnc-get-current-root-account))))
;; Are we importing to an existing, security-denominated account?
(if (and (not (null? existing-gnc-acct))
(memv (xaccAccountGetType existing-gnc-acct)
(list GNC-STOCK-TYPE GNC-MUTUAL-TYPE)))
;; Yes, so that security is the one to use. Add it
;; to the security hash table.
(let ((commodity
(xaccAccountGetCommodity existing-gnc-acct)))
(hash-set! security-hash security-name commodity))
;; Otherwise, since we can't definitively match this QIF
;; security to a GnuCash security, create a new one with
;; some (hopefully) intelligent defaults.
(let* ((qif-symbol
(qif-ticker-map:lookup-symbol ticker-map
security-name))
(qif-type
(qif-ticker-map:lookup-type ticker-map
security-name))
(namespace (qif-dialog:default-namespace qif-symbol
qif-type
sec-prefs)))
;; If no symbol has been provided, default to the name.
(if (not qif-symbol)
(set! qif-symbol security-name))
;; Create the new security and add it to the hash table.
(hash-set! security-hash
security-name
(gnc-commodity-new book
security-name
namespace
qif-symbol
""
100000))
;; Add the hash key to the list to be returned.
(set! names (cons security-name names))))))
#f))
#f acct-hash)
(if (not (null? names))
(sort names string<?)
#f)))
;; this is used within the dialog to get a list of all the new
;; accounts the importer thinks it's going to make. Passed to the
;; account picker.
;;
;; returned is a tree-structured list of all the old and new accounts
;; like so : (name new? children). trees are sorted alphabetically.
;; This should probably change but it's beeter than no sort at all.
(define (qif-import:get-all-accts extra-maps)
(define (cvt-to-tree path new?)
(if (null? path)
'()
(list (car path) new?
(if (null? (cdr path)) '()
(list (cvt-to-tree (cdr path) new?))))))
(define (merge-into-tree tree path new?)
(if (null? path)
tree
(if (null? tree)
(list (cvt-to-tree path new?))
(let ((newtree '()))
(let loop ((tree-elt (car tree))
(tree-left (cdr tree)))
(if (string=? (car path) (car tree-elt))
(let ((old-children (caddr tree-elt)))
(set! newtree
(cons (list (car path)
(and new? (cadr tree-elt))
(merge-into-tree
old-children (cdr path) new?))
(append newtree tree-left))))
(begin
(set! newtree (cons tree-elt newtree))
(if (not (null? tree-left))
(loop (car tree-left) (cdr tree-left))
(set! newtree (cons (cvt-to-tree path new?)
newtree))))))
(sort newtree (lambda (a b) (string<? (car a) (car b))))))))
(let ((accts '())
(acct-tree '())
(sep (gnc-get-account-separator-string)))
;; get the new accounts from the account map
(for-each
(lambda (acctmap)
(if acctmap
(hash-fold
(lambda (k v p)
(if (qif-map-entry:display? v)
(set! accts
(cons
(cons (gnc:substring-split (qif-map-entry:gnc-name v)
sep)
(qif-map-entry:new-acct? v))
accts)))
#f)
#f acctmap)))
extra-maps)
;; get the old accounts from the current account group
(for-each
(lambda (acct)
(set! accts
(cons
(cons (gnc:substring-split (gnc-account-get-full-name acct) sep)
#f)
accts)))
(gnc-account-get-descendants-sorted (gnc-get-current-root-account)))
;; now build a tree structure
(for-each
(lambda (acct)
(set! acct-tree
(merge-into-tree acct-tree (car acct) (cdr acct))))
accts)
;; we're done
acct-tree))
(define (qif-import:refresh-match-selection matches item)
(if (> item -1)
(let ((i 0))
(for-each
(lambda (match)
(if (= i item)
(if (cdr match)
(set-cdr! match #f)
(set-cdr! match #t))
(set-cdr! match #f))
(set! i (+ 1 i)))
matches))))