1
2
3
4
5
6
7
8
9
10 """Code for reading and writing Vcard
11
12 VCARD is defined in RFC 2425 and 2426
13 """
14 from __future__ import with_statement
15 import sys
16 import quopri
17 import base64
18 import codecs
19 import cStringIO
20
21 import common
22 import nameparser
23 import phonenumber
24
27
29 _charset_aliases={
30 'MACINTOSH': 'MAC_ROMAN'
31 }
37
40
42
43 while True:
44 line=self._getnextline()
45 if line is None:
46 raise StopIteration()
47 if len(line)!=0:
48 break
49
50
51
52 normalcontinuations=True
53 colon=line.find(':')
54 if colon>0:
55 s=line[:colon].lower().split(";")
56 if "quoted-printable" in s or 'encoding=quoted-printable' in s:
57 normalcontinuations=False
58 while line[-1]=="=" or line[-2]=='=':
59 if line[-1]=='=':
60 i=-1
61 else:
62 i=-2
63 nextl=self._getnextline()
64 if nextl[0] in ("\t", " "): nextl=nextl[1:]
65 line=line[:i]+nextl
66
67 while normalcontinuations:
68 nextline=self._lookahead()
69 if nextline is None:
70 break
71 if len(nextline)==0:
72 break
73 if nextline[0]!=' ' and nextline[0]!='\t':
74 break
75 line+=self._getnextline()[1:]
76
77 colon=line.find(':')
78 if colon<1:
79
80
81 if __debug__:
82 print "Fixing up bad line",line
83 colon=len(line)
84 line+=":"
85
86 b4=line[:colon]
87 line=line[colon+1:].strip()
88
89
90 items=b4.upper().split(";")
91
92 newitems=[]
93 if isinstance(line, unicode):
94 charset=None
95 else:
96 charset="LATIN-1"
97 for i in items:
98
99
100
101 if i.startswith("CHARSET="):
102 charset = i[8:] or "LATIN-1"
103 continue
104
105 if not i.startswith("ENCODING=") and not i=="QUOTED-PRINTABLE":
106
107 newitems.append(i)
108 continue
109 try:
110 if i=='QUOTED-PRINTABLE' or i=="ENCODING=QUOTED-PRINTABLE":
111
112 line=quopri.decodestring(line)
113 elif i=='ENCODING=B':
114 line=base64.decodestring(line)
115 charset=None
116 else:
117 raise VFileException("unknown encoding: "+i)
118 except Exception,e:
119 if isinstance(e,VFileException):
120 raise e
121 raise VFileException("Exception %s while processing encoding %s on data '%s'" % (str(e), i, line))
122
123
124 if len(newitems)==0:
125 raise VFileException("Line contains no property: %s" % (line,))
126
127 if charset is not None:
128 try:
129 decoder=codecs.getdecoder(self._charset_aliases.get(charset, charset))
130 line,_=decoder(line)
131 except LookupError:
132 raise VFileException("unknown character set '%s' in parameters %s" % (charset, b4))
133 if newitems==["BEGIN"] or newitems==["END"]:
134 line=line.upper()
135 return newitems,line
136
138 if self.saved is not None:
139 line=self.saved
140 self.saved=None
141 return line
142 else:
143 return self._readandstripline()
144
146 line=self.source.readline()
147 if line is not None:
148 if len(line)==0:
149 return None
150 elif line[-2:]=="\r\n":
151
152
153 return line[:-2]
154 elif line[-1]=='\r' or line[-1]=='\n':
155 return line[:-1]
156 return line
157
159 assert self.saved is None
160 self.saved=self._readandstripline()
161 return self.saved
162
164 "Understands vcards in a vfile"
165
168
171
173
174 field=value=None
175 for field,value in self.vfile:
176 if (field,value)!=(["BEGIN"], "VCARD"):
177 continue
178 found=True
179 break
180 if (field,value)!=(["BEGIN"], "VCARD"):
181
182 raise StopIteration()
183
184 lines=[]
185 for field,value in self.vfile:
186 if (field,value)!=(["END"], "VCARD"):
187 lines.append( (field,value) )
188 continue
189 break
190 if (field,value)!=(["END"], "VCARD"):
191 raise VFileException("There is a BEGIN:VCARD but no END:VCARD")
192 return VCard(lines)
193
195 "A single vcard"
196
198 self._version=(2,0)
199 self._origin=None
200 self._data={}
201 self._groups={}
202 self.lines=[]
203
204 for f,v in lines:
205 assert len(f)
206 if f==["X-EVOLUTION-FILE-AS"]:
207 self._origin="evolution"
208 if f[0].startswith("ITEM") and (f[0].endswith(".X-ABADR") or f[0].endswith(".X-ABLABEL")):
209 self._origin="apple"
210 if len(v) and v[0].find(">!$_") > v[0].find("_$!<") >=0:
211 self.origin="apple"
212 if f==["VERSION"]:
213 ver=v.split(".")
214 try:
215 ver=[int(xx) for xx in ver]
216 except ValueError:
217 raise VFileException(v+" is not a valid vcard version")
218 self._version=ver
219 continue
220
221
222 if f[0]=="HOME.TEL": f[0:1]=["TEL", "HOME"]
223 elif f[0]=="HOME.LABEL": f[0:1]=["LABEL", "HOME"]
224 elif f[0]=="WORK.TEL": f[0:1]=["TEL", "WORK"]
225 elif f[0]=="WORK.LABEL": f[0:1]=["LABEL", "WORK"]
226 self.lines.append( (f,v) )
227 self._parse(self.lines, self._data)
228 self._update_groups(self._data)
229
231 "Returns a dict of the data parsed out of the vcard"
232 return self._data
233
235 """Returns the fieldname to use in the dict.
236
237 For example, if name is "email" and there is no "email" field
238 in dict, then "email" is returned. If there is already an "email"
239 field then "email2" is returned, etc"""
240 if name not in dict:
241 return name
242 for i in xrange(2,99999):
243 if name+`i` not in dict:
244 return name+`i`
245
246 - def _parse(self, lines, result):
247 for field,value in lines:
248 if len(value.strip())==0:
249 continue
250 if '.' in field[0]:
251 f=field[0][field[0].find('.')+1:]
252 else: f=field[0]
253 t=f.replace("-", "_")
254 func=getattr(self, "_field_"+t, self._default_field)
255 func(field, value, result)
256
258 """Update the groups info """
259 for k,e in self._groups.items():
260 self._setvalue(result, *e)
261
262
263
266
267 _field_LABEL=_field_ignore
268 _field_BDAY=_field_ignore
269 _field_ROLE=_field_ignore
270 _field_CALURI=_field_ignore
271 _field_CALADRURI=_field_ignore
272 _field_FBURL=_field_ignore
273 _field_REV=_field_ignore
274 _field_KEY=_field_ignore
275 _field_SOURCE=_field_ignore
276 _field_PHOTO=_field_ignore
277
278
279
282
285
289
292
294 result["uid"]=self.unquote(value)
295
296
297
298
299
300 - def _field_N(self, field, value, result):
301 value=self.splitandunquote(value)
302 familyname=givenname=additionalnames=honorificprefixes=honorificsuffixes=None
303 try:
304 familyname=value[0]
305 givenname=value[1]
306 additionalnames=value[2]
307 honorificprefixes=value[3]
308 honorificsuffixes=value[4]
309 except IndexError:
310 pass
311 if familyname is not None and len(familyname):
312 result[self._getfieldname("last name", result)]=familyname
313 if givenname is not None and len(givenname):
314 result[self._getfieldname("first name", result)]=givenname
315 if additionalnames is not None and len(additionalnames):
316 result[self._getfieldname("middle name", result)]=additionalnames
317 if honorificprefixes is not None and len(honorificprefixes):
318 result[self._getfieldname("prefix", result)]=honorificprefixes
319 if honorificsuffixes is not None and len(honorificsuffixes):
320 result[self._getfieldname("suffix", result)]=honorificsuffixes
321
322 _field_NAME=_field_N
323
330
331 _field_O=_field_ORG
332
334 value=self.unquote(value)
335
336 types=[]
337 for f in field[1:]:
338 if f.startswith("TYPE="):
339 ff=f[len("TYPE="):].split(",")
340 else: ff=[f]
341 types.extend(ff)
342
343
344
345 type=None
346 for t in types:
347 if t=="HOME": type="home"
348 if t=="WORK": type="business"
349 if t=="X400": return
350
351 preferred="PREF" in types
352
353 if type is None:
354 self._setvalue(result, "email", value, preferred)
355 else:
356 addr={'email': value, 'type': type}
357 self._setvalue(result, "email", addr, preferred)
358
360
361
362 value=self.unquote(value)
363
364 types=[]
365 for f in field[1:]:
366 if f.startswith("TYPE="):
367 ff=f[len("TYPE="):].split(",")
368 else: ff=[f]
369 types.extend(ff)
370
371 type=None
372 for t in types:
373 if t=="HOME": type="home"
374 if t=="WORK": type="business"
375
376 preferred="PREF" in types
377
378 if type is None:
379 self._setvalue(result, "url", value, preferred)
380 else:
381 addr={'url': value, 'type': type}
382 self._setvalue(result, "url", addr, preferred)
383
385 if '.' in field[0]:
386 group=field[0][:field[0].find('.')]
387 else:
388 group=None
389 if group is None:
390
391 print 'speedial has no group'
392 else:
393 self._setgroupvalue(result, 'phone', { 'speeddial': int(value) },
394 group, False)
395
397 value=self.unquote(value)
398
399 if '.' in field[0]:
400 group=field[0][:field[0].find('.')]
401 else:
402 group=None
403
404
405 types=[]
406 for f in field[1:]:
407 if f.startswith("TYPE="):
408 ff=f[len("TYPE="):].split(",")
409 else: ff=[f]
410 types.extend(ff)
411
412
413 munge={ "BBS": "DATA", "MODEM": "DATA", "ISDN": "DATA", "CAR": "CELL", "PCS": "CELL" }
414 types=[munge.get(t, t) for t in types]
415
416
417 types=[t for t in types if t in ("HOME", "WORK", "MSG", "PREF", "VOICE", "FAX", "CELL", "VIDEO", "PAGER", "DATA")]
418
419
420 antivoice=["FAX", "PAGER", "DATA"]
421 if "VOICE" in types:
422 voice=True
423 else:
424 voice=True
425 for f in antivoice:
426 if f in types:
427 voice=False
428 break
429
430 preferred="PREF" in types
431
432
433
434
435
436 iswork=False
437 ishome=False
438 if "WORK" in types: iswork=True
439 if "HOME" in types: ishome=True
440
441 if len(types)==0 or types==["PREF"]: iswork=True
442
443
444 value=phonenumber.normalise(value)
445 if iswork and voice:
446 self._setgroupvalue(result,
447 "phone", {"type": "business", "number": value},
448 group, preferred)
449 if ishome and voice:
450 self._setgroupvalue(result,
451 "phone", {"type": "home", "number": value},
452 group, preferred)
453 if not iswork and not ishome and "FAX" in types:
454
455 self._setgroupvalue(result,
456 "phone", {"type": "fax", "number": value},
457 group, preferred)
458 else:
459 if iswork and "FAX" in types:
460 self._setgroupvalue(result, "phone",
461 {"type": "business fax", "number": value},
462 group, preferred)
463 if ishome and "FAX" in types:
464 self._setgroupvalue(result, "phone",
465 {"type": "home fax", "number": value},
466 group, preferred)
467 if "CELL" in types:
468 self._setgroupvalue(result,
469 "phone", {"type": "cell", "number": value},
470 group, preferred)
471 if "PAGER" in types:
472 self._setgroupvalue(result,
473 "phone", {"type": "pager", "number": value},
474 group, preferred)
475 if "DATA" in types:
476 self._setgroupvalue(result,
477 "phone", {"type": "data", "number": value},
478 group, preferred)
479
480 - def _setgroupvalue(self, result, type, value, group, preferred=False):
481 """ Set value of an item of a group
482 """
483 if group is None:
484
485 return self._setvalue(result, type, value, preferred)
486 group_type=self._groups.get(group, None)
487 if group_type is None:
488
489 self._groups[group]=[type, value, preferred]
490 else:
491 if type!=group_type[0]:
492 print 'Group',group,'has different types:',type,groups_type[0]
493 if preferred:
494 group_type[2]=True
495 group_type[1].update(value)
496
497 - def _setvalue(self, result, type, value, preferred=False):
498 if type not in result:
499 result[type]=value
500 return
501 if not preferred:
502 result[self._getfieldname(type, result)]=value
503 return
504
505 values=[value]
506 for suffix in [""]+range(2,99):
507 if type+str(suffix) in result:
508 values.append(result[type+str(suffix)])
509 else:
510 break
511 suffixes=[""]+range(2,len(values)+1)
512 for l in range(len(suffixes)):
513 result[type+str(suffixes[l])]=values[l]
514
516
517 values=self.splitandunquote(value, seperator=",")
518 values=[v.replace(";", "").strip() for v in values]
519 values=[v for v in values if len(v)]
520 v=result.get('categories', None)
521 if v:
522 result['categories']=';'.join([v, ";".join(values)])
523 else:
524 result['categories']=';'.join(values)
525
527
528 values=self.splitandunquote(value, seperator=",")
529 values=[v.replace(";", "").strip() for v in values]
530 values=[v for v in values if len(v)]
531 result[self._getfieldname("ringtones", result)]=";".join(values)
532
533 _field_CATEGORY=_field_CATEGORIES
534
536
537 preferred=False
538 type="business"
539 for f in field[1:]:
540 if f.startswith("TYPE="):
541 ff=f[len("TYPE="):].split(",")
542 else: ff=[f]
543 for x in ff:
544 if x=="HOME":
545 type="home"
546 if x=="PREF":
547 preferred=True
548
549 value=self.splitandunquote(value)
550 pobox=extendedaddress=streetaddress=locality=region=postalcode=country=None
551 try:
552 pobox=value[0]
553 extendedaddress=value[1]
554 streetaddress=value[2]
555 locality=value[3]
556 region=value[4]
557 postalcode=value[5]
558 country=value[6]
559 except IndexError:
560 pass
561 addr={}
562 if pobox is not None and len(pobox):
563 addr["pobox"]=pobox
564 if extendedaddress is not None and len(extendedaddress):
565 addr["street2"]=extendedaddress
566 if streetaddress is not None and len(streetaddress):
567 addr["street"]=streetaddress
568 if locality is not None and len(locality):
569 addr["city"]=locality
570 if region is not None and len(region):
571 addr["state"]=region
572 if postalcode is not None and len(postalcode):
573 addr["postalcode"]=postalcode
574 if country is not None and len(country):
575 addr["country"]=country
576 if len(addr):
577 addr["type"]=type
578 self._setvalue(result, "address", addr, preferred)
579
581
582 ff=field[0].split(".")
583 f0=ff[0]
584 f1=ff[1] if len(ff)>1 else ''
585 if f0.startswith('X-PALM-CATEGORY') or f1.startswith('X-PALM-CATEGORY'):
586 self._field_CATEGORIES(['CATEGORIES'], value, result)
587 elif f0=='X-PALM-NICKNAME' or f1=='X-PALM-NICKNAME':
588 self._field_NICKNAME(['NICKNAME'], value, result)
589 else:
590 if __debug__:
591 print 'ignoring PALM custom field',field
592
594 ff=field[0].split(".")
595 f0=ff[0]
596 f1=ff[1] if len(ff)>1 else ''
597 if f0.startswith('X-PALM-') or f1.startswith('X-PALM-'):
598 self._field_X_PALM(field, value, result)
599 return
600 elif f0.startswith("X-") or f1.startswith("X-"):
601 if __debug__:
602 print "ignoring custom field",field
603 return
604 if __debug__:
605 print "no idea what do with"
606 print "field",field
607 print "value",value[:80]
608
617
619
620
621
622 if value.find("\\"+seperator)<0:
623 return [self.unquote(v) for v in value.split(seperator)]
624
625
626 res=[]
627 build=""
628 v=0
629 while v<len(value):
630 if value[v]==seperator:
631 res.append(build)
632 build=""
633 v+=1
634 continue
635
636
637 if value[v]=="\\":
638 build+=value[v:v+2]
639 v+=2
640 continue
641 build+=value[v]
642 v+=1
643 if len(build):
644 res.append(build)
645
646 return [self.unquote(v) for v in res]
647
649 "Best guess as to vcard version"
650 return self._version
651
653 "Best guess as to what program wrote the vcard"
654 return self._origin
655
662
663
664
665
666
667
668
670 """My own routine to do qouted printable since the builtin one doesn't encode CR or NL!"""
671 return quopri.encodestring(value).replace("\r", "=0D").replace("\n", "=0A")
672
676
680
681 _string_formatters=(format_stringv2, format_stringv3)
682
687
689 """Determine if v is a sequence such as passed to value in out_line.
690 Note that a sequence of chars is not a sequence for our purposes."""
691 return isinstance(v, (type( () ), type([])))
692
693 -def out_line(name, attributes, value, formatter, join_char=";"):
694 """Returns a single field correctly formatted and encoded (including trailing newline)
695
696 @param name: The field name
697 @param attributes: A list of string attributes (eg "TYPE=intl,post" ). Usually
698 empty except for TEL and ADR. You can also pass in None.
699 @param value: The field value. You can also pass in a list of components which will be
700 joined with join_char such as the 6 components of N
701 @param formatter: The function that formats the value/components. See the
702 various format_ functions. They will automatically ensure that
703 ENCODING=foo attributes are added if appropriate"""
704
705 if attributes is None: attributes=[]
706 else: attributes=list(attributes[:])
707
708 if formatter in _string_formatters:
709 if _is_sequence(value):
710 qp=False
711 for f in value:
712 f=formatter(f)
713 if myqpencodestring(f)!=f:
714 qp=True
715 break
716 if qp:
717 attributes.append("ENCODING=QUOTED-PRINTABLE")
718 value=[myqpencodestring(f) for f in value]
719
720 value=join_char.join(value)
721 else:
722 value=formatter(value)
723
724 qp= myqpencodestring(value)!=value
725 if qp:
726 value=myqpencodestring(value)
727 attributes.append("ENCODING=QUOTED-PRINTABLE")
728 else:
729 assert not _is_sequence(value)
730 if formatter is not None:
731 value=formatter(value)
732
733 res=";".join([name]+attributes)+":"
734 res+=_line_reformat(value, 70, 70-len(res))
735 assert res[-1]!="\n"
736
737 return res+"\n"
738
761
763 res=""
764 for v in vals[:limit]:
765
766 res+=out_line("FN", None, nameparser.formatsimplename(v), formatter)
767
768 f,m,l=nameparser.getparts(v)
769 res+=out_line("N", None, (l,f,m,"",""), formatter)
770
771 nn=v.get("nickname", "")
772 if len(nn):
773 res+=out_line("NICKNAME", None, nn, formatter)
774 return res
775
776
778 cats=[v.get("category") for v in vals]
779 if len(cats):
780 return out_line(field, None, cats, formatter, join_char=",")
781 return ""
782
785
786
787 -def out_eu(vals, formatter, field, bpkey):
788 res=""
789 first=True
790 for v in vals:
791 val=v.get(bpkey)
792 type=v.get("type", "")
793 if len(type):
794 if type=="business": type="work"
795 type=type.upper()
796 if first:
797 type=type+",PREF"
798 elif first:
799 type="PREF"
800 if len(type):
801 type=["TYPE="+type+["",",INTERNET"][field=="EMAIL"]]
802 else:
803 type=None
804 res+=out_line(field, type, val, formatter)
805 first=False
806 return res
807
809 return out_eu(vals, formatter, "EMAIL", "email")
810
812 return out_eu(vals, formatter, "URL", "url")
813
814
815 _out_tel_mapping={ 'home': 'HOME',
816 'office': 'WORK',
817 'cell': 'CELL',
818 'fax': 'FAX',
819 'pager': 'PAGER',
820 'data': 'MODEM',
821 'none': 'VOICE'
822 }
824
825 phones=['phone'+str(x) for x in ['']+range(2,len(vals)+1)]
826 res=""
827 first=True
828 idx=0
829 for v in vals:
830 sp=v.get('speeddial', None)
831 if sp is None:
832
833 res+=out_line("TEL",
834 ["TYPE=%s%s" % (_out_tel_mapping[v['type']], ("", ",PREF")[first])],
835 phonenumber.format(v['number']), formatter)
836 else:
837 res+=out_line(phones[idx]+".TEL",
838 ["TYPE=%s%s" % (_out_tel_mapping[v['type']], ("", ",PREF")[first])],
839 phonenumber.format(v['number']), formatter)
840 res+=out_line(phones[idx]+".X-SPEEDDIAL", None, str(sp), formatter)
841 idx+=1
842 first=False
843 return res
844
845
847
848 res=""
849 first=True
850 for v in vals:
851 o=v.get("company", "")
852 if len(o):
853 res+=out_line("ORG", None, o, formatter)
854 if v.get("type")=="home": type="HOME"
855 else: type="WORK"
856 type="TYPE="+type+("", ",PREF")[first]
857 res+=out_line("ADR", [type], [v.get(k, "") for k in (None, "street2", "street", "city", "state", "postalcode", "country")], formatter)
858 first=False
859 return res
860
862 return "".join([out_line("NOTE", None, v["memo"], formatter) for v in vals[:limit]])
863
864
866 res=""
867 _pref=len(vals)>1
868 for v in vals:
869 res+=out_line("TEL",
870 ["TYPE=%s%s" % ("PREF," if _pref else "",
871 _out_tel_mapping[v['type']])],
872 phonenumber.format(v['number']), formatter)
873 _pref=False
874 return res
876 res=''
877 for _idx in range(min(len(vals), 2)):
878 v=vals[_idx]
879 if v.get('email', None):
880 res+=out_line('EMAIL', ['TYPE=INTERNET'],
881 v['email'], formatter)
882 return res
884 if vals and vals[0].get('url', None):
885 return out_line('URL', None, vals[0]['url'], formatter)
886 return ''
888 for v in vals:
889 if v.get('type', None)=='home':
890 _type='HOME'
891 else:
892 _type='WORK'
893 return out_line("ADR", ['TYPE=%s'%_type],
894 [v.get(k, "") for k in (None, "street2", "street", "city", "state", "postalcode", "country")],
895 formatter)
896 return ''
897
898
899
900
901 _field_order=("names", "wallpapers", "addresses", "numbers", "categories", "emails", "urls", "ringtones", "flags", "memos", "serials")
902
903 -def output_entry(entry, profile, limit_fields=None):
904
905
906 if __debug__ and limit_fields is not None:
907 assert len([f for f in limit_fields if f not in _field_order])==0
908
909 fmt=profile["_formatter"]
910 io=cStringIO.StringIO()
911 io.write(out_line("BEGIN", None, "VCARD", None))
912 io.write(out_line("VERSION", None, profile["_version"], None))
913
914 if limit_fields is None:
915 fields=_field_order
916 else:
917 fields=[f for f in _field_order if f in limit_fields]
918
919 for f in fields:
920 if f in entry and f in profile:
921 func=profile[f]
922
923 if "limit" in func.func_code.co_varnames[:func.func_code.co_argcount]:
924 lines=func(entry[f], fmt, limit=profile["_limit"])
925 else:
926 lines=func(entry[f], fmt)
927 if len(lines):
928 io.write(lines)
929
930 io.write(out_line("END", None, "VCARD", fmt))
931 return io.getvalue()
932
933 profile_vcard2={
934 '_formatter': format_stringv2,
935 '_limit': 1,
936 '_version': "2.1",
937 'names': out_names,
938 'categories': out_categories,
939 'emails': out_emails,
940 'urls': out_urls,
941 'numbers': out_tel,
942 'addresses': out_adr,
943 'memos': out_note,
944 }
945
946 profile_vcard3=profile_vcard2.copy()
947 profile_vcard3['_formatter']=format_stringv3
948 profile_vcard3['_version']="3.0"
949
950 profile_apple=profile_vcard3.copy()
951 profile_apple['categories']=out_categories_apple
952
953 profile_full=profile_vcard3.copy()
954 profile_full['_limit']=99999
955
956 profile_scp6600=profile_full.copy()
957 del profile_scp6600['categories']
958 profile_scp6600.update(
959 { 'numbers': out_tel_scp6600,
960 'emails': out_email_scp6600,
961 'urls': out_url_scp660,
962 'addresses': out_adr_scp6600,
963 })
964
965 profiles={
966 'vcard2': { 'description': "vCard v2.1", 'profile': profile_vcard2 },
967 'vcard3': { 'description': "vCard v3.0", 'profile': profile_vcard3 },
968 'apple': { 'description': "Apple", 'profile': profile_apple },
969 'fullv3': { 'description': "Full vCard v3.0", 'profile': profile_full},
970 'scp6600': { 'description': "Sanyo SCP-6600 (Katana)",
971 'profile': profile_scp6600 },
972 }
973
974
975 if __name__=='__main__':
976
982
987
989 p="fullv3"
990 if len(sys.argv)==4: p=sys.argv[4]
991 print "Using profile", profiles[p]['description']
992 profile=profiles[p]['profile']
993
994 d={'result': {}}
995 try:
996 execfile(sys.argv[1], d,d)
997 except UnicodeError:
998 common.unicode_execfile(sys.argv[1], d,d)
999
1000 with file(sys.argv[2], "wt") as f:
1001 for k in d['result']['phonebook']:
1002 print >>f, output_entry(d['result']['phonebook'][k], profile)
1003
1004 if len(sys.argv)==2:
1005
1006
1007 _wrap(dump_vcards)
1008 elif len(sys.argv)==3 or len(sys.argv)==4:
1009 _wrap(turn_around)
1010 else:
1011 print """one arg: import the named vcard file
1012 two args: first arg is phonebook/index.idx file, write back out to arg2 in vcard format
1013 three args: same as two but last arg is profile to use.
1014 profiles are""", profiles.keys()
1015