gnucash master: Multiple changes pushed

Christopher Lam clam at code.gnucash.org
Sun Aug 4 09:06:39 EDT 2019


Updated	 via  https://github.com/Gnucash/gnucash/commit/d1fddf55 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/5bd854c5 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/08de1a0a (commit)
	 via  https://github.com/Gnucash/gnucash/commit/409b97a9 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/944e7814 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/b2d1ad52 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a146d2cd (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a3150f38 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/277ba729 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/1873c2f7 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/8b7093e8 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a7a3f786 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/19114cc1 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/db93aec5 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/fbb6a956 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/76ba1331 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/d4dd2891 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/aca8a734 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/69f76c63 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/b56203e0 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/aadb3d14 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/5de4b27b (commit)
	 via  https://github.com/Gnucash/gnucash/commit/8cd7c6f7 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/32692721 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/c81e9354 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/955a5651 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e506b7c3 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e3a695d0 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e8a41bbf (commit)
	 via  https://github.com/Gnucash/gnucash/commit/bd0cbbf9 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/66511f17 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/887f7fac (commit)
	 via  https://github.com/Gnucash/gnucash/commit/456ab224 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a0a0ffbb (commit)
	 via  https://github.com/Gnucash/gnucash/commit/c77607c8 (commit)
	from  https://github.com/Gnucash/gnucash/commit/e5aabe41 (commit)



commit d1fddf557c496df910581d61a84ebc3ee7912dc5
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Aug 4 18:51:49 2019 +0800

    Modifications to consider module changes in master

diff --git a/gnucash/import-export/qif-imp/test/CMakeLists.txt b/gnucash/import-export/qif-imp/test/CMakeLists.txt
index fea1e7ac6..efa8c4123 100644
--- a/gnucash/import-export/qif-imp/test/CMakeLists.txt
+++ b/gnucash/import-export/qif-imp/test/CMakeLists.txt
@@ -9,11 +9,26 @@ set(scm_qifimp_test_with_srfi64_SOURCES
   test-qif-merge-groups.scm
   )
 
-
+set (GUILE_DEPENDS
+  scm-gnc-module
+  scm-app-utils
+  scm-engine
+  scm-test-engine
+  scm-gettext
+  scm-scm
+  scm-qif-import
+)
 gnc_add_test(test-link-qif-imp test-link.c QIF_IMP_TEST_INCLUDE_DIRS QIF_IMP_TEST_LIBS)
 
 if (HAVE_SRFI64)
   gnc_add_scheme_tests("${scm_qifimp_test_with_srfi64_SOURCES}")
