Boris Goldowsky ([EMAIL PROTECTED]) pointed me to some problems of
bbdb-letter which I sent to the list yesterday. Here is an improved version.

 -- Josef


-------------------------------------cut here-----------------------------------
;;; This is bbdb-letter.el, version 0.96.
;;; It was partly derived from bbdb-print.el, version 2.3.
;;;
;;; The Insidious Big Brother Database 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 1, 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 GNU Emacs; see the file COPYING.  If not, write to
;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
;;;
;;;
;;;
;;; This module is for creating formatted excerpts of BBDB databases.
;;; It can be used to create letter templates or address/phone lists
;;; with LaTeX/TeX. It might also be used to create, e.g., import
;;; files for a EXCEL(tm) database. 
;;;
;;; USE:  In the *BBDB* buffer, type l or *l to create a template of
;;;       the current bbdb-record or of all the currently displayed
;;;       records using the format (usually LaTeX/TeX) it will prompt
;;;       for. It will prompt you for a filename and for
;;;       a format (type of document).  Then run LaTeX/TeX (or an
;;;       appropriate processor) on that file and
;;;       print it out.
;;;
;;;       In order to use bbdb-letter for personal purposes, it is
;;;       very likely that you will need to define your own document
;;;       types/formats.
;;;
;;;       Please direct any comments, improvements, or bug reports to
;;;       [EMAIL PROTECTED] I would also be happy to
;;;       integrate new document types/formats.
;;;
;;; INSTALLATION: Put this file somewhere on your load-path.
;;;       Put (require 'bbdb-letter) in your .emacs, or autoload it.
;;;       Put bbdb-print.tex (from bbdb-print) somewhere on your
;;;          TEXINPUTS path, or put its absolute pathname in
;;;          bbdb-print-format-file-name.
;;;
;;;       In order to use the "phone list" format, you will need the
;;;       supertab.sty LaTeX style. This can be obtained, e.g., by
;;;       mail from [EMAIL PROTECTED] (put the line "SENDME STY.SUPERTAB"
;;;       into the meassage) an put supertab.sty on your TEXINPUTS
;;;       path.
;;;
;;; DEFINITION OF DOCUMENT TYPES/FORMATS: New document types for
;;;       bbdb-letter are defined by extending or redefining the variable
;;;       bbdb-letter-alist.
;;;
;;;       Any pair of bbdb-letter-alist consists of a string denoting
;;;       the document type and a number of elisp expressions. The
;;;       elisp expressions should assign (setq ...) values to the
;;;       variables bbdb-print-prolog, bbdb-print-separator,
;;;       bbdb-print-record, bbdb-print-epilog, and
;;;       bbdb-print-address-default.
;;;
;;;       Customization usually extends bbdb-letter-alist as follows:
;;;       (setq bbdb-letter-alist
;;;             (append bbdb-letter-alist
;;;                   '((\"myown letter\" 
;;;                      ;;
;;;                      (setq bbdb-letter-prolog
;;;                            '(\"\\documentstyle ...
;;;                              ... ))))))")
;;; VARIABLES:
;;;       bbdb-letter-prolog
;;;           A list of strings forming the beginning of the LaTeX/TeX
;;;           document.
;;;       bbdb-letter-separator
;;;           A string which is passed to format to produce a
;;;           separator, when the first character changes.
;;;           bbdb-letter-separator may also have the value nil.
;;;       bbdb-letter-epilog
;;;           A list of strings forming the end of the LaTeX/TeX.
;;;           document.
;;;       bbdb-letter-address-default
;;;           String denoting the default address location.
;;;       bbdb-letter-phone-default
;;;           String denoting the default phone location.
;;;       bbdb-letter-record
;;;           This is a list consisting of strings, bbdb-keywords,
;;;           lists, (bball ...) or (bbif ...) exprs.
;;;
;;;           NOTE: An expression (bbif condition ifbranch elsebranch)
;;;           or (bball ...) is NOT a regular elisp expression! It can
;;;           not be evaluated! It is simply a data structure or macro
;;;           which will be expanded later.
;;;
;;;           If an element of the list bbdb-letter-record is a
;;;           string: This string is copied to the output document
;;;                   (escape characters "\" for elisp strings are
;;;                   necessary)
;;;           bbdb-keyword: this is interpreted as a bbdb keyword. It
;;;                   is a symbol and it may be one out of
;;;                    ('name 'firstname 'lastname 'company 'aka 'net
;;;                     'notes 'addresses 'address-location 'address-street1
;;;                     'address-street2 'address-street3 'address-city
;;;                     'address-state 'address-zip-string 'phone-location
;;;                     'phone-string 'phones)
;;;                   or a user defined keyword of a bbdb record (see
;;;                   bbdb-propnames for user defined keywords).
;;;           bbif:   has the form
;;;                     (bbif 'bbdb-keyword if-part else-part)
;;;                   where if-part and else-part again is a string,
;;;                   bbdb-keyword, list, (bball ...) or (bbif ...) expr.
;;;           bball:  has the form
;;;                     (bball addresses-or-phones separator)
;;;                   where addresses-or-phones is one of
;;;                   ('addresses 'phones)
;;;                   and separator again is a string,
;;;                   bbdb-keyword, list, (bball ...) or (bbif ...) expr.
;;;           list:   This is only for grouping. Any element of the list
;;;                   is a string, bbdb-keyword, list, (bball ...) or
;;;                   (bbif ...) expr.
;;;
;;; EXAMPLE:
;;;  see also (defvar bbdb-letter-alist ...) below.
;;;
;;;  (setq bbdb-letter-alist
;;;        (append bbdb-letter-alist
;;;           '(("my own letter" 
;;;              ;;
;;;              (setq bbdb-letter-prolog
;;;                    '("\\documentstyle[11pt]{myletter}\n"
;;;                      "\\germanletter\n"
;;;                      "\\dvips        % Driver-Selection\n"
;;;                      "% --- Signature\n"
;;;                      "\\name{\\vspace{2cm}(Steven Spielberg)}\n"
;;;                      "\\aktzei{}\n"
;;;                      "\\begin{document}\n\n"))
;;;              ;;
;;;              (setq bbdb-letter-separator "\\separator{%s}\n\n")
;;;              ;;
;;;              (setq bbdb-letter-record
;;;                    '("\\begin{letter}{" 
;;;                      (bbif title title "Mr") " "
;;;                      ;; well, this requires that you have a user defined
;;;                      ;; field called tex-name ...
;;;                      (bbif tex-name tex-name (bbif name name)) " \\\\"
;;;                      (bbif company (company " \\\\"))
;;;                      (bbif address-street1 (address-street1 " \\\\"))
;;;                      (bbif address-street2 (address-street2 " \\\\"))
;;;                      (bbif address-street3 (address-street3 " \\\\"))
;;;                      "[1ex]\n" "{\\bf "
;;;                      address-zip-string " " address-city "}}\n"
;;;                      "\\opening{Dear Mr.\ "
;;;                      lastname ",}\n"
;;;                      "\\sloppy\n"
;;;                      "%****************** BEGIN TEXT ******************\n\n"
;;;                      "%******************   END TEXT ******************\n"
;;;                      "\\closing{Sincerely yours}\n"
;;;                      "%\\cop{}\n"
;;;                      "%\\encl{}\n"
;;;                      "\\end{letter}\n\n"))
;;;              ;;
;;;              (setq bbdb-letter-epilog '("\\end{document}\n"))
;;;              ;;
;;;              (setq bbdb-letter-address-default "office"))
;;;             )))
;;;
;;; This program was written by Josef Schneeberger
;;; <[EMAIL PROTECTED]>. The code is based on the bbdb-print
;;; (version 2.3) program of Boris Goldowsky
;;; <[EMAIL PROTECTED]> and Dirk Grunwald
;;; <[EMAIL PROTECTED]> using a TeX format from Luigi Semenzato
;;; <[EMAIL PROTECTED]>. Thanks to Boris Goldowsky
;;; <[EMAIL PROTECTED]>, Tim Geisler
;;; <[EMAIL PROTECTED]> and
;;; [EMAIL PROTECTED] (Franz Kurfess) for comments and
;;; bug reports.
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; ToDo:
;;; 1. Documentation strings for all variables and function definition
;;; 2. insert quotation feature of bbdb-print.el. I think the best variant
;;;    would be to introduce a new keword "bbquote" (like "bball" or "bbif")
;;; 3. "<lastname>.tex" as default-filename
;;; 4. Default for Documenttype
;;; 5. texinfo file (integrated into the bbdb documentation)
;;;
;;;for debugging ...
;;;(insert (apply 'concat (bbdb-format myrecord mystringlist nil 'latex)))
;;;(bbdb-record-get-field-internal-2 (bbdb-current-record) 'name)
;;;(("project") ("title") ("alias") ("country") ("telex") ("tex-name"))
;;;(setq myrecord (bbdb-current-record))

(require 'bbdb)
(require 'bbdb-com)

(define-key bbdb-mode-map "l" 'bbdb-compose-letter)

;;;
;;; Variables
;;;

(defvar bbdb-letter-file-name "bbdb.tex"
  "*Default file name for printouts of BBDB database.")

(defvar bbdb-letter-directory-name "~/"
  "*Default directory for printouts of BBDB database.")

(defvar bbdb-letter-address-default nil
  "*String denoting the default address location.")

(defvar bbdb-letter-phone-default nil
  "*String denoting the default phone location.")

(defvar bbdb-letter-prolog nil
  "*A list of strings forming the beginning of the LaTeX/TeX document.")

(defvar bbdb-letter-separator nil
  "*A string which is passed to format to produce a separator, when
the first character changes.

bbdb-letter-separator may also have the value nil.")

(defvar bbdb-letter-record nil
  "*This is a list consisting of strings, bbdb-keywords, lists,
(bball ...) or (bbif ...) exprs.

See documentation for more details.")

(defvar bbdb-letter-epilog nil
  "*A list of strings forming the end of the LaTeX/TeX document.")

(defvar bbdb-letter-alist
      '(("LaTeX letter" 
         ;;
         (setq bbdb-letter-prolog 
               '("\\documentstyle{letter}\n"
                "\\name{Dr. L. User}\n"
                "\\signature{Larry User}\n"
                "\\address{3245 Foo St.\\\\Gnu York}\n"
                "\\location{Room 374}\n"
                "\\telephone{(415)123-4567}\n"
                "% \\makelabels\n"
                "\\begin{document}\n\n"))
         ;;
         (setq bbdb-letter-separator "")
         ;;
         (setq bbdb-letter-record
               '("\\begin{letter}{Mr. " 
                (bbif tex-name tex-name (bbif name name)) " \\\\"
                (bbif company (company " \\\\"))
                (bbif address-street1 (address-street1 " \\\\"))
                (bbif address-street2 (address-street2 " \\\\"))
                (bbif address-street3 (address-street3 " \\\\"))
                "[1ex]\n" "{\\bf "
                address-city ", " address-zip-string "}}\n"
                "\\opening{Dear "
                firstname ",}\n"
                "\\sloppy\n"
                "%****************** Start TEXT ******************\n\n"
                "%******************  End  TEXT ******************\n"
                "\\closing{ Yours truly,}\n"
                "%\\cop{}\n"
                "%\\encl{}\n"
                "\\end{letter}\n\n"))
         ;;
         (setq bbdb-letter-epilog '("\\end{document}\n"))
         ;;
         (setq bbdb-letter-address-default nil))   ; if nil, no default
        ("A4 LaTeX letter" 
         ;;
         (setq bbdb-letter-prolog 
               '("\\documentstyle{a4letter}\n"
                "\\name{Dr. L. User}\n"
                "\\signature{Larry User}\n"
                "\\address{3245 Foo St.\\\\Gnu York}\n"
                "\\location{Room 374}\n"
                "\\telephone{(415)123-4567}\n"
                "% \\makelabels\n"
                "\\begin{document}\n\n"))
         ;;
         (setq bbdb-letter-separator "")
         ;;
         (setq bbdb-letter-record
               '("\\begin{letter}{Mr. " 
                (bbif tex-name tex-name (bbif name name)) " \\\\"
                (bbif company (company " \\\\"))
                (bbif address-street1 (address-street1 " \\\\"))
                (bbif address-street2 (address-street2 " \\\\"))
                (bbif address-street3 (address-street3 " \\\\"))
                "[1ex]\n" "{\\bf "
                address-city ", " address-zip-string "}}\n"
                "\\opening{Dear "
                firstname ",}\n"
                "\\sloppy\n"
                "%****************** Start TEXT ******************\n\n"
                "%******************  End  TEXT ******************\n"
                "\\closing{ Yours truly,}\n"
                "%\\cop{}\n"
                "%\\encl{}\n"
                "\\end{letter}\n\n"))
         ;;
         (setq bbdb-letter-epilog '("\\end{document}\n"))
         ;;
         (setq bbdb-letter-address-default nil))   ; if nil, no default
        ("LaTeX multiple letter" 
         ;;
         (setq bbdb-letter-prolog 
               '("\\documentstyle{letter}\n"
                 "\\name{Dr. L. User}\n"
                 "\\signature{Larry User}\n"
                 "\\address{3245 Foo St.\\\\Gnu York}\n"
                 "\\location{Room 374}\n"
                 "\\telephone{(415)123-4567}\n"
                 "% \\makelabels\n"
                 "\\newcommand{\\MYLETTER}[2]{"
                 "\\begin{letter}{#1}" 
                 "\\opening{#2}\n"
                 "  \\sloppy\n"
                 "  %****************** Start TEXT ******************\n\n"
                 "  %******************  End  TEXT ******************\n"
                 "  \\closing{ Yours truly,}\n"
                 "  %\\cop{}\n"
                 "  %\\encl{}\n"
                 "  \\end{letter}}\n\n"
                 "\\begin{document}\n\n"))
         ;;
         (setq bbdb-letter-separator nil)
         ;;
         (setq bbdb-letter-record
               '("\\MYLETTER{Mr. "
                 (bbif tex-name tex-name (bbif name name)) " \\\\"
                 (bbif company (company " \\\\"))
                 (bbif address-street1 (address-street1 " \\\\"))
                 (bbif address-street2 (address-street2 " \\\\"))
                 (bbif address-street3 (address-street3 " \\\\"))
                 "[1ex] " "{\\bf "
                 address-city ", " address-zip-string
                 "}}{Dear " firstname ",}\n\n"))
         ;;
         (setq bbdb-letter-epilog '("\\end{document}\n"))
         ;;
         (setq bbdb-letter-address-default nil))   ; if nil, no default
        ("phone list" 
         ;;
         (setq bbdb-letter-prolog '("\\documentstyle[supertab]{article}\n"
                                   "\\begin{document}\n\n"
                                   "\\begin{supertabular}{|ll|r|}\n"))
         ;;
         (setq bbdb-letter-separator
               "\\hline\n\\multicolumn{3}{|c|}{\\bf %s}\\\\\n\\hline\n")
         ;;
         (setq bbdb-letter-record
               '(lastname ", & " firstname " & " phone-string " \\\\\n"))
         ;;
         (setq bbdb-letter-epilog '("\\hline\n"
                                   "\\end{supertabular}\n"
                                   "\\end{document}\n"))
         ;;
         (setq bbdb-letter-address-default nil))
        ("address booklet" 
         ;;
         (setq bbdb-letter-prolog '("\\documentstyle{article}\n"
                                   "\\setlength{\\parindent}{0pt}\n"
                                   "\\setlength{\\textheight}{16cm}\n"
                                   "\\begin{document}\n"
                                   "\\small\n"))
         ;;
         (setq bbdb-letter-separator "\\newpage\n")
         ;;
         (setq bbdb-letter-record
               '("\\parbox[t]{6cm}{"
                 "\\underline{" firstname " " lastname "} \\\\"
                 (bbif company (company " \\\\[1ex]\n") "[1ex]\n")
                 "\\hspace*{2mm}\\begin{tabular}[t]{p{60mm}}\n"
                 (bball addresses
                        ("{\\it " address-location "}\\\\\n"
                         (bbif address-street1 (address-street1 " \\\\"))
                         (bbif address-street2 (address-street2 " \\\\"))
                         (bbif address-street3 (address-street3 " \\\\"))
                         " {\\bf " address-zip-string " " address-city
                         "}\\\\[1ex]"))
                 "  \\end{tabular}}\n"
                 "\\hspace{-2cm}\n"
                 "\\parbox[t]{5cm}{\\hfill\n"
                 "  \\begin{tabular}[t]{r}\n"
                 (bball phones
                        (phone-location ": " phone-string " \\\\"))
                 "  \\end{tabular}}\\\\[2ex]\n"))
         ;;
         (setq bbdb-letter-epilog '("\\end{document}\n"))
         ;;
         (setq bbdb-letter-address-default nil)) ; if nil, no default
        ("BBDB print list syle" 
         ;;
         (setq bbdb-letter-prolog
               '("%%%% ====== Phone/Address list in -*-TeX-*- Format =====\n"
                 "%%%%        *LIKE* bbdb-print, version 3.0\n\n"
                 "\\input bbdb-print\n"
                 "\\input multicol\n\n"
                 "% See bbdb-print.tex with the following parameters\n"
                 "\\setsize{6}\n"
                 "\\setseparator{2}\n"
                 "\\threecol\n\n"))
         ;;
         (setq bbdb-letter-separator "\\separator{%s}\n\n")
         (setq bbdb-letter-record
               '("\\beginrecord\n"
                 "\\firstline{"
                 (bbif tex-name tex-name (firstname " " lastname))
                 "}{}\n"
                 (bbif company ("\\comp{" company "}\n"))
                 (bbif phones
                       (bball phones
                              ("\\phone{" phone-location ": " phone-string
                               "}\n")))
                 (bbif net ("\\email{" net "}\n"))
                 (bbif addresses
                       (bball addresses
                              ("\\address{"
                               (bbif address-street1 (address-street1 " \\\\"))
                               (bbif address-street2 (address-street2 " \\\\"))
                               (bbif address-street3 (address-street3 " \\\\"))
                               (bbif address-city (address-city))
                               (bbif address-state (", " address-state))
                               (bbif address-zip-string
                                     (", " address-zip-string))
                               "}\n")))
                 (bbif notes ("\\notes{" notes "}\n"))
                 "\\endrecord\n\n"))
         ;;
         (setq bbdb-letter-epilog '("\\endaddresses\n\\bye\n"))
         ;;
         (setq bbdb-letter-address-default nil)) ; if nil, no default
        )
      "*Alist defining document types for bbdb-letter.

