diff --git a/src/scm/bs-interp.scm b/src/scm/bs-interp.scm new file mode 100644 index 0000000000..b418d1c0b1 --- /dev/null +++ b/src/scm/bs-interp.scm @@ -0,0 +1,9 @@ +;;;; startup-interpreter.scm -*-scheme-*- + +;; Load the necessary files for use in interpreter mode. + +(primitive-load "bootstrap.scm") +(gnc:load "startup.scm") +(gnc:load "main.scm") +(gnc:startup) + diff --git a/src/scm/convenience-wrappers.scm b/src/scm/convenience-wrappers.scm index dfc1fc60d2..e69de29bb2 100644 --- a/src/scm/convenience-wrappers.scm +++ b/src/scm/convenience-wrappers.scm @@ -1,38 +0,0 @@ - - -(define (gnc:query-dialog message default-answer - yes-button? ok-button? no-button? cancel-button?) - ;; Show yes/no/cancel dialog box with given message. - ;; - ;; display message, and wait for a yes, no, or cancel, depending on - ;; the arguments. Each of the *-button? arguments indicates whether - ;; or not the dialog should contain a button of that type. - ;; default-answer may be set to 'yes, 'ok, 'no or 'cancel. If you - ;; allow both yes and OK buttons, and set 'yes or 'ok as the default - ;; answer, which button is the default is undefined, but the result - ;; is the same either way, and why would be doing that anyhow? - ;; - ;; This function returns 'yes for yes (or OK), 'no for no, or 'cancel. - ;; If there was an unrecoverable error, this function returns #f. - ;; - ;; NOTE: This function does not return until the dialog is closed.") - - (let* ((default (case default-answer - ((yes) 1) - ((ok) 1) - ((no) 2) - ((cancel) 3))) - (result - (gnc:_query-dialog-lowlev_ - message default yes-button? ok-button? no-button? cancel-button?))) - (cond - ((< result 0) #f) - (else - (case result - ((1) 'yes) - ((2) 'no) - ((3) 'cancel)))))) - -(define (gnc:message-dialog message) - (let ((result (gnc:query-dialog message 'ok #f #t #f #f))) - #t)) diff --git a/src/scm/dates-qif.scm b/src/scm/dates-qif.scm index f21b648c97..b063496543 100644 --- a/src/scm/dates-qif.scm +++ b/src/scm/dates-qif.scm @@ -1,6 +1,6 @@ +;; $Id$ ;;;;;;; Date-related code (define findspace (substring-search-maker " ")) -(define findslash (substring-search-maker "/")) ;;; Replace spaces in date fields with zeros so ;;; "4/ 7/99" transforms to "4/07/99" @@ -16,6 +16,28 @@ (substring string (+ 1 spacepos) slen))) string))) +(if testing? + (begin + (display "Check replacespace0:") + (let* ((v1 "4/ 7/99") + (v1res (replacespace0 v1)) + (v1exp "4/07/99") + (v2 " 1234 ") + (v2res (replacespace0 v2)) + (v2exp "00012340")) + (display (string-append "Rewrite:" v1 " Expect:" v1exp " Got:" v1res)) + (newline) + (if (string=? v1res v1exp) + 'ok + (begin + (display "ERROR - Unexpected results!!!")(newline))) + (display (string-append "Rewrite:" v2 " Expect:" v2exp " Got:" v2res)) + (newline) + (if (string=? v2res v2exp) + 'ok + (begin + (display "ERROR - Unexpected results!!!")(newline)))))) + ;;;; Check the way the dates look; figure out whether it's ;;;; DD/MM/YY, MM/DD/YY, YY/MM/DD, or whatever... (define date-low #f) @@ -27,8 +49,8 @@ (define max-date-low #f) (define max-date-med #f) (define max-date-high #f) -(define (resetdates) - (set! date-low #f) +(define (resetdates) + (set! date-low #f) (set! date-med #f) (set! date-high #f) (set! min-date-low 9999) @@ -88,7 +110,7 @@ (begin (if (or (eq? vl vm) (eq? vl vh) (eq? vm vh)) (begin - (display "Problem: Range occurs twice!") ; Problem! A range appears twice! + (display "Problem: Range occurs twice!") (newline) (display "Low Values:(Low Medium High)") (display (list min-date-low min-date-med min-date-high)) (newline) @@ -97,16 +119,16 @@ (display (string-append "(VL VM VH) (" - (number->string v1) + (symbol->string vl) " " - (number->string v2) - " " (number->string v3) ")" )) + (symbol->string vm) + " " (symbol->string vh) ")" )) (newline) (display "Assuming common default of MM/DD/YY") (newline) (set! date-low 'mm) (set! date-med 'dd) - (set! date-high yy) + (set! date-high 'yy) ;; This would be a great place to put a "hook" to allow the ;; user to interactively set (date-low, date-med, date-high) ;; to their favorite permuatation of ('mm 'dd 'yy) @@ -116,11 +138,6 @@ (set! date-med vm) (set! date-high vh)))))) -(define (atom? x) - (and - (not (pair? x)) - (not (null? x)))) - (define (rewrite-dates txn) (cond ((atom? txn) txn) @@ -132,9 +149,75 @@ (cons (rewrite-dates (car txn)) (rewrite-dates (cdr txn)))))) +(define (date-window year) + (let ((window-range 80) ;;;; Date adjustment window + (first-century 100) ;;;; First century + (next-century 2000) ;;;; Add this to year values that are + ;;;; less than the value of + ;;;; window-range. + (this-century 1900)) ;;;; Add this-century to year values + ;;;; that are greater than window-range, + ;;;; and less than first-century + + ;Based on this set of parameters, the following year substitutions + ;would take place: + ;YEAR --> New Value + ; 00 --> 2000 + ; 70 --> 2070 + ; 85 --> 1985 + ; 99 --> 1999 + ; 100 --> 100 + ;1102 --> 1102 + ;1932 --> 1932 + ; + ; Changing window-range changes the cut-off between last + ; century and this one; somewhere around 100 years from + ; now, it will probably be appropriate to change + ; next-century to 2100, and this-century to 2000. + (cond + ((< year window-range) + (+ year next-century)) + ((and (> year window-range) (< year first-century)) + (+ year this-century)) + (else ;;; Otherwise, do nothing to the year. + year)))) + +;;; does string contain #\- or #\/ or #\.??? +(define date-delimiters-list '(#\- #\/ #\.)) + +(define (which-delimiter str charlist) + (let ((len (string-length str))) ;;; Compute length once + (let loop ((pos 0)) + (let ((cchar (string-ref str pos))) + (if (member cchar charlist) + cchar + (if (< pos len) + (loop (+ pos 1)))))))) + +(testing "which-delimiter" + "99/01/03" + #\/ + (which-delimiter "99/01/03" date-delimiters-list)) + +(testing "which-delimiter" + "99/01/03" + #\/ + (which-delimiter "99/01/03" date-delimiters-list)) + +(testing "which-delimiter" + "99.02.03" + #\. + (which-delimiter "99.02.03" date-delimiters-list)) + +(testing "which-delimiter" + "12345-" + #\- + (which-delimiter "12345-" date-delimiters-list)) + (define (reformat-date date-as-string) (let* - ((datesplitup (split-on-somechar date-as-string #\/)) + ((delimiter (which-delimiter date-as-string date-delimiters-list)) + (datesplitup (split-on-somechar date-as-string delimiter)) (p1 (string->number (car datesplitup))) (p2 (string->number (cadr datesplitup))) (p3 (string->number (caddr datesplitup))) @@ -153,36 +236,48 @@ (dropin date-low p1) (dropin date-med p2) (dropin date-high p3) - (set! YEAR - (let ((window-range 80) ;;;; Date adjustment window - (first-century 100) ;;;; First century - (next-century 2000) ;;;; Add this to year values that are - ;;;; less than the value of - ;;;; window-range. - (this-century 1900)) ;;;; Add this-century to year values - ;;;; that are greater than window-range, - ;;;; and less than first-century - - ;Based on this set of parameters, the following year substitutions - ; would take place: - ;YEAR --> New Value - ; 00 --> 2000 - ; 70 --> 2070 - ; 85 --> 1985 - ; 99 --> 1999 - ; 100 --> 100 - ;1102 --> 1102 - ;1932 --> 1932 - ; - ; Changing window-range changes the cut-off between last - ; century and this one; somewhere around 100 years from - ; now, it will probably be appropriate to change - ; next-century to 2100, and this-century to 2000. - (cond - ((< YEAR window-range) - (+ YEAR next-century)) - ((and (> YEAR window-range) (< YEAR first-century)) - (+ YEAR this-century)) - (else ;;; Otherwise, do nothing to the year. - YEAR)))) - (list YEAR MONTH DAY)))) + (list (date-window YEAR) MONTH DAY)))) + + + +(if testing? + (begin + (let + ((ambdatelist ; ambiguous; date-versus-month + '(("00" "01" "02") ; is not clear, as both are < 12 + ("97" "02" "03") + ("99" "04" "07")))) + (resetdates) + (for-each newdatemaxes ambdatelist) + (display "Testing date conversion based on ambiguous date list:") (newline) + (display "(ambdatelist ") (display ambdatelist) (display ")") (newline) + (checkdatemaxes) + (display "Results: ") + (display (list date-low date-med date-high)) (newline)) + (let + ((ambdatelist ; also ambiguous + '(("13" "02" "02") + ("02" "03" "03") + ("03" "04" "07")))) + (resetdates) + (for-each newdatemaxes ambdatelist) + (display "Testing date conversion based on ambiguous date list:") (newline) + (display "(ambdatelist ") (display ambdatelist) (display ")") (newline) + (checkdatemaxes) + (display "Results: ") + (display (list date-low date-med date-high)) (newline)) + + (let + ((datelist ; not ambiguous + '(("13" "00" "02") + ("02" "03" "03") + ("03" "04" "07")))) + (resetdates) + (for-each newdatemaxes datelist) + (display "Testing date conversion based on ambiguous date list:") (newline) + (display "(datelist ") (display datelist) (display ")") (newline) + (checkdatemaxes) + (display "Results: ") + (display (list date-low date-med date-high)) (newline)))) + + diff --git a/src/scm/extensions.scm b/src/scm/extensions.scm index 7333507c76..5bf5f1c87e 100644 --- a/src/scm/extensions.scm +++ b/src/scm/extensions.scm @@ -7,6 +7,12 @@ (gnc:debug "Setting up extensions menu " win "\n") + (gnc:extensions-menu-add-item + "Test Account creation" + "Creates three accounts and adds them to the top group" + (lambda () + (gnc:test-creation))) + (gnc:extensions-menu-add-item "Export data as text" "Export data as text hint" (lambda () @@ -15,7 +21,7 @@ (gnc:extensions-menu-add-item "Test error dialog" "Test error dialog hint" (lambda () - (gnc:error-message-dialog + (gnc:error-dialog "Some error didn't occur."))) (gnc:extensions-menu-add-item "QIF Import" @@ -45,41 +51,31 @@ (cond ((eq? result #f) - (gnc:error-message-dialog + (gnc:error-dialog "Fatal error in choose item from list dialog.")) ((eq? result 'cancel) - (gnc:error-message-dialog "Choose item from list dialog canceled.")) + (gnc:error-dialog "Choose item from list dialog canceled.")) (else - (gnc:error-message-dialog + (gnc:error-dialog (call-with-output-string (lambda (string-port) - (display "Choose item result: " string-port) + (display "Choose item result: " + string-port) (write result string-port))))))))) (gnc:extensions-menu-add-item "Test verify dialog" "Test verify dialog hint" (lambda () - (let ((result (gnc:verify-dialog "Would you like to play a game?"))) + (let ((result (gnc:verify-dialog "Would you like to play a game?" #t))) (if result - (gnc:error-message-dialog "You said yes.") - (gnc:error-message-dialog "You said no."))))) + (gnc:info-dialog "You said yes.") + (gnc:info-dialog "You said no."))))) (gnc:extensions-menu-add-item - "Test query dialog" - "Test query dialog hint" - (lambda () - (let ((result (gnc:query-dialog - "Would you like to play a game?" - 'yes - #t #f #t #t))) - (case result - ((yes) (gnc:message-dialog "You said yes.")) - ((no) (gnc:message-dialog "You said no.")) - ((cancel) (gnc:message-dialog "You said cancel.")) - (else - (gnc:message-dialog "Something awful happened.")))))) - - + "Test info dialog" + "Test info dialog hint" + (lambda () (gnc:info-dialog "This is information."))) + (gnc:extensions-menu-add-item "Simple extension test" "Simple extension test hint" gnc:extensions-menu-test-func)) diff --git a/src/scm/gc-import-qifs.scm b/src/scm/gc-import-qifs.scm new file mode 100644 index 0000000000..578c79ac2b --- /dev/null +++ b/src/scm/gc-import-qifs.scm @@ -0,0 +1,192 @@ +;;; $Id$ +(display "Started gc-impor.scm") +(newline) +(define (gnc:get-account-list account-group) + (if testing? + gc-accts + (let ((fullacclist + (flatten + (gnc:group-map-accounts get-names-of-accounts + account-group)))) + (display "acclist:") + (display fullacclist) + (newline) + (filteroutnulls fullacclist)))) + +(define gnc:account-types (initialize-lookup)) + +(define (account-type->number symbol) + (let + ((s (lookup symbol gnc:account-types))) + (if s + (cdr s) + #f))) + +(display (account-type->number 'INCOME)) (newline) +(define (gnc:get-incomes-list account-group) + (if testing? + gc-cats + (filteroutnulls + (flatten + (gnc:group-map-accounts + get-names-of-incomes + account-group))))) + +(define gnc-asset-account-types + '(0 1 2 3 4 7)) +; (map account-type->number +; '(CASH CREDIT ASSET LIABILITY CURRENCY))) + +(if testing? + (begin + (display "gnc-asset-account-types:") + (display gnc-asset-account-types) + (newline))) +;;; '(1 2 3 4 7)) +;;;;;;;;;;;;;;;;;;;;;;; add, eventually, 11 12 13 14)) +;;; aka CHECKING SAVINGS MONEYMRKT CREDITLINE)) +;(define gnc-income-account-types '(8 9)) +(define gnc-income-account-types + (map account-type->number '(INCOME EXPENSE))) + +(if testing? + (begin + (display "gnc-income-account-types:") + (display gnc-income-account-types) + (newline))) + +(define gnc-invest-account-types '(5 6 10)) + +(define gnc-invest-account-types + (map account-type->number '(EQUITY STOCK MUTUAL))) + +(if testing? + (begin + (display "gnc-invest-account-types:") + (display gnc-invest-account-types) + (newline))) + +(define (get-names-of-accounts a) + (list + (if (member (gnc:account-get-type a) gnc-asset-account-types) + (gnc:account-get-name a) + #f)) + (gnc:group-map-accounts get-names-of-accounts + (gnc:account-get-children a))) + +(define (get-names-of-incomes a) + (list + (if (member (gnc:account-get-type a) gnc-income-account-types) + (gnc:account-get-name a) + #f)) + (gnc:group-map-accounts get-names-of-incomes + (gnc:account-get-children a))) + +(define (get-names-of-expenses a) + (list + (if (member (gnc:account-get-type a) gnc-expense-account-types) + (gnc:account-get-name a) + #f)) + (gnc:group-map-accounts get-names-of-expenses + (gnc:account-get-children a))) + +(define (gnc:import-file-into-account-group account-group) + ;(sample-dialog) + (let ((file-name + (gnc:file-selection-dialog "Select file for QIF import" "*.qif"))) + (if file-name + (begin + (gnc:debug "Loading data from file " file-name) + (let* ((txn-list (read-qif-file file-name account-group)) + (category-analysis (analyze-qif-transaction-categories txn-list))) + ;;; Now, take steps: + (qif-to-gnucash txn-list file-name) + (list txn-list category-analysis)))))) + +;;; Set up QIF Category + +(define (get-all-types) + (set! gnc:account-types (initialize-lookup)) + (let loop + ((i 0)) + (let ((typesymbol (gnc:account-type->symbol i))) + (set! gnc:account-types + (lookup-set! gnc:account-types typesymbol i)) + (if (< i 14) + (loop (+ i 1)))))) + +(define (gnc:create-account AccPtr name description notes type) + (gnc:init-account AccPtr) + (gnc:account-begin-edit AccPtr 0) + (gnc:account-set-name AccPtr name) + (gnc:account-set-description AccPtr description) + (gnc:account-set-notes AccPtr notes) + (gnc:account-set-type AccPtr type) + (gnc:account-commit-edit AccPtr)) + +;;;;;;;;;;; This one's REAL IMPORTANT!!! ;;;;;;;;;;;; +(display (account-type->number 'CASH)) +(display (account-type->number 'INCOME)) + +(define (gnc:create-transaction Account txnlist) + (define (associt type) + (let + ((result (lookup type txnlist))) + (if result + (cdr result) + #f))) + (let + ((Txn (gnc:transaction-create)) + (Category (associt 'category)) + (Payee (associt 'payee)) + (Id (associt 'id)) + (Date (associt 'date)) + (Status (associt 'status)) + (Amount (associt 'amount)) + (Memo (associt 'memo)) + (Splits (associt 'splits))) + (gnc:trans-begin-edit Txn 1) + (let ((source-split (gnc:transaction-get-split Txn 0)) + (build-split-entry + (lambda (splitentry) + (define (assocsplit type) + (let + ((result (assoc type splitentry))) + (if result + (cdr result) + #f))) + (let + ((Split (gnc:split-create)) + (Category (assocsplit 'category)) + (Amount (assocsplit 'amount)) + (Memo (assocsplit 'memo))) + (if Category + (gnc:account-insert-split + (gnc:xaccGetXferQIFAccount Account Category) + Split)) + (if Amount + (gnc:split-set-value Split (- Amount))) + (if Memo + (gnc:split-set-memo Split Memo)))))) + (if Category + (gnc:account-insert-split + (gnc:xaccGetXFerQIFAccount Account Category) + source-split)) + (if Payee + (gnc:transaction-set-description Txn Payee)) + (if Id + (gnc:transaction-set-xnum Txn Id)) + (if Status + (gnc:split-set-reconcile source-split (string-ref Status 0))) + (if Date + (gnc:trans-set-datesecs + Txn + (gnc:gnc_dmy2timespec (caddr Date) (cadr Date) (car Date)))) + (if Amount + (gnc:split-set-value source-split Amount)) + (if Memo + (gnc:transaction-set-memo Txn Memo)) + (if Splits + ;;;; Do something with split + (for-each build-split-entry Splits))) + (gnc:trans-commit-edit Txn))) diff --git a/src/scm/guess-category-qif.scm b/src/scm/guess-category-qif.scm index 8ab226e492..9cc3eea325 100644 --- a/src/scm/guess-category-qif.scm +++ b/src/scm/guess-category-qif.scm @@ -1,4 +1,4 @@ -(define (guess-cat inputcat gnucash-cats gnucash-accs) +;;; $Id$ ;;; Need a bunch of metrics, and probably to vectorize this... ;;; 1. Braces --> pick gnucash entries from account list ;;; No braces --> pick gnucash entries from category list @@ -6,94 +6,68 @@ ;;; 3. a contains b, b contains a --> end of list ;;; 4. First 2 letters match --> end of list ;;; 5. First letter matches --> end of list -;;; g) I'd like a "similarity match" of some sort -;;; h) Is it in old-matches? If so, toss that to front of list. +;;; 6. I'd like a "similarity match" of some sort +;;; 7. Is it in old-matches? If so, toss that to front of list. ;;; Lastly, shorten the list to no more than 4 items. + +(define (guess-gnucash-category + inputcat gc-income-categories gc-account-categories) (let* - ((picklist '()) - (size-of-list 4) ;; Find the 4 lowest items... - (lowestn '()) - (catlength (string-length inputcat)) ; How long is incat? - (is-acct? (and ; as the list to compare to + ((picklist (initialize-lookup)) + (qifname (inputcat 'get 'name)) + (catlength (string-length (qifname))) + (is-acct? (and (>= catlength 2) (string=? (substring inputcat 0 1) "[") - (string=? (substring inputcat (- catlength 1) catlength) "]"))) - (acctlist ; Pick either gnucash-cats/gnucash-accs + (string=? (substring inputcat + (- catlength 1) catlength) "]"))) + (netdebit? (< (inputcat 'get 'value))) + (acctlist ; Pick either gc-income-categories/gc-account-categories (if is-acct? - gnucash-accs - gnucash-cats)) - + gc-account-categories + gc-income-categories)) (incat (if is-acct? (substring inputcat 1 (- catlength 1)) inputcat)) - ; (null (if is-acct? (write (string-append "Account!" incat)))) (add-to-picklist (lambda (string value) - (let - ((inlist? (assoc string picklist))) - (if - inlist? - (let - ((oldvalue (cdr inlist?))) - (if - (> oldvalue value) - (set-cdr! inlist? value))) - (set! picklist (cons (cons string value) picklist)))))) - + (set! picklist (lookup-set! picklist string value)))) (match-against-list (lambda (itemstring) (if (string=? itemstring incat) ;;; Exact match (add-to-picklist itemstring 1)) - (if (or ((substring-search-maker incat) itemstring) ;;; Inclusion + (if (or ((substring-search-maker incat) itemstring) ;;; Inclusion ((substring-search-maker itemstring) incat)) (add-to-picklist itemstring 3)) (if (string=? - (substring incat 0 (min 2 (string-length incat))) ;; Match first 2 chars + (substring incat 0 + (min 2 (string-length incat))) ;; Match first 2 chars (substring itemstring 0 (min 2 (string-length itemstring))) ) (add-to-picklist itemstring 5)) (if (string=? - (substring incat 0 (min 1 (string-length incat))) ;; Match first 1 char - (substring itemstring 0 (min 1 (string-length itemstring))) ) + (substring incat 0 + (min 1 (string-length incat)));; Match first 1 char + (substring itemstring 0 (min 1 (string-length itemstring)))) (add-to-picklist itemstring 7))))) ;;;;;;;; Now, apply the matching... (for-each match-against-list acctlist) - (write (string-append "Match-against list: " incat)) (write picklist) (newline) ;;;;;;;; Shorten picklist, keeping top 4 items - (let ((shortened '())) - (let loop ((count size-of-list)) - (if (> count 0) - (let - ((bestitem (find-min-cdr picklist))) - (if bestitem - (begin - (if (> 99 (cdr bestitem)) - (set! shortened (cons (car bestitem) shortened))) - (set-cdr! bestitem 999) ;;;; Force off list... - (loop (- count 1))))))) - shortened))) + (shorten-to-best 4 picklist))) -(define (find-min-cdr mlist) - (if - (null? mlist) - #f - (let - ((first (car mlist)) - (rest (find-min-cdr (cdr mlist)))) - (if - rest ;;; Found a value for rest - (if (> (cdr first) (cdr rest)) - rest - first) - first)))) - -(define (guess-corresponding-categories import-cats gnucash-cats - gnucash-accs) - (define (apply-guess-cat incat) - (guess-cat (car incat) gnucash-cats gnucash-accs)) - (map apply-guess-cat import-cats)) +(define (guess-corresponding-categories + import-categories + gc-income-categories gc-account-categories) + (define apply-guess-category + (lambda (incat) + (list incat + (guess-gnucash-category (car incat) + gc-income-categories + gc-account-categories)))) + + (map apply-guess-category import-categories)) ;;; Make use of "old-matches," which is an association list ;;; containing the correspondences that have been used previously. @@ -121,53 +95,9 @@ ;;;;; (amount (assoc 'amount transaction))) ;;;;; (add-transaction use-category date amount) ;;;;; -;;;;; - Transactions should not be marked off as being finally reconciled on -;;;;; the GnuCash side, as the reconciliation hasn't been done there. -;;;;; -;;;;; Bad Things would happen if we double-load a batch of QIF transactions, -;;;;; and treat it as if it were fully reconciled. - - -;;;;; This returns the "thunk" that should be used to translate statuses -(define (status-handling tlist) - (define cleared? #f) - (define (look-for-cleared txn) - (if - (string=? "X" (cdr (assoc 'status txn))) - (set! cleared #t))) - (for-each look-for-cleared tlist) - (if cleared? - (begin - (display "Warning: This transaction list includes transactions marked as cleared.") - (display "Are you *completely* confident of the correctness of that") - (display "reconciliation, and that it is *truly* safe to mark them as reconciled") - (display "in GnuCash?") - (display "It is suggested that you indicate ``No,'' which will result in those") - (display "transactions being statused as ``marked,'' which should make the") - (display "reconciliation in GnuCash take place reasonably quickly.") - ;;;; Now ask if the user is certain... - ;;;; Need some code here... - (if (not certain?) - (set! cleared #f)))) - (let* - ((cleared-to-what (if cleared? 'cleared 'marked)) - (ttable -;;; QIF Status translation table -;;; The CARs are values expected from Quicken. -;;; The CDRs are the values that gnc:transaction-put-status requires... - '(("X" cleared-to-what) - ("*" 'marked) - ("?" 'budgeted-new) - ("!" 'budgeted-old) - ("" 'unmarked)))) - -;;; And here's the "thunk" that is to be returned. It translates QIF statuses -;;; into the form GnuCash expects to pass to gnc:transaction-put-status - (lambda (status) - (let - ((a (assoc status ttable))) - (if - a - (cdr a) ;;; If the value was found, use it.. - (cdr (assoc "" ttable))))))) ;;; No value? Take the null value from ttable +(define (guess-results account-group kept-categories) + (guess-corresponding-categories + kept-categories + (gnc:get-incomes-list account-group) + (gnc:get-account-list account-group))) \ No newline at end of file diff --git a/src/scm/hooks.scm b/src/scm/hooks.scm index c478666a87..6ac3fad050 100644 --- a/src/scm/hooks.scm +++ b/src/scm/hooks.scm @@ -61,7 +61,12 @@ (define gnc:*shutdown-hook* (gnc:hook-define 'shutdown-hook - "Functions to run at shutdown. Hook args: ()")) + "Functions to run at guile shutdown. Hook args: ()")) + +(define gnc:*ui-shutdown-hook* + (gnc:hook-define + 'ui-shutdown-hook + "Functions to run at ui shutdown. Hook args: ()")) (define gnc:*main-window-opened-hook* (gnc:hook-define diff --git a/src/scm/importqif.scm b/src/scm/importqif.scm index e5c5c127d9..e5b2219b0d 100644 --- a/src/scm/importqif.scm +++ b/src/scm/importqif.scm @@ -1,11 +1,40 @@ -;;; Parse QIF -(gnc:load "sstring-qif.scm") -(gnc:load "dates-qif.scm") -(gnc:load "split-qif.scm") -(gnc:load "parseqif.scm") -(gnc:load "guess-category-qif.scm") -(gnc:load "analytical-qifs.scm") -(gnc:load "gc-import-qifs.scm") +;;; $Id$ +;;; Import QIF File +(define testing? #f) ;;; Should we do testing? + +(define favorite-currency "USD") ;;;; This may need to change... + +(define (gnc:extensions-qif-import win) + (let ((account-group #f) + (session (gnc:main-window-get-session))) + (if session (set! account-group (gnc:session-get-group session))) + (if (not account-group) + (gnc:error-message-dialog + "No account group available for text export.") + (begin + (display "account-group:") (display account-group) (newline) + (gnc:load "testbed.scm") + (gnc:load "sstring-qif.scm") + (gnc:load "qifutils.scm") + (gnc:load "structure.scm") + (gnc:load "dates-qif.scm") + (gnc:load "split-qif.scm") + (gnc:load "qifcats.scm") + (gnc:load "parseqif.scm") + (gnc:load "qifstate.scm") + (gnc:load "qifstat.scm") + (gnc:load "qif2gc.scm") + (gnc:load "guess-category-qif.scm") + (gnc:load "analytical-qifs.scm") + (gnc:load "test.scm") + (gnc:load "gc-import-qifs.scm") + (begin + (get-all-types) + (display "Account type list:") + (display gnc:account-types) + (newline)) + (test-load account-group) ;;;;; This tries to create some accounts + (gnc:import-file-into-account-group account-group))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;; Now, let's actually execute the code... @@ -21,26 +50,4 @@ ;;;;; a group of transactions, querying the user to select the appropriate ;;;;; account. ;;;;; -;;;;; - What to do with transfers? -;;;;; -;;;;; A transaction where the category is [AA Chequing] or [ACM MasterCard] -;;;;; is obviously a transfer to/from that account. Unfortunately, there is -;;;;; no guarantee that an account by the same exact name exists in GnuCash. -;;;;; Probably ought to cons up a list of categories, agree them to GnuCash, -;;;;; and, most crucially, construct a "category translation table" -;;;;; to indicate what to do with them. -;;;;; -;;;;; The same is true, albeit less critical, for income/expense categories. -;;;;; -;;;;; - Further transfer issue: -;;;;; -;;;;; Note that a QIF load may provide duplicate transactions for transfers, -;;;;; once you load in the amounts for both sides of the transaction. -;;;;; -;;;;; - Category management: -;;;;; -;;;;; What should we do if there are categories in the QIF file that don't -;;;;; exist in GnuCash? Create the new category, maybehaps, but probably -;;;;; by collecting up a list, and giving the option of converting QIF -;;;;; categories to "something new." Again, reference back to the -;;;;; "category translation table" + diff --git a/src/scm/main.scm b/src/scm/main.scm index fedefcf27e..b489e89568 100644 --- a/src/scm/main.scm +++ b/src/scm/main.scm @@ -6,11 +6,11 @@ ;; Now we can load a bunch of files. - (gnc:load "hooks.scm") (gnc:load "doc.scm") (gnc:load "extensions.scm") ; Should this be here or somewhere else? (gnc:load "text-export.scm") (gnc:load "importqif.scm") + (gnc:load "test.scm") ;; Load the system and user configs (if (not (gnc:load-system-config-if-needed)) @@ -29,24 +29,41 @@ (define (gnc:shutdown exit-status) (gnc:debug "Shutdown -- exit-status: " exit-status) - - (gnc:hook-run-danglers gnc:*shutdown-hook*) - (gnc:_shutdown_ exit-status) - (exit exit-status)) + + (cond ((gnc:ui-is-running?) + (if (not (gnc:ui-is-terminating?)) + (begin + (gnc:hook-run-danglers gnc:*ui-shutdown-hook*) + (gnc:ui-shutdown)))) + + (else + (gnc:hook-run-danglers gnc:*shutdown-hook*) + (gnc:ui-destroy) + (exit exit-status)))) + +(define (gnc:ui-finish) + (gnc:debug "UI Shutdown hook.") + + (gnc:file-query-save) + (gnc:file-quit)) (define (gnc:main) - + ;; Now the fun begins. (gnc:startup) - + (if (not (= (gnc:lowlev-app-init) 0)) (gnc:shutdown 0)) (if (pair? gnc:*command-line-files*) ;; You can only open single files right now... (gnc:ui-open-file (car gnc:*command-line-files*))) - - (gnc:lowlev-app-main) + + (gnc:hook-add-dangler gnc:*ui-shutdown-hook* gnc:ui-finish) + + (gnc:ui-main) + + (gnc:hook-remove-dangler gnc:*ui-shutdown-hook* gnc:ui-finish) (gnc:shutdown 0)) diff --git a/src/scm/parseqif.scm b/src/scm/parseqif.scm index 855ae69ac3..f3c689508f 100644 --- a/src/scm/parseqif.scm +++ b/src/scm/parseqif.scm @@ -1,126 +1,104 @@ +;;; $Id$ ;;;;;;;;;;; QIF Parsing ;;;;;;;;;;;;;; -(define tlist '()) -(define atrans '()) +(define qif-txn-list '()) + +(define qif-txn-structure + (define-mystruct '(memo date id payee addresslist amount status category splitlist))) + +(define thetxn + (build-mystruct-instance qif-txn-structure)) + (define addresslist '()) -(define process-qif-file - (lambda (file account-group) - ; Opens file, rewrites all the lines, closes files - (display (string-append "rewriting file:" file)) (newline) - (set! tlist '()) ; Reset the transaction list... - (set! atrans '()) - (reset-categories) - (resetdates) ; Reset the date checker - (let* - ;((infile (open-input-file (string-append srcdir file))) - ((infile (open-input-file file)) -; (outfile (open-output-file (string-append destdir file) 'replace)) - (outfile (open-output-file (string-append file ".XAC"))) - (write-to-output-thunk - (lambda (txn) - (write (rewrite-dates txn) outfile) - (newline outfile)))) - (begin - (display (string-append ";;;; Data from " file) outfile) - (newline outfile) - (newline outfile)) - (let loop - ((line (read-line infile))) - (if - (eof-object? line) #f - (let - ((newline (rewrite-line line))) - (loop (read-line infile))))) - (if - (checkdatemaxes) - #f - (begin - (display "Problem: Illegal date format!") (newline) - (display ";;;; Problem - date format conflict!" outfile) - (newline outfile))) - (display ";;; Transactional data:" outfile) - (newline outfile) - (display "(define transactions '(" outfile) - (newline outfile) - (for-each write-to-output-thunk tlist) - (display (string-append - "Total transactions: " - (number->string (length tlist)))) - (newline) - (display ")) ;;; End of transaction data" outfile) - (newline outfile) - (display "(define categories '" outfile) - (write kept-categories outfile) - (display ")" outfile) - (newline outfile) - (display (string-append - "Total categories: " - (number->string (length kept-categories)))) - (newline) - (display "(define acclist") - (display (acclist account-group)) - (display ")") - (newline) - (display "(define acclist") - (display (catlist account-group)) - (display ")") - (newline) - (let* - ((acclist (acclist account-group)) - (catlist (catlist account-group)) - (guesses (guess-corresponding-categories kept-categories catlist acclist))) - (display "(define cattrans '" outfile) - (write guesses outfile) - (display ")" outfile) - (newline outfile)) - - (close-input-port infile) - (close-output-port outfile)))) - -;;; Rewrite a line -(define qifstate '()) - -(define rewrite-line - (lambda (line) - (if - (string=? (substring line 0 1) "!") ;;; Starts with a ! - (newstate line)) ;;; Jump to a new state... - (if (equal? qifstate 'txn) ;;; If it's a transaction - (rewrite-txn-line (striptrailingwhitespace line))))) ;;; Rewrite it - ;;; otherwise, do nothing... - -(define QIFstates - '(("!Type:Cat" . 'category) - ("!Option:AutoSwitch" . 'accounts) - ("!Clear:AutoSwitch" . 'account) - ("!Account" . 'accounts) - ("!Type:Memorized" . 'memorized) - ("!Type:Bank" . 'txn) - ("!Type:CCard" . 'txn) - ("!Type:Oth A" . 'txn))) - -;;;; Strip off trailing whitespace -(define (striptrailingwhitespace line) - (let - ((stringsize (string-length line))) - (if - (< stringsize 1) - "" - (let* - ((lastchar (string-ref line (- stringsize 1)))) - (if - (char-whitespace? lastchar) - (striptrailingwhitespace (substring line 0 (- stringsize 1))) - line))))) - -(define (newstate line) +(define (read-qif-file file account-group) + (set! qif-txn-list '()) ; Reset the transaction list... + (set! thetxn (build-mystruct-instance qif-txn-structure)) + (resetdates) ; Reset the date checker (let* - ((statepair (assoc (striptrailingwhitespace line) QIFstates))) - (begin + ((infile (open-input-file file))) + (let loop + ((line (read-line infile))) (if - (pair? statepair) - (set! qifstate (car (cddr statepair))) - #f)))) + (eof-object? line) #f + (let + ((newline (read-qiffile-line line))) + (loop (read-line infile))))) + (if + (checkdatemaxes) + #f ;;; Do nothing; all is ok + (begin + (display "Problem with dating - ambiguous data!") + (newline))) + ;;; Now, return results: + qif-txn-list)) + +(define (process-qif-file file account-group) + ; Opens file, rewrites all the lines, closes files + (display (string-append "rewriting file:" file)) (newline) + (let* + ((qif-txn-list (read-qif-file file account-group)) + (category-analysis (analyze-qif-transaction-categories qif-txn-list)) + (outfile (open-output-file (string-append file ".XAC") 'replace)) +; (outfile (open-output-file (string-append file ".XAC"))) + (write-to-output-thunk + (lambda (txn) + (write (cdr (txn 'geteverything 'nil)) outfile) + (newline outfile)))) + + (display (string-append ";;;; Data from " file) outfile) + (newline outfile) + (newline outfile) + (display ";;; Transactional data:" outfile) + (newline outfile) + (display "(define transactions '(" outfile) + (newline outfile) + (for-each write-to-output-thunk qif-txn-list) + (display (string-append + "Total transactions: " + (number->string (length qif-txn-list)))) + (newline) + (display ")) ;;; End of transaction data" outfile) + (newline outfile) + (newline outfile) + (display "(define acclist") + (display (gnc:get-account-list account-group)) + (display ")") + (newline) + (display "(define acclist") + (display (gnc:get-incomes-list account-group)) + (display ")") + (newline) + (display "(define category-analysis '" outfile) + (for-each (lambda (x) (display "(" outfile) + (write (car x) outfile) + (display " " outfile) + (write ((cdr x) 'list 'all) outfile) + (display ")" outfile) + (newline outfile)) category-analysis) + (display ")" outfile) + (display "(define category-analysis '") + (for-each (lambda (x) + (display "(") + (write (car x)) + (display " ") + (write ((cdr x) 'list 'all)) + (display ")") + (newline)) category-analysis) + (display ")") + (newline outfile) + (close-output-port outfile))) + +(define (read-qiffile-line line) + (display (string-append "Line:" line)) (newline) + (if + (char=? (string-ref line 0) #\!) ;;; Starts with a ! + (newqifstate line)) ;;; Jump to a new state... + (cond + ((eq? qifstate 'txn) ;;; If it's a transaction + (rewrite-txn-line (striptrailingwhitespace line))) + (else + (display "Ignoring non-transaction:") (display qifstate)(newline)))) + (define (transnull line) #f) ; do nothing with line @@ -132,7 +110,7 @@ (define (rewrite-txn-line line) (let* ((fchar (substring line 0 1)) - (found (assoc fchar trans-jumptable))) + (found (lookup fchar trans-jumptable))) (if found (let @@ -140,129 +118,91 @@ (tfunction line)) (oops-new-command-type line)))) -;;;; Category management -(define kept-categories '()) - -(define (reset-categories) ;; reset the list - (set! kept-categories '())) - -;;;;(keep-category-for-summary category) -(define (keep-category-for-summary category) - (let - ((found (assoc category kept-categories))) - (if - found - (set-cdr! found (+ (cdr found) 1)) - (set! kept-categories (cons (cons category 1) kept-categories))))) - -;;; Is the account a QIF "category"? -(define (account-category? category) - (and - (string=? (substring category 0 1) "[") - (let - ((len (string-length category))) - (string=? - (substring category (- len 1) len) "]")))) - -;;;; "numerizeamount" takes the commaed string that QIF provides, -;;;; removes commas, and turns it into a number. -(define (numerizeamount amount-as-string) - (let* - ((commasplit (split-on-somechar amount-as-string #\,)) - (decommaed (apply string-append commasplit)) - (numeric (string->number decommaed))) - (if - numeric ; did the conversion succeed? - numeric ; Yup. Return the value - amount-as-string))) ; Nope. Return the original value. - ;;;; At the end of a transaction, -;;;; Insert queued material into "atrans" (such as splits, address) -;;;; Add "atrans" to the master list of transactions, +;;;; Insert queued material into "thetxn" (such as splits, address) +;;;; Add "thetxn" to the master list of transactions, ;;;; And then clear stateful variables. (define (end-of-transaction line) ; End of transaction (if (not (null? addresslist)) - (set! atrans (cons (cons 'address addresslist) atrans))) + (thetxn 'put 'addresslist addresslist)) (if splits? (begin - (set! atrans (cons (cons 'splits splitlist) atrans)) - (ensure-split-adds-up))) - (set! tlist (cons atrans tlist)) + (thetxn 'put 'splitslist splitlist) + (ensure-split-adds-up) + (resetsplits))) + (set! qif-txn-list (cons thetxn qif-txn-list)) (set! addresslist '()) - (resetsplits) - (set! atrans '())) + (set! thetxn (build-mystruct-instance qif-txn-structure))) ;;;;;;;;;;; Various "trans" functions for different ;;;;;;;;;;; sorts of QIF lines (define (transmemo line) - (let* - ((linelen (string-length line)) - (memo (substring line 1 linelen))) - (set! atrans (cons (cons 'memo memo) atrans)))) + (thetxn 'put 'memo (strip-qif-header line))) (define (transaddress line) - (let* - ((linelen (string-length line)) - (addline (substring line 1 linelen))) - (set! addresslist (cons addline addresslist)))) + (set! addresslist (cons (strip-qif-header line) addresslist))) (define (transdate line) (let* - ((linelen (string-length line)) - (date (replacespace0 (substring line 1 linelen))) + ((date (replacespace0 (strip-qif-header line))) (dpieces (split-on-somechar date #\/))) - (set! atrans (cons (cons 'date date) atrans)) + (thetxn 'put 'date date) (newdatemaxes dpieces))) ; collect info on date field ordering ; so we can guess the date format at ; the end based on what the population ; looks like (define (transamt line) - (let* - ((linelen (string-length line)) - (amount (numerizeamount (substring line 1 linelen)))) - (set! atrans (cons (cons 'amount amount) atrans)))) + (define (numerizeamount amount-as-string) + (let* + ((commasplit (split-on-somechar amount-as-string #\,)) + (decommaed (apply string-append commasplit)) + (numeric (string->number decommaed))) + (if + numeric ; did the conversion succeed? + numeric ; Yup. Return the value + amount-as-string))) ; Nope. Return the original value. + (thetxn 'put 'amount (numerizeamount (strip-qif-header line)))) (define (transid line) - (let* - ((linelen (string-length line)) - (id (substring line 1 linelen))) - (set! atrans (cons (cons 'id id) atrans)))) + (thetxn 'put 'id (strip-qif-header line))) (define (transstatus line) - (let* - ((linelen (string-length line)) - (status (substring line 1 linelen))) - (set! atrans (cons (cons 'status status) atrans)))) + (thetxn 'put 'status (strip-qif-header line))) (define (transpayee line) - (let* - ((linelen (string-length line)) - (payee (substring line 1 linelen))) - (set! atrans (cons (cons 'payee payee) atrans)))) + (thetxn 'put 'payee (strip-qif-header line))) (define (transcategory line) - (let* - ((linelen (string-length line)) - (category (substring line 1 linelen))) - (keep-category-for-summary category) - (set! atrans (cons (cons 'category category) atrans)))) + (thetxn 'put 'category (strip-qif-header line))) -(define - trans-jumptable - (list - (cons "^" end-of-transaction) - (cons "D" transdate) - (cons "T" transamt) - (cons "N" transid) - (cons "C" transstatus) - (cons "P" transpayee) - (cons "L" transcategory) - (cons "M" transmemo) - (cons "!" transnull) - (cons "U" transnull) - (cons "S" transsplitcategory) - (cons "A" transaddress) - (cons "$" transsplitamt) - (cons "%" transsplitpercent) - (cons "E" transsplitmemo))) +(define trans-jumptable (initialize-lookup)) + +(let* + ((ltable + '(("^" end-of-transaction) + ("D" transdate) + ("T" transamt) + ("N" transid) + ("C" transstatus) + ("P" transpayee) + ("L" transcategory) + ("M" transmemo) + ("!" transnull) + ("U" transnull) + ("S" transsplitcategory) + ("A" transaddress) + ("$" transsplitamt) + ("%" transsplitpercent) + ("E" transsplitmemo))) + (setter + (lambda (lst) + (let ((command (car lst)) + (function (eval (cadr lst)))) + (set! trans-jumptable + (lookup-set! trans-jumptable command function)))))) + (for-each setter ltable)) + +(display "trans-jumptable") +(display trans-jumptable) +(newline) \ No newline at end of file diff --git a/src/scm/qif2gc.scm b/src/scm/qif2gc.scm new file mode 100644 index 0000000000..05e918e982 --- /dev/null +++ b/src/scm/qif2gc.scm @@ -0,0 +1,184 @@ +;;; $Id$ +;;;; Take the set of stuff from a QIF file, and turn it into the +;;;; structures expected by GnuCash. + +;;; In each of these, "gncpointer" should be populated with the +;;; address of the object. This way the object can be maintained +;;; on both sides of the Lisp<==>C boundary +;;; For instance: +; (define (initialize-split) ;;; Returns a gnc-split-structure +; (let ((ptr (gnc:split-create)) +; (splitstruct (build-mystruct-instance gnc-split-structure))) +; (splitstruct 'put 'gncpointer ptr) +; splitstruct)) + +(define gnc-account-structure + (define-mystruct '(id name flags type code description + notes currency security splitlist + parentaccountgroup + childrenaccountgroup))) + +(define gnc-account-group-structure + (define-mystruct '(parentaccount peercount + peerlist))) + +(define gnc-txn-structure + (define-mystruct '(num date-posted date-entered description + docref splitlist))) + +(define gnc-split-structure + (define-mystruct '(memo action reconcile-state + reconciled-date docref share-amount + share-price account parenttransaction))) + +(define gnc-txn-list (initialize-lookup)) +(define gnc-acc-list (initialize-lookup)) +(define gnc-split-list (initialize-lookup)) + +(define (add-qif-transaction-to-gnc-lists txn curtxn cursplitlist) + (define txnref (gensym)) + (set! gnc-txn-list (lookup-set! gnc-txn-list txnref curtxn)) + ;;; Fill in gnc-txn-list, gnc-acc-list, gnc-split-list + ;;; First, let's fill in curtxn with some values from txn + (curtxn 'put 'num (txn 'get 'id)) + (curtxn 'put 'date-posted (txn 'get 'date)) + (curtxn 'put 'date-entered '(1999 0903)) ;;; Which should get replaced! + (curtxn 'put 'description (txn 'get 'memo)) + (curtxn 'put 'docref (txn 'get 'id)) + ;;; Now, set up the list of splits... + (let ((mainref (gensym)) + (mainsplit (build-mystruct-instance gnc-split-structure))) + (mainsplit 'put 'memo (txn 'get 'memo)) + (mainsplit 'put 'share-amount (txn 'get 'amount)) + (mainsplit 'put 'reconcile-state (txn 'get 'status)) + (mainsplit 'put 'reconcile-state + (if (string=? (txn 'get 'status) "*") + '(1999 09 03) #f)) + (mainsplit 'put 'docref (txn 'get 'id)) + (mainsplit 'put 'parenttransaction txnref) + (mainsplit 'put 'account accountname) + (set! gnc-split-list (lookup-set! gnc-split-list mainref mainsplit))) + + ;;;; Chunk of missing code: + ;;;; ---> Take a look at the split list in (txn 'get 'splitlist) + ;;;; Add a split for each one of these + ;;;; Alternatively, add a split for (txn 'get 'category) + ;;;; ---> Attach all the accounts to the corresponding splits + (curtxn 'put 'splitlist lookup-keys cursplitlist)) + +(define (qif-to-gnucash txnlist accountname) + (letrec + ((curtxn (build-mystruct-instance gnc-txn-structure)) + (cursplitlist (initialize-lookup)) + (process-txn (lambda (x) (add-qif-transaction-to-gnc-lists x curtxn cursplitlist)))) + (for-each process-txn txnlist))) + +; QIF essentially provides a structure that sort of looks like +; (chequing +; (deposit 500 salary) +; (withdraw 300 rent) +; (transfer 200 mastercard)) + +; Asset account +; --> Bunch of transactions, implicitly associated with it +; --> That are also associated with income/expense accounts + +; This must be transformed to something more like: +;;; Account points to vector of splits, each split points to a transaction + +; Accounts look like: +; ('chequing +; (500 'chequing 'deposit) +; (-300 'chequing 'withdraw) +; (-200 'chequing 'transfer)) + +; ('mastercard +; (200 'mastercard 'transfer)) + +; ('salary +; (-500 'salary 'deposit)) + +; ('rent +; (-500 'rent 'withdraw)) + +; Transactions look like: +; ('deposit +; (500 'chequing 'deposit) +; (-500 'salary 'deposit)) + +; (withdraw +; (-300 'chequing 'withdraw) +; (-500 'rent 'withdraw)) + +; (transfer +; (200 'mastercard 'transfer) +; (-200 'chequing 'transfer)) + +; And the splits are the subordinates in both cases... + +;;; Thus, the approach should be: +; -- For each QIF transaction QT +; -- Create transaction +; -- Construct the splits for the current transaction +; If there's no QIF split, then there's two: +; - One for the [current account] +; - Offset by the [category] +; Alternatively: +; - One for the [current account] +; - Offset by the set of QIF split items +; - Link splits to transaction +; - Link transaction to split list +; - Link each splits to appropriate account +; - Add each split to the account-to-splits list for the account + +(define (initialize-split) ;;; Returns a gnc-split-structure + (let ((ptr (gnc:split-create)) + (splitstruct (build-mystruct-instance gnc-split-structure))) + (splitstruct 'put 'gncpointer ptr) + splitstruct)) + +(define (gnc:set-split-values q-txn q-split) + (let ((g:split (initialize-split)) + (g:memo (q-split 'get 'memo)) + (g:amount (q-split 'get 'amount)) + (g:docref (q-split 'get 'id)) + (g:action (q-txn 'get 'status))) + (if g:amount (gnc:split-set-value g:split g:amount)) + (if g:memo (gnc:split-set-memo g:split g:memo)) + (if g:action (gnc:split-set-action g:split g:action)) + (if g:docref (gnc:split-set-docref g:split g:docref)))) + +(define (gnc:link-split-to-parents g:split g:account g:transaction) + (gnc:transaction-append-split g:transaction g:split) + (gnc:account-insert-split g:account g:split)) + +(define (initialize-account) ;;; Returns a gnc-split-structure + (let ((ptr (gnc:malloc-account)) + (accstruct (build-mystruct-instance gnc-account-structure))) + (accstruct 'put 'gncpointer ptr) + accstruct)) + +(define (initialize-txn) ;;; Returns a gnc-split-structure + (let ((ptr (gnc:transaction-create)) + (txnstruct (build-mystruct-instance gnc-transaction-structure))) + (txnstruct 'put 'gncpointer ptr) + txnstruct)) + +(if testing? + (begin + (display "need test scripts in qif2gc.scm"))) + +(define best-guesses (initialize-lookup)) + +(define (add-best-guess qif gnc) + (set! best-guesses (lookup-set! best-guesses qif gnc))) + +(define (find-best-guess qif) + (lookup qif best-guesses)) + +(define qif-to-gnc-acct-xlation-table (initialize-lookup)) + +(define (improve-qif-to-gnc-translation qif gnc) + (set! qif-to-gnc-acct-xlation-table + (lookup-set! qif-to-gnc-acct-xlation-table + qif gnc)))diff -u /dev/null 'gnucash/src/scm/qifcats.scm' diff --git a/src/scm/qifcats.scm b/src/scm/qifcats.scm new file mode 100644 index 0000000000..6efac0d3f9 --- /dev/null +++ b/src/scm/qifcats.scm @@ -0,0 +1,58 @@ +;;; $Id$ +;;;;; Category management + +(define qif-cat-list (initialize-lookup)) + +(define qif-category-structure + (define-mystruct '(name count value))) + +(define (analyze-qif-categories) + (define (analyze-qif-category item) + (let* + ((id (car item)) + (q (cdr item)) + (gc (build-mystruct-instance gnc-account-structure)) + (positive? (<= 0 (q 'get 'amount))) + (balance-sheet? (char=? (string-ref id 0) #\[)) + (propername (if balance-sheet? + (substring 1 (- (string-length id) 1)) + id))) + (gc 'put 'type + (if positive? + (if balance-sheet? + 'BANK + 'CREDIT) + (if balance-sheet? + 'INCOME + 'EXPENSE))) + (gc 'put 'description id) + (gc 'put 'currency favorite-currency))) + (set! qif-analysis (initialize-lookup)) + (for-each analyze-qif-category qif-category-list)) + +(define (analyze-qif-transaction-categories qif-txn-list) + (define (analyze-qif-txn-category txn) + (collect-cat-stats (txn 'get 'category) + (txn 'get 'amount)) + (let ((splits (txn 'get 'splitlist))) + (if splits + (for-each analyze-qif-split-category splits)))) + (set! qif-cat-list (initialize-lookup)) + (for-each analyze-qif-txn-category qif-txn-list) + qif-cat-list) + +(define (analyze-qif-split-category split) + (collect-cat-stats (split 'get 'category) (split 'get 'amount))) + +(define (collect-cat-stats category amount) + (let* ((s (lookup category qif-cat-list))) + (if s ;;; Did we find it in qif-cat-list? + (let ((sc (cdr s))) + (sc 'put 'value (+ amount (sc 'get 'value))) + (sc 'put 'count (+ 1 (sc 'get 'count)))) + (begin ;;; Nope; need to add new entry to qif-cat-list + (let ((nc (build-mystruct-instance qif-category-structure))) + (nc 'put 'name category) + (nc 'put 'count 1) + (nc 'put 'value amount) + (set! qif-cat-list (lookup-set! qif-cat-list category nc))))))) diff --git a/src/scm/qifstate.scm b/src/scm/qifstate.scm new file mode 100644 index 0000000000..f14abeeb6c --- /dev/null +++ b/src/scm/qifstate.scm @@ -0,0 +1,39 @@ +;;; $Id$ +(define qifstate #f) + +(define (newqifstate line) + (let* + ((QIFstates + '(("!Type:Cat" . category) + ("!Option:AutoSwitch" . accounts) + ("!Clear:AutoSwitch" . accounts) + ("!Account" . accounts) + ("!Type:Memorized" . memorized) + ("!Type:Bank" . txn) + ("!Type:CCard" . txn) + ("!Type:Oth A" . txn))) + (name (striptrailingwhitespace line)) + (statepair (assoc name QIFstates))) + (if (pair? statepair) + (begin + (display "New qifstate:") (display (cdr statepair)) + (newline) + (set! qifstate (cdr statepair)) + (cdr statepair)) + (begin + (display "No new QIF state") (newline))))) + +(testing "newqifstate" + "!Account" + 'accounts + (newqifstate "!Account")) + +(testing "newqifstate" + "!Type:Cat " + 'category + (newqifstate "!Type:Cat")) + +(testing "newqifstate" + "nothing" + #f + (newqifstate "nothing")) diff --git a/src/scm/split-qif.scm b/src/scm/split-qif.scm index 977b3d2296..aac81a4d5d 100644 --- a/src/scm/split-qif.scm +++ b/src/scm/split-qif.scm @@ -1,13 +1,15 @@ +;;; $Id$ ;;;;;;;;;;; QIF Split Management ;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Variables used to handle splits ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define splits? #f) (define splitlist '()) -(define splitcategory #f) -(define splitamount #f) -(define splitmemo #f) -(define splitpercent #f) +(define qif-split-structure + (define-mystruct '(category memo amount percent))) + +(define thesplit (build-mystruct-instance qif-split-structure)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; And functions to nuke out the splits ;;;; ;;;; at the start/end of each transaction ;;;; @@ -15,44 +17,38 @@ (define (resetsplits) ;;; Do this at end of whole txn (set! splits? #f) (set! splitlist '()) - (resetsplit)) - -(define (resetsplit) ;;; After each split item - (set! splitcategory #f) - (set! splitmemo #f) - (set! splitpercent #f)) + (set! thesplit (build-mystruct-instance qif-split-structure))) ;;;; This function *should* validate that a split adds up to ;;;; the same value as the transaction, and gripe if it's not. ;;;; I'm not sure how to usefully gripe, so I leave this as a stub. (define (ensure-split-adds-up) (let* - ((txnamount (cdr (assoc 'amount atrans))) - (find-amount (lambda (txnlist) (cdr (assoc 'amount txnlist)))) + ((txnamount (thetxn 'get 'amount)) + (find-amount (lambda (splitstructure) (splitstructure 'get 'amount))) (total-of-split (apply + (map find-amount splitlist)))) (if (< (abs (- txnamount total-of-split)) 0.01) ; Difference tiny - #t - (begin + #t ;;; OK - adds up to near enough zero. + (begin ;;; Problem: Doesn't add up (display (string-append "Error - Transaction amount, " - (number->string txnamount) + (number->string txnamount) " not equal to sum of split amount, " (number->string total-of-split))) + (newline) + (display splitlist) + (newline) #f)))) (define (transsplitamt line) (set! splits? #T) - (let* - ((linelen (string-length line)) - (amount (numerizeamount (substring line 1 linelen))) - (amtlist (cons 'amount amount)) - (catlist (cons 'category splitcategory)) - (entry (list amtlist catlist))) - ;;; And now, add amount and memo to splitlist - (set! splitlist - (cons entry splitlist)))) + (thesplit 'put 'amount (numerizeamount (strip-qif-header line))) + ;;; And now, add amount and memo to splitlist + (display (thesplit 'what 'what)) (newline) + (set! splitlist (cons thesplit splitlist)) + (set! thesplit (build-mystruct-instance qif-split-structure))) ;;;; percentages only occur as parts of memorized transactions (define (transsplitpercent line) @@ -61,15 +57,8 @@ (define (transsplitmemo line) (set! splits? #T) - (let* - ((linelen (string-length line)) - (memo (substring line 1 linelen))) - (set! splitmemo memo))) + (thesplit 'put 'memo (strip-qif-header line))) (define (transsplitcategory line) (set! splits? #T) - (let* - ((linelen (string-length line)) - (category (substring line 1 linelen))) - (keep-category-for-summary category) - (set! splitcategory category))) + (thesplit 'put 'category (strip-qif-header line))) diff --git a/src/scm/sstring-qif.scm b/src/scm/sstring-qif.scm index 6aa900b081..727f508987 100644 --- a/src/scm/sstring-qif.scm +++ b/src/scm/sstring-qif.scm @@ -1,3 +1,4 @@ +;;; $Id$ ; IMPLEMENTS Substring search ; AUTHOR Ken Dickey ; DATE 1991 August 6 diff --git a/src/scm/startup.scm b/src/scm/startup.scm index abc75d05ff..9cb6c11efa 100644 --- a/src/scm/startup.scm +++ b/src/scm/startup.scm @@ -11,4 +11,5 @@ (gnc:load "prefs.scm") (gnc:load "command-line.scm") (gnc:load "convenience-wrappers.scm") +(gnc:load "hooks.scm") (gnc:load "main.scm") diff --git a/src/scm/structure.scm b/src/scm/structure.scm new file mode 100644 index 0000000000..eac3add3b3 --- /dev/null +++ b/src/scm/structure.scm @@ -0,0 +1,73 @@ +;;; $Id$ +;;; Some functions to help build structures + +;;; define-mystruct is used to build an association list that defines +;;; the layout of a structure... +(define (define-mystruct lst) + (define alist '()) ;; Association list + (define count 0) ;; Number of entries + (define (add-item item) + (set! alist (cons (cons item count) alist)) + (set! count (+ 1 count))) + (add-item 'gensymid) + (for-each add-item lst) + alist) +;;; Use as follows: +;;; (define qif-split-structure (define-mystruct '(category memo +;;; amount percent))) +;;; + +(define (build-mystruct-instance structinfo) + ;;; struct-instance is the vector for the data... + (define struct-instance (make-vector (length structinfo) #f)) + (define (get-item field-id) ;;; Look up entry based on ID + (let ((assocv (assoc field-id structinfo))) + (if assocv + (vector-ref struct-instance (cdr assocv)) + (begin + (display (string-append "No such field as " + (symbol->string field-id) + " in ")) + (display structinfo) + (newline) + #f)))) + + + (define (set-item! field-id value) ;;; Plunk in new value + (let ((assocv (assoc field-id structinfo))) + (if assocv + (vector-set! struct-instance (cdr assocv) value) + #f))) + + (define (actions action field . value) ;;; now, methods to be applied + (cond + ((eq? action 'get) + (let ((item (get-item field))) + (if item + (car item) + #f))) + ((eq? action 'put) + (set-item! field value)) + (else + (list structinfo struct-instance)))) + (set-item! 'gensymid (list (gensym))) ;;; Attach a unique identifier + actions) + +(if testing? + (begin + (display "Testing structur.scm - define-mystruct, build-mystruct-instance") + (newline) + (let* ((ms (define-mystruct '(f1 f2 f3))) + (mi (build-mystruct-instance ms))) + (mi 'put 'f1 122) + (mi 'put 'f3 "hello") + (display "Empty list entry:") (display (mi 'get 'f2)) (newline) + (display "and two that aren't (f1 f3):") + (display (list (mi 'get 'f1) (mi 'get 'f3))) (newline) + (display "Whole thang:") + (display (mi 'whole 'thang)) (newline) + (display "Overlay 'f3 with 42, add to 'f1 value") + (mi 'put 'f3 42) + (display (number->string (+ (mi 'get 'f1) (mi 'get 'f3)))) (newline)))) + + diff --git a/src/scm/text-export.scm b/src/scm/text-export.scm index 6b4da31f8d..ab3a90451b 100644 --- a/src/scm/text-export.scm +++ b/src/scm/text-export.scm @@ -1,6 +1,5 @@ +;;; $Id$ (require 'pretty-print) - - (define (gnc:group-map-accounts thunk group) (let loop ((num-accounts (gnc:group-get-num-accounts group)) (i 0)) @@ -26,45 +25,13 @@ ; (loop num-splits (+ i 1)))))) -(define (gnc:transaction-map-splits thunk transaction) - (let loop ((num-splits (gnc:transaction-get-split-count transaction)) - (i 0)) - (if (< i num-splits) - (cons - (thunk (gnc:transaction-get-split transaction i)) - (loop num-splits (+ i 1))) - '()))) -(define (gnc:split->output-form split) - (list - 'split - (gnc:split-get-memo split) - (gnc:split-get-action split) - (gnc:split-get-reconcile-state split) - (gnc:split-get-reconciled-date split) - (gnc:split-get-docref split) - (gnc:split-get-share-amount split) - (gnc:split-get-share-price split) - (gnc:split-get-share-price split) - (let ((xfer-account (gnc:split-get-account split)) - (xfer-account-id #f)) - (if (not (pointer-token-null? xfer-account)) - (set! xfer-account-id (gnc:account-get-id xfer-account))) - xfer-account-id))) - - -(define (gnc:transaction->output-form transaction) - (list - 'transaction - (gnc:transaction-get-num transaction) - (gnc:transaction-get-date-posted transaction) - (gnc:transaction-get-date-entered transaction) - (gnc:transaction-get-description transaction) - (gnc:transaction-get-docref transaction) - (gnc:transaction-map-splits gnc:split->output-form transaction) - - )) +(define (gnc:main-win-export-data-as-text win) + (let ((account-group (gnc:get-current-group))) + (if (not account-group) + (gnc:error-dialog "No account group available for text export.") + (gnc:account-group-export-as-text account-group)))) (define (gnc:account->output-form a) @@ -89,6 +56,25 @@ gnc:account->output-form (gnc:account-get-children a))))) +(define (gnc:account-group-export-as-text account-group) + (let ((file-name (gnc:file-selection-dialog + "Select file for text export" ""))) + (if file-name + (begin + (gnc:debug "Running text exporting to (not really) " file-name) + (pretty-print 'gnucash-data-file) + (pretty-print '(version "1.0")) + (display "\n\n;;; Account information\n") + ;; Print all the accounts + (pretty-print + (gnc:group-map-accounts + gnc:account->output-form + account-group)) + (display "\n\n;;; Transactions\n\n") + ;; Now print all the transactions + (gnc:group-begin-staged-transaction-traversals account-group) + (gnc:group-map-accounts gnc:account-transactions-export-as-text + account-group))))) (define (gnc:account-transactions-export-as-text account) (gnc:account-staged-transaction-traversal @@ -96,40 +82,38 @@ 1 (lambda (t) (pretty-print (gnc:transaction->output-form t))))) +(define (gnc:transaction->output-form transaction) + (list + 'transaction + (gnc:transaction-get-num transaction) + (gnc:transaction-get-date-posted transaction) + (gnc:transaction-get-date-entered transaction) + (gnc:transaction-get-description transaction) + (gnc:transaction-get-docref transaction) + (gnc:transaction-map-splits gnc:split->output-form transaction))) -(define (gnc:main-win-export-data-as-text win) - (let ((account-group #f) -; (session (gnc:main-window-get-session win))) - (session (gnc:main-window-get-session))) - - (if session (set! account-group (gnc:session-get-group session))) - - (if (not account-group) - (gnc:error-message-dialog "No account group available for text export.") - (gnc:account-group-export-as-text account-group)))) +(define (gnc:transaction-map-splits thunk transaction) + (let loop ((num-splits (gnc:transaction-get-split-count transaction)) + (i 0)) + (if (< i num-splits) + (cons + (thunk (gnc:transaction-get-split transaction i)) + (loop num-splits (+ i 1))) + '()))) - -(define (gnc:account-group-export-as-text account-group) - (let ((file-name (gnc:file-selection-dialog "Select file for text export" ""))) - (if file-name - (begin - (gnc:debug "Running text exporting to (not really) " file-name) - - (pretty-print 'gnucash-data-file) - (pretty-print '(version "1.0")) - - (display "\n\n;;; Account information\n") - - ;; Print all the accounts - (pretty-print - (gnc:group-map-accounts - gnc:account->output-form - account-group)) - - (display "\n\n;;; Transactions\n\n") - - ;; Now print all the transactions - (gnc:group-begin-staged-transaction-traversals account-group) - (gnc:group-map-accounts - gnc:account-transactions-export-as-text - account-group))))) +(define (gnc:split->output-form split) + (list + 'split + (gnc:split-get-memo split) + (gnc:split-get-action split) + (gnc:split-get-reconcile-state split) + (gnc:split-get-reconciled-date split) + (gnc:split-get-docref split) + (gnc:split-get-share-amount split) + (gnc:split-get-share-price split) + (gnc:split-get-share-price split) + (let ((xfer-account (gnc:split-get-account split)) + (xfer-account-id #f)) + (if (not (pointer-token-null? xfer-account)) + (set! xfer-account-id (gnc:account-get-id xfer-account))) + xfer-account-id)))