summaryrefslogtreecommitdiffstats
path: root/lisp/bbdb/bbdb-tex.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/bbdb/bbdb-tex.el')
-rw-r--r--lisp/bbdb/bbdb-tex.el557
1 files changed, 557 insertions, 0 deletions
diff --git a/lisp/bbdb/bbdb-tex.el b/lisp/bbdb/bbdb-tex.el
new file mode 100644
index 0000000..38654e3
--- /dev/null
+++ b/lisp/bbdb/bbdb-tex.el
@@ -0,0 +1,557 @@
+;;; bbdb-tex.el --- feed BBDB into LaTeX -*- lexical-binding: t -*-
+
+;; Copyright (C) 2010-2017 Free Software Foundation, Inc.
+
+;; Authors: Boris Goldowsky <boris@cs.rochester.edu>
+;; Dirk Grunwald <grunwald@cs.colorado.edu>
+;; Luigi Semenzato <luigi@paris.cs.berkeley.edu>
+
+;; This file is part of the Insidious Big Brother Database (aka BBDB),
+
+;; BBDB is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; BBDB is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with BBDB. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file lets you feed BBDB into LaTeX.
+;; See the BBDB info manual for documentation.
+;;
+;; In the *BBDB* buffer, type M-x `bbdb-tex' to convert the listing
+;; to LaTeX format.
+;;
+;; TeX macros appearing in the output:
+;; \name{first}{last}
+;; \organization{foo bar}
+;; \affix{foo bar}
+;; \aka{foo bar}
+;; \phone{key}{123 456 7890}
+;; \address{key}{foo bar}
+;; \mail{foo@bar.com}{Smith <foo@bar.com>}
+;; \xfield{key}{value}
+;; Each macro may appear multiple times.
+;;
+;; The detailed grammar of the output is defined in `bbdb-tex-alist'.
+;; The output starts with a prolog where you can specify LaTeX packages
+;; and other customizations in the usual way. The above macros should get
+;; defined, too. By default, this happens in the style file bbdb.sty that
+;; is shipped with BBDB.
+;;
+;; The body of the output contains the BBDB records. Usually, the records
+;; are placed inside some "bbdb" environment. You can customize which fields
+;; of each record should appear in the listing and in which order.
+;; Also, you can put separators between individual fields. A separator macro
+;; can also separate records when the first character of the last name differs
+;; from the first character of the last name of the previous record.
+;; The listing ends with an epilog.
+
+;; A few notes on "advanced usage" of `bbdb-tex':
+;;
+;; It should be possible to use `bbdb-tex' with all the bells and whistles
+;; of LaTeX by loading the appropriate LaTeX style files and packages or
+;; embedding the output of `bbdb-tex' into more complex LaTeX documents.
+;; For this you can customize the rules in `bbdb-tex-alist' and use
+;; customized style files for interpreting the TeX macros used by `bbdb-tex'.
+;;
+;; Generally, lisp customizations for `bbdb-tex' are intended to provide control
+;; of *what* appears in the TeX listing. But there are no lisp customization
+;; options to control the actual layout that should be handled by LaTeX.
+;; BBDB is shipped with one basic LaTeX style file bbdb.sty to handle
+;; the TeX macros listed above. You should customize this LaTeX style file
+;; to match your taste and / or your needs. Note also that `bbdb-tex-alist'
+;; allows you to specify an arbitrary number of rules that may use different
+;; style files for the above TeX macros.
+
+;; Generally, it will be advantageous to make all relevant style files
+;; and packages known to LaTeX by putting them in the appropriate directories
+;; of your TeX installation. Likely, the user variable `bbdb-tex-path'
+;; should not be used in such advanced cases. The main purpose of the
+;; inlining mechanism provided via `bbdb-tex-path' is that we can ship
+;; and install BBDB without worrying about the tricky question where to
+;; (auto-) install the basic style file bbdb.sty shipped with BBDB so that
+;; TeX finds it. Most often, it will be best to manually install even bbdb.sty
+;; in a directory where TeX finds it and bind `bbdb-tex-path' to t to fully
+;; suppress the inlining.
+;;
+;; Before generating the TeX output, the field values of a record are massaged
+;; by `bbdb-tex-field' that passes these values by default to `bbdb-tex-replace',
+;; see also `bbdb-tex-replace-list'. Instead the user may also define functions
+;; `bbdb-tex-output-...' that take precedence, see `bbdb-tex-field'.
+;;
+;; `bbdb-tex' understands one new BBDB xfield: tex-name, see also
+;; `bbdb-tex-name'. If this xfield is defined for a record,
+;; this will be used for the TeXed listing instead of the name field
+;; of that record. The value of the xfield tex-name is used verbatim,
+;; it does not see `bbdb-tex-field' and `bbdb-tex-replace-list'.
+;;
+;;
+;; This program was adapted for BBDB by Boris Goldowsky
+;; <boris@cs.rochester.edu> and Dirk Grunwald
+;; <grunwald@cs.colorado.edu> using a TeX format designed by Luigi
+;; Semenzato <luigi@paris.cs.berkeley.edu>.
+;; We are also grateful to numerous people on the bbdb-info
+;; mailing list for suggestions and bug reports.
+
+;;; Code:
+
+(require 'bbdb)
+(require 'bbdb-com)
+
+;;; Variables:
+
+(defcustom bbdb-tex-name 'tex-name
+ "Xfield holding the name in TeX format.
+The string in this field gets split into first and last name
+using `bbdb-separator-alist'. The separator defaults to \"#\"."
+ :group 'bbdb-utilities-tex
+ :type '(symbol :tag "Xfield"))
+
+(defcustom bbdb-tex-alist
+ `((multi-line
+ (demand (or address phone))
+ (prolog ,(concat "\\documentclass{article}\n\\usepackage{bbdb}\n"
+ "\\usepackage{multicol}\n"
+ "\\begin{document}\n\\begin{multicols}{2}"))
+ (record "\\begin{bbdbrecord}" name organization ; affix aka
+ (address t) (phone t) (mail t)
+ (xfields t nil
+ (omit ,bbdb-tex-name mail-alias creation-date timestamp))
+ "\\end{bbdbrecord}\n")
+ (separator "\\bbdbseparator{%s}\n")
+ (epilog ,(concat "\\noindent\\hrulefill\\\\\nPrinted \\today\n"
+ "\\end{multicols}\n\\end{document}"))
+ (options (bbdb-tex-linebreak "\\\\\\\\\n")
+ (bbdb-tex-address-layout 2)))
+
+ (one-line
+ (demand phone)
+ (prolog ,(concat "\\documentclass{article}\n\\usepackage{bbdb}\n"
+ "\\begin{document}\n\\begin{bbdb}{llllll}"))
+ (record name "&" (organization 1) "&" (phone 2 "&") "&" (mail 1)
+ "&" (address 1) "\\\\")
+ (separator "\\bbdbseparator{%s}")
+ (epilog "\\end{bbdb}\n\\end{document}")
+ (options (bbdb-tex-linebreak ", ")
+ (bbdb-tex-address-layout 3)))
+
+ (phone
+ (demand phone)
+ (prolog ,(concat "\\documentclass{article}\n\\usepackage{bbdb}\n"
+ "\\begin{document}\n\\begin{bbdb}{ll}"))
+ (record name "&" (phone 2 "&") "\\\\")
+ (separator "\\bbdbseparator{%s}")
+ (epilog "\\end{bbdb}\n\\end{document}")
+ (options (bbdb-tex-linebreak ", ")
+ (bbdb-tex-address-layout 3)))
+
+ (example ; another rule with more examples
+ (demand (or address phone))
+ (prolog ,(concat "\\documentclass{article}\n\\usepackage{bbdb}\n"
+ "\\usepackage{multicol}\n"
+ "\\begin{document}\n\\begin{multicols}{2}"))
+ (record "\\begin{bbdbrecord}" name organization
+ (address 1 nil (omit "work"))
+ (phone 2 nil (admit "home" "cell"))
+ (mail t)
+ (birthday t)
+ (xfields t nil
+ (omit ,bbdb-tex-name mail-alias creation-date timestamp))
+ "\\end{bbdbrecord}\n")
+ (separator "\\bbdbseparator{%s}\n")
+ (epilog ,(concat "\\noindent\\hrulefill\\\\\nPrinted \\today\n"
+ "\\end{multicols}\n\\end{document}"))
+ (options (bbdb-tex-linebreak "\\\\\\\\\n")
+ (bbdb-tex-address-layout 2))))
+
+ "Alist of rules for passing BBDB to LaTeX.
+Each rule has the form (RULE LIST1 LIST2 ...).
+The symbol RULE identifies the rule.
+The remainder are lists LIST that should have one of these forms:
+
+ (demand FORM)
+
+ Here FORM is a lisp expression. A record will be TeXed only
+ if evaluating FORM yields a non-nil value for this record.
+ When FORM is evaluated, the symbols name, affix, organization, mail,
+ phone, address, and xfields are set to the corresponding values
+ of this record; these symbols are nil if the respective field
+ does not exist for this record.
+
+ (prolog STRING)
+
+ The string STRING is inserted at the beginning of the buffer.
+ If STRING contains the substring \"\\usepackage{foo}\" and
+ a file \"foo.sty\" exists within `bbdb-tex-path', replace
+ \"\\usepackage{foo}\" with the content of the file \"foo.sty\",
+ surrounded by \"\\makeatletter\" and \"\\makeatother\".
+ Note: This fails with more sophisticated LaTeX style files
+ using, e.g., optional arguments for the \"\\usepackage\" macro.
+
+ (record ELT1 ELT2 ...)
+
+ Here ELT may be one of the following:
+
+ IF ELT is name, this expands to \"\\name{first}{last}\"
+
+ If ELT is affix, organization, or aka, ELT expands to \"\\ELT{value}\".
+ Here the elements of ELT are concatenated to get one value.
+
+ If ELT is the key of an xfield, ELT expands to \"\\xfield{ELT}{value}\".
+
+ If ELT is a string, this is inserted \"as is\" in the TeX buffer.
+
+ ELT may also be a loop (FLD COUNT [SEPARATOR] [OPT...])
+ looping over the values of FLD.
+
+ If FLD is mail, this expands to \"\\mail{short}{long}\",
+ such as \"\\mail{foo@bar.com}{Smith <foo@bar.com>}\",
+ If FLD is phone, this expands to \"\\phone{key}{number}\"
+ If FLD is address, this expands to \"\\address{key}{value}\".
+ If FLD is xfields, this expands to \"\\xfield{key}{value}\".
+ If FLD is the key of an xfield, split the value of FLD
+ using `bbdb-separator-alist' to generate a list of values,
+ which then expand to \"\\xfield{FLD}{value}\".
+
+ If COUNT is a number, process at most COUNT values of FLD.
+ IF COUNT is t, process all values of FLD.
+
+ If SEPARATOR is non-nil, it is a string that is inserted between
+ the values of FLD. Insert COUNT - 1 instances of SEPARATOR,
+ even if there are fewer values of FLD.
+
+ If FLD is mail, phone, address, or xfields,
+ OPT may be a list (admit KEY ...) or (omit KEY ...).
+ Then a value is admitted or omitted if its key KEY is listed here.
+
+ (separator STRING)
+
+ When the first letter of the records' sortkey increases compared with
+ the previous record in the TeX listing, the new letter is formatted
+ using the format string STRING to generate a separator macro.
+
+ (epilog STRING)
+
+ The string STRING is inserted at the end of the buffer."
+ :group 'bbdb-utilities-tex)
+
+(defcustom bbdb-tex-rule-default 'multi-line
+ "Default rule for BBDB tex.
+This symbol should be a key in `bbdb-tex-alist'."
+ :group 'bbdb-utilities-tex
+ :type '(symbol :tag "rule"))
+
+;; FIXME
+;; (defcustom bbdb-tex-empty-fields nil
+;; "If non-nil generate TeX output even for empty fields."
+;; :group 'bbdb-utilities-tex)
+
+(defcustom bbdb-tex-replace-list
+ '(("[#$%&_]" . "\\\\\\&")
+ ("<" . "\\\\textless ")
+ (">" . "\\\\textgreater ")
+ ("~" . "\\\\textasciitilde ")
+ ("{" . "\\\\textbraceleft ")
+ ("}" . "\\\\textbraceright "))
+ "Replacement list for TeX's special characters.
+Each element is of the form (REGEXP . REPLACE)."
+ :group 'bbdb-utilities-tex
+ :type '(repeat (cons regexp string)))
+
+(defcustom bbdb-tex-linebreak "\\\\\\\\\n"
+ "Replacement for linebreaks."
+ :group 'bbdb-utilities-tex
+ :type 'string)
+
+(defcustom bbdb-tex-address-format-list bbdb-address-format-list
+ "List of address formatting rules for `bbdb-tex'.
+Each element may take the same values as in `bbdb-address-format-list'.
+The elements EDIT of `bbdb-address-format-list' are ignored."
+ :group 'bbdb-utilities-tex
+ :type '(repeat (list (choice (const :tag "Default" t)
+ (function :tag "Function")
+ (repeat (string)))
+ (choice (string)
+ (function :tag "Function"))
+ (choice (string)
+ (function :tag "Function"))
+ (choice (string)
+ (function :tag "Function")))))
+
+(defcustom bbdb-tex-address-layout 2
+ "Address layout according to `bbdb-tex-address-format-list'.
+2 is multi-line layout, 3 is one-line layout."
+ :group 'bbdb-utilities-tex)
+
+(defcustom bbdb-tex-file "~/bbdb.tex"
+ "Default file name for TeXing BBDB."
+ :group 'bbdb-utilities-tex
+ :type 'file)
+
+;;; Internal variables
+
+(defvar bbdb-tex-rule-last bbdb-tex-rule-default
+ "Last rule used for TeXing BBDB.")
+
+(defvar bbdb-tex-file-last bbdb-tex-file
+ "Last used TeX file")
+
+;;; Functions:
+
+;; While we use `bbdb-tex-replace' only once in `bbdb-tex-field',
+;; we keep it as a separate function so that it can also be used
+;; inside user-defined functions `bbdb-tex-output-...'.
+(defun bbdb-tex-replace (string)
+ "Apply replacement rules `bbdb-tex-replace-list' to STRING.
+Also, replace linebreaks by `bbdb-tex-linebreak'."
+ (if (not string)
+ ""
+ (dolist (elt bbdb-tex-replace-list)
+ (setq string (replace-regexp-in-string (car elt) (cdr elt) string)))
+ (replace-regexp-in-string "\n" bbdb-tex-linebreak string)))
+
+(defun bbdb-tex-field (field str)
+ "Massage string STR for LaTeX.
+By default, STR is passed to `bbdb-tex-replace'.
+The user may also define a function `bbdb-tex-output-FIELD'
+that takes precedence."
+ (let ((fun (intern-soft (format "bbdb-tex-output-%s" field))))
+ (if fun
+ (funcall fun str)
+ (bbdb-tex-replace str))))
+
+(defun bbdb-tex-list (list rule fun)
+ "Use function FUN to generate output for LIST according to RULE.
+LIST is a list of field values such as a list of addresses.
+RULE is an element of a record list as in `bbdb-tex-alist'
+used to select the elements of LIST that get processed by calling FUN."
+ (let ((admit (cdr (assq 'admit rule)))
+ (omit (cdr (assq 'omit rule)))
+ (num (if (numberp (nth 1 rule)) (nth 1 rule)))
+ (sep (if (nth 2 rule) (concat (nth 2 rule) "\n")))
+ (i -1)
+ new-list elt)
+
+ ;; Select the relevant elements of LIST.
+ (cond (admit
+ (dolist (l list)
+ (if (member (elt l 0) admit)
+ (push l new-list)))
+ (setq new-list (nreverse new-list)))
+
+ (omit
+ (dolist (l list)
+ (unless (member (elt l 0) omit)
+ (push l new-list)))
+ (setq new-list (nreverse new-list)))
+
+ (t
+ (setq new-list list)))
+
+ (cond ((not num)
+ (insert (mapconcat fun new-list (or sep ""))))
+ ((not sep)
+ (while (and (< (setq i (1+ i)) num)
+ (setq elt (pop new-list)))
+ (insert (funcall fun elt))))
+ (t
+ (while (< (setq i (1+ i)) num)
+ (if (setq elt (pop new-list))
+ (insert (funcall fun elt)))
+ (if (< (1+ i) num)
+ (insert sep)))))))
+
+;;;###autoload
+(defun bbdb-tex (records file rule)
+ "Generate FILE for TeXing RECORDS.
+Interactively, use BBDB prefix \
+\\<bbdb-mode-map>\\[bbdb-do-all-records], see `bbdb-do-all-records'.
+RULE should be an element of `bbdb-tex-alist'."
+ (interactive
+ (list (bbdb-do-records)
+ (read-file-name
+ (format "TeX file: (default %s) "
+ (abbreviate-file-name bbdb-tex-file-last))
+ (file-name-directory bbdb-tex-file-last)
+ bbdb-tex-file-last)
+ (intern (completing-read (format "Rule: (default %s) "
+ bbdb-tex-rule-last)
+ bbdb-tex-alist nil t
+ nil nil (symbol-name bbdb-tex-rule-last)))))
+ ;; Remember our choice for `bbdb-tex-file-last'.
+ (setq bbdb-tex-file-last (expand-file-name file))
+
+ (find-file bbdb-tex-file-last)
+ (let* ((buffer-undo-list t)
+ (rule (assq rule bbdb-tex-alist))
+ (demand (nth 1 (assq 'demand rule)))
+ (separator (nth 1 (assq 'separator rule)))
+ current-letter p-symbols p-values)
+ (erase-buffer)
+
+ ;; Options
+ (dolist (option (cdr (assq 'options rule)))
+ (push (car option) p-symbols)
+ (push (cadr option) p-values))
+ (cl-progv p-symbols p-values
+
+ ;; Prolog
+ (let ((prolog (nth 1 (assq 'prolog rule))))
+ (when prolog
+ (insert prolog)
+ (when (consp bbdb-tex-path)
+ (goto-char (point-min))
+ (while (re-search-forward "\\\\usepackage[ \t\n]*{\\([^}]+\\)}" nil t)
+ (let ((sty (locate-file (match-string 1) bbdb-tex-path '(".sty"))))
+ (when sty
+ (replace-match (format "\n\\\\makeatletter\n%% begin %s\n%% end %s\n\\\\makeatother\n" sty sty))
+ (save-excursion
+ (forward-line -2)
+ (insert-file-contents sty))))))
+ (goto-char (point-max))
+ (unless (bolp) (insert "\n"))
+ (insert "% end BBDB prolog\n")))
+
+ ;; Process Records
+ (dolist (record (bbdb-record-list records))
+ (let* ((first-letter
+ (substring (bbdb-record-sortkey record) 0 1))
+ (firstname (bbdb-record-firstname record))
+ (lastname (bbdb-record-lastname record))
+ (name (bbdb-record-name record))
+ (name-lf (bbdb-record-name-lf record))
+ (organization (bbdb-record-organization record))
+ (affix (bbdb-record-affix record))
+ (aka (bbdb-record-aka record))
+ (mail (bbdb-record-mail record))
+ (phone (bbdb-record-phone record))
+ (address (bbdb-record-address record))
+ (xfields (bbdb-record-xfields record))
+ (lex-env `((firstname . ,firstname) (lastname . ,lastname)
+ (name . ,name) (name-lf . ,name-lf) (aka . ,aka)
+ (organization . ,organization) (affix . ,affix)
+ (mail . ,mail) (phone . ,phone)
+ (address . ,address) (xfields . ,xfields)))
+ (bbdb-address-format-list bbdb-tex-address-format-list))
+
+ ;; A record is processed only if the form DEMAND
+ ;; evaluates to a non-nil value.
+ (when (or (not demand)
+ (eval demand lex-env))
+
+ ;; Separator
+ (if (and separator
+ (not (and current-letter
+ (equal first-letter current-letter))))
+ (insert (format separator (upcase first-letter)) "\n"))
+ (setq current-letter first-letter)
+
+ (dolist (elt (cdr (assq 'record rule)))
+ (cond ((stringp elt)
+ (insert elt "\n"))
+
+ ((eq elt 'name) ; name of record
+ (let ((tex-name (and bbdb-tex-name
+ (bbdb-record-field record bbdb-tex-name)))
+ (fmt "\\name{%s}{%s}\n"))
+ (if tex-name
+ (let ((first-last (bbdb-split bbdb-tex-name tex-name)))
+ (cond ((eq 2 (length first-last))
+ (insert (format fmt (car first-last) (cadr first-last))))
+ ((eq 1 (length first-last))
+ (insert (format fmt "" (car first-last))))
+ (t (error "TeX name %s cannot be split" tex-name))))
+ (insert (format fmt
+ (bbdb-tex-field 'firstname firstname)
+ (bbdb-tex-field 'lastname lastname))))))
+
+ ;; organization, affix or aka as single string
+ ((memq elt '(organization affix aka))
+ (let ((val (bbdb-record-field record elt)))
+ (if val
+ (insert (format "\\%s{%s}\n" elt
+ (bbdb-tex-field elt (bbdb-concat elt val)))))))
+
+ ;; organization, affix or aka as list of strings
+ ((memq (car elt) '(organization affix aka))
+ (bbdb-tex-list
+ (bbdb-record-field record (car elt))
+ elt
+ `(lambda (o)
+ (format "\\%s{%s}\n" ',(car elt)
+ (bbdb-tex-field ',(car elt) o)))))
+
+ ((eq (car elt) 'mail) ; mail
+ (bbdb-tex-list
+ mail elt
+ (lambda (m)
+ (format "\\mail{%s}{%s}\n"
+ ;; No processing of plain mail address
+ (nth 1 (bbdb-decompose-bbdb-address m))
+ (bbdb-tex-field 'mail m)))))
+
+ ((eq (car elt) 'address) ; address
+ (bbdb-tex-list
+ address elt
+ (lambda (a)
+ (format "\\address{%s}{%s}\n"
+ (bbdb-tex-field 'address-label (bbdb-address-label a))
+ (bbdb-tex-field 'address (bbdb-format-address
+ a bbdb-tex-address-layout))))))
+
+ ((eq (car elt) 'phone) ; phone
+ (bbdb-tex-list
+ phone elt
+ (lambda (p)
+ (format "\\phone{%s}{%s}\n"
+ (bbdb-tex-field 'phone-label (bbdb-phone-label p))
+ (bbdb-tex-field 'phone (bbdb-phone-string p))))))
+
+ ((eq (car elt) 'xfields) ; list of xfields
+ (bbdb-tex-list
+ (bbdb-record-field record 'xfields)
+ elt
+ (lambda (x)
+ (format "\\xfield{%s}{%s}\n"
+ (bbdb-tex-field 'xfield-label (symbol-name (car x)))
+ (bbdb-tex-field 'xfield (cdr x))))))
+
+ ((symbolp elt) ; xfield as single string
+ ;; The value of an xfield may be a sexp instead of a string.
+ ;; Ideally, a sexp should be formatted by `pp-to-string',
+ ;; then printed verbatim.
+ (let ((val (format "%s" (bbdb-record-field record elt))))
+ (if val
+ (insert (format "\\xfield{%s}{%s}\n" elt
+ (bbdb-tex-field elt (bbdb-concat elt val)))))))
+
+ ((consp elt) ; xfield as list of strings
+ (bbdb-tex-list
+ (bbdb-split (car elt)
+ (format "%s" (bbdb-record-field record (car elt))))
+ elt
+ `(lambda (x)
+ (format "\\xfield{%s}{%s}\n" ',(car elt)
+ (bbdb-tex-field ',(car elt) x)))))
+
+ (t (error "Rule `%s' undefined" elt)))))))
+
+ ;; Epilog
+ (let ((epilog (nth 1 (assq 'epilog rule))))
+ (when epilog
+ (insert "% begin BBDB epilog\n" epilog)
+ (unless (bolp) (insert "\n"))))))
+ (setq buffer-undo-list nil)
+ (save-buffer))
+
+(provide 'bbdb-tex)
+
+;;; bbdb-tex.el ends here