Any pair of bbdb-letter-alist consists of a string denoting the
document type and a number of elisp expressions. The elisp expressions
should assign (setq ...) values to the variables bbdb-letter-prolog,
bbdb-letter-separator, bbdb-letter-record, bbdb-letter-epilog, and
bbdb-letter-address-default.

Customization usually extends bbdb-letter-alist as follows:
(setq bbdb-letter-alist
      (append bbdb-letter-alist
              '((\"myown letter\" 
                 ;;
                 (setq bbdb-letter-prolog
                       '(\"\\documentstyle ...
                         ... ))))))")

(defvar current-char nil)
(defvar bbdb-current-address nil)
(defvar bbdb-current-phone nil)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun bbdb-compose-letter (bbdb-record)
  "Compose a letter out from the selected BBDB entries.
The first (most-recently-added) address is used if there are more than one.
\\<bbdb-mode-map>
If \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-compose-letter]\" is \
used instead of simply \"\\[bbdb-compose-letter]\", then letters are created \
to all of the
folks listed in the *BBDB* buffer instead of just the person at point."
  (interactive (list (if (bbdb-do-all-records-p)
                         (mapcar 'car bbdb-records)
                       (bbdb-current-record))))
  (let ((file-name
         (expand-file-name
          (read-file-name "Print To File: "
                          (concat bbdb-letter-directory-name
                                  bbdb-letter-file-name))))
        (completion-ignore-case t))
    (find-file file-name)
    (if (if (> (buffer-size) 0)
            (not (yes-or-no-p "File is not empty; delete contents "))
          nil)
        nil
      (delete-region (point-min) (point-max))
      (mapcar 'eval
              (cdr (assoc (completing-read "Type of document: "
                                           bbdb-letter-alist
                                           nil t)
                          bbdb-letter-alist)))
      (if (consp bbdb-record)
          (bbdb-compose-letter-many bbdb-record)
        (bbdb-compose-letter-1 bbdb-record)))))

