Depend on bbdb-com
[bbdb-vcard.git] / bbdb-vcard.el
blobab0ce65ca8de64508cfa552b0bf32bf763e3a731
1 ;;; bbdb-vcard.el --- vCard import/export for BBDB
3 ;; Copyright (c) 2010 Bert Burgemeister
5 ;; Author: Bert Burgemeister <trebbu@googlemail.com>
6 ;; Keywords: data calendar mail news
7 ;; URL: http://github.com/trebb/bbdb-vcard
8 ;; Version: 0.2
10 ;; This program is free software; you can redistribute it and/or
11 ;; modify it under the terms of the GNU General Public License as
12 ;; published by the Free Software Foundation; either version 2, or (at
13 ;; your option) any later version.
15 ;; This program is distributed in the hope that it will be useful, but
16 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 ;; General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
25 ;; The exporter functionality is based on code from
26 ;; bbdb-vcard-export.el by Jim Hourihan and Alex Schroeder.
27 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
30 ;;; Commentary:
31 ;;
32 ;; Purpose
33 ;; =======
34 ;;
35 ;; Import and export of vCards as defined in RFC 2425 and RFC 2426
36 ;; to/from The Insidious Big Brother Database (BBDB).
37 ;;
39 ;; Usage
40 ;; =====
42 ;; vCard Import
43 ;; ------------
45 ;; On a file, a buffer or a region containing one or more vCards, use
46 ;; `bbdb-vcard-import-file', `bbdb-vcard-import-buffer', or
47 ;; `bbdb-vcard-import-region' respectively to import them into BBDB.
49 ;; Preferred input format is vCard version 3.0. Version 2.1 vCards
50 ;; are converted to version 3.0 on import.
53 ;; vCard Export
54 ;; ------------
56 ;; In buffer *BBDB*, press v to export the record under point. Press
57 ;; * v to export all records in buffer into one vCard file. Press *
58 ;; C-u v to export them into one file each.
60 ;; To put one or all vCard(s) into the kill ring, press V or * V
61 ;; respectively.
63 ;; Exported vCards are always version 3.0. They can be re-imported
64 ;; without data loss with one exception: North American phone numbers
65 ;; lose their structure and are stored as flat strings.
68 ;; There are a few customization variables grouped under `bbdb-vcard'.
71 ;; Installation
72 ;; ============
74 ;; Put this file and file vcard.el into your `load-path' and add the
75 ;; following line to your Emacs initialization file:
77 ;; (require 'bbdb-vcard)
80 ;; Implementation
81 ;; ==============
83 ;; vCard Import
84 ;; ------------
86 ;; For conversion of v2.1 vCards into v3.0 on import, Noah Friedman's
87 ;; vcard.el is needed.
89 ;; An existing BBDB record is extended by new information from a vCard
90 ;;
91 ;; (a) if name and company and an email address match
92 ;; (b) or if name and company match
93 ;; (c) or if name and an email address match
94 ;; (d) or if name and birthday match
95 ;; (e) or if name and a phone number match.
97 ;; Otherwise, a fresh BBDB record is created.
99 ;; When `bbdb-vcard-try-merge' is set to nil, there is always a fresh
100 ;; record created.
102 ;; In cases (c), (d), and (e), if the vCard has ORG defined, this ORG
103 ;; would overwrite an existing Company in BBDB.
105 ;; Phone numbers are always imported as strings.
107 ;; For vCard types that have more or less direct counterparts in BBDB,
108 ;; labels and parameters are translated and structured values
109 ;; (lastname; firstname; additional names; prefixes etc.) are
110 ;; converted appropriately with the risk of some (hopefully
111 ;; unessential) information loss. For labels of the vCard types ADR
112 ;; and TEL, parameter translation is defined in
113 ;; `bbdb-vcard-import-translation-table'.
115 ;; If there is a REV element, it is stored in BBDB's creation-date in
116 ;; newly created BBDB records, or discarded for existing ones.
118 ;; VCard type prefixes (A.ADR:..., B.ADR:... etc.) are stripped off
119 ;; and discarded from the following types: N, FN, NICKNAME, ORG (first
120 ;; occurrence), ADR, TEL, EMAIL, URL, BDAY (first occurrence), NOTE.
122 ;; VCard types that are prefixed `X-BBDB-' are stored in BBDB without
123 ;; the prefix.
125 ;; VCard type X-BBDB-ANNIVERSARY may contain (previously exported)
126 ;; newline-separated non-birthday anniversaries that are meant to be
127 ;; read by org-mode.
129 ;; All remaining vCard types that don't match the regexp in
130 ;; `bbdb-vcard-skip-on-import' and that have a non-empty value are
131 ;; stored unaltered in the BBDB Notes alist where, for instance,
132 ;; `TZ;VALUE=text:-05:00' is stored as `(tz\;value=text . "-05:00")'.
133 ;; From the BBDB data fields AKA, Phones, Addresses, Net Addresses,
134 ;; and Notes, duplicates are removed, respectively.
136 ;; VCards found inside other vCards (as values of type AGENT) are
137 ;; imported as well.
140 ;; Handling of the individual types defined in RFC2426 during import
141 ;; (assuming default label translation and no vCard type exclusion):
142 ;; "
143 ;; |----------------------+----------------------------------------|
144 ;; | VCARD TYPE; | STORAGE IN BBDB |
145 ;; | PARAMETERS | |
146 ;; |----------------------+----------------------------------------|
147 ;; | VERSION | - |
148 ;; |----------------------+----------------------------------------|
149 ;; | N | First occurrence: |
150 ;; | | Firstname |
151 ;; | | Lastname |
152 ;; | | |
153 ;; | | Rest: |
154 ;; | | AKAs (append) |
155 ;; |----------------------+----------------------------------------|
156 ;; | FN | AKAs (append) |
157 ;; | NICKNAME | AKAs (append) |
158 ;; |----------------------+----------------------------------------|
159 ;; | ORG | First occurrence: |
160 ;; | | Company |
161 ;; | | |
162 ;; | | Rest: |
163 ;; | | Notes<org |
164 ;; | | (repeatedly) |
165 ;; |----------------------+----------------------------------------|
166 ;; | ADR;TYPE=x,HOME,y | Addresses<Home |
167 ;; | ADR;TYPE=x;TYPE=HOME | Addresses<Home |
168 ;; | ADR;TYPE=x,WORK,y | Addresses<Office |
169 ;; | ADR;TYPE=x;TYPE=WORK | Addresses<Office |
170 ;; | ADR;TYPE=x,y,z | Addresses<x,y,z |
171 ;; | ADR;TYPE=x;TYPE=y | Addresses<x,y |
172 ;; | ADR | Addresses<Office |
173 ;; |----------------------+----------------------------------------|
174 ;; | TEL;TYPE=x,HOME,y | Phones<Home (append) |
175 ;; | TEL;TYPE=x;TYPE=HOME | Phones<Home (append) |
176 ;; | TEL;TYPE=x,WORK,y | Phones<Office (append) |
177 ;; | TEL;TYPE=x;TYPE=WORK | Phones<Office (append) |
178 ;; | TEL;TYPE=x,CELL,y | Phones<Mobile (append) |
179 ;; | TEL;TYPE=x;TYPE=CELL | Phones<Mobile (append) |
180 ;; | TEL;TYPE=x,y,z | Phones<x,y,z (append) |
181 ;; | TEL;TYPE=x;TYPE=y | Phones<x,y (append) |
182 ;; | TEL | Phones<Office (append) |
183 ;; |----------------------+----------------------------------------|
184 ;; | EMAIL;TYPE=x,y,z | Net-Addresses (append) |
185 ;; | URL | Notes<www |
186 ;; |----------------------+----------------------------------------|
187 ;; | BDAY | Notes<anniversary (append as birthday) |
188 ;; | X-BBDB-ANNIVERSARY | Notes<anniversary (append) |
189 ;; |----------------------+----------------------------------------|
190 ;; | NOTE | Notes<notes (append) |
191 ;; | REV | Notes<creation-date |
192 ;; | CATEGORIES | Notes<mail-alias (append) |
193 ;; | SORT-STRING | Notes<sort-string |
194 ;; | KEY | Notes<key |
195 ;; | GEO | Notes<geo |
196 ;; | TZ | Notes<tz |
197 ;; | PHOTO | Notes<photo |
198 ;; | LABEL | Notes<label |
199 ;; | LOGO | Notes<logo |
200 ;; | SOUND | Notes<sound |
201 ;; | TITLE | Notes<title |
202 ;; | ROLE | Notes<role |
203 ;; | AGENT | Notes<agent |
204 ;; | MAILER | Notes<mailer |
205 ;; | UID | Notes<uid |
206 ;; | PRODID | Notes<prodid |
207 ;; | CLASS | Notes<class |
208 ;; | X-foo | Notes<x-foo |
209 ;; | X-BBDB-bar | Notes<bar |
210 ;; |----------------------+----------------------------------------|
211 ;; | anyJunK;a=x;b=y | Notes<anyjunk;a=x;b=y |
212 ;; |----------------------+----------------------------------------|
213 ;; "
215 ;; vCard Export
216 ;; ------------
218 ;; VCard types N (only fields lastname, firstname) and FN both come
219 ;; from BBDB's Name.
221 ;; Members of BBDB field AKA are stored comma-separated under the
222 ;; vCard type NICKNAME.
224 ;; Labels of Addresses and Phones are translated as defined in
225 ;; `bbdb-vcard-export-translation-table' into type parameters of
226 ;; vCard types ADR and TEL, respectively.
228 ;; In vCard type ADR, fields postbox and extended address are always
229 ;; empty. Newlines which subdivide BBDB Address fields are converted
230 ;; into commas subdividing vCard ADR fields.
232 ;; The value of 'anniversary in Notes is supposed to be subdivided by
233 ;; newlines. The birthday part (either just a date or a date followed
234 ;; by \"birthday\") is stored under vCard type BDAY. The rest is
235 ;; stored newline-separated in the non-standard vCard type
236 ;; X-BBDB-ANNIVERSARY.
238 ;; Field names listed in `bbdb-vcard-x-bbdb-candidates' are in the
239 ;; exported vCard prepended by `X-BBDB-'.
241 ;; The creation-date of the BBDB record is stored as vCard type REV.
243 ;; Remaining members of BBDB Notes are exported to the vCard without
244 ;; change.
248 ;;; History:
252 ;;; Code:
254 (require 'cl)
255 (require 'bbdb)
256 (require 'vcard)
257 (require 'bbdb-com)
259 (defconst bbdb-vcard-version "0.2"
260 "Version of the vCard importer/exporter.
261 The major part increases on user-visible changes.")
265 ;;;; User Variables
267 (defgroup bbdb-vcard nil
268 "Customizations for vCards"
269 :group 'bbdb)
271 (defcustom bbdb-vcard-skip-on-import "X-GSM-"
272 "Regexp describing vCard elements that are to be discarded during import.
273 Example: `X-GSM-\\|X-MS-'."
274 :group 'bbdb-vcard
275 :type 'regexp)
277 (defcustom bbdb-vcard-skip-valueless t
278 "Skip vCard element types with an empty value.
279 Nil means insert empty types into BBDB."
280 :group 'bbdb-vcard
281 :type 'boolean)
283 (defcustom bbdb-vcard-import-translation-table
284 '(("CELL\\|CAR" . "Mobile")
285 ("WORK" . "Office")
286 ("HOME" . "Home") ; translates e.g. "dom,home,postal,parcel" to "Home"
287 ("^$" . "Office")) ; acts as a default for parameterless ADR or TEL
288 "Label translation on vCard import.
289 Alist with translations of location labels for addresses and phone
290 numbers. Cells are (VCARD-LABEL-REGEXP . BBDB-LABEL). One entry
291 should map a default BBDB label to the empty string (`\"^$\"') which
292 corresponds to unlabelled vCard elements."
293 :group 'bbdb-vcard
294 :type '(alist :key-type
295 (choice regexp (const :tag "Empty (as default)" "^$"))
296 :value-type string))
298 (defcustom bbdb-vcard-try-merge t
299 "Try to merge vCards into existing BBDB records.
300 Nil means create a fresh bbdb record each time a vCard is read."
301 :group 'bbdb-vcard
302 :type 'boolean)
304 (defcustom bbdb-vcard-type-canonicalizer 'upcase
305 "Function to apply to vCard type names on export.
306 Most reasonable choices are `upcase' and `downcase'."
307 :group 'bbdb-vcard
308 :type 'function)
310 (defcustom bbdb-vcard-x-bbdb-candidates
311 '(attribution
312 finger-host
313 gnus-score
314 mark-char
315 mail-name
316 face
317 tex-name
318 aka) ; not sure what this is for
319 "List of translatable BBDB user field names.
320 On export to a vCard, they are transformed into vCard-compliant
321 extended types by prepending `X-BBDB-'. On (re-)import, this prefix
322 is removed again."
323 :group 'bbdb-vcard
324 :type '(repeat symbol))
326 (defcustom bbdb-vcard-export-translation-table
327 '(("Mobile" . "CELL")
328 ("Office" . "WORK"))
329 "Label translation on vCard export.
330 Alist with translations of location labels for addresses and phone
331 numbers. Cells are (BBDB-LABEL-REGEXP . VCARD-LABEL)."
332 :group 'bbdb-vcard
333 :type '(alist :key-type
334 (choice regexp (const :tag "Empty (as default)" "^$"))
335 :value-type string))
337 (defcustom bbdb-vcard-export-coding-system
338 'utf-8-dos ; dos line endings mandatory according to RFC 2426
339 "Coding system to use when writing vCard files."
340 :group 'bbdb-vcard
341 :type 'symbol)
343 (defcustom bbdb-vcard-default-dir "~/exported-vcards/"
344 "Default storage directory for exported vCards.
345 Nil means current directory."
346 :group 'bbdb-vcard
347 :type '(choice directory (const :tag "Current directory" nil)))
351 ;;;; User Functions
353 ;;;###autoload
354 (defun bbdb-vcard-import-region (begin end)
355 "Import the vCards between BEGIN and END into BBDB.
356 Existing BBDB records may be altered."
357 (interactive "r")
358 (bbdb-vcard-iterate-vcards 'bbdb-vcard-import-vcard
359 (buffer-substring-no-properties begin end)))
361 ;;;###autoload
362 (defun bbdb-vcard-import-buffer (vcard-buffer)
363 "Import vCards from VCARD-BUFFER into BBDB.
364 Existing BBDB records may be altered."
365 (interactive (list (current-buffer)))
366 (set-buffer vcard-buffer)
367 (bbdb-vcard-import-region (point-min) (point-max)))
369 ;;;###autoload
370 (defun bbdb-vcard-import-file (vcard-file)
371 "Import vCards from VCARD-FILE into BBDB.
372 If VCARD-FILE is a wildcard, import each matching file. Existing BBDB
373 records may be altered."
374 (interactive "FvCard file (or wildcard): ")
375 (dolist (vcard-file (file-expand-wildcards vcard-file))
376 (with-temp-buffer
377 (insert-file-contents vcard-file)
378 (bbdb-vcard-import-region (point-min) (point-max)))))
380 ;;;###autoload
381 (defun bbdb-vcard-export
382 (filename-or-directory all-records-p one-file-per-record-p)
383 "From Buffer *BBDB*, write one or more record(s) as vCard(s) to file(s).
384 \\<bbdb-mode-map>\
385 If \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-vcard-export]\"\
386 is used instead of simply \"\\[bbdb-vcard-export]\", then export all \
387 records currently
388 in the *BBDB* buffer. If used with prefix argument, store records
389 in individual files."
390 (interactive
391 (let ((default-filename ; argument filename-or-directory
392 (bbdb-vcard-make-file-name (bbdb-current-record nil)))
393 (all-records-p (bbdb-do-all-records-p)))
394 (list
395 (if all-records-p
396 (if current-prefix-arg
397 (read-directory-name "Write vCard files to directory: "
398 bbdb-vcard-default-dir nil 42)
399 (read-file-name
400 "Write vCards to file: "
401 bbdb-vcard-default-dir
402 nil nil
403 (format-time-string "%Y-%m-%dT%H:%M.vcf" (current-time))))
404 (read-file-name "Write current record to vCard file: "
405 bbdb-vcard-default-dir nil nil default-filename))
406 all-records-p ; argument all-records-p
407 current-prefix-arg))) ; argument one-file-per-record-p
408 (if all-records-p
409 (let ((records (progn (set-buffer bbdb-buffer-name)
410 (mapcar 'car bbdb-records)))
411 used-up-basenames) ; keep them unique
412 (if one-file-per-record-p
413 (progn
414 (dolist (record records)
415 (with-temp-buffer
416 (let ((basename
417 (bbdb-vcard-make-file-name record
418 used-up-basenames)))
419 (insert (bbdb-vcard-from record))
420 (bbdb-vcard-write-buffer
421 (concat filename-or-directory basename))
422 (push basename used-up-basenames))))
423 (message "Wrote %d vCards to %s"
424 (length used-up-basenames) filename-or-directory))
425 (with-temp-buffer ; all visible BBDB records in one file
426 (dolist (record records)
427 (insert (bbdb-vcard-from record)))
428 (bbdb-vcard-write-buffer filename-or-directory))))
429 (let ((vcard (bbdb-vcard-from (bbdb-current-record nil)))) ; current record
430 (with-temp-buffer
431 (insert vcard)
432 (bbdb-vcard-write-buffer filename-or-directory)))))
434 ;;;###autoload
435 (defun bbdb-vcard-export-to-kill-ring (all-records-p)
436 "From Buffer *BBDB*, copy one or more record(s) as vCard(s) to the kill ring.
437 \\<bbdb-mode-map>\
438 If \"\\[bbdb-apply-next-command-to-all-records]\
439 \\[bbdb-vcard-export-to-kill-ring]\"\
440 is used instead of simply \"\\[bbdb-vcard-export-to-kill-ring]\", \
441 then export all records currently in
442 the *BBDB* buffer."
443 (interactive (let ((all-records-p (bbdb-do-all-records-p)))
444 (list all-records-p)))
445 (if all-records-p
446 (let ((records (progn (set-buffer bbdb-buffer-name)
447 (mapcar 'car bbdb-records))))
448 (kill-new "")
449 (dolist (record records)
450 (kill-append (bbdb-vcard-from record) nil))
451 (message "Saved %d records as vCards" (length records)))
452 (kill-new (bbdb-vcard-from (bbdb-current-record nil)))
453 (message "Saved record as vCard")))
455 ;;;###autoload (define-key bbdb-mode-map [(v)] 'bbdb-vcard-export)
456 (define-key bbdb-mode-map [(v)] 'bbdb-vcard-export)
457 ;;;###autoload (define-key bbdb-mode-map [(V)] 'bbdb-vcard-export-to-kill-ring)
458 (define-key bbdb-mode-map [(V)] 'bbdb-vcard-export-to-kill-ring)
462 (defun bbdb-vcard-iterate-vcards (vcard-processor vcards)
463 "Apply VCARD-PROCESSOR successively to each vCard in string VCARDS.
464 When VCARDS is nil, return nil. Otherwise, return t."
465 (with-temp-buffer
466 (insert vcards)
467 (goto-char (point-min))
468 ;; Change CRLF into CR if necessary, dealing with inconsistent line
469 ;; endings.
470 (while (re-search-forward "\r\n" nil t)
471 (replace-match "\n" nil nil nil 1))
472 (setf (buffer-string) (bbdb-vcard-unfold-lines (buffer-string)))
473 (goto-char (point-min))
474 (while (re-search-forward
475 "^\\([[:alnum:]-]*\\.\\)?*BEGIN:VCARD[\n[:print:][:cntrl:]]*?\\(^\\([[:alnum:]-]*\\.\\)?END:VCARD\\)"
476 nil t)
477 (let ((vcard (match-string 0)))
478 (if (string= "3.0" (bbdb-vcard-version-of vcard))
479 (funcall vcard-processor vcard)
480 (funcall vcard-processor ; probably a v2.1 vCard
481 (bbdb-vcard-unfold-lines
482 (bbdb-vcard-convert-to-3.0 vcard))))))))
484 (defun bbdb-vcard-version-of (vcard)
485 "Return version number string of VCARD."
486 (with-temp-buffer
487 (insert vcard)
488 (car (bbdb-vcard-values-of-type "version" "value"))))
490 (defun bbdb-vcard-import-vcard (vcard)
491 "Store VCARD (version 3.0) in BBDB.
492 Extend existing BBDB records where possible."
493 (with-temp-buffer
494 (insert vcard)
495 (let* ((raw-name (car (bbdb-vcard-values-of-type "N" "value" t t)))
496 ;; Name suitable for storing in BBDB:
497 (name (bbdb-vcard-unescape-strings
498 (bbdb-vcard-unvcardize-name raw-name)))
499 ;; Name to search for in BBDB now:
500 (name-to-search-for
501 (when raw-name (if (stringp raw-name)
502 raw-name
503 (concat (nth 1 raw-name) ;given name
504 " .*"
505 (nth 0 raw-name))))) ; family name
506 ;; Additional names from prefixed types like A.N, B.N, etc.:
507 (other-names
508 (mapcar
509 (lambda (n)
510 (bbdb-join (bbdb-vcard-unvcardize-name (cdr (assoc "value" n)))
511 " "))
512 (bbdb-vcard-elements-of-type "N" nil t)))
513 (vcard-formatted-names (bbdb-vcard-unescape-strings
514 (bbdb-vcard-values-of-type "FN" "value")))
515 (vcard-nicknames
516 (bbdb-vcard-unescape-strings
517 (bbdb-vcard-split-structured-text
518 (car (bbdb-vcard-values-of-type "NICKNAME" "value"))
519 "," t)))
520 ;; Company suitable for storing in BBDB:
521 (vcard-org
522 (bbdb-vcard-unescape-strings
523 (bbdb-vcard-unvcardize-org
524 (car (bbdb-vcard-values-of-type "ORG" "value" t t)))))
525 ;; Company to search for in BBDB now:
526 (org-to-search-for vcard-org) ; sorry
527 ;; Email suitable for storing in BBDB:
528 (vcard-email (bbdb-vcard-values-of-type "EMAIL" "value"))
529 ;; Email to search for in BBDB now:
530 (email-to-search-for
531 (when vcard-email
532 (concat "\\(" (bbdb-join vcard-email "\\)\\|\\(") "\\)")))
533 ;; Phone numbers suitable for storing in BBDB:
534 (vcard-tels
535 (mapcar (lambda (tel)
536 (vector (bbdb-vcard-translate
537 (or (cdr (assoc "type" tel)) ""))
538 (cdr (assoc "value" tel))))
539 (bbdb-vcard-elements-of-type "TEL")))
540 ;; Phone numbers to search for in BBDB now:
541 (tel-to-search-for
542 (when vcard-tels
543 (concat "\\("
544 (mapconcat (lambda (x) (elt x 1))
545 vcard-tels "\\)\\|\\(")
546 "\\)")))
547 ;; Addresses
548 (vcard-adrs
549 (mapcar 'bbdb-vcard-unvcardize-adr
550 (bbdb-vcard-elements-of-type "ADR" nil t)))
551 (vcard-url (car (bbdb-vcard-values-of-type "URL" "value" t)))
552 (vcard-notes (bbdb-vcard-values-of-type "NOTE" "value"))
553 (raw-bday (car (bbdb-vcard-values-of-type "BDAY" "value" t)))
554 ;; Birthday suitable for storing in BBDB (usable by org-mode):
555 (vcard-bday (when raw-bday (concat raw-bday " birthday")))
556 ;; Birthday to search for in BBDB now:
557 (bday-to-search-for vcard-bday)
558 ;; Non-birthday anniversaries, probably exported by ourselves:
559 (vcard-x-bbdb-anniversaries
560 (bbdb-vcard-split-structured-text
561 (car (bbdb-vcard-values-of-type "X-BBDB-ANNIVERSARY" "value"))
562 "\\\\n" t))
563 (vcard-rev (car (bbdb-vcard-values-of-type "REV" "value")))
564 (vcard-categories (bbdb-vcard-values-of-type "CATEGORIES" "value"))
565 ;; The BBDB record to change:
566 (record-freshness-info "BBDB record changed:") ; default user info
567 (bbdb-record
569 ;; Try to find an existing one ...
570 ;; (a) try company and net and name:
571 (car (and bbdb-vcard-try-merge
572 (bbdb-vcard-search-intersection
573 (bbdb-records)
574 name-to-search-for
575 org-to-search-for email-to-search-for)))
576 ;; (b) try company and name:
577 (car (and bbdb-vcard-try-merge
578 (bbdb-vcard-search-intersection
579 (bbdb-records) name-to-search-for org-to-search-for)))
580 ;; (c) try net and name; we may change company here:
581 (car (and bbdb-vcard-try-merge
582 (bbdb-vcard-search-intersection
583 (bbdb-records)
584 name-to-search-for nil email-to-search-for)))
585 ;; (d) try birthday and name; we may change company here:
586 (car (and bbdb-vcard-try-merge
587 (bbdb-vcard-search-intersection
588 (bbdb-records)
589 name-to-search-for nil nil bday-to-search-for)))
590 ;; (e) try phone and name; we may change company here:
591 (car (and bbdb-vcard-try-merge
592 (bbdb-vcard-search-intersection
593 (bbdb-records)
594 name-to-search-for nil nil nil tel-to-search-for)))
595 ;; No existing record found; make a fresh one:
596 (let ((fresh-record (make-vector bbdb-record-length nil)))
597 (bbdb-record-set-cache fresh-record
598 (make-vector bbdb-cache-length nil))
599 (if vcard-rev ; For fresh records,
600 (bbdb-record-putprop ; set creation-date from vcard-rev
601 fresh-record 'creation-date
602 (replace-regexp-in-string
603 "\\([0-9]\\{4\\}-[01][0-9]-[0-3][0-9]\\).*" "\\1"
604 vcard-rev))
605 (bbdb-invoke-hook 'bbdb-create-hook fresh-record))
606 (setq record-freshness-info "BBDB record added:") ; user info
607 fresh-record)))
608 (bbdb-akas (bbdb-record-aka bbdb-record))
609 (bbdb-addresses (bbdb-record-addresses bbdb-record))
610 (bbdb-phones (bbdb-record-phones bbdb-record))
611 (bbdb-nets (bbdb-record-net bbdb-record))
612 (bbdb-raw-notes (bbdb-record-raw-notes bbdb-record))
613 notes
614 other-vcard-type)
615 (bbdb-vcard-elements-of-type "BEGIN") ; get rid of delimiter
616 (bbdb-vcard-elements-of-type "END") ; get rid of delimiter
617 (bbdb-vcard-elements-of-type "VERSION") ; get rid of this too
618 (when name ; which should be the case as N is mandatory in vCard
619 (bbdb-record-set-firstname bbdb-record (car name))
620 (bbdb-record-set-lastname bbdb-record (cadr name)))
621 (bbdb-record-set-aka
622 bbdb-record
623 (remove (concat (bbdb-record-firstname bbdb-record)
624 " " (bbdb-record-lastname bbdb-record))
625 (reduce (lambda (x y) (union x y :test 'string=))
626 (list vcard-nicknames
627 other-names
628 vcard-formatted-names
629 bbdb-akas))))
630 (when vcard-org (bbdb-record-set-company bbdb-record vcard-org))
631 (bbdb-record-set-net
632 bbdb-record (union vcard-email bbdb-nets :test 'string=))
633 (bbdb-record-set-addresses
634 bbdb-record (union vcard-adrs bbdb-addresses :test 'equal))
635 (bbdb-record-set-phones bbdb-record
636 (union vcard-tels bbdb-phones :test 'equal))
637 ;; prepare bbdb's notes:
638 (when vcard-url (push (cons 'www vcard-url) bbdb-raw-notes))
639 (when vcard-notes
640 ;; Put vCard NOTEs under key 'notes (append if necessary).
641 (unless (assq 'notes bbdb-raw-notes)
642 (push (cons 'notes "") bbdb-raw-notes))
643 (setf (cdr (assq 'notes bbdb-raw-notes))
644 (bbdb-vcard-merge-strings
645 (cdr (assq 'notes bbdb-raw-notes))
646 (bbdb-vcard-unescape-strings vcard-notes)
647 ";\n")))
648 (when (or vcard-bday vcard-x-bbdb-anniversaries)
649 ;; Put vCard BDAY and vCard X-BBDB-ANNIVERSARY's under key
650 ;; 'anniversary (append if necessary) where org-mode can find it.
651 (when vcard-bday (push vcard-bday vcard-x-bbdb-anniversaries))
652 (unless (assq 'anniversary bbdb-raw-notes)
653 (push (cons 'anniversary "") bbdb-raw-notes))
654 (setf (cdr (assq 'anniversary bbdb-raw-notes))
655 (bbdb-vcard-merge-strings
656 (cdr (assq 'anniversary bbdb-raw-notes))
657 (bbdb-vcard-unescape-strings vcard-x-bbdb-anniversaries)
658 "\n")))
659 (when vcard-categories
660 ;; Put vCard CATEGORIES under key 'mail-alias (append if necessary).
661 (unless (assq 'mail-alias bbdb-raw-notes)
662 (push (cons 'mail-alias "") bbdb-raw-notes))
663 (setf (cdr (assq 'mail-alias bbdb-raw-notes))
664 (bbdb-vcard-merge-strings
665 (cdr (assq 'mail-alias bbdb-raw-notes))
666 vcard-categories
667 ",")))
668 (while (setq other-vcard-type (bbdb-vcard-other-element))
669 (when (string-match "^\\([[:alnum:]-]*\\.\\)?AGENT"
670 (symbol-name (car other-vcard-type)))
671 ;; Notice other vCards inside the current one.
672 (bbdb-vcard-iterate-vcards
673 'bbdb-vcard-import-vcard ; needed for inner v2.1 vCards:
674 (replace-regexp-in-string "\\\\" "" (cdr other-vcard-type))))
675 (unless (or (and bbdb-vcard-skip-on-import
676 (string-match bbdb-vcard-skip-on-import
677 (symbol-name (car other-vcard-type))))
678 (and bbdb-vcard-skip-valueless
679 (zerop (length (cdr other-vcard-type)))))
680 (push (bbdb-vcard-remove-x-bbdb other-vcard-type) bbdb-raw-notes)))
681 (bbdb-record-set-raw-notes
682 bbdb-record
683 (remove-duplicates bbdb-raw-notes :test 'equal :from-end t))
684 (bbdb-change-record bbdb-record t)
685 ;; Tell the user what we've done.
686 (message "%s %s %s -- %s"
687 record-freshness-info
688 (bbdb-record-firstname bbdb-record)
689 (bbdb-record-lastname bbdb-record)
690 (replace-regexp-in-string
691 "\n" "; " (or (bbdb-record-company bbdb-record) "-"))))))
693 (defun bbdb-vcard-from (record)
694 "Return BBDB RECORD as a vCard."
695 (with-temp-buffer
696 (let* ((name (bbdb-record-name record))
697 (first-name (bbdb-record-firstname record))
698 (last-name (bbdb-record-lastname record))
699 (aka (bbdb-record-aka record))
700 (company (bbdb-record-company record))
701 (net (bbdb-record-net record))
702 (phones (bbdb-record-phones record))
703 (addresses (bbdb-record-addresses record))
704 (www (bbdb-get-field record 'www))
705 (notes
706 (bbdb-vcard-split-structured-text (bbdb-record-notes record)
707 ";\n" t))
708 (raw-anniversaries (bbdb-vcard-split-structured-text
709 (bbdb-get-field record 'anniversary) "\n" t))
710 (birthday-regexp
711 "\\([0-9]\\{4\\}-[01][0-9]-[0-3][0-9]\\)\\([[:blank:]]+birthday\\)?\\'")
712 (birthday
713 (car (bbdb-vcard-split-structured-text
714 (find-if (lambda (x) (string-match birthday-regexp x))
715 raw-anniversaries)
716 " " t)))
717 (other-anniversaries
718 (remove-if (lambda (x) (string-match birthday-regexp x))
719 raw-anniversaries :count 1))
720 (creation-date (bbdb-get-field record 'creation-date))
721 (mail-aliases (bbdb-record-getprop record
722 bbdb-define-all-aliases-field))
723 (raw-notes (copy-alist (bbdb-record-raw-notes record))))
724 (bbdb-vcard-insert-vcard-element "BEGIN" "VCARD")
725 (bbdb-vcard-insert-vcard-element "VERSION" "3.0")
726 (bbdb-vcard-insert-vcard-element "FN" (bbdb-vcard-escape-strings name))
727 (bbdb-vcard-insert-vcard-element
728 "N" (bbdb-vcard-escape-strings last-name)
729 ";" (bbdb-vcard-escape-strings first-name)
730 ";;;") ; Additional Names, Honorific Prefixes, Honorific Suffixes
731 (bbdb-vcard-insert-vcard-element
732 "NICKNAME" (bbdb-join (bbdb-vcard-escape-strings aka) ","))
733 (bbdb-vcard-insert-vcard-element
734 "ORG" (bbdb-vcard-escape-strings company))
735 (dolist (mail net)
736 (bbdb-vcard-insert-vcard-element
737 "EMAIL;TYPE=INTERNET" (bbdb-vcard-escape-strings mail)))
738 (dolist (phone phones)
739 (bbdb-vcard-insert-vcard-element
740 (concat
741 "TEL;TYPE="
742 (bbdb-vcard-escape-strings
743 (bbdb-vcard-translate (bbdb-phone-location phone) t)))
744 (bbdb-vcard-escape-strings (bbdb-phone-string phone))))
745 (dolist (address addresses)
746 (bbdb-vcard-insert-vcard-element
747 (concat
748 "ADR;TYPE="
749 (bbdb-vcard-escape-strings
750 (bbdb-vcard-translate (bbdb-address-location address) t)))
751 ";;" ; no Postbox, no Extended
752 (bbdb-join (bbdb-vcard-escape-strings (bbdb-address-streets address))
753 ",")
754 ";" (bbdb-vcard-vcardize-address-element
755 (bbdb-vcard-escape-strings (bbdb-address-city address)))
756 ";" (bbdb-vcard-vcardize-address-element
757 (bbdb-vcard-escape-strings (bbdb-address-state address)))
758 ";" (bbdb-vcard-vcardize-address-element
759 (bbdb-vcard-escape-strings (bbdb-address-zip address)))
760 ";" (bbdb-vcard-vcardize-address-element
761 (bbdb-vcard-escape-strings (bbdb-address-country address)))))
762 (bbdb-vcard-insert-vcard-element "URL" www)
763 (dolist (note notes)
764 (bbdb-vcard-insert-vcard-element
765 "NOTE" (bbdb-vcard-escape-strings note)))
766 (bbdb-vcard-insert-vcard-element "BDAY" birthday)
767 (bbdb-vcard-insert-vcard-element ; non-birthday anniversaries
768 "X-BBDB-ANNIVERSARY" (bbdb-join other-anniversaries "\\n"))
769 (bbdb-vcard-insert-vcard-element "REV" creation-date)
770 (bbdb-vcard-insert-vcard-element
771 "CATEGORIES"
772 (bbdb-join (bbdb-vcard-escape-strings
773 (bbdb-vcard-split-structured-text mail-aliases "," t)) ","))
774 ;; prune raw-notes...
775 (dolist (key '(www notes anniversary mail-alias creation-date timestamp))
776 (setq raw-notes (assq-delete-all key raw-notes)))
777 ;; ... and output what's left
778 (dolist (raw-note raw-notes)
779 (bbdb-vcard-insert-vcard-element
780 (symbol-name (bbdb-vcard-prepend-x-bbdb-maybe (car raw-note)))
781 (bbdb-vcard-escape-strings (cdr raw-note))))
782 (bbdb-vcard-insert-vcard-element "END" "VCARD")
783 (bbdb-vcard-insert-vcard-element nil)) ; newline
784 (buffer-string)))
788 (defun bbdb-vcard-convert-to-3.0 (vcard)
789 "Convert VCARD from v2.1 to v3.0.
790 Return a version 3.0 vCard as a string. Don't bother about the vCard
791 v3.0 mandatory elements N and FN."
792 ;; Prevent customization of vcard.el's from being changed behind our back:
793 (let ((vcard-standard-filters '(vcard-filter-html)))
794 (with-temp-buffer
795 (bbdb-vcard-insert-vcard-element "BEGIN" "VCARD")
796 (bbdb-vcard-insert-vcard-element "VERSION" "3.0")
797 (dolist (element (remove*
798 "VERSION" (vcard-parse-string vcard)
799 :key (lambda (x) (upcase (caar x))) :test 'string=))
800 (bbdb-vcard-insert-vcard-element
801 (concat (caar element)
802 (mapconcat 'bbdb-vcard-parameter-pair (cdar element) ""))
803 (bbdb-join (bbdb-vcard-escape-strings (cdr element)) ";")))
804 (bbdb-vcard-insert-vcard-element "END" "VCARD")
805 (bbdb-vcard-insert-vcard-element nil)
806 (buffer-string))))
808 (defun bbdb-vcard-parameter-pair (input)
809 "Return \"parameter=value\" made from INPUT.
810 INPUT is its representation in vcard.el. Return empty string if INPUT
811 is nil."
812 (cond ((consp input) (concat ";" (car input) "=" (cdr input)))
813 ((stringp input) (concat ";TYPE=" input))
814 ((null input) "")))
818 (defun bbdb-vcard-values-of-type
819 (type parameter &optional one-is-enough-p split-value-at-semi-colon-p)
820 "Return in a list the values of PARAMETER of vCard element of TYPE.
821 The VCard element is read and deleted from current buffer which is
822 supposed to contain a single vCard. If ONE-IS-ENOUGH-P is non-nil,
823 read and delete only the first element of TYPE. If PARAMETER is
824 \"value\" and SPLIT-VALUE-AT-SEMI-COLON-P is non-nil, split the value
825 at semi-colons into a list."
826 (mapcar (lambda (x) (cdr (assoc parameter x)))
827 (bbdb-vcard-elements-of-type
828 type one-is-enough-p split-value-at-semi-colon-p)))
830 (defun bbdb-vcard-elements-of-type
831 (type &optional one-is-enough-p split-value-at-semi-colon-p)
832 "From current buffer read and delete the vCard elements of TYPE.
833 The current buffer is supposed to contain a single vCard. If
834 ONE-IS-ENOUGH-P is non-nil, read and delete only the first element of
835 TYPE. Return a list of alists, one per element. Each alist has a
836 cell with key \"value\" containing the element's value, and may have
837 other elements of the form \(parameter-name . parameter-value). If
838 SPLIT-VALUE-AT-SEMI-COLON-P is non-nil, split the value at key
839 \"value\" at semi-colons into a list."
840 (goto-char (point-min))
841 (let (values parameters read-enough)
842 (while
843 (and
844 (not read-enough)
845 (re-search-forward
846 (concat
847 "^\\([[:alnum:]-]*\\.\\)?\\(" type "\\)\\(;.*\\)?:\\(.*\\)$")
848 nil t))
849 (goto-char (match-end 2))
850 (setq parameters nil)
851 (push (cons "value" (if split-value-at-semi-colon-p
852 (bbdb-vcard-split-structured-text
853 (match-string 4) ";")
854 (match-string 4)))
855 parameters)
856 (while (re-search-forward "\\([^;:=]+\\)=\\([^;:]+\\)"
857 (line-end-position) t)
858 (let* ((parameter-key (downcase (match-string 1)))
859 (parameter-value (downcase (match-string 2)))
860 (parameter-sibling (assoc parameter-key parameters)))
861 (if parameter-sibling ; i.e., pair with equal key
862 ;; collect vCard parameter list `;a=x;a=y;a=z'
863 ;; into vCard value list `;a=x,y,z'; becoming ("a" . "x,y,z")
864 (setf (cdr parameter-sibling)
865 (concat (cdr parameter-sibling) "," parameter-value))
866 ;; vCard parameter pair `;key=value;' with new key
867 (push (cons parameter-key parameter-value) parameters))))
868 (push parameters values)
869 (delete-region (line-end-position 0) (line-end-position))
870 (when one-is-enough-p (setq read-enough t)))
871 (nreverse values)))
873 (defun bbdb-vcard-other-element ()
874 "From current buffer read and delete the topmost vCard element.
875 Buffer is supposed to contain a single vCard. Return (TYPE . VALUE)."
876 (goto-char (point-min))
877 (when (re-search-forward "^\\([[:graph:]]*?\\):\\(.*\\)$" nil t)
878 (let ((type (match-string 1))
879 (value (match-string 2)))
880 (delete-region (match-beginning 0) (match-end 0))
881 (cons (intern (downcase type)) (bbdb-vcard-unescape-strings value)))))
883 (defun bbdb-vcard-insert-vcard-element (type &rest values)
884 "Insert a vCard element comprising TYPE, `:', VALUES into current buffer.
885 Take care of TYPE canonicalization, line folding, and closing newline.
886 Do nothing if TYPE is non-nil and VALUES are empty. Insert just a
887 newline if TYPE is nil."
888 (if type
889 (let ((value (bbdb-join values "")))
890 (unless (zerop (length value))
891 (insert (bbdb-vcard-fold-line
892 (concat (bbdb-vcard-canonicalize-vcard-type type)
893 ":" value)))))
894 (insert (bbdb-vcard-fold-line ""))))
898 (defun bbdb-vcard-unfold-lines (vcards)
899 "Return folded vCard lines from VCARDS unfolded."
900 (replace-regexp-in-string "\n\\( \\|\t\\)" "" vcards))
902 (defun bbdb-vcard-fold-line (long-line)
903 "Insert after every 75th position in LONG-LINE a newline and a space."
904 (with-temp-buffer (insert long-line)
905 (goto-char (point-min))
906 (while (< (goto-char (+ (point) 75))
907 (point-max))
908 (insert "\n "))
909 (insert "\n")
910 (buffer-string)))
912 (defun bbdb-vcard-unescape-strings (escaped-strings)
913 "Unescape escaped `;', `,', `\\', and newlines in ESCAPED-STRINGS.
914 ESCAPED-STRINGS may be a string or a sequence of strings."
915 (flet ((unescape (x) (replace-regexp-in-string
916 "\\([\\\\]\\)\\([,;\\]\\)" ""
917 (replace-regexp-in-string "\\\\n" "\n" x)
918 nil nil 1)))
919 (bbdb-vcard-process-strings 'unescape escaped-strings)))
921 (defun bbdb-vcard-escape-strings (unescaped-strings )
922 "Escape `;', `,', `\\', and newlines in UNESCAPED-STRINGS.
923 UNESCAPED-STRINGS may be a string or a sequence of strings."
924 (flet ((escape (x) (replace-regexp-in-string ; from 2.1 conversion:
925 "\r" "" (replace-regexp-in-string
926 "\n" "\\\\n" (replace-regexp-in-string
927 "\\(\\)[,;\\]" "\\\\" (or x "")
928 nil nil 1)))))
929 (bbdb-vcard-process-strings 'escape unescaped-strings)))
931 (defun bbdb-vcard-process-strings (string-processor strings)
932 "Apply STRING-PROCESSOR to STRINGS.
933 STRINGS may be a string or a sequence of strings."
934 (if (stringp strings)
935 (funcall string-processor strings)
936 (mapcar string-processor strings)))
940 (defun bbdb-vcard-remove-x-bbdb (vcard-element)
941 "Remove the `X-BBDB-' prefix from the type part of VCARD-ELEMENT if any."
942 (cons (intern (replace-regexp-in-string
943 "^X-BBDB-" "" (symbol-name (car vcard-element))))
944 (cdr vcard-element)))
946 (defun bbdb-vcard-prepend-x-bbdb-maybe (bbdb-fieldname)
947 "If BBDB-FIELDNAME is in `bbdb-vcard-x-bbdb-candidates', prepend `X-BBDB'."
948 (if (member bbdb-fieldname bbdb-vcard-x-bbdb-candidates)
949 (intern (concat "x-bbdb-" (symbol-name bbdb-fieldname)))
950 bbdb-fieldname)) ; lowercase more consistent here
952 (defun bbdb-vcard-unvcardize-name (vcard-name)
953 "Convert VCARD-NAME (type N) into (FIRSTNAME LASTNAME)."
954 (if (stringp vcard-name) ; unstructured N
955 (bbdb-divide-name vcard-name)
956 (let ((vcard-name
957 (mapcar (lambda (x)
958 (bbdb-join (bbdb-vcard-split-structured-text x "," t)
959 " "))
960 vcard-name))) ; flatten comma-separated substructure
961 (list (concat (nth 3 vcard-name) ; honorific prefixes
962 (unless (zerop (length (nth 3 vcard-name))) " ")
963 (nth 1 vcard-name) ; given name
964 (unless (zerop (length (nth 2 vcard-name))) " ")
965 (nth 2 vcard-name)) ; additional names
966 (concat (nth 0 vcard-name) ; family name
967 (unless (zerop (length (nth 4 vcard-name))) " ")
968 (nth 4 vcard-name)))))) ; honorific suffixes
970 (defun bbdb-vcard-unvcardize-org (vcard-org)
971 "Convert VCARD-ORG (type ORG), which may be a list, into a string."
972 (if (or (null vcard-org)
973 (stringp vcard-org)) ; unstructured, probably non-standard ORG
974 vcard-org ; Company, unit 1, unit 2...
975 (bbdb-join vcard-org "\n")))
977 (defun bbdb-vcard-unvcardize-adr (vcard-adr)
978 "Convert VCARD-ADR into BBDB format.
979 Turn a vCard element of type ADR into (TYPE STREETS CITY STATE ZIP
980 COUNTRY)."
981 (let ((adr-type (or (cdr (assoc "type" vcard-adr)) ""))
982 (streets ; all comma-separated sub-elements of
983 (remove ; Postbox, Extended, Streets go into one list
984 "" (reduce 'append
985 (mapcar (lambda (x)
986 (bbdb-vcard-split-structured-text x "," t))
987 (subseq (cdr (assoc "value" vcard-adr))
988 0 3)))))
989 (non-streets ; turn comma-separated substructure into
990 (mapcar ; newline-separated text
991 (lambda (x) (bbdb-join
992 (bbdb-vcard-split-structured-text x "," t)
993 "\n"))
994 (subseq (cdr (assoc "value" vcard-adr))
995 3 nil))))
996 (vector (bbdb-vcard-translate adr-type)
997 streets
998 (or (elt non-streets 0) "") ; City
999 (or (elt non-streets 1) "") ; State
1000 (or (elt non-streets 2) "") ; Zip
1001 (or (elt non-streets 3) "")))) ; Country
1003 (defun bbdb-vcard-vcardize-address-element (address-element)
1004 "Replace escaped newlines in ADDRESS-ELEMENT by commas."
1005 (replace-regexp-in-string "\\\\n" "," address-element))
1007 (defun bbdb-vcard-translate (label &optional exportp)
1008 "Translate LABEL from vCard to BBDB or, if EXPORTP is non-nil, vice versa.
1009 Translations are defined in `bbdb-vcard-import-translation-table' and
1010 `bbdb-vcard-export-translation-table' respectively."
1011 (when label
1012 (capitalize
1013 (or (assoc-default label
1014 (if exportp
1015 bbdb-vcard-export-translation-table
1016 bbdb-vcard-import-translation-table) 'string-match)
1017 label))))
1019 (defun bbdb-vcard-merge-strings (old-string new-strings separator)
1020 "Merge strings successively from list NEW-STRINGS into OLD-STRING.
1021 If an element of NEW-STRINGS is already in OLD-STRING, leave
1022 OLD-STRING unchanged. Otherwise append SEPARATOR and NEW-STRING."
1023 (with-temp-buffer
1024 (insert old-string)
1025 (dolist (new-string new-strings)
1026 (unless (prog1 (search-backward new-string nil t)
1027 (goto-char (point-max)))
1028 (unless (zerop (buffer-size)) (insert separator))
1029 (insert new-string)))
1030 (buffer-string)))
1032 (defun bbdb-vcard-split-structured-text
1033 (text separator &optional return-always-list-p)
1034 "Split TEXT at unescaped occurrences of SEPARATOR; return parts in a list.
1035 Return text unchanged if there aren't any separators and RETURN-ALWAYS-LIST-P
1036 is nil."
1037 (when (stringp text)
1038 (let ((string-elements
1039 (split-string
1040 (replace-regexp-in-string
1041 (concat "\\\\\r" separator) (concat "\\\\" separator)
1042 (replace-regexp-in-string separator (concat "\r" separator) text))
1043 (concat "\r" separator))))
1044 (if (and (null return-always-list-p)
1045 (= 1 (length string-elements)))
1046 (car string-elements)
1047 string-elements))))
1049 (defun bbdb-vcard-canonicalize-vcard-type (&rest strings)
1050 "Concatenate STRINGS and apply `bbdb-vcard-type-canonicalizer' to them."
1051 (funcall bbdb-vcard-type-canonicalizer (bbdb-join strings "")))
1053 (defun bbdb-vcard-write-buffer (vcard-file-name)
1054 "Write current buffer to VCARD-FILE-NAME.
1055 Create directories where necessary."
1056 (make-directory (file-name-directory vcard-file-name) t)
1057 (let ((buffer-file-coding-system bbdb-vcard-export-coding-system))
1058 (write-region nil nil vcard-file-name nil nil nil t)))
1060 (defun bbdb-vcard-make-file-name (bbdb-record &optional used-up-basenames)
1061 "Come up with a vCard filename given a BBDB-RECORD.
1062 Make it unique against the list USED-UP-BASENAMES."
1063 (let ((name (bbdb-record-name bbdb-record))
1064 (aka (car (bbdb-record-aka bbdb-record)))
1065 (unique-number 0)
1066 filename)
1067 (while (member
1068 (setq filename
1069 (concat
1070 (replace-regexp-in-string
1071 "[[:blank:]]+" "_"
1072 (or (unless (zerop (length name)) name)
1073 (unless (zerop (length aka)) aka)
1074 "bbdb-record"))
1075 (unless (zerop unique-number)
1076 (concat "-" (number-to-string unique-number)))
1077 ".vcf"))
1078 used-up-basenames)
1079 (incf unique-number))
1080 filename))
1082 (defmacro bbdb-vcard-search-intersection
1083 (records &optional name company net notes phone)
1084 "Search RECORDS for records that match each non-nil argument."
1085 (let*
1086 ((phone-search
1087 (if phone `(when ,phone (bbdb-search ,records nil nil nil nil ,phone))
1088 records))
1089 (notes-search
1090 (if notes `(when ,notes (bbdb-search ,phone-search nil nil nil ,notes))
1091 phone-search))
1092 (net-search
1093 (if net `(when ,net (bbdb-search ,notes-search nil nil ,net))
1094 notes-search))
1095 (company-search
1096 (if company `(when ,company (bbdb-search ,net-search nil ,company))
1097 net-search))
1098 (name-search
1099 (if name `(when ,name (bbdb-search ,company-search ,name))
1100 company-search)))
1101 name-search))
1105 (provide 'bbdb-vcard)
1107 ;;; bbdb-vcard.el ends here