r16973 - gnucash/branches/2.2/src/import-export/qif-import - [r16955] Prevent unresponsive QIF druid by cleaning up any existing progress dialog
Andreas Köhler
andi5 at cvs.gnucash.org
Thu Feb 28 17:09:05 EST 2008
Author: andi5
Date: 2008-02-28 17:09:05 -0500 (Thu, 28 Feb 2008)
New Revision: 16973
Trac: http://svn.gnucash.org/trac/changeset/16973
Modified:
gnucash/branches/2.2/src/import-export/qif-import/qif-file.scm
Log:
[r16955] Prevent unresponsive QIF druid by cleaning up any existing progress dialog
if a Scheme error should occur while reading a QIF file (similar to bug
#516178). Also includes many whitespace and comment improvements.
Committed by cedayiv.
Modified: gnucash/branches/2.2/src/import-export/qif-import/qif-file.scm
===================================================================
--- gnucash/branches/2.2/src/import-export/qif-import/qif-file.scm 2008-02-28 22:08:55 UTC (rev 16972)
+++ gnucash/branches/2.2/src/import-export/qif-import/qif-file.scm 2008-02-28 22:09:05 UTC (rev 16973)
@@ -1,434 +1,481 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; qif-file.scm
-;;; read a QIF file into a <qif-file> object
;;;
-;;; Bill Gribble <grib at billgribble.com> 20 Feb 2000
+;;; Read a QIF file into a <qif-file> object.
+;;;
+;;; Bill Gribble <grib at billgribble.com> 20 Feb 2000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; qif-file:read-file self path
-;; suck in all the transactions; don't do any string interpretation,
-;; just store the fields "raw".
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
(use-modules (gnucash core-utils))
+(use-modules (ice-9 regex))
+(use-modules (srfi srfi-13))
(cond
((or (string=? "1.3.4" (version))
(string=? "1.4" (substring (version) 0 3))) #f)
(else (use-modules (ice-9 rdelim))))
-(define qif-bad-numeric-rexp
+(define qif-bad-numeric-rexp
(make-regexp "^\\.\\.\\."))
(define (not-bad-numeric-string? input)
(let ((match (regexp-exec qif-bad-numeric-rexp input)))
(if match #f #t)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; qif-file:read-file
+;;
+;; Suck in all the lines. Don't do any string interpretation,
+;; just store the fields "raw".
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
(define (qif-file:read-file self path ticker-map window)
- (false-if-exception
- (let* ((qstate-type #f)
- (current-xtn #f)
- (current-split #f)
- (current-account-name #f)
- (last-seen-account-name #f)
- (default-split #f)
- (first-xtn #f)
- (ignore-accounts #f)
- (return-val #t)
- (line #f)
- (tag #f)
- (value #f)
- (heinous-error #f)
- (missing-date-warned #f)
- (delimiters (string #\cr #\nl))
- (progress-dialog '())
- (file-stats (stat path))
- (file-size (stat:size file-stats))
- (bytes-read 0))
+ (let ((progress-dialog '())
+ (retval #f))
- (qif-file:set-path! self path)
+ ;; This procedure does all the work. We'll define it, then call it safely.
+ (define (private-read)
+ (let* ((qstate-type #f)
+ (current-xtn #f)
+ (current-split #f)
+ (current-account-name #f)
+ (last-seen-account-name #f)
+ (default-split #f)
+ (first-xtn #f)
+ (ignore-accounts #f)
+ (return-val #t)
+ (line #f)
+ (tag #f)
+ (value #f)
+ (heinous-error #f)
+ (missing-date-warned #f)
+ (delimiters (string #\cr #\nl))
+ (file-stats (stat path))
+ (file-size (stat:size file-stats))
+ (bytes-read 0))
- (if (> file-size 10000)
- (begin
- (set! progress-dialog (gnc-progress-dialog-new window #f))
- (gnc-progress-dialog-set-title progress-dialog (_ "Progress"))
- (gnc-progress-dialog-set-heading progress-dialog
- (_ "Loading QIF file..."))))
+ (qif-file:set-path! self path)
- (with-input-from-file path
- (lambda ()
- ;; loop over lines
- (let line-loop ()
- (set! line (read-delimited delimiters))
- (if (and
- (not (eof-object? line))
- (not (string=? line "")))
- (begin
- ;; add to the bytes-read tally
- (set! bytes-read
- (+ bytes-read 1 (string-length line)))
+ (if (> file-size 10000)
+ (begin
+ (set! progress-dialog (gnc-progress-dialog-new window #f))
+ (gnc-progress-dialog-set-title progress-dialog (_ "Progress"))
+ (gnc-progress-dialog-set-heading progress-dialog
+ (_ "Loading QIF file..."))))
- ;; pick the 1-char tag off from the remainder of the line
- (set! tag (string-ref line 0))
- (set! value (substring line 1))
- (set! value (gnc-utf8-strip-invalid-strdup value))
+ (with-input-from-file path
+ (lambda ()
+ ;; loop over lines
+ (let line-loop ()
+ (set! line (read-delimited delimiters))
+ (if (and (not (eof-object? line))
+ (not (string=? line "")))
+ (begin
+ ;; Add to the bytes-read tally.
+ (set! bytes-read
+ (+ bytes-read 1 (string-length line)))
- ;; now do something with the line
- (if
- (eq? tag #\!)
- (let ((old-qstate qstate-type))
- (set! qstate-type (qif-parse:parse-bang-field value))
- (case qstate-type
- ((type:bank type:cash type:ccard type:invst type:port
- #{type:oth\ a}# #{type:oth\ l}#)
- (if ignore-accounts
- (set! current-account-name last-seen-account-name))
- (set! ignore-accounts #f)
- (set! current-xtn (make-qif-xtn))
- (set! default-split (make-qif-split))
- (set! first-xtn #t))
- ((type:class)
- (set! current-xtn (make-qif-class)))
- ((type:cat)
- (set! current-xtn (make-qif-cat)))
- ((account)
- (set! current-xtn (make-qif-acct)))
- ((type:security)
- (set! current-xtn (make-qif-stock-symbol)))
- ((option:autoswitch)
- (set! ignore-accounts #t))
- ((clear:autoswitch)
- (set! ignore-accounts #f))
- (else
- ;; Ignore any other "option:" identifiers and
- ;; just return to the previously known !type
- (if (string-match "^option:"
- (symbol->string qstate-type))
- (begin
- (display "qif-file:read-file ignoring ")
- (write qstate-type)
- (newline)
- (set! qstate-type old-qstate))))))
-
-;;; (#t
-;;; (display "qif-file:read-file can't handle ")
-;;; (write qstate-type)
-;;; (display " transactions yet.")
-;;; (newline))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; bank-account type transactions
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (case qstate-type
- ((type:bank type:cash type:ccard type:invst type:port
- #{type:oth\ a}# #{type:oth\ l}#)
- (case tag
- ;; D : transaction date
- ((#\D)
- (qif-xtn:set-date! current-xtn value))
-
- ;; T : total amount
- ((#\T)
- (if (and default-split (not-bad-numeric-string? value))
- (qif-split:set-amount! default-split value)))
-
- ;; P : payee
- ((#\P)
- (qif-xtn:set-payee! current-xtn value))
-
- ;; A : address
- ;; multiple "A" lines are appended together with
- ;; newlines; some Quicken files have a lot of
- ;; A lines.
- ((#\A)
- (qif-xtn:set-address!
- current-xtn
- (let ((current (qif-xtn:address current-xtn)))
- (if (not (string? current))
- (set! current ""))
- (string-append current "\n" value))))
-
- ;; N : check number / transaction number /xtn direction
- ;; there's both an action and a number in gnucash,
- ;; one for securities, one for banks.
- ((#\N)
- (if (or (eq? qstate-type 'type:invst)
- (eq? qstate-type 'type:port))
- (qif-xtn:set-action! current-xtn value)
- (qif-xtn:set-number! current-xtn value)))
+ ;; Pick the 1-char tag off from the remainder of the line.
+ (set! tag (string-ref line 0))
+ (set! value (substring line 1))
- ;; C : cleared flag
- ((#\C)
- (qif-xtn:set-cleared! current-xtn value))
-
- ;; M : memo
- ((#\M)
- (if default-split
- (qif-split:set-memo! default-split value)))
-
- ;; I : share price (stock transactions)
- ((#\I)
- (qif-xtn:set-share-price! current-xtn value))
-
- ;; Q : number of shares (stock transactions)
- ((#\Q)
- (qif-xtn:set-num-shares! current-xtn value))
-
- ;; Y : name of security (stock transactions)
- ((#\Y)
- (qif-xtn:set-security-name! current-xtn value))
-
- ;; O : commission (stock transactions)
- ((#\O)
- (qif-xtn:set-commission! current-xtn value))
-
- ;; L : category
- ((#\L)
- (if default-split
- (qif-split:set-category! default-split value)))
-
- ;; S : split category
- ;; At this point we are ignoring the default-split
- ;; completely, but save it for later -- we need to use
- ;; it to determine whether to reverse the split
- ;; values.
- ((#\S)
- (set! current-split (make-qif-split))
- (if default-split
- (qif-xtn:set-default-split! current-xtn default-split))
- (set! default-split #f)
- (qif-split:set-category! current-split value)
- (qif-xtn:set-splits!
- current-xtn
- (cons current-split (qif-xtn:splits current-xtn))))
-
- ;; E : split memo
- ((#\E)
- (if current-split
- (qif-split:set-memo! current-split value)))
-
- ;; $ : split amount (if there are splits)
- ((#\$)
- (if (and current-split (not-bad-numeric-string? value))
- (qif-split:set-amount! current-split value)))
-
- ;; ^ : end-of-record
- ((#\^)
- (if (null? (qif-xtn:splits current-xtn))
- (qif-xtn:set-splits! current-xtn
- (list default-split)))
- (if first-xtn
- (let ((opening-balance-payee
- (qif-file:process-opening-balance-xtn
- self current-account-name current-xtn
- qstate-type)))
- (if (not current-account-name)
- (set! current-account-name
- opening-balance-payee))
- (set! first-xtn #f)))
-
- (if (and
- (or (eq? qstate-type 'type:invst)
- (eq? qstate-type 'type:port))
- (not (qif-xtn:security-name current-xtn)))
- (qif-xtn:set-security-name! current-xtn ""))
-
- (qif-xtn:set-from-acct! current-xtn
- current-account-name)
-
- (if (qif-xtn:date current-xtn)
- (qif-file:add-xtn! self current-xtn)
- ;; The date is missing! Warn the user if they
- ;; haven't been warned already.
- (if (not missing-date-warned)
- (begin
- (set! missing-date-warned #t)
- (gnc-warning-dialog '() (string-append
- (_ "One or more transactions is missing a date.")
- "\n"
- (_ "Some transactions may be discarded."))))))
+ ;; If the line doesn't conform to UTF-8, remove any invalid
+ ;; characters. This could be smarter, perhaps by trying a
+ ;; a default character set conversion based on the locale.
+ (set! value (gnc-utf8-strip-invalid-strdup value))
- ;;(write current-xtn) (newline)
- (set! current-xtn (make-qif-xtn))
- (set! current-split #f)
- (set! default-split (make-qif-split)))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Class transactions
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ((type:class)
- (case tag
- ;; N : name
- ((#\N)
- (qif-class:set-name! current-xtn value))
-
- ;; D : description
- ((#\D)
- (qif-class:set-description! current-xtn value))
-
- ;; R : tax copy designator (ignored for now)
- ((#\R)
- #t)
+ (if (eq? tag #\!)
+ ;; The "!" tag has the highest precedence and is used
+ ;; to switch between different sections of the file.
+ (let ((old-qstate qstate-type))
+ (set! qstate-type (qif-parse:parse-bang-field value))
+ (case qstate-type
+ ;; Transaction list for a particular account
+ ((type:bank type:cash type:ccard type:invst type:port
+ #{type:oth\ a}# #{type:oth\ l}#)
+ (if ignore-accounts
+ (set! current-account-name
+ last-seen-account-name))
+ (set! ignore-accounts #f)
+ (set! current-xtn (make-qif-xtn))
+ (set! default-split (make-qif-split))
+ (set! first-xtn #t))
- ;; end-of-record
- ((#\^)
- (qif-file:add-class! self current-xtn)
- (set! current-xtn (make-qif-class)))
-
- (else
- (display "qif-file:read-file : unknown Class slot ")
- (display tag)
- (display " .. continuing anyway.")
- (newline))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Account definitions
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ((account)
- (case tag
- ((#\N)
- (qif-acct:set-name! current-xtn value)
- (set! last-seen-account-name value))
- ((#\D)
- (qif-acct:set-description! current-xtn value))
- ((#\T)
- (qif-acct:set-type! current-xtn value))
- ((#\L)
- (qif-acct:set-limit! current-xtn value))
- ((#\B)
- (qif-acct:set-budget! current-xtn value))
- ((#\^)
- (if (not ignore-accounts)
- (set! current-account-name
- (qif-acct:name current-xtn)))
- (qif-file:add-account! self current-xtn)
- (set! current-xtn (make-qif-acct)))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Category (Cat) transactions
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ((type:cat)
- (case tag
- ;; N : category name
- ((#\N)
- (qif-cat:set-name! current-xtn value))
-
- ;; D : category description
- ((#\D)
- (qif-cat:set-description! current-xtn value))
-
- ;; T : is this a taxable category?
- ((#\T)
- (qif-cat:set-taxable! current-xtn #t))
-
- ;; E : is this an expense category?
- ((#\E)
- (qif-cat:set-expense-cat! current-xtn #t))
-
- ;; I : is this an income category?
- ((#\I)
- (qif-cat:set-income-cat! current-xtn #t))
-
- ;; R : tax form/line designator
- ((#\R)
- (qif-cat:set-tax-class! current-xtn value))
-
- ;; B : budget amount. not really supported.
- ((#\B)
- (qif-cat:set-budget-amt! current-xtn value))
-
- ;; end-of-record
- ((#\^)
- (qif-file:add-cat! self current-xtn)
- (set! current-xtn (make-qif-cat)))
-
- (else
- (display "qif-file:read-file : unknown Cat slot ")
- (display tag)
- (display " .. continuing anyway") (newline))))
+ ;; Class list
+ ((type:class)
+ (set! current-xtn (make-qif-class)))
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Security transactions
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ((type:security)
- (case tag
- ;; N : stock name
- ((#\N)
- (qif-stock-symbol:set-name! current-xtn value))
-
- ;; S : ticker symbol
- ((#\S)
- (qif-stock-symbol:set-symbol! current-xtn value))
-
- ;; T : type
- ((#\T)
- (qif-stock-symbol:set-type! current-xtn value))
-
- ;; G : asset class (ignored)
- ((#\G)
- #t)
-
- ;; end-of-record
- ((#\^)
- (qif-ticker-map:add-ticker! ticker-map current-xtn)
- (set! current-xtn (make-qif-stock-symbol)))
-
- (else
- (display "qif-file:read-file : unknown Security slot ")
- (display tag)
- (display " .. continuing anyway.")
- (newline))))
-
-
- ;; trying to sneak one by, eh?
- (else
- (if (and (not qstate-type)
- (not (string=? (string-trim line) "")))
- (begin
- (display "line = ") (display line) (newline)
- (display "qif-file:read-file : ")
- (display "file does not appear to be a QIF file.")
- (newline)
- (set!
- return-val
- (list #f "File does not appear to be a QIF file."))
- (set! heinous-error #t))))))
+ ;; Category list
+ ((type:cat)
+ (set! current-xtn (make-qif-cat)))
- ;; update the progress bar for each line read
- (if (not (null? progress-dialog))
- (begin
- (gnc-progress-dialog-set-value
- progress-dialog (/ bytes-read file-size))
- (gnc-progress-dialog-update progress-dialog)))
-
- ;; this is if we read a normal (non-null, non-eof) line...
- (if (not heinous-error)
- (line-loop)))
-
- ;; and this is if we read a null or eof line
- (if (and (not heinous-error)
- (not (eof-object? line)))
- (line-loop))))))
-
- ;; now reverse the transaction list so xtns are in the same order that
- ;; they were in the file. This is important in a few cases.
- (qif-file:set-xtns! self (reverse (qif-file:xtns self)))
+ ;; Account list
+ ((account)
+ (set! current-xtn (make-qif-acct)))
- (if (not (null? progress-dialog))
- (gnc-progress-dialog-destroy progress-dialog))
+ ;; Security list
+ ((type:security)
+ (set! current-xtn (make-qif-stock-symbol)))
- return-val)))
+ ;; Memorized transaction list
+ ((type:memorized)
+ ;; Not supported. We really should warn the user.
+ #f)
+ ;; Security price list
+ ((type:prices)
+ ;; Not supported. We really should warn the user.
+ #f)
+
+ ((option:autoswitch)
+ (set! ignore-accounts #t))
+
+ ((clear:autoswitch)
+ (set! ignore-accounts #f))
+
+ (else
+ ;; Ignore any other "option:" identifiers and
+ ;; just return to the previously known !type
+ (if (string-match "^option:"
+ (symbol->string qstate-type))
+ (begin
+ (display "qif-file:read-file ignoring ")
+ (write qstate-type)
+ (newline)
+ (set! qstate-type old-qstate))))))
+
+
+ ;; It's not a "!" tag, so the meaning depends on what
+ ;; type of section we are currently working on.
+ (case qstate-type
+
+ ;;;;;;;;;;;;;;;;;;;;;;
+ ;; Transaction list ;;
+ ;;;;;;;;;;;;;;;;;;;;;;
+
+ ((type:bank type:cash type:ccard type:invst type:port
+ #{type:oth\ a}# #{type:oth\ l}#)
+ (case tag
+ ;; D : transaction date
+ ((#\D)
+ (qif-xtn:set-date! current-xtn value))
+
+ ;; T : total amount
+ ((#\T)
+ (if (and default-split
+ (not-bad-numeric-string? value))
+ (qif-split:set-amount! default-split value)))
+
+ ;; P : payee
+ ((#\P)
+ (qif-xtn:set-payee! current-xtn value))
+
+ ;; A : address
+ ;; multiple "A" lines are appended together with
+ ;; newlines; some Quicken files have a lot of
+ ;; A lines.
+ ((#\A)
+ (qif-xtn:set-address!
+ current-xtn
+ (let ((current (qif-xtn:address current-xtn)))
+ (if (not (string? current))
+ (set! current ""))
+ (string-append current "\n" value))))
+
+ ;; N : For transactions involving a security, this
+ ;; is the investment action. For all others, this
+ ;; is a check number or transaction number.
+ ((#\N)
+ (if (or (eq? qstate-type 'type:invst)
+ (eq? qstate-type 'type:port))
+ (qif-xtn:set-action! current-xtn value)
+ (qif-xtn:set-number! current-xtn value)))
+
+ ;; C : cleared flag
+ ((#\C)
+ (qif-xtn:set-cleared! current-xtn value))
+
+ ;; M : memo
+ ((#\M)
+ (if default-split
+ (qif-split:set-memo! default-split value)))
+
+ ;; I : share price (stock transactions)
+ ((#\I)
+ (qif-xtn:set-share-price! current-xtn value))
+
+ ;; Q : number of shares (stock transactions)
+ ((#\Q)
+ (qif-xtn:set-num-shares! current-xtn value))
+
+ ;; Y : name of security (stock transactions)
+ ((#\Y)
+ (qif-xtn:set-security-name! current-xtn value))
+
+ ;; O : commission (stock transactions)
+ ((#\O)
+ (qif-xtn:set-commission! current-xtn value))
+
+ ;; L : category
+ ((#\L)
+ (if default-split
+ (qif-split:set-category! default-split value)))
+
+ ;; S : split category
+ ;; At this point we are ignoring the default-split
+ ;; completely, but save it for later -- we need it
+ ;; to determine whether to reverse the split values.
+ ((#\S)
+ (set! current-split (make-qif-split))
+ (if default-split
+ (qif-xtn:set-default-split! current-xtn
+ default-split))
+ (set! default-split #f)
+ (qif-split:set-category! current-split value)
+ (qif-xtn:set-splits!
+ current-xtn
+ (cons current-split
+ (qif-xtn:splits current-xtn))))
+
+ ;; E : split memo
+ ((#\E)
+ (if current-split
+ (qif-split:set-memo! current-split value)))
+
+ ;; $ : split amount (if there are splits)
+ ((#\$)
+ (if (and current-split
+ (not-bad-numeric-string? value))
+ (qif-split:set-amount! current-split value)))
+
+ ;; ^ : end-of-record
+ ((#\^)
+ (if (null? (qif-xtn:splits current-xtn))
+ (qif-xtn:set-splits! current-xtn
+ (list default-split)))
+ (if first-xtn
+ (let ((opening-balance-payee
+ (qif-file:process-opening-balance-xtn
+ self current-account-name current-xtn
+ qstate-type)))
+ (if (not current-account-name)
+ (set! current-account-name
+ opening-balance-payee))
+ (set! first-xtn #f)))
+
+ (if (and (or (eq? qstate-type 'type:invst)
+ (eq? qstate-type 'type:port))
+ (not (qif-xtn:security-name current-xtn)))
+ (qif-xtn:set-security-name! current-xtn ""))
+
+ (qif-xtn:set-from-acct! current-xtn
+ current-account-name)
+
+ (if (qif-xtn:date current-xtn)
+ (qif-file:add-xtn! self current-xtn)
+ ;; The date is missing! Warn the user if they
+ ;; haven't been warned already.
+ (if (not missing-date-warned)
+ (begin
+ (set! missing-date-warned #t)
+ (gnc-warning-dialog '() (string-append
+ (_ "One or more transactions is missing a date.")
+ "\n"
+ (_ "Some transactions may be discarded."))))))
+
+ ;;(write current-xtn) (newline)
+ (set! current-xtn (make-qif-xtn))
+ (set! current-split #f)
+ (set! default-split (make-qif-split)))))
+
+
+ ;;;;;;;;;;;;;;;;
+ ;; Class list ;;
+ ;;;;;;;;;;;;;;;;
+
+ ((type:class)
+ (case tag
+ ;; N : name
+ ((#\N)
+ (qif-class:set-name! current-xtn value))
+
+ ;; D : description
+ ((#\D)
+ (qif-class:set-description! current-xtn value))
+
+ ;; R : tax copy designator (ignored for now)
+ ((#\R)
+ #t)
+
+ ;; end-of-record
+ ((#\^)
+ (qif-file:add-class! self current-xtn)
+ (set! current-xtn (make-qif-class)))
+
+ (else
+ (display "qif-file:read-file : unknown Class slot ")
+ (display tag)
+ (display " .. continuing anyway.")
+ (newline))))
+
+
+ ;;;;;;;;;;;;;;;;;;
+ ;; Account List ;;
+ ;;;;;;;;;;;;;;;;;;
+
+ ((account)
+ (case tag
+ ((#\N)
+ (qif-acct:set-name! current-xtn value)
+ (set! last-seen-account-name value))
+ ((#\D)
+ (qif-acct:set-description! current-xtn value))
+ ((#\T)
+ (qif-acct:set-type! current-xtn value))
+ ((#\L)
+ (qif-acct:set-limit! current-xtn value))
+ ((#\B)
+ (qif-acct:set-budget! current-xtn value))
+ ((#\^)
+ (if (not ignore-accounts)
+ (set! current-account-name
+ (qif-acct:name current-xtn)))
+ (qif-file:add-account! self current-xtn)
+ (set! current-xtn (make-qif-acct)))))
+
+
+ ;;;;;;;;;;;;;;;;;;;
+ ;; Category list ;;
+ ;;;;;;;;;;;;;;;;;;;
+
+ ((type:cat)
+ (case tag
+ ;; N : category name
+ ((#\N)
+ (qif-cat:set-name! current-xtn value))
+
+ ;; D : category description
+ ((#\D)
+ (qif-cat:set-description! current-xtn value))
+
+ ;; T : is this a taxable category?
+ ((#\T)
+ (qif-cat:set-taxable! current-xtn #t))
+
+ ;; E : is this an expense category?
+ ((#\E)
+ (qif-cat:set-expense-cat! current-xtn #t))
+
+ ;; I : is this an income category?
+ ((#\I)
+ (qif-cat:set-income-cat! current-xtn #t))
+
+ ;; R : tax form/line designator
+ ((#\R)
+ (qif-cat:set-tax-class! current-xtn value))
+
+ ;; B : budget amount. not really supported.
+ ((#\B)
+ (qif-cat:set-budget-amt! current-xtn value))
+
+ ;; end-of-record
+ ((#\^)
+ (qif-file:add-cat! self current-xtn)
+ (set! current-xtn (make-qif-cat)))
+
+ (else
+ (display "qif-file:read-file : unknown Cat slot ")
+ (display tag)
+ (display " .. continuing anyway") (newline))))
+
+
+ ;;;;;;;;;;;;;;;;;;;
+ ;; Security list ;;
+ ;;;;;;;;;;;;;;;;;;;
+
+ ((type:security)
+ (case tag
+ ;; N : stock name
+ ((#\N)
+ (qif-stock-symbol:set-name! current-xtn value))
+
+ ;; S : ticker symbol
+ ((#\S)
+ (qif-stock-symbol:set-symbol! current-xtn value))
+
+ ;; T : type
+ ((#\T)
+ (qif-stock-symbol:set-type! current-xtn value))
+
+ ;; G : asset class (ignored)
+ ((#\G)
+ #t)
+
+ ;; end-of-record
+ ((#\^)
+ (qif-ticker-map:add-ticker! ticker-map current-xtn)
+ (set! current-xtn (make-qif-stock-symbol)))
+
+ (else
+ (display "qif-file:read-file : unknown Security slot ")
+ (display tag)
+ (display " .. continuing anyway.")
+ (newline))))
+
+
+ ;; trying to sneak one by, eh?
+ (else
+ (if (and (not qstate-type)
+ (not (string=? (string-trim line) "")))
+ (begin
+ (display "line = ") (display line) (newline)
+ (display "qif-file:read-file : ")
+ (display "file does not appear to be a QIF file.")
+ (newline)
+ (set! return-val
+ (list #f "File does not appear to be a QIF file."))
+ (set! heinous-error #t))))))
+
+ ;; Update the progress bar for each line read.
+ (if (not (null? progress-dialog))
+ (begin
+ (gnc-progress-dialog-set-value
+ progress-dialog (/ bytes-read file-size))
+ (gnc-progress-dialog-update progress-dialog)))
+
+ ;; This is if we read a normal (non-null, non-eof) line...
+ (if (not heinous-error)
+ (line-loop)))
+
+ ;; ...and this is if we read a null or eof line.
+ (if (and (not heinous-error)
+ (not (eof-object? line)))
+ (line-loop))))))
+
+ ;; Reverse the transaction list so xtns are in the same order that
+ ;; they appeared in the file. This is important in a few cases.
+ (qif-file:set-xtns! self (reverse (qif-file:xtns self)))
+
+ return-val))
+
+
+ ;; Safely read the file.
+ (set! retval (gnc:backtrace-if-exception private-read))
+
+ ;; Get rid of the progress dialog (if any).
+ (if (not (null? progress-dialog))
+ (gnc-progress-dialog-destroy progress-dialog))
+
+ retval))
+
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; qif-file:process-opening-balance-xtn self xtn
+;; qif-file:process-opening-balance-xtn
;;
-;; this gets called for the first transaction after a !Type: tag.
-;;
-;; if the first transaction after a !Type: tag has a payee of
+;; This gets called for the first transaction after a !Type: tag.
+;;
+;; If the first transaction after a !Type: tag has a payee of
;; "Opening Balance", we have to massage the transaction a little.
;; The meaning of an OB transaction is "transfer from Equity to the
;; account specified in the L line." idiomatically, ms-money and some
@@ -436,23 +483,23 @@
;; specify "this" account (the from-account for all following
;; transactions), so we have to allow for that.
;;
-;; even if the payee isn't "Opening Balance", we know that if there's
+;; Even if the payee isn't "Opening Balance", we know that if there's
;; no default from-account by this time, we need to set one. In that
;; case, we set the default account based on the file name.
-;;
-;; If we DO know the account already, and this is a tranfer to it,
-;; it's also an opening balance regardless of the payee.
+;;
+;; If we DO know the account already, and this is a tranfer to it,
+;; it's also an opening balance regardless of the payee.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define (qif-file:process-opening-balance-xtn self acct-name xtn type)
+(define (qif-file:process-opening-balance-xtn self acct-name xtn type)
(let ((payee (qif-xtn:payee xtn))
(category (qif-split:category (car (qif-xtn:splits xtn))))
- (cat-is-acct? (qif-split:category-is-account?
+ (cat-is-acct? (qif-split:category-is-account?
(car (qif-xtn:splits xtn))))
(security (qif-xtn:security-name xtn)))
(if (or (and (not acct-name)
(not security)
- payee (string? payee)
+ payee (string? payee)
(string=? (string-remove-trailing-space payee)
"Opening Balance")
cat-is-acct?)
@@ -473,7 +520,7 @@
;; need to ask for an explicit account.
(define (qif-file:check-from-acct self)
(let ((retval #t))
- (for-each
+ (for-each
(lambda (xtn)
(if (not (qif-xtn:from-acct xtn))
(set! retval #f)))
@@ -482,163 +529,171 @@
;; if the date format was ambiguous, this will get called to reparse.
(define (qif-file:reparse-dates self new-format)
- (check-and-parse-field
+ (check-and-parse-field
qif-xtn:date qif-xtn:set-date! equal?
qif-parse:check-date-format (list new-format)
- qif-parse:parse-date/format
+ qif-parse:parse-date/format
(qif-file:xtns self)
qif-parse:print-date
'error-on-ambiguity
(lambda (t e) e) 'date))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; qif-file:parse-fields-results results type
-;; take the results from qif-file:parse fields and find
-;; the results for a particular type of parse
+;; qif-file:parse-fields-results
+;;
+;; Take the results from qif-file:parse fields and find the
+;; results for a particular type of parse.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
(define (qif-file:parse-fields-results results type)
(define (test-results results)
(if (null? results) #f
- (let* ((this-res (car results))
- (this-type (car this-res)))
- (if (eq? this-type type)
- (cdr this-res)
- (test-results (cdr results))))))
+ (let* ((this-res (car results))
+ (this-type (car this-res)))
+ (if (eq? this-type type)
+ (cdr this-res)
+ (test-results (cdr results))))))
(if results (test-results results) #f))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; qif-file:parse-fields self
-;; take a previously-read qif file and convert fields
-;; from strings to the appropriate type.
+;; qif-file:parse-fields
+;;
+;; Take a previously-read qif file and convert fields from
+;; strings to the appropriate type.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (qif-file:parse-fields self)
-; (false-if-exception
+; (false-if-exception
(let* ((error #f)
(all-ok #f)
- (set-error
- (lambda (t e)
+ (set-error
+ (lambda (t e)
(if (not error)
(set! error (list (cons t e)))
(set! error (cons (cons t e) error)))))
- (errlist-to-string
+ (errlist-to-string
(lambda (lst)
- (with-output-to-string
+ (with-output-to-string
(lambda ()
- (for-each
+ (for-each
(lambda (elt)
(display elt))
lst))))))
- (and
- ;; fields of categories.
- (check-and-parse-field
+ (and
+ ;; fields of categories.
+ (check-and-parse-field
qif-cat:tax-class qif-cat:set-tax-class! gnc-numeric-equal
qif-parse:check-number-format '(decimal comma)
qif-parse:parse-number/format (qif-file:cats self)
qif-parse:print-number
'guess-on-ambiguity
set-error 'tax-class)
-
- (check-and-parse-field
+
+ (check-and-parse-field
qif-cat:budget-amt qif-cat:set-budget-amt! gnc-numeric-equal
- qif-parse:check-number-format '(decimal comma)
+ qif-parse:check-number-format '(decimal comma)
qif-parse:parse-number/format (qif-file:cats self)
qif-parse:print-number
'guess-on-ambiguity
set-error 'budget-amt)
-
- ;; fields of accounts
- (check-and-parse-field
+
+ ;; fields of accounts
+ (check-and-parse-field
qif-acct:limit qif-acct:set-limit! gnc-numeric-equal
- qif-parse:check-number-format '(decimal comma)
+ qif-parse:check-number-format '(decimal comma)
qif-parse:parse-number/format (qif-file:accounts self)
qif-parse:print-number
'guess-on-ambiguity
set-error 'limit)
-
- (check-and-parse-field
+
+ (check-and-parse-field
qif-acct:budget qif-acct:set-budget! gnc-numeric-equal
- qif-parse:check-number-format '(decimal comma)
+ qif-parse:check-number-format '(decimal comma)
qif-parse:parse-number/format (qif-file:accounts self)
qif-parse:print-number
'guess-on-ambiguity
set-error 'budget)
-
- (parse-field
+
+ (parse-field
qif-acct:type qif-acct:set-type!
qif-parse:parse-acct-type (qif-file:accounts self)
set-error)
- ;; fields of transactions
- (check-and-parse-field
+ ;; fields of transactions
+ (check-and-parse-field
qif-xtn:date qif-xtn:set-date! equal?
- qif-parse:check-date-format '(m-d-y d-m-y y-m-d y-d-m)
- qif-parse:parse-date/format
+ qif-parse:check-date-format '(m-d-y d-m-y y-m-d y-d-m)
+ qif-parse:parse-date/format
(qif-file:xtns self)
qif-parse:print-date
'error-on-ambiguity
set-error 'date)
-
- (parse-field
+
+ (parse-field
qif-xtn:cleared qif-xtn:set-cleared!
qif-parse:parse-cleared-field (qif-file:xtns self) set-error)
- (parse-field
+ (parse-field
qif-xtn:action qif-xtn:set-action!
qif-parse:parse-action-field (qif-file:xtns self) set-error)
-
- (check-and-parse-field
+
+ (check-and-parse-field
qif-xtn:share-price qif-xtn:set-share-price! gnc-numeric-equal
- qif-parse:check-number-format '(decimal comma)
+ qif-parse:check-number-format '(decimal comma)
qif-parse:parse-number/format (qif-file:xtns self)
qif-parse:print-number
'guess-on-ambiguity
set-error 'share-price)
-
- (check-and-parse-field
+
+ (check-and-parse-field
qif-xtn:num-shares qif-xtn:set-num-shares! gnc-numeric-equal
- qif-parse:check-number-format '(decimal comma)
+ qif-parse:check-number-format '(decimal comma)
qif-parse:parse-number/format (qif-file:xtns self)
qif-parse:print-number
'guess-on-ambiguity
set-error 'num-shares)
-
- (check-and-parse-field
+
+ (check-and-parse-field
qif-xtn:commission qif-xtn:set-commission! gnc-numeric-equal
- qif-parse:check-number-format '(decimal comma)
+ qif-parse:check-number-format '(decimal comma)
qif-parse:parse-number/format (qif-file:xtns self)
qif-parse:print-number
'guess-on-ambiguity
set-error 'commission)
-
- ;; this one's a little tricky... it checks and sets all the
- ;; split amounts for the transaction together.
- (check-and-parse-field
+
+ ;; this one's a little tricky... it checks and sets all the
+ ;; split amounts for the transaction together.
+ (check-and-parse-field
qif-xtn:split-amounts qif-xtn:set-split-amounts! gnc-numeric-equal
- qif-parse:check-number-formats '(decimal comma)
+ qif-parse:check-number-formats '(decimal comma)
qif-parse:parse-numbers/format (qif-file:xtns self)
qif-parse:print-numbers
'guess-on-ambiguity
set-error 'split-amounts)
-
- (begin
+
+ (begin
(set! all-ok #t)
#t))
-
+
(cond (error
(cons all-ok error))
(#t #t))))
;)
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; parse-field
-;; a simplified version of check-and-parse-field which just calls
-;; the parser on every instance of the field in the set of objects
+;; parse-field
+;;
+;; A simplified version of check-and-parse-field which just
+;; calls the parser on every instance of the field in the set
+;; of objects.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (parse-field getter setter parser objects errormsg)
- (for-each
+ (for-each
(lambda (obj)
(let ((unparsed (getter obj)))
(if (and unparsed (string? unparsed))
@@ -650,38 +705,38 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; check-and-parse-field
;;
-;; this is a semi-generic routine to apply a format check and
-;; parsing routine to fields that can have multiple possible
-;; formats. In this case, any amount field cam be decimal or
+;; This is a semi-generic routine to apply a format check and
+;; parsing routine to fields that can have multiple possible
+;; formats. In this case, any amount field cam be decimal or
;; comma radix and the date field can be any of several possible
-;; types.
+;; types.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define (check-and-parse-field getter setter equiv-thunk checker
- formats parser objects printer
+(define (check-and-parse-field getter setter equiv-thunk checker
+ formats parser objects printer
on-error errormsg errortype)
;; first find the right format for the field
(let ((do-parsing #f)
(retval #t)
- (format #f))
- ;; loop over objects. If the formats list ever gets down
- ;; to 1 element, we can stop right there.
+ (format #f))
+ ;; loop over objects. If the formats list ever gets down
+ ;; to 1 element, we can stop right there.
(if (not (null? objects))
(let loop ((current (car objects))
(rest (cdr objects)))
(let ((val (getter current)))
- (if val
- (begin
- (set! do-parsing #t)
+ (if val
+ (begin
+ (set! do-parsing #t)
(set! formats (checker val formats)))))
(if (and (not (null? formats))
;; (not (null? (cdr formats)))
(not (null? rest)))
(loop (car rest) (cdr rest)))))
-
+
;; if there's nothing left in formats, there's no format that will
;; fit all the values for a given field. We have to give up at
- ;; that point.
+ ;; that point.
;; If there are multiple items in formats, we look at the on-error
;; arg. If it's 'guess-on-ambiguity, we take the default (first)
@@ -689,7 +744,7 @@
;; 'fail-on-ambiguity (or anything else, actually) we return the
;; list of acceptable formats.
- (cond
+ (cond
((or (not formats)
(null? formats))
(errormsg errortype "Data for number or date does not match a known format.")
@@ -699,34 +754,34 @@
;; there are multiple formats that fit. If they all produce the
;; same interpretation for every data point in the set, then
;; just ignore the format ambiguity. Otherwise, it's really an
- ;; error. ATM since there's no way to correct the error let's
+ ;; error. ATM since there's no way to correct the error let's
;; just leave it be.
(if (or (eq? on-error 'guess-on-ambiguity)
- (all-formats-equivalent? getter parser equiv-thunk formats
+ (all-formats-equivalent? getter parser equiv-thunk formats
objects printer errormsg errortype))
(set! format (car formats))
- (begin
+ (begin
(errormsg errortype formats)
(set! do-parsing #f)
(set! retval #t))))
- (#t
+ (#t
(set! format (car formats))))
-
+
;; do-parsing is false if there were no objects with non-#f values
;; in the field, or the data format is ambiguous and
;; 'fail-on-ambiguity was passed. We would have had to look at
;; all of them once, but at least not twice.
(if do-parsing
- (for-each
+ (for-each
(lambda (current)
(let ((val (getter current))
- (parsed #f))
+ (parsed #f))
(if val
- (begin
- (set! parsed (parser val format))
- (if parsed
+ (begin
+ (set! parsed (parser val format))
+ (if parsed
(setter current parsed)
- (begin
+ (begin
(set! retval #f)
(errormsg errortype
"Data format inconsistent in QIF file.")))))))
@@ -734,38 +789,43 @@
retval))
-;; check for the off chance that even though there are multiple
-;; possible interpretations they are all the same. (i.e. the numbers
-;; "1000 2000 3000 4000" could be interpreted as decimal or comma
-;; radix, but who cares? The values will be the same).
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; all-formats-equivalent?
+;;
+;; This predicate checks for the off chance that even though
+;; there are multiple possible interpretations they are all the
+;; same. (i.e. the numbers "1000 2000 3000 4000" could be
+;; interpreted as decimal or comma radix, but who cares? The
+;; values will be the same).
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define (all-formats-equivalent? getter parser equiv-thunk formats objects
+(define (all-formats-equivalent? getter parser equiv-thunk formats objects
printer errormsg errortype)
(let ((all-ok #t))
(let obj-loop ((objlist objects))
(let* ((unparsed (getter (car objlist)))
- (parsed #f))
+ (parsed #f))
(if (string? unparsed)
- (begin
+ (begin
(set! parsed (parser unparsed (car formats)))
- (for-each
+ (for-each
(lambda (fmt)
(let ((this-parsed (parser unparsed fmt)))
(if (not (equiv-thunk parsed this-parsed))
- (begin
- (set! all-ok #f)
+ (begin
+ (set! all-ok #f)
(errormsg errortype
- (with-output-to-string
+ (with-output-to-string
(lambda ()
- (for-each
+ (for-each
(lambda (elt)
(display elt))
- (list
+ (list
"Parse ambiguity : between formats "
formats "\nValue " unparsed " could be "
- (printer parsed) " or "
+ (printer parsed) " or "
(printer this-parsed)
- "\nand no evidence exists to distinguish."
+ "\nand no evidence exists to distinguish."
"\nUsing " (printer parsed) ". "
"\nSee help for more info.")))))))))
(cdr formats))))
More information about the gnucash-changes
mailing list