Initial Commit
[packages] / xemacs-packages / bbdb / lisp / bbdb-print.el
1 ;;; bbdb-print.el -- for printing BBDB databases using TeX.
2
3 ;;; Authors: Boris Goldowsky <boris@cs.rochester.edu>
4 ;;;          Dirk Grunwald <grunwald@cs.colorado.edu>
5 ;;;          Luigi Semenzato <luigi@paris.cs.berkeley.edu>
6 ;;; Copyright (C) 1993 Boris Goldowsky
7 ;;; Version: 3.92; 4Jan95
8
9 ;;; This file is part of the bbdb-print extensions to the Insidious
10 ;;; Big Brother Database, which is for use with GNU Emacs.
11 ;;;
12 ;;; The Insidious Big Brother Database is free software; you can redistribute
13 ;;; it and/or modify it under the terms of the GNU General Public License as
14 ;;; published by the Free Software Foundation; either version 1, or (at your
15 ;;; option) any later version.
16 ;;;
17 ;;; BBDB is distributed in the hope that it will be useful, but WITHOUT ANY
18 ;;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 ;;; FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20 ;;; details.
21 ;;;
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with GNU Emacs; see the file COPYING.  If not, write to
24 ;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
25
26 ;;; Commentary:
27 ;;;
28 ;;; In the *BBDB* buffer, type P to convert the listing to TeX
29 ;;; format. It will prompt you for a filename.  Then run TeX on that
30 ;;; file and print it out.
31 ;;;
32 ;;; Bbdb-print understands one new bbdb field: tex-name.  If it
33 ;;; exists, this will be used for the printed listing instead of the
34 ;;; name field of that record.  This is designed for entering names
35 ;;; with lots of accents that would mess up mailers, or when for any
36 ;;; reason you want the printed version of the name to be different
37 ;;; from the version that appears on outgoing mail and in the *BBDB*
38 ;;; buffer.  You may want to add tex-name to a omit list of the variable
39 ;;; bbdb-display-layout-alist so you only see it in the printout.
40 ;;; tex-name is exempted from the usual special-character quoting done by
41 ;;; bbdb-print; it is used verbatim.
42 ;;;
43 ;;; Not all fields or records need be printed.  To not print a certain
44 ;;; field, add it to `bbdb-print-omit-fields' (which see).  If after eliding
45 ;;; fields a record contains no interesting information, it will not
46 ;;; be printed at all; the variable `bbdb-print-require' determines
47 ;;; what is meant by "interesting" information.  You can also restrict
48 ;;; printing to just the records currently in the *BBDB* buffer by
49 ;;; using *P instead of P.
50 ;;;
51 ;;; There are various options for the way the formatting is done; most
52 ;;; are controlled by the variable bbdb-print-alist. See its
53 ;;; documentation for the allowed options.
54
55 ;;
56 ;; $Id: bbdb-print.el,v 1.7 2007-02-23 20:24:08 fenk Exp $
57 ;;
58
59 ;;; Installation:
60 ;;;
61 ;;; Put this file somewhere on your load-path.  Put bbdb-print.tex and
62 ;;; bbdb-cols.tex somewhere on your TEXINPUTS path, or put absolute
63 ;;; pathnames into the variable bbdb-print-format-files (which see). Put
64 ;;; (add-hook 'bbdb-load-hook (function (lambda () (require 'bbdb-print))))
65 ;;; into your .emacs, or autoload it.
66 ;;;
67 ;;; This program was adapted for BBDB by Boris Goldowsky
68 ;;; <boris@cs.rochester.edu> and Dirk Grunwald
69 ;;; <grunwald@cs.colorado.edu> using a TeX format designed by Luigi
70 ;;; Semenzato <luigi@paris.cs.berkeley.edu>.
71 ;;; We are also grateful to numerous people on the bbdb-info
72 ;;; mailing list for suggestions and bug reports.
73
74 ;;; Code:
75
76 (require 'bbdb)
77 (require 'bbdb-com)
78
79 ;;; Variables:
80
81 (defcustom bbdb-print-file-name "~/bbdb.tex"
82   "*Default file name for printouts of BBDB database."
83   :group 'bbdb-utilities-print
84   :type 'file)
85
86 (defcustom bbdb-print-omit-fields '(omit tex-name aka mail-alias)
87   "*List of fields NOT to print in address list.
88 See also bbdb-print-require."
89   :group 'bbdb-utilities-print
90   :type '(repeat (symbol :tag "Field to exclude")))
91
92 (defcustom bbdb-print-require '(or address phone)
93   "*What fields are required for printing a record.
94 This is evaluated for each record, and the record will be printed only
95 if it returns non-nil.  The symbols name, company, net, phone,
96 address, and notes will be set to appropriate values when this is
97 evaluated; they will be nil if the field does not exist or is elided.
98
99 The value of this variable can be any lisp expression, but typically
100 it will be used for a boolean combination of the field variables, as
101 in the following simple examples:
102
103   Print only people whose phone numbers are known:
104     (setq bbdb-print-require 'phone)
105   Print people whose names AND companies are known:
106     (setq bbdb-print-require '(and name company))
107   Print people whose names, and either addresses OR phone numbers are known:
108     (setq bbdb-print-require '(and name (or address phone)))."
109   :group 'bbdb-utilities-print
110   :type '(choice (const :tag "Print all records" t)
111                  (symbol :tag "Print all records with this field" phone)
112                  (sexp :tag "Print only when this evaluates to non-nil"
113                        '(or phone address phone))))
114
115 (defun bbdb-print-field-shown-p (field)
116   (not (memq field bbdb-print-omit-fields)))
117
118 (define-widget 'bbdb-print-alist-widget 'repeat
119   "For use in Customize"
120   :args `((choice
121            (cons :tag "Column specification" :value (column . 1)
122                  (const :tag "Column mode" column)
123                  (radio-button-choice (const :tag "One column" 1)
124                                       (const :tag "Two columns" 2)
125                                       (const :tag "Three columns" 3)
126                                       (const :tag "Four columns" 4)
127                                       (const :tag "Quad" quad)
128                                       (const :tag "Grid" grid)))
129            (cons :tag "Separator specification" :value (separator . 0)
130                  (const :tag "Separator" separator)
131                  (radio-button-choice (const :tag "None" 0)
132                                       (const :tag "Line" 1)
133                                       (const :tag "Boxed letters" 2)
134                                       (const :tag "Large boxed letters" 3)
135                                       (const :tag "Large letters" 4)
136                                       (const :tag "Letters with lines" 5)
137                                       (const :tag "Letters with suits" 6)
138                                       (const :tag "Boxed letters with suits" 7)))
139            (cons :tag "Omit certain area codes"
140                  :value (omit-area-code . ,(concat "^("
141                                                    (if (integerp bbdb-default-area-code)
142                                                        (int-to-string bbdb-default-area-code)
143                                                      "000")  ") "))
144                  (const :tag "Omit certain area codes" omit-area-code)
145                  (regexp :tag "Pattern to omit"))
146            (cons :tag "Phone number location" :value (phone-on-first-line . t)
147                  (const :tag "Phone number location" phone-on-first-line)
148                  (choice (const :tag "First home number on same line as name" t)
149                          (const :tag "Don't put the phone number on the name line" nil)
150                          (regexp :tag "Use phone number whose location matches" "^work$")))
151            (cons :tag "Limit included phone numbers" :value (n-phones . 3)
152                  (const :tag "Limit included phone numbers" n-phones)
153                  (integer :tag "Maximum number to include" 3))
154            (cons :tag "Limit included addresses" :value (n-addresses . 3)
155                  (const :tag "Limit included addresses" n-addresses)
156                  (integer :tag "Maximum number to include" 3))
157            (cons :tag "Include additional TeX input files" :value (include-files . nil)
158                  (const :tag "Additional TeX input files to include" include-files)
159                  (repeat (file :tag "TeX file to include")))
160            (cons :tag "Font type selection" :value (ps-fonts . nil)
161                  (const :tag "Select font type" ps-fonts)
162                  (choice (const :tag "Use standard TeX fonts" nil)
163                          (const :tag "Use Postscript fonts" t)))
164            (cons :tag "Font size selection" :value (font-size . 10)
165                  (const :tag "Select font size" font-size)
166                  (integer :tag "Font size in points" 10))
167            (cons :tag "Page height selection" :value (v-size . nil)
168                  (const :tag "Select page height" v-size)
169                  (choice (const :tag "Use TeX default" nil)
170                          (string :tag "Height (must be valid TeX dimension)" "9in")))
171            (cons :tag "Page width selection" :value (h-size . nil)
172                  (const :tag "Select page width" h-size)
173                  (choice (const :tag "Use TeX default" nil)
174                          (string :tag "Width (must be valid TeX dimension)" "6in")))
175            (cons :tag "Vertical offset (top margin)" :value (voffset . nil)
176                  (const :tag "Select vertical offset (top margin)" voffset)
177                  (choice (const :tag "Use TeX default" nil)
178                          (string :tag "Vertical offset (must be valid TeX dimension)" "1in")))
179            (cons :tag "Horizontal offset (left margin)" :value (hoffset . nil)
180                  (const :tag "Select horizontal offset (left margin)" hoffset)
181                  (choice (const :tag "Use TeX default" nil)
182                          (string :tag "Horizontal offset (must be valid TeX dimension)" "1in")))
183            (cons :tag "Quad format height" :value (quad-vsize . "")
184                  (const :tag "Select height for quad format pages" quad-vsize)
185                  (string :tag "Height (must be valid TeX dimension)"))
186            (cons :tag "Quad format width" :value (quad-hsize . "")
187                  (const :tag "Select width for quad format pages" quad-hsize)
188                  (string :tag "Width (must be valid TeX dimension)")))))
189
190 (defcustom bbdb-print-alist
191   `((omit-area-code . ,(concat "^(" (if (integerp bbdb-default-area-code)
192                                         (int-to-string bbdb-default-area-code)
193                                       "000") ") "))
194     (phone-on-first-line . "^[ \t]*$")
195     (ps-fonts . nil)
196     (font-size . 6)
197     (quad-hsize . "3.15in")
198     (quad-vsize . "4.5in"))
199   "*Formatting options for `bbdb-print', all formats.
200 This is an alist of the form ((option1 . value1) (option2 . value2) ...)
201
202 You can have separate settings for brief and non-brief printouts;
203 see the variables `bbdb-print-brief-alist' and `bbdb-print-full-alist'.
204 Settings there will override the common settings in this variable.
205
206 The possible options and legal values are:
207  - columns: 1, 2, 3, 4 or 'quad (4 little 2-column pages per sheet)
208      or 'grid (12 credit-card-sized pages per sheet).
209  - separator: 0-7, the style of heading for each letter.
210      0=none, 1=line, 2=boxed letters, 3=large boxed letters, 4=large letters,
211      5=letters with lines, 6=letters with suits, 7=boxed letters with suits.
212  - omit-area-code: a regular expression matching area codes to omit.
213  - phone-on-first-line: t means to put first phone number on the same
214      line with the name, nil means just the name.  A string means to
215      use the first phone number whose \"location\" matches that string,
216      which should be a valid regular expression.
217  - n-phones: maximum number of phone numbers to include.
218  - n-addresses: maximum number of addresses to include.
219  - include-files: list of TeX files to \\input.  If these filenames are not
220    absolute, the files must be located somewhere that TeX will find them.
221  - ps-fonts: nonnil means to use them, nil to use standard TeX fonts.
222  - font-size: in points, any integer (assuming fonts in that size exist!).
223  - hsize, vsize: horizontal dimension of pages.  String value can be any valid
224    TeX dimension, or nil to use TeX's default.
225  - hoffset, voffset: shift TeX's output rightward (downward) by this distance
226    (any TeX dimension).  Nil or 0 uses TeX's default positioning.
227  - quad-hsize, quad-vsize: for the quad format, horizontal and
228      vertical size of the little pages.  These must be strings which
229      are valid TeX dimensions, eg \"10cm\"."
230   :group 'bbdb-utilities-print
231   :type 'bbdb-print-alist-widget)
232
233 (defcustom bbdb-print-full-alist
234   '((columns . 3)
235     (separator . 2)
236     (include-files "bbdb-print" "bbdb-cols"))
237   "*Extra options for `bbdb-print' non-brief format.
238 These supplement or override entries in `bbdb-print-alist'; see description
239 of possible contents there."
240   :group 'bbdb-utilities-print
241   :type 'bbdb-print-alist-widget)
242
243 (defcustom bbdb-print-brief-alist
244   '((columns . 1)
245     (separator . 1)
246     (n-phones . 2)
247     (n-addresses . 1)
248     (include-files "bbdb-print-brief" "bbdb-cols"))
249   "*Extra Options for `bbdb-print', brief format.
250 These supplement or override entries in `bbdb-print-alist'; see description
251 of possible contents there."
252   :group 'bbdb-utilities-print
253   :type 'bbdb-print-alist-widget)
254
255 (defconst bbdb-print-filofax-alist
256   (append '((font-size . 12)
257             (columns . 2)
258             (voffset . "-2cm")
259             (hoffset . "-2cm")
260             (vsize   . "27cm"))
261           bbdb-print-full-alist)
262   "Example setup for making pages for a Filofax binder.")
263
264
265 (defcustom bbdb-print-prolog
266   (concat "%%%% ====== Phone/Address list in -*-TeX-*- Format =====\n"
267           "%%%%        produced by bbdb-print, version 3.0\n\n")
268   "*TeX statements to include at the beginning of the `bbdb-print' file."
269   :group 'bbdb-utilities-print
270   :type '(text :format "%t:\n%v"))
271
272 (defcustom bbdb-print-epilog "\\endaddresses\n\\bye\n"
273   "*TeX statements to include at the end of the `bbdb-print' file."
274   :group 'bbdb-utilities-print
275   :type '(text :format "%t:\n%v"))
276
277 (defcustom bbdb-print-net 'primary
278   "*Indicates whether only the primary or all email addresses are printed.
279 Symbol `primary' means print the primary email address only.
280 Symbol `all' means print all email addresses."
281   :group 'bbdb-utilities-print
282   :type '(choice (const primary)
283          (const all)))
284
285 ;;; Functions:
286
287 (defsubst bbdb-print-if-not-blank (string &rest more)
288   "If STRING is not null, then return it concatenated
289 with rest of arguments.  If it is null, then all arguments are
290 ignored and the null string is returned."
291   (if (or (null string) (equal "" string))
292       ""
293     (apply 'concat string more)))
294
295 ;;;###autoload
296 (defun bbdb-print (visible-records to-file brief)
297   "Make a TeX file for printing out the bbdb database.\\<bbdb-mode-map>
298 If \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-print]\" is \
299 used instead of simply \"\\[bbdb-print]\", then includes only the
300 people currently in the *BBDB* buffer.  With a prefix argument, makes
301 a brief \(one-line-per-entry) printout.
302
303 There are various variables for customizing the content & format of
304 the printout, notably the variables `bbdb-print-alist' and
305 `bbdb-print-require'.  See the file bbdb-print.el for more information."
306   (interactive (list (bbdb-do-all-records-p)
307                      (read-file-name "Print To File: "
308                                      (file-name-directory bbdb-print-file-name)
309                                      bbdb-print-file-name
310                                      nil
311                                      (file-name-nondirectory bbdb-print-file-name))
312                      current-prefix-arg))
313   (setq bbdb-print-file-name (expand-file-name to-file))
314   (let* ((alist (append (if brief bbdb-print-brief-alist bbdb-print-full-alist)
315                         bbdb-print-alist))
316          (records (if (not visible-records)
317                       (bbdb-records)
318                     (set-buffer bbdb-buffer-name)
319                     (mapcar 'car bbdb-records)))
320          (psstring (if (cdr (assoc 'ps-fonts alist))
321                        "ps" ""))
322          (columns (cdr (assoc 'columns alist)))
323          (current-letter t)
324          (pofl (cdr (assoc 'phone-on-first-line alist)))
325          (n-phones (cdr (assoc 'n-phones alist)))
326          (n-addresses (cdr (assoc 'n-addresses alist))))
327     (find-file bbdb-print-file-name)
328     (widen) (erase-buffer)
329     (insert bbdb-print-prolog)
330     (let ((dimens '(hsize vsize hoffset voffset))
331           val)
332       (while dimens
333         (setq val (cdr (assoc (car dimens) alist)))
334         (if val
335             (insert (format "\\%s=%s\n" (car dimens) val)))
336         (setq dimens (cdr dimens))))
337     (let ((infiles (cdr (assoc 'include-files alist))))
338       (while infiles
339         (insert (format "\\input %s\n" (car infiles)))
340         (setq infiles (cdr infiles))))
341     (insert (format "\n\\set%ssize{%d}\n"
342                     psstring (cdr (assoc 'font-size alist)))
343             (format "\\setseparator{%d}\n"
344                     (cdr (assoc 'separator alist)))
345             (cond ((eq 'quad columns)
346                    (format "\\quadformat{%s}{%s}"
347                            (cdr (assoc 'quad-hsize alist))
348                            (cdr (assoc 'quad-vsize alist))))
349                   ((eq 'grid columns) "\\grid")
350                   ((= 4 columns) "\\fourcol")
351                   ((= 3 columns) "\\threecol")
352                   ((= 2 columns) "\\twocol")
353                   ((= 1 columns) "\\onecol"))
354             ;; catcodes are font-encoding specific !
355             ;; Add more if you know them
356             (if (equal psstring "ps")
357                 (concat "\n\n"
358                         ;; Adobe Times and Courier
359                         )
360               (concat "\n\n"
361                       ;; ec fonts
362                       "\\catcode`ß=\\active\\chardefß=\"FF"))
363             "\n\n\\beginaddresses\n")
364     (while records
365       (setq current-letter
366             (bbdb-print-format-record (car records) current-letter
367                                       brief pofl n-phones n-addresses))
368       (setq records (cdr records)))
369     (insert bbdb-print-epilog)
370     (goto-char (point-min))))
371
372 (defvar bbdb-address-print-formatting-alist
373   '((bbdb-address-is-continental . bbdb-print-format-address-continental)
374     (nil . bbdb-print-format-address-default))
375   "Alist of address identifying and address formatting functions for printing.
376 The key is an identifying function which accepts an address.  The
377 associated value is a formatting function which inserts the formatted
378 address in the current buffer.  If the identifying function returns
379 non-nil, the formatting function is called.  The nil key is a default
380 value will allways calls the associated formatting function.  Therefore
381 you should always have (nil . bbdb-print-format-address-default) as the
382 last element in the alist.
383
384 The functions must take one argument, the address.
385
386 See also `bbdb-address-formatting-alist'.")
387
388 (defun bbdb-print-format-address-continental (addr)
389   "Insert formated continental address ADDR in current buffer for printing.
390 This format is used in western Europe, for example.
391
392 This function is a possible formatting function for
393 `bbdb-address-print-formatting-alist'.
394
395 The result looks like this:
396  street
397  street
398  ...
399  zip city, state
400  country"
401   (insert
402    (format
403     "\\address{%s}\n"
404     (bbdb-print-tex-quote
405      (if addr
406          (concat
407           (mapconcat (function (lambda(str)
408                                  (if (= 0 (length (bbdb-string-trim str)))
409                                      ()
410                                    (concat str"\\\\\n"))))
411                      (bbdb-address-streets addr)
412                      "")
413           (let ((c (bbdb-address-city addr))
414                 (s (bbdb-address-state addr))
415                 (z (bbdb-address-zip addr)))
416             (if (or (> (length c) 0)
417                     (> (length z) 0)
418                     (> (length s) 0))
419                 (concat z (if (and (> (length z) 0)
420                                    (> (length c) 0)) " " "")
421                         c (if (and (or (> (length z) 0)
422                                        (> (length c) 0))
423                                    (> (length s) 0)) ", " "")
424                         s "\\\\\n") ""))
425           (bbdb-print-if-not-blank (bbdb-address-country addr) "\\\\\n"))
426        "")))))
427
428 (defun bbdb-print-format-address-default (addr)
429   "Insert formated address ADDR in current buffer for printing.
430 This is the default format; it is used in the US, for example.
431
432 This function is a possible formatting function for
433 `bbdb-address-print-formatting-alist'.
434
435 The result looks like this:
436  street
437  street
438  ...
439  city, state  zip
440  country"
441   (insert
442    (format
443     "\\address{%s}\n"
444     (bbdb-print-tex-quote
445      (if addr
446          (concat
447           (mapconcat (function (lambda(str)
448                                  (if (= 0 (length (bbdb-string-trim str)))
449                                      ()
450                                    (concat str "\\\\\n"))))
451                      (bbdb-address-streets addr)
452                      "")
453           (let ((c (bbdb-address-city addr))
454                 (s (bbdb-address-state addr))
455                 (z (bbdb-address-zip addr)))
456             (if (or (> (length c) 0)
457                     (> (length z) 0)
458                     (> (length s) 0))
459                 (concat c (if (and (> (length c) 0)
460                                    (> (length s) 0)) ", " "")
461                         s (if (and (or (> (length c) 0)
462                                        (> (length s) 0))
463                                    (> (length z) 0)) "  " "")
464                         z "\\\\\n") ""))
465           (bbdb-print-if-not-blank (bbdb-address-country addr) "\\\\\n"))
466        "")))))
467
468 (defun bbdb-print-format-record (record current-letter
469                                         brief pofl n-phones n-addresses)
470   "Insert the bbdb RECORD in TeX format.
471 Second arg CURRENT-LETTER is the first letter of the sortkey of the previous
472 record.  If this is non-nil and RECORD begins differently, a section heading is
473 output.  If CURRENT-LETTER is t always produces a heading.
474 3rd argument BRIEF is for 1-line-per-record printouts.
475 Args 3-5 PHONE-ON-FIRST-LINE, N-PHONES, and N-ADDRESSES are the respective
476 values from `bbdb-print-alist'.
477
478 The return value is the new CURRENT-LETTER."
479
480   (bbdb-debug (if (bbdb-record-deleted-p record)
481                   (error "plus ungood: tex formatting deleted record")))
482
483   (let* ((first-letter
484           (substring (concat (bbdb-record-sortkey record) "?") 0 1))
485          (name    (and (bbdb-print-field-shown-p 'name)
486                        (or (bbdb-record-getprop record 'tex-name)
487                            (bbdb-print-tex-quote
488                             (bbdb-record-name record)))))
489          (company (and (bbdb-print-field-shown-p 'company)
490                        (bbdb-record-company record)))
491          (net     (and (bbdb-print-field-shown-p 'net)
492                        (bbdb-record-net record)))
493          (phone   (and (bbdb-print-field-shown-p 'phone)
494                        (bbdb-record-phones record)))
495          (address (and (bbdb-print-field-shown-p 'address)
496                        (bbdb-record-addresses record)))
497          (notes   (bbdb-record-raw-notes record)))
498
499     (if (not (eval bbdb-print-require))
500         nil                             ; lacks required fields
501
502       ;; Section header, if neccessary.
503
504       (if (and current-letter
505                (not (string-equal first-letter current-letter)))
506           (insert (format "\\goodbreak\n\\separator{%s}\n%%\n"
507                           (bbdb-print-tex-quote (upcase first-letter)))))
508
509       (insert "\\beginrecord\n")
510
511       ;; if there is no name, use company instead
512       (if (and (not name) company)
513           (setq name (bbdb-print-tex-quote company)
514                 company nil))
515
516       (let ((rightside ""))
517         (cond ((null phone))
518               ((eq t pofl)
519                (setq rightside (bbdb-print-phone-string (car phone))
520                      phone (cdr phone)))
521               ((stringp pofl)
522                (let ((p (bbdb-print-front-if
523                          (function (lambda (ph)
524                                      (string-match pofl (aref ph 0))))
525                          phone)))
526                  (if p
527                      (setq rightside (bbdb-print-phone-string (car p))
528                            phone (cdr p))))))
529         (insert (format "\\firstline{%s}{%s}\n"
530                         name
531                         (bbdb-print-tex-quote rightside))))
532
533       (if company
534           (insert (format "\\comp{%s}\n" (bbdb-print-tex-quote company))))
535
536       ;; Phone numbers
537
538       (if n-phones
539           (setq phone (bbdb-print-firstn (- n-phones (if pofl 1 0))
540                                          phone brief)))
541       (while phone
542         (if (car phone)
543             (let ((place (aref (car phone) 0))
544                   (number (bbdb-print-phone-string (car phone))))
545               (insert (format "\\phone{%s%s}\n"
546                               (bbdb-print-tex-quote
547                                (bbdb-print-if-not-blank place ": "))
548                               (bbdb-print-tex-quote number))))
549           (insert (format "\\phone{}\n")))
550         (setq phone (cdr phone)))
551
552       ;; Email address
553       ;;  Make all dots legal line-breaks.
554
555       (when net
556     (let ((net-addrs
557            (cond ((eq bbdb-print-net 'primary)
558               (list (car net)))
559              ((eq bbdb-print-net 'all)
560               net)
561              (t nil))))
562       (insert
563        (format
564         "\\email{%s}\n"
565         (mapconcat
566          (lambda (net-addr)
567            (setq net-addr (bbdb-print-tex-quote net-addr))
568            (let ((start 0))
569          (while (string-match "\\." net-addr start)
570            (setq net-addr
571              (concat (substring net-addr 0 (match-beginning 0))
572                  ".\\-"
573                  (substring net-addr (match-end 0))))
574            (setq start (+ 2 (match-end 0)))))
575            net-addr)
576          net-addrs ", ")))))
577
578       ;; Addresses.  FUTURE: If none left, should use phones instead.
579
580       (if n-addresses
581           (setq address
582                 (bbdb-print-firstn n-addresses address brief)))
583       (while address
584         (bbdb-format-address (car address) 'printing)
585         (setq address (cdr address)))
586
587       ;; Notes
588
589       (if (stringp notes)
590           (setq notes (list (cons 'notes notes))))
591       (while notes
592         (let ((thisnote (car notes)))
593           (if (bbdb-print-field-shown-p (car thisnote))
594               (progn
595                 (if (eq 'notes (car thisnote))
596                     (insert (format "\\notes{%s}\n" (bbdb-print-tex-quote
597                                                      (cdr thisnote))))
598                   (insert (format "\\note{%s}{%s}\n"
599                                   (bbdb-print-tex-quote (symbol-name
600                                                          (car thisnote)))
601                                   (bbdb-print-tex-quote (cdr thisnote))))))))
602         (setq notes (cdr notes)))
603
604       ;; Mark end of the record.
605
606       (insert "\\endrecord\n%\n")
607           (setq current-letter first-letter)))
608
609   current-letter)
610
611 (defun bbdb-print-phone-string (phone)
612   "Format PHONE-NUMBER as a string, obeying omit-area-code setting.
613 Omit-area-code is one of the allowed symbols in `bbdb-print-alist', which see."
614   (let ((str (bbdb-phone-string phone))
615         (omit (cdr (assoc 'omit-area-code bbdb-print-alist))))
616     (if (and omit (string-match omit str))
617         (substring str (match-end 0))
618       str)))
619
620 (defun bbdb-print-front-if (func list)
621   "Move first elt of LIST satisfying FUNC to front.
622 The car of the returned list is the first element that returned nonnil;
623 The cdr is the rest of the list.
624 But if the FUNC returns nil for every elements of the LIST, returns nil."
625   (cond ((null list) nil)
626         ((funcall func (car list))
627          list)
628         ((let ((rest (bbdb-print-front-if func (cdr list))))
629            (if rest
630                (cons (car rest)
631                      (cons (car list) (cdr rest))))))))
632
633 (defun bbdb-print-firstn (n list force)
634   "The first N elements of LIST.
635 If 3rd arg FORCE is nonnil, will extend the list to length N if necessary, by
636 adding nil's.  If N is nil, just returns LIST."
637   (cond ((null n) list)
638         ((null list) (if force (make-list n nil) nil))
639         ((<= n 0) nil)
640         (t (cons (car list) (bbdb-print-firstn (1- n) (cdr list) force)))))
641
642 (defun bbdb-print-tex-quote (string)
643   "Quote any unquoted TeX special characters that appear in STRING.
644 In other words, # alone will be replaced by \\#, but \\^ will be left for
645 TeX to process as an accent."
646   (if string
647       (save-excursion
648         (set-buffer (get-buffer-create " bbdb-print-tex-quote"))
649         (erase-buffer)
650         (insert string)
651         (goto-char (point-min))
652         (while (not (eobp))
653           (cond ((looking-at "[<>=]+")
654                  (replace-match "$\\&$"))
655                 ((and (looking-at "[#$%&_]")
656                       (not (eq ?\\ (char-after (1- (point))))))
657                  (insert "\\")
658                  (forward-char 1))
659                 ((and (looking-at "~")
660                       (not (eq ?\\ (char-after (1- (point))))))
661                  (insert "\\")
662                  (forward-char 1)
663                  (insert "{}"))
664                 ((and (looking-at "[{}]")
665                       (not (eq ?\\ (char-after (1- (point))))))
666                  (insert "$\\")
667                  (forward-char 1)
668                  (insert "$"))
669                 (t (forward-char 1))))
670         (buffer-string))))
671
672
673 (provide 'bbdb-print)
674
675 ;;; bbdb-print ends here.
676