4464: VCard import/export needs update to new surname and spec incompatibilities
svn: r16745
This commit is contained in:
parent
28387648b7
commit
1794a5e500
91
data/gramps_canonicalize.xsl
Normal file
91
data/gramps_canonicalize.xsl
Normal file
@ -0,0 +1,91 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Gramps - a GTK+/GNOME based genealogy program
|
||||
|
||||
Copyright (C) 2011 Michiel D. Nauta
|
||||
|
||||
This program 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 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program 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 this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
$Id$
|
||||
-->
|
||||
<xsl:stylesheet version="1.0"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:g="http://gramps-project.org/xml/1.4.0/">
|
||||
|
||||
<!--
|
||||
Transform a Gramps XML file into "canonical form", that is strip the
|
||||
timestamps in the change attributes, and order all elements for which
|
||||
the id attribute is used. The idea is that "canonical Gramps XML" files
|
||||
can be compared with eachother with the help of ordinary diff tools.
|
||||
-->
|
||||
|
||||
<xsl:output method="xml"/>
|
||||
|
||||
<xsl:param name="replace_handles"/>
|
||||
<xsl:key name="primary_obj" match="g:person|g:family|g:event|g:placeobj|g:source|g:repository|g:object|g:note|g:tag" use="@handle"/>
|
||||
|
||||
<xsl:template match="*|@*|text()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="*|@*|text()"/>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="@change">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="g:researcher">
|
||||
<xsl:copy/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="g:people|g:families|g:events|g:places|g:sources|g:repositories|g:objects|g:notes|g:tags">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="*">
|
||||
<xsl:sort select="@id"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="@handle">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$replace_handles='ID'">
|
||||
<xsl:attribute name="handle">
|
||||
<xsl:value-of select="../@id"/>
|
||||
</xsl:attribute>
|
||||
</xsl:when>
|
||||
<xsl:when test="$replace_handles='strip'">
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:copy/>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="@hlink">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$replace_handles='ID'">
|
||||
<xsl:attribute name="hlink">
|
||||
<xsl:value-of select="key('primary_obj',.)/@id"/>
|
||||
</xsl:attribute>
|
||||
</xsl:when>
|
||||
<xsl:when test="$replace_handles='strip'">
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:copy/>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
@ -5,6 +5,7 @@
|
||||
# Copyright (C) 2005-2008 Donald N. Allingham
|
||||
# Copyright (C) 2008 Brian G. Matherly
|
||||
# Copyright (C) 2010 Jakim Friant
|
||||
# Copyright (C) 2011 Michiel D. Nauta
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -22,7 +23,7 @@
|
||||
#
|
||||
# $Id$
|
||||
|
||||
"Export Persons to vCard."
|
||||
"Export Persons to vCard (RFC 2426)."
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -30,8 +31,7 @@
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import sys
|
||||
import os
|
||||
from gen.ggettext import gettext as _
|
||||
from textwrap import TextWrapper
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
@ -46,17 +46,88 @@ log = logging.getLogger(".ExportVCard")
|
||||
# GRAMPS modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.ggettext import gettext as _
|
||||
from ExportOptions import WriterOptionBox
|
||||
from Filters import GenericFilter, Rules, build_filter_model
|
||||
import const
|
||||
from gen.lib import Date
|
||||
import Errors
|
||||
from glade import Glade
|
||||
from gen.lib.urltype import UrlType
|
||||
from gen.lib.eventtype import EventType
|
||||
from gen.display.name import displayer as _nd
|
||||
|
||||
class CardWriter(object):
|
||||
def __init__(self, database, filename, msg_callback, option_box=None, callback=None):
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# ExportOpenFileContextManager class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class ExportOpenFileContextManager:
|
||||
"""Context manager to open a file or stdout for writing."""
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.filehandle = None
|
||||
|
||||
def __enter__(self):
|
||||
if self.filename == '-':
|
||||
self.filehandle = sys.stdout
|
||||
else:
|
||||
self.filehandle = open(self.filename, 'w')
|
||||
return self.filehandle
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.filehandle and self.filename != '-':
|
||||
self.filehandle.close()
|
||||
return False
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Support Functions
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
def exportData(database, filename, msg_callback, option_box=None, callback=None):
|
||||
"""Function called by Gramps to export data on persons in VCard format."""
|
||||
cardw = VCardWriter(database, filename, option_box, callback)
|
||||
try:
|
||||
cardw.export_data()
|
||||
except EnvironmentError, msg:
|
||||
msg_callback(_("Could not create %s") % filename, str(msg))
|
||||
return False
|
||||
except:
|
||||
# Export shouldn't bring Gramps down.
|
||||
msg_callback(_("Could not create %s") % filename)
|
||||
return False
|
||||
return True
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# VCardWriter class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class VCardWriter(object):
|
||||
"""Class to create a file with data in VCard format."""
|
||||
LINELENGTH = 73 # unclear if the 75 chars of spec includes \r\n.
|
||||
ESCAPE_CHAR = '\\'
|
||||
TOBE_ESCAPED = ['\\', ',', ';'] # order is important
|
||||
LINE_CONTINUATION = [' ', '\t']
|
||||
|
||||
@staticmethod
|
||||
def esc(data):
|
||||
"""Escape the special chars of the VCard protocol."""
|
||||
if type(data) == type('string') or type(data) == type(u'string'):
|
||||
for char in VCardWriter.TOBE_ESCAPED:
|
||||
data = data.replace(char, VCardWriter.ESCAPE_CHAR + char)
|
||||
return data
|
||||
elif type(data) == type([]):
|
||||
return list(map(VCardWriter.esc, data))
|
||||
elif type(data) == type(()):
|
||||
return tuple(map(VCardWriter.esc, data))
|
||||
else:
|
||||
raise TypeError(_("VCard escaping is not implemented for "
|
||||
"data type %s.") % str(type(data)))
|
||||
|
||||
def __init__(self, database, filename, option_box=None, callback=None):
|
||||
self.db = database
|
||||
self.filename = filename
|
||||
self.msg_callback = msg_callback
|
||||
self.filehandle = None
|
||||
self.option_box = option_box
|
||||
self.callback = callback
|
||||
if callable(self.callback): # callback is really callable
|
||||
@ -68,10 +139,20 @@ class CardWriter(object):
|
||||
self.option_box.parse_options()
|
||||
self.db = option_box.get_filtered_database(self.db)
|
||||
|
||||
self.txtwrp = TextWrapper(width=self.LINELENGTH,
|
||||
expand_tabs=False,
|
||||
replace_whitespace=False,
|
||||
drop_whitespace=False,
|
||||
subsequent_indent=self.LINE_CONTINUATION[0])
|
||||
self.count = 0
|
||||
self.total = 0
|
||||
|
||||
def update_empty(self):
|
||||
"""Progress can't be reported."""
|
||||
pass
|
||||
|
||||
def update_real(self):
|
||||
"""Report progress."""
|
||||
self.count += 1
|
||||
newval = int(100*self.count/self.total)
|
||||
if newval != self.oldval:
|
||||
@ -79,96 +160,180 @@ class CardWriter(object):
|
||||
self.oldval = newval
|
||||
|
||||
def writeln(self, text):
|
||||
#self.g.write('%s\n' % (text.encode('iso-8859-1')))
|
||||
self.g.write('%s\n' % (text.encode(sys.getfilesystemencoding())))
|
||||
"""
|
||||
Write a property of the VCard to file.
|
||||
|
||||
def export_data(self, filename):
|
||||
Can't cope with nested VCards, section 2.4.2 of RFC 2426.
|
||||
"""
|
||||
sysencoding = sys.getfilesystemencoding()
|
||||
self.filehandle.write('%s\r\n' % '\r\n'.join(
|
||||
[line.encode(sysencoding) for line in self.txtwrp.wrap(text)]))
|
||||
|
||||
self.dirname = os.path.dirname (filename)
|
||||
try:
|
||||
self.g = open(filename,"w")
|
||||
except IOError,msg:
|
||||
msg2 = _("Could not create %s") % filename
|
||||
self.msg_callback(msg2, str(msg))
|
||||
return False
|
||||
except:
|
||||
self.msg_callback(_("Could not create %s") % filename)
|
||||
return False
|
||||
|
||||
self.count = 0
|
||||
self.oldval = 0
|
||||
self.total = len([x for x in self.db.iter_person_handles()])
|
||||
for key in self.db.iter_person_handles():
|
||||
self.write_person(key)
|
||||
self.update()
|
||||
|
||||
self.g.close()
|
||||
def export_data(self):
|
||||
"""Open the file and loop over everyone two write their VCards."""
|
||||
with ExportOpenFileContextManager(self.filename) as self.filehandle:
|
||||
if self.filehandle:
|
||||
self.count = 0
|
||||
self.oldval = 0
|
||||
self.total = self.db.get_number_of_people()
|
||||
for key in self.db.iter_person_handles():
|
||||
self.write_person(key)
|
||||
self.update()
|
||||
return True
|
||||
|
||||
def write_person(self, person_handle):
|
||||
"""Create a VCard for the specified person."""
|
||||
person = self.db.get_person_from_handle(person_handle)
|
||||
if person:
|
||||
self.writeln("BEGIN:VCARD");
|
||||
self.write_header()
|
||||
prname = person.get_primary_name()
|
||||
|
||||
self.writeln("FN:%s" % prname.get_regular_name())
|
||||
self.writeln("N:%s;%s;%s;%s;%s" %
|
||||
(prname.get_surname(),
|
||||
prname.get_first_name(),
|
||||
person.get_nick_name(),
|
||||
prname.get_surname_prefix(),
|
||||
prname.get_suffix()
|
||||
)
|
||||
)
|
||||
if prname.get_title():
|
||||
self.writeln("TITLE:%s" % prname.get_title())
|
||||
|
||||
birth_ref = person.get_birth_ref()
|
||||
if birth_ref:
|
||||
birth = self.db.get_event_from_handle(birth_ref.ref)
|
||||
if birth:
|
||||
b_date = birth.get_date_object()
|
||||
mod = b_date.get_modifier()
|
||||
if (mod != Date.MOD_TEXTONLY and
|
||||
not b_date.is_empty() and
|
||||
not mod == Date.MOD_SPAN and
|
||||
not mod == Date.MOD_RANGE):
|
||||
(day, month, year, sl) = b_date.get_start_date()
|
||||
if day > 0 and month > 0 and year > 0:
|
||||
self.writeln("BDAY:%s-%02d-%02d" % (year, month,
|
||||
day))
|
||||
self.write_formatted_name(prname)
|
||||
self.write_name(prname)
|
||||
self.write_sortstring(prname)
|
||||
self.write_nicknames(person, prname)
|
||||
self.write_birthdate(person)
|
||||
self.write_addresses(person)
|
||||
self.write_urls(person)
|
||||
self.write_occupation(person)
|
||||
self.write_footer()
|
||||
|
||||
address_list = person.get_address_list()
|
||||
for address in address_list:
|
||||
postbox = ""
|
||||
ext = ""
|
||||
street = address.get_street()
|
||||
city = address.get_city()
|
||||
state = address.get_state()
|
||||
zip = address.get_postal_code()
|
||||
country = address.get_country()
|
||||
if street or city or state or zip or country:
|
||||
self.writeln("ADR:%s;%s;%s;%s;%s;%s;%s" %
|
||||
(postbox, ext, street, city,state, zip, country))
|
||||
|
||||
phone = address.get_phone()
|
||||
if phone:
|
||||
self.writeln("TEL:%s" % phone)
|
||||
|
||||
url_list = person.get_url_list()
|
||||
for url in url_list:
|
||||
href = url.get_path()
|
||||
if href:
|
||||
self.writeln("URL:%s" % href)
|
||||
def write_header(self):
|
||||
"""Write the opening lines of a VCard."""
|
||||
self.writeln("BEGIN:VCARD")
|
||||
self.writeln("VERSION:3.0")
|
||||
self.writeln("PRODID:-//Gramps//NONSGML %s %s//EN" %
|
||||
(const.PROGRAM_NAME, const.VERSION))
|
||||
|
||||
self.writeln("END:VCARD");
|
||||
self.writeln("");
|
||||
def write_footer(self):
|
||||
"""Write the closing lines of a VCard."""
|
||||
self.writeln("END:VCARD")
|
||||
self.writeln("")
|
||||
|
||||
def write_formatted_name(self, prname):
|
||||
"""Write the compulsory FN property of VCard."""
|
||||
regular_name = prname.get_regular_name().strip()
|
||||
title = prname.get_title()
|
||||
if title:
|
||||
regular_name = "%s %s" % (title, regular_name)
|
||||
self.writeln("FN:%s" % self.esc(regular_name))
|
||||
|
||||
def write_name(self, prname):
|
||||
"""Write the compulsory N property of a VCard."""
|
||||
family_name = ''
|
||||
given_name = ''
|
||||
additional_names = ''
|
||||
hon_prefix = ''
|
||||
suffix = ''
|
||||
|
||||
primary_surname = prname.get_primary_surname()
|
||||
surname_list = prname.get_surname_list()
|
||||
if not surname_list[0].get_primary():
|
||||
surname_list.remove(primary_surname)
|
||||
surname_list.insert(0, primary_surname)
|
||||
family_name = ','.join(self.esc([("%s %s %s" % (surname.get_prefix(),
|
||||
surname.get_surname(), surname.get_connector())).strip()
|
||||
for surname in surname_list]))
|
||||
|
||||
call_name = prname.get_call_name()
|
||||
if call_name:
|
||||
given_name = self.esc(call_name)
|
||||
additional_name_list = prname.get_first_name().split()
|
||||
if call_name in additional_name_list:
|
||||
additional_name_list.remove(call_name)
|
||||
additional_names = ','.join(self.esc(additional_name_list))
|
||||
else:
|
||||
name_list = prname.get_first_name().split()
|
||||
if len(name_list) > 0:
|
||||
given_name = self.esc(name_list[0])
|
||||
if len(name_list) > 1:
|
||||
additional_names = ','.join(self.esc(name_list[1:]))
|
||||
# Alternate names are ignored because names just don't add up:
|
||||
# if one name is Jean and an alternate is Paul then you can't
|
||||
# conclude the Jean Paul is also an alternate name of that person.
|
||||
|
||||
# Assume all titles/suffixes that apply are present in primary name.
|
||||
hon_prefix = ','.join(self.esc(prname.get_title().split()))
|
||||
suffix = ','.join(self.esc(prname.get_suffix().split()))
|
||||
|
||||
self.writeln("N:%s;%s;%s;%s;%s" % (family_name, given_name,
|
||||
additional_names, hon_prefix, suffix))
|
||||
|
||||
def write_sortstring(self, prname):
|
||||
"""Write the SORT-STRING property of a VCard."""
|
||||
# TODO only add sort-string if needed
|
||||
self.writeln("SORT-STRING:%s" % self.esc(_nd.sort_string(prname)))
|
||||
|
||||
def write_nicknames(self, person, prname):
|
||||
"""Write the NICKNAME property of a VCard."""
|
||||
nicknames = [x.get_nick_name() for x in person.get_alternate_names()
|
||||
if x.get_nick_name()]
|
||||
if prname.get_nick_name():
|
||||
nicknames.insert(0, prname.get_nick_name())
|
||||
if len(nicknames) > 0:
|
||||
self.writeln("NICKNAME:%s" % (','.join(self.esc(nicknames))))
|
||||
|
||||
def write_birthdate(self, person):
|
||||
"""Write the BDAY property of a VCard."""
|
||||
birth_ref = person.get_birth_ref()
|
||||
if birth_ref:
|
||||
birth = self.db.get_event_from_handle(birth_ref.ref)
|
||||
if birth:
|
||||
b_date = birth.get_date_object()
|
||||
mod = b_date.get_modifier()
|
||||
if (mod != Date.MOD_TEXTONLY and
|
||||
not b_date.is_empty() and
|
||||
not mod == Date.MOD_SPAN and
|
||||
not mod == Date.MOD_RANGE):
|
||||
(day, month, year, slash) = b_date.get_start_date()
|
||||
if day > 0 and month > 0 and year > 0:
|
||||
self.writeln("BDAY:%s-%02d-%02d" % (year, month, day))
|
||||
|
||||
def write_addresses(self, person):
|
||||
"""Write ADR and TEL properties of a VCard."""
|
||||
address_list = person.get_address_list()
|
||||
for address in address_list:
|
||||
postbox = ""
|
||||
ext = ""
|
||||
street = address.get_street()
|
||||
city = address.get_city()
|
||||
state = address.get_state()
|
||||
zipcode = address.get_postal_code()
|
||||
country = address.get_country()
|
||||
if street or city or state or zipcode or country:
|
||||
self.writeln("ADR:%s;%s;%s;%s;%s;%s;%s" % self.esc(
|
||||
(postbox, ext, street, city, state, zipcode, country)))
|
||||
|
||||
phone = address.get_phone()
|
||||
if phone:
|
||||
self.writeln("TEL:%s" % phone)
|
||||
|
||||
def write_urls(self, person):
|
||||
"""Write URL and EMAIL properties of a VCard."""
|
||||
url_list = person.get_url_list()
|
||||
for url in url_list:
|
||||
href = url.get_path()
|
||||
if href:
|
||||
if url.get_type() == UrlType(UrlType.EMAIL):
|
||||
if href.startswith('mailto:'):
|
||||
href = href[len('mailto:'):]
|
||||
self.writeln("EMAIL:%s" % self.esc(href))
|
||||
else:
|
||||
self.writeln("URL:%s" % self.esc(href))
|
||||
|
||||
def write_occupation(self, person):
|
||||
"""
|
||||
Write ROLE property of a VCard.
|
||||
|
||||
Use the most recent occupation event.
|
||||
"""
|
||||
event_refs = person.get_primary_event_ref_list()
|
||||
events = [event for event in
|
||||
[self.db.get_event_from_handle(ref.ref) for ref in event_refs]
|
||||
if event.get_type() == EventType(EventType.OCCUPATION)]
|
||||
if len(events) > 0:
|
||||
events.sort(cmp=lambda x, y: cmp(x.get_date_object(),
|
||||
y.get_date_object()))
|
||||
occupation = events[-1].get_description()
|
||||
if occupation:
|
||||
self.writeln("ROLE:%s" % occupation)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
def exportData(database, filename, msg_callback, option_box=None, callback=None):
|
||||
cw = CardWriter(database, filename, msg_callback, option_box, callback)
|
||||
return cw.export_data(filename)
|
||||
|
199
src/plugins/export/test/exportVCard_test.py
Normal file
199
src/plugins/export/test/exportVCard_test.py
Normal file
@ -0,0 +1,199 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2011 Michiel D. Nauta
|
||||
#
|
||||
# This program 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 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# $Id$
|
||||
|
||||
"""
|
||||
Unittest for export to VCard
|
||||
|
||||
To be called from src directory.
|
||||
"""
|
||||
|
||||
# Uses vcf for input, would be better to use Gramps-XML, but import of
|
||||
# Gramps-XML via stdin is hard.
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.curdir)
|
||||
sys.path.append(os.path.join(os.curdir, 'plugins', 'export'))
|
||||
import subprocess
|
||||
|
||||
from const import VERSION
|
||||
import Errors
|
||||
import ExportVCard
|
||||
|
||||
class VCardCheck(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.expect = ["BEGIN:VCARD", "VERSION:3.0", "PRODID:-//Gramps//NONSGML Gramps %s//EN" % VERSION, "FN:Lastname", "N:Lastname;;;;", "SORT-STRING:" + "Lastname".ljust(55), "END:VCARD"]
|
||||
self.input_list = ["BEGIN:VCARD", "VERSION:3.0", "FN:Lastname", "N:Lastname;;;;", "END:VCARD"]
|
||||
|
||||
def do_test(self, input_str, expect_str, debug=False):
|
||||
process = subprocess.Popen('python gramps.py -i - -f vcf -e - -f vcf',
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)
|
||||
result_str, err_str = process.communicate(input_str)
|
||||
if err_str:
|
||||
print err_str
|
||||
if debug:
|
||||
print result_str
|
||||
print expect_str
|
||||
self.assertEqual(result_str, expect_str)
|
||||
|
||||
def test_base(self):
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_esc_string_none(self):
|
||||
self.assertEqual(ExportVCard.VCardWriter.esc("nothing"), "nothing")
|
||||
|
||||
def test_esc_string_all(self):
|
||||
self.assertEqual(ExportVCard.VCardWriter.esc("backslash\\_comma,_semicolon;"),
|
||||
"backslash\\\\_comma\\,_semicolon\\;")
|
||||
|
||||
def test_esc_string_list(self):
|
||||
self.assertEqual(ExportVCard.VCardWriter.esc(["comma,", "semicolon;"]),["comma\\,", "semicolon\\;"])
|
||||
|
||||
def test_esc_string_tuple(self):
|
||||
self.assertEqual(ExportVCard.VCardWriter.esc(("comma,", "semicolon;")),("comma\\,", "semicolon\\;"))
|
||||
|
||||
def test_esc_string_wrongtype(self):
|
||||
self.assertRaises(TypeError, ExportVCard.VCardWriter.esc,
|
||||
{"comma,":"semicolon;"})
|
||||
|
||||
def test_write_formatted_name_title(self):
|
||||
self.input_list[3] = "N:Lastname;;;Sir.;"
|
||||
self.expect[3] = "FN:Sir. Lastname"
|
||||
self.expect[4] = "N:Lastname;;;Sir.;"
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_write_name_multiple_surname(self):
|
||||
self.input_list[3] = "N:van Oranje,Nassau;;;;"
|
||||
self.expect[3] = "FN:van Oranje Nassau"
|
||||
self.expect[4] = "N:van Oranje,Nassau;;;;"
|
||||
self.expect[5] = "SORT-STRING:" + "Oranje".ljust(55)
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_write_name_callname(self):
|
||||
self.input_list[2] = "FN:A B C Lastname"
|
||||
self.input_list[3] = "N:Lastname;B;A,C;;"
|
||||
self.expect[3] = "FN:A B C Lastname"
|
||||
self.expect[4] = "N:Lastname;B;A,C;;"
|
||||
self.expect[5] = "SORT-STRING:" + "Lastname".ljust(25) + "A B C".ljust(30)
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
#def test_write_name_callname_in_addnames(self):
|
||||
# impossible to test with VCF input, need XML
|
||||
|
||||
def test_write_name_no_callname(self):
|
||||
self.input_list[2] = "FN:A B C Lastname"
|
||||
self.input_list[3] = "N:Lastname;A;B,C;;"
|
||||
self.expect[3] = "FN:A B C Lastname"
|
||||
self.expect[4] = "N:Lastname;A;B,C;;"
|
||||
self.expect[5] = "SORT-STRING:" + "Lastname".ljust(25) + "A B C".ljust(30)
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_write_name_no_additional_names(self):
|
||||
self.input_list[2] = "FN:A Lastname"
|
||||
self.input_list[3] = "N:Lastname;A;;;"
|
||||
self.expect[3] = "FN:A Lastname"
|
||||
self.expect[4] = "N:Lastname;A;;;"
|
||||
self.expect[5] = "SORT-STRING:" + "Lastname".ljust(25) + "A".ljust(30)
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_write_name_honprefix(self):
|
||||
self.input_list[3] = "N:Lastname;;;Sir;"
|
||||
self.expect[3] = "FN:Sir Lastname"
|
||||
self.expect[4] = "N:Lastname;;;Sir;"
|
||||
self.expect[5] = "SORT-STRING:" + "Lastname".ljust(55)
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_write_name_honsuffix(self):
|
||||
self.input_list[3] = "N:Lastname;;;;Jr."
|
||||
self.expect[3] = "FN:Lastname\, Jr."
|
||||
self.expect[4] = "N:Lastname;;;;Jr."
|
||||
self.expect[5] = "SORT-STRING:" + "Lastname".ljust(55)+ "Jr."
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
|
||||
def test_nicknames_regular(self):
|
||||
self.input_list.insert(4, "NICKNAME:Nick,N.")
|
||||
self.expect.insert(6, "NICKNAME:Nick,N.")
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
#def test_nicknames_primary_nick(self)
|
||||
# impossible to test with VCF input, need XML
|
||||
|
||||
def test_write_birthdate_regular(self):
|
||||
self.input_list.insert(4, "BDAY:2001-02-28")
|
||||
self.expect.insert(6, "BDAY:2001-02-28")
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
#def test_write_birthdate_empty(self):
|
||||
#def test_write_birhtdate_textonly(self):
|
||||
#def test_write_birthdate_span(self):
|
||||
#def test_write_birthdate_range(self):
|
||||
# impossible to test with VCF input, need XML
|
||||
|
||||
def test_write_addresses_regular(self):
|
||||
self.input_list.insert(4, "ADR:pobox;bis;street;place;province;zip;country")
|
||||
self.expect.insert(6, "ADR:;;pobox bis street;place;province;zip;country")
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_write_addresses_phone(self):
|
||||
self.input_list.insert(4, "TEL:01234-56789")
|
||||
self.expect.insert(6, "TEL:01234-56789")
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_write_urls_email(self):
|
||||
self.input_list.insert(4, "EMAIL:me@example.com")
|
||||
self.expect.insert(6, "EMAIL:me@example.com")
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
#def test_write_urls_emial_mailto(self):
|
||||
# impossible to test with VCF input, need XML
|
||||
|
||||
def test_write_urls_url(self):
|
||||
self.input_list.insert(4, "URL:http://www.example.org")
|
||||
self.expect.insert(6, "URL:http://www.example.org")
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
def test_write_occupation_regular(self):
|
||||
self.input_list.insert(4, "ROLE:carpenter")
|
||||
self.expect.insert(6, "ROLE:carpenter")
|
||||
self.do_test("\r\n".join(self.input_list),
|
||||
"\r\n".join(self.expect) + '\r\n\r\n')
|
||||
|
||||
#def test_write_occupation_lastdate(self):
|
||||
# impossible to test with VCF input, need XML
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -3,6 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2000-2006 Martin Hawlisch, Donald N. Allingham
|
||||
# Copyright (C) 2008 Brian G. Matherly
|
||||
# Copyright (C) 2011 Michiel D. Nauta
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -21,13 +22,14 @@
|
||||
|
||||
# $Id$
|
||||
|
||||
"Import from vCard"
|
||||
"Import from vCard (RFC 2426)"
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# standard python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import sys
|
||||
import re
|
||||
import time
|
||||
from gen.ggettext import gettext as _
|
||||
@ -53,52 +55,198 @@ from QuestionDialog import ErrorDialog
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
# ImportOpenFileContextManager class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
def importData(database, filename, cb=None):
|
||||
class ImportOpenFileContextManager:
|
||||
"""Context manager to open a file or stdin for reading."""
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.filehandle = None
|
||||
|
||||
try:
|
||||
g = VCardParser(database,filename)
|
||||
except IOError,msg:
|
||||
ErrorDialog(_("%s could not be opened\n") % filename,str(msg))
|
||||
return
|
||||
def __enter__(self):
|
||||
if self.filename == '-':
|
||||
# TODO how to add U to mode?
|
||||
self.filehandle = sys.stdin
|
||||
else:
|
||||
self.filehandle = open(self.filename, "rU")
|
||||
return self.filehandle
|
||||
|
||||
try:
|
||||
status = g.parse_vCard_file()
|
||||
except IOError,msg:
|
||||
errmsg = _("%s could not be opened\n") % filename
|
||||
ErrorDialog(errmsg,str(msg))
|
||||
return
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.filename != '-':
|
||||
self.filehandle.close()
|
||||
return False
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Support Functions
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
def importData(database, filename, cb_progress=None):
|
||||
"""Function called by Gramps to import data on persons in VCard format."""
|
||||
parser = VCardParser(database, filename)
|
||||
try:
|
||||
return parser.parse_vCard_file()
|
||||
except EnvironmentError, msg:
|
||||
ErrorDialog(_("%s could not be opened\n") % filename, str(msg))
|
||||
return
|
||||
except Errors.GedcomError, msg:
|
||||
ErrorDialog(_("%s could not be opened\n") % filename, str(msg))
|
||||
return
|
||||
|
||||
|
||||
def splitof_nameprefix(name):
|
||||
"""
|
||||
Return a (prefix, Surname) tuple by splitting on first uppercase char.
|
||||
|
||||
Shame on Python for not supporting [[:upper:]] in re!
|
||||
"""
|
||||
look_for_capital = False
|
||||
for i, char in enumerate(name):
|
||||
if look_for_capital:
|
||||
if char.isupper():
|
||||
return (name[:i].rstrip(), name[i:])
|
||||
else:
|
||||
look_for_capital = False
|
||||
if not char.isalpha():
|
||||
look_for_capital = True
|
||||
return ('', name)
|
||||
|
||||
|
||||
def fitin(prototype, receiver, element):
|
||||
"""
|
||||
Return the index in string receiver at which element should be inserted
|
||||
to match part of prototype.
|
||||
|
||||
Assume that the part of receiver that is not tested does match.
|
||||
Don't split to work with lists because element may contain a space!
|
||||
Example: fitin("Mr. Gaius Julius Caesar", "Gaius Caesar", "Julius") = 6
|
||||
|
||||
:param prototype: Partly to be matched by inserting element in receiver.
|
||||
:type prototype: str
|
||||
:param receiver: Space separated words that miss words to match prototype.
|
||||
:type receiver: str
|
||||
:param element: Words that need to be inserted; error if not in prototype.
|
||||
:type element: str
|
||||
:returns: Returns index where element fits in receiver, -1 if receiver
|
||||
not in prototype, or throws IndexError if element at end receiver.
|
||||
:rtype: int
|
||||
"""
|
||||
receiver_idx = 0
|
||||
receiver_chunks = receiver.split()
|
||||
element_idx = prototype.index(element)
|
||||
i = 0
|
||||
idx = prototype.find(receiver_chunks[i])
|
||||
while idx < element_idx:
|
||||
if idx == -1:
|
||||
return -1
|
||||
receiver_idx += len(receiver_chunks[i]) + 1
|
||||
i += 1
|
||||
idx = prototype.find(receiver_chunks[i])
|
||||
return receiver_idx
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# VCardParser class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class VCardParser(object):
|
||||
def __init__(self, dbase, file):
|
||||
self.db = dbase
|
||||
self.f = open(file,"rU")
|
||||
self.filename = file
|
||||
"""Class to read data in VCard format from a file."""
|
||||
DATE_RE = re.compile(r'^(\d{4}-\d{1,2}-\d{1,2})|(?:(\d{4})-?(\d\d)-?(\d\d))')
|
||||
GROUP_RE = re.compile(r'^(?:[-0-9A-Za-z]+\.)?(.+)$') # see RFC 2425 sec5.8.2
|
||||
ESCAPE_CHAR = '\\'
|
||||
TOBE_ESCAPED = ['\\', ',', ';'] # order is important
|
||||
LINE_CONTINUATION = [' ', '\t']
|
||||
|
||||
@staticmethod
|
||||
def name_value_split(data):
|
||||
"""Property group.name:value split is on first unquoted colon."""
|
||||
colon_idx = data.find(':')
|
||||
if colon_idx < 1:
|
||||
return ()
|
||||
quote_count = data.count('"', 0, colon_idx)
|
||||
while quote_count % 2 == 1:
|
||||
colon_idx = data.find(':', colon_idx + 1)
|
||||
quote_count = data.count('"', 0, colon_idx)
|
||||
group_name, value = data[:colon_idx], data[colon_idx+1:]
|
||||
name_parts = VCardParser.GROUP_RE.match(group_name)
|
||||
return (name_parts.group(1), value)
|
||||
|
||||
@staticmethod
|
||||
def unesc(data):
|
||||
"""Remove VCard escape sequences."""
|
||||
if type(data) == type('string'):
|
||||
for char in reversed(VCardParser.TOBE_ESCAPED):
|
||||
data = data.replace(VCardParser.ESCAPE_CHAR + char, char)
|
||||
return data
|
||||
elif type(data) == type([]):
|
||||
return list(map(VCardParser.unesc, data))
|
||||
else:
|
||||
raise TypeError(_("VCard unescaping is not implemented for "
|
||||
"data type %s.") % str(type(data)))
|
||||
|
||||
@staticmethod
|
||||
def count_escapes(strng):
|
||||
"""Count the number of escape characters at the end of a string"""
|
||||
count = 0
|
||||
for char in reversed(strng):
|
||||
if char != VCardParser.ESCAPE_CHAR:
|
||||
return count
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
def split_unescaped(strng, sep):
|
||||
"""Split on sep if sep is unescaped."""
|
||||
strng_parts = strng.split(sep)
|
||||
for i in reversed(xrange(len(strng_parts[:]))):
|
||||
if VCardParser.count_escapes(strng_parts[i]) % 2 == 1:
|
||||
# the sep was escaped so undo split
|
||||
appendix = strng_parts.pop(i+1)
|
||||
strng_parts[i] += sep + appendix
|
||||
return strng_parts
|
||||
|
||||
def __init__(self, dbase, filename):
|
||||
self.database = dbase
|
||||
self.filename = filename
|
||||
self.formatted_name = ''
|
||||
self.name_parts = ''
|
||||
self.filehandle = None
|
||||
self.next_line = None
|
||||
self.trans = None
|
||||
self.version = None
|
||||
self.person = None
|
||||
|
||||
def get_next_line(self):
|
||||
line = self.f.readline()
|
||||
"""
|
||||
Read and return the line with the next property of the VCard.
|
||||
|
||||
Also if it spans multiple lines (RFC 2425 sec.5.8.1).
|
||||
"""
|
||||
line = self.next_line
|
||||
self.next_line = self.filehandle.readline()
|
||||
while self.next_line and self.next_line[0] in self.LINE_CONTINUATION:
|
||||
line = line.rstrip("\n")
|
||||
#TODO perhaps next lines superflous because of rU open parameter?
|
||||
if len(line) > 0 and line[-1] == "\r":
|
||||
line = line[:-1]
|
||||
line += self.next_line[1:]
|
||||
self.next_line = self.filehandle.readline()
|
||||
if line:
|
||||
line = line.strip()
|
||||
else:
|
||||
line = None
|
||||
return line
|
||||
|
||||
def parse_vCard_file(self):
|
||||
with DbTxn(_("vCard import"), self.db, batch=True) as self.trans:
|
||||
self.db.disable_signals()
|
||||
t = time.time()
|
||||
self.person = None
|
||||
|
||||
line_reg = re.compile('^([^:]+)+:(.*)$')
|
||||
try:
|
||||
while 1:
|
||||
def parse_vCard_file(self):
|
||||
"""Read each line of the input file and act accordingly."""
|
||||
tym = time.time()
|
||||
self.person = None
|
||||
with DbTxn(_("vCard import"), self.database, batch=True) as self.trans:
|
||||
self.database.disable_signals()
|
||||
with ImportOpenFileContextManager(self.filename) as self.filehandle:
|
||||
self.next_line = self.filehandle.readline()
|
||||
while True:
|
||||
line = self.get_next_line()
|
||||
if line is None:
|
||||
break
|
||||
@ -107,109 +255,277 @@ class VCardParser(object):
|
||||
|
||||
if line.find(":") == -1:
|
||||
continue
|
||||
line_parts = line_reg.match( line)
|
||||
line_parts = self.name_value_split(line)
|
||||
if not line_parts:
|
||||
continue
|
||||
|
||||
fields = line_parts.group(1).split(";")
|
||||
# No check for escaped ; because only fields[0] is used.
|
||||
fields = line_parts[0].split(";")
|
||||
|
||||
#for field in line_parts.groups():
|
||||
# print " p "+field
|
||||
#for field in fields:
|
||||
# print " f "+field
|
||||
|
||||
if fields[0] == "BEGIN":
|
||||
property_name = fields[0].upper()
|
||||
if property_name == "BEGIN":
|
||||
self.next_person()
|
||||
elif fields[0] == "END":
|
||||
elif property_name == "END":
|
||||
self.finish_person()
|
||||
elif fields[0] == "FN":
|
||||
self.set_nick_name(fields, line_parts.group(2))
|
||||
elif fields[0] == "N":
|
||||
self.add_name(fields, line_parts.group(2))
|
||||
elif fields[0] == "ADR":
|
||||
self.add_address(fields, line_parts.group(2))
|
||||
elif fields[0] == "TEL":
|
||||
self.add_phone(fields, line_parts.group(2))
|
||||
elif fields[0] == "BDAY":
|
||||
self.add_birthday(fields, line_parts.group(2))
|
||||
elif fields[0] == "TITLE":
|
||||
self.add_title(fields, line_parts.group(2))
|
||||
elif fields[0] == "URL":
|
||||
self.add_url(fields, line_parts.group(2))
|
||||
elif property_name == "VERSION":
|
||||
self.check_version(fields, line_parts[1])
|
||||
elif property_name == "FN":
|
||||
self.add_formatted_name(fields, line_parts[1])
|
||||
elif property_name == "N":
|
||||
self.add_name_parts(fields, line_parts[1])
|
||||
elif property_name == "NICKNAME":
|
||||
self.add_nicknames(fields, line_parts[1])
|
||||
elif property_name == "SORT-STRING":
|
||||
self.add_sortas(fields, line_parts[1])
|
||||
elif property_name == "ADR":
|
||||
self.add_address(fields, line_parts[1])
|
||||
elif property_name == "TEL":
|
||||
self.add_phone(fields, line_parts[1])
|
||||
elif property_name == "BDAY":
|
||||
self.add_birthday(fields, line_parts[1])
|
||||
elif property_name == "ROLE":
|
||||
self.add_occupation(fields, line_parts[1])
|
||||
elif property_name == "URL":
|
||||
self.add_url(fields, line_parts[1])
|
||||
elif property_name == "EMAIL":
|
||||
self.add_email(fields, line_parts[1])
|
||||
elif property_name == "PRODID":
|
||||
# Included cause VCards made by Gramps have this prop.
|
||||
pass
|
||||
else:
|
||||
LOG.warn("Token >%s< unknown. line skipped: %s" % (fields[0],line))
|
||||
except Errors.GedcomError, err:
|
||||
self.errmsg(str(err))
|
||||
|
||||
t = time.time() - t
|
||||
msg = ngettext('Import Complete: %d second','Import Complete: %d seconds', t ) % t
|
||||
|
||||
self.db.enable_signals()
|
||||
self.db.request_rebuild()
|
||||
|
||||
LOG.warn("Token >%s< unknown. line skipped: %s" %
|
||||
(fields[0],line))
|
||||
self.database.enable_signals()
|
||||
|
||||
tym = time.time() - tym
|
||||
msg = ngettext('Import Complete: %d second',
|
||||
'Import Complete: %d seconds', tym ) % tym
|
||||
LOG.debug(msg)
|
||||
|
||||
self.database.request_rebuild()
|
||||
return None
|
||||
|
||||
def finish_person(self):
|
||||
"""All info has been collected, write to database."""
|
||||
if self.person is not None:
|
||||
self.db.add_person(self.person,self.trans)
|
||||
if self.add_name():
|
||||
self.database.add_person(self.person, self.trans)
|
||||
self.person = None
|
||||
|
||||
def next_person(self):
|
||||
"""A VCard for another person is started."""
|
||||
if self.person is not None:
|
||||
self.db.add_person(self.person,self.trans)
|
||||
self.finish_person()
|
||||
LOG.warn("BEGIN property not properly closed by END property, "
|
||||
"Gramps can't cope with nested VCards.")
|
||||
self.person = gen.lib.Person()
|
||||
self.formatted_name = ''
|
||||
self.name_parts = ''
|
||||
|
||||
def set_nick_name(self, fields, data):
|
||||
if self.person is not None:
|
||||
attr = gen.lib.Attribute()
|
||||
attr.set_type(gen.lib.AttributeType.NICKNAME)
|
||||
attr.set_value(data)
|
||||
self.person.add_attribute(attr)
|
||||
def check_version(self, fields, data):
|
||||
"""Check the version of the VCard, only version 3.0 is supported."""
|
||||
self.version = data
|
||||
if self.version != "3.0":
|
||||
raise Errors.GedcomError(_("Import of VCards version %s is not "
|
||||
"supported by Gramps.") % self.version)
|
||||
|
||||
def add_formatted_name(self, fields, data):
|
||||
"""Read the FN property of a VCard."""
|
||||
if not self.formatted_name:
|
||||
self.formatted_name = self.unesc(str(data)).strip()
|
||||
|
||||
def add_name_parts(self, fields, data):
|
||||
"""Read the N property of a VCard."""
|
||||
if not self.name_parts:
|
||||
self.name_parts = data.strip()
|
||||
|
||||
def add_name(self):
|
||||
"""
|
||||
Add the name to the person.
|
||||
|
||||
Returns True on success, False on failure.
|
||||
"""
|
||||
if not self.name_parts.strip():
|
||||
LOG.warn("VCard is malformed missing the compulsory N property, "
|
||||
"so there is no name; skip it.")
|
||||
return False
|
||||
if not self.formatted_name:
|
||||
LOG.warn("VCard is malformed missing the compulsory FN property, "
|
||||
"get name from N alone.")
|
||||
data_fields = self.split_unescaped(self.name_parts, ';')
|
||||
if len(data_fields) != 5:
|
||||
LOG.warn("VCard is malformed wrong number of name components.")
|
||||
|
||||
def add_name(self, fields, data):
|
||||
data_fields = data.split(";")
|
||||
name = gen.lib.Name()
|
||||
name.set_type(gen.lib.NameType(gen.lib.NameType.AKA))
|
||||
name.set_surname(data_fields[0])
|
||||
name.set_first_name(data_fields[1])
|
||||
if data_fields[2]:
|
||||
name.set_first_name(data_fields[1]+" "+data_fields[2])
|
||||
name.set_surname_prefix(data_fields[3])
|
||||
name.set_suffix(data_fields[4])
|
||||
name.set_type(gen.lib.NameType(gen.lib.NameType.BIRTH))
|
||||
|
||||
if data_fields[0].strip():
|
||||
# assume first surname is primary
|
||||
for surname_str in self.split_unescaped(data_fields[0], ','):
|
||||
surname = gen.lib.Surname()
|
||||
prefix, sname = splitof_nameprefix(self.unesc(surname_str))
|
||||
surname.set_surname(sname.strip())
|
||||
surname.set_prefix(prefix.strip())
|
||||
name.add_surname(surname)
|
||||
|
||||
if len(data_fields) > 1 and data_fields[1].strip():
|
||||
given_name = ' '.join(self.unesc(
|
||||
self.split_unescaped(data_fields[1], ',')))
|
||||
else:
|
||||
given_name = ''
|
||||
if len(data_fields) > 2 and data_fields[2].strip():
|
||||
additional_names = ' '.join(self.unesc(
|
||||
self.split_unescaped(data_fields[2], ',')))
|
||||
else:
|
||||
additional_names = ''
|
||||
self.add_firstname(given_name.strip(), additional_names.strip(), name)
|
||||
|
||||
if len(data_fields) > 3 and data_fields[3].strip():
|
||||
name.set_title(' '.join(self.unesc(
|
||||
self.split_unescaped(data_fields[3], ','))))
|
||||
if len(data_fields) > 4 and data_fields[4].strip():
|
||||
name.set_suffix(' '.join(self.unesc(
|
||||
self.split_unescaped(data_fields[4], ','))))
|
||||
|
||||
self.person.set_primary_name(name)
|
||||
return True
|
||||
|
||||
def add_title(self, fields, data):
|
||||
name = gen.lib.Name()
|
||||
name.set_type(gen.lib.NameType(gen.lib.NameType.AKA))
|
||||
name.set_title(data)
|
||||
self.person.add_alternate_name(name)
|
||||
def add_firstname(self, given_name, additional_names, name):
|
||||
"""
|
||||
Combine given_name and additional_names and add as firstname to name.
|
||||
|
||||
If possible try to add given_name as call name.
|
||||
"""
|
||||
default = "%s %s" % (given_name, additional_names)
|
||||
if self.formatted_name:
|
||||
if given_name:
|
||||
if additional_names:
|
||||
given_name_pos = self.formatted_name.find(given_name)
|
||||
if given_name_pos != -1:
|
||||
add_names_pos = self.formatted_name.find(additional_names)
|
||||
if add_names_pos != -1:
|
||||
if given_name_pos <= add_names_pos:
|
||||
firstname = default
|
||||
# Uncertain if given name is used as callname
|
||||
else:
|
||||
firstname = "%s %s" % (additional_names,
|
||||
given_name)
|
||||
name.set_call_name(given_name)
|
||||
else:
|
||||
idx = fitin(self.formatted_name, additional_names,
|
||||
given_name)
|
||||
if idx == -1:
|
||||
# Additional names is not in formatted name
|
||||
firstname = default
|
||||
else: # Given name in middle of additional names
|
||||
firstname = "%s%s %s" % (additional_names[:idx],
|
||||
given_name, additional_names[idx:])
|
||||
name.set_call_name(given_name)
|
||||
else: # Given name is not in formatted name
|
||||
firstname = default
|
||||
else: # There are no additional_names
|
||||
firstname = given_name
|
||||
else: # There is no given_name
|
||||
firstname = additional_names
|
||||
else: # There is no formatted name
|
||||
firstname = default
|
||||
name.set_first_name(firstname.strip())
|
||||
return
|
||||
|
||||
def add_nicknames(self, fields, data):
|
||||
"""Read the NICKNAME property of a VCard."""
|
||||
for nick in self.split_unescaped(data, ','):
|
||||
nickname = nick.strip()
|
||||
if nickname:
|
||||
name = gen.lib.Name()
|
||||
name.set_nick_name(self.unesc(nickname))
|
||||
self.person.add_alternate_name(name)
|
||||
|
||||
def add_sortas(self, fields, data):
|
||||
"""Read the SORT-STRING property of a VCard."""
|
||||
#TODO
|
||||
pass
|
||||
|
||||
def add_address(self, fields, data):
|
||||
data_fields = data.split(";")
|
||||
addr = gen.lib.Address()
|
||||
addr.set_street(data_fields[0]+data_fields[1]+data_fields[2])
|
||||
addr.set_city(data_fields[3])
|
||||
addr.set_state(data_fields[4])
|
||||
addr.set_postal_code(data_fields[5])
|
||||
addr.set_country(data_fields[6])
|
||||
self.person.add_address(addr)
|
||||
"""Read the ADR property of a VCard."""
|
||||
data_fields = self.split_unescaped(data, ';')
|
||||
data_fields = [x.strip() for x in self.unesc(data_fields)]
|
||||
if ''.join(data_fields):
|
||||
addr = gen.lib.Address()
|
||||
def add_street(strng):
|
||||
if strng:
|
||||
already = addr.get_street()
|
||||
if already:
|
||||
addr.set_street("%s %s" % (already, strng))
|
||||
else:
|
||||
addr.set_street(strng)
|
||||
addr.add_street = add_street
|
||||
set_func = ['add_street', 'add_street', 'add_street', 'set_city',
|
||||
'set_state', 'set_postal_code', 'set_country']
|
||||
for i, data in enumerate(data_fields):
|
||||
if i >= len(set_func):
|
||||
break
|
||||
getattr(addr, set_func[i])(data)
|
||||
self.person.add_address(addr)
|
||||
|
||||
def add_phone(self, fields, data):
|
||||
addr = gen.lib.Address()
|
||||
addr.set_phone(data)
|
||||
self.person.add_address(addr)
|
||||
"""Read the TEL property of a VCard."""
|
||||
tel = data.strip()
|
||||
if tel:
|
||||
addr = gen.lib.Address()
|
||||
addr.set_phone(self.unesc(tel))
|
||||
self.person.add_address(addr)
|
||||
|
||||
def add_birthday(self, fields, data):
|
||||
event = gen.lib.Event()
|
||||
event.set_type(gen.lib.EventType(gen.lib.EventType.BIRTH))
|
||||
self.db.add_event(event,self.trans)
|
||||
|
||||
event_ref = gen.lib.EventRef()
|
||||
event_ref.set_reference_handle(event.get_handle())
|
||||
self.person.set_birth_ref(event_ref)
|
||||
"""Read the BDAY property of a VCard."""
|
||||
date_str = data.strip()
|
||||
date_match = VCardParser.DATE_RE.match(date_str)
|
||||
if date_match:
|
||||
if date_match.group(2):
|
||||
date_str = "%s-%s-%s" % (date_match.group(2),
|
||||
date_match.group(3), date_match.group(4))
|
||||
else:
|
||||
date_str = date_match.group(1)
|
||||
event = gen.lib.Event()
|
||||
event.set_type(gen.lib.EventType(gen.lib.EventType.BIRTH))
|
||||
date = gen.lib.Date()
|
||||
date.set_yr_mon_day(*[int(x, 10) for x in date_str.split('-')])
|
||||
event.set_date_object(date)
|
||||
self.database.add_event(event, self.trans)
|
||||
|
||||
event_ref = gen.lib.EventRef()
|
||||
event_ref.set_reference_handle(event.get_handle())
|
||||
self.person.set_birth_ref(event_ref)
|
||||
else:
|
||||
LOG.warn("Date %s not in appropriate format yyyy-mm-dd, "
|
||||
"line skipped." % date_str)
|
||||
|
||||
def add_occupation(self, fields, data):
|
||||
"""Read the ROLE property of a VCard."""
|
||||
occupation = data.strip()
|
||||
if occupation:
|
||||
event = gen.lib.Event()
|
||||
event.set_type(gen.lib.EventType(gen.lib.EventType.OCCUPATION))
|
||||
event.set_description(self.unesc(occupation))
|
||||
self.database.add_event(event, self.trans)
|
||||
|
||||
event_ref = gen.lib.EventRef()
|
||||
event_ref.set_reference_handle(event.get_handle())
|
||||
self.person.add_event_ref(event_ref)
|
||||
|
||||
def add_url(self, fields, data):
|
||||
url = gen.lib.Url()
|
||||
url.set_path(data)
|
||||
self.person.add_url(url)
|
||||
"""Read the URL property of a VCard."""
|
||||
href = data.strip()
|
||||
if href:
|
||||
url = gen.lib.Url()
|
||||
url.set_path(self.unesc(href))
|
||||
self.person.add_url(url)
|
||||
|
||||
def add_email(self, fields, data):
|
||||
"""Read the EMAIL property of a VCard."""
|
||||
email = data.strip()
|
||||
if email:
|
||||
url = gen.lib.Url()
|
||||
url.set_type(gen.lib.UrlType(gen.lib.UrlType.EMAIL))
|
||||
url.set_path(self.unesc(email))
|
||||
self.person.add_url(url)
|
||||
|
555
src/plugins/import/test/importVCard_test.py
Normal file
555
src/plugins/import/test/importVCard_test.py
Normal file
@ -0,0 +1,555 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2011 Michiel D. Nauta
|
||||
#
|
||||
# This program 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 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# $Id$
|
||||
|
||||
"""
|
||||
Unittest of import of VCard
|
||||
|
||||
To be called from src directory.
|
||||
"""
|
||||
|
||||
#TODO id number depend on user format.
|
||||
|
||||
# in case of a failing test, add True as last parameter to do_test to see the output.
|
||||
|
||||
from cStringIO import StringIO
|
||||
import time
|
||||
import unittest
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.curdir)
|
||||
sys.path.append(os.path.join(os.curdir, 'plugins','import'))
|
||||
sys.path.append(os.path.join(os.curdir, 'plugins', 'lib'))
|
||||
import subprocess
|
||||
import libxml2
|
||||
import libxslt
|
||||
|
||||
from libgrampsxml import GRAMPS_XML_VERSION
|
||||
|
||||
from const import ROOT_DIR, VERSION
|
||||
import Errors
|
||||
import ImportVCard
|
||||
from ImportVCard import VCardParser
|
||||
|
||||
class VCardCheck(unittest.TestCase):
|
||||
def setUp(self):
|
||||
date = time.localtime(time.time())
|
||||
libxml2.keepBlanksDefault(0)
|
||||
styledoc = libxml2.parseFile(os.path.join(ROOT_DIR,
|
||||
"../data/gramps_canonicalize.xsl"))
|
||||
self.style = libxslt.parseStylesheetDoc(styledoc)
|
||||
self.header = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE database PUBLIC "-//GRAMPS//DTD GRAMPS XML %s//EN"
|
||||
"http://gramps-project.org/xml/%s/grampsxml.dtd">
|
||||
<database xmlns="http://gramps-project.org/xml/%s/">
|
||||
<header>
|
||||
<created date="%04d-%02d-%02d" version="%s"/>
|
||||
<researcher/>
|
||||
</header>""" % \
|
||||
(GRAMPS_XML_VERSION, GRAMPS_XML_VERSION, GRAMPS_XML_VERSION,
|
||||
date[0], date[1], date[2], VERSION)
|
||||
expect_str = self.header + """<people><person handle="I0000" """ \
|
||||
"""id="I0000"><gender>U</gender><name type="Birth Name">""" \
|
||||
"""<surname>Lastname</surname></name></person></people></database>"""
|
||||
self.gramps = libxml2.readDoc(expect_str, '', None, libxml2.XML_PARSE_NONET)
|
||||
self.person = self.gramps.getRootElement().firstElementChild().\
|
||||
nextElementSibling().firstElementChild()
|
||||
self.name = self.person.firstElementChild().nextElementSibling()
|
||||
self.vcard = ["BEGIN:VCARD", "VERSION:3.0", "FN:Lastname",
|
||||
"N:Lastname;;;;", "END:VCARD"]
|
||||
|
||||
def tearDown(self):
|
||||
self.style.freeStylesheet()
|
||||
#self.gramps.freeDoc() # makes is crash
|
||||
|
||||
def string2canonicalxml(self, input_str, buf):
|
||||
if type(input_str) == type('string'):
|
||||
doc = libxml2.readDoc(input_str, '', None, libxml2.XML_PARSE_NONET)
|
||||
elif isinstance(input_str, libxml2.xmlDoc):
|
||||
doc = input_str
|
||||
else:
|
||||
raise TypeError
|
||||
param = {'replace_handles':"'ID'"}
|
||||
canonical_doc = self.style.applyStylesheet(doc, param)
|
||||
canonical_doc.saveFormatFileTo(buf, 'UTF-8', 1)
|
||||
doc.freeDoc()
|
||||
canonical_doc.freeDoc()
|
||||
return
|
||||
|
||||
def do_test(self, input_str, expect_str, debug=False):
|
||||
expect_canonical_strfile = StringIO()
|
||||
buf = libxml2.createOutputBuffer(expect_canonical_strfile, 'UTF-8')
|
||||
self.string2canonicalxml(expect_str, buf)
|
||||
|
||||
process = subprocess.Popen('python gramps.py -i - -f vcf -e - -f gramps',
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)
|
||||
result_str, err_str = process.communicate(input_str)
|
||||
if err_str:
|
||||
print err_str
|
||||
result_canonical_strfile = StringIO()
|
||||
buf2 = libxml2.createOutputBuffer(result_canonical_strfile, 'UTF-8')
|
||||
self.string2canonicalxml(result_str, buf2)
|
||||
|
||||
if debug:
|
||||
print result_canonical_strfile.getvalue()
|
||||
print expect_canonical_strfile.getvalue()
|
||||
self.assertEqual(result_canonical_strfile.getvalue(),
|
||||
expect_canonical_strfile.getvalue())
|
||||
expect_canonical_strfile.close()
|
||||
result_canonical_strfile.close()
|
||||
|
||||
def test_base(self):
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_splitof_nameprefix_noprefix(self):
|
||||
self.assertEqual(ImportVCard.splitof_nameprefix("Noprefix"), ('',"Noprefix"))
|
||||
|
||||
def test_splitof_nameprefix_prefix(self):
|
||||
self.assertEqual(ImportVCard.splitof_nameprefix("van Prefix"), ('van',"Prefix"))
|
||||
|
||||
def test_splitof_nameprefix_doublespace(self):
|
||||
self.assertEqual(ImportVCard.splitof_nameprefix("van Prefix"), ('van',"Prefix"))
|
||||
|
||||
def test_fitin_regular(self):
|
||||
self.assertEqual(ImportVCard.fitin("Mr. Gaius Julius Caesar",
|
||||
"Gaius Caesar", "Julius"), 6)
|
||||
|
||||
def test_fitin_wrong_receiver(self):
|
||||
self.assertEqual(ImportVCard.fitin("A B C", "A D", "B"), -1)
|
||||
|
||||
def test_fitin_wrong_element(self):
|
||||
self.assertRaises(ValueError, ImportVCard.fitin, "A B C", "A C", "D")
|
||||
|
||||
def test_fitin_last_element(self):
|
||||
self.assertRaises(IndexError, ImportVCard.fitin, "A B C", "A B", "C")
|
||||
|
||||
def test_name_value_split_begin_colon(self):
|
||||
self.vcard.insert(4, ":email@example.com")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_name_value_split_quoted_colon(self):
|
||||
self.vcard.insert(4, 'TEL;TYPE="FANCY:TYPE":01234-56789')
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'phone', '01234-56789')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_name_value_split_grouped(self):
|
||||
self.vcard[1] = "group." + self.vcard[1]
|
||||
self.vcard[3] = "group." + self.vcard[3]
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_unesc_string(self):
|
||||
self.assertEqual(VCardParser.unesc("TEL:012\\\\345\\,67\\;89"),
|
||||
"TEL:012\\345,67;89")
|
||||
|
||||
def test_unesc_list(self):
|
||||
self.assertEqual(VCardParser.unesc(["Last\,name", "First\;name"]),
|
||||
["Last,name", "First;name"])
|
||||
|
||||
def test_unesc_tuple(self):
|
||||
self.assertRaises(TypeError, VCardParser.unesc, ("Last\,name", "First\;name"))
|
||||
|
||||
def test_count_escapes_null(self):
|
||||
self.assertEqual(VCardParser.count_escapes("Lastname"), 0)
|
||||
|
||||
def test_count_escapes_one(self):
|
||||
self.assertEqual(VCardParser.count_escapes("Lastname\\"), 1)
|
||||
|
||||
def test_count_escapes_two(self):
|
||||
self.assertEqual(VCardParser.count_escapes(r"Lastname\\"), 2)
|
||||
|
||||
def test_split_unescaped_regular(self):
|
||||
self.assertEqual(VCardParser.split_unescaped("Lastname;Firstname", ';'),
|
||||
["Lastname", "Firstname"])
|
||||
|
||||
def test_split_unescaped_one(self):
|
||||
self.assertEqual(VCardParser.split_unescaped("Lastname\\;Firstname", ';'),
|
||||
["Lastname\\;Firstname",])
|
||||
|
||||
def test_split_unescaped_two(self):
|
||||
self.assertEqual(VCardParser.split_unescaped("Lastname\\\\;Firstname", ';'),
|
||||
["Lastname\\\\", "Firstname",])
|
||||
|
||||
def test_split_unescaped_three(self):
|
||||
self.assertEqual(VCardParser.split_unescaped(r"Lastname\\\;Firstname", ';'),
|
||||
[r"Lastname\\\;Firstname",])
|
||||
|
||||
def test_get_next_line_continuation(self):
|
||||
self.vcard.insert(4, "TEL:01234-\r\n 56789")
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'phone', '01234-56789')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_get_next_line_cr(self):
|
||||
self.vcard.insert(4, "TEL:01234-56789\r")
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'phone', '01234-56789')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_get_next_line_strip(self):
|
||||
self.vcard.insert(4, "TEL:01234-56789 ")
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'phone', '01234-56789')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_get_next_line_none(self):
|
||||
self.vcard.insert(4, "")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_parse_vCard_file_no_colon(self):
|
||||
self.vcard.insert(4, "TEL;01234-56789")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_parse_vCard_file_lowercase(self):
|
||||
self.vcard.insert(4, "tel:01234-56789")
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'phone', '01234-56789')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_parse_vCard_unknown_property(self):
|
||||
self.vcard.insert(4, "TELEPHONE:01234-56789")
|
||||
self.gramps = self.gramps
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_next_person_no_end(self):
|
||||
self.vcard[4] = "BEGIN:VCARD"
|
||||
self.vcard.extend(["VERSION:3.0", "FN:Another", "N:Another;;;;", "END:VCARD"])
|
||||
people = self.gramps.getRootElement().firstElementChild().nextElementSibling()
|
||||
person = people.newChild(None, 'person', None)
|
||||
person.newProp('handle', 'I0001')
|
||||
person.newProp('id', 'I0001')
|
||||
person.newChild(None, 'gender', 'U')
|
||||
name = person.newChild(None, 'name', None)
|
||||
name.newProp('type', 'Birth Name')
|
||||
name.newChild(None, 'surname', 'Another')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_check_version(self):
|
||||
self.vcard.extend(["BEGIN:VCARD", "VERSION:3.7", "FN:Another",
|
||||
"N:Another;;;;", "END:VCARD"])
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_formatted_name_twice(self):
|
||||
self.vcard[2] = "FN:Lastname B A"
|
||||
self.vcard[3] = "N:Lastname;A;B;;"
|
||||
self.vcard.insert(4, "FN:Lastname A B")
|
||||
name = self.person.firstElementChild().nextElementSibling()
|
||||
surname = name.firstElementChild()
|
||||
firstname = name.newChild(None, "first", "B A")
|
||||
callname = name.newChild(None, "call", "A")
|
||||
callname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_name_parts_twice(self):
|
||||
self.vcard.insert(4, "N:Another;;;;")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_name_regular(self):
|
||||
self.vcard[2] = "FN:Mr. Firstname Middlename Lastname Jr."
|
||||
self.vcard[3] = "N:Lastname;Firstname;Middlename;Mr.;Jr."
|
||||
name = self.person.firstElementChild().nextElementSibling()
|
||||
surname = name.firstElementChild()
|
||||
firstname = name.newChild(None, 'first', 'Firstname Middlename')
|
||||
firstname.addNextSibling(surname)
|
||||
name.newChild(None, 'suffix', 'Jr.')
|
||||
name.newChild(None, 'title', 'Mr.')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_name_multisurname(self):
|
||||
self.vcard[2] = "FN:Lastname Lastname2"
|
||||
self.vcard[3] = "N:Lastname,Lastname2;;;;"
|
||||
surname = self.name.newChild(None, 'surname', 'Lastname2')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_name_prefixsurname(self):
|
||||
self.vcard[2] = "FN:van d'Alembert"
|
||||
self.vcard[3] = "N:van d'Alembert;;;;"
|
||||
surname = self.name.firstElementChild()
|
||||
surname.setContent('Alembert')
|
||||
surname.newProp('prefix', "van d'")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_name_only_surname(self):
|
||||
self.vcard[3] = "N:Lastname"
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_name_upto_firstname(self):
|
||||
self.vcard[2] = "FN:Firstname Lastname"
|
||||
self.vcard[3] = "N:Lastname; Firstname;"
|
||||
surname = self.name.firstElementChild()
|
||||
first = self.name.newChild(None, 'first', 'Firstname')
|
||||
first.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_name_empty(self):
|
||||
self.vcard[2] = "FN:Lastname"
|
||||
self.vcard[3] = "N: "
|
||||
people = self.gramps.getRootElement().firstElementChild().nextElementSibling()
|
||||
people.unlinkNode()
|
||||
people.freeNode()
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_firstname_regular(self):
|
||||
self.vcard[2] = "FN:A B Lastname"
|
||||
self.vcard[3] = "N:Lastname;A;B;;"
|
||||
surname = self.name.firstElementChild()
|
||||
firstname = self.name.newChild(None, 'first', 'A B')
|
||||
firstname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_firstname_callname(self):
|
||||
self.vcard[2] = "FN:A B Lastname"
|
||||
self.vcard[3] = "N:Lastname;B;A;;"
|
||||
surname = self.name.firstElementChild()
|
||||
firstname = self.name.newChild(None, 'first', 'A B')
|
||||
callname = self.name.newChild(None, 'call', 'B')
|
||||
callname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_firstname_incomplete_fn(self):
|
||||
self.vcard[2] = "FN:A Lastname"
|
||||
self.vcard[3] = "N:Lastname;A;B;;"
|
||||
surname = self.name.firstElementChild()
|
||||
firstname = self.name.newChild(None, 'first', 'A B')
|
||||
firstname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_firstname_middle(self):
|
||||
self.vcard[2] = "FN:A B C Lastname"
|
||||
self.vcard[3] = "N:Lastname;B;A C;;"
|
||||
surname = self.name.firstElementChild()
|
||||
firstname = self.name.newChild(None, 'first', 'A B C')
|
||||
callname = self.name.newChild(None, 'call', 'B')
|
||||
callname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_firstname_fn_not_given(self):
|
||||
self.vcard[2] = "FN:B Lastname"
|
||||
self.vcard[3] = "N:Lastname;A;B;;"
|
||||
surname = self.name.firstElementChild()
|
||||
firstname = self.name.newChild(None, 'first', 'A B')
|
||||
firstname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_firstname_no_addnames(self):
|
||||
self.vcard[2] = "FN:A Lastname"
|
||||
self.vcard[3] = "N:Lastname;A;;;"
|
||||
surname = self.name.firstElementChild()
|
||||
firstname = self.name.newChild(None, 'first', 'A')
|
||||
firstname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_firstname_no_givenname(self):
|
||||
self.vcard[2] = "FN:A Lastname"
|
||||
self.vcard[3] = "N:Lastname;;A;;"
|
||||
surname = self.name.firstElementChild()
|
||||
firstname = self.name.newChild(None, 'first', 'A')
|
||||
firstname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_firstname_no_fn(self):
|
||||
self.vcard[2] = "FN:"
|
||||
self.vcard[3] = "N:Lastname;;A;;"
|
||||
surname = self.name.firstElementChild()
|
||||
firstname = self.name.newChild(None, 'first', 'A')
|
||||
firstname.addNextSibling(surname)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_nicknames_single(self):
|
||||
self.vcard.insert(4, "NICKNAME:Ton")
|
||||
name = self.person.newChild(None, "name", None)
|
||||
name.newProp("alt", "1")
|
||||
name.newProp("type", "Birth Name")
|
||||
name.newChild(None, "nick", "Ton")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_nicknames_empty(self):
|
||||
self.vcard.insert(4, "NICKNAME:")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_nicknames_multiple(self):
|
||||
self.vcard.insert(4, "NICKNAME:A,B\,C")
|
||||
name = self.person.newChild(None, "name", None)
|
||||
name.newProp("alt", "1")
|
||||
name.newProp("type", "Birth Name")
|
||||
name.newChild(None, "nick", "A")
|
||||
name = self.person.newChild(None, "name", None)
|
||||
name.newProp("alt", "1")
|
||||
name.newProp("type", "Birth Name")
|
||||
name.newChild(None, "nick", "B,C")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_address_all(self):
|
||||
self.vcard.insert(4, "ADR:box 1;bis;Broadway 11; New York; New York; NY1234; USA")
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'street', 'box 1 bis Broadway 11')
|
||||
address.newChild(None, 'city', 'New York')
|
||||
address.newChild(None, 'state', 'New York')
|
||||
address.newChild(None, 'country', 'USA')
|
||||
address.newChild(None, 'postal', 'NY1234')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_address_too_many(self):
|
||||
self.vcard.insert(4, "ADR:;;Broadway 11; New\,York; ;; USA; Earth")
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'street', 'Broadway 11')
|
||||
address.newChild(None, 'city', 'New,York')
|
||||
address.newChild(None, 'country', 'USA')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_address_too_few(self):
|
||||
self.vcard.insert(4, "ADR:;;Broadway 11; New York")
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'street', 'Broadway 11')
|
||||
address.newChild(None, 'city', 'New York')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_address_empty(self):
|
||||
self.vcard.insert(4, "ADR: ")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_phone_regular(self):
|
||||
self.vcard.insert(4, "TEL:01234-56789")
|
||||
address = self.person.newChild(None, 'address', None)
|
||||
address.newChild(None, 'phone', '01234-56789')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_phone_empty(self):
|
||||
self.vcard.insert(4, "TEL: ")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_birthday_regular(self):
|
||||
self.vcard.insert(4, 'BDAY:2001-09-28')
|
||||
eventref = self.person.newChild(None, 'eventref', None)
|
||||
eventref.newProp('hlink', 'E0000')
|
||||
eventref.newProp('role', 'Primary')
|
||||
events = self.gramps.getRootElement().newChild(None, 'events', None)
|
||||
event = events.newChild(None, 'event', None)
|
||||
event.newProp('handle', 'E0000')
|
||||
event.newProp('id', 'E0000')
|
||||
event.newChild(None, 'type', 'Birth')
|
||||
date = event.newChild(None, 'dateval', None)
|
||||
date.newProp('val', '2001-09-28')
|
||||
people = self.gramps.getRootElement().firstElementChild().nextElementSibling()
|
||||
events.addNextSibling(people)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_birthday_datetime(self):
|
||||
self.vcard.insert(4, 'BDAY:2001-09-28T09:23:47Z')
|
||||
eventref = self.person.newChild(None, 'eventref', None)
|
||||
eventref.newProp('hlink', 'E0000')
|
||||
eventref.newProp('role', 'Primary')
|
||||
events = self.gramps.getRootElement().newChild(None, 'events', None)
|
||||
event = events.newChild(None, 'event', None)
|
||||
event.newProp('handle', 'E0000')
|
||||
event.newProp('id', 'E0000')
|
||||
event.newChild(None, 'type', 'Birth')
|
||||
date = event.newChild(None, 'dateval', None)
|
||||
date.newProp('val', '2001-09-28')
|
||||
people = self.gramps.getRootElement().firstElementChild().nextElementSibling()
|
||||
events.addNextSibling(people)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_birthday_no_dash(self):
|
||||
self.vcard.insert(4, 'BDAY:20010928')
|
||||
eventref = self.person.newChild(None, 'eventref', None)
|
||||
eventref.newProp('hlink', 'E0000')
|
||||
eventref.newProp('role', 'Primary')
|
||||
events = self.gramps.getRootElement().newChild(None, 'events', None)
|
||||
event = events.newChild(None, 'event', None)
|
||||
event.newProp('handle', 'E0000')
|
||||
event.newProp('id', 'E0000')
|
||||
event.newChild(None, 'type', 'Birth')
|
||||
date = event.newChild(None, 'dateval', None)
|
||||
date.newProp('val', '2001-09-28')
|
||||
people = self.gramps.getRootElement().firstElementChild().nextElementSibling()
|
||||
events.addNextSibling(people)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_birthday_one_dash(self):
|
||||
self.vcard.insert(4, 'BDAY:2001-0928')
|
||||
eventref = self.person.newChild(None, 'eventref', None)
|
||||
eventref.newProp('hlink', 'E0000')
|
||||
eventref.newProp('role', 'Primary')
|
||||
events = self.gramps.getRootElement().newChild(None, 'events', None)
|
||||
event = events.newChild(None, 'event', None)
|
||||
event.newProp('handle', 'E0000')
|
||||
event.newProp('id', 'E0000')
|
||||
event.newChild(None, 'type', 'Birth')
|
||||
date = event.newChild(None, 'dateval', None)
|
||||
date.newProp('val', '2001-09-28')
|
||||
people = self.gramps.getRootElement().firstElementChild().nextElementSibling()
|
||||
events.addNextSibling(people)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_birthday_ddmmyyyy(self):
|
||||
self.vcard.insert(4, "BDAY:28-09-2001")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
#def test_add_birthday_non_existant(self):
|
||||
# # This test fails, I think gen.lib.date.set_yr_mon_day should raise
|
||||
# # an error if a wrong date is entered.
|
||||
# self.vcard.insert(4, 'BDAY:2001-13-28')
|
||||
# self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_birthday_empty(self):
|
||||
self.vcard.insert(4, "BDAY: ")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_occupation_regular(self):
|
||||
self.vcard.insert(4, "ROLE:scarecrow")
|
||||
eventref = self.person.newChild(None, 'eventref', None)
|
||||
eventref.newProp('hlink', 'E0000')
|
||||
eventref.newProp('role', 'Primary')
|
||||
events = self.gramps.getRootElement().newChild(None, 'events', None)
|
||||
event = events.newChild(None, 'event', None)
|
||||
event.newProp('handle', 'E0000')
|
||||
event.newProp('id', 'E0000')
|
||||
event.newChild(None, 'type', 'Occupation')
|
||||
event.newChild(None, 'description', 'scarecrow')
|
||||
people = self.gramps.getRootElement().firstElementChild().nextElementSibling()
|
||||
events.addNextSibling(people)
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_occupation_empty(self):
|
||||
self.vcard.insert(4, "ROLE: ")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_url_regular(self):
|
||||
self.vcard.insert(4, "URL:http://www.example.com")
|
||||
url = self.person.newChild(None, 'url', None)
|
||||
url.newProp('href', 'http://www.example.com')
|
||||
url.newProp('type', 'Unknown')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_url_empty(self):
|
||||
self.vcard.insert(4, "URL: ")
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
def test_add_email(self):
|
||||
self.vcard.insert(4, "EMAIL:me@example.org")
|
||||
url = self.person.newChild(None, 'url', None)
|
||||
url.newProp('href', 'me@example.org')
|
||||
url.newProp('type', 'E-mail')
|
||||
self.do_test("\r\n".join(self.vcard), self.gramps)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user