You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
5.6 KiB
191 lines
5.6 KiB
#!/bin/env python
|
|
""" vcardin -- convert vCard .vcf data to hCard XHTML
|
|
|
|
Kid User's Guide
|
|
0.6.
|
|
Revision: 131
|
|
Date: 2005-03-09 15:26:45 -0500 (Wed, 09 Mar 2005)
|
|
|
|
.. kid_: http://lesscode.org/projects/kid/ moved> http://www.kid-templating.org/
|
|
|
|
"""
|
|
|
|
__version__ = '$Id: vcardin.py,v 1.6 2009/04/28 04:26:34 connolly Exp $'
|
|
|
|
# python std lib
|
|
import codecs, base64
|
|
# peer open source stuff
|
|
import kid # http://lesscode.org/projects/kid/
|
|
# local stuff
|
|
import icslex
|
|
|
|
class Usage(Exception):
|
|
"""
|
|
Usage:
|
|
|
|
python vcardin.py contact-template.kid c1.vcf c2.vcf >contacts.html
|
|
"""
|
|
|
|
def __str__(self):
|
|
return self.__doc__ + "\n"
|
|
|
|
def main(argv):
|
|
|
|
try:
|
|
kidfn = argv[1]
|
|
except IndexError:
|
|
raise Usage()
|
|
|
|
template=kid.Template(file=kidfn)
|
|
for txt in convert(template, concat(argv[2:])):
|
|
sys.stdout.write(txt)
|
|
|
|
# property : (type, fields, min, max)
|
|
Properties = {'fn': ('text', None, 1, 1),
|
|
'n': ('text', ('family-name',
|
|
'given-name',
|
|
'additional-name',
|
|
'honorific-prefix',
|
|
'honorific-suffix',), 1, 1),
|
|
'nickname': ('text', None, 0, 1),
|
|
'photo': ('mime', None, 0, 1),
|
|
'bday': ('text', None, 0, 1), # datetime?
|
|
'adr': ('text', ('post-office-box',
|
|
'extended-address',
|
|
'street-address',
|
|
'locality',
|
|
'region',
|
|
'postal-code',
|
|
'country-name'), 0, None),
|
|
# 'type' and 'value' are lower level
|
|
'label': ('text', None, 0, 1),
|
|
'tel': ('text', None, 0, None),
|
|
'email': ('text', None, 0, None),
|
|
'mailer': ('text', None, 0, 1),
|
|
'tz': ('text', None, 0, 1),
|
|
'geo': ('float', ('latitude',
|
|
'longitude'), 0, 1),
|
|
'title': ('text', None, 0, 1),
|
|
'role': ('text', None, 0, 1),
|
|
'logo': ('mime', None, 0, 1),
|
|
'agent': ('vcard', None, 0, None),
|
|
'org': ('text', ('organization-name',
|
|
'organization-unit'), 0, 1),
|
|
'categories': ('text', (), 0, 1), #hmm... category or categories?
|
|
'note': ('text', None, 0, 1),
|
|
'rev': ('text', None, 0, 1), # datetime?
|
|
'sort-string': ('text', None, 0, 1),
|
|
'sound': ('mime', None, 0, 1),
|
|
'uid': ('text', None, 0, 1),
|
|
'url': ('text', None, 0, 1), #hmm... more than one URL?
|
|
'class': ('text', None, 0, 1),
|
|
'key': ('text', None, 0, 1),
|
|
|
|
'prodid': ('text', None, 0, 1),
|
|
'version': ('text', None, 0, 1),
|
|
'source': ('text', None, 0, 1),
|
|
'name': ('text', None, 0, 1),
|
|
}
|
|
|
|
def convert(template, data):
|
|
"""generate XHTML from vcard data and kid template
|
|
"""
|
|
lines = icslex.unbreak(data)
|
|
template.contacts = list(contacts(Properties, lines))
|
|
for txt in template.generate(output='xml', encoding='utf-8'):
|
|
yield txt
|
|
|
|
|
|
def concat(files):
|
|
"""iterate over lines in a bunch of (utf-8) files
|
|
|
|
@@this is in the python stdlib, surely
|
|
"""
|
|
for fn in files:
|
|
for line in codecs.open(fn, encoding='utf-8'):
|
|
yield line
|
|
|
|
|
|
def contacts(schema, lines):
|
|
while 1:
|
|
try:
|
|
delim, name, items = icslex.propertyitems(lines)
|
|
except StopIteration:
|
|
break
|
|
|
|
if delim == 'begin': continue
|
|
|
|
obj = {}
|
|
for n, v in items:
|
|
try:
|
|
ty, fields, cmin, cmax = schema[n]
|
|
except KeyError: # extension field
|
|
ty, fields, cmin, cmax = ('text', None, 0, None)
|
|
|
|
if v.has_key('type'):
|
|
v['type'] = v['type'].split(',')
|
|
if fields:
|
|
v.update(dict(lex_fields(v['_'], fields)))
|
|
elif ty == 'text':
|
|
v['text'] = icslex.unesc(v['_'])
|
|
elif ty == 'mime':
|
|
fix_bin(v)
|
|
else:
|
|
raise RuntimeError, 'type not implemented: %s' % ty
|
|
|
|
if cmax is None:
|
|
vlist = obj.get(n, None)
|
|
if vlist is None:
|
|
vlist = obj[n] = []
|
|
vlist.append(v)
|
|
else:
|
|
obj[n] = v
|
|
|
|
yield obj
|
|
|
|
|
|
def lex_fields(txt, names, ty='text'):
|
|
"""parse the N parts and elaborate using hCard names
|
|
|
|
>>> lex_fields('Doe;John;;;;', Properties['n'][1])
|
|
[('family-name', 'Doe'), ('given-name', 'John')]
|
|
"""
|
|
parts = txt.split(';') # @@ quoting details?
|
|
if ty == 'text':
|
|
parts = [icslex.unesc(v) for v in parts]
|
|
return [(k, v) for k, v in zip(names, parts) if v]
|
|
|
|
|
|
def fix_bin(v):
|
|
if v.get('value', None) == 'uri':
|
|
v['uri'] = v['_'] #@@ unescaping?
|
|
else:
|
|
enc = v.get('encoding', None)
|
|
if enc == 'b':
|
|
b64 = v['_']
|
|
v['uri'] = 'data:image/%s,%s' % (v['type'], b64)
|
|
v['binary'] = base64.decodestring(b64)
|
|
elif enc is None:
|
|
plain = v['_']
|
|
v['binary'] = plain
|
|
v['uri'] = 'data:image/%s,%s' % (v['type'], plain)
|
|
else:
|
|
raise ValueError, "unknown encoding: %s" % enc
|
|
|
|
def _test():
|
|
import doctest
|
|
doctest.testmod()
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
|
|
if '--test' in sys.argv:
|
|
_test()
|
|
else:
|
|
try:
|
|
main(sys.argv)
|
|
except Usage, e:
|
|
sys.stderr.write(str(e))
|
|
sys.exit(2)
|
|
|
|
|