+
+  gnc_add_scheme_test_targets(scm-test-qif-imp-srfi64
+    "${scm_qifimp_test_with_srfi64_SOURCES}"
+    "tests"
+    "${GUILE_DEPENDS};scm-srfi64-extras"
+    FALSE
+    )
 endif (HAVE_SRFI64)
 
 set_dist_list(test_qif_import_DIST CMakeLists.txt test-link.c
diff --git a/gnucash/import-export/qif-imp/test/test-qif-imp.scm b/gnucash/import-export/qif-imp/test/test-qif-imp.scm
index 384cb2df7..0cd0524f7 100644
--- a/gnucash/import-export/qif-imp/test/test-qif-imp.scm
+++ b/gnucash/import-export/qif-imp/test/test-qif-imp.scm
@@ -1,9 +1,9 @@
 (use-modules (gnucash gnc-module))
 (gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
 (use-modules (srfi srfi-64))
-(use-modules (gnucash engine test srfi64-extras))
-(use-modules (gnucash import-export qif-import))
-(use-modules (gnucash import-export string))
+(use-modules (tests srfi64-extras))
+(use-modules (gnucash qif-import))
+(use-modules (gnucash string))
 
 (define (run-test)
   (test-runner-factory gnc:test-runner)
diff --git a/gnucash/import-export/qif-imp/test/test-qif-merge-groups.scm b/gnucash/import-export/qif-imp/test/test-qif-merge-groups.scm
index 9bbf8ad56..bd9eb022a 100644
--- a/gnucash/import-export/qif-imp/test/test-qif-merge-groups.scm
+++ b/gnucash/import-export/qif-imp/test/test-qif-merge-groups.scm
@@ -1,11 +1,10 @@
 (use-modules (gnucash gnc-module))
 (gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
 (use-modules (srfi srfi-64))
-(use-modules (gnucash engine test srfi64-extras))
-(use-modules (gnucash import-export qif-import))
-(use-modules (gnucash import-export string))
-(use-modules (gnucash engine test test-extras))
-(use-modules (gnucash report report-system))
+(use-modules (tests srfi64-extras))
+(use-modules (gnucash qif-import))
+(use-modules (gnucash string))
+(use-modules (tests test-engine-extras))
 
 (define (run-test)
   (test-runner-factory gnc:test-runner)
@@ -23,7 +22,7 @@
 
 (define (test-gnc:account-tree-get-transactions)
   (define gnc:account-tree-get-transactions
-    (@@ (gnucash import-export qif-import) gnc:account-tree-get-transactions))
+    (@@ (gnucash qif-import) gnc:account-tree-get-transactions))
 
   (test-group-with-cleanup "test-gnc:account-tree-get-transactions"
     (create-test-data)
@@ -37,7 +36,7 @@
 
 (define (test-gnc:account-tree-find-duplicates)
   (define gnc:account-tree-find-duplicates
-    (@@ (gnucash import-export qif-import) gnc:account-tree-find-duplicates))
+    (@@ (gnucash qif-import) gnc:account-tree-find-duplicates))
   (define new-structure
     (list "Root" (list (cons 'type ACCT-TYPE-ASSET))
           (list "Asset"
diff --git a/gnucash/import-export/qif-imp/test/test-qif-parse.scm b/gnucash/import-export/qif-imp/test/test-qif-parse.scm
index ea47029d9..9d16b244c 100644
--- a/gnucash/import-export/qif-imp/test/test-qif-parse.scm
+++ b/gnucash/import-export/qif-imp/test/test-qif-parse.scm
@@ -1,9 +1,9 @@
 (use-modules (gnucash gnc-module))
 (gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
 (use-modules (srfi srfi-64))
-(use-modules (gnucash engine test srfi64-extras))
-(use-modules (gnucash import-export qif-import))
-(use-modules (gnucash import-export string))
+(use-modules (tests srfi64-extras))
+(use-modules (gnucash qif-import))
+(use-modules (gnucash string))
 
 (define (run-test)
   (test-runner-factory gnc:test-runner)
@@ -28,27 +28,27 @@
 
 ;; the following isn't exported but can be tested anyway!
 (define qif-parse:fix-year
-  (@@ (gnucash import-export qif-import) qif-parse:fix-year))
+  (@@ (gnucash qif-import) qif-parse:fix-year))
 (define qif-parse:parse-acct-type
-  (@@ (gnucash import-export qif-import) qif-parse:parse-acct-type))
+  (@@ (gnucash qif-import) qif-parse:parse-acct-type))
 (define qif-parse:parse-cleared-field
-  (@@ (gnucash import-export qif-import) qif-parse:parse-cleared-field))
+  (@@ (gnucash qif-import) qif-parse:parse-cleared-field))
 (define qif-split:parse-category
-  (@@ (gnucash import-export qif-import) qif-split:parse-category))
+  (@@ (gnucash qif-import) qif-split:parse-category))
 (define qif-parse:parse-action-field
-  (@@ (gnucash import-export qif-import) qif-parse:parse-action-field))
+  (@@ (gnucash qif-import) qif-parse:parse-action-field))
 (define qif-parse:check-date-format
-  (@@ (gnucash import-export qif-import) qif-parse:check-date-format))
+  (@@ (gnucash qif-import) qif-parse:check-date-format))
 (define qif-parse:parse-date/format
-  (@@ (gnucash import-export qif-import) qif-parse:parse-date/format))
+  (@@ (gnucash qif-import) qif-parse:parse-date/format))
 (define qif-parse:check-number-format
-  (@@ (gnucash import-export qif-import) qif-parse:check-number-format))
+  (@@ (gnucash qif-import) qif-parse:check-number-format))
 (define qif-parse:parse-number/format
-  (@@ (gnucash import-export qif-import) qif-parse:parse-number/format))
+  (@@ (gnucash qif-import) qif-parse:parse-number/format))
 (define qif-parse:check-number-formats
-  (@@ (gnucash import-export qif-import) qif-parse:check-number-formats))
+  (@@ (gnucash qif-import) qif-parse:check-number-formats))
 (define qif-parse:parse-numbers/format
-  (@@ (gnucash import-export qif-import) qif-parse:parse-numbers/format))
+  (@@ (gnucash qif-import) qif-parse:parse-numbers/format))
 
 
 (define (test-qif-parse:fix-year)
diff --git a/gnucash/report/eguile-html-utilities.scm b/gnucash/report/eguile-html-utilities.scm
index 2dd85917d..2c8692acb 100644
--- a/gnucash/report/eguile-html-utilities.scm
+++ b/gnucash/report/eguile-html-utilities.scm
@@ -30,7 +30,6 @@
 (gnc:module-load "gnucash/report" 0)
 (gnc:module-load "gnucash/app-utils" 0)
 
-(use-modules (gnucash report eguile-gnc))
 (use-modules (ice-9 regex))  ; for regular expressions
 (use-modules (srfi srfi-13)) ; for extra string functions
 
diff --git a/libgnucash/app-utils/test/CMakeLists.txt b/libgnucash/app-utils/test/CMakeLists.txt
index 86c49a8a0..111f7b3b8 100644
--- a/libgnucash/app-utils/test/CMakeLists.txt
+++ b/libgnucash/app-utils/test/CMakeLists.txt
@@ -91,6 +91,7 @@ set_dist_list(test_app_utils_DIST
   test-sx.cpp
   test-c-interface.scm
   test-date-utilities.scm
+  test-options.scm
   ${test_app_utils_scheme_SOURCES}
   ${test_app_utils_SOURCES}
 )
diff --git a/libgnucash/app-utils/test/test-options.scm b/libgnucash/app-utils/test/test-options.scm
index 94c288089..0ead090a9 100644
--- a/libgnucash/app-utils/test/test-options.scm
+++ b/libgnucash/app-utils/test/test-options.scm
@@ -1,7 +1,7 @@
 (use-modules (gnucash gnc-module))
 (gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
 (use-modules (srfi srfi-64))
-(use-modules (gnucash engine test srfi64-extras))
+(use-modules (tests srfi64-extras))
 
 (define (run-test)
   (test-runner-factory gnc:test-runner)

commit 5bd854c5508677ab4e12a07e86afaea665ff4a6e
Merge: e5aabe41d 08de1a0ab
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Aug 4 13:44:12 2019 +0800

    Merge branch 'maint'

diff --cc gnucash/import-export/qif-imp/qif-parse.scm
index aa1877370,3c9194ee5..4235c361d
--- a/gnucash/import-export/qif-imp/qif-parse.scm
+++ b/gnucash/import-export/qif-imp/qif-parse.scm
@@@ -23,29 -23,8 +23,8 @@@
  ;; Boston, MA  02110-1301,  USA       gnu at gnu.org
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
 -(use-modules (gnucash import-export string))
 +(use-modules (gnucash string))
- 
- (define qif-category-compiled-rexp
-   (make-regexp "^ *(\\[)?([^]/|]*)(]?)(/?)([^|]*)(\\|(\\[)?([^]/]*)(]?)(/?)(.*))? *$"))
- 
- (define qif-date-compiled-rexp
-   (make-regexp "^ *([0-9]+) *[-/.'] *([0-9]+) *[-/.'] *([0-9]+).*$|^ *([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]).*$"))
- 
- (define qif-date-mdy-compiled-rexp
-   (make-regexp "([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])"))
- 
- (define qif-date-ymd-compiled-rexp
-   (make-regexp "([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])"))
- 
- (define decimal-radix-regexp
-   (make-regexp
-    "^ *[$]?[+-]?[$]?[0-9]+[+-]?$|^ *[$]?[+-]?[$]?[0-9]?[0-9]?[0-9]?([,'][0-9][0-9][0-9])*(\\.[0-9]*)?[+-]? *$|^ *[$]?[+-]?[$]?[0-9]+\\.[0-9]*[+-]? *$"))
- 
- (define comma-radix-regexp
-   (make-regexp
-    "^ *[$]?[+-]?[$]?[0-9]+[+-]?$|^ *[$]?[+-]?[$]?[0-9]?[0-9]?[0-9]?([\\.'][0-9][0-9][0-9])*(,[0-9]*)?[+-]? *$|^ *[$]?[+-]?[$]?[0-9]+,[0-9]*[+-]? *$"))
- 
- (define integer-regexp (make-regexp "^[$]?[+-]?[$]?[0-9]+[+-]? *$"))
+ (use-modules (srfi srfi-13))
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;  qif-split:parse-category
diff --cc gnucash/report/eguile-html-utilities.scm
index 05a5aef09,d123f6109..2dd85917d
--- a/gnucash/report/eguile-html-utilities.scm
+++ b/gnucash/report/eguile-html-utilities.scm
@@@ -27,9 -29,10 +27,10 @@@
  (use-modules (gnucash utilities))
  (use-modules (gnucash gnc-module))
  (use-modules (gnucash app-utils))
 -(gnc:module-load "gnucash/report/report-system" 0)
 +(gnc:module-load "gnucash/report" 0)
  (gnc:module-load "gnucash/app-utils" 0)
  
+ (use-modules (gnucash report eguile-gnc))
  (use-modules (ice-9 regex))  ; for regular expressions
  (use-modules (srfi srfi-13)) ; for extra string functions
  
diff --cc gnucash/report/eguile-utilities.scm
index b55a39fa1,0e536212f..19413551e
--- a/gnucash/report/eguile-utilities.scm
+++ b/gnucash/report/eguile-utilities.scm
@@@ -28,10 -30,9 +28,9 @@@
  (use-modules (gnucash gnc-module))
  (use-modules (gnucash app-utils))
  (use-modules (gnucash core-utils))
 -(gnc:module-load "gnucash/report/report-system" 0)
 +(gnc:module-load "gnucash/report" 0)
  (gnc:module-load "gnucash/app-utils" 0)
  
- 
  (define-public (fmtnumber n)
    ;; Format a number (integer or real) into something printable
    (number->string (if (integer? n) 
@@@ -57,59 -50,27 +48,56 @@@
  ;; 'Safe' versions of cdr and cadr that don't crash
  ;; if the list is empty  (is there a better way?)
  (define-public (safe-cdr l)
-   (if (null? l)
-     '()
-     (cdr l)))
+   (if (null? l) '()
+       (cdr l)))
  (define-public (safe-cadr l)
-   (if (null? l)
-     '()
-     (if (null? (cdr l))
-       '()
-       (cadr l))))
+   (cond
+    ((null? l) '())
+    ((null? (cdr l)) '())
+    (else (cadr l))))
  
 +; deprecated - use find-stylesheet or find-template instead
  (define-public (find-file fname)
    ;; Find the file 'fname', and return its full path.
 -  ;; First look in the user's .gnucash directory.
 +  ;; First look in the user's .config/gnucash directory.
    ;; Then look in Gnucash's standard report directory.
    ;; If no file is found, returns just 'fname' for use in error messages.
 -  ;; Note: this has been tested on Linux and Windows Vista so far...
 -  (let ((userpath (gnc-build-userdata-path fname))
 -        (syspath  (gnc-build-report-path fname)))
 -    ;; make sure there's a trailing delimiter
 -    (cond
 -     ((access? userpath R_OK) userpath)
 -     ((access? syspath R_OK) syspath)
 -     (else fname))))
 +  (let* ((stylesheetpath (find-stylesheet fname))
 +         (templatepath  (find-template fname)))
 +    ; make sure there's a trailing delimiter
 +      (issue-deprecation-warning "find-file is deprecated. Please use find-stylesheet or find-template instead.")
-       (if (access? stylesheetpath R_OK)
-         stylesheetpath
-         (if (access? templatepath R_OK)
-           templatepath
-           fname))))
++      (cond
++       ((access? stylesheetpath R_OK) stylesheetpath)
++       ((access? templatepath R_OK) templatepath)
++       (else fname))))
 +
 +(define (find-internal ftype fname)
 +  ;; Find the file fname', and return its full path.
 +  ;; First look in the user's .config/gnucash directory.
 +  ;; Then look in Gnucash' gnucash/reports/'ftype' directory.
 +  ;; If no file is found, returns just 'fname' for use in error messages.
 +  (let* ((userpath (gnc-build-userdata-path fname))
 +         (frelpath (string-join (list (symbol->string ftype) fname) "/"))
 +         (syspath  (gnc-build-reports-path frelpath)))
 +        (if (access? userpath R_OK)
 +          userpath
 +          (if (access? syspath R_OK)
 +            syspath
 +            fname))))
 +
 +(define-public (find-stylesheet fname)
 +  ;; Find the stylesheet 'fname', and return its full path.
 +  ;; First look in the user's .config/gnucash directory.
 +  ;; Then look in Gnucash' gnucash/reports/stylesheets directory.
 +  ;; If no file is found, returns just 'fname' for use in error messages.
 +  (find-internal 'stylesheets fname))
 +
 +(define-public (find-template fname)
 +  ;; Find the template 'ftype'/'fname', and return its full path.
 +  ;; First look in the user's .config/gnucash directory.
 +  ;; Then look in Gnucash' gnucash/reports/templates directory.
 +  ;; If no file is found, returns just 'fname' for use in error messages.
 +  (find-internal 'templates fname))
  
  ; Define syntax for more readable for loops (the built-in for-each requires an
  ; explicit lambda and has the list expression all the way at the end).
diff --cc gnucash/report/reports/standard/balsheet-eg.scm
index c32ccf245,ccec8c5ae..4f815ff2a
--- a/gnucash/report/reports/standard/balsheet-eg.scm
+++ b/gnucash/report/reports/standard/balsheet-eg.scm
@@@ -35,9 -35,10 +35,8 @@@
  (use-modules (gnucash utilities))
  (use-modules (gnucash gnc-module))
  (use-modules (gnucash gettext))
 -(use-modules (gnucash report eguile-gnc))
 -(use-modules (gnucash report eguile-utilities))
 -(use-modules (gnucash report eguile-html-utilities))
 +(use-modules (gnucash eguile))
  
- (use-modules (ice-9 regex))  ; for regular expressions
  (use-modules (ice-9 local-eval))  ; for the-environment
  (use-modules (srfi srfi-13)) ; for extra string functions
  
diff --cc libgnucash/app-utils/test/CMakeLists.txt
index 369f27a8e,dd9e259a1..86c49a8a0
--- a/libgnucash/app-utils/test/CMakeLists.txt
+++ b/libgnucash/app-utils/test/CMakeLists.txt
@@@ -47,11 -47,12 +47,12 @@@ set(test_app_utils_scheme_SOURCE
  
  set (test_app_utils_scheme_SRFI64_SOURCES
    test-date-utilities.scm
+   test-options.scm
  )
  
 -gnc_add_scheme_targets(scm-test-load-app-utils-module
 +gnc_add_scheme_test_targets(scm-test-load-app-utils-module
    "test-load-app-utils-module.scm"
 -  "gnucash/reports"
 +  "tests"
    "${GUILE_DEPENDS}"
    FALSE
  )

commit 08de1a0ab01bf15128f639dc97a41c0a653a0c8c
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Jul 30 22:10:26 2019 +0800

    [qif/qif-import] fix whitespace

diff --git a/gnucash/import-export/qif-imp/qif-import.scm b/gnucash/import-export/qif-imp/qif-import.scm
index 9f955fd9d..ea7630926 100644
--- a/gnucash/import-export/qif-imp/qif-import.scm
+++ b/gnucash/import-export/qif-imp/qif-import.scm
@@ -30,9 +30,8 @@
 
 ;; We do this initialization here because src/gnome isn't a real module.
 ;; Note: Guile 2 needs to find the symbols from the extension at compile time already
-(eval-when
-      (compile load eval expand)
-      (load-extension "libgnc-gnome" "scm_init_sw_gnome_module"))
+(eval-when (compile load eval expand)
+  (load-extension "libgnc-gnome" "scm_init_sw_gnome_module"))
 
 (use-modules (sw_gnome))
 

commit 409b97a988f944b7b0412fa39bf586256b18bc9b
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Jul 30 18:37:24 2019 +0800

    [qif/string] compact function

diff --git a/gnucash/import-export/qif-imp/string.scm b/gnucash/import-export/qif-imp/string.scm
index 44bea5902..95fd8a17b 100644
--- a/gnucash/import-export/qif-imp/string.scm
+++ b/gnucash/import-export/qif-imp/string.scm
@@ -108,7 +108,7 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define-public (gnc:list-display lst)
-  (for-each (lambda (elt) (display elt)) lst))
+  (for-each display lst))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  gnc:list-display-to-string

commit 944e78144ec502a72761d085d286186ddda9d4b2
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sat Aug 3 18:38:33 2019 +0800

    [qif/qif-merge-groups] speed up duplicate-transaction finding
    
    old method would scan the new-xtn-list (i.e. imported qif
    transactions), create a query for each, and run query to find
    candidate old-transactions to match each new-transaction.
    
    new method creates 1 query only to scan old-transactions within 1 week
    of earliest and latest new-transaction date. then creates a match list
    using same heuristics:
    
    * account full name must match
    * split value must match
    * dates must differ by 1 week maximum

diff --git a/gnucash/import-export/qif-imp/qif-merge-groups.scm b/gnucash/import-export/qif-imp/qif-merge-groups.scm
index 9bf5c2fb9..e576f48a9 100644
--- a/gnucash/import-export/qif-imp/qif-merge-groups.scm
+++ b/gnucash/import-export/qif-imp/qif-merge-groups.scm
@@ -58,145 +58,103 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define (gnc:account-tree-find-duplicates old-root new-root progress-dialog)
+  (define old-accounts (gnc-account-get-descendants-sorted old-root))
+  (define (progress v)
+    (when progress-dialog (gnc-progress-dialog-set-value progress-dialog v)))
 
   ;; This procedure does all the work. We'll define it, then call it safely.
   (define (private-find)
-
-    (let ((old-accounts (gnc-account-get-descendants-sorted old-root)))
-
-      (cond
-       ((any pair? (map xaccAccountGetSplitList old-accounts))
-        ;; Get all the transactions in the new tree, thisthen iterate
-        ;; over them trying to find matches in the old tree.  If
-        ;; there are matches, push the matches onto a list.
-        (let* ((new-xtns (gnc:account-tree-get-transactions new-root))
-               (work-to-do (length new-xtns))
-               (work-done 0)
-               (matches '()))
-
-          ;; This procedure handles progress reporting, pause, and cancel.
-          (define (update-progress)
-            (set! work-done (+ 1 work-done))
-            (when (and progress-dialog (zero? (modulo work-done 8)))
-              (gnc-progress-dialog-set-value progress-dialog
-                                             (/ work-done work-to-do))
-              (qif-import:check-pause progress-dialog)
-              (if qif-import:canceled (throw 'cancel))))
-
-          (when progress-dialog
-            (gnc-progress-dialog-set-sub progress-dialog
-                                         (_ "Finding duplicate transactions")))
-
-          ;; For each transaction in the new account tree, build a query
-          ;; that matches possibly duplicate transactions in the old tree.
-          (for-each
-           (lambda (xtn)
-             (let ((query (qof-query-create-for-splits))
-                   (num-splits 0))
-               (qof-query-set-book query (gnc-account-get-book old-root))
-
-               ;; First, we only want to find only transactions
-               ;; from accounts in the old tree.
-               (xaccQueryAddAccountMatch
-                query old-accounts QOF-GUID-MATCH-ANY QOF-QUERY-AND)
-
-               ;; The date should be close to the same.. +/- a week.
-               (let ((date (xaccTransGetDate xtn)))
-                 (xaccQueryAddDateMatchTT
-                  query #t (decdate date WeekDelta)
-                  #t (incdate date WeekDelta) QOF-QUERY-AND))
-
-               ;; For each split in the new transaction, add a
-               ;; term that can match on its properties.
-               (let ((q-splits (qof-query-create-for-splits)))
-                 (for-each
-                  (lambda (split)
-                    (set! num-splits (+ num-splits 1))
-                    (let ((sq (qof-query-create-for-splits)))
-                      (qof-query-set-book sq (gnc-account-get-book old-root))
-
-                      ;; Require a match on the account name. If the name
-                      ;; doesn't exist in the old tree (indicating a new
-                      ;; account), the match will be NULL and the query
-                      ;; won't find anything.  Optimize this later.
-                      (xaccQueryAddSingleAccountMatch
-                       sq (gnc-account-lookup-by-full-name
-                           old-root (gnc-account-get-full-name
-                                     (xaccSplitGetAccount split)))
-                       QOF-QUERY-AND)
-
-                      ;; Require the value of the split in the new tree
-                      ;; to match the the value of the split in the old
-                      ;; tree.  We should really check for fuzziness.
-                      (xaccQueryAddValueMatch
-                       sq (xaccSplitGetValue split) QOF-NUMERIC-MATCH-ANY
-                       QOF-COMPARE-EQUAL QOF-QUERY-AND)
-
-                      ;; Now merge into the split query.  Reminder: q-splits
-                      ;; must be merged with an OR. Otherwise, nothing will
-                      ;; match. (For example, something can be equal to 4 or
-                      ;; to -4, but not both.)
-                      (let ((q-new (qof-query-merge q-splits sq QOF-QUERY-OR)))
-                        (qof-query-destroy q-splits)
-                        (qof-query-destroy sq)
-                        (set! q-splits q-new))))
-                  (xaccTransGetSplitList xtn))
-
-                 ;; Now q-splits will find every split that is the same as
-                 ;; any one split of the new-root transaction.  Merge it in.
-                 (let ((q-new (qof-query-merge query q-splits QOF-QUERY-AND)))
-                   (qof-query-destroy query)
-                   (qof-query-destroy q-splits)
-                   (set! query q-new)))
-
-               ;; Now that we have built a query that finds matching splits
-               ;; in the old tree, run it and build a list of transactions
-               ;; from the results.
-               ;;
-               ;; If the transaction from the new tree has more than two
-               ;; splits, then we'll assume that it fully reflects what
-               ;; occurred, and only consider transactions in the old tree
-               ;; that match with every single split.
-               ;;
-               ;; All other new transactions could be incomplete, so we'll
-               ;; consider transactions from the old tree to be possible
-               ;; duplicates even if only one split matches.
-               ;;
-               ;; For more information, see bug 481528.
-
-               (let ((old-xtns (map (lambda (elt) (cons elt #f))
-                                    (xaccQueryGetTransactions
-                                     query (if (> num-splits 2)
-                                               QUERY-TXN-MATCH-ALL
-                                               QUERY-TXN-MATCH-ANY)))))
-                 (display "\n*** gnc:account-tree-find-duplicates\n")
-                 (for-each pk old-xtns)
-
-                 ;; If anything matched the query, add it to our "matches"
-                 ;; association list, keyed by the new-root transaction.
-                 (if (not (null? old-xtns))
-                     (set! matches (cons (cons xtn old-xtns) matches))))
-
-               (qof-query-destroy query))
-             (update-progress))
-           new-xtns)
-
-          ;; Finished.
-          (when progress-dialog
-            (gnc-progress-dialog-set-value progress-dialog 1))
-
-          ;; Return the matches.
-          matches))
-
-       ;; Since there are either no accounts or no transactions in the old
-       ;; tree, duplicate checking is unnecessary.
-       (else
-        (when progress-dialog (gnc-progress-dialog-set-value progress-dialog 1))
-        '()))))
+    (cond
+     ((any (compose pair? xaccAccountGetSplitList) old-accounts)
+      ;; Get all the splits in the new tree, then iterate over them
+      ;; trying to find matches in the old tree.  If there are
+      ;; matches, push the splits' parent onto a list.
+      (let ((WeekSecs (* 60 60 24 7)))
+
+        (define new-splits
+          (let ((q (qof-query-create-for-splits))
+                (accounts (gnc-account-get-descendants-sorted new-root)))
+            (qof-query-set-book q (gnc-account-get-book new-root))
+            (xaccQueryAddAccountMatch q accounts QOF-GUID-MATCH-ANY QOF-QUERY-AND)
+            (let ((new-splits (qof-query-run q)))
+              (qof-query-destroy q)
+              new-splits)))
+
+        (define old-splits
+          (let ((q (qof-query-create-for-splits))
+                (dates (map (compose xaccTransGetDate xaccSplitGetParent) new-splits)))
+            (qof-query-set-book q (gnc-account-get-book old-root))
+            (xaccQueryAddAccountMatch q old-accounts QOF-GUID-MATCH-ANY QOF-QUERY-AND)
+            (xaccQueryAddDateMatchTT q
+                                     #t (decdate (apply min dates) WeekDelta)
+                                     #t (incdate (apply max dates) WeekDelta)
+                                     QOF-QUERY-AND)
+            (let ((splits (qof-query-run q)))
+              (qof-query-destroy q)
+              splits)))
+
+        (define work-to-do (length new-splits))
+        (define (update-progress work-done)
+          (when (and progress-dialog (zero? (modulo work-done 8)))
+            (progress (/ work-done work-to-do))
+            (qif-import:check-pause progress-dialog)
+            (if qif-import:canceled (throw 'cancel))))
+
+        (when progress-dialog
+          (gnc-progress-dialog-set-sub progress-dialog
+                                       (_ "Finding duplicate transactions")))
+
+        (let loop ((new-splits new-splits)
+                   (work-done 0)
+                   (matches '()))
+          (cond
+           ((null? new-splits)
+            (progress 1)
+            matches)
+
+           ((assoc (xaccSplitGetParent (car new-splits)) matches)
+            ;; txn has already been matched, by another split within same txn
+            (loop (cdr new-splits)
+                  (1+ work-done)
+                  matches))
+
+           (else
+            (let* ((new-split (car new-splits))
+                   (candidate-old-splits
+                    (filter
+                     (lambda (old-split)
+                       (and
+                        ;; split value matches
+                        (= (xaccSplitGetValue old-split)
+                           (xaccSplitGetValue new-split))
+                        ;; account name matches
+                        (string=?
+                         (gnc-account-get-full-name (xaccSplitGetAccount old-split))
+                         (gnc-account-get-full-name (xaccSplitGetAccount new-split)))
+                        ;; maximum 1 week date difference
+                        (<= (abs (- (xaccTransGetDate (xaccSplitGetParent old-split))
+                                    (xaccTransGetDate (xaccSplitGetParent new-split))))
+                            WeekSecs)))
+                     old-splits)))
+              (update-progress work-done)
+              (loop (cdr new-splits)
+                    (1+ work-done)
+                    (if (null? candidate-old-splits)
+                        matches
+                        (cons (cons (xaccSplitGetParent new-split)
+                                    (map (lambda (s) (cons (xaccSplitGetParent s) #f))
+                                         candidate-old-splits))
+                              matches)))))))))
+
+     ;; Since there are either no accounts or no transactions in the old
+     ;; tree, duplicate checking is unnecessary.
+     (else
+      (progress 1)
+      '())))
 
   ;; Safely do the work and return the result.
   (gnc:backtrace-if-exception
-   (lambda () (catch 'cancel private-find (lambda (key . args) #t)))))
+   (lambda () (catch 'cancel private-find (const #t)))))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

commit b2d1ad526c3b45718a82c11f1f00cb81de845af9
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Fri Aug 2 19:03:41 2019 +0800

    [qif/qif-merge-groups] compact functions

diff --git a/gnucash/import-export/qif-imp/qif-merge-groups.scm b/gnucash/import-export/qif-imp/qif-merge-groups.scm
index b53c87240..9bf5c2fb9 100644
--- a/gnucash/import-export/qif-imp/qif-merge-groups.scm
+++ b/gnucash/import-export/qif-imp/qif-merge-groups.scm
@@ -32,24 +32,12 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (define (gnc:account-tree-get-transactions root)
   (let ((accounts (gnc-account-get-descendants-sorted root)))
-    (if (null? accounts)
-        '()
-        (let ((query (qof-query-create-for-splits))
-              (xtns #f))
-
-          (qof-query-set-book query (gnc-account-get-book root))
-
-          ;; we want to find all transactions with every split inside the
-          ;; account group.
-          (xaccQueryAddAccountMatch query accounts
-                                    QOF-GUID-MATCH-ANY QOF-QUERY-AND)
-
-          (set! xtns (xaccQueryGetTransactions query QUERY-TXN-MATCH-ALL))
-
-          ;; lose the query
-          (qof-query-destroy query)
-          xtns))))
-
+    (let ((q (qof-query-create-for-splits)))
+      (qof-query-set-book q (gnc-account-get-book root))
+      (xaccQueryAddAccountMatch q accounts QOF-GUID-MATCH-ANY QOF-QUERY-AND)
+      (let ((xtns (xaccQueryGetTransactions q QUERY-TXN-MATCH-ALL)))
+        (qof-query-destroy q)
+        xtns))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  gnc:account-tree-find-duplicates
@@ -74,170 +62,141 @@
   ;; This procedure does all the work. We'll define it, then call it safely.
   (define (private-find)
 
-    ;; Given a list of accounts, this predicate returns true if any
-    ;; of those accounts are involved in a transaction.
-    (define (has-any-xtns? acctlist)
-      (if (null? acctlist)
-          #f
-          (let ((splits (xaccAccountGetSplitList (car acctlist))))
-            (if (null? splits)
-                (has-any-xtns? (cdr acctlist))
-                #t))))
-
-
     (let ((old-accounts (gnc-account-get-descendants-sorted old-root)))
-      (if (has-any-xtns? old-accounts)
-          ;; Get all the transactions in the new tree, then iterate over them
-          ;; trying to find matches in the old tree.  If there are matches,
-          ;; push the matches onto a list.
-          (let* ((new-xtns (gnc:account-tree-get-transactions new-root))
-                 (work-to-do (length new-xtns))
-                 (work-done 0)
-                 (matches '()))
-
-            ;; This procedure handles progress reporting, pause, and cancel.
-            (define (update-progress)
-              (set! work-done (+ 1 work-done))
-              (if (and progress-dialog
-                       (zero? (remainder work-done 8)))
-                  (begin
-                    (gnc-progress-dialog-set-value progress-dialog
-                                                   (/ work-done work-to-do))
-                    (qif-import:check-pause progress-dialog)
-                    (if qif-import:canceled
-                        (throw 'cancel)))))
-
 
-            (if progress-dialog
-                (gnc-progress-dialog-set-sub progress-dialog
+      (cond
+       ((any pair? (map xaccAccountGetSplitList old-accounts))
+        ;; Get all the transactions in the new tree, thisthen iterate
+        ;; over them trying to find matches in the old tree.  If
+        ;; there are matches, push the matches onto a list.
+        (let* ((new-xtns (gnc:account-tree-get-transactions new-root))
+               (work-to-do (length new-xtns))
+               (work-done 0)
+               (matches '()))
+
+          ;; This procedure handles progress reporting, pause, and cancel.
+          (define (update-progress)
+            (set! work-done (+ 1 work-done))
+            (when (and progress-dialog (zero? (modulo work-done 8)))
+              (gnc-progress-dialog-set-value progress-dialog
+                                             (/ work-done work-to-do))
+              (qif-import:check-pause progress-dialog)
+              (if qif-import:canceled (throw 'cancel))))
+
+          (when progress-dialog
+            (gnc-progress-dialog-set-sub progress-dialog
                                          (_ "Finding duplicate transactions")))
 
-            ;; For each transaction in the new account tree, build a query
-            ;; that matches possibly duplicate transactions in the old tree.
-            (for-each
-              (lambda (xtn)
-                (let ((query (qof-query-create-for-splits))
-                      (num-splits 0))
-                  (qof-query-set-book query (gnc-account-get-book old-root))
-
-                  ;; First, we only want to find only transactions
-                  ;; from accounts in the old tree.
-                  (xaccQueryAddAccountMatch query
-                                            old-accounts
-                                            QOF-GUID-MATCH-ANY QOF-QUERY-AND)
-
-                  ;; The date should be close to the same.. +/- a week.
-                  (let ((date (xaccTransGetDate xtn)))
-                    (xaccQueryAddDateMatchTT query
-                                             #t (decdate date WeekDelta)
-                                             #t (incdate date WeekDelta)
-                                             QOF-QUERY-AND))
-
-                  ;; For each split in the new transaction, add a
-                  ;; term that can match on its properties.
-                  (let ((q-splits (qof-query-create-for-splits)))
-                    (for-each
-                      (lambda (split)
-                        (set! num-splits (+ num-splits 1))
-                        (let ((sq (qof-query-create-for-splits)))
-                          (qof-query-set-book sq (gnc-account-get-book old-root))
-
-                          ;; Require a match on the account name. If the name
-                          ;; doesn't exist in the old tree (indicating a new
-                          ;; account), the match will be NULL and the query
-                          ;; won't find anything.  Optimize this later.
-                          (xaccQueryAddSingleAccountMatch
-                            sq
-                            (gnc-account-lookup-by-full-name old-root
-                              (gnc-account-get-full-name
-                                (xaccSplitGetAccount split)))
-                            QOF-QUERY-AND)
-
-                          ;; Require the value of the split in the new tree
-                          ;; to match the the value of the split in the old
-                          ;; tree.  We should really check for fuzziness.
-                          (xaccQueryAddValueMatch sq
-                                                  (xaccSplitGetValue split)
-                                                  QOF-NUMERIC-MATCH-ANY
-                                                  QOF-COMPARE-EQUAL
-                                                  QOF-QUERY-AND)
-
-                          ;; Now merge into the split query.  Reminder: q-splits
-                          ;; must be merged with an OR. Otherwise, nothing will
-                          ;; match. (For example, something can be equal to 4 or
-                          ;; to -4, but not both.)
-                          (let ((q-new (qof-query-merge q-splits
-                                                        sq
-                                                        QOF-QUERY-OR)))
-                            (qof-query-destroy q-splits)
-                            (qof-query-destroy sq)
-                            (set! q-splits q-new))))
-                      (xaccTransGetSplitList xtn))
-
-                    ;; Now q-splits will find every split that is the same as
-                    ;; any one split of the new-root transaction.  Merge it in.
-                    (let ((q-new (qof-query-merge query
-                                                  q-splits
-                                                  QOF-QUERY-AND)))
-                      (qof-query-destroy query)
-                      (qof-query-destroy q-splits)
-                      (set! query q-new)))
-
-                  ;; Now that we have built a query that finds matching splits
-                  ;; in the old tree, run it and build a list of transactions
-                  ;; from the results.
-                  ;;
-                  ;; If the transaction from the new tree has more than two
-                  ;; splits, then we'll assume that it fully reflects what
-                  ;; occurred, and only consider transactions in the old tree
-                  ;; that match with every single split.
-                  ;;
-                  ;; All other new transactions could be incomplete, so we'll
-                  ;; consider transactions from the old tree to be possible
-                  ;; duplicates even if only one split matches.
-                  ;;
-                  ;; For more information, see bug 481528.
-                  (let ((old-xtns (xaccQueryGetTransactions
-                                    query
-                                    (if (> num-splits 2)
-                                        QUERY-TXN-MATCH-ALL
-                                        QUERY-TXN-MATCH-ANY))))
-
-                    ;; Turn the resulting list of possibly duplicated
-                    ;; transactions into an association list.
-                    (set! old-xtns (map
-                                     (lambda (elt)
-                                       (cons elt #f)) old-xtns))
-
-                    ;; If anything matched the query, add it to our "matches"
-                    ;; association list, keyed by the new-root transaction.
-                    (if (not (null? old-xtns))
-                        (set! matches (cons (cons xtn old-xtns) matches))))
-
-                  (qof-query-destroy query))
-                (update-progress))
-              new-xtns)
-
-            ;; Finished.
-            (if progress-dialog
-                (gnc-progress-dialog-set-value progress-dialog 1))
-
-            ;; Return the matches.
-            matches)
-
-          ;; Since there are either no accounts or no transactions in the old
-          ;; tree, duplicate checking is unnecessary.
-          (begin
-            ;; Finished.
-            (if progress-dialog
-                (gnc-progress-dialog-set-value progress-dialog 1))
-
-            ;; Return an empty list.
-            '()))))
+          ;; For each transaction in the new account tree, build a query
+          ;; that matches possibly duplicate transactions in the old tree.
+          (for-each
+           (lambda (xtn)
+             (let ((query (qof-query-create-for-splits))
+                   (num-splits 0))
+               (qof-query-set-book query (gnc-account-get-book old-root))
+
+               ;; First, we only want to find only transactions
+               ;; from accounts in the old tree.
+               (xaccQueryAddAccountMatch
+                query old-accounts QOF-GUID-MATCH-ANY QOF-QUERY-AND)
+
+               ;; The date should be close to the same.. +/- a week.
+               (let ((date (xaccTransGetDate xtn)))
+                 (xaccQueryAddDateMatchTT
+                  query #t (decdate date WeekDelta)
+                  #t (incdate date WeekDelta) QOF-QUERY-AND))
+
+               ;; For each split in the new transaction, add a
+               ;; term that can match on its properties.
+               (let ((q-splits (qof-query-create-for-splits)))
+                 (for-each
+                  (lambda (split)
+                    (set! num-splits (+ num-splits 1))
+                    (let ((sq (qof-query-create-for-splits)))
+                      (qof-query-set-book sq (gnc-account-get-book old-root))
+
+                      ;; Require a match on the account name. If the name
+                      ;; doesn't exist in the old tree (indicating a new
+                      ;; account), the match will be NULL and the query
+                      ;; won't find anything.  Optimize this later.
+                      (xaccQueryAddSingleAccountMatch
+                       sq (gnc-account-lookup-by-full-name
+                           old-root (gnc-account-get-full-name
+                                     (xaccSplitGetAccount split)))
+                       QOF-QUERY-AND)
+
+                      ;; Require the value of the split in the new tree
+                      ;; to match the the value of the split in the old
+                      ;; tree.  We should really check for fuzziness.
+                      (xaccQueryAddValueMatch
+                       sq (xaccSplitGetValue split) QOF-NUMERIC-MATCH-ANY
+                       QOF-COMPARE-EQUAL QOF-QUERY-AND)
+
+                      ;; Now merge into the split query.  Reminder: q-splits
+                      ;; must be merged with an OR. Otherwise, nothing will
+                      ;; match. (For example, something can be equal to 4 or
+                      ;; to -4, but not both.)
+                      (let ((q-new (qof-query-merge q-splits sq QOF-QUERY-OR)))
+                        (qof-query-destroy q-splits)
+                        (qof-query-destroy sq)
+                        (set! q-splits q-new))))
+                  (xaccTransGetSplitList xtn))
+
+                 ;; Now q-splits will find every split that is the same as
+                 ;; any one split of the new-root transaction.  Merge it in.
+                 (let ((q-new (qof-query-merge query q-splits QOF-QUERY-AND)))
+                   (qof-query-destroy query)
+                   (qof-query-destroy q-splits)
+                   (set! query q-new)))
+
+               ;; Now that we have built a query that finds matching splits
+               ;; in the old tree, run it and build a list of transactions
+               ;; from the results.
+               ;;
+               ;; If the transaction from the new tree has more than two
+               ;; splits, then we'll assume that it fully reflects what
+               ;; occurred, and only consider transactions in the old tree
+               ;; that match with every single split.
+               ;;
+               ;; All other new transactions could be incomplete, so we'll
+               ;; consider transactions from the old tree to be possible
+               ;; duplicates even if only one split matches.
+               ;;
+               ;; For more information, see bug 481528.
+
+               (let ((old-xtns (map (lambda (elt) (cons elt #f))
+                                    (xaccQueryGetTransactions
+                                     query (if (> num-splits 2)
+                                               QUERY-TXN-MATCH-ALL
+                                               QUERY-TXN-MATCH-ANY)))))
+                 (display "\n*** gnc:account-tree-find-duplicates\n")
+                 (for-each pk old-xtns)
+
+                 ;; If anything matched the query, add it to our "matches"
+                 ;; association list, keyed by the new-root transaction.
+                 (if (not (null? old-xtns))
+                     (set! matches (cons (cons xtn old-xtns) matches))))
+
+               (qof-query-destroy query))
+             (update-progress))
+           new-xtns)
+
+          ;; Finished.
+          (when progress-dialog
+            (gnc-progress-dialog-set-value progress-dialog 1))
+
+          ;; Return the matches.
+          matches))
+
+       ;; Since there are either no accounts or no transactions in the old
+       ;; tree, duplicate checking is unnecessary.
+       (else
+        (when progress-dialog (gnc-progress-dialog-set-value progress-dialog 1))
+        '()))))
 
   ;; Safely do the work and return the result.
   (gnc:backtrace-if-exception
-    (lambda () (catch 'cancel private-find (lambda (key . args) #t)))))
+   (lambda () (catch 'cancel private-find (lambda (key . args) #t)))))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -252,20 +211,13 @@
 
 (define (gnc:prune-matching-transactions match-list)
   (for-each
-   (lambda (match)
-     (let ((new-xtn (car match))
-           (matches (cdr match))
-           (do-delete #f))
-       (for-each
-        (lambda (old)
-          (if (cdr old)
-              (set! do-delete #t)))
-        matches)
-       (if do-delete
-           (begin
-             (xaccTransBeginEdit new-xtn)
-             (xaccTransDestroy new-xtn)
-             (xaccTransCommitEdit new-xtn)))))
+   (lambda (txn-match)
+     (let ((new-xtn (car txn-match))
+           (matches (cdr txn-match)))
+       (when (any cdr matches)
+         (xaccTransBeginEdit new-xtn)
+         (xaccTransDestroy new-xtn)
+         (xaccTransCommitEdit new-xtn))))
    match-list))
 
 

commit a146d2cd5876a295d779b7a14c7627b0b1fbf946
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Fri Aug 2 18:58:02 2019 +0800

    [qif/qif-parse] compact functions

diff --git a/gnucash/import-export/qif-imp/qif-parse.scm b/gnucash/import-export/qif-imp/qif-parse.scm
index 3c12c9d7e..3c9194ee5 100644
--- a/gnucash/import-export/qif-imp/qif-parse.scm
+++ b/gnucash/import-export/qif-imp/qif-parse.scm
@@ -26,28 +26,6 @@
 (use-modules (gnucash import-export string))
 (use-modules (srfi srfi-13))
 
-(define qif-category-compiled-rexp
-  (make-regexp "^ *(\\[)?([^]/|]*)(]?)(/?)([^|]*)(\\|(\\[)?([^]/]*)(]?)(/?)(.*))? *$"))
-
-(define qif-date-compiled-rexp
-  (make-regexp "^ *([0-9]+) *[-/.'] *([0-9]+) *[-/.'] *([0-9]+).*$|^ *([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]).*$"))
-
-(define qif-date-mdy-compiled-rexp
-  (make-regexp "([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])"))
-
-(define qif-date-ymd-compiled-rexp
-  (make-regexp "([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])"))
-
-(define decimal-radix-regexp
-  (make-regexp
-   "^ *[$]?[+-]?[$]?[0-9]+[+-]?$|^ *[$]?[+-]?[$]?[0-9]?[0-9]?[0-9]?([,'][0-9][0-9][0-9])*(\\.[0-9]*)?[+-]? *$|^ *[$]?[+-]?[$]?[0-9]+\\.[0-9]*[+-]? *$"))
-
-(define comma-radix-regexp
-  (make-regexp
-   "^ *[$]?[+-]?[$]?[0-9]+[+-]?$|^ *[$]?[+-]?[$]?[0-9]?[0-9]?[0-9]?([\\.'][0-9][0-9][0-9])*(,[0-9]*)?[+-]? *$|^ *[$]?[+-]?[$]?[0-9]+,[0-9]*[+-]? *$"))
-
-(define integer-regexp (make-regexp "^[$]?[+-]?[$]?[0-9]+[+-]? *$"))
-
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  qif-split:parse-category
 ;;  this one just gets nastier and nastier.
@@ -61,37 +39,42 @@
 ;;  gosh, I love regular expressions.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
+(define qif-category-compiled-rexp
+  (make-regexp "^ *(\\[)?([^]/|]*)(]?)(/?)([^|]*)(\\|(\\[)?([^]/]*)(]?)(/?)(.*))? *$"))
 (define (qif-split:parse-category self value)
-  (let ((match (regexp-exec qif-category-compiled-rexp value)))
-    (if match
-        (let ((rv
-               (list (match:substring match 2)
-                     (if (and (match:substring match 1)
-                              (match:substring match 3))
-                         #t #f)
-                     (if (match:substring match 4)
-                         (match:substring match 5)
-                         #f)
-                     ;; miscx category name
-                     (if (match:substring match 6)
-                         (match:substring match 8)
-                         #f)
-                     ;; is it an account?
-                     (if (and (match:substring match 7)
-                              (match:substring match 9))
-                         #t #f)
-                     (if (match:substring match 10)
-                         (match:substring match 11)
-                         #f))))
-          rv)
-        (begin
-          ;; Parsing failed. Bug detected!
-          (gnc:warn "qif-split:parse-category: can't parse [" value "].")
-          (throw 'bug
-                 "qif-split:parse-category"
-                 "Can't parse account or category ~A."
-                 (list value)
-                 #f)))))
+  ;; example category regex matches (excluding initial 'L'):
+  ;; field1
+  ;; field1/field2
+  ;; field1/|field3
+  ;; field1/|field3/field4
+
+  ;; where field1 is a category or [account]
+  ;;   and field2 is a class
+  ;;   and field3 is a miscx-category or [miscx-account]
+  ;;   and field4 is a miscx-class
+  (cond
+   ((regexp-exec qif-category-compiled-rexp value) =>
+    (lambda (rmatch)
+      (list (match:substring rmatch 2)
+            (and (match:substring rmatch 1)
+                 (match:substring rmatch 3)
+                 #t)
+            (and (match:substring rmatch 4)
+                 (match:substring rmatch 5))
+            ;; miscx category name
+            (and (match:substring rmatch 6)
+                 (match:substring rmatch 8))
+            ;; is it an account?
+            (and (match:substring rmatch 7)
+                 (match:substring rmatch 9)
+                 #t)
+            (and (match:substring rmatch 10)
+                 (match:substring rmatch 11)))))
+   (else
+    ;; Parsing failed. Bug detected!
+    (gnc:warn "qif-split:parse-category: can't parse [" value "].")
+    (throw 'bug "qif-split:parse-category""Can't parse account or category ~A."
+           (list value) #f))))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -102,59 +85,40 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define (qif-parse:fix-year year-string y2k-threshold)
-  (let ((fixed-string #f)
-        (post-read-value #f)
-        (y2k-fixed-value #f))
-
-    ;; quicken prints 2000 as "' 0" for at least some versions.
-    ;; thanks dave p for reporting this.
-    (if (eq? (string-ref year-string 0) #\')
-        (begin
-          (gnc:warn "qif-file:fix-year: found weird QIF Y2K year ["
-                    year-string "].")
-          (set! fixed-string
-                (substring year-string 2 (string-length year-string))))
-        (set! fixed-string year-string))
-
-    ;; now the string should just have a number in it plus some
-    ;; optional trailing space.
-    (set! post-read-value
-          (with-input-from-string fixed-string
-            (lambda () (read))))
+  (let* ((fixed-string
+          (cond
+           ((char=? (string-ref year-string 0) #\')
+            (gnc:warn "qif-file:fix-year: weird QIF year [" year-string "].")
+            (substring year-string 2 (string-length year-string)))
+           (else year-string)))
+         (post-read-value (with-input-from-string fixed-string read)))
 
     (cond
      ;; 2-digit numbers less than the window size are interpreted to
      ;; be post-2000.
-     ((and (integer? post-read-value)
-           (< post-read-value y2k-threshold))
-      (set! y2k-fixed-value (+ 2000 post-read-value)))
+     ((and (integer? post-read-value) (< post-read-value y2k-threshold))
+      (+ 2000 post-read-value))
 
-     ;; there's a common bug in printing post-2000 dates that
-     ;; prints 2000 as 19100 etc.
-     ((and (integer? post-read-value)
-           (> post-read-value 19000))
-      (set! y2k-fixed-value (+ 1900 (- post-read-value 19000))))
+     ;; there's a common bug in printing post-2000 dates that prints
+     ;; 2000 as 19100 etc.
+     ((and (integer? post-read-value) (> post-read-value 19000))
+      (+ 1900 (- post-read-value 19000)))
 
      ;; normal dates represented in unix years (i.e. year-1900, so
      ;; 2000 => 100.)  We also want to allow full year specifications,
      ;; (i.e. 1999, 2001, etc) and there's a point at which you can't
      ;; determine which is which.  this should eventually be another
      ;; field in the qif-file struct but not yet.
-          ((and (integer? post-read-value)
-           (< post-read-value 1902))
-      (set! y2k-fixed-value (+ 1900 post-read-value)))
+     ((and (integer? post-read-value) (< post-read-value 1902))
+      (+ 1900 post-read-value))
 
      ;; this is a normal, 4-digit year spec (1999, 2000, etc).
-     ((integer? post-read-value)
-      (set! y2k-fixed-value post-read-value))
+     ((integer? post-read-value) post-read-value)
 
      ;; No idea what the string represents.  Maybe a new bug in Quicken!
-     (#t
-      (gnc:warn "qif-file:fix-year: ay caramba! What is this? ["
-                year-string "].")))
-
-    y2k-fixed-value))
-
+     (else
+      (gnc:warn "qif-file:fix-year: ay! What is this? [" year-string "].")
+      #f))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  parse-acct-type : set the type of the account, using gnucash
@@ -162,34 +126,22 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define (qif-parse:parse-acct-type read-value errorproc errortype)
-  (let ((mangled-string
-         (string-downcase! (string-trim-both read-value))))
-    (cond
-     ((string=? mangled-string "bank")
-      (list GNC-BANK-TYPE))
-     ((string=? mangled-string "port")
-      (list GNC-BANK-TYPE))
-     ((string=? mangled-string "cash")
-      (list GNC-CASH-TYPE))
-     ((string=? mangled-string "ccard")
-      (list GNC-CCARD-TYPE))
-     ((string=? mangled-string "invst") ;; these are brokerage accounts.
-      (list GNC-BANK-TYPE))
-     ((string=? mangled-string "401(k)/403(b)")
-      (list GNC-BANK-TYPE))
-     ((string=? mangled-string "oth a")
-      (list GNC-ASSET-TYPE GNC-BANK-TYPE GNC-CASH-TYPE))
-     ((string=? mangled-string "oth l")
-      (list GNC-LIABILITY-TYPE GNC-CCARD-TYPE))
-     ((string=? mangled-string "oth s") ;; German asset account
-      (list GNC-ASSET-TYPE GNC-BANK-TYPE GNC-CASH-TYPE))
-     ((string=? mangled-string "mutual")
-      (list GNC-BANK-TYPE))
-     (#t
-      (errorproc errortype
-                 (format #f (_ "Unrecognized account type '~s'. Defaulting to Bank.")
-                          read-value))
-      (list GNC-BANK-TYPE)))))
+  (define string-map-alist
+    (list (list "bank" GNC-BANK-TYPE)
+          (list "port" GNC-BANK-TYPE)
+          (list "cash" GNC-CASH-TYPE)
+          (list "ccard" GNC-CCARD-TYPE)
+          (list "invst" GNC-BANK-TYPE)
+          (list "401(k)/403(b)" GNC-BANK-TYPE)
+          (list "oth a" GNC-ASSET-TYPE GNC-BANK-TYPE GNC-CASH-TYPE)
+          (list "oth l" GNC-LIABILITY-TYPE GNC-CCARD-TYPE)
+          (list "oth s" GNC-ASSET-TYPE GNC-BANK-TYPE GNC-CASH-TYPE)
+          (list "mutual" GNC-BANK-TYPE)))
+  (or (assoc-ref string-map-alist (string-downcase! (string-trim-both read-value)))
+      (let ((msg (format #f (_ "Unrecognized account type '~s'. Defaulting to Bank.")
+                         read-value)))
+        (errorproc errortype msg)
+        (list GNC-BANK-TYPE))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  parse-bang-field : the bang fields switch the parse context
@@ -198,104 +150,59 @@
 
 (define (qif-parse:parse-bang-field read-value)
   (let ((bang-field (string-downcase! (string-trim read-value))))
-;; The QIF files output by the WWW site of Credit Lyonnais
-;; begin by:   !type bank
-;; instead of: !Type:bank
+    ;; The QIF files output by the WWW site of Credit Lyonnais
+    ;; begin by:   !type bank
+    ;; instead of: !Type:bank
     (if (>= (string-length bang-field) 5)
         (if (string=? (substring bang-field 0 5) "type ")
             (string-set! bang-field 4 #\:)))
-
     (string->symbol bang-field)))
 
-
 (define (qif-parse:parse-action-field read-value errorproc errortype)
-  (if read-value
-      (begin
-        (case (string->symbol (string-downcase (string-trim-both read-value)))
-          ;; buy
-          ((buy cvrshrt kauf)
-           'buy)
-          ((buyx cvrshrtx kaufx)
-           'buyx)
-          ((cglong kapgew) ;; Kapitalgewinnsteuer
-           'cglong)
-          ((cglongx kapgewx)
-           'cglongx)
-          ((cgmid) ;; Kapitalgewinnsteuer
-           'cgmid)
-          ((cgmidx)
-           'cgmidx)
-          ((cgshort k.gewsp)
-           'cgshort)
-          ((cgshortx k.gewspx)
-           'cgshortx)
-          ((div)   ;; dividende
-           'div)
-          ((divx)
-           'divx)
-;          ((exercise)
-;           'exercise)
-;          ((exercisx)
-;           'exercisx)
-;          ((expire)
-;           'expire)
-;          ((grant)
-;           'grant)
-          ((int intinc) ;; zinsen
-           'intinc)
-          ((intx intincx)
-           'intincx)
-          ((margint)
-           'margint)
-          ((margintx)
-           'margintx)
-          ((miscexp)
-           'miscexp)
-          ((miscexpx)
-           'miscexpx)
-          ((miscinc cash)
-           'miscinc)
-          ((miscincx)
-           'miscincx)
-          ((reinvdiv)
-           'reinvdiv)
-          ((reinvint reinvzin)
-           'reinvint)
-          ((reinvlg reinvkur)
-           'reinvlg)
-          ((reinvmd)
-           'reinvmd)
-          ((reinvsg reinvksp)
-           'reinvsg)
-          ((reinvsh)
-           'reinvsh)
-          ((reminder erinnerg)
-           'reminder)
-          ((rtrncap)
-           'rtrncap)
-          ((rtrncapx)
-           'rtrncapx)
-          ((sell shtsell verkauf)  ;; verkaufen
-           'sell)
-          ((sellx shtsellx verkaufx)
-           'sellx)
-          ((shrsin aktzu)
-           'shrsin)
-          ((shrsout aktab)
-           'shrsout)
-          ((stksplit aktsplit)
-           'stksplit)
-          ((xin contribx)
-           'xin)
-          ((xout withdrwx)
-           'xout)
-;          ((vest)
-;           'vest)
-          (else
-           (errorproc errortype
-                      (format #f (_ "Unrecognized action '~a'.") read-value))
-           #f)))
-      #f))
+  (define action-map
+    '((buy cvrshrt kauf)
+      (buyx cvrshrtx kaufx)
+      (cglong cglong kapgew)
+      (cglongx cglongx kapgewx)
+      (cgmid cgmid)
+      (cgmidx cgmidx)
+      (cgshort cgshort k.gewsp)
+      (cgshortx cgshortx k.gewspx)
+      (div div)
+      (divx divx)
+      ;; (exercise exercise)
+      ;; (exercisx exercisx)
+      ;; (expire expire)
+      ;; (grant grant)
+      (intinc int intinc)
+      (intincx intx intincx)
+      (margint margint)
+      (margintx margintx)
+      (miscexp miscexp)
+      (miscexpx miscexpx)
+      (miscinc miscinc cash)
+      (miscincx miscincx)
+      (reinvdiv reinvdiv)
+      (reinvint reinvint reinvzin)
+      (reinvlg reinvlg reinvkur)
+      (reinvmd reinvmd)
+      (reinvsg reinvsg reinvksp)
+      (reinvsh reinvsh)
+      (reminder reminder erinnerg)
+      (rtrncap rtrncap)
+      (rtrncapx rtrncapx)
+      (sell sell shtsell verkauf)
+      (sellx sellx shtsellx verkaufx)
+      (shrsin shrsin aktzu)
+      (shrsout shrsout aktab)
+      (stksplit stksplit aktsplit)
+      (xin xin contribx)
+      (xout xout withdrwx)))
+  (and read-value
+       (let ((sym (string->symbol (string-downcase (string-trim-both read-value)))))
+         (or (any (lambda (lst) (and (memq sym lst) (car lst))) action-map)
+             (let ((msg (format #f (_ "Unrecognized action '~a'.") read-value)))
+               (errorproc errortype msg))))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  parse-cleared-field : In a "C" (cleared status) QIF line,
@@ -304,24 +211,18 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define (qif-parse:parse-cleared-field read-value errorproc errortype)
-  (if (and (string? read-value)
-           (not (string-null? read-value)))
-      (let ((secondchar (string-ref read-value 0)))
-        (case secondchar
-          ;; Reconciled is the most likely, especially for large imports,
-          ;; so check that first. Also allow for lowercase.
-          ((#\X #\x #\R #\r)
-           'reconciled)
-          ((#\* #\C #\c)
-           'cleared)
-          ((#\? #\!)
-           'budgeted)
-          (else
-            (errorproc errortype
-                       (format #f (_ "Unrecognized status '~a'. Defaulting to uncleared.")
-                                read-value))
-            #f)))
-      #f))
+  (define maplist
+    '((reconciled #\X #\x #\R #\r)
+      (cleared #\* #\C #\c)
+      (budgeted #\? #\!)))
+  (and
+   (string? read-value)
+   (not (string-null? read-value))
+   (let* ((secondchar (string-ref read-value 0)))
+     (or (any (lambda (m) (and (memq secondchar (cdr m)) (car m))) maplist)
+         (let ((msg (format #f (_ "Unrecognized status '~a'. Defaulting to uncleared.")
+                            read-value)))
+           (errorproc errortype msg))))))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -331,115 +232,69 @@
 ;;  that this date string could actually be.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (define (parse-check-date-format match possible-formats)
-  (let ((date-parts (list (match:substring match 1)
-                          (match:substring match 2)
-                          (match:substring match 3)))
-        (numeric-date-parts '())
-        (retval '()))
-
-    ;;(define (print-list l)
-    ;;  (for-each (lambda (x) (display x) (display " ")) l))
-
-    ;;(for-each (lambda (x) (if (list? x) (print-list x) (display x)))
-    ;;      (list "parsing: " date-parts " in " possible-formats "\n"))
-
-    ;; get the strings into numbers (but keep the strings around)
-    (set! numeric-date-parts
-          (map (lambda (elt)
-                 (with-input-from-string elt
-                   (lambda () (read))))
-               date-parts))
-
-    (let ((possibilities possible-formats)
-          (n1 (car numeric-date-parts))
-          (n2 (cadr numeric-date-parts))
-          (n3 (caddr numeric-date-parts))
-          (s1 (car date-parts))
-          (s3 (caddr date-parts)))
-
-      ;; filter the possibilities to eliminate (hopefully)
-      ;; all but one
-      (if (or (not (number? n1)) (> n1 12))
-          (set! possibilities (delq 'm-d-y possibilities)))
-      (if (or (not (number? n1)) (> n1 31))
-          (set! possibilities (delq 'd-m-y possibilities)))
-      (if (or (not (number? n1)) (< n1 1))
-          (set! possibilities (delq 'd-m-y possibilities)))
-      (if (or (not (number? n1)) (< n1 1))
-          (set! possibilities (delq 'm-d-y possibilities)))
-
-      (if (or (not (number? n2)) (> n2 12))
-          (begin
-            (set! possibilities (delq 'd-m-y possibilities))
-            (set! possibilities (delq 'y-m-d possibilities))))
-
-      (if (or (not (number? n2)) (> n2 31))
-          (begin
-            (set! possibilities (delq 'm-d-y possibilities))
-            (set! possibilities (delq 'y-d-m possibilities))))
-
-      (if (or (not (number? n3)) (> n3 12))
-          (set! possibilities (delq 'y-d-m possibilities)))
-      (if (or (not (number? n3)) (> n3 31))
-          (set! possibilities (delq 'y-m-d possibilities)))
-
-      (if (or (not (number? n3)) (< n3 1))
-          (set! possibilities (delq 'y-m-d possibilities)))
-      (if (or (not (number? n3)) (< n3 1))
-          (set! possibilities (delq 'y-d-m possibilities)))
-
-      ;; If we've got a 4-character year, make sure the date
-      ;; is after 1930.  Don't check the high value (perhaps
-      ;; we should?).
-      (if (= (string-length s1) 4)
-          (if (or (not (number? n1)) (< n1 1930))
-              (begin
-                (set! possibilities (delq 'y-m-d possibilities))
-                (set! possibilities (delq 'y-d-m possibilities)))))
-      (if (= (string-length s3) 4)
-          (if (or (not (number? n3)) (< n3 1930))
-              (begin
-                (set! possibilities (delq 'm-d-y possibilities))
-                (set! possibilities (delq 'd-m-y possibilities)))))
-
-      (set! retval possibilities))
-    retval))
+  (define (date? d m y ys)
+    (and (number? d) (<= 1 d 31)
+         (number? m) (<= 1 m 12)
+         (= 4 (string-length ys))
+         (number? y) (> y 1930)))
+  (let* ((date-parts (list (match:substring match 1)
+                           (match:substring match 2)
+                           (match:substring match 3)))
+         (numeric-date-parts (map (lambda (elt) (with-input-from-string elt read))
+                                  date-parts))
+         (n1 (car numeric-date-parts))
+         (n2 (cadr numeric-date-parts))
+         (n3 (caddr numeric-date-parts))
+         (s1 (car date-parts))
+         (s3 (caddr date-parts))
+         (format-alist (list (list 'd-m-y n1 n2 n3 s3)
+                             (list 'm-d-y n2 n1 n3 s3)
+                             (list 'y-m-d n3 n2 n1 s1)
+                             (list 'y-d-m n2 n3 n1 s1))))
+
+    (let lp ((possible-formats possible-formats)
+             (res '()))
+      (cond
+       ((null? possible-formats) (reverse res))
+       (else
+        (lp (cdr possible-formats)
+            (let ((args (assq (car possible-formats) format-alist)))
+              (if (apply date? (cdr args)) (cons (car args) res) res))))))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  qif-parse:check-date-format
 ;;  given a list of possible date formats, return a pruned list
 ;;  of possibilities.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(define qif-date-compiled-rexp
+  (make-regexp "^ *([0-9]+) *[-/.'] *([0-9]+) *[-/.'] *([0-9]+).*$|^ *([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]).*$"))
+
+(define qif-date-mdy-compiled-rexp
+  (make-regexp "([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])"))
+
+(define qif-date-ymd-compiled-rexp
+  (make-regexp "([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])"))
+
 (define (qif-parse:check-date-format date-string possible-formats)
-  (let ((retval '()))
-    (if (or (not (string? date-string))
-            (not (> (string-length date-string) 0)))
-        (set! retval #f)
-        (let ((match (regexp-exec qif-date-compiled-rexp date-string)))
-      (if match
-          (if (match:substring match 1)
-              (set! retval (parse-check-date-format match possible-formats))
-
-              ;; Uh oh -- this is a string XXXXXXXX; we don't know which
-              ;; way to test..  So test both YYYYxxxx and xxxxYYYY,
-              ;; and let the parser verify the year is valid.
-              (let* ((new-date-string (match:substring match 4))
-                     (date-ymd (regexp-exec qif-date-ymd-compiled-rexp
-                                            new-date-string))
-                     (date-mdy (regexp-exec qif-date-mdy-compiled-rexp
-                                               new-date-string))
-                     (res1 '())
-                     (res2 '()))
-                (if (or (memq 'y-d-m possible-formats)
-                        (memq 'y-m-d possible-formats))
-                    (set! res1 (parse-check-date-format date-ymd possible-formats)))
-                (if (or (memq 'd-m-y possible-formats)
-                        (memq 'm-d-y possible-formats))
-                    (set! res2 (parse-check-date-format date-mdy possible-formats)))
-
-                (set! retval (append res1 res2)))))))
-
-    retval))
+  (and (string? date-string)
+       (not (string-null? date-string))
+       (let ((rmatch (regexp-exec qif-date-compiled-rexp date-string)))
+         (if rmatch
+             (if (match:substring rmatch 1)
+                 (parse-check-date-format rmatch possible-formats)
+                 ;; Uh oh -- this is a string XXXXXXXX; we don't know which
+                 ;; way to test..  So test both YYYYxxxx and xxxxYYYY,
+                 ;; and let the parser verify the year is valid.
+                 (let* ((newstr (match:substring rmatch 4))
+                        (date-ymd (regexp-exec qif-date-ymd-compiled-rexp newstr))
+                        (date-mdy (regexp-exec qif-date-mdy-compiled-rexp newstr)))
+                   (append
+                    (if (or (memq 'y-d-m possible-formats)
+                            (memq 'y-m-d possible-formats))
+                        (parse-check-date-format date-ymd possible-formats))
+                    (if (or (memq 'd-m-y possible-formats)
+                            (memq 'm-d-y possible-formats))
+                        (parse-check-date-format date-mdy possible-formats)))))))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  qif-parse:parse-date/format
@@ -447,107 +302,71 @@
 ;;  date and return a list of day, month, year
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define (qif-parse:parse-date/format date-string format)
-  (let ((date-parts '())
-        (numeric-date-parts '())
-        (retval #f)
-
-        (match (regexp-exec qif-date-compiled-rexp date-string)))
-    (if match
-        (if (match:substring match 1)
-             (set! date-parts (list (match:substring match 1)
-                                    (match:substring match 2)
-                                    (match:substring match 3)))
-             ;; This is of the form XXXXXXXX; split the string based on
-             ;; whether the format is YYYYxxxx or xxxxYYYY
-             (let ((date-str (match:substring match 4)))
-               (case format
-                 ((d-m-y m-d-y)
-                  (let ((m (regexp-exec qif-date-mdy-compiled-rexp date-str)))
-                    (set! date-parts (list (match:substring m 1)
-                                           (match:substring m 2)
-                                           (match:substring m 3)))))
-                 ((y-m-d y-d-m)
-                  (let ((m (regexp-exec qif-date-ymd-compiled-rexp date-str)))
-                    (set! date-parts (list (match:substring m 1)
-                                           (match:substring m 2)
-                                           (match:substring m 3)))))
-                 ))))
-
-    ;; get the strings into numbers (but keep the strings around)
-    (set! numeric-date-parts
-          (map (lambda (elt)
-                 (with-input-from-string elt
-                   (lambda () (read))))
-               date-parts))
+(define (qif-parse:parse-date/format date-string dateformat)
+  (define (date? d m y)
+    (and (number? d) (<= 1 d 31)
+         (number? m) (<= 1 m 12)))
+  (let* ((rmatch (regexp-exec qif-date-compiled-rexp date-string))
+         (date-parts
+          (if rmatch
+              (if (match:substring rmatch 1)
+                  (list (match:substring rmatch 1)
+                        (match:substring rmatch 2)
+                        (match:substring rmatch 3))
+                  ;; This is of the form XXXXXXXX; split the string based on
+                  ;; whether the format is YYYYxxxx or xxxxYYYY
+                  (let ((date-str (match:substring rmatch 4)))
+                    (case dateformat
+                      ((d-m-y m-d-y)
+                       (let ((m (regexp-exec qif-date-mdy-compiled-rexp date-str)))
+                         (list (match:substring m 1)
+                               (match:substring m 2)
+                               (match:substring m 3))))
+                      ((y-m-d y-d-m)
+                       (let ((m (regexp-exec qif-date-ymd-compiled-rexp date-str)))
+                         (list (match:substring m 1)
+                               (match:substring m 2)
+                               (match:substring m 3)))))))
+              '()))
+         ;; get the strings into numbers (but keep the strings around)
+         (numeric-date-parts (map (lambda (elt) (with-input-from-string elt read))
+                                  date-parts)))
+
+    (define (refs->list dd mm yy)
+      (let ((d (list-ref numeric-date-parts dd))
+            (m (list-ref numeric-date-parts mm))
+            (y (qif-parse:fix-year (list-ref date-parts yy) 50)))
+        (cond
+         ((date? d m y) (list d m y))
+         (else (gnc:warn "qif-parse:parse-date/format: format is " dateformat
+                         " but date is [" date-string "].") #f))))
 
     ;; if the date parts list doesn't have 3 parts, we're in trouble
-    (if (not (eq? 3 (length date-parts)))
-        (gnc:warn "qif-parse:parse-date/format: can't interpret date ["
-                  date-string "]\nDate parts: " date-parts)
-        (case format
-          ((d-m-y)
-           (let ((d (car numeric-date-parts))
-                 (m (cadr numeric-date-parts))
-                 (y (qif-parse:fix-year (caddr date-parts) 50)))
-             (if (and (integer? d) (integer? m) (integer? y)
-                      (<= m 12) (<= d 31))
-                 (set! retval (list d m y))
-                 (gnc:warn "qif-parse:parse-date/format: "
-                           "format is d/m/y, but date is ["
-                           date-string "]."))))
-
-          ((m-d-y)
-           (let ((m (car numeric-date-parts))
-                 (d (cadr numeric-date-parts))
-                 (y (qif-parse:fix-year (caddr date-parts) 50)))
-             (if (and (integer? d) (integer? m) (integer? y)
-                      (<= m 12) (<= d 31))
-                 (set! retval (list d m y))
-                 (gnc:warn "qif-parse:parse-date/format: "
-                           "format is m/d/y, but date is ["
-                           date-string "]."))))
-
-          ((y-m-d)
-           (let ((y (qif-parse:fix-year (car date-parts) 50))
-                 (m (cadr numeric-date-parts))
-                 (d (caddr numeric-date-parts)))
-             (if (and (integer? d) (integer? m) (integer? y)
-                      (<= m 12) (<= d 31))
-                 (set! retval (list d m y))
-                 (gnc:warn "qif-parse:parse-date/format: "
-                           "format is y/m/d, but date is ["
-                           date-string "]."))))
-
-          ((y-d-m)
-           (let ((y (qif-parse:fix-year (car date-parts) 50))
-                 (d (cadr numeric-date-parts))
-                 (m (caddr numeric-date-parts)))
-             (if (and (integer? d) (integer? m) (integer? y)
-                      (<= m 12) (<= d 31))
-                 (set! retval (list d m y))
-                 (gnc:warn "qif-parse:parse-date/format: "
-                           "format is y/d/m, but date is ["
-                           date-string "]."))))))
-    retval))
-
+    (cond
+     ((not (= 3 (length date-parts)))
+      (gnc:warn "qif-parse:parse-date/format: can't interpret date ["
+                date-string "]\nDate parts: " date-parts) #f)
+     ((eq? dateformat 'd-m-y) (refs->list 0 1 2))
+     ((eq? dateformat 'm-d-y) (refs->list 1 0 2))
+     ((eq? dateformat 'y-m-d) (refs->list 2 1 0))
+     ((eq? dateformat 'y-d-m) (refs->list 2 0 1)))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  number format predicates
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define (value-is-decimal-radix? value)
-  (if (regexp-exec decimal-radix-regexp value)
-      #t #f))
 
-(define (value-is-comma-radix? value)
-  (if (regexp-exec comma-radix-regexp value)
-      #t #f))
+;; eg 1000.00 or 1,500.00 or 2'000.00
+(define decimal-radix-regexp
+  (make-regexp "^ *[$]?[+-]?[$]?[0-9]+[+-]?$|^ *[$]?[+-]?[$]?[0-9]?[0-9]?[0-9]?([,'][0-9][0-9][0-9])*(\\.[0-9]*)?[+-]? *$|^ *[$]?[+-]?[$]?[0-9]+\\.[0-9]*[+-]? *$"))
 
-(define (value-is-integer? value)
-  (if (regexp-exec integer-regexp value)
-      #t #f))
+;; eg 5.000,00 or 4'500,00
+(define comma-radix-regexp
+  (make-regexp "^ *[$]?[+-]?[$]?[0-9]+[+-]?$|^ *[$]?[+-]?[$]?[0-9]?[0-9]?[0-9]?([\\.'][0-9][0-9][0-9])*(,[0-9]*)?[+-]? *$|^ *[$]?[+-]?[$]?[0-9]+,[0-9]*[+-]? *$"))
 
+;; eg 456 or 123
+(define integer-regexp
+  (make-regexp "^[$]?[+-]?[$]?[0-9]+[+-]? *$"))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  qif-parse:check-number-format
@@ -556,15 +375,12 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define (qif-parse:check-number-format value-string possible-formats)
-  (let ((retval possible-formats))
-    (if (not (value-is-decimal-radix? value-string))
-        (set! retval (delq 'decimal retval)))
-    (if (not (value-is-comma-radix? value-string))
-        (set! retval (delq 'comma retval)))
-    (if (not (value-is-integer? value-string))
-        (set! retval (delq 'integer retval)))
-    retval))
-
+  (define numtypes-alist
+    (list (cons 'decimal decimal-radix-regexp)
+          (cons 'comma comma-radix-regexp)
+          (cons 'integer integer-regexp)))
+  (filter (lambda (fmt) (regexp-exec (assq-ref numtypes-alist fmt) value-string))
+          possible-formats))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  qif-parse:parse-number/format
@@ -573,69 +389,35 @@
 ;;  represent the number
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
+;; the following is a working refactored function
 (define (qif-parse:parse-number/format value-string format)
-  (let ((minus-index (string-index value-string #\-))
-        (filtered-string (gnc:string-delete-chars value-string "$'+-")))
-    (case format
-      ((decimal)
-       (let* ((read-string (gnc:string-delete-chars filtered-string ","))
-              (read-val (with-input-from-string read-string
-                                                (lambda () (read)))))
-         (if (number? read-val)
-             (double-to-gnc-numeric
-              (if minus-index (- 0.0 read-val) (+ 0.0 read-val))
-              GNC-DENOM-AUTO
-              (logior (GNC-DENOM-SIGFIGS
-                       (string-length (gnc:string-delete-chars read-string ".")))
-                      GNC-RND-ROUND))
-             (gnc-numeric-zero))))
-      ((comma)
-       (let* ((read-string (gnc:string-replace-char
-                              (gnc:string-delete-chars filtered-string ".")
-                              #\, #\.))
-              (read-val (with-input-from-string read-string
-                                                (lambda () (read)))))
-         (if (number? read-val)
-             (double-to-gnc-numeric
-              (if minus-index (- 0.0 read-val) (+ 0.0 read-val))
-              GNC-DENOM-AUTO
-              (logior (GNC-DENOM-SIGFIGS
-                       (string-length (gnc:string-delete-chars read-string ".")))
-                      GNC-RND-ROUND))
-             (gnc-numeric-zero))))
-      ((integer)
-       (let ((read-val (with-input-from-string filtered-string
-                                               (lambda () (read)))))
-         (if (number? read-val)
-             (double-to-gnc-numeric
-              (if minus-index (- 0.0 read-val) (+ 0.0 read-val))
-              1 GNC-RND-ROUND)
-             (gnc-numeric-zero)))))))
-
+  (let* ((filtered-string (gnc:string-delete-chars value-string "$'+"))
+         (read-string (case format
+                        ((decimal) (gnc:string-delete-chars filtered-string ","))
+                        ((comma) (gnc:string-replace-char
+                                  (gnc:string-delete-chars filtered-string ".")
+                                  #\, #\.))
+                        ((integer) filtered-string))))
+    (or (string->number (string-append "#e" read-string)) 0)))
+
+;; input: list of numstrings eg "10.50" "20.54"
+;; input: formats to test '(decimal comma integer)
+;; output: list of formats applicable eg '(decimal)
 (define (qif-parse:check-number-formats amt-strings formats)
-  (let ((retval formats))
-    (for-each
-     (lambda (amt)
-       (if amt
-           (set! retval (qif-parse:check-number-format amt retval))))
-     amt-strings)
-    retval))
-
+  (let lp ((amt-strings amt-strings)
+           (formats formats))
+    (if (null? amt-strings)
+        formats
+        (lp (cdr amt-strings)
+            (qif-parse:check-number-format (car amt-strings) formats)))))
+
+;; list of number-strings and format -> list of numbers eg '("1,00"
+;; "2,50" "3,99") 'comma --> '(1 5/2 399/100) this function would
+;; formerly attempt to return #f if a list element couldn't be parsed;
+;; but in practice always returns a list, with unparsed numbers as 0.
 (define (qif-parse:parse-numbers/format amt-strings format)
-  (let* ((all-ok #t)
-         (tmp #f)
-         (parsed
-          (map
-           (lambda (amt)
-             (if amt
-                 (begin
-                   (set! tmp (qif-parse:parse-number/format amt format))
-                   (if (not tmp)
-                       (set! all-ok #f))
-                   tmp)
-                 (gnc-numeric-zero)))
-           amt-strings)))
-    (if all-ok parsed #f)))
+  (map (lambda (amt) (if amt (qif-parse:parse-number/format amt format) 0))
+       amt-strings))
 
 (define (qif-parse:print-date date-list)
   (let ((tm (gnc-localtime (current-time))))

commit a3150f383f3939ab6f698f20f5b905e260bcc189
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Fri Aug 2 18:58:09 2019 +0800

    [qif/test-qif-merge-groups] initial commit

diff --git a/gnucash/import-export/qif-imp/test/CMakeLists.txt b/gnucash/import-export/qif-imp/test/CMakeLists.txt
index 629eaa635..fea1e7ac6 100644
--- a/gnucash/import-export/qif-imp/test/CMakeLists.txt
+++ b/gnucash/import-export/qif-imp/test/CMakeLists.txt
@@ -6,6 +6,7 @@ set(QIF_IMP_TEST_LIBS)
 set(scm_qifimp_test_with_srfi64_SOURCES
   test-qif-imp.scm
   test-qif-parse.scm
+  test-qif-merge-groups.scm
   )
 
 
diff --git a/gnucash/import-export/qif-imp/test/test-qif-merge-groups.scm b/gnucash/import-export/qif-imp/test/test-qif-merge-groups.scm
new file mode 100644
index 000000000..9bbf8ad56
--- /dev/null
+++ b/gnucash/import-export/qif-imp/test/test-qif-merge-groups.scm
@@ -0,0 +1,113 @@
+(use-modules (gnucash gnc-module))
+(gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
+(use-modules (srfi srfi-64))
+(use-modules (gnucash engine test srfi64-extras))
+(use-modules (gnucash import-export qif-import))
+(use-modules (gnucash import-export string))
+(use-modules (gnucash engine test test-extras))
+(use-modules (gnucash report report-system))
+
+(define (run-test)
+  (test-runner-factory gnc:test-runner)
+  (test-begin "test-qif-merge-groups")
+  (test-gnc:account-tree-get-transactions)
+  (test-gnc:account-tree-find-duplicates)
+  (test-end "test-qif-merge-groups"))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; qif-merge-groups.scm
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (teardown)
+  (gnc-clear-current-session))
+
+(define (test-gnc:account-tree-get-transactions)
+  (define gnc:account-tree-get-transactions
+    (@@ (gnucash import-export qif-import) gnc:account-tree-get-transactions))
+
+  (test-group-with-cleanup "test-gnc:account-tree-get-transactions"
+    (create-test-data)
+
+    (test-equal "gnc:account-tree-get-transactions"
+      59
+      (length
+       (gnc:account-tree-get-transactions (gnc-get-current-root-account))))
+
+    (teardown)))
+
+(define (test-gnc:account-tree-find-duplicates)
+  (define gnc:account-tree-find-duplicates
+    (@@ (gnucash import-export qif-import) gnc:account-tree-find-duplicates))
+  (define new-structure
+    (list "Root" (list (cons 'type ACCT-TYPE-ASSET))
+          (list "Asset"
+                (list "Bank")
+                (list "Wallet")
+                (list "Income" (list (cons 'type ACCT-TYPE-INCOME)))
+                (list "Expenses" (list (cons 'type ACCT-TYPE-EXPENSE))))))
+
+  (test-group-with-cleanup "test-gnc:account-tree-find-duplicates"
+    (let* ((env (create-test-env))
+           (old-alist (create-test-data))
+           (old-root (assoc-ref old-alist "Root"))
+           (old-bank (assoc-ref old-alist "Bank"))
+           (old-expenses (assoc-ref old-alist "Expenses"))
+           (old-wallet (assoc-ref old-alist "Wallet"))
+           (new-alist (env-create-account-structure-alist env new-structure))
+           (new-root (assoc-ref new-alist "Root"))
+           (new-bank (assoc-ref new-alist "Bank"))
+           (new-expenses (assoc-ref new-alist "Expenses"))
+           (new-wallet (assoc-ref new-alist "Wallet")))
+
+      ;; the following are the qif-transactions:
+      (define new-txn1 (env-transfer env 01 01 1970 new-bank new-expenses 5))
+
+      ;; note the old-book txn is dated 14.02.1971; the new-book dated
+      ;; 20.02.1971 will match because it's less than 1wk away. note
+      ;; the old-book txn is a multisplit, but it will still match
+      ;; because the bank value is -100.
+      (define new-txn2 (env-transfer env 20 02 1971 new-bank new-expenses 100))
+      ;; old-book txn dated 13.02.1971 will also match above txn
+      (define old-txn2 (env-transfer env 13 02 1971 old-bank old-expenses 100))
+
+      ;; the following imported txn will not match an existing
+      ;; txn because the date difference from 14.02.1971 is > 1 week
+      (define new-txn3 (env-transfer env 22 02 1971 new-bank new-expenses 100))
+
+      (let ((matches (gnc:account-tree-find-duplicates old-root new-root #f)))
+        (test-equal "test-gnc:account-tree-find-duplicates - 2 txns matched"
+          2
+          (length matches))
+
+        (display "before pruning\n")
+        (test-equal "test-gnc:account-tree-find-duplicates - 1st txn matches 1"
+          1
+          (length (assoc-ref matches new-txn1)))
+
+        (test-equal "test-gnc:account-tree-find-duplicates - 2nd txn matches 2"
+          2
+          (length (assoc-ref matches new-txn2)))
+
+        (test-equal "test-gnc:account-tree-find-duplicates - 3nd txn matches none"
+          #f
+          (assoc-ref matches new-txn3))
+
+        (test-assert "mark the new-txn2, 1st match as duplicate"
+          (set-cdr! (car (assoc-ref matches new-txn2)) #t))
+
+        (test-assert "gnc:prune-matching-transactions completed"
+          (gnc:prune-matching-transactions matches)))
+
+      (let ((matches (gnc:account-tree-find-duplicates old-root new-root #f)))
+
+        (display "after pruning:\n")
+        (test-equal "test-gnc:account-tree-find-duplicates - 1st txn matches 1"
+          1
+          (length (assoc-ref matches new-txn1)))
+
+        (test-equal "test-gnc:account-tree-find-duplicates - 2nd txn destroyed"
+          #f
+          (assoc-ref matches new-txn2))))
+
+    (teardown)))
+

commit 277ba729d16e970adaa0efdcfadbcde79f523858
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sat Aug 3 16:21:48 2019 +0800

    [qif/test-qif-parse] increase coverage

diff --git a/gnucash/import-export/qif-imp/test/test-qif-parse.scm b/gnucash/import-export/qif-imp/test/test-qif-parse.scm
index 1fe5d5767..ea47029d9 100644
--- a/gnucash/import-export/qif-imp/test/test-qif-parse.scm
+++ b/gnucash/import-export/qif-imp/test/test-qif-parse.scm
@@ -275,6 +275,34 @@
 ;; unfinished
 (define (test-qif-split:parse-category)
   
-  (test-equal "qif-split:parse-category"
-    '("LGas" #f "" #f #f #f)
-    (qif-split:parse-category #f "LGas")))
+  (test-equal "qif-split:parse-category [Transfer]/Class"
+    '("Transfer" #t "Class" #f #f #f)
+    (qif-split:parse-category #f "[Transfer]/Class"))
+
+  (test-equal "qif-split:parse-category Category/Class"
+    '("Category" #f "Class" #f #f #f)
+    (qif-split:parse-category #f "Category/Class"))
+
+  (test-equal "qif-split:parse-category Category"
+    '("Category" #f "" #f #f #f)
+    (qif-split:parse-category #f "Category"))
+
+  (test-equal "qif-split:parse-category [Transfer]"
+    '("Transfer" #t "" #f #f #f)
+    (qif-split:parse-category #f "[Transfer]"))
+
+  (test-equal "qif-split:parse-category Category/|miscx-category"
+    '("Category" #f "" "miscx-category" #f "")
+    (qif-split:parse-category #f "Category/|miscx-category"))
+
+  (test-equal "qif-split:parse-category Category/|[miscx-account]"
+    '("Category" #f "" "miscx-account" #t "")
+    (qif-split:parse-category #f "Category/|[miscx-account]"))
+
+  (test-equal "qif-split:parse-category Category/|miscx-category/miscx-class"
+    '("Category" #f "" "miscx-category" #f "miscx-class")
+    (qif-split:parse-category #f "Category/|miscx-category/miscx-class"))
+
+  (test-equal "qif-split:parse-category Category/|[miscx-account]/miscx-class"
+    '("Category" #f "" "miscx-account" #t "miscx-class")
+    (qif-split:parse-category #f "Category/|[miscx-account]/miscx-class")))

commit 1873c2f70581f5b07a866142cbede1e62a416fab
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sat Aug 3 17:22:00 2019 +0800

    [qif/assistant-qif-import.c] gfec_apply requires a list of arguments
    
    * instead of argument, send a list of arguments.
    * the undo scm function would never run otherwise

diff --git a/gnucash/import-export/qif-imp/assistant-qif-import.c b/gnucash/import-export/qif-imp/assistant-qif-import.c
index edc448bcb..ca5591d73 100644
--- a/gnucash/import-export/qif-imp/assistant-qif-import.c
+++ b/gnucash/import-export/qif-imp/assistant-qif-import.c
@@ -1111,7 +1111,8 @@ gnc_ui_qif_import_convert_undo (QIFImportWindow * wind)
 
     /* Undo the conversion. */
     if (wind->imported_account_tree != SCM_BOOL_F)
-        gfec_apply (undo, wind->imported_account_tree, _gfec_error_handler);
+        gfec_apply (undo, scm_list_1 (wind->imported_account_tree),
+                    _gfec_error_handler);
 
     /* There's no imported account tree any more. */
     scm_gc_unprotect_object (wind->imported_account_tree);

commit 8b7093e8d02f8d0d7a124cba7b1190114abf81c5
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Wed Jul 31 19:34:53 2019 +0800

    [qif/test-qif-parse] initial commit
    
    near-100% coverage for qif-parse.scm

diff --git a/gnucash/import-export/qif-imp/test/CMakeLists.txt b/gnucash/import-export/qif-imp/test/CMakeLists.txt
index 119303ae8..629eaa635 100644
--- a/gnucash/import-export/qif-imp/test/CMakeLists.txt
+++ b/gnucash/import-export/qif-imp/test/CMakeLists.txt
@@ -5,6 +5,7 @@ set(QIF_IMP_TEST_LIBS)
 
 set(scm_qifimp_test_with_srfi64_SOURCES
   test-qif-imp.scm
+  test-qif-parse.scm
   )
 
 
diff --git a/gnucash/import-export/qif-imp/test/test-qif-parse.scm b/gnucash/import-export/qif-imp/test/test-qif-parse.scm
new file mode 100644
index 000000000..1fe5d5767
--- /dev/null
+++ b/gnucash/import-export/qif-imp/test/test-qif-parse.scm
@@ -0,0 +1,280 @@
+(use-modules (gnucash gnc-module))
+(gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
+(use-modules (srfi srfi-64))
+(use-modules (gnucash engine test srfi64-extras))
+(use-modules (gnucash import-export qif-import))
+(use-modules (gnucash import-export string))
+
+(define (run-test)
+  (test-runner-factory gnc:test-runner)
+  (test-begin "test-qif-imp")
+  (test-qif-parse:fix-year)
+  (test-qif-parse:parse-acct-type)
+  (test-qif-parse:parse-cleared-field)
+  (test-qif-parse:parse-action-field)
+  (test-qif-parse:check-date-format)
+  (test-qif-parse:parse-date/format)
+  (test-qif-parse:check-number-format)
+  (test-qif-parse:parse-number/format)
+  (test-qif-parse:check-number-formats)
+  (test-qif-parse:parse-numbers/format)
+  (test-qif-split:parse-category)
+  (test-end "test-qif-imp"))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; qif-parse.scm
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;; the following isn't exported but can be tested anyway!
+(define qif-parse:fix-year
+  (@@ (gnucash import-export qif-import) qif-parse:fix-year))
+(define qif-parse:parse-acct-type
+  (@@ (gnucash import-export qif-import) qif-parse:parse-acct-type))
+(define qif-parse:parse-cleared-field
+  (@@ (gnucash import-export qif-import) qif-parse:parse-cleared-field))
+(define qif-split:parse-category
+  (@@ (gnucash import-export qif-import) qif-split:parse-category))
+(define qif-parse:parse-action-field
+  (@@ (gnucash import-export qif-import) qif-parse:parse-action-field))
+(define qif-parse:check-date-format
+  (@@ (gnucash import-export qif-import) qif-parse:check-date-format))
+(define qif-parse:parse-date/format
+  (@@ (gnucash import-export qif-import) qif-parse:parse-date/format))
+(define qif-parse:check-number-format
+  (@@ (gnucash import-export qif-import) qif-parse:check-number-format))
+(define qif-parse:parse-number/format
+  (@@ (gnucash import-export qif-import) qif-parse:parse-number/format))
+(define qif-parse:check-number-formats
+  (@@ (gnucash import-export qif-import) qif-parse:check-number-formats))
+(define qif-parse:parse-numbers/format
+  (@@ (gnucash import-export qif-import) qif-parse:parse-numbers/format))
+
+
+(define (test-qif-parse:fix-year)
+
+  (test-equal "qif-parse:fix-year 1998"
+    1998
+    (qif-parse:fix-year "1998" 50))
+
+  (test-equal "qif-parse:fix-year ' 0 = 2000"
+    2000
+    (qif-parse:fix-year "' 0" 50))
+
+  (test-equal "qif-parse:fix-year 98>50 = 1998"
+    1998
+    (qif-parse:fix-year "98" 50))
+
+  (test-equal "qif-parse:fix-year 48<50 = 2048"
+    2048
+    (qif-parse:fix-year "48" 50))
+
+  (test-equal "qif-parse:fix-year 19134 = 2034"
+    2034
+    (qif-parse:fix-year "19134" 50))
+
+  (test-equal "qif-parse:fix-year 102 = 2002"
+    2002
+    (qif-parse:fix-year "102" 50)))
+
+
+
+
+
+(define (test-qif-parse:parse-acct-type)
+  (test-equal "qif-parse:parse-acct-type ccard"
+    (list 3)
+    (qif-parse:parse-acct-type "ccard" #f #f))
+
+  (test-equal "qif-parse:parse-acct-type oth s"
+    (list 2 0 1)
+    (qif-parse:parse-acct-type "oth s" #f #f))
+
+  (test-equal "qif-parse:parse-acct-type zzz"
+    (list 0)
+    (qif-parse:parse-acct-type "zzz" (const #f) #f)))
+
+
+
+
+
+(define (test-qif-parse:parse-cleared-field)
+  (test-equal "qif-parse:parse-cleared-field xx = reconciled"
+    'reconciled
+    (qif-parse:parse-cleared-field "xx" (const #f) #f))
+
+  (test-equal "qif-parse:parse-cleared-field cc = cleared"
+    'cleared
+    (qif-parse:parse-cleared-field "cc" (const #f) #f))
+
+  (test-equal "qif-parse:parse-cleared-field !! = budgeted"
+    'budgeted
+    (qif-parse:parse-cleared-field "!!" (const #f) #f))
+
+  (test-equal "qif-parse:parse-cleared-field qq = #f"
+    #f
+    (qif-parse:parse-cleared-field "qq" (const #f) #f)))
+
+
+
+
+
+(define (test-qif-parse:parse-action-field)
+  (test-equal "qif-parse:parse-action-field BuY"
+    'buy
+    (qif-parse:parse-action-field "BuY" (const #f) #f))
+
+  
+  (test-equal "qif-parse:parse-action-field WithDrwX"
+    'xout
+    (qif-parse:parse-action-field "WithDrwX" (const #f) #f))
+
+  (test-equal "qif-parse:parse-action-field k.gewspx"
+    'cgshortx
+    (qif-parse:parse-action-field "k.gewspx" (const #f) #f)))
+
+
+
+
+
+(define (test-qif-parse:check-date-format)
+
+  (test-equal "qif-parse:check-date-format 20/02/1981"
+    '(d-m-y)
+    (qif-parse:check-date-format
+     "20/02/1981"
+     '(d-m-y y-m-d y-d-m m-d-y)))
+
+  (test-equal "qif-parse:check-date-format 12/02/1981"
+    '(d-m-y m-d-y)
+    (qif-parse:check-date-format
+     "12/02/1981"
+     '(d-m-y y-m-d y-d-m m-d-y)))
+
+  (test-equal "qif-parse:check-date-format 1979/03/03"
+    '(y-m-d y-d-m)
+    (qif-parse:check-date-format
+     "1979/03/03"
+     '(d-m-y y-m-d m-d-y y-d-m)))
+
+  (test-equal "qif-parse:check-date-format 19790303"
+    '(y-m-d y-d-m)
+    (qif-parse:check-date-format
+     "19790303"
+     '(d-m-y y-m-d m-d-y y-d-m))))
+
+
+
+
+(define (test-qif-parse:parse-date/format)
+
+  (test-equal "qif-parse:parse-date/format ok"
+    (list 31 01 1981)
+    (qif-parse:parse-date/format "31/01/81" 'd-m-y))
+
+  (test-equal "qif-parse:parse-date/format error"
+    #f
+    (qif-parse:parse-date/format "31/01/81" 'm-d-y)))
+
+
+
+
+(define (test-qif-parse:check-number-format)
+
+  (test-equal "test-qif-parse:check-number-format 1,00"
+    '(comma)
+    (qif-parse:check-number-format "1,00" '(comma integer decimal)))
+
+  (test-equal "test-qif-parse:check-number-format 999"
+    '(comma integer decimal)
+    (qif-parse:check-number-format "999" '(comma integer decimal)))
+  
+  (test-equal "test-qif-parse:check-number-format 999.20"
+    '(decimal)
+    (qif-parse:check-number-format "999.20" '(comma integer decimal)))
+
+  (test-equal "test-qif-parse:check-number-format 9.200,99"
+    '(comma)
+    (qif-parse:check-number-format "9.200,99" '(comma integer decimal)))
+
+  (test-equal "test-qif-parse:check-number-format $1000"
+    '(comma integer decimal)
+    (qif-parse:check-number-format "$1000" '(comma integer decimal))))
+
+
+
+
+(define (test-qif-parse:parse-number/format)
+  (test-equal "qif-parse:parse-number/format 1,23"
+    123/100
+    (qif-parse:parse-number/format "1,23" 'comma))
+
+  (test-equal "qif-parse:parse-number/format 1,234.00"
+    1234
+    (qif-parse:parse-number/format "1,234.00" 'decimal))
+
+  (test-equal "qif-parse:parse-number/format -1234"
+    -1234
+    (qif-parse:parse-number/format "-1234" 'integer))
+
+  (test-equal "qif-parse:parse-number/format 1234"
+    1234
+    (qif-parse:parse-number/format "1234" 'integer))
+
+  )
+
+
+
+(define (test-qif-parse:check-number-formats)
+  (test-equal "qif-parse:check-number-formats 1,000 2,000 300"
+    '(comma)
+    (qif-parse:check-number-formats '("1,00" "2,00" "300,00")
+                                    '(decimal comma integer)))
+
+  (test-equal "qif-parse:check-number-formats 10.50 20.54"
+    '(decimal)
+    (qif-parse:check-number-formats '("10.50" "20.54")
+                                    '(decimal comma integer)))
+
+  (test-equal "qif-parse:check-number-formats 1234 4567"
+    '(decimal comma integer)
+    (qif-parse:check-number-formats '("1234" "4567")
+                                    '(decimal comma integer))))
+
+(define (test-qif-parse:parse-numbers/format)
+  (test-equal "qif-parse:parse-numbers/format 1,00 2,00 300,00"
+    '(1 2 300)
+    (qif-parse:parse-numbers/format '("1,00" "2,00" "300,00")
+                                    'comma))
+
+  (test-equal "qif-parse:parse-numbers/format 1,00 2,50 3,99"
+    '(1 5/2 399/100)
+    (qif-parse:parse-numbers/format '("1,00" "2,50" "3,99")
+                                    'comma))
+
+  (test-equal "qif-parse:parse-numbers/format 1.00 2.00 300.00"
+    '(1 2 300)
+    (qif-parse:parse-numbers/format '("1.00" "2.00" "300.00")
+                                    'decimal))
+
+  (test-equal "qif-parse:parse-numbers/format 1 2 300"
+    '(1 2 300)
+    (qif-parse:parse-numbers/format '("1" "2" "300")
+                                    'integer))
+  
+  (test-equal "qif-parse:parse-numbers/format 1 * 300"
+    '(1 0 300)
+    (qif-parse:parse-numbers/format '("1" "*" "300")
+                                    'integer))
+
+  (test-equal "qif-parse:parse-numbers/format 1 #f 300"
+    '(1 0 300)
+    (qif-parse:parse-numbers/format '("1" #f "300")
+                                    'integer)))
+
+;; unfinished
+(define (test-qif-split:parse-category)
+  
+  (test-equal "qif-split:parse-category"
+    '("LGas" #f "" #f #f #f)
+    (qif-split:parse-category #f "LGas")))

commit a7a3f78648812d2df9fd31a78633b9b30b87e0a3
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Jul 30 18:36:56 2019 +0800

    [qif/test-qif-imp] initial commit
    
    * strings.scm
    * qif-objects.scm

diff --git a/gnucash/import-export/qif-imp/test/CMakeLists.txt b/gnucash/import-export/qif-imp/test/CMakeLists.txt
index db9cba269..119303ae8 100644
--- a/gnucash/import-export/qif-imp/test/CMakeLists.txt
+++ b/gnucash/import-export/qif-imp/test/CMakeLists.txt
@@ -3,6 +3,16 @@
 set(QIF_IMP_TEST_INCLUDE_DIRS)
 set(QIF_IMP_TEST_LIBS)
 
+set(scm_qifimp_test_with_srfi64_SOURCES
+  test-qif-imp.scm
+  )
+
+
 gnc_add_test(test-link-qif-imp test-link.c QIF_IMP_TEST_INCLUDE_DIRS QIF_IMP_TEST_LIBS)
 
-set_dist_list(test_qif_import_DIST CMakeLists.txt test-link.c)
\ No newline at end of file
+if (HAVE_SRFI64)
+  gnc_add_scheme_tests("${scm_qifimp_test_with_srfi64_SOURCES}")
+endif (HAVE_SRFI64)
+
+set_dist_list(test_qif_import_DIST CMakeLists.txt test-link.c
+  ${scm_qifimp_test_with_srfi64_SOURCES})
diff --git a/gnucash/import-export/qif-imp/test/test-qif-imp.scm b/gnucash/import-export/qif-imp/test/test-qif-imp.scm
new file mode 100644
index 000000000..384cb2df7
--- /dev/null
+++ b/gnucash/import-export/qif-imp/test/test-qif-imp.scm
@@ -0,0 +1,60 @@
+(use-modules (gnucash gnc-module))
+(gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
+(use-modules (srfi srfi-64))
+(use-modules (gnucash engine test srfi64-extras))
+(use-modules (gnucash import-export qif-import))
+(use-modules (gnucash import-export string))
+
+(define (run-test)
+  (test-runner-factory gnc:test-runner)
+  (test-begin "test-qif-imp")
+  (test-string)
+  (test-qif-objects)
+  (test-end "test-qif-imp"))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; string.scm
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (test-string)
+  (test-equal "string-rcontains"
+    9
+    (gnc:string-rcontains "foobarfoobarf" "bar"))
+
+  (test-equal "string-rcontains"
+    2
+    (gnc:substring-count "foobarfoobarfoo" "bar"))
+
+  (test-equal "substring-split"
+    '("foo" "foo" "f")
+    (gnc:substring-split "foobarfoobarf" "bar"))
+
+  (test-equal "string-replace-char"
+    "fcc"
+    (gnc:string-replace-char "foo" #\o #\c))
+
+  (test-equal "string-delete"
+    "ad"
+    (gnc:string-delete-chars "abcd" "cb"))
+
+  (test-equal "list-display"
+    "abc"
+    (with-output-to-string
+      (lambda ()
+        (gnc:list-display '("a" "b" "c")))))
+
+  (test-equal "list-display-to-string"
+    "abc"
+    (gnc:list-display-to-string '("a" "b" "c"))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; qif-objects.scm
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (test-qif-objects)
+  (test-assert "make-qif-file is called from C"
+    (make-qif-file))
+
+  (test-assert "make-ticker-map is called from C"
+    (make-ticker-map)))
+

commit 19114cc111d5a685a31ff121a74a82d1d528275d
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Jul 30 14:55:59 2019 +0100

    Fix register tooltip crash
    
    When the register is in double line mode, a tooltip for the transaction
    association column can be shown if one is present by hovering the mouse
    over the cell. If the register 'cursor' is highlighting any transaction
    row this works but if the 'cursor' is on a split and mouse moves to an
    association cell with an entry, Gnucash will crash. To fix this use the
    SheetBlockStyle from block instead of the GnucashCursor.

diff --git a/gnucash/register/register-gnome/gnucash-sheet.c b/gnucash/register/register-gnome/gnucash-sheet.c
index 683455b6c..7de2f0e10 100644
--- a/gnucash/register/register-gnome/gnucash-sheet.c
+++ b/gnucash/register/register-gnome/gnucash-sheet.c
@@ -2676,7 +2676,6 @@ gnucash_sheet_tooltip (GtkWidget  *widget, gint x, gint y,
                gpointer    user_data)
 {
     GnucashSheet *sheet = GNUCASH_SHEET (widget);
-    GnucashCursor *cursor = sheet->cursor;
     Table *table = sheet->table;
     VirtualLocation virt_loc;
     gchar *tooltip_text;
@@ -2716,7 +2715,7 @@ gnucash_sheet_tooltip (GtkWidget  *widget, gint x, gint y,
     by = block->origin_y;
 
     // get the cell location and dimensions
-    gnucash_sheet_style_get_cell_pixel_rel_coords (cursor->style,
+    gnucash_sheet_style_get_cell_pixel_rel_coords (block->style,
             virt_loc.phys_row_offset, virt_loc.phys_col_offset,
             &cx, &cy, &cw, &ch);
 

commit db93aec58d32e871b7d7f17840d9bdec6ee07cc6
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 21:10:14 2019 +0800

    [qif-utils] use srfi-13 instead of regexp functions

diff --git a/gnucash/import-export/qif-imp/qif-file.scm b/gnucash/import-export/qif-imp/qif-file.scm
index f053daa2e..ad73f2a99 100644
--- a/gnucash/import-export/qif-imp/qif-file.scm
+++ b/gnucash/import-export/qif-imp/qif-file.scm
@@ -569,7 +569,7 @@
     (if (or (and (not acct-name)
                  (not security)
                  payee (string? payee)
-                 (string=? (string-remove-trailing-space payee)
+                 (string=? (string-trim-right payee)
                            "Opening Balance")
                  cat-is-acct?)
             (and acct-name (string? acct-name)
diff --git a/gnucash/import-export/qif-imp/qif-objects.scm b/gnucash/import-export/qif-imp/qif-objects.scm
index d4be4d92c..1eaf8cd86 100644
--- a/gnucash/import-export/qif-imp/qif-objects.scm
+++ b/gnucash/import-export/qif-imp/qif-objects.scm
@@ -525,8 +525,8 @@
                              (if last-dot 
                                  last-dot 
                                  (string-length namestring)))))
-          (set! namestring (string-replace-char! namestring #\- #\space))
-          (set! namestring (string-replace-char! namestring #\_ #\space))
+          (set! namestring (gnc:string-replace-char namestring #\- #\space))
+          (set! namestring (gnc:string-replace-char namestring #\_ #\space))
           namestring)
         "QIF Import")))
 
diff --git a/gnucash/import-export/qif-imp/qif-parse.scm b/gnucash/import-export/qif-imp/qif-parse.scm
index 1a636694d..3c12c9d7e 100644
--- a/gnucash/import-export/qif-imp/qif-parse.scm
+++ b/gnucash/import-export/qif-imp/qif-parse.scm
@@ -24,6 +24,7 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (use-modules (gnucash import-export string))
+(use-modules (srfi srfi-13))
 
 (define qif-category-compiled-rexp
   (make-regexp "^ *(\\[)?([^]/|]*)(]?)(/?)([^|]*)(\\|(\\[)?([^]/]*)(]?)(/?)(.*))? *$"))
@@ -162,8 +163,7 @@
 
 (define (qif-parse:parse-acct-type read-value errorproc errortype)
   (let ((mangled-string
-         (string-downcase! (string-remove-trailing-space
-                            (string-remove-leading-space read-value)))))
+         (string-downcase! (string-trim-both read-value))))
     (cond
      ((string=? mangled-string "bank")
       (list GNC-BANK-TYPE))
@@ -197,8 +197,7 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define (qif-parse:parse-bang-field read-value)
-  (let ((bang-field (string-downcase!
-                     (string-remove-trailing-space read-value))))
+  (let ((bang-field (string-downcase! (string-trim read-value))))
 ;; The QIF files output by the WWW site of Credit Lyonnais
 ;; begin by:   !type bank
 ;; instead of: !Type:bank
@@ -211,8 +210,8 @@
 
 (define (qif-parse:parse-action-field read-value errorproc errortype)
   (if read-value
-      (let ((action-symbol (string-to-canonical-symbol read-value)))
-        (case action-symbol
+      (begin
+        (case (string->symbol (string-downcase (string-trim-both read-value)))
           ;; buy
           ((buy cvrshrt kauf)
            'buy)
@@ -579,7 +578,7 @@
         (filtered-string (gnc:string-delete-chars value-string "$'+-")))
     (case format
       ((decimal)
-       (let* ((read-string (string-remove-char filtered-string #\,))
+       (let* ((read-string (gnc:string-delete-chars filtered-string ","))
               (read-val (with-input-from-string read-string
                                                 (lambda () (read)))))
          (if (number? read-val)
@@ -587,12 +586,12 @@
               (if minus-index (- 0.0 read-val) (+ 0.0 read-val))
               GNC-DENOM-AUTO
               (logior (GNC-DENOM-SIGFIGS
-                       (string-length (string-remove-char read-string #\.)))
+                       (string-length (gnc:string-delete-chars read-string ".")))
                       GNC-RND-ROUND))
              (gnc-numeric-zero))))
       ((comma)
        (let* ((read-string (gnc:string-replace-char
-                              (string-remove-char filtered-string #\.)
+                              (gnc:string-delete-chars filtered-string ".")
                               #\, #\.))
               (read-val (with-input-from-string read-string
                                                 (lambda () (read)))))
@@ -601,7 +600,7 @@
               (if minus-index (- 0.0 read-val) (+ 0.0 read-val))
               GNC-DENOM-AUTO
               (logior (GNC-DENOM-SIGFIGS
-                       (string-length (string-remove-char read-string #\.)))
+                       (string-length (gnc:string-delete-chars read-string ".")))
                       GNC-RND-ROUND))
              (gnc-numeric-zero))))
       ((integer)
diff --git a/gnucash/import-export/qif-imp/qif-utils.scm b/gnucash/import-export/qif-imp/qif-utils.scm
index 845994cb0..9c4359697 100644
--- a/gnucash/import-export/qif-imp/qif-utils.scm
+++ b/gnucash/import-export/qif-imp/qif-utils.scm
@@ -24,72 +24,34 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 
-(use-modules (ice-9 regex))
+(use-modules (srfi srfi-13))
 
 (define qif-import:paused #f)
 (define qif-import:canceled #f)
 
-(define (simple-filter pred list)
-  (let ((retval '()))
-    (map (lambda (elt)
-           (if (pred elt)
-               (set! retval (cons elt retval))))
-         list)
-    (reverse retval)))
-
-(define remove-trailing-space-rexp 
-  (make-regexp "^(.*[^ ]+) *$"))
-
-(define remove-leading-space-rexp 
-  (make-regexp "^ *([^ ].*)$"))
-
 (define (string-remove-trailing-space str)
-  (let ((match (regexp-exec remove-trailing-space-rexp str)))
-    (if match
-        (string-copy (match:substring match 1))
-        "")))
+  (issue-deprecation-warning "string-remove-trailing-space - use string-trim-right")
+  (string-trim-right str))
 
 (define (string-remove-leading-space str)
-  (let ((match (regexp-exec remove-leading-space-rexp str)))
-    (if match 
-        (string-copy (match:substring match 1))
-        "")))
+  (issue-deprecation-warning "string-remove-leading-space - use string-trim")
+  (string-trim str))
 
 (define (string-remove-char str char)
-  (let ((rexpstr 
-         (case char  
-           ((#\.) "\\.")
-           ((#\^) "\\^")
-           ((#\$) "\\$")
-           ((#\*) "\\*")
-           ((#\+) "\\+")
-           ((#\\) "\\\\")
-           ((#\?) "\\?")
-           (else 
-             (make-string 1 char)))))
-    (regexp-substitute/global #f rexpstr str 'pre 'post)))
-
-
-(define (string-char-count str char)
-  (length (simple-filter (lambda (elt) (eq? elt char))
-                         (string->list str))))
-
+  (issue-deprecation-warning "string-remove-char - use gnc:string-delete-chars")
+  (gnc:string-delete-chars s (list char)))
 
 (define (string-replace-char! str old new)
-  (let ((rexpstr 
-         (if (not (eq? old #\.))
-             (make-string 1 old)
-             "\\."))
-        (newstr (make-string 1 new)))
-    (regexp-substitute/global #f rexpstr str 'pre newstr 'post)))
+  (issue-deprecation-warning "string-replace-char! - use gnc:string-replace-char")
+  (gnc:string-replace-char str old new))
 
 (define (string-to-canonical-symbol str)
+  (issue-deprecation-warning "string-to-canonical-symbol - inline instead")
   (string->symbol 
    (string-downcase
     (string-remove-leading-space
      (string-remove-trailing-space str)))))
 
-
 (define (qif-import:log progress-dialog proc str)
   (if progress-dialog
       (gnc-progress-dialog-append-log progress-dialog (string-append str "\n"))
@@ -103,15 +65,13 @@
   (set! qif-import:canceled #t))
 
 (define (qif-import:toggle-pause progress-dialog)
-  (if qif-import:paused
-      (begin
-        (set! qif-import:paused #f)
-        (if progress-dialog
-            (gnc-progress-dialog-resume progress-dialog)))
-      (begin
-        (set! qif-import:paused #t)
-        (if progress-dialog
-            (gnc-progress-dialog-pause progress-dialog)))))
+  (cond
+   (qif-import:paused
+    (set! qif-import:paused #f)
+    (when progress-dialog (gnc-progress-dialog-resume progress-dialog)))
+   (else
+    (set! qif-import:paused #t)
+    (when progress-dialog (gnc-progress-dialog-pause progress-dialog)))))
 
 (define (qif-import:check-pause progress-dialog)
   (while (and qif-import:paused (not qif-import:canceled))

commit fbb6a956002776aaee143bf0fc0524706ab1ac91
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 20:01:38 2019 +0800

    [simple-obj] deprecate this module
    
    * it's only a wrapper for make-record-type
    * use record-types directly in modules

diff --git a/gnucash/import-export/qif-imp/qif-guess-map.scm b/gnucash/import-export/qif-imp/qif-guess-map.scm
index 12802086e..f19ab0c52 100644
--- a/gnucash/import-export/qif-imp/qif-guess-map.scm
+++ b/gnucash/import-export/qif-imp/qif-guess-map.scm
@@ -39,6 +39,14 @@
 (define GNC-RECEIVABLE-TYPE 11)
 (define GNC-PAYABLE-TYPE 12)
 
+(define (record-fields->list record)
+  (let ((type (record-type-descriptor record)))
+    (map
+     (lambda (field) ((record-accessor type field) record))
+     (record-type-fields type))))
+
+(define (list->record-fields lst type)
+  (apply (record-constructor type) lst))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  qif-import:load-map-prefs
@@ -180,7 +188,7 @@
   (let ((table '()))
     (hash-fold
      (lambda (key value p)
-       (set! table (cons (cons key (simple-obj-to-list value)) table))
+       (set! table (cons (cons key (record-fields->list value)) table))
        #f) #f hashtab)
     (write table)))
 
@@ -192,7 +200,7 @@
     (for-each
      (lambda (entry)
        (let ((key (car entry))
-             (value (simple-obj-from-list (cdr entry) <qif-map-entry>)))
+             (value (list->record-fields (cdr entry) <qif-map-entry>)))
 
          ;; If the account separator has changed, fix the account name.
          (if changed-sep?
diff --git a/gnucash/import-export/qif-imp/qif-objects.scm b/gnucash/import-export/qif-imp/qif-objects.scm
index 0f1a7aa5c..d4be4d92c 100644
--- a/gnucash/import-export/qif-imp/qif-objects.scm
+++ b/gnucash/import-export/qif-imp/qif-objects.scm
@@ -24,6 +24,9 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 
+(define (construct class)
+  (apply (record-constructor class)
+         (map (const #f) (record-type-fields class))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  qif-file class 
@@ -34,7 +37,7 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define <qif-file>
-  (make-simple-class 
+  (make-record-type
    'qif-file 
    '(path                 ;; where file was loaded 
      y2k-threshold
@@ -47,43 +50,43 @@
   (record-predicate <qif-file>))
 
 (define qif-file:path 
-  (simple-obj-getter <qif-file> 'path))
+  (record-accessor <qif-file> 'path))
 
 (define qif-file:set-path! 
-  (simple-obj-setter <qif-file> 'path))
+  (record-modifier <qif-file> 'path))
 
 (define qif-file:y2k-threshold 
-  (simple-obj-getter <qif-file> 'y2k-threshold))
+  (record-accessor <qif-file> 'y2k-threshold))
 
 (define qif-file:set-y2k-threshold!
-  (simple-obj-setter <qif-file> 'y2k-threshold))
+  (record-modifier <qif-file> 'y2k-threshold))
 
 (define qif-file:cats 
-  (simple-obj-getter <qif-file> 'cats))
+  (record-accessor <qif-file> 'cats))
 
 (define qif-file:set-cats!
-  (simple-obj-setter <qif-file> 'cats))
+  (record-modifier <qif-file> 'cats))
 
 (define qif-file:classes 
-  (simple-obj-getter <qif-file> 'classes))
+  (record-accessor <qif-file> 'classes))
 
 (define qif-file:set-classes!
-  (simple-obj-setter <qif-file> 'classes))
+  (record-modifier <qif-file> 'classes))
 
 (define qif-file:xtns 
-  (simple-obj-getter <qif-file> 'xtns))
+  (record-accessor <qif-file> 'xtns))
 
 (define qif-file:set-xtns!
-  (simple-obj-setter <qif-file> 'xtns))
+  (record-modifier <qif-file> 'xtns))
 
 (define qif-file:accounts 
-  (simple-obj-getter <qif-file> 'accounts))
+  (record-accessor <qif-file> 'accounts))
 
 (define qif-file:set-accounts!
-  (simple-obj-setter <qif-file> 'accounts))
+  (record-modifier <qif-file> 'accounts))
 
 (define (make-qif-file) 
-  (let ((self (make-simple-obj <qif-file>)))
+  (let ((self (construct <qif-file>)))
     (qif-file:set-y2k-threshold! self 50)
     (qif-file:set-xtns! self '())
     (qif-file:set-accounts! self '())
@@ -97,16 +100,16 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define <qif-split>
-  (make-simple-class 
+  (make-record-type
    'qif-split
    '(category class memo amount category-is-account? matching-cleared mark
               miscx-category miscx-is-account? miscx-class)))
 
 (define qif-split:category 
-  (simple-obj-getter <qif-split> 'category))
+  (record-accessor <qif-split> 'category))
 
 (define qif-split:set-category-private!
-  (simple-obj-setter <qif-split> 'category))
+  (record-modifier <qif-split> 'category))
 
 (define (qif-split:set-category! self value)
   (let* ((cat-info 
@@ -125,61 +128,61 @@
     (qif-split:set-miscx-class! self miscx-class)))
     
 (define qif-split:class 
-  (simple-obj-getter <qif-split> 'class))
+  (record-accessor <qif-split> 'class))
 
 (define qif-split:set-class!
-  (simple-obj-setter <qif-split> 'class))
+  (record-modifier <qif-split> 'class))
 
 (define qif-split:memo 
-  (simple-obj-getter <qif-split> 'memo))
+  (record-accessor <qif-split> 'memo))
 
 (define qif-split:set-memo! 
-  (simple-obj-setter <qif-split> 'memo))
+  (record-modifier <qif-split> 'memo))
 
 (define qif-split:amount 
-  (simple-obj-getter <qif-split> 'amount))
+  (record-accessor <qif-split> 'amount))
 
 (define qif-split:set-amount! 
-  (simple-obj-setter <qif-split> 'amount))
+  (record-modifier <qif-split> 'amount))
 
 (define qif-split:mark 
-  (simple-obj-getter <qif-split> 'mark))
+  (record-accessor <qif-split> 'mark))
 
 (define qif-split:set-mark! 
-  (simple-obj-setter <qif-split> 'mark))
+  (record-modifier <qif-split> 'mark))
 
 (define qif-split:matching-cleared 
-  (simple-obj-getter <qif-split> 'matching-cleared))
+  (record-accessor <qif-split> 'matching-cleared))
 
 (define qif-split:set-matching-cleared! 
-  (simple-obj-setter <qif-split> 'matching-cleared))
+  (record-modifier <qif-split> 'matching-cleared))
 
 (define qif-split:category-is-account? 
-  (simple-obj-getter <qif-split> 'category-is-account?))
+  (record-accessor <qif-split> 'category-is-account?))
 
 (define qif-split:set-category-is-account?! 
-  (simple-obj-setter <qif-split> 'category-is-account?))
+  (record-modifier <qif-split> 'category-is-account?))
 
 (define qif-split:miscx-is-account? 
-  (simple-obj-getter <qif-split> 'miscx-is-account?))
+  (record-accessor <qif-split> 'miscx-is-account?))
 
 (define qif-split:set-miscx-is-account?!
-  (simple-obj-setter <qif-split> 'miscx-is-account?))
+  (record-modifier <qif-split> 'miscx-is-account?))
 
 (define qif-split:miscx-category 
-  (simple-obj-getter <qif-split> 'miscx-category))
+  (record-accessor <qif-split> 'miscx-category))
 
 (define qif-split:set-miscx-category!
-  (simple-obj-setter <qif-split> 'miscx-category))
+  (record-modifier <qif-split> 'miscx-category))
 
 (define qif-split:miscx-class 
-  (simple-obj-getter <qif-split> 'miscx-class))
+  (record-accessor <qif-split> 'miscx-class))
 
 (define qif-split:set-miscx-class!
-  (simple-obj-setter <qif-split> 'miscx-class))
+  (record-modifier <qif-split> 'miscx-class))
 
 (define (make-qif-split)
-  (let ((self (make-simple-obj <qif-split>)))
+  (let ((self (construct <qif-split>)))
     (qif-split:set-category! self "")
     self))
 
@@ -200,7 +203,7 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define <qif-xtn>
-  (make-simple-class 
+  (make-record-type
    'qif-xtn
    '(date payee address number action cleared  
           from-acct share-price num-shares security-name commission 
@@ -210,97 +213,97 @@
   (record-predicate <qif-xtn>))
 
 (define qif-xtn:date
-  (simple-obj-getter <qif-xtn> 'date))
+  (record-accessor <qif-xtn> 'date))
 
 (define qif-xtn:set-date! 
-  (simple-obj-setter <qif-xtn> 'date))
+  (record-modifier <qif-xtn> 'date))
 
 (define qif-xtn:payee
-  (simple-obj-getter <qif-xtn> 'payee))
+  (record-accessor <qif-xtn> 'payee))
 
 (define qif-xtn:set-payee! 
-  (simple-obj-setter <qif-xtn> 'payee))
+  (record-modifier <qif-xtn> 'payee))
 
 (define qif-xtn:address
-  (simple-obj-getter <qif-xtn> 'address))
+  (record-accessor <qif-xtn> 'address))
 
 (define qif-xtn:set-address! 
-  (simple-obj-setter <qif-xtn> 'address))
+  (record-modifier <qif-xtn> 'address))
 
 (define qif-xtn:number
-  (simple-obj-getter <qif-xtn> 'number))
+  (record-accessor <qif-xtn> 'number))
 
 (define qif-xtn:set-number! 
-  (simple-obj-setter <qif-xtn> 'number))
+  (record-modifier <qif-xtn> 'number))
 
 (define qif-xtn:action
-  (simple-obj-getter <qif-xtn> 'action))
+  (record-accessor <qif-xtn> 'action))
 
 (define qif-xtn:set-action! 
-  (simple-obj-setter <qif-xtn> 'action))
+  (record-modifier <qif-xtn> 'action))
 
 (define qif-xtn:cleared
-  (simple-obj-getter <qif-xtn> 'cleared))
+  (record-accessor <qif-xtn> 'cleared))
 
 (define qif-xtn:set-cleared! 
-  (simple-obj-setter <qif-xtn> 'cleared))
+  (record-modifier <qif-xtn> 'cleared))
 
 (define qif-xtn:from-acct
-  (simple-obj-getter <qif-xtn> 'from-acct))
+  (record-accessor <qif-xtn> 'from-acct))
 
 (define qif-xtn:set-from-acct! 
-  (simple-obj-setter <qif-xtn> 'from-acct))
+  (record-modifier <qif-xtn> 'from-acct))
 
 (define qif-xtn:share-price
-  (simple-obj-getter <qif-xtn> 'share-price))
+  (record-accessor <qif-xtn> 'share-price))
 
 (define qif-xtn:set-share-price! 
-  (simple-obj-setter <qif-xtn> 'share-price))
+  (record-modifier <qif-xtn> 'share-price))
 
 (define qif-xtn:num-shares
-  (simple-obj-getter <qif-xtn> 'num-shares))
+  (record-accessor <qif-xtn> 'num-shares))
 
 (define qif-xtn:set-num-shares! 
-  (simple-obj-setter <qif-xtn> 'num-shares))
+  (record-modifier <qif-xtn> 'num-shares))
 
 (define qif-xtn:security-name
-  (simple-obj-getter <qif-xtn> 'security-name))
+  (record-accessor <qif-xtn> 'security-name))
 
 (define qif-xtn:set-security-name! 
-  (simple-obj-setter <qif-xtn> 'security-name))
+  (record-modifier <qif-xtn> 'security-name))
 
 (define qif-xtn:commission
-  (simple-obj-getter <qif-xtn> 'commission))
+  (record-accessor <qif-xtn> 'commission))
 
 (define qif-xtn:set-commission! 
-  (simple-obj-setter <qif-xtn> 'commission))
+  (record-modifier <qif-xtn> 'commission))
 
 (define qif-xtn:default-split
-  (simple-obj-getter <qif-xtn> 'default-split))
+  (record-accessor <qif-xtn> 'default-split))
 
 (define qif-xtn:set-default-split! 
-  (simple-obj-setter <qif-xtn> 'default-split))
+  (record-modifier <qif-xtn> 'default-split))
 
 (define qif-xtn:splits
-  (simple-obj-getter <qif-xtn> 'splits))
+  (record-accessor <qif-xtn> 'splits))
 
 (define qif-xtn:set-splits! 
-  (simple-obj-setter <qif-xtn> 'splits))
+  (record-modifier <qif-xtn> 'splits))
 
 (define qif-xtn:mark
-  (simple-obj-getter <qif-xtn> 'mark))
+  (record-accessor <qif-xtn> 'mark))
 
 (define qif-xtn:set-mark! 
-  (simple-obj-setter <qif-xtn> 'mark))
+  (record-modifier <qif-xtn> 'mark))
 
 (define (make-qif-xtn)
-  (let ((self (make-simple-obj <qif-xtn>)))
+  (let ((self (construct <qif-xtn>)))
     (qif-xtn:set-mark! self #f)
     (qif-xtn:set-splits! self '())
     self))
 
 (define (qif-xtn:print self)
-  (simple-obj-print self))
+  (write self))
 
 
 (define (qif-xtn:split-amounts self)
@@ -340,42 +343,42 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define <qif-acct>
-  (make-simple-class 
+  (make-record-type
    'qif-acct
    '(name type description limit budget)))
 
 (define qif-acct:name
-  (simple-obj-getter <qif-acct> 'name))
+  (record-accessor <qif-acct> 'name))
 
 (define qif-acct:set-name! 
-  (simple-obj-setter <qif-acct> 'name))
+  (record-modifier <qif-acct> 'name))
 
 (define qif-acct:type
-  (simple-obj-getter <qif-acct> 'type))
+  (record-accessor <qif-acct> 'type))
 
 (define qif-acct:set-type! 
-  (simple-obj-setter <qif-acct> 'type))
+  (record-modifier <qif-acct> 'type))
 
 (define qif-acct:description
-  (simple-obj-getter <qif-acct> 'description))
+  (record-accessor <qif-acct> 'description))
 
 (define qif-acct:set-description! 
-  (simple-obj-setter <qif-acct> 'description))
+  (record-modifier <qif-acct> 'description))
 
 (define qif-acct:limit
-  (simple-obj-getter <qif-acct> 'limit))
+  (record-accessor <qif-acct> 'limit))
 
 (define qif-acct:set-limit! 
-  (simple-obj-setter <qif-acct> 'limit))
+  (record-modifier <qif-acct> 'limit))
 
 (define qif-acct:budget
-  (simple-obj-getter <qif-acct> 'budget))
+  (record-accessor <qif-acct> 'budget))
 
 (define qif-acct:set-budget! 
-  (simple-obj-setter <qif-acct> 'budget))
+  (record-modifier <qif-acct> 'budget))
 
 (define (make-qif-acct)
-  (let ((retval (make-simple-obj <qif-acct>)))
+  (let ((retval (construct <qif-acct>)))
     (qif-acct:set-type! retval "Bank")
     (qif-acct:set-name! retval "Default Account")
     retval))
@@ -384,7 +387,7 @@
   (record-predicate <qif-acct>))
 
 (define (qif-acct:print self)
-  (simple-obj-print self))
+  (write self))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;  <qif-class>
@@ -393,27 +396,27 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define <qif-class>
-  (make-simple-class
+  (make-record-type
    'qif-class
    '(name description)))
 
 (define qif-class:name
-  (simple-obj-getter <qif-class> 'name))
+  (record-accessor <qif-class> 'name))
 
 (define qif-class:set-name! 
-  (simple-obj-setter <qif-class> 'name))
+  (record-modifier <qif-class> 'name))
 
 (define qif-class:description
-  (simple-obj-getter <qif-class> 'description))
+  (record-accessor <qif-class> 'description))
 
 (define qif-class:set-description! 
-  (simple-obj-setter <qif-class> 'description))
+  (record-modifier <qif-class> 'description))
 
 (define (qif-class:print self)
-  (simple-obj-print self))
+  (write self))
 
 (define (make-qif-class)
-  (make-simple-obj <qif-class>))
+  (construct <qif-class>))
 
 (define qif-class? 
   (record-predicate <qif-class>))
@@ -431,60 +434,60 @@
 
 
 (define <qif-cat>
-  (make-simple-class 
+  (make-record-type
    'qif-cat
    '(name description taxable expense-cat income-cat tax-class budget-amt)))
 
 (define qif-cat:name
-  (simple-obj-getter <qif-cat> 'name))
+  (record-accessor <qif-cat> 'name))
 
 (define qif-cat:set-name! 
-  (simple-obj-setter <qif-cat> 'name))
+  (record-modifier <qif-cat> 'name))
 
 (define qif-cat:description
-  (simple-obj-getter <qif-cat> 'description))
+  (record-accessor <qif-cat> 'description))
 
 (define qif-cat:set-description! 
-  (simple-obj-setter <qif-cat> 'description))
+  (record-modifier <qif-cat> 'description))
 
 (define qif-cat:taxable
-  (simple-obj-getter <qif-cat> 'taxable))
+  (record-accessor <qif-cat> 'taxable))
 
 (define qif-cat:set-taxable! 
-  (simple-obj-setter <qif-cat> 'taxable))
+  (record-modifier <qif-cat> 'taxable))
 
 (define qif-cat:expense-cat
-  (simple-obj-getter <qif-cat> 'expense-cat))
+  (record-accessor <qif-cat> 'expense-cat))
 
 (define qif-cat:set-expense-cat! 
-  (simple-obj-setter <qif-cat> 'expense-cat))
+  (record-modifier <qif-cat> 'expense-cat))
 
 (define qif-cat:income-cat
-  (simple-obj-getter <qif-cat> 'income-cat))
+  (record-accessor <qif-cat> 'income-cat))
 
 (define qif-cat:set-income-cat! 
-  (simple-obj-setter <qif-cat> 'income-cat))
+  (record-modifier <qif-cat> 'income-cat))
 
 (define qif-cat:tax-class
-  (simple-obj-getter <qif-cat> 'tax-class))
+  (record-accessor <qif-cat> 'tax-class))
 
 (define qif-cat:set-tax-class! 
-  (simple-obj-setter <qif-cat> 'tax-class))
+  (record-modifier <qif-cat> 'tax-class))
 
 (define qif-cat:budget-amt
-  (simple-obj-getter <qif-cat> 'budget-amt))
+  (record-accessor <qif-cat> 'budget-amt))
 
 (define qif-cat:set-budget-amt! 
-  (simple-obj-setter <qif-cat> 'budget-amt))
+  (record-modifier <qif-cat> 'budget-amt))
 
 (define (make-qif-cat) 
-  (make-simple-obj <qif-cat>))
+  (construct <qif-cat>))
 
 (define qif-cat? 
   (record-predicate <qif-cat>))
 
 (define (qif-cat:print self)
-  (simple-obj-print self))
+  (write self))
 
 (define (qif-file:add-xtn! self xtn)
   (qif-file:set-xtns! self 
@@ -535,7 +538,7 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define <qif-map-entry>
-  (make-simple-class
+  (make-record-type
    'qif-map-entry
    '(qif-name       ;; set while parsing file 
      allowed-types  ;; set while parsing file 
@@ -545,7 +548,7 @@
      display?)))    ;; set when non-zero transactions 
 
 (define (make-qif-map-entry)
-  (make-simple-obj <qif-map-entry>))
+  (construct <qif-map-entry>))
 
 (define (qif-map-entry:clone orig)
   (let ((me (make-qif-map-entry)))
@@ -586,40 +589,40 @@
 
 
 (define qif-map-entry:qif-name
-  (simple-obj-getter <qif-map-entry> 'qif-name))
+  (record-accessor <qif-map-entry> 'qif-name))
 
 (define qif-map-entry:set-qif-name!
-  (simple-obj-setter <qif-map-entry> 'qif-name))
+  (record-modifier <qif-map-entry> 'qif-name))
 
 (define qif-map-entry:allowed-types
-  (simple-obj-getter <qif-map-entry> 'allowed-types))
+  (record-accessor <qif-map-entry> 'allowed-types))
 
 (define qif-map-entry:set-allowed-types!
-  (simple-obj-setter <qif-map-entry> 'allowed-types))
+  (record-modifier <qif-map-entry> 'allowed-types))
 
 (define qif-map-entry:description
-  (simple-obj-getter <qif-map-entry> 'description))
+  (record-accessor <qif-map-entry> 'description))
 
 (define qif-map-entry:set-description!
-  (simple-obj-setter <qif-map-entry> 'description))
+  (record-modifier <qif-map-entry> 'description))
 
 (define qif-map-entry:gnc-name
-  (simple-obj-getter <qif-map-entry> 'gnc-name))
+  (record-accessor <qif-map-entry> 'gnc-name))
 
 (define qif-map-entry:set-gnc-name!
-  (simple-obj-setter <qif-map-entry> 'gnc-name))
+  (record-modifier <qif-map-entry> 'gnc-name))
 
 (define qif-map-entry:new-acct?
-  (simple-obj-getter <qif-map-entry> 'new-acct?))
+  (record-accessor <qif-map-entry> 'new-acct?))
 
 (define qif-map-entry:set-new-acct?!
-  (simple-obj-setter <qif-map-entry> 'new-acct?))
+  (record-modifier <qif-map-entry> 'new-acct?))
 
 (define qif-map-entry:display?
-  (simple-obj-getter <qif-map-entry> 'display?))
+  (record-accessor <qif-map-entry> 'display?))
 
 (define qif-map-entry:set-display?!
-  (simple-obj-setter <qif-map-entry> 'display?))
+  (record-modifier <qif-map-entry> 'display?))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -630,51 +633,51 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define <qif-stock-symbol>
-  (make-simple-class
+  (make-record-type
    'qif-stock-symbol
    '(name symbol type)))
 
 (define qif-stock-symbol:name
-  (simple-obj-getter <qif-stock-symbol> 'name))
+  (record-accessor <qif-stock-symbol> 'name))
 
 (define qif-stock-symbol:set-name! 
-  (simple-obj-setter <qif-stock-symbol> 'name))
+  (record-modifier <qif-stock-symbol> 'name))
 
 (define qif-stock-symbol:symbol
-  (simple-obj-getter <qif-stock-symbol> 'symbol))
+  (record-accessor <qif-stock-symbol> 'symbol))
 
 (define qif-stock-symbol:set-symbol! 
-  (simple-obj-setter <qif-stock-symbol> 'symbol))
+  (record-modifier <qif-stock-symbol> 'symbol))
 
 (define qif-stock-symbol:type
-  (simple-obj-getter <qif-stock-symbol> 'type))
+  (record-accessor <qif-stock-symbol> 'type))
 
 (define qif-stock-symbol:set-type! 
-  (simple-obj-setter <qif-stock-symbol> 'type))
+  (record-modifier <qif-stock-symbol> 'type))
 
 (define (qif-stock-symbol:print self)
-  (simple-obj-print self))
+  (write self))
 
 (define (make-qif-stock-symbol)
-  (let ((retval (make-simple-obj <qif-stock-symbol>)))
+  (let ((retval (construct <qif-stock-symbol>)))
     (qif-stock-symbol:set-name! retval "")
     (qif-stock-symbol:set-symbol! retval "")
     (qif-stock-symbol:set-type! retval "")
     retval))
 
 (define <qif-ticker-map>
-  (make-simple-class
+  (make-record-type
    'qif-ticker-map
    '(stocks)))
 
 (define qif-ticker-map:ticker-map
-  (simple-obj-getter <qif-ticker-map> 'stocks))
+  (record-accessor <qif-ticker-map> 'stocks))
 
 (define qif-ticker-map:set-ticker-map!
-  (simple-obj-setter <qif-ticker-map> 'stocks))
+  (record-modifier <qif-ticker-map> 'stocks))
 
 (define (make-ticker-map) 
-  (let ((self (make-simple-obj <qif-ticker-map>)))
+  (let ((self (construct <qif-ticker-map>)))
     (qif-ticker-map:set-ticker-map! self '())
     self))
 
diff --git a/libgnucash/app-utils/app-utils.scm b/libgnucash/app-utils/app-utils.scm
index f4437a2af..ca789412e 100644
--- a/libgnucash/app-utils/app-utils.scm
+++ b/libgnucash/app-utils/app-utils.scm
@@ -270,23 +270,23 @@
 (re-export HOOK-REPORT)
 
 ;; simple-obj
-(export make-simple-class)
-(export simple-obj-getter)
-(export simple-obj-setter)
-(export simple-obj-print)
-(export simple-obj-to-list)
-(export simple-obj-from-list)
-(export make-simple-obj)
+(export make-simple-class)              ;deprecate
+(export simple-obj-getter)              ;deprecate
+(export simple-obj-setter)              ;deprecate
+(export simple-obj-print)               ;deprecate
+(export simple-obj-to-list)             ;deprecate
+(export simple-obj-from-list)           ;deprecate
+(export make-simple-obj)                ;deprecate
 
 (define gnc:*kvp-option-path* (list KVP-OPTION-PATH))
 (export gnc:*kvp-option-path*)
 
 (load-from-path "c-interface")
 (load-from-path "options")
-(load-from-path "hooks")
+(load-from-path "hooks")                ;deprecate
 (load-from-path "prefs")
 (load-from-path "date-utilities")
-(load-from-path "simple-obj")
+(load-from-path "simple-obj")           ;deprecate
 
 ;; Business options
 (define gnc:*business-label* (N_ "Business"))
diff --git a/libgnucash/app-utils/simple-obj.scm b/libgnucash/app-utils/simple-obj.scm
index bb32ad3fa..9e502fa11 100644
--- a/libgnucash/app-utils/simple-obj.scm
+++ b/libgnucash/app-utils/simple-obj.scm
@@ -23,7 +23,6 @@
 ;; Boston, MA  02110-1301,  USA       gnu at gnu.org
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-
 ;;  this is an extremely rudimentary object system.  Each object is a
 ;;  cons cell, where the car is a symbol with the class name and the
 ;;  cdr is a vector of the slots.  
@@ -41,18 +40,23 @@
 
 ;; the 'simple-class' class.  
 (define (make-simple-class class-symbol slot-names) 
+  (issue-deprecation-warning "make-simple-class is deprecated. use make-record-type.")
   (make-record-type (symbol->string class-symbol) slot-names))
 
 (define (simple-obj-getter class slot)  
+  (issue-deprecation-warning "simple-obj-getter is deprecated. use record-accessor.")
   (record-accessor class slot))
 
 (define (simple-obj-setter class slot)
+  (issue-deprecation-warning "simple-obj-setter is deprecated. use record-modifier.")
   (record-modifier class slot))
 
 (define (simple-obj-print obj)
+  (issue-deprecation-warning "simple-obj-print is deprecated. use write.")
   (write obj))
 
 (define (simple-obj-to-list obj)
+  (issue-deprecation-warning "simple-obj-to-list is deprecated. use record-type->list in qif-guess-map.scm")
   (let ((retval '()))
     (for-each 
      (lambda (slot)
@@ -62,6 +66,7 @@
     (reverse retval)))
 
 (define (simple-obj-from-list list type)
+  (issue-deprecation-warning "simple-obj-from-list-obj is deprecated. use list->record-type in qif-guess-map.scm")
   (let ((retval (make-simple-obj type)))
     (for-each 
      (lambda (slot)
@@ -73,6 +78,7 @@
 
 
 (define (make-simple-obj class)
+  (issue-deprecation-warning "make-simple-obj is deprecated. use construct in qif-objects.scm")
   (let ((ctor (record-constructor class))
         (field-defaults 
          (map (lambda (v) #f) (record-type-fields class))))

commit 76ba133174bc1b638ae0a186a248733e03ae1aaf
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Thu Jul 18 15:43:38 2019 +0100

    Account Picker dialogue warning message placement
    
    Currently this message is below the dialogue buttons so move the
    message to above them.

diff --git a/gnucash/gtkbuilder/dialog-import.glade b/gnucash/gtkbuilder/dialog-import.glade
index d1cd6396b..74e040d92 100644
--- a/gnucash/gtkbuilder/dialog-import.glade
+++ b/gnucash/gtkbuilder/dialog-import.glade
@@ -79,7 +79,7 @@
             <property name="expand">False</property>
             <property name="fill">False</property>
             <property name="pack_type">end</property>
-            <property name="position">0</property>
+            <property name="position">4</property>
           </packing>
         </child>
         <child>
@@ -108,6 +108,20 @@
             <property name="position">1</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkScrolledWindow" id="account_tree_sw">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
         <child>
           <object class="GtkBox" id="placeholder_warning_hbox">
             <property name="can_focus">False</property>
@@ -143,22 +157,7 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">True</property>
-            <property name="pack_type">end</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkScrolledWindow" id="account_tree_sw">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <child>
-              <placeholder/>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">4</property>
+            <property name="position">3</property>
           </packing>
         </child>
       </object>
@@ -1283,9 +1282,6 @@
             <property name="position">0</property>
           </packing>
         </child>
-        <child>
-          <placeholder/>
-        </child>
       </object>
     </child>
     <action-widgets>

commit d4dd289113452e2aceedbbecc3ef8ad837ce3859
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Thu Jul 18 15:42:27 2019 +0100

    Error when cancelling the create a new account in register
    
    When you are asked whether you want to create a new account on the
    register, possibly due to a typo and you answer no, the same dialogue
    will appear another three times before resetting the account cell to
    the original value.
    
    These changes eliminate that by returning you back to the cell with the
    invalid entry so that you can amend / cancel or use the dialogue again
    to create a new account based on an amended entry.

diff --git a/gnucash/gnome/gnc-split-reg.c b/gnucash/gnome/gnc-split-reg.c
index 628da74d5..5291e41bb 100644
--- a/gnucash/gnome/gnc-split-reg.c
+++ b/gnucash/gnome/gnc-split-reg.c
@@ -2268,7 +2268,7 @@ gnc_split_reg_set_sort_reversed(GNCSplitReg *gsr, gboolean rev, gboolean refresh
         gnc_ledger_display_refresh( gsr->ledger );
 }
 
-static void
+static gboolean
 gnc_split_reg_record (GNCSplitReg *gsr)
 {
     SplitRegister *reg;
@@ -2282,7 +2282,7 @@ gnc_split_reg_record (GNCSplitReg *gsr)
     if (!gnc_split_register_save (reg, TRUE))
     {
         LEAVE("no save");
-        return;
+        return FALSE;
     }
 
     gsr_emit_include_date_signal( gsr, xaccTransGetDate(trans) );
@@ -2291,6 +2291,7 @@ gnc_split_reg_record (GNCSplitReg *gsr)
      * since gui_refresh events should handle this. */
     /* gnc_split_register_redraw (reg); */
     LEAVE(" ");
+    return TRUE;
 }
 
 static gboolean
@@ -2354,7 +2355,14 @@ gnc_split_reg_enter( GNCSplitReg *gsr, gboolean next_transaction )
     }
 
     /* First record the transaction. This will perform a refresh. */
-    gnc_split_reg_record( gsr );
+    if (!gnc_split_reg_record (gsr))
+    {
+        /* make sure the sheet has the focus if the record is FALSE
+         * which results in no cursor movement. */
+        gnc_split_reg_focus_on_sheet (gsr);
+        LEAVE(" ");
+        return;
+    }
 
     if (!goto_blank && next_transaction)
         gnc_split_register_expand_current_trans (sr, FALSE);
diff --git a/gnucash/register/ledger-core/split-register.c b/gnucash/register/ledger-core/split-register.c
index 9fc109927..a73ac9382 100644
--- a/gnucash/register/ledger-core/split-register.c
+++ b/gnucash/register/ledger-core/split-register.c
@@ -1735,8 +1735,12 @@ gnc_split_register_save (SplitRegister *reg, gboolean do_commit)
           blank_split, blank_trans, pending_trans, trans);
 
     /* Act on any changes to the current cell before the save. */
-    (void) gnc_split_register_check_cell (reg,
-                                          gnc_table_get_current_cell_name (reg->table));
+    if (!gnc_split_register_check_cell (reg,
+            gnc_table_get_current_cell_name (reg->table)))
+    {
+        LEAVE("need another go at changing cell");
+        return FALSE;
+    }
 
     if (!gnc_split_register_auto_calc (reg, split))
     {
@@ -1945,6 +1949,10 @@ gnc_split_register_get_account_by_name (SplitRegister *reg, BasicCell * bcell,
     if (!account)
         account = gnc_account_lookup_by_code(gnc_get_current_root_account(), name);
 
+    /* if gnc_ui_new_accounts_from_name_window is used, there is a call to
+     * refresh which subsequently calls this function again, thats the
+     * reason for static creating_account. */
+
     if (!account && !creating_account)
     {
         /* Ask if they want to create a new one. */
@@ -1958,21 +1966,27 @@ gnc_split_register_get_account_by_name (SplitRegister *reg, BasicCell * bcell,
             return NULL;
     }
 
-    /* Now have the account. */
-    account_name = gnc_get_account_name_for_split_register (account, reg->show_leaf_accounts);
-    if (g_strcmp0(account_name, gnc_basic_cell_get_value(bcell)))
+    if (!creating_account)
     {
-        /* The name has changed. Update the cell. */
-        gnc_combo_cell_set_value (cell, account_name);
-        gnc_basic_cell_set_changed (&cell->cell, TRUE);
-    }
-    g_free (account_name);
+        /* Now have the account. */
+        account_name = gnc_get_account_name_for_split_register (account, reg->show_leaf_accounts);
+        if (g_strcmp0(account_name, gnc_basic_cell_get_value(bcell)))
+        {
+            /* The name has changed. Update the cell. */
+            gnc_combo_cell_set_value (cell, account_name);
+            gnc_basic_cell_set_changed (&cell->cell, TRUE);
+        }
+        g_free (account_name);
 
-    /* See if the account (either old or new) is a placeholder. */
-    if (account && xaccAccountGetPlaceholder (account))
-    {
-        gnc_error_dialog (GTK_WINDOW (gnc_split_register_get_parent (reg)),
-                          placeholder, name);
+        /* See if the account (either old or new) is a placeholder. */
+        if (account && xaccAccountGetPlaceholder (account))
+        {
+            gchar *fullname = gnc_account_get_full_name (account);
+            gnc_error_dialog (GTK_WINDOW (gnc_split_register_get_parent (reg)),
+                              placeholder, fullname);
+            g_free (fullname);
+            return NULL;
+        }
     }
 
     /* Be seeing you. */
diff --git a/gnucash/register/register-gnome/gnucash-sheet.c b/gnucash/register/register-gnome/gnucash-sheet.c
index abfaf6039..683455b6c 100644
--- a/gnucash/register/register-gnome/gnucash-sheet.c
+++ b/gnucash/register/register-gnome/gnucash-sheet.c
@@ -1855,7 +1855,12 @@ gnucash_sheet_key_press_event_internal (GtkWidget *widget, GdkEventKey *event)
 
     /* If that would leave the register, abort */
     if (abort_move)
+    {
+        // Make sure the sheet is the focus
+        if (!gtk_widget_has_focus(GTK_WIDGET (sheet)))
+            gtk_widget_grab_focus (GTK_WIDGET (sheet));
         return TRUE;
+    }
 
     /* Clear the saved selection for the new cell. */
     sheet->end_sel = sheet->start_sel;

commit aca8a734b0ae0f058dadb1ef3f18e81f987a5eb6
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Jul 16 13:34:13 2019 +0100

    Error when creating a new account on expanded transaction
    
    With the register in journal mode and you edit the account field so the
    'Account x does not exist, do you want to create it' dialogue is shown
    you get the following error...
    
    gboolean boolean_from_key(const Account*, const std::vector<std::
    __cxx11::basic_string<char> >&): assertion 'GNC_IS_ACCOUNT(acc)' failed
    
    This was tracked down to a refresh being triggered with the account
    dialogue so add a check for a valid account as part of the placeholder
    test.

diff --git a/gnucash/register/ledger-core/split-register.c b/gnucash/register/ledger-core/split-register.c
index 852c85cb7..9fc109927 100644
--- a/gnucash/register/ledger-core/split-register.c
+++ b/gnucash/register/ledger-core/split-register.c
@@ -1969,7 +1969,7 @@ gnc_split_register_get_account_by_name (SplitRegister *reg, BasicCell * bcell,
     g_free (account_name);
 
     /* See if the account (either old or new) is a placeholder. */
-    if (xaccAccountGetPlaceholder (account))
+    if (account && xaccAccountGetPlaceholder (account))
     {
         gnc_error_dialog (GTK_WINDOW (gnc_split_register_get_parent (reg)),
                           placeholder, name);

commit 69f76c63787d991605b8890a867674f54ec489a5
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Jul 16 13:30:01 2019 +0100

    Bug 797301/2 - Sub-account register not seeing changes to sub accounts.
    
    If you have a sub account register open and you create another account
    that is a descendant of the top account the transaction will not be
    seen as the query used for the sub account register does not include
    the new account. Add a check to 'refresh_handler' to check for the same
    number of sub accounts, if different recreate query.

diff --git a/gnucash/register/ledger-core/gnc-ledger-display.c b/gnucash/register/ledger-core/gnc-ledger-display.c
index c84c1f7cd..456048f1b 100644
--- a/gnucash/register/ledger-core/gnc-ledger-display.c
+++ b/gnucash/register/ledger-core/gnc-ledger-display.c
@@ -70,6 +70,8 @@ struct gnc_ledger_display
     GNCLedgerDisplayGetParent get_parent;
 
     gpointer user_data;
+    
+    gint number_of_subaccounts;
 
     gint component_id;
 };
@@ -87,9 +89,13 @@ gnc_ledger_display_internal (Account *lead_account, Query *q,
                              SplitRegisterStyle style,
                              gboolean use_double_line,
                              gboolean is_template);
+
 static void gnc_ledger_display_refresh_internal (GNCLedgerDisplay *ld,
-        GList *splits);
+                             GList *splits);
 
+static void gnc_ledger_display_make_query (GNCLedgerDisplay *ld,
+                             gint limit,
+                             SplitRegisterType type);
 
 /** Implementations *************************************************/
 
@@ -572,6 +578,21 @@ refresh_handler (GHashTable *changes, gpointer user_data)
         }
     }
 
+    /* if subaccount ledger, check to see if still the same number
+     *  of subaccounts, if not recreate the query. */
+    if (ld->ld_type == LD_SUBACCOUNT)
+    {
+        Account *leader = gnc_ledger_display_leader (ld);
+        GList *accounts = gnc_account_get_descendants (leader);
+
+        if (g_list_length (accounts) != ld->number_of_subaccounts)
+            gnc_ledger_display_make_query (ld,
+                               gnc_prefs_get_float(GNC_PREFS_GROUP_GENERAL_REGISTER, GNC_PREF_MAX_TRANS),
+                               gnc_get_reg_type (leader, ld->ld_type));
+
+        g_list_free (accounts);
+    }
+
     /* Its not clear if we should re-run the query, or if we should
      * just use qof_query_last_run().  Its possible that the dates
      * changed, requiring a full new query.  Similar considerations
@@ -647,8 +668,14 @@ gnc_ledger_display_make_query (GNCLedgerDisplay *ld,
 
     leader = gnc_ledger_display_leader (ld);
 
+    /* if this is a subaccount ledger, record the number of 
+     * subaccounts so we can determine if the query needs 
+     * recreating on a refresh. */
     if (ld->ld_type == LD_SUBACCOUNT)
+    {
         accounts = gnc_account_get_descendants (leader);
+        ld->number_of_subaccounts = g_list_length (accounts);
+    }
     else
         accounts = NULL;
 

commit b56203e06f6a87f17b362dd5a7ed1e1112fe3116
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Jul 16 13:29:18 2019 +0100

    Bug 797301/1 - Menu View->Refresh not refreshing register
    
    The Menu 'View->Refresh' only worked for the report page which reloaded
    the report so added functions for other pages as follows. The Register
    and Budget pages are reloaded with the remaining pages being redrawn
    with a call to gtk_widget_queue_draw.

diff --git a/gnucash/gnome/gnc-plugin-page-account-tree.c b/gnucash/gnome/gnc-plugin-page-account-tree.c
index 5f4ac0a83..36c7c81d7 100644
--- a/gnucash/gnome/gnc-plugin-page-account-tree.c
+++ b/gnucash/gnome/gnc-plugin-page-account-tree.c
@@ -146,6 +146,7 @@ static void gnc_plugin_page_account_tree_cmd_delete_account (GtkAction *action,
 static void gnc_plugin_page_account_tree_cmd_renumber_accounts (GtkAction *action, GncPluginPageAccountTree *page);
 static void gnc_plugin_page_account_tree_cmd_view_filter_by (GtkAction *action, GncPluginPageAccountTree *plugin_page);
 static void gnc_plugin_page_account_tree_cmd_reconcile (GtkAction *action, GncPluginPageAccountTree *page);
+static void gnc_plugin_page_account_tree_cmd_refresh (GtkAction *action, GncPluginPageAccountTree *page);
 static void gnc_plugin_page_account_tree_cmd_autoclear (GtkAction *action, GncPluginPageAccountTree *page);
 static void gnc_plugin_page_account_tree_cmd_transfer (GtkAction *action, GncPluginPageAccountTree *page);
 static void gnc_plugin_page_account_tree_cmd_stock_split (GtkAction *action, GncPluginPageAccountTree *page);
@@ -253,6 +254,11 @@ static GtkActionEntry gnc_plugin_page_account_tree_actions [] =
         "ViewFilterByAction", NULL, N_("_Filter By..."), NULL, NULL,
         G_CALLBACK (gnc_plugin_page_account_tree_cmd_view_filter_by)
     },
+    {
+        "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
+        N_("Refresh this window"),
+        G_CALLBACK (gnc_plugin_page_account_tree_cmd_refresh)
+    },
 
     /* Actions menu */
     {
@@ -1656,6 +1662,18 @@ gnc_plugin_page_account_tree_cmd_renumber_accounts (GtkAction *action,
     gnc_account_renumber_create_dialog(window, account);
 }
 
+static void
+gnc_plugin_page_account_tree_cmd_refresh (GtkAction *action,
+        GncPluginPageAccountTree *page)
+{
+    GncPluginPageAccountTreePrivate *priv;
+
+    g_return_if_fail(GNC_IS_PLUGIN_PAGE_ACCOUNT_TREE(page));
+
+    priv = GNC_PLUGIN_PAGE_ACCOUNT_TREE_GET_PRIVATE(page);
+    gtk_widget_queue_draw (priv->widget);
+}
+
 /*********************/
 
 static void
diff --git a/gnucash/gnome/gnc-plugin-page-budget.c b/gnucash/gnome/gnc-plugin-page-budget.c
index d47ae121c..5d785682e 100644
--- a/gnucash/gnome/gnc-plugin-page-budget.c
+++ b/gnucash/gnome/gnc-plugin-page-budget.c
@@ -117,6 +117,8 @@ static void gnc_plugin_page_budget_cmd_estimate_budget(
     GtkAction *action, GncPluginPageBudget *page);
 static void gnc_plugin_page_budget_cmd_allperiods_budget(
     GtkAction *action, GncPluginPageBudget *page);
+static void gnc_plugin_page_budget_cmd_refresh (
+    GtkAction *action, GncPluginPageBudget *page);
 
 static GtkActionEntry gnc_plugin_page_budget_actions [] =
 {
@@ -165,6 +167,11 @@ static GtkActionEntry gnc_plugin_page_budget_actions [] =
         "ViewFilterByAction", NULL, N_("_Filter By..."), NULL, NULL,
         G_CALLBACK (gnc_plugin_page_budget_cmd_view_filter_by)
     },
+    {
+        "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
+        N_("Refresh this window"),
+        G_CALLBACK (gnc_plugin_page_budget_cmd_refresh)
+    },
 
 };
 
@@ -1145,3 +1152,18 @@ gnc_plugin_page_budget_cmd_view_filter_by (GtkAction *action,
 
     LEAVE(" ");
 }
+
+static void
+gnc_plugin_page_budget_cmd_refresh (GtkAction *action,
+        GncPluginPageBudget *page)
+{
+    GncPluginPageBudgetPrivate *priv;
+
+    g_return_if_fail (GNC_IS_PLUGIN_PAGE_BUDGET(page));
+    ENTER("(action %p, page %p)", action, page);
+
+    priv = GNC_PLUGIN_PAGE_BUDGET_GET_PRIVATE(page);
+
+    gnc_budget_view_refresh (priv->budget_view);
+    LEAVE(" ");
+}
diff --git a/gnucash/gnome/gnc-plugin-page-invoice.c b/gnucash/gnome/gnc-plugin-page-invoice.c
index cbd4b8497..ba7bafb4c 100644
--- a/gnucash/gnome/gnc-plugin-page-invoice.c
+++ b/gnucash/gnome/gnc-plugin-page-invoice.c
@@ -69,6 +69,7 @@ static void gnc_plugin_page_invoice_cmd_edit (GtkAction *action, GncPluginPageIn
 static void gnc_plugin_page_invoice_cmd_duplicateInvoice (GtkAction *action, GncPluginPageInvoice *plugin_page);
 static void gnc_plugin_page_invoice_cmd_post (GtkAction *action, GncPluginPageInvoice *plugin_page);
 static void gnc_plugin_page_invoice_cmd_unpost (GtkAction *action, GncPluginPageInvoice *plugin_page);
+static void gnc_plugin_page_invoice_cmd_refresh (GtkAction *action, GncPluginPageInvoice *plugin_page);
 
 static void gnc_plugin_page_invoice_cmd_sort_changed (GtkAction *action,
         GtkRadioAction *current,
@@ -147,6 +148,13 @@ static GtkActionEntry gnc_plugin_page_invoice_actions [] =
         G_CALLBACK (gnc_plugin_page_invoice_cmd_unpost)
     },
 
+    /* View menu */
+    {
+        "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
+        N_("Refresh this window"),
+        G_CALLBACK (gnc_plugin_page_invoice_cmd_refresh)
+    },
+
     /* Actions menu */
     {
         "RecordEntryAction", "list-add", N_("_Enter"), NULL,
@@ -853,6 +861,20 @@ gnc_plugin_page_invoice_cmd_sort_changed (GtkAction *action,
     LEAVE(" ");
 }
 
+static void
+gnc_plugin_page_invoice_cmd_refresh (GtkAction *action,
+                                     GncPluginPageInvoice *plugin_page)
+{
+    GncPluginPageInvoicePrivate *priv;
+
+    g_return_if_fail(GNC_IS_PLUGIN_PAGE_INVOICE(plugin_page));
+
+    ENTER("(action %p, plugin_page %p)", action, plugin_page);
+    priv = GNC_PLUGIN_PAGE_INVOICE_GET_PRIVATE(plugin_page);
+
+    gtk_widget_queue_draw (priv->widget);
+    LEAVE(" ");
+}
 
 static void
 gnc_plugin_page_invoice_cmd_enter (GtkAction *action,
diff --git a/gnucash/gnome/gnc-plugin-page-owner-tree.c b/gnucash/gnome/gnc-plugin-page-owner-tree.c
index b58e94039..d4d2cbd57 100644
--- a/gnucash/gnome/gnc-plugin-page-owner-tree.c
+++ b/gnucash/gnome/gnc-plugin-page-owner-tree.c
@@ -123,6 +123,7 @@ static void gnc_plugin_page_owner_tree_cmd_edit_owner (GtkAction *action, GncPlu
 static void gnc_plugin_page_owner_tree_cmd_delete_owner (GtkAction *action, GncPluginPageOwnerTree *page);
 #endif
 static void gnc_plugin_page_owner_tree_cmd_view_filter_by (GtkAction *action, GncPluginPageOwnerTree *page);
+static void gnc_plugin_page_owner_tree_cmd_refresh (GtkAction *action, GncPluginPageOwnerTree *page);
 static void gnc_plugin_page_owner_tree_cmd_new_invoice (GtkAction *action, GncPluginPageOwnerTree *page);
 static void gnc_plugin_page_owner_tree_cmd_owners_report (GtkAction *action, GncPluginPageOwnerTree *plugin_page);
 static void gnc_plugin_page_owner_tree_cmd_owner_report (GtkAction *action, GncPluginPageOwnerTree *plugin_page);
@@ -181,6 +182,11 @@ static GtkActionEntry gnc_plugin_page_owner_tree_actions [] =
         "ViewFilterByAction", NULL, N_("_Filter By..."), NULL, NULL,
         G_CALLBACK (gnc_plugin_page_owner_tree_cmd_view_filter_by)
     },
+    {
+        "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
+        N_("Refresh this window"),
+        G_CALLBACK (gnc_plugin_page_owner_tree_cmd_refresh)
+    },
 
     /* Business menu */
     {
@@ -1150,6 +1156,17 @@ gnc_plugin_page_owner_tree_cmd_view_filter_by (GtkAction *action,
     LEAVE(" ");
 }
 
+static void
+gnc_plugin_page_owner_tree_cmd_refresh (GtkAction *action,
+        GncPluginPageOwnerTree *page)
+{
+    GncPluginPageOwnerTreePrivate *priv;
+
+    g_return_if_fail(GNC_IS_PLUGIN_PAGE_OWNER_TREE(page));
+
+    priv = GNC_PLUGIN_PAGE_OWNER_TREE_GET_PRIVATE(page);
+    gtk_widget_queue_draw (priv->widget);
+}
 
 static void
 gnc_plugin_page_owner_tree_cmd_new_invoice (GtkAction *action,
diff --git a/gnucash/gnome/gnc-plugin-page-register.c b/gnucash/gnome/gnc-plugin-page-register.c
index f199a7dab..452fb1226 100644
--- a/gnucash/gnome/gnc-plugin-page-register.c
+++ b/gnucash/gnome/gnc-plugin-page-register.c
@@ -175,6 +175,7 @@ static void gnc_plugin_page_register_cmd_reinitialize_transaction (GtkAction *ac
 static void gnc_plugin_page_register_cmd_expand_transaction (GtkToggleAction *action, GncPluginPageRegister *plugin_page);
 static void gnc_plugin_page_register_cmd_exchange_rate (GtkAction *action, GncPluginPageRegister *plugin_page);
 static void gnc_plugin_page_register_cmd_jump (GtkAction *action, GncPluginPageRegister *plugin_page);
+static void gnc_plugin_page_register_cmd_reload (GtkAction *action, GncPluginPageRegister *plugin_page);
 static void gnc_plugin_page_register_cmd_schedule (GtkAction *action, GncPluginPageRegister *plugin_page);
 static void gnc_plugin_page_register_cmd_scrub_all (GtkAction *action, GncPluginPageRegister *plugin_page);
 static void gnc_plugin_page_register_cmd_scrub_current (GtkAction *action, GncPluginPageRegister *plugin_page);
@@ -349,6 +350,11 @@ static GtkActionEntry gnc_plugin_page_register_actions [] =
         "ViewFilterByAction", NULL, N_("_Filter By..."), NULL, NULL,
         G_CALLBACK (gnc_plugin_page_register_cmd_view_filter_by)
     },
+    {
+        "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
+        N_("Refresh this window"),
+        G_CALLBACK (gnc_plugin_page_register_cmd_reload)
+    },
 
     /* Actions menu */
 
@@ -4043,6 +4049,29 @@ gnc_plugin_page_register_cmd_view_filter_by (GtkAction *action,
     LEAVE(" ");
 }
 
+static void
+gnc_plugin_page_register_cmd_reload (GtkAction *action, GncPluginPageRegister *plugin_page)
+{
+    GncPluginPageRegisterPrivate *priv;
+    SplitRegister *reg;
+
+    ENTER("(action %p, page %p)", action, plugin_page);
+
+    g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER (plugin_page));
+
+    priv = GNC_PLUGIN_PAGE_REGISTER_GET_PRIVATE (plugin_page);
+    reg = gnc_ledger_display_get_split_register (priv->ledger);
+
+    /* Check for trans being edited */
+    if (gnc_split_register_changed (reg))
+    {
+        LEAVE("register has pending edits");
+        return;
+    }
+    gnc_ledger_display_refresh (priv->ledger);
+    LEAVE(" ");
+}
+
 static void
 gnc_plugin_page_register_cmd_style_changed (GtkAction *action,
         GtkRadioAction *current,
diff --git a/gnucash/gnome/gnc-plugin-page-sx-list.c b/gnucash/gnome/gnc-plugin-page-sx-list.c
index ec4851dd2..10f1b9a27 100644
--- a/gnucash/gnome/gnc-plugin-page-sx-list.c
+++ b/gnucash/gnome/gnc-plugin-page-sx-list.c
@@ -127,6 +127,7 @@ static void gnc_plugin_page_sx_list_cmd_edit2(GtkAction *action, GncPluginPageSx
 /*################## Added for Reg2 #################*/
 #endif
 static void gnc_plugin_page_sx_list_cmd_delete(GtkAction *action, GncPluginPageSxList *page);
+static void gnc_plugin_page_sx_list_cmd_refresh (GtkAction *action, GncPluginPageSxList *page);
 
 /* Command callbacks */
 static GtkActionEntry gnc_plugin_page_sx_list_actions [] =
@@ -160,6 +161,13 @@ static GtkActionEntry gnc_plugin_page_sx_list_actions [] =
         "SxListDeleteAction", GNC_ICON_DELETE_ACCOUNT, N_("_Delete"), NULL,
         N_("Delete the selected scheduled transaction"), G_CALLBACK(gnc_plugin_page_sx_list_cmd_delete)
     },
+
+    /* View menu */
+
+    {
+        "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
+        N_("Refresh this window"), G_CALLBACK (gnc_plugin_page_sx_list_cmd_refresh)
+    },
 };
 /** The number of actions provided by this plugin. */
 static guint gnc_plugin_page_sx_list_n_actions = G_N_ELEMENTS (gnc_plugin_page_sx_list_actions);
@@ -659,6 +667,17 @@ gnc_plugin_page_sx_list_cmd_new2 (GtkAction *action, GncPluginPageSxList *page)
 /*################## Added for Reg2 #################*/
 #endif
 
+static void
+gnc_plugin_page_sx_list_cmd_refresh (GtkAction *action, GncPluginPageSxList *page)
+{
+    GncPluginPageSxListPrivate *priv;
+
+    g_return_if_fail (GNC_IS_PLUGIN_PAGE_SX_LIST(page));
+
+    priv = GNC_PLUGIN_PAGE_SX_LIST_GET_PRIVATE(page);
+    gtk_widget_queue_draw (priv->widget);
+}
+
 static void
 _edit_sx(gpointer data, gpointer user_data)
 {

commit aadb3d14629a975c67353c0d3d8211ef03efacd8
Author: Mike Evans <mikee at saxicola.co.uk>
Date:   Mon Jul 29 14:01:31 2019 +0100

    Bug 797085 - Import Bills & Invoices – headers not translated.
    
    Make strings translatable.
    
    Translators: This adds 22 new translatable strings.

diff --git a/gnucash/import-export/bi-import/dialog-bi-import-gui.c b/gnucash/import-export/bi-import/dialog-bi-import-gui.c
index ef35c74cd..6221c4622 100644
--- a/gnucash/import-export/bi-import/dialog-bi-import-gui.c
+++ b/gnucash/import-export/bi-import/dialog-bi-import-gui.c
@@ -137,30 +137,30 @@ gnc_plugin_bi_import_showGUI (GtkWindow *parent)
   column = gtk_tree_view_column_new_with_attributes (description, renderer, "text", column_id, NULL); \
   gtk_tree_view_column_set_resizable (column, TRUE); \
   gtk_tree_view_append_column (GTK_TREE_VIEW (gui->tree_view), column);
-    CREATE_COLUMN ("id", ID);
-    CREATE_COLUMN ("date__opened", DATE_OPENED);
-    CREATE_COLUMN ("owner__id", OWNER_ID);
-    CREATE_COLUMN ("billing__id", BILLING_ID);
-    CREATE_COLUMN ("notes", NOTES);
-
-    CREATE_COLUMN ("date", DATE);
-    CREATE_COLUMN ("desc", DESC);
-    CREATE_COLUMN ("action", ACTION);
-    CREATE_COLUMN ("account", ACCOUNT);
-    CREATE_COLUMN ("quantity", QUANTITY);
-    CREATE_COLUMN ("price", PRICE);
-    CREATE_COLUMN ("disc__type", DISC_TYPE);
-    CREATE_COLUMN ("disc__how", DISC_HOW);
-    CREATE_COLUMN ("discount", DISCOUNT);
-    CREATE_COLUMN ("taxable", TAXABLE);
-    CREATE_COLUMN ("taxincluded", TAXINCLUDED);
-    CREATE_COLUMN ("tax__table", TAX_TABLE);
-
-    CREATE_COLUMN ("date__posted", DATE_POSTED);
-    CREATE_COLUMN ("due__date", DUE_DATE);
-    CREATE_COLUMN ("account__posted", ACCOUNT_POSTED);
-    CREATE_COLUMN ("memo__posted", MEMO_POSTED);
-    CREATE_COLUMN ("accu__splits", ACCU_SPLITS);
+    CREATE_COLUMN (_("id"), ID);
+    CREATE_COLUMN (_("date__opened"), DATE_OPENED);
+    CREATE_COLUMN (_("owner__id"), OWNER_ID);
+    CREATE_COLUMN (_("billing__id"), BILLING_ID);
+    CREATE_COLUMN (_("notes"), NOTES);
+
+    CREATE_COLUMN (_("date"), DATE);
+    CREATE_COLUMN (_("desc"), DESC);
+    CREATE_COLUMN (_("action"), ACTION);
+    CREATE_COLUMN (_("account"), ACCOUNT);
+    CREATE_COLUMN (_("quantity"), QUANTITY);
+    CREATE_COLUMN (_("price"), PRICE);
+    CREATE_COLUMN (_("disc__type"), DISC_TYPE);
+    CREATE_COLUMN (_("disc__how"), DISC_HOW);
+    CREATE_COLUMN (_("discount"), DISCOUNT);
+    CREATE_COLUMN (_("taxable"), TAXABLE);
+    CREATE_COLUMN (_("taxincluded"), TAXINCLUDED);
+    CREATE_COLUMN (_("tax__table"), TAX_TABLE);
+
+    CREATE_COLUMN (_("date__posted"), DATE_POSTED);
+    CREATE_COLUMN (_("due__date"), DUE_DATE);
+    CREATE_COLUMN (_("account__posted"), ACCOUNT_POSTED);
+    CREATE_COLUMN (_("memo__posted"), MEMO_POSTED);
+    CREATE_COLUMN (_("accu__splits"), ACCU_SPLITS);
 
     gui->component_id = gnc_register_gui_component ("dialog-bi-import-gui",
                         NULL,

commit 5de4b27b2510d0ec8fcc859adf172018fdc89c1e
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 14:37:19 2019 +0800

    [balsheet-eg] dedupe functions

diff --git a/gnucash/report/business-reports/balsheet-eg.scm b/gnucash/report/business-reports/balsheet-eg.scm
index d1b5cf2ae..ccec8c5ae 100644
--- a/gnucash/report/business-reports/balsheet-eg.scm
+++ b/gnucash/report/business-reports/balsheet-eg.scm
@@ -37,8 +37,8 @@
 (use-modules (gnucash gettext))
 (use-modules (gnucash report eguile-gnc))
 (use-modules (gnucash report eguile-utilities))
+(use-modules (gnucash report eguile-html-utilities))
 
-(use-modules (ice-9 regex))  ; for regular expressions
 (use-modules (ice-9 local-eval))  ; for the-environment
 (use-modules (srfi srfi-13)) ; for extra string functions
 
@@ -47,38 +47,6 @@
 
 (define debugging? #f)
 
-;;; these could go into a separate module..........
-;;;
-;; Useful routines to use in the template
-(define (escape-html s1)
-  ;; convert string s1 to escape HTML special characters < > and &
-  ;; i.e. convert them to < > and & respectively.
-  ;; Maybe there's a way to do this in one go... (but order is important)
-  (set! s1 (regexp-substitute/global #f "&" s1 'pre "&" 'post))
-  (set! s1 (regexp-substitute/global #f "<" s1 'pre "<" 'post))
-  (regexp-substitute/global #f ">" s1 'pre ">" 'post))
-
-(define (nl->br str)
-  ;; replace newlines with <br>
-  (regexp-substitute/global #f "\n" str 'pre "<br />" 'post))
-
-(define (nbsp str)
-  ;; replace spaces with   (non-breaking spaces)
-  ;; (yes, I know <nobr> is non-standard, but webkit splits e.g. "-£40.00" between
-  ;; the '-' and the '£' without it.)
-  (string-append "<nobr>" (regexp-substitute/global #f " " str 'pre " " 'post) "</nobr>"))
-
-(define (dump x) (escape-html (object->string x)))
-(define (ddump x) (display (dump x)))
-
-(define (string-repeat s n)
-  ;; return a string made of n copies of string s
-  ;; (there's probably a better way)
-  (let ((s2 ""))
-    (do ((i 1 (1+ i))) ((> i n))
-      (set! s2 (string-append s2 s)))
-    s2))
-
 (define (debug . args)
   (if debugging?
     (for arg in args do
@@ -92,28 +60,6 @@
   (display cols)
   (display "\"> </td></tr>\n"))
 
-(define (empty-cells n)
-  ;; Display n empty table cells
-  (display (string-repeat "<td class=\"empty\"></td>" n)))
-
-(define (indent-cells n)
-  ;; Display n empty table cells with width attribute for indenting
-  ;; (the  s are just there in case CSS isn't working)
-  (display (string-repeat "<td min-width=\"32\" class=\"indent\">  </td>" n)))
-
-;; 'Safe' versions of cdr and cadr that don't crash
-;; if the list is empty  (is there a better way?)
-(define (safe-cdr l)
-  (if (null? l)
-    '()
-    (cdr l)))
-(define (safe-cadr l)
-  (if (null? l)
-    '()
-    (if (null? (cdr l))
-      '()
-      (cadr l))))
-
 (define (add-to-cc cc com num neg?)
   ; add a numeric and commodity to a commodity-collector,
   ; changing sign if required

commit 8cd7c6f7556c62c561c2b13820206811a5ddc0f7
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 22:13:38 2019 +0800

    [taxinvoice-eg] fix html
    
    <nobr> has never been standard. Don't need it because (nbsp ...) will
    insert <span white-space=nowrap> tags.

diff --git a/gnucash/report/business-reports/taxinvoice.eguile.scm b/gnucash/report/business-reports/taxinvoice.eguile.scm
index ed82b336c..9a8036273 100644
--- a/gnucash/report/business-reports/taxinvoice.eguile.scm
+++ b/gnucash/report/business-reports/taxinvoice.eguile.scm
@@ -341,7 +341,7 @@
     ?>
     <tr valign="top">
       <?scm (if opt-col-date (begin ?>
-      <td align="center" ><nobr><?scm:d (nbsp (qof-print-date (gncEntryGetDate entry))) ?></nobr></td>
+      <td align="center" ><?scm:d (nbsp (qof-print-date (gncEntryGetDate entry))) ?></td>
       <?scm )) ?>
       <td align="left"><?scm:d (gncEntryGetDescription entry) ?></td>
       <!-- td align="left">< ?scm:d (gncEntryGetNotes entry) ?></td -->

commit 326927215c8481f5b9024e1ec9fe121c2bff7d0f
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 14:36:22 2019 +0800

    [eguile-html-utilities] dedupe, fix html
    
    * prefer srfi-13 over regex
    * instead of <nobr> use <span style="white-space:nowrap">
    * reuse functions defined in eguile-gnc and eguile-utilities
    * compact functions
    * move make-regexp toplevel to ensure one compilation

diff --git a/gnucash/report/report-system/eguile-html-utilities.scm b/gnucash/report/report-system/eguile-html-utilities.scm
index 8a7030bab..d123f6109 100644
--- a/gnucash/report/report-system/eguile-html-utilities.scm
+++ b/gnucash/report/report-system/eguile-html-utilities.scm
@@ -32,29 +32,24 @@
 (gnc:module-load "gnucash/report/report-system" 0)
 (gnc:module-load "gnucash/app-utils" 0)
 
+(use-modules (gnucash report eguile-gnc))
 (use-modules (ice-9 regex))  ; for regular expressions
 (use-modules (srfi srfi-13)) ; for extra string functions
 
-(define-public (escape-html s1) 
-  ;; Convert string s1 to escape HTML special characters < > and &
-  ;; i.e. convert them to < > and & respectively.
-  ;; Maybe there's a way to do this in one go... (but order is important)
-  (set! s1 (regexp-substitute/global #f "&" s1 'pre "&" 'post))
-  (set! s1 (regexp-substitute/global #f "<" s1 'pre "<" 'post))
-  (regexp-substitute/global #f ">" s1 'pre ">" 'post))
+(define (string-repeat s n)
+  ;; return a string made of n copies of string s
+  (string-join (make-list n s) ""))
 
 (define-public (nl->br str)
   ;; Replace newlines with <br>
-  (regexp-substitute/global #f "\n" str 'pre "<br>" 'post))
+  (string-substitute-alist str '((#\newline . "<br/>"))))
 
 (define-public (nbsp str)
   ;; Replace spaces with   (non-breaking spaces)
-  ;; (yes, I know <nobr> is non-standard, but webkit splits e.g. "-£40.00" between
-  ;; the '-' and the '£' without it.)
-  (string-append 
-    "<nobr>" 
-    (regexp-substitute/global #f " " str 'pre " " 'post) 
-    "</nobr>"))
+  (string-append
+   "<span style=\"white-space:nowrap;\">"
+   (string-substitute-alist str '((#\space . " ")))
+   "</span>"))
 
 (define-public (empty-cells n)
   ;; Display n empty table cells
@@ -63,7 +58,8 @@
 (define-public (indent-cells n)
   ;; Display n empty table cells with width attribute for indenting
   ;; (the  s are just there in case CSS isn't working)
-  (display (string-repeat "<td min-width=\"32\" class=\"indent\">  </td>" n)))
+  (display
+   (string-repeat "<td min-width=\"32\" class=\"indent\">  </td>" n)))
 
 (define-public (negstyle item)
   ;; apply styling for negative amounts
@@ -86,9 +82,13 @@
 (define-public (display-comm-coll-total comm-coll negative?)
   ;; Display the total(s) of a commodity collector as HTML
   (for-each
-    (lambda (pair)
-      (display (nbsp (gnc:monetary->string pair))))
-    (comm-coll 'format gnc:make-gnc-monetary negative?)))
+   (lambda (pair)
+     (display (nbsp (gnc:monetary->string pair))))
+   (comm-coll 'format gnc:make-gnc-monetary negative?)))
+
+;; (thanks to Peter Brett for this regexp and the use of match:prefix)
+(define fontre
+  (make-regexp "([[:space:]]+(bold|semi-bold|book|regular|medium|light))?([[:space:]]+(normal|roman|italic|oblique))?([[:space:]]+(condensed))?[[:space:]]+([[:digit:]]+)" regexp/icase))
 
 (define-public (font-name-to-style-info font-name)
   ;;; Convert a font name as return by a font option to CSS format.
@@ -98,31 +98,28 @@
          (font-weight "normal")
          (font-style  "normal")
          (font-size   "medium")
-         (match "")
-         ; (thanks to Peter Brett for this regexp and the use of match:prefix)
-         (fontre (make-regexp "([[:space:]]+(bold|semi-bold|book|regular|medium|light))?([[:space:]]+(normal|roman|italic|oblique))?([[:space:]]+(condensed))?[[:space:]]+([[:digit:]]+)" regexp/icase))
          (match (regexp-exec fontre font-name)))
-    (if match
-      (begin
-        ; font name parsed OK -- assemble the bits for CSS
-        (set! font-family (match:prefix match))
-        (if (match:substring match 2)
-          ; weight given -- some need translating
+    (when match
+      ;; font name parsed OK -- assemble the bits for CSS
+      (set! font-family (match:prefix match))
+      (if (match:substring match 2)
+          ;; weight given -- some need translating
           (let ((weight (match:substring match 2)))
             (cond
-              ((string-ci=? weight "bold")      (set! font-weight "bold"))
-              ((string-ci=? weight "semi-bold") (set! font-weight "600"))
-              ((string-ci=? weight "light")     (set! font-weight "200")))))
-        (if (match:substring match 4)
-          ; style 
+             ((string-ci=? weight "bold")      (set! font-weight "bold"))
+             ((string-ci=? weight "semi-bold") (set! font-weight "600"))
+             ((string-ci=? weight "light")     (set! font-weight "200")))))
+      (if (match:substring match 4)
+          ;; style
           (let ((style (match:substring match 4)))
             (cond
-              ((string-ci=? style "italic")  (set! font-style "italic"))
-              ((string-ci=? style "oblique") (set! font-style "oblique")))))
-        ; ('condensed' is ignored)
-        (if (match:substring match 7)
-          ; size is in points
-          (set! font-size (string-append (match:substring match 7) "pt")))))
-    ; construct the result (the order of these is important)
-    (string-append "font: " font-weight " " font-style " " font-size " \"" font-family "\";")))
+             ((string-ci=? style "italic")  (set! font-style "italic"))
+             ((string-ci=? style "oblique") (set! font-style "oblique")))))
+      ;; ('condensed' is ignored)
+      (if (match:substring match 7)
+          ;; size is in points
+          (set! font-size (string-append (match:substring match 7) "pt"))))
+    ;; construct the result (the order of these is important)
+    (string-append "font: " font-weight " " font-style
+                   " " font-size " \"" font-family "\";")))
 

commit c81e9354f742ed9bd5b5d2035e0feae6e6642ebe
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 14:30:58 2019 +0800

    [eguile-gnc] fix whitespace

diff --git a/gnucash/report/report-system/eguile-gnc.scm b/gnucash/report/report-system/eguile-gnc.scm
index 977a4cce7..1731ca6ae 100644
--- a/gnucash/report/report-system/eguile-gnc.scm
+++ b/gnucash/report/report-system/eguile-gnc.scm
@@ -134,44 +134,40 @@
   ;; display either code or text
   (define (display-it t code?)
     (if code?
-      (display t)
-      (display-text t)))  
+        (display t)
+        (display-text t)))
 
   (define stop textstop)    ; text to output at end of current section
 
   ;; switch between code and text modes
   (define (switch-mode code? dmodifier?)
     (display stop)
-    (if code?
-      (begin ; code mode to text mode
-        (display textstart)
-        (set! stop textstop))
-      (begin ; text mode to code mode
-        (if dmodifier?
-          (begin
-            (display dcodestart)
-            (set! stop dcodestop))
-          (begin
-            (display codestart)
-            (set! stop codestop))))))
+    (cond
+     (code? (display textstart)
+            (set! stop textstop))
+     (dmodifier? (display dcodestart)
+                 (set! stop dcodestop))
+     (else (display codestart)
+           (set! stop codestop))))
 
   ;; recursively process input stream
   (define (loop inp needle other code? line)
-    (if (equal? line "")
+    (when (string-null? line)
       (set! line (read-line inp 'concat)))
-    (if (not (eof-object? line)) 
-      (let ((match (regexp-exec needle line)))
-        (if match
-          (let ((dmodifier? #f))
-            (display-it (match:prefix match) code?)
-            (if (not code?)
-              ; switching from text to code -- check for modifier
-              (set! dmodifier? (match:substring match 1)))
-            (switch-mode code? dmodifier?)
-            (loop inp other needle (not code?) (match:suffix match)))
-          (begin    ; no match - output whole line and continue
-            (display-it line code?)
-            (loop inp needle other code? ""))))))
+    (unless (eof-object? line)
+      (cond
+       ((regexp-exec needle line)
+        => (lambda (rmatch)
+             (let ((dmodifier? #f))
+               (display-it (match:prefix rmatch) code?)
+               (unless code?
+                 ;; switching from text to code -- check for modifier
+                 (set! dmodifier? (match:substring rmatch 1)))
+               (switch-mode code? dmodifier?)
+               (loop inp other needle (not code?) (match:suffix rmatch)))))
+       (else    ; no match - output whole line and continue
+        (display-it line code?)
+        (loop inp needle other code? "")))))
 
   (display textstart)
   (loop (current-input-port) startre endre #f "")
@@ -182,7 +178,7 @@
 ;; Evaluate input containing Scheme code, trapping errors
 ;; e.g. (display "Text ")(display (+ x 2))(display ".") -> Text 42.
 ;; Parameters:
-;;   env  - environment in which to do the evaluation; 
+;;   env  - environment in which to do the evaluation;
 ;;          if #f, (the-environment) will be used
 (define (script->output env)
   ; Placeholder for the normal stack and error stack in case of an error
@@ -195,7 +191,7 @@
 	     ; Capture the current stack, so we can detect from where we
 	     ; need to display the stack trace
 	     (set! good-stack (make-stack #t))
-             (local-eval s-expression (or env (the-environment))) 
+             (local-eval s-expression (or env (the-environment)))
              (set! s-expression (read)))))
 
   ; Error handler to display any errors while evaluating the template
@@ -221,7 +217,10 @@
 		   (error-length (stack-length error-stack)))
 	      ; Show the backtrace. Remove one extra from the "first"
 	      ; argument, since that is an index, not a count.
-	      (display-backtrace error-stack (current-output-port) (- (- error-length remove-top) 1) (- (- error-length remove-top) remove-bottom)))
+	      (display-backtrace error-stack
+                                 (current-output-port)
+                                 (- (- error-length remove-top) 1)
+                                 (- (- error-length remove-top) remove-bottom)))
     (display "</pre><br>"))
 
   ; This handler will be called by catch before unwinding the
@@ -249,15 +248,18 @@
 
 ;; Process a template file and return the result as a string
 (define (eguile-file-to-string infile environment)
-  (if (not (access? infile R_OK))  
-    (format #f (_ "Template file \"~a\" can not be read") infile)
-    (let ((script (with-input-from-file
-                    infile
-                    (lambda () (with-output-to-string template->script)))))
+  (cond
+   ((not (access? infile R_OK))
+    (format #f (_ "Template file \"~a\" can not be read") infile))
+   (else
+    (let ((script (with-input-from-file infile
+                    (lambda ()
+                      (with-output-to-string template->script)))))
       (with-output-to-string
-        (lambda () (with-input-from-string 
-                     script
-                     (lambda () (script->output environment))))))))
+        (lambda ()
+          (with-input-from-string script
+            (lambda ()
+              (script->output environment)))))))))
 
 (export eguile-file-to-string)
 

commit 955a5651d8b18db4b096fbec0f98a891959d4c3e
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 14:30:30 2019 +0800

    [eguile-gnc] use string-for-each instead of regex

diff --git a/gnucash/report/report-system/eguile-gnc.scm b/gnucash/report/report-system/eguile-gnc.scm
index 9a9e130c8..977a4cce7 100644
--- a/gnucash/report/report-system/eguile-gnc.scm
+++ b/gnucash/report/report-system/eguile-gnc.scm
@@ -88,17 +88,24 @@
 (use-modules (ice-9 local-eval))  ; for the-environment
 (use-modules (gnucash app-utils)) ; for _
 
+(define-public (string-substitute-alist str sub-alist)
+  (with-output-to-string
+    (lambda ()
+      (string-for-each
+       (lambda (c)
+         (display
+          (or (assv-ref sub-alist c)
+              c)))
+       str))))
+
 ;; This is needed for displaying error messages -- note that it assumes that
 ;; the output is HTML, which is a pity, because otherwise this module is
 ;; non-specific -- it is designed to output a mixture of Guile and any other
 ;; sort of text.  Oh well.
-(define (escape-html s1) 
-  ;; convert string s1 to escape HTML special characters < > and &
-  ;; i.e. convert them to < > and & respectively.
-  ;; Maybe there's a way to do this in one go... (but order is important)
-  (set! s1 (regexp-substitute/global #f "&" s1 'pre "&" 'post))
-  (set! s1 (regexp-substitute/global #f "<" s1 'pre "<" 'post))
-  (regexp-substitute/global #f ">" s1 'pre ">" 'post))
+(define-public (escape-html s1)
+  (string-substitute-alist s1 '((#\< . "<")
+                                (#\> . ">")
+                                (#\& . "&"))))
 
 ;; regexps used to find start and end of code segments
 (define startre (make-regexp "<\\?scm(:d)?[[:space:]]"))

commit e506b7c3325f09e84c1e5d9519e551cc49943535
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 13:36:22 2019 +0800

    [eguile-utilities] compact functions
    
    * whitespace
    * move string-repeat to eguile-html-utilities where it's used

diff --git a/gnucash/report/report-system/eguile-utilities.scm b/gnucash/report/report-system/eguile-utilities.scm
index 25a91d860..0e536212f 100644
--- a/gnucash/report/report-system/eguile-utilities.scm
+++ b/gnucash/report/report-system/eguile-utilities.scm
@@ -33,7 +33,6 @@
 (gnc:module-load "gnucash/report/report-system" 0)
 (gnc:module-load "gnucash/app-utils" 0)
 
-
 (define-public (fmtnumber n)
   ;; Format a number (integer or real) into something printable
   (number->string (if (integer? n) 
@@ -46,28 +45,18 @@
 
 (define-public (gnc-monetary-neg? monetary)
   ; return true if the monetary value is negative
-  (gnc-numeric-negative-p (gnc:gnc-monetary-amount monetary)))
-
-(define-public (string-repeat s n)
-  ;; return a string made of n copies of string s
-  ;; (there's probably a better way)
-  (let ((s2 ""))
-    (do ((i 1 (1+ i))) ((> i n))
-      (set! s2 (string-append s2 s)))
-    s2))
+  (negative? (gnc:gnc-monetary-amount monetary)))
 
 ;; 'Safe' versions of cdr and cadr that don't crash
 ;; if the list is empty  (is there a better way?)
 (define-public (safe-cdr l)
-  (if (null? l)
-    '()
-    (cdr l)))
+  (if (null? l) '()
+      (cdr l)))
 (define-public (safe-cadr l)
-  (if (null? l)
-    '()
-    (if (null? (cdr l))
-      '()
-      (cadr l))))
+  (cond
+   ((null? l) '())
+   ((null? (cdr l)) '())
+   (else (cadr l))))
 
 (define-public (find-file fname)
   ;; Find the file 'fname', and return its full path.
@@ -75,40 +64,27 @@
   ;; Then look in Gnucash's standard report directory.
   ;; If no file is found, returns just 'fname' for use in error messages.
   ;; Note: this has been tested on Linux and Windows Vista so far...
-  (let* ((userpath (gnc-build-userdata-path fname))
-         (syspath  (gnc-build-report-path fname)))
-    ; make sure there's a trailing delimiter
-      (if (access? userpath R_OK)
-        userpath
-        (if (access? syspath R_OK)
-          syspath
-          fname))))
+  (let ((userpath (gnc-build-userdata-path fname))
+        (syspath  (gnc-build-report-path fname)))
+    ;; make sure there's a trailing delimiter
+    (cond
+     ((access? userpath R_OK) userpath)
+     ((access? syspath R_OK) syspath)
+     (else fname))))
 
 ; Define syntax for more readable for loops (the built-in for-each requires an
 ; explicit lambda and has the list expression all the way at the end).
-(define-syntax for
-  (syntax-rules (for in => do hash)
-		; Multiple variables and equal number of lists (in
-		; parenthesis). e.g.:
-		;
-		;   (for (a b) in (lsta lstb) do (display (+ a b)))
-		;
-		; Note that this template must be defined before the
-		; next one, since the template are evaluated in-order.
-                ((for (<var> ...) in (<list> ...) do <expr> ...)
-                 (for-each (lambda (<var> ...) <expr> ...) <list> ...))
-		; Single variable and list. e.g.:
-		;
-		; (for a in lst do (display a))
-                ((for <var> in <list> do <expr> ...)
-                 (for-each (lambda (<var>) <expr> ...) <list>))
-		; Iterate over key & values in a hash. e.g.:
-		;
-		; (for key => value in hash do (display (* key value)))
-                ((for <key> => <value> in <hash> do <expr> ...)
-		 ; We use fold to iterate over the hash (instead of
-		 ; hash-for-each, since that is not present in guile
-		 ; 1.6).
-                 (hash-fold (lambda (<key> <value> accum) (begin <expr> ... accum)) *unspecified* <hash>))
-                ))
 (export for)
+(define-syntax for
+  (syntax-rules (for in do)
+    ;; Multiple variables and equal number of lists (in
+    ;; parenthesis). e.g.:
+    ;;   (for (a b) in (lsta lstb) do (display (+ a b)))
+    ;; Note that this template must be defined before the
+    ;; next one, since the template are evaluated in-order.
+    ((for (<var> ...) in (<list> ...) do <expr> ...)
+     (for-each (lambda (<var> ...) <expr> ...) <list> ...))
+
+    ;; Single variable and list. e.g.: (for a in lst do (display a))
+    ((for <var> in <list> do <expr> ...)
+     (for-each (lambda (<var>) <expr> ...) <list>))))

commit e3a695d0d4c565e55be5d8086e6e7494d8f4c4a8
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 19:56:45 2019 +0800

    [qif-imp/string] fix argument order for string-delete
    
    Guile formerly used the wrong argument order for string-delete and
    string-filter. Fix to correct order.
    
    See bug report http://savannah.gnu.org/bugs/?31681

diff --git a/gnucash/import-export/qif-imp/string.scm b/gnucash/import-export/qif-imp/string.scm
index f11cddf89..44bea5902 100644
--- a/gnucash/import-export/qif-imp/string.scm
+++ b/gnucash/import-export/qif-imp/string.scm
@@ -98,7 +98,7 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define-public (gnc:string-delete-chars s chars)
-  (string-delete s (lambda (c) (string-index chars c))))
+  (string-delete (lambda (c) (string-index chars c)) s))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

commit e8a41bbf5480d3beec08034c422a51a25cc7122b
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 11:23:10 2019 +0800

    [options] compact lookup-option

diff --git a/libgnucash/app-utils/options.scm b/libgnucash/app-utils/options.scm
index 7c1622566..499da99bc 100644
--- a/libgnucash/app-utils/options.scm
+++ b/libgnucash/app-utils/options.scm
@@ -1653,70 +1653,68 @@ the option '~a'."))
 
   (define callback-hash (make-hash-table 23))
   (define last-callback-id 0)
+  (define new-names-alist
+    '(("Accounts to include" #f "Accounts")
+      ("Exclude transactions between selected accounts?" #f
+       "Exclude transactions between selected accounts")
+      ("Filter Accounts" #f "Filter By...")
+      ("Flatten list to depth limit?" #f "Flatten list to depth limit")
+      ("From" #f "Start Date")
+      ("Report Accounts" #f "Accounts")
+      ("Report Currency" #f "Report's currency")
+      ("Show Account Code?" #f "Show Account Code")
+      ("Show Full Account Name?" #f "Show Full Account Name")
+      ("Show Multi-currency Totals?" #f "Show Multi-currency Totals")
+      ("Show zero balance items?" #f "Show zero balance items")
+      ("Sign Reverses?" #f "Sign Reverses")
+      ("To" #f "End Date")
+      ("Charge Type" #f "Action") ;easy-invoice.scm, renamed June 2018
+      ;; the following 4 options in income-gst-statement.scm renamed Dec 2018
+      ("Individual income columns" #f "Individual sales columns")
+      ("Individual expense columns" #f "Individual purchases columns")
+      ("Remittance amount" #f "Gross Balance")
+      ("Net Income" #f "Net Balance")
+      ;; transaction.scm:
+      ("Use Full Account Name?" #f "Use Full Account Name")
+      ("Use Full Other Account Name?" #f "Use Full Other Account Name")
+      ("Void Transactions?" "Filter" "Void Transactions")
+      ("Void Transactions" "Filter" "Void Transactions")
+      ("Account Substring" "Filter" "Account Name Filter")
+      ;; invoice.scm, renamed November 2018
+      ("Individual Taxes" #f "Use Detailed Tax Summary")
+      ))
 
   (define (lookup-option section name)
     (let ((section-hash (hash-ref option-hash section)))
-      (if section-hash
-          (let ((option-hash (hash-ref section-hash name)))
-            (if option-hash
-                option-hash
-                ;; Option name was not found. Perhaps it was renamed ?
-                ;; Let's try to map it to a known new name.
-                ;; This list will try match names - if one is found
-                ;; the next item will describe a pair.
-                ;; (cons newsection newname)
-                ;; If newsection is #f then reuse previous section name.
-                ;;
-                ;; Please note the rename list currently supports renaming
-                ;; individual option names, or individual option names moved
-                ;; to another section. It does not currently support renaming
-                ;; whole sections.
-                (let* ((new-names-list (list
-                                        "Accounts to include" (cons #f "Accounts")
-                                        "Exclude transactions between selected accounts?" (cons #f "Exclude transactions between selected accounts")
-                                        "Filter Accounts" (cons #f "Filter By...")
-                                        "Flatten list to depth limit?" (cons #f "Flatten list to depth limit")
-                                        "From" (cons #f "Start Date")
-                                        "Report Accounts" (cons #f "Accounts")
-                                        "Report Currency" (cons #f "Report's currency")
-                                        "Show Account Code?" (cons #f "Show Account Code")
-                                        "Show Full Account Name?" (cons #f "Show Full Account Name")
-                                        "Show Multi-currency Totals?" (cons #f "Show Multi-currency Totals")
-                                        "Show zero balance items?" (cons #f "Show zero balance items")
-                                        "Sign Reverses?" (cons #f "Sign Reverses")
-                                        "To" (cons #f "End Date")
-                                        "Charge Type" (cons #f "Action") ;easy-invoice.scm, renamed June 2018
-                                        ;; the following 4 options in income-gst-statement.scm renamed Dec 2018
-                                        "Individual income columns" (cons #f "Individual sales columns")
-                                        "Individual expense columns" (cons #f "Individual purchases columns")
-                                        "Remittance amount" (cons #f "Gross Balance")
-                                        "Net Income" (cons #f "Net Balance")
-                                        ;; transaction.scm:
-                                        "Use Full Account Name?" (cons #f "Use Full Account Name")
-                                        "Use Full Other Account Name?" (cons #f "Use Full Other Account Name")
-                                        "Void Transactions?" (cons "Filter" "Void Transactions")
-                                        "Void Transactions" (cons "Filter" "Void Transactions")
-                                        "Account Substring" (cons "Filter" "Account Name Filter")
-                                        ;; invoice.scm, renamed November 2018
-                                        "Individual Taxes" (cons "#f" "Use Detailed Tax Summary")
-                                        ))
-                       (name-match (member name new-names-list)))
-
-                  (and name-match
-                       (let ((new-section (car (cadr name-match)))
-                             (new-name (cdr (cadr name-match))))
-                         (gnc:debug
-                          (format #f "option ~s/~s has been renamed to ~s/~s\n"
-                                  section name new-section new-name))
-                         ;; compare if new-section name exists.
-                         (if new-section
-                             ;; if so, if it's different to current section name
-                             ;; then try new section name
-                             (and (not (string=? new-section section))
-                                  (lookup-option new-section new-name))
-                             ;; else reuse section-name with new-name
-                             (lookup-option section new-name)))))))
-          #f)))
+      (and section-hash
+           (or (hash-ref section-hash name)
+               ;; Option name was not found. Perhaps it was renamed?
+               ;; Let's try to map to a known new name.  The alist
+               ;; new-names-alist will try match names - car is the old
+               ;; name, cdr is the 2-element list describing
+               ;; newsection newname. If newsection is #f then reuse
+               ;; previous section name. Please note the rename list
+               ;; currently supports renaming individual option names,
+               ;; or individual option names moved to another
+               ;; section. It does not currently support renaming
+               ;; whole sections.
+               (let ((name-match (assoc-ref new-names-alist name)))
+                 (and name-match
+                      (let ((new-section (car name-match))
+                            (new-name (cadr name-match)))
+                        (gnc:debug
+                         (format #f "option ~a/~a has been renamed to ~a/~a\n"
+                                 section name new-section new-name))
+                        (cond
+                         ;; new-name only
+                         ((not new-section)
+                          (lookup-option section new-name))
+                         ;; new-section different to current section
+                         ;; name, and possibly new-name
+                         ((not (string=? new-section section))
+                          (lookup-option new-section new-name))
+                         ;; no match, return #f
+                         (else #f)))))))))
 
   (define (option-changed section name)
     (set! options-changed #t)

commit bd0cbbf9311d7dbc79a3361d50c89d052a2dbe5e
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 11:45:34 2019 +0800

    [test-options] initial commit
    
    * test lookup option changed section/name

diff --git a/libgnucash/app-utils/test/CMakeLists.txt b/libgnucash/app-utils/test/CMakeLists.txt
index d3a55cc41..dd9e259a1 100644
--- a/libgnucash/app-utils/test/CMakeLists.txt
+++ b/libgnucash/app-utils/test/CMakeLists.txt
@@ -47,6 +47,7 @@ set(test_app_utils_scheme_SOURCES
 
 set (test_app_utils_scheme_SRFI64_SOURCES
   test-date-utilities.scm
+  test-options.scm
 )
 
 gnc_add_scheme_targets(scm-test-load-app-utils-module
diff --git a/libgnucash/app-utils/test/test-options.scm b/libgnucash/app-utils/test/test-options.scm
new file mode 100644
index 000000000..94c288089
--- /dev/null
+++ b/libgnucash/app-utils/test/test-options.scm
@@ -0,0 +1,28 @@
+(use-modules (gnucash gnc-module))
+(gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
+(use-modules (srfi srfi-64))
+(use-modules (gnucash engine test srfi64-extras))
+
+(define (run-test)
+  (test-runner-factory gnc:test-runner)
+  (test-begin "test-options")
+  (test-lookup-option)
+  (test-end "test-options"))
+
+(define (test-lookup-option)
+  (let ((options (gnc:new-options)))
+    (gnc:register-option
+     options
+     (gnc:make-simple-boolean-option
+      "Section" "Start Date" "sort-tag" "docstring" 'default-val))
+
+    (gnc:register-option
+     options
+     (gnc:make-simple-boolean-option
+      "Filter" "Void Transactions" "sort-tag" "docstring" 'default-val))
+
+    (test-assert "lookup-option changed name"
+      (gnc:lookup-option options "Section" "From"))
+
+    (test-assert "lookup-option changed section and name"
+      (gnc:lookup-option options "Section" "Void Transactions?"))))

commit 66511f17bb7848c290f5d08e58b52b8fb4eaf061
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 28 11:22:36 2019 +0800

    [c-interface] compact functions
    
    fix whitespace. this module has good coverage in test-c-interface.scm.

diff --git a/libgnucash/app-utils/c-interface.scm b/libgnucash/app-utils/c-interface.scm
index 559bc32bb..8dba2b985 100644
--- a/libgnucash/app-utils/c-interface.scm
+++ b/libgnucash/app-utils/c-interface.scm
@@ -20,30 +20,25 @@
         (captured-error #f)
         (result #f))
     (catch #t
-        (lambda ()
-            ;; Execute the code in which
-            ;; you want to catch errors here.
-            (if (procedure? cmd)
-                (set! result (apply cmd args)))
-            (if (string? cmd)
-                (set! result (eval-string cmd)))
-            )
-        (lambda (key . parameters)
-            ;; Put the code which you want
-            ;; to handle an error after the
-            ;; stack has been unwound here.
-            (let* ((str-port (open-output-string)))
-                    (display-backtrace captured-stack str-port)
-                    (display "\n" str-port)
-                    (print-exception str-port #f key parameters)
-                    (set! captured-error (get-output-string str-port))))
-        (lambda (key . parameters)
-            ;; Capture the stack here, cut the last 3 frames which are
-            ;; make-stack, this one, and the throw handler.
-                (set! captured-stack (make-stack #t 3))))
-
-    (list result captured-error)
-))
+      (lambda ()
+        ;; Execute the code in which you want to catch errors here.
+        (cond
+         ((procedure? cmd) (set! result (apply cmd args)))
+         ((string? cmd) (set! result (eval-string cmd)))))
+      (lambda (key . parameters)
+        ;; Put the code which you want to handle an error after the
+        ;; stack has been unwound here.
+        (set! captured-error
+          (call-with-output-string
+            (lambda (port)
+              (display-backtrace captured-stack port)
+              (newline port)
+              (print-exception port #f key parameters)))))
+      (lambda (key . parameters)
+        ;; Capture the stack here, cut the last 3 frames which are
+        ;; make-stack, this one, and the throw handler.
+        (set! captured-stack (make-stack #t 3))))
+    (list result captured-error)))
 
 ;; gnc:eval-string-with-error-handling will evaluate the input string (cmd)
 ;; an captures any exception that would be generated. It returns
@@ -53,7 +48,7 @@
 ;; We'll use this to wrap guile calls in C(++), allowing
 ;; the C(++) code to decide how to handle the errors.
 (define (gnc:eval-string-with-error-handling cmd)
-    (gnc:call-with-error-handling cmd '()))
+  (gnc:call-with-error-handling cmd '()))
 
 ;; gnc:apply-with-error-handling will call guile's apply to run func with args
 ;; an captures any exception that would be generated. It returns
@@ -63,33 +58,28 @@
 ;; We'll use this to wrap guile calls in C(++), allowing
 ;; the C(++) code to decide how to handle the errors.
 (define (gnc:apply-with-error-handling func args)
-    (gnc:call-with-error-handling func args))
-
+  (gnc:call-with-error-handling func args))
 
 (define (gnc:backtrace-if-exception proc . args)
   (let* ((apply-result (gnc:apply-with-error-handling proc args))
          (result (car apply-result))
          (error (cadr apply-result)))
-        (if error
-            (begin
-                (display error (current-error-port))
-                (if (defined? 'gnc:warn)
-                    (gnc:warn error)))
-            result)))
+    (cond
+     (error
+      (display error (current-error-port))
+      (when (defined? 'gnc:warn)
+        (gnc:warn error)))
+     (else result))))
 
 ;; This database can be used to store and retrieve translatable
 ;; strings. Strings that are returned by the lookup function are
 ;; translated with gettext.
 (define (gnc:make-string-database)
-
-  (define string-hash (make-hash-table 23))
-
+  (define string-hash (make-hash-table))
   (define (lookup key)
     (_ (hash-ref string-hash key)))
-
   (define (store key string)
     (hash-set! string-hash key string))
-
   (define (dispatch message . args)
     (let ((func (case message
                   ((lookup) lookup)
@@ -98,5 +88,4 @@
       (if func
           (apply func args)
           (gnc:warn "string-database: bad message" message "\n"))))
-
   dispatch)

commit 887f7fac32ee328b99dff918f7db85584451a903
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Jul 27 15:15:55 2019 -0700

    Bug 746937 - Template transaction splits are loaded in reverse order...
    
    and then not sorted before saving.
    
    Actually not sorted after loading because the template accounts weren't
    being committed.

diff --git a/libgnucash/backend/xml/io-gncxml-v2.cpp b/libgnucash/backend/xml/io-gncxml-v2.cpp
index 8f42abe84..085119e35 100644
--- a/libgnucash/backend/xml/io-gncxml-v2.cpp
+++ b/libgnucash/backend/xml/io-gncxml-v2.cpp
@@ -853,6 +853,9 @@ qof_session_load_from_xml_file_v2_full (
     gnc_account_foreach_descendant (root,
                                     (AccountCb) xaccAccountCommitEdit,
                                     NULL);
+    gnc_account_foreach_descendant (gnc_book_get_template_root (book),
+                                    (AccountCb) xaccAccountCommitEdit,
+                                    NULL);
 
     /* start logging again */
     xaccLogEnable ();

commit 456ab2241f8e5532c1b6724fc43c77e06d32fab5
Author: John Ralls <jralls at ceridwen.us>
Date:   Fri Jul 26 16:17:09 2019 -0700

    Remove unused ifdefs for IMPLEMENT_BOOK_DOM_TREES_LATER.
    
    Apparently stub code for a different way of generating the DOM tree, never implemented.

diff --git a/libgnucash/backend/xml/gnc-book-xml-v2.cpp b/libgnucash/backend/xml/gnc-book-xml-v2.cpp
index ed8416b9c..529eb2d93 100644
--- a/libgnucash/backend/xml/gnc-book-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-book-xml-v2.cpp
@@ -60,45 +60,6 @@ static QofLogModule log_module = GNC_MOD_IO;
 
 /* ================================================================ */
 
-#ifdef IMPLEMENT_BOOK_DOM_TREES_LATER
-
-static void
-append_account_tree (xmlNodePtr parent,
-                     Account* account,
-                     gboolean allow_incompat)
-{
-    GList* children, *node;
-
-    children = gnc_account_get_children (account);
-    children = g_list_sort (children, qof_instance_guid_compare);
-    for (node = children; node; node = node->next)
-    {
-        xmlNodePtr accnode;
-        Account* account;
-
-        account = node->data;
-        accnode = gnc_account_dom_tree_create (account, FALSE, allow_incompat);
-        xmlAddChild (parent, accnode);
-        append_account_tree (accnode, account);
-    }
-    g_list_free (children);
-}
-
-static int
-traverse_txns (Transaction* txn, gpointer data)
-{
-    xmlNodePtr node;
-    xmlNodePtr parent = data;
-
-    node = gnc_transaction_dom_tree_create (txn);
-    xmlAddChild (parent, node);
-
-    return 0;
-}
-#endif
-
-/* ================================================================ */
-
 xmlNodePtr
 gnc_book_dom_tree_create (QofBook* book)
 {
@@ -115,32 +76,6 @@ gnc_book_dom_tree_create (QofBook* book)
     xmlAddChild (ret, qof_instance_slots_to_dom_tree (book_slots_string,
                                                       QOF_INSTANCE (book)));
 
-#ifdef IMPLEMENT_BOOK_DOM_TREES_LATER
-    /* theoretically, we should be adding all the below to the book
-     * but in fact, there's enough brain damage in the code already
-     * that we are only going to hand-edit the file at a higher layer.
-     * And that's OK, since its probably a performance boost anyway.
-     */
-    xmlAddChild (ret, gnc_commodity_dom_tree_create (
-                     gnc_commodity_table_get_table (book)));
-    xmlAddChild (ret, gnc_pricedb_dom_tree_create (gnc_pricedb_get_db (book)));
-    if (allow_incompat)
-    {
-        accnode = gnc_account_dom_tree_create (account, FALSE);
-        xmlAddChild (ret, rootAccNode);
-    }
-    append_account_tree (ret, gnc_book_get_root (book));
-
-    xaccAccountTreeForEachTransaction (gnc_book_get_root_account (book),
-                                       traverse_txns, ret);
-
-    /* xxx FIXME hack alert how are we going to handle
-     *  gnc_book_get_template_group handled ???   */
-    xmlAddChild (ret, gnc_schedXaction_dom_tree_create (
-                     gnc_book_get_schedxactions (book)));
-
-#endif
-
     return ret;
 }
 
diff --git a/libgnucash/backend/xml/io-gncxml-v2.cpp b/libgnucash/backend/xml/io-gncxml-v2.cpp
index 3b2c0b35f..8f42abe84 100644
--- a/libgnucash/backend/xml/io-gncxml-v2.cpp
+++ b/libgnucash/backend/xml/io-gncxml-v2.cpp
@@ -981,31 +981,6 @@ write_book (FILE* out, QofBook* book, sixtp_gdv2* gd)
 {
     struct file_backend be_data;
 
-#ifdef IMPLEMENT_BOOK_DOM_TREES_LATER
-    /* We can't just blast out the dom tree, because the dom tree
-     * doesn't have the books, transactions, etc underneath it.
-     * But that is just as well, since I think the performance
-     * will be much better if we write out as we go along
-     */
-    xmlNodePtr node;
-
-    node = gnc_book_dom_tree_create (book);
-
-    if (!node)
-    {
-        return FALSE;
-    }
-
-    xmlElemDump (out, NULL, node);
-    xmlFreeNode (node);
-
-    if (ferror (out) || fprintf (out, "\n") < 0)
-    {
-        return FALSE;
-    }
-
-#endif
-
     be_data.out = out;
     be_data.book = book;
     be_data.gd = gd;

commit a0a0ffbb773cee29a3990f0d61263158fcfe4370
Author: Frank H. Ellenberger <frank.h.ellenberger at gmail.com>
Date:   Thu Jul 25 23:51:50 2019 +0200

    Bug 797319 - Mauritanian ouguiya MRO shows too few decimals
    
    Add info about cash vs. banking

diff --git a/libgnucash/engine/iso-4217-currencies.xml b/libgnucash/engine/iso-4217-currencies.xml
index 2737cb227..9624d807a 100644
--- a/libgnucash/engine/iso-4217-currencies.xml
+++ b/libgnucash/engine/iso-4217-currencies.xml
@@ -1748,6 +1748,7 @@
   local-symbol="UM"
 />
 <!-- "MRU" - "Ouguiya"
+;; Bug 797319: In cash parts-per-unit is still 5, but not in banking 
 -->
 <currency
   isocode="MRU"

commit c77607c8c09e6f0b994e0508d7f14a0ca51c9328
Author: Frank H. Ellenberger <frank.h.ellenberger at gmail.com>
Date:   Thu Jul 25 23:33:42 2019 +0200

    Bug 797316 - New Mauritanian ouguiya MRU not supported
    
    Add info, which Mauritanian ouguiya is recent

diff --git a/libgnucash/engine/iso-4217-currencies.xml b/libgnucash/engine/iso-4217-currencies.xml
index a17e75fe8..2737cb227 100644
--- a/libgnucash/engine/iso-4217-currencies.xml
+++ b/libgnucash/engine/iso-4217-currencies.xml
@@ -1734,6 +1734,7 @@
   local-symbol="MOP$"
 />
 <!-- "MRO" - "Ouguiya"
+  2018-01-01 "MRU" 10
 -->
 <currency
   isocode="MRO"



Summary of changes:
 gnucash/gnome/gnc-plugin-page-account-tree.c       |  18 +
 gnucash/gnome/gnc-plugin-page-budget.c             |  22 +
 gnucash/gnome/gnc-plugin-page-invoice.c            |  22 +
 gnucash/gnome/gnc-plugin-page-owner-tree.c         |  17 +
 gnucash/gnome/gnc-plugin-page-register.c           |  29 +
 gnucash/gnome/gnc-plugin-page-sx-list.c            |  19 +
 gnucash/gnome/gnc-split-reg.c                      |  14 +-
 gnucash/gtkbuilder/dialog-import.glade             |  36 +-
 .../import-export/bi-import/dialog-bi-import-gui.c |  48 +-
 .../import-export/qif-imp/assistant-qif-import.c   |   3 +-
 gnucash/import-export/qif-imp/qif-file.scm         |   2 +-
 gnucash/import-export/qif-imp/qif-guess-map.scm    |  12 +-
 gnucash/import-export/qif-imp/qif-import.scm       |   5 +-
 gnucash/import-export/qif-imp/qif-merge-groups.scm | 294 +++-----
 gnucash/import-export/qif-imp/qif-objects.scm      | 271 ++++----
 gnucash/import-export/qif-imp/qif-parse.scm        | 767 ++++++++-------------
 gnucash/import-export/qif-imp/qif-utils.scm        |  74 +-
 gnucash/import-export/qif-imp/simple-obj.scm       |   8 +-
 gnucash/import-export/qif-imp/string.scm           |   4 +-
 gnucash/import-export/qif-imp/test/CMakeLists.txt  |  29 +-
 .../import-export/qif-imp/test/test-qif-imp.scm    |  60 ++
 .../qif-imp/test/test-qif-merge-groups.scm         | 112 +++
 .../import-export/qif-imp/test/test-qif-parse.scm  | 308 +++++++++
 gnucash/register/ledger-core/gnc-ledger-display.c  |  29 +-
 gnucash/register/ledger-core/split-register.c      |  44 +-
 gnucash/register/register-gnome/gnucash-sheet.c    |   8 +-
 gnucash/report/eguile-html-utilities.scm           |  76 +-
 gnucash/report/eguile-utilities.scm                |  72 +-
 gnucash/report/eguile.scm                          | 101 +--
 gnucash/report/reports/standard/balsheet-eg.scm    |  55 --
 .../report/reports/support/taxinvoice.eguile.scm   |   2 +-
 libgnucash/app-utils/c-interface.scm               |  67 +-
 libgnucash/app-utils/options.scm                   | 120 ++--
 libgnucash/app-utils/test/CMakeLists.txt           |   2 +
 libgnucash/app-utils/test/test-options.scm         |  28 +
 libgnucash/backend/xml/gnc-book-xml-v2.cpp         |  65 --
 libgnucash/backend/xml/io-gncxml-v2.cpp            |  28 +-
 libgnucash/engine/iso-4217-currencies.xml          |   2 +
 38 files changed, 1541 insertions(+), 1332 deletions(-)
 create mode 100644 gnucash/import-export/qif-imp/test/test-qif-imp.scm
 create mode 100644 gnucash/import-export/qif-imp/test/test-qif-merge-groups.scm
 create mode 100644 gnucash/import-export/qif-imp/test/test-qif-parse.scm
 create mode 100644 libgnucash/app-utils/test/test-options.scm



More information about the gnucash-changes mailing list