Bug 951, handle thousand separators.

Rewrote handling for decimal comma to be much simpler.  Why can't I see the simple way first?
This commit is contained in:
Craig Earls 2013-04-13 21:55:06 -07:00
parent b2c88149cb
commit 971bcf22f4
5 changed files with 290 additions and 268 deletions

View file

@ -36,10 +36,10 @@
(defun ledger-split-commodity-string (str) (defun ledger-split-commodity-string (str)
"Split a commoditized string, STR, into two parts. "Split a commoditized string, STR, into two parts.
Returns a list with (value commodity)." Returns a list with (value commodity)."
(if (> (length str) 0)
(let ((number-regex (if (assoc "decimal-comma" ledger-environment-alist) (let ((number-regex (if (assoc "decimal-comma" ledger-environment-alist)
ledger-amount-decimal-comma-regex ledger-amount-decimal-comma-regex
ledger-amount-decimal-period-regex))) ledger-amount-decimal-period-regex)))
(if (> (length str) 0)
(with-temp-buffer (with-temp-buffer
(insert str) (insert str)
(goto-char (point-min)) (goto-char (point-min))
@ -48,38 +48,35 @@ Returns a list with (value commodity)."
(let ((com (delete-and-extract-region (let ((com (delete-and-extract-region
(match-beginning 1) (match-beginning 1)
(match-end 1)))) (match-end 1))))
(if (re-search-forward number-regex nil t) (if (re-search-forward
number-regex nil t)
(list (list
(string-to-number (ledger-string-to-number
(ledger-commodity-string-number-decimalize (delete-and-extract-region (match-beginning 0) (match-end 0)))
(delete-and-extract-region (match-beginning 0) (match-end 0)) :from-user))
com)))) com))))
((re-search-forward number-regex nil t) ((re-search-forward number-regex nil t)
;; found a number in the current locale, return it in ;; found a number in the current locale, return it in the
;; the car. Anything left over is annotation, ;; car. Anything left over is annotation, the first
;; the first thing should be the commodity, separated ;; thing should be the commodity, separated by
;; by whitespace, return it in the cdr. I can't think of any ;; whitespace, return it in the cdr. I can't think of
;; counterexamples ;; any counterexamples
(list (list
(string-to-number (ledger-string-to-number
(ledger-commodity-string-number-decimalize (delete-and-extract-region (match-beginning 0) (match-end 0)))
(delete-and-extract-region (match-beginning 0) (match-end 0)) :from-user))
(nth 0 (split-string (buffer-substring-no-properties (point-min) (point-max)))))) (nth 0 (split-string (buffer-substring-no-properties (point-min) (point-max))))))
((re-search-forward "0" nil t) ((re-search-forward "0" nil t)
;; couldn't find a decimal number, look for a single 0, ;; couldn't find a decimal number, look for a single 0,
;; indicating account with zero balance ;; indicating account with zero balance
(list 0 ledger-reconcile-default-commodity))))) (list 0 ledger-reconcile-default-commodity))))
;; nothing found, return 0 ;; nothing found, return 0
(list 0 ledger-reconcile-default-commodity))) (list 0 ledger-reconcile-default-commodity))))
(defun ledger-string-balance-to-commoditized-amount (str) (defun ledger-string-balance-to-commoditized-amount (str)
"Return a commoditized amount (val, 'comm') from STR." "Return a commoditized amount (val, 'comm') from STR."
(let ((fields (split-string str "[\n\r]"))) ; break any balances ; break any balances with multi commodities into a list
; with multi commodities (mapcar #'(lambda (st)
; into a list (ledger-split-commodity-string st))
(mapcar #'(lambda (str) (split-string str "[\n\r]")))
(ledger-split-commodity-string str))
fields)))
(defun -commodity (c1 c2) (defun -commodity (c1 c2)
"Subtract C2 from C1, ensuring their commodities match." "Subtract C2 from C1, ensuring their commodities match."
@ -93,27 +90,53 @@ Returns a list with (value commodity)."
(list (+ (car c1) (car c2)) (cadr c1)) (list (+ (car c1) (car c2)) (cadr c1))
(error "Can't add different commodities, %S to %S" c1 c2))) (error "Can't add different commodities, %S to %S" c1 c2)))
(defun ledger-commodity-string-number-decimalize (number-string direction) (defun ledger-strip (str char)
"Take NUMBER-STRING and ensure proper decimalization for use by string-to-number and number-to-string. (let (new-str )
DIRECTION can be :to-user or :from-user. All math calculations (dolist (ch (append str nil))
are done with decimal-period, some users may prefer decimal-comma (unless (= ch char)
which must be translated both directions." (setq new-str (append new-str (list ch)))))
(let ((val number-string)) (concat new-str)))
(if (assoc "decimal-comma" ledger-environment-alist)
(cond ((eq direction :from-user) (defun ledger-string-to-number (str &optional decimal-comma)
;; change string to decimal-period "improve builtin string-to-number by handling internationalization, and return nil of number can't be parsed"
(while (string-match "," val) (let ((nstr (if (or decimal-comma
(setq val (replace-match "." nil nil val)))) ;; switch to period separator (assoc "decimal-comma" ledger-environment-alist))
((eq direction :to-user) (ledger-strip str ?.)
;; change to decimal-comma (ledger-strip str ?,))))
(while (string-match "\\." val) (while (string-match "," nstr)
(setq val (replace-match "," nil nil val)))) ;; gets rid of periods (setq nstr (replace-match "." nil nil nstr)))
(t (string-to-number nstr)))
(error "ledger-commodity-string-number-decimalize: direction not properly specified %S" direction)))
(while (string-match "," val) (defun ledger-number-to-string (n &optional decimal-comma)
(setq val (replace-match "" nil nil val)))) (let ((str (number-to-string n)))
val)) (if (or decimal-comma
(assoc "decimal-comma" ledger-environment-alist))
(while (string-match "\\." str)
(setq str (replace-match "," nil nil str)))
str)))
;; (defun ledger-commodity-string-number-decimalize (number-string direction)
;; "Take NUMBER-STRING and ensure proper decimalization for use by string-to-number and number-to-string.
;; DIRECTION can be :to-user or :from-user. All math calculations
;; are done with decimal-period, some users may prefer decimal-comma
;; which must be translated both directions."
;; (let ((val number-string))
;; (if (assoc "decimal-comma" ledger-environment-alist)
;; (cond ((eq direction :from-user)
;; ;; change string to decimal-period
;; (while (string-match "," val)
;; (setq val (replace-match "." nil nil val)))) ;; switch to period separator
;; ((eq direction :to-user)
;; ;; change to decimal-comma
;; (while (string-match "\\." val)
;; (setq val (replace-match "," nil nil val)))) ;; gets rid of periods
;; (t
;; (error "ledger-commodity-string-number-decimalize: direction not properly specified %S" direction)))
;; (while (string-match "," val)
;; (setq val (replace-match "" nil nil val))))
;; val))
@ -121,12 +144,11 @@ which must be translated both directions."
"Return string representing C1. "Return string representing C1.
Single character commodities are placed ahead of the value, Single character commodities are placed ahead of the value,
longer ones are after the value." longer ones are after the value."
(let ((val (ledger-commodity-string-number-decimalize (let ((str (ledger-number-to-string (car c1)))
(number-to-string (car c1)) :to-user))
(commodity (cadr c1))) (commodity (cadr c1)))
(if (> (length commodity) 1) (if (> (length commodity) 1)
(concat val " " commodity) (concat str " " commodity)
(concat commodity " " val)))) (concat commodity " " str))))
(defun ledger-read-commodity-string (prompt) (defun ledger-read-commodity-string (prompt)
(let ((str (read-from-minibuffer (let ((str (read-from-minibuffer

View file

@ -217,7 +217,7 @@ BEG, END, and LEN control how far it can align."
(let ((end-of-amount (re-search-forward "[-.,0-9]+" (line-end-position) t))) (let ((end-of-amount (re-search-forward "[-.,0-9]+" (line-end-position) t)))
;; determine if there is an amount to edit ;; determine if there is an amount to edit
(if end-of-amount (if end-of-amount
(let ((val (ledger-commodity-string-number-decimalize (match-string 0) :from-user))) (let ((val (ledger-string-to-number (match-string 0))))
(goto-char (match-beginning 0)) (goto-char (match-beginning 0))
(delete-region (match-beginning 0) (match-end 0)) (delete-region (match-beginning 0) (match-end 0))
(calc) (calc)

View file

@ -37,7 +37,7 @@
"-?[1-9][0-9.]*[,]?[0-9]*") "-?[1-9][0-9.]*[,]?[0-9]*")
(defconst ledger-amount-decimal-period-regex (defconst ledger-amount-decimal-period-regex
"-?[1-9][0-9.]*[.]?[0-9]*") "-?[1-9][0-9,]*[.]?[0-9]*")
(defconst ledger-other-entries-regex (defconst ledger-other-entries-regex
"\\(^[~=A-Za-z].+\\)+") "\\(^[~=A-Za-z].+\\)+")