vcard2xml.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. #!/usr/bin/env python3
  2. # -*- coding: latin-1 -*-
  3. """
  4. Copyright © 2003 Bogdan Sumanariu <zarrok@yahoo.com>
  5. This file is free software; you can redistribute it and/or modify it
  6. under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful, but
  10. WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. script name : evolutionvcard2claws.py
  17. script purpose : convert an evolution addressbook VCARD file
  18. into a Claws Mail addressbook
  19. tested with evolution 1.2.x, and 1.4.x
  20. """
  21. import string
  22. import sys
  23. import time
  24. import os
  25. from io import StringIO
  26. keywds = ('x-evolution-file-as','fn', 'n','email;internet','nickname', 'url', 'org')
  27. def normalizeLongLines(file):
  28. """
  29. Skip line breaks after 72 chars
  30. """
  31. buf = ''
  32. line = file.readline()
  33. while line:
  34. if line[0] == ' ':
  35. buf = buf.rstrip('\n')
  36. line = line.lstrip();
  37. buf += line
  38. else:
  39. buf += line
  40. line = file.readline()
  41. return buf
  42. def getEmailAddress(vcard):
  43. """
  44. Get email address.
  45. Supported formats:
  46. - email;something
  47. - email;type=something
  48. something := (internet,work,home, other)
  49. """
  50. for key in vcard:
  51. items = key.split(';')
  52. if len(items) == 2:
  53. if items[0].lower() == 'email':
  54. list = vcard[key]
  55. return list[0]
  56. else:
  57. if key.lower() == 'email':
  58. list = vcard[key]
  59. return list[0]
  60. return ""
  61. def findName(vcard):
  62. """
  63. Find a version 3.0 name
  64. """
  65. for key in vcard:
  66. items = key.split(';')
  67. if len(items) == 2:
  68. if items[0].lower() == 'n':
  69. return vcard[key]
  70. else:
  71. if key.lower() == 'n':
  72. return vcard[key]
  73. return None
  74. ################################################################################
  75. ## reads a vcard and stores as hash pairs key/value where value is a list ##
  76. ################################################################################
  77. def readVCARD (buffer) :
  78. """
  79. skips fom <file> until a 'begin' tag from VCARD is encountered.
  80. from this point starts constructing a map (key, [values] )
  81. VCARD entry format -> tag:value
  82. key <- tag
  83. [values] <- list with the values of <tag> if there are more tags with the same name
  84. """
  85. r=' '
  86. bgn,end = -1, -1;
  87. d = dict()
  88. while r and bgn < 0 :
  89. r = buffer.readline()
  90. if len (r) == 0 : return dict()
  91. if r.strip().lower().find('begin') :
  92. bgn = 1
  93. while r and end < 0 :
  94. r = buffer.readline()
  95. s = r.strip().lower().split(':')
  96. if s[0] != '' :
  97. if s[0] in d :
  98. d[s[0]].append(s[1])
  99. elif len(s) > 1:
  100. d[s[0]] = [s[1]]
  101. else :
  102. d[s[0]] = ['']
  103. if s[0] == 'end' : end = 1
  104. return d
  105. ##################################################################################
  106. ###############################################################################################
  107. ## writes on a given file an xml representation for claws-mail addressbook received as a hash ##
  108. ###############################################################################################
  109. def writeXMLREPR (vcard,file,uid) :
  110. """
  111. based on <vcard> and <uid> writes only recognized tags (the ones defined in <keywds> list)
  112. NOTE: <url> and <org> tag will be written as attributes (there are such tags in claws-mail's
  113. XML schema)
  114. """
  115. if len (vcard.keys()) == 0 : return
  116. item = vcard.get(keywds[2]);
  117. if item:
  118. name = item[0].split(';')
  119. else:
  120. """ version 3.0 n ?"""
  121. name = findName(vcard)
  122. if not name:
  123. return
  124. fn, ln, nick, cn, a = '', '', '', '', ''
  125. if len(name) >= 2 :
  126. fn = name[0]
  127. ln = name[1]
  128. elif len(name) ==1 :
  129. fn = name[0]
  130. if keywds[4] in vcard :
  131. nick = vcard.get(keywds[4])[0]
  132. if len(vcard.get(keywds[1])[0]) :
  133. cn = vcard.get(keywds[1])[0]
  134. else :
  135. cn = vcard.get(keywds[0])[0];
  136. a += str('\n<person uid=\"' + str(uid[0]) + '\" first-name=\"' + fn + '\" last-name=\"' + ln
  137. + '\" nick-name=\"' + nick + '\" cn=\"' + cn + '\" >\n')
  138. a += '\t<address-list>\n'
  139. if vcard.get(keywds[3]) :
  140. for c in vcard.get(keywds[3]) :
  141. uid[0] = uid[0] + 1
  142. a += '\t\t<address uid=\"' + str(uid[0]) + '\" alias=\"' + nick + '\" email=\"' + c + '\" remarks=\"\" />\n'
  143. else :
  144. email = getEmailAddress(vcard)
  145. uid[0] = uid[0]+1
  146. a += '\t\t<address uid=\"' + str(uid[0]) + '\" alias=\"' + nick + '\" email=\"' + email + '\" remarks=\"\" />\n'
  147. a += '\t</address-list>\n'
  148. a += '\t<attribute-list>\n'
  149. for key in keywds[5:] :
  150. if vcard.get(key) :
  151. for c in vcard.get(key) :
  152. uid[0] = uid[0] + 1
  153. a += '\t\t<attribute uid=\"' + str(uid[0]) + '\" name=\"' + key +'\">'+c+'</attribute>\n'
  154. a += '\t</attribute-list>\n'
  155. a += '</person>\n'
  156. file.write(a)
  157. file.flush()
  158. ###################################################################################################
  159. def convert (in_f, o_f, name='INBOX') :
  160. d = {'d':1}
  161. uid = [int(time.time())]
  162. try :
  163. print('proccessing...\n')
  164. o_f.write('<?xml version="1.0" encoding="ISO-8859-1" ?>\n<address-book name="'+name+'" >\n');
  165. buf = normalizeLongLines(in_f)
  166. buffer = StringIO(buf)
  167. while len(d.keys()) > 0 :
  168. d = readVCARD(buffer)
  169. writeXMLREPR (d, o_f, uid)
  170. uid[0] = uid [0]+1
  171. o_f.write('\n</address-book>')
  172. print('finished processing...\n')
  173. except IOError as err :
  174. print('Caught an IOError : ',err,'\t ABORTING!!!')
  175. raise err
  176. #################################################################################################
  177. def execute () :
  178. if len(sys.argv) != 3 and len(sys.argv) != 2 :
  179. print(str("\nUsage: vcard2xml.py source_file [destination_file]\n\n" +
  180. '\tWhen only <source_file> is specified will overwrite the existing addressbook.\n'+
  181. '\tWhen both arguments are suplied will create a new additional addressbook named \n\tas the destination file.'+'\n\tNOTE: in both cases the Claws Mail must be closed and ran at least once.\n\n'))
  182. sys.exit(1)
  183. in_file = None
  184. out_file = None
  185. path_to_out = os.environ['HOME']+'/.claws-mail/addrbook/'
  186. adr_idx = 'addrbook--index.xml'
  187. adr_idx_file = None
  188. tmp_adr_idx_file= None
  189. got_ex = 0
  190. try :
  191. in_file = open(sys.argv[1])
  192. except IOError as e:
  193. print('Could not open input file <',sys.argv[1],'> ABORTING')
  194. sys.exit(1)
  195. if len(sys.argv) == 2 :
  196. try :
  197. dlist = os.listdir(path_to_out);
  198. flist=[]
  199. for l in dlist :
  200. if l.find('addrbook') == 0 and l.find("addrbook--index.xml") < 0 and l.find('bak') < 0 :
  201. flist.append(l)
  202. flist.sort()
  203. out_file = flist.pop()
  204. os.rename(path_to_out+out_file, path_to_out+out_file+'.tmp')
  205. out_file = open(path_to_out+out_file,'w')
  206. convert(in_file, out_file)
  207. except Exception as e:
  208. got_ex = 1
  209. print('got exception: ', e)
  210. else :
  211. try :
  212. os.rename(path_to_out+adr_idx, path_to_out+adr_idx+'.tmp')
  213. tmp_adr_idx_file = open(path_to_out+adr_idx+'.tmp')
  214. adr_idx_file = open(path_to_out+adr_idx,'w')
  215. except Exception as e :
  216. print('Could not open <', path_to_out+adr_idx,'> file. Make sure you started Claws Mail at least once.')
  217. sys.exit(1)
  218. try :
  219. out_file = open(path_to_out+sys.argv[2],'w')
  220. convert(in_file, out_file, sys.argv[2].split('.xml')[0])
  221. l = tmp_adr_idx_file.readline()
  222. while l :
  223. if l.strip() == '</book_list>' :
  224. adr_idx_file.write('\t<book name="'+sys.argv[2].split('.xml')[0] +'" file="'+sys.argv[2]+'" />\n')
  225. adr_idx_file.write(l)
  226. else :
  227. adr_idx_file.write(l)
  228. l = tmp_adr_idx_file.readline()
  229. except Exception as e:
  230. got_ex = 1
  231. print('got exception: ', e)
  232. if got_ex :
  233. #clean up the mess
  234. print('got exception, cleaning up the mess... changed files will be restored...\n')
  235. if adr_idx_file :
  236. adr_idx_file.close()
  237. if out_file :
  238. out_file.close()
  239. if len(sys.argv) == 2 :
  240. os.rename(out_file.name+'.tmp', out_file.name)
  241. else :
  242. os.remove(out_file.name)
  243. os.rename(path_to_out+adr_idx+'.tmp', path_to_out+adr_idx)
  244. if tmp_adr_idx_file :
  245. tmp_adr_idx_file.close()
  246. else :
  247. #closing all and moving temporary data into place
  248. print('closing open files...\n')
  249. in_file.close()
  250. out_file.close()
  251. if len(sys.argv) == 3 :
  252. os.rename(path_to_out+adr_idx+'.tmp',path_to_out+adr_idx+'.bak' )
  253. if len(sys.argv) == 2 :
  254. os.rename(out_file.name+'.tmp', out_file.name+'.bak')
  255. if adr_idx_file :
  256. adr_idx_file.close()
  257. if tmp_adr_idx_file :
  258. tmp_adr_idx_file.close()
  259. print('done!')
  260. if __name__ == '__main__':
  261. execute ()