Function: org-table-eval-formula
org-table-eval-formula is an autoloaded, interactive and byte-compiled
function defined in org-table.el.gz.
Signature
(org-table-eval-formula &optional ARG EQUATION SUPPRESS-ALIGN SUPPRESS-CONST SUPPRESS-STORE SUPPRESS-ANALYSIS)
Documentation
Replace the table field value at the cursor by the result of a calculation.
In a table, this command replaces the value in the current field with the
result of a formula. It also installs the formula as the "current" column
formula, by storing it in a special line below the table. When called
with a C-u (universal-argument) prefix the formula is installed as a field formula.
When called with a C-u (universal-argument) C-u (universal-argument) prefix, insert the active equation for the field
back into the current field, so that it can be edited there. This is useful
in order to use C-c ? (org-table-show-reference) to check the referenced fields.
When called, the command first prompts for a formula, which is read in the minibuffer. Previously entered formulas are available through the history list, and the last used formula is offered as a default. These stored formulas are adapted correctly when moving, inserting, or deleting columns with the corresponding commands.
The formula can be any algebraic expression understood by the Calc package. For details, see the Org mode manual.
This function can also be called from Lisp programs and offers additional arguments: EQUATION can be the formula to apply. If this argument is given, the user will not be prompted.
SUPPRESS-ALIGN is used to speed-up recursive calls by by-passing unnecessary aligns.
SUPPRESS-CONST suppresses the interpretation of constants in the formula, assuming that this has been done already outside the function.
SUPPRESS-STORE means the formula should not be stored, either because it is already stored, or because it is a modified equation that should not overwrite the stored one.
SUPPRESS-ANALYSIS prevents analyzing the table and checking location of point.
Key Bindings
Source Code
;; Defined in /usr/src/emacs/lisp/org/org-table.el.gz
;;;###autoload
(defun org-table-eval-formula (&optional arg equation
suppress-align suppress-const
suppress-store suppress-analysis)
"Replace the table field value at the cursor by the result of a calculation.
In a table, this command replaces the value in the current field with the
result of a formula. It also installs the formula as the \"current\" column
formula, by storing it in a special line below the table. When called
with a `\\[universal-argument]' prefix the formula is installed as a \
field formula.
When called with a `\\[universal-argument] \\[universal-argument]' prefix, \
insert the active equation for the field
back into the current field, so that it can be edited there. This is \
useful
in order to use \\<org-table-fedit-map>`\\[org-table-show-reference]' to \
check the referenced fields.
When called, the command first prompts for a formula, which is read in
the minibuffer. Previously entered formulas are available through the
history list, and the last used formula is offered as a default.
These stored formulas are adapted correctly when moving, inserting, or
deleting columns with the corresponding commands.
The formula can be any algebraic expression understood by the Calc package.
For details, see the Org mode manual.
This function can also be called from Lisp programs and offers
additional arguments: EQUATION can be the formula to apply. If this
argument is given, the user will not be prompted.
SUPPRESS-ALIGN is used to speed-up recursive calls by by-passing
unnecessary aligns.
SUPPRESS-CONST suppresses the interpretation of constants in the
formula, assuming that this has been done already outside the
function.
SUPPRESS-STORE means the formula should not be stored, either
because it is already stored, or because it is a modified
equation that should not overwrite the stored one.
SUPPRESS-ANALYSIS prevents analyzing the table and checking
location of point."
(interactive "P")
(unless suppress-analysis
(org-table-check-inside-data-field nil t)
(org-table-analyze))
(if (equal arg '(16))
(let ((eq (org-table-current-field-formula)))
(org-table-get-field nil eq)
(org-table-align)
(setq org-table-may-need-update t))
(let* (fields
(ndown (if (integerp arg) arg 1))
(org-table-automatic-realign nil)
(case-fold-search nil)
(down (> ndown 1))
(formula (if (and equation suppress-store)
equation
(org-table-get-formula equation (equal arg '(4)))))
(n0 (org-table-current-column))
(calc-modes (copy-sequence org-calc-default-modes))
(numbers nil) ; was a variable, now fixed default
(keep-empty nil)
form form0 formrpl formrg bw fmt ev orig lispp literal
duration duration-output-format)
;; Parse the format string. Since we have a lot of modes, this is
;; a lot of work. However, I think calc still uses most of the time.
(if (string-match "\\(.*\\);\\(.*\\)" formula)
(progn
(setq fmt (concat (cdr (assoc "%" org-table-local-parameters))
(match-string-no-properties 2 formula)))
(setq formula (match-string-no-properties 1 formula))
(while (string-match "\\([pnfse]\\)\\(-?[0-9]+\\)" fmt)
(let ((c (string-to-char (match-string 1 fmt)))
(n (string-to-number (match-string 2 fmt))))
(cl-case c
(?p (setf (cl-getf calc-modes 'calc-internal-prec) n))
(?n (setf (cl-getf calc-modes 'calc-float-format) (list 'float n)))
(?f (setf (cl-getf calc-modes 'calc-float-format) (list 'fix n)))
(?s (setf (cl-getf calc-modes 'calc-float-format) (list 'sci n)))
(?e (setf (cl-getf calc-modes 'calc-float-format) (list 'eng n)))))
;; Remove matched flags from the mode string.
(setq fmt (replace-match "" t t fmt)))
(while (string-match "\\([tTUNLEDRFSu]\\)" fmt)
(let ((c (string-to-char (match-string 1 fmt))))
(cl-case c
(?t (setq duration t numbers t
duration-output-format org-table-duration-custom-format))
(?T (setq duration t numbers t duration-output-format nil))
(?U (setq duration t numbers t duration-output-format 'hh:mm))
(?N (setq numbers t))
(?L (setq literal t))
(?E (setq keep-empty t))
(?D (setf (cl-getf calc-modes 'calc-angle-mode) 'deg))
(?R (setf (cl-getf calc-modes 'calc-angle-mode) 'rad))
(?F (setf (cl-getf calc-modes 'calc-prefer-frac) t))
(?S (setf (cl-getf calc-modes 'calc-symbolic-mode) t))
(?u (setf (cl-getf calc-modes 'calc-simplify-mode) 'units))))
;; Remove matched flags from the mode string.
(setq fmt (replace-match "" t t fmt)))
(unless (string-match "\\S-" fmt)
(setq fmt nil))))
(when (and (not suppress-const) org-table-formula-use-constants)
(setq formula (org-table-formula-substitute-names formula)))
(setq orig (or (get-text-property 1 :orig-formula formula) "?"))
(setq formula (org-table-formula-handle-first/last-rc formula))
(while (> ndown 0)
(setq fields (org-split-string
(org-trim
(buffer-substring-no-properties
(line-beginning-position) (line-end-position)))
" *| *"))
;; replace fields with duration values if relevant
(if duration
(setq fields
(mapcar #'org-table-time-string-to-seconds fields)))
(if (eq numbers t)
(setq fields (mapcar
(lambda (x)
(if (string-match "\\S-" x)
(number-to-string (string-to-number x))
x))
fields)))
(setq ndown (1- ndown))
(setq form (copy-sequence formula)
lispp (and (> (length form) 2) (equal (substring form 0 2) "'(")))
(if (and lispp literal) (setq lispp 'literal))
;; Insert row and column number of formula result field
(while (string-match "[@$]#" form)
(setq form
(replace-match
(format "%d"
(save-match-data
(if (equal (substring form (match-beginning 0)
(1+ (match-beginning 0)))
"@")
(org-table-current-dline)
(org-table-current-column))))
t t form)))
;; Check for old vertical references
(org-table--error-on-old-row-references form)
;; Insert remote references
(setq form (org-table-remote-reference-indirection form))
(while (string-match "\\<remote([ \t]*\\([^,)]+\\)[ \t]*,[ \t]*\\([^\n)]+\\))" form)
(setq form
(replace-match
(save-match-data
(org-table-make-reference
(let ((rmtrng (org-table-get-remote-range
(match-string 1 form) (match-string 2 form))))
(if duration
(if (listp rmtrng)
(mapcar (lambda(x) (org-table-time-string-to-seconds x)) rmtrng)
(org-table-time-string-to-seconds rmtrng))
rmtrng))
keep-empty numbers lispp))
t t form)))
;; Insert complex ranges
(while (and (string-match org-table-range-regexp form)
(> (length (match-string 0 form)) 1))
(setq formrg
(save-match-data
(org-table-get-range
(match-string 0 form) org-table-current-begin-pos n0)))
(setq formrpl
(save-match-data
(org-table-make-reference
;; possibly handle durations
(if duration
(if (listp formrg)
(mapcar (lambda(x) (org-table-time-string-to-seconds x)) formrg)
(org-table-time-string-to-seconds formrg))
formrg)
keep-empty numbers lispp)))
(if (not (save-match-data
(string-match (regexp-quote form) formrpl)))
(setq form (replace-match formrpl t t form))
(user-error "Spreadsheet error: invalid reference \"%s\"" form)))
;; Insert simple ranges, i.e. included in the current row.
(while (string-match
"\\$\\(\\([-+]\\)?[0-9]+\\)\\.\\.\\$\\(\\([-+]\\)?[0-9]+\\)"
form)
(setq form
(replace-match
(save-match-data
(org-table-make-reference
(cl-subseq fields
(+ (if (match-end 2) n0 0)
(string-to-number (match-string 1 form))
-1)
(+ (if (match-end 4) n0 0)
(string-to-number (match-string 3 form))))
keep-empty numbers lispp))
t t form)))
(setq form0 form)
;; Insert the references to fields in same row
(while (string-match "\\$\\(\\([-+]\\)?[0-9]+\\)" form)
(let* ((n (+ (string-to-number (match-string 1 form))
(if (match-end 2) n0 0)))
(x (nth (1- (if (= n 0) n0 (max n 1))) fields)))
(setq formrpl (save-match-data
(org-table-make-reference
x keep-empty numbers lispp)))
(when (or (not x)
(save-match-data
(string-match (regexp-quote formula) formrpl)))
(user-error "Invalid field specifier \"%s\""
(match-string 0 form))))
(setq form (replace-match formrpl t t form)))
(if lispp
(setq ev (condition-case nil
;; FIXME: Arbitrary code evaluation.
(eval (eval (read form)))
(error "#ERROR"))
ev (if (numberp ev) (number-to-string ev) ev)
ev (if duration (org-table-time-seconds-to-string
(string-to-number ev)
duration-output-format)
ev))
;; Use <...> timestamps so that Calc can handle them.
(setq form
(replace-regexp-in-string org-ts-regexp-inactive "<\\1>" form))
;; Internationalize local timestamps by setting locale to
;; "C".
(setq form
(replace-regexp-in-string
org-ts-regexp
(lambda (ts)
(let ((system-time-locale "C"))
(format-time-string
(org-time-stamp-format
(string-match-p "[0-9]\\{1,2\\}:[0-9]\\{2\\}" ts))
(save-match-data (org-time-string-to-time ts)))))
form t t))
(setq ev (if (and duration (string-match "^[0-9]+:[0-9]+\\(?::[0-9]+\\)?$" form))
form
(calc-eval (cons form calc-modes)
(when (and (not keep-empty) numbers) 'num)))
ev (if (and duration
;; When the result is an empty string,
;; keep it empty.
;; See https://list.orgmode.org/orgmode/CAF_DUeEFpNU5UXjE80yB1MB9xj5oVLqG=XadnkqCdzWtakWdPg@mail.gmail.com/
(not (string-empty-p ev)))
(org-table-time-seconds-to-string
(if (string-match "^[0-9]+:[0-9]+\\(?::[0-9]+\\)?$" ev)
(string-to-number (org-table-time-string-to-seconds ev))
(string-to-number ev))
duration-output-format)
ev)))
(when org-table-formula-debug
(let ((wcf (current-window-configuration)))
(with-output-to-temp-buffer "*Substitution History*"
(princ (format "Substitution history of formula
Orig: %s
$xyz-> %s
@r$c-> %s
$1-> %s\n" orig formula form0 form))
(if (consp ev)
(princ (format " %s^\nError: %s"
(make-string (car ev) ?\-) (nth 1 ev)))
(princ (format "Result: %s\nFormat: %s\nFinal: %s"
ev (or fmt "NONE")
(if fmt (format fmt (string-to-number ev)) ev)))))
(setq bw (get-buffer-window "*Substitution History*"))
(org-fit-window-to-buffer bw)
(unless (and (called-interactively-p 'any) (not ndown))
(unless (let (inhibit-redisplay)
(y-or-n-p "Debugging Formula. Continue to next? "))
(org-table-align)
(user-error "Abort"))
(delete-window bw)
(message "")
(set-window-configuration wcf))))
(when (consp ev) (setq fmt nil ev "#ERROR"))
(org-table-justify-field-maybe
(format org-table-formula-field-format
(cond
((not (stringp ev)) ev)
(fmt (format fmt (string-to-number ev)))
;; Replace any active time stamp in the result with
;; an inactive one. Dates in tables are likely
;; piece of regular data, not meant to appear in the
;; agenda.
(t (replace-regexp-in-string org-ts-regexp "[\\1]" ev)))))
(if (and down (> ndown 0) (looking-at ".*\n[ \t]*|[^-]"))
(call-interactively 'org-return)
(setq ndown 0)))
(and down (org-table-maybe-recalculate-line))
(or suppress-align (and org-table-may-need-update
(org-table-align))))))