(defun bbdb-compose-letter-1 (bbdb-record)    
  (if bbdb-inside-electric-display
      (bbdb-electric-throw-to-execute
        (list 'bbdb-compose-letter bbdb-record)))
  ;; else...

  (cond ((null bbdb-record) (error "record unexists"))
;       ((null (bbdb-record-addresses bbdb-record))
;        (error "Current record unhas addresses."))
        (t (insert (apply 'concat (bbdb-format bbdb-record bbdb-letter-record)))
           (goto-char (point-max)) (insert (apply 'concat bbdb-letter-epilog))
           (goto-char (point-min)) (insert (apply 'concat bbdb-letter-prolog))
           )))

(defun bbdb-compose-letter-many (bbdb-records)
  (if bbdb-inside-electric-display
      (bbdb-electric-throw-to-execute
        (list 'bbdb-compose-letter-many (list 'quote bbdb-records))))
  ;; else...

  (let (first-char)
    (insert (apply 'concat bbdb-letter-prolog))
    (while bbdb-records
      ;;
      ;; Section header, if neccessary.
      ;;
      (setq first-char (substring (concat (bbdb-record-sortkey
                                           (car bbdb-records)) "?") 0 1))
      (if (and current-char
               (string-equal first-char current-char))
          nil
        (if bbdb-letter-separator
            (insert (format bbdb-letter-separator (upcase first-char))))
        (setq current-char first-char))
      ;;
      ;; the record itself
      ;;
      (insert (apply 'concat
                     (bbdb-format (car bbdb-records) bbdb-letter-record)))
      (setq bbdb-records (cdr bbdb-records)))
    (insert (apply 'concat bbdb-letter-epilog)))
  (if (re-search-backward "^Subject: $" nil t) (end-of-line)))

(defun bbdb-format (record stringlist &optional  quotation)
  ;;
  ;; This expands the description of a list of strings and the
  ;; corresponding substructures. The resulting list is concatenated
  ;; an inserted into the buffer.
  ;;
  (setq bbdb-current-address nil)
  (setq bbdb-current-phone nil)
  (let (nsl ns)
    (while stringlist
      (setq ns (bbdb-format-1 record (car stringlist)  quotation))
      (if (listp ns)
          (setq nsl (append nsl ns))
        (setq nsl (append nsl (list ns))))
      (setq stringlist (cdr stringlist)))
    nsl))

(defun bbdb-format-1 (record string &optional quotation)
  ;;
  ;; Subfunction of bbdb-format to expand a single item on the
  ;; stringlist passed to bbdb-format. If the item is a list,
  ;; bbdb-format-1 is called recursively. Lists may be one of bbif,
  ;; bball or simply a list for blocking.
  ;;
  (cond ((stringp string) string)
        ((symbolp string)
         ;; be shure to return nil if there is an empty string.
         (bbdb-record-get-field-string record string quotation))
        ((listp string)
         (cond ((eq (car string) 'bbif)
                (if (bbdb-record-get-field-string record (nth 1 string)
                                                  quotation)
                    (bbdb-format-1 record (nth 2 string)  quotation)
                  (if (> (length string) 3)
                      (bbdb-format-1 record (nthcdr 3 string)  quotation))))
               ((and (eq (car string) 'bball)
                     (eq (nth 1 string) 'addresses))
                (mapcar (function (lambda (a)
                                    (setq bbdb-current-address a)
                                    (bbdb-format-1 record (nth 2 string)
                                                   quotation)))
                        (bbdb-record-addresses record)))
               ((and (eq (car string) 'bball)
                     (eq (nth 1 string) 'phones))
                (mapcar (function (lambda (p)
                                    (setq bbdb-current-phone p)
                                    (bbdb-format-1 record (nth 2 string)
                                                   quotation)))
                        (bbdb-record-phones record)))
               (t (let (nsl ns)
                    (while string
                      (setq ns (bbdb-format-1 record (car string) quotation))
                      (if (listp ns)
                          (setq nsl (append nsl ns))
                        (setq nsl (append nsl (list ns))))
                      (setq string (cdr string)))
                    (apply 'concat nsl)))))))


(defun bbdb-record-get-field-string (record field quotation)
  ;;
  ;; returns the database entry for a symbol.
  ;; We have to distinguish between standard fields and user defined
  ;; fields.
  ;;
  (funcall
   ;; do not return the empty string !
   (function (lambda (string) (if (and (stringp string) (string= string ""))
                                  nil
                                string)))
   (cond ((eq field 'name)              (bbdb-record-name record))
         ((eq field 'net)               (bbdb-record-net record))
         ((eq field 'aka)               (bbdb-record-aka record))
         ((eq field 'firstname)         (bbdb-record-firstname record))
         ((eq field 'lastname)          (bbdb-record-lastname record))
         ((eq field 'company)           (bbdb-record-company record))
         ((eq field 'notes)             (bbdb-record-notes record))
         ((eq field 'phones)            (bbdb-record-phones record))
         ((eq field 'addresses)         (bbdb-record-addresses record))
         ((memq field '(address-location address-street1 address-street2
                        address-street3 address-city address-state
                        address-zip-string))
          (bbdb-record-get-addressfield-string record field))
         ((memq field '(phone-location phone-string))
          (bbdb-record-get-phonefield-string record field))
         ((and (listp (bbdb-record-raw-notes record))
               (assq field (bbdb-record-raw-notes record)))
          (cdr (assq field (bbdb-record-raw-notes record))))
         ((assoc (symbol-name field) (bbdb-propnames))
          nil)                          ; field is defined, but the current
                                        ; record has no such entry.
                                        ; Therfore nil
         (t (error "doubleplus ungood: unknown field type %s\n
You probably did not define field type %s in your BBDB database." field)))))

(defun bbdb-record-get-addressfield-string (record field)
  (let* ((addrs (bbdb-record-addresses record))
         (addrs-alist (mapcar (function (lambda (a)
                                          (cons (bbdb-address-location a) a)))
                              addrs))
         (completion-ignore-case t))
    (if (not addrs)
        ;; return nil if there is no address
        nil
      (cond (bbdb-current-address nil)
            ;; ... else
            ;; if there is only one address ... grab it
            ((= (length addrs) 1)
             (setq bbdb-current-address (car addrs)))
            ;; ... else
            ;; try to find bbdb-letter-address-default address
            ((and bbdb-letter-address-default
                  (assoc bbdb-letter-address-default addrs-alist))
             (while addrs
               (cond ((equal bbdb-letter-address-default 
                             (bbdb-address-location (car addrs)))
                      (setq bbdb-current-address (car addrs))
                      (setq addrs nil))
                     (t (setq addrs (cdr addrs))))))
            ;; ... else
            ;; if nothing was found, ask user
            (t (setq bbdb-current-address
                     (cdr (assoc (completing-read
                                  (format "Which Address of %s: "
                                          (bbdb-record-name record))
                                  addrs-alist nil t)
                                 addrs-alist)))) )
      ;; ... now, return the field info ...
      (cond ((eq field 'address-location)
             (bbdb-address-location bbdb-current-address))
            ((eq field 'address-street1)
             (bbdb-address-street1 bbdb-current-address))
            ((eq field 'address-street2)
             (bbdb-address-street2 bbdb-current-address))
            ((eq field 'address-street3)
             (bbdb-address-street3 bbdb-current-address))
            ((eq field 'address-city)
             (bbdb-address-city bbdb-current-address))
            ((eq field 'address-state)
             (bbdb-address-state bbdb-current-address))
            ((eq field 'address-zip-string)
             (bbdb-address-zip-string bbdb-current-address))))
    ))

(defun bbdb-record-get-phonefield-string (record field)
  (let* ((phones (bbdb-record-phones record))
         (phns-alist (mapcar (function (lambda (p)
                                         (cons (bbdb-phone-location p) p)))
                             phones))
         (completion-ignore-case t))
    (if (not phones)
        ;; return nil if there is no phones
        nil
      (cond (bbdb-current-phone nil)
            ;; ... else
            ;; if there is only one phone ... grab it
            ((= (length phones) 1)
             (setq bbdb-current-phone (car phones)))
            ;; ... else
            ;; try to find bbdb-letter-phone-default phone
            ((and bbdb-letter-phone-default
                  (assoc bbdb-letter-phone-default phns-alist))
             (while phones
               (cond ((equal bbdb-letter-phone-default 
                             (bbdb-phone-location (car phones)))
                      (setq bbdb-current-phone (car phones))
                      (setq phones nil))
                     (t (setq phones (cdr phones))))))
            ;; ... else
            ;; if nothing was found, ask user
            (t (setq bbdb-current-phone
                     (cdr (assoc (completing-read
                                  (format "Which Phone/Fax of %s: "
                                          (bbdb-record-name record))
                                  phns-alist nil t)
                                 phns-alist)))) )
      ;; ... now, return the field info ...
      (cond ((eq field 'phone-location)
             (bbdb-phone-location bbdb-current-phone))
            ((eq field 'phone-string)
             (bbdb-phone-string bbdb-current-phone))))
    ))

;;
;; ... this was stolen from bbdb-print
;; but not yet used ...
;;
(defun bbdb-print-tex-quote (string)
  "Quote any unquoted TeX special characters that appear in STRING.
In other words, # alone will be replaced by \#, but \^ will be left for 
TeX to process as an accent."
  (if string
      (save-excursion
        (set-buffer (get-buffer-create " bbdb-print-tex-quote"))
        (delete-region (point-min) (point-max))
        (insert string)
        (goto-char (point-min))
        (while (not (eobp))
          (cond ((looking-at "[<>=]+") 
                 (replace-match "$\\&$"))
                ((and (looking-at "[#$%&~_]")
                      (not (eq ?\\ (char-after (1- (point))))))
                 (replace-match "\\\\\\&"))
                ((and (looking-at "[{}]")
                      (not (eq ?\\ (char-after (1- (point))))))
                 (replace-match "$\\\\\\&$"))
                ((and (looking-at "\\^")
                      (not (eq ?\\ (char-after (1- (point))))))
                 (replace-match "\\\\^{ }"))
                (t (forward-char 1))))
        (buffer-string))))

(provide 'bbdb-letter)

Reply via email to