@cindex printer functions
@cindex cell formatting
@cindex formatting cells
-@findex ses-read-cell-printer
-@findex ses-read-column-printer
-@findex ses-read-default-printer
-@findex ses-define-local-printer
-@findex ses-center
-@findex ses-center-span
-@findex ses-dashfill
-@findex ses-dashfill-span
-@findex ses-tildefill-span
-
Printer functions convert binary cell values into the print forms that
Emacs will display on the screen.
-A printer can be a format string, like @samp{"$%.2f"}. The result
+@menu
+* Various kinds of printer functions::
+* Configuring what printer function applies::
+* Standard printer functions::
+* Local printer functions::
+* Writing a lambda printer function::
+@end menu
+
+@node Various kinds of printer functions
+@subsection Various kinds of printer functions
+
+When configuring what printer function applies (@pxref{Configuring
+what printer function applies}), you can enter a printer function as
+one of the following:
+
+@itemize
+@item
+A format string, like @samp{"$%.2f"}. The result
string is right-aligned within the print cell. To get left-alignment,
-use parentheses: @samp{("$%.2f")}. A printer can also be a
-one-argument function (a symbol or a lambda), whose result is a string
-(right-aligned) or list of one string (left-aligned). While typing in
-a lambda, you can use @kbd{M-@key{TAB}} to complete the names of symbols.
+use parentheses: @samp{("$%.2f")}.
+@item
+A printer can also be a one-argument function, the result of which is
+a string (right-aligned) or list of one string (left-aligned). Such a
+function can be in turn configured as:
+@itemize
+@item
+A lambda expression, for instance:
+
+@lisp
+(lambda (x)
+ (cond
+ ((null x) "")
+ ((numberp x) (format "%.2f" x))
+ (t (ses-center-span x ?# 'ses-prin1))))
+@end lisp
+
+While typing in a lambda, you can use @kbd{M-@key{TAB}} to complete
+the names of symbols.
+@item
+A symbol referring to a standard printer function (@pxref{Standard
+printer functions}).
+@item
+A symbol referring to a local printer function (@pxref{Local printer
+functions}).
+@end itemize
+
+
+@end itemize
+
+
+@node Configuring what printer function applies
+@subsection Configuring what printer function applies
Each cell has a printer. If @code{nil}, the column-printer for the cell's
column is used. If that is also @code{nil}, the default-printer for the
@table @kbd
@item p
+@findex ses-read-cell-printer
Enter a printer for current cell or range (@code{ses-read-cell-printer}).
@item M-p
+@findex ses-read-column-printer
Enter a printer for the current column (@code{ses-read-column-printer}).
@item C-c C-p
+@findex ses-read-default-printer
Enter the default printer for the spreadsheet
(@code{ses-read-default-printer}).
@end table
-The @code{ses-read-@r{XXX}-printer} commands have their own minibuffer
-history, which is preloaded with the set of all printers used in this
-spreadsheet, plus the standard printers.
+The @code{ses-read-@var{xxx}-printer} commands have their own
+minibuffer history, which is preloaded with the set of all printers
+used in this spreadsheet, plus the standard printers (@pxref{Standard
+printer functions}) and the local printers (@pxref{Local printer
+functions}).
-The standard printers are suitable only for cells, not columns or
-default, because they format the value using the column-printer (or
-default-printer if @code{nil}) and then center the result:
+@node Standard printer functions
+@subsection Standard printer functions
-@table @code
+
+Except for @code{ses-prin1}, the other standard printers are suitable
+only for cells, not columns or default, because they format the value
+using the column-printer (or default-printer if @code{nil}) and then
+center the result:
+
+@ftable @code
@item ses-center
Just centering.
@item ses-tildefill-span
Centering with tildes (~) and spill-over.
-@end table
+@item ses-prin1
+This is the fallback printer, used when calling the configured printer
+throws some error.
+@end ftable
+
+@node Local printer functions
+@subsection Local printer functions
+
+@findex ses-define-local-printer
You can define printer function local to a sheet with the command
@code{ses-define-local-printer}. For instance, define a printer
@samp{foo} to @code{"%.2f"}, and then use symbol @samp{foo} as a
@code{"%.3f"}, all the cells using printer @samp{foo} will be
reprinted accordingly.
-When you define a printer function with a lambda expression taking one
-argument, please take care that the returned value is a string, or a
-list containing a string, even when the input argument has an
+Sometimes there are local printers that you want to define or
+re-define automatically every time you open a sheet. For instance
+imagine that you want to define/re-define automatically a local
+printer @code{euro} to display a number like an amount of euros, that
+is to say number @code{3.1} would be displayed as
+@code{3.10@dmn{}@euro{}}. To do so in any non read-only SES buffer,
+you can add some code like this to your @file{.emacs} init file:
+
+@lisp
+(defun my-ses-mode-hook ()
+ (unless buffer-read-only
+ (ses-define-local-printer
+ 'euro
+ (lambda (x)
+ (cond
+ ((null x) "")
+ ((numberp x) (format "%.2f€" x))
+ (t (ses-center-span x ?# 'ses-prin1)))))))
+(add-hook 'ses-mode-hook 'my-ses-mode-hook)
+@end lisp
+
+If you replace command @code{ses-define-local-printer} by function
+@code{ses-define-if-new-local-printer}
+@findex ses-define-if-new-local-printer
+the definition will occur only if a local printer with the same name
+in not already defined.
+
+
+@node Writing a lambda printer function
+@subsection Writing a lambda printer function
+
+You can write a printer function with a lambda expression taking one
+argument in two cases:
+
+@itemize
+@item
+when you configure the printer function applying to a cell or column, or
+@item
+when you define a local printer function with command
+@code{ses-define-local-printer}.
+@end itemize
+
+When doing so, please take care that the returned value is a string,
+or a list containing a string, even when the input argument has an
unexpected value. Here is an example:
@example
(cond
((null val) "")
((and (numberp val) (>= val 0)) (format "%.1f" val))
- (t (ses-center-span (format "%S" val) ?#))))
+ (t (ses-center-span val ?# 'ses-prin1))))
@end example
This example will:
+
@itemize
@item
When the cell is empty (ie.@: when @code{val} is @code{nil}), print an
fixed-point notation with one decimal after point
@item
Otherwise, handle the value as erroneous by printing it as an
-s-expression (using @code{prin1}), centered and surrounded by @code{#}
-filling.
+s-expression (using @code{ses-prin1}), centered and surrounded by
+@code{#} filling.
@end itemize
+Another precaution to take is to avoid stack-overflow (due to a
+printer function indefintely recursively re-calling itself). This can
+happen mistakenly when you use a local printer as a column printer,
+and this local printer implicitely call the current column printer, so
+will call itself recursively. Imagine for instance that you want to
+create some local printer @code{=fill} that would center the content
+of a cell and surround it by equal signs @code{=}, and you do it this
+way:
+
+@lisp
+(lambda (x)
+ (cond
+ ((null x) "")
+ (t (ses-center x 0 ?=))))
+@end lisp
+Because @code{=fill} uses standard printer @code{ses-center} without
+passing explicitely any printer to it, @code{ses-center} will call the
+current column printer if any or the spreadsheet default printer
+otherwise. So using @code{=fill} as a column printer will result in a
+stack overflow in this column. SES does not make any check for that,
+you just have to be careful. For instance re-write @code{=fill} like
+this:
+
+@lisp
+(lambda (x)
+ (cond
+ ((null x) "")
+ ((stringp x) (ses-center x 0 ?= " %s "))
+ (t (ses-center-span x ?# 'ses-prin1))))
+@end lisp
+The code above applies the @code{=} filling only to strings, it also
+surrounds the string by one space on each side before filling with
+@code{=} signs. So string @samp{Foo} will be displayed like @samp{@w{===
+Foo ===}} in an 11 character wide column. Anything else than empty cell
+or non string is displayed like errouneous by using @code{#} filling.
@node Clearing cells
@section Clearing cells
"Display properties to create a raised box for cells in the header line.")
(defconst ses-standard-printer-functions
- '(ses-center ses-center-span ses-dashfill ses-dashfill-span
- ses-tildefill-span)
- "List of print functions to be included in initial history of printer
-functions. None of these standard-printer functions is suitable for use as a
-column printer or a global-default printer because they invoke the column or
-default printer and then modify its output.")
+ '(ses-center
+ ses-center-span ses-dashfill ses-dashfill-span
+ ses-tildefill-span
+ ses-prin1)
+ "List of print functions to be included in initial history of
+printer functions. None of these standard-printer functions,
+except function `ses-prin1', is suitable for use as a column
+printer or a global-default printer because they invoke the
+column or default printer and then modify its output.")
;;----------------------------------------------------------------------------
(car value))))
(error
(setq ses-call-printer-return signal)
- (prin1-to-string value t))))
+ (ses-prin1 value))))
(defun ses-adjust-print-width (col change)
"Insert CHANGE spaces in front of column COL, or at end of line if
(when (eq (car-safe item) 'quote)
(push "'" result)
(setq item (cadr item)))
- (setq item (prin1-to-string item t))
+ (setq item (ses-prin1 item))
(setq item (replace-regexp-in-string "\t" "\\\\t" item))
(push item result)
(cond
(ses-begin-change))
(ses-print-cell row col)))))))
-(defun ses-define-local-printer (name)
- "Define a local printer with name NAME."
- (interactive "*SEnter printer name: ")
+
+(defun ses-define-local-printer (name definition)
+ "Define a local printer with name NAME and definition DEFINITION.
+
+NAME shall be a symbol. Use TAB to complete over existing local
+printer names.
+
+DEFINITION shall be either a string formatter, e.g.:
+
+ \"%.2f\" or (\"%.2f\") for left alignment.
+
+or a lambda expression, e.g. for formatting in ISO format dates
+created with a '(calcFunc-date YEAR MONTH DAY)' formula:
+
+ (lambda (x)
+ (cond
+ ((null val) \"\")
+ ((eq (car-safe x) 'date)
+ (let ((calc-format-date '(X YYYY \"-\" MM \"-\" DD)))
+ (math-format-date x)))
+ (t (ses-center-span val ?# 'ses-prin1))))
+
+If NAME is already used to name a local printer function, then
+the current definition is proposed as default value, and the
+function is redefined."
+ (interactive
+ (let (name def already-defined-names)
+ (maphash (lambda (key val) (push (symbol-name key) already-defined-names))
+ ses--local-printer-hashmap)
+ (setq name (completing-read "Enter printer name: " already-defined-names))
+ (when (string= name "")
+ (error "Invalid printer name"))
+ (setq name (intern name))
+ (let* ((cur-printer (gethash name ses--local-printer-hashmap))
+ (default (and cur-printer (ses--locprn-def cur-printer))))
+ (setq def (ses-read-printer (format "Enter definition of printer %S: " name)
+ default)))
+ (list name def)))
+
(let* ((cur-printer (gethash name ses--local-printer-hashmap))
- (default (and (vectorp cur-printer) (ses--locprn-def cur-printer)))
- create-printer
- (new-def
- (ses-read-printer (format "Enter definition of printer %S: " name)
- default)))
+ (default (and cur-printer (ses--locprn-def cur-printer)))
+ create-printer)
(cond
;; cancelled operation => do nothing
- ((eq new-def t))
+ ((eq definition t))
;; no change => do nothing
- ((and (vectorp cur-printer) (equal new-def default)))
+ ((and cur-printer (equal definition default)))
;; re-defined printer
- ((vectorp cur-printer)
+ (cur-printer
(setq create-printer 0)
- (setf (ses--locprn-def cur-printer) new-def)
+ (setf (ses--locprn-def cur-printer) definition)
(ses-refresh-local-printer
name
(setf (ses--locprn-compiled cur-printer)
- (ses-local-printer-compile new-def))))
+ (ses-local-printer-compile definition))))
;; new definition
(t
(setq create-printer 1)
(puthash name
(setq cur-printer
- (ses-make-local-printer-info new-def))
+ (ses-make-local-printer-info definition))
ses--local-printer-hashmap)))
(when create-printer
(let ((printer-def-text
(when (= create-printer 1)
(ses-file-format-extend-parameter-list 3)
(ses-set-parameter 'ses--numlocprn
- (+ ses--numlocprn create-printer))))))))))
+ (1+ ses--numlocprn))))))))))
+(defsubst ses-define-if-new-local-printer (name def)
+ "Same as function `ses-define-if-new-local-printer', except
+that the definition occurs only when the local printer does not
+already exists.
+
+Function `ses-define-if-new-local-printer' is not interactive, it
+is intended for mode hooks to programatically automatically add
+local printers."
+ (unless (gethash name ses--local-printer-hashmap)
+ (ses-define-local-printer name def)))
;;----------------------------------------------------------------------------
;; Checking formulas for safety
;; Standard print functions
;;----------------------------------------------------------------------------
-(defun ses-center (value &optional span fill)
+(defun ses-center (value &optional span fill printer)
"Print VALUE, centered within column.
FILL is the fill character for centering (default = space).
SPAN indicates how many additional rightward columns to include
-in width (default = 0)."
- (let ((printer (or (ses-col-printer ses--col) ses--default-printer))
- (width (ses-col-width ses--col))
+in width (default = 0).
+PRINTER is the printer to use for printing the value, default is the
+column printer if any, or the spreadsheet the spreadsheet default
+printer otherwise."
+ (setq printer (or printer (ses-col-printer ses--col) ses--default-printer))
+ (let ((width (ses-col-width ses--col))
half)
(or fill (setq fill ?\s))
(or span (setq span 0))
(concat half value half
(if (> (% width 2) 0) (char-to-string fill))))))
-(defun ses-center-span (value &optional fill)
+(defun ses-center-span (value &optional fill printer)
"Print VALUE, centered within the span that starts in the current column
and continues until the next nonblank column.
FILL specifies the fill character (default = space)."
(while (and (< end ses--numcols)
(memq (ses-cell-value ses--row end) '(nil *skip*)))
(setq end (1+ end)))
- (ses-center value (- end ses--col 1) fill)))
+ (ses-center value (- end ses--col 1) fill printer)))
-(defun ses-dashfill (value &optional span)
+(defun ses-dashfill (value &optional span printer)
"Print VALUE centered using dashes.
SPAN indicates how many rightward columns to include in width (default = 0)."
- (ses-center value span ?-))
+ (ses-center value span ?- printer))
-(defun ses-dashfill-span (value)
+(defun ses-dashfill-span (value &optional printer)
"Print VALUE, centered using dashes within the span that starts in the
current column and continues until the next nonblank column."
- (ses-center-span value ?-))
+ (ses-center-span value ?- printer))
-(defun ses-tildefill-span (value)
+(defun ses-tildefill-span (value &optional printer)
"Print VALUE, centered using tildes within the span that starts in the
current column and continues until the next nonblank column."
- (ses-center-span value ?~))
+ (ses-center-span value ?~ printer))
+
+(defun ses-prin1 (value)
+ "Shorthand for '(prin1-to-string VALUE t)'.
+Usefull to handle the default behaviour in custom lambda based
+printer functions."
+ (prin1-to-string value t))
(defun ses-unsafe (_value)
"Substitute for an unsafe formula or printer."