diff --git a/data/grampsxml.dtd b/data/grampsxml.dtd index b55ef1350..309530ec1 100644 --- a/data/grampsxml.dtd +++ b/data/grampsxml.dtd @@ -24,45 +24,46 @@ --> - - + bookmarks?, namemaps?)> + - @@ -86,7 +87,7 @@ HEADER - @@ -98,12 +99,12 @@ PEOPLE + parentin*, personref*, noteref*, sourceref*, tagref*)> @@ -112,8 +113,8 @@ GENDER has values of M, F, or U. --> - + - + - + - + @@ -162,20 +164,20 @@ GENDER has values of M, F, or U. - - + @@ -187,10 +189,10 @@ FAMILY @@ -198,33 +200,33 @@ FAMILY - - + - - + @@ -232,22 +234,23 @@ SOURCES - - + - + - - + @@ -290,56 +293,71 @@ OBJECTS description CDATA #REQUIRED > - - + - - + - - + - + + + + + + + @@ -350,7 +368,7 @@ BOOKMARKS hlink IDREF #REQUIRED > - @@ -361,7 +379,7 @@ NAME MAPS value CDATA #REQUIRED > - @@ -374,10 +392,10 @@ NAME FORMATS active (0|1) #IMPLIED > - - + - + - + - + - + - + - + + - + + + @@ -454,13 +477,13 @@ SHARED ELEMENTS - + - + diff --git a/data/grampsxml.rng b/data/grampsxml.rng index 818b92fdf..3c35f729a 100644 --- a/data/grampsxml.rng +++ b/data/grampsxml.rng @@ -31,7 +31,7 @@ @@ -53,6 +53,12 @@ + + + + + + @@ -129,15 +135,19 @@ - - + + + + + + + 0 1 - @@ -188,6 +198,9 @@ + + + @@ -268,10 +281,10 @@ calculated - + 01 - - + + @@ -281,10 +294,10 @@ calculated - + 01 - - + + @@ -298,10 +311,10 @@ estimated calculated - + 01 - - + + @@ -504,7 +517,7 @@ - + bold italic @@ -650,4 +663,15 @@ + + + + + + + + + + + diff --git a/src/plugins/export/ExportXml.py b/src/plugins/export/ExportXml.py index f7ffee567..056d6193b 100644 --- a/src/plugins/export/ExportXml.py +++ b/src/plugins/export/ExportXml.py @@ -198,9 +198,10 @@ class GrampsXmlWriter(UpdateCallback): repo_len = self.db.get_number_of_repositories() obj_len = self.db.get_number_of_media_objects() note_len = self.db.get_number_of_notes() + tag_len = self.db.get_number_of_tags() total_steps = person_len + family_len + event_len + source_len \ - + place_len + repo_len + obj_len + note_len + + place_len + repo_len + obj_len + note_len + tag_len self.set_total(total_steps) @@ -233,6 +234,16 @@ class GrampsXmlWriter(UpdateCallback): # by the time we get to person's names self.write_name_formats() + # Write table objects + if tag_len > 0: + self.g.write(" \n") + for key in self.db.get_tag_handles(): + tag = self.db.get_tag_from_handle(key) + self.write_tag(tag, 2) + self.update() + self.g.write(" \n") + + # Write primary objects if event_len > 0: self.g.write(" \n") for handle in self.db.get_event_handles(): @@ -384,6 +395,19 @@ class GrampsXmlWriter(UpdateCallback): escxml(name), escxml(fmt_str), int(active)) ) self.g.write(" \n") + def write_tag(self, tag, index=2): + """ + Write a tag definition. + """ + if not tag: + return + + self.write_table_tag('tag', tag, index, close=False) + self.g.write(' name="%s"' % escxml(tag.get_name())) + self.g.write(' color="%s"' % tag.get_color()) + self.g.write(' priority="%d"' % tag.get_priority()) + self.g.write('/>\n') + def fix(self,line): try: l = unicode(line) @@ -426,7 +450,7 @@ class GrampsXmlWriter(UpdateCallback): name = tag.name.xml_str() value = tag.value - self.g.write(' ' * index + '\n') @@ -435,7 +459,7 @@ class GrampsXmlWriter(UpdateCallback): self.g.write((' ' * (index + 1)) + '\n' % (start, end)) - self.g.write(' ' * index + '\n') + self.g.write(' ' * index + '\n') def write_text(self, val, text, indent=0): if not text: @@ -488,6 +512,10 @@ class GrampsXmlWriter(UpdateCallback): for s in person.get_source_references(): self.dump_source_ref(s,index+2) + + for tag_handle in person.get_tag_list(): + self.write_ref("tagref", tag_handle, index+1) + self.g.write("%s\n" % sp) def write_family(self,family,index=1): @@ -711,22 +739,37 @@ class GrampsXmlWriter(UpdateCallback): self.g.write('%s<%s hlink="_%s"%s%s>\n' % (sp,tagname, handle,extra_text,close_tag)) - def write_primary_tag(self,tagname, obj,index=1,close=True): + def write_primary_tag(self, tagname, obj, index=1, close=True): + """ + Write the tag attributes common to all primary objects. + """ if not obj: return - sp = " "*index marker = obj.get_marker().xml_str() if marker: marker_text = ' marker="%s"' % escxml(marker) else: marker_text = '' priv_text = conf_priv(obj) - change_text = ' change="%d"' % obj.get_change_time() - handle_id_text = ' id="%s" handle="_%s"' % (escxml(obj.gramps_id), obj.handle) - obj_text = '%s<%s' % (sp,tagname) + id_text = ' id="%s"' % escxml(obj.gramps_id) - self.g.write(obj_text + handle_id_text + priv_text + marker_text + - change_text) + self.write_table_tag(tagname, obj, index, False) + self.g.write(id_text + priv_text + marker_text) + if close: + self.g.write('>\n') + + def write_table_tag(self, tagname, obj, index=1, close=True): + """ + Write the tag attributes common to all table objects. + """ + if not obj: + return + sp = " " * index + change_text = ' change="%d"' % obj.get_change_time() + handle_text = ' handle="_%s"' % obj.get_handle() + + obj_text = '%s<%s' % (sp, tagname) + self.g.write(obj_text + handle_text + change_text) if close: self.g.write('>\n') diff --git a/src/plugins/import/ImportXml.py b/src/plugins/import/ImportXml.py index 749dc3c97..54aa419e6 100644 --- a/src/plugins/import/ImportXml.py +++ b/src/plugins/import/ImportXml.py @@ -46,7 +46,8 @@ import Utils import DateHandler from gen.display.name import displayer as name_displayer from gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, EVENT_KEY, - MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY) + MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, + TAG_KEY) from gen.updatecallback import UpdateCallback import const import libgrampsxml @@ -198,7 +199,7 @@ class ImportInfo(object): Class object that can hold information about the import """ keyorder = [PERSON_KEY, FAMILY_KEY, SOURCE_KEY, EVENT_KEY, MEDIA_KEY, - PLACE_KEY, REPOSITORY_KEY, NOTE_KEY] + PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, TAG_KEY] key2data = { PERSON_KEY : 0, FAMILY_KEY : 1, @@ -207,7 +208,8 @@ class ImportInfo(object): MEDIA_KEY: 4, PLACE_KEY: 5, REPOSITORY_KEY: 6, - NOTE_KEY: 7 + NOTE_KEY: 7, + TAG_KEY: 8 } def __init__(self): @@ -216,8 +218,8 @@ class ImportInfo(object): This creates the datastructures to hold info """ - self.data_mergeoverwrite = [{},{},{},{},{},{},{},{}] - self.data_newobject = [0,0,0,0,0,0,0,0] + self.data_mergeoverwrite = [{}] * 9 + self.data_newobject = [0] * 9 self.data_relpath = False @@ -257,6 +259,8 @@ class ImportInfo(object): return _(" Repository %(id)s\n") % {'id': obj.gramps_id} elif key == NOTE_KEY: return _(" Note %(id)s\n") % {'id': obj.gramps_id} + elif key == TAG_KEY: + return _(" Tag %(name)s\n") % {'name': obj.get_name()} def info_text(self): """ @@ -271,6 +275,7 @@ class ImportInfo(object): PLACE_KEY : _(' Places: %d\n'), REPOSITORY_KEY : _(' Repositories: %d\n'), NOTE_KEY : _(' Notes: %d\n'), + TAG_KEY : _(' Tags: %d\n'), } txt = _("Number of new objects imported:\n") for key in self.keyorder: @@ -373,6 +378,7 @@ class GrampsParser(UpdateCallback): self.in_note = 0 self.in_stext = 0 self.in_scomments = 0 + self.note = None self.note_text = None self.note_tags = [] self.in_witness = False @@ -529,8 +535,11 @@ class GrampsParser(UpdateCallback): "stext": (None, self.stop_stext), "stitle": (None, self.stop_stitle), "street": (None, self.stop_street), + "style": (self.start_style, None), "suffix": (None, self.stop_suffix), "tag": (self.start_tag, None), + "tagref": (self.start_tagref, None), + "tags": (None, None), "text": (None, self.stop_text), "title": (None, self.stop_title), "url": (self.start_url, None), @@ -1321,7 +1330,10 @@ class GrampsParser(UpdateCallback): self.name.prefix = attrs.get('prefix', '') self.name.group_as = attrs.get('group', '') - def start_tag(self, attrs): + def start_style(self, attrs): + """ + Styled text tag in notes (v1.4.0 onwards). + """ tagtype = gen.lib.StyledTextTagType() tagtype.set_from_xml_str(attrs['name']) @@ -1334,6 +1346,42 @@ class GrampsParser(UpdateCallback): return self.note_tags.append(gen.lib.StyledTextTag(tagtype, tagvalue)) + + def start_tag(self, attrs): + """ + Tag definition. + """ + if self.note is not None: + # Styled text tag in notes (prior to v1.4.0) + self.start_style(attrs) + return + + # Tag defintion + self.tag, new = self.db.find_tag_from_handle( + attrs['handle'].replace('_', ''), self.trans) + if new: + #keep change time from xml file + self.tag.change = int(attrs.get('change', self.change)) + self.info.add('new-object', TAG_KEY, self.tag) + else: + self.tag.change = self.change + self.info.add('merge-overwrite', TAG_KEY, self.tag) + + self.tag.set_name(attrs['name']) + self.tag.set_color(attrs['color']) + self.tag.set_priority(int(attrs['priority'])) + + self.db.commit_tag(self.tag, self.trans, self.tag.get_change_time()) + + def start_tagref(self, attrs): + """ + Tag reference in a primary object. + """ + handle = attrs['hlink'].replace('_', '') + self.db.check_tag_from_handle(handle, self.trans) + + if self.person: + self.person.add_tag(handle) def start_range(self, attrs): self.note_tags[-1].ranges.append((int(attrs['start']), diff --git a/src/plugins/lib/libgrampsxml.py b/src/plugins/lib/libgrampsxml.py index 09ca97676..cc2f79869 100644 --- a/src/plugins/lib/libgrampsxml.py +++ b/src/plugins/lib/libgrampsxml.py @@ -35,5 +35,5 @@ # Public Constants # #------------------------------------------------------------------------ -GRAMPS_XML_VERSION = "1.3.0" +GRAMPS_XML_VERSION = "1.4.0" diff --git a/src/plugins/lib/libmixin.py b/src/plugins/lib/libmixin.py index bbcde470a..135fe626a 100644 --- a/src/plugins/lib/libmixin.py +++ b/src/plugins/lib/libmixin.py @@ -29,8 +29,8 @@ Mixin for DbDir to enable find_from_handle and check_from_handle methods. # Gramps Modules # #------------------------------------------------------------------------------ -from gen.lib import (GenderStats, Person, Family, Event, Place, Source, - MediaObject, Repository, Note) +from gen.lib import (Person, Family, Event, Place, Source, + MediaObject, Repository, Note, Tag) #------------------------------------------------------------------------------ # @@ -50,10 +50,11 @@ class DbMixin(object): where "database" is the object name of your instance of the gramps database. """ - def find_from_handle(self, handle, transaction, class_type, dmap, + def __find_primary_from_handle(self, handle, transaction, class_type, dmap, add_func): """ - Find a object of class_type in the database from the passed handle. + Find a primary object of class_type in the database from the passed + handle. If no object exists, a new object is added to the database. @@ -74,14 +75,57 @@ class DbMixin(object): add_func(obj, transaction) return obj, new - def __check_from_handle(self, handle, transaction, class_type, dmap, + def __find_table_from_handle(self, handle, transaction, class_type, dmap, + add_func): + """ + Find a table object of class_type in the database from the passed + handle. + + If no object exists, a new object is added to the database. + + @return: Returns a tuple, first the object, second a bool which is True + if the object is new + @rtype: tuple + """ + obj = class_type() + handle = str(handle) + if handle in dmap: + obj.unserialize(dmap.get(handle)) + return obj, False + else: + obj.set_handle(handle) + add_func(obj, transaction) + return obj, True + + def __check_primary_from_handle(self, handle, transaction, class_type, dmap, add_func, set_gid=True): + """ + Check whether a primary object of class_type with the passed handle + exists in the database. + + If no such object exists, a new object is added to the database. + If set_gid then a new gramps_id is created, if not, None is used. + """ handle = str(handle) if handle not in dmap: obj = class_type() obj.set_handle(handle) add_func(obj, transaction, set_gid=set_gid) + def __check_table_from_handle(self, handle, transaction, class_type, dmap, + add_func): + """ + Check whether a table object of class_type with the passed handle exists + in the database. + + If no such object exists, a new object is added to the database. + """ + handle = str(handle) + if handle not in dmap: + obj = class_type() + obj.set_handle(handle) + add_func(obj, transaction) + def find_person_from_handle(self, handle, transaction): """ Find a Person in the database from the passed handle. @@ -92,7 +136,7 @@ class DbMixin(object): if the object is new @rtype: tuple """ - return self.find_from_handle(handle, transaction, Person, + return self.__find_primary_from_handle(handle, transaction, Person, self.person_map, self.add_person) def find_source_from_handle(self, handle, transaction): @@ -105,7 +149,7 @@ class DbMixin(object): if the object is new @rtype: tuple """ - return self.find_from_handle(handle, transaction, Source, + return self.__find_primary_from_handle(handle, transaction, Source, self.source_map, self.add_source) def find_event_from_handle(self, handle, transaction): @@ -118,7 +162,7 @@ class DbMixin(object): if the object is new @rtype: tuple """ - return self.find_from_handle(handle, transaction, Event, + return self.__find_primary_from_handle(handle, transaction, Event, self.event_map, self.add_event) def find_object_from_handle(self, handle, transaction): @@ -131,7 +175,7 @@ class DbMixin(object): if the object is new @rtype: tuple """ - return self.find_from_handle(handle, transaction, MediaObject, + return self.__find_primary_from_handle(handle, transaction, MediaObject, self.media_map, self.add_object) def find_place_from_handle(self, handle, transaction): @@ -144,7 +188,7 @@ class DbMixin(object): if the object is new @rtype: tuple """ - return self.find_from_handle(handle, transaction, Place, + return self.__find_primary_from_handle(handle, transaction, Place, self.place_map, self.add_place) def find_family_from_handle(self, handle, transaction): @@ -157,7 +201,7 @@ class DbMixin(object): if the object is new @rtype: tuple """ - return self.find_from_handle(handle, transaction, Family, + return self.__find_primary_from_handle(handle, transaction, Family, self.family_map, self.add_family) def find_repository_from_handle(self, handle, transaction): @@ -170,7 +214,7 @@ class DbMixin(object): if the object is new @rtype: tuple """ - return self.find_from_handle(handle, transaction, Repository, + return self.__find_primary_from_handle(handle, transaction, Repository, self.repository_map, self.add_repository) def find_note_from_handle(self, handle, transaction): @@ -183,9 +227,22 @@ class DbMixin(object): if the object is new @rtype: tuple """ - return self.find_from_handle(handle, transaction, Note, + return self.__find_primary_from_handle(handle, transaction, Note, self.note_map, self.add_note) + def find_tag_from_handle(self, handle, transaction): + """ + Find a Tag in the database from the passed handle. + + If no such Tag exists, a new Tag is added to the database. + + @return: Returns a tuple, first the object, second a bool which is True + if the object is new + @rtype: tuple + """ + return self.__find_table_from_handle(handle, transaction, Tag, + self.tag_map, self.add_tag) + def check_person_from_handle(self, handle, transaction, set_gid=True): """ Check whether a Person with the passed handle exists in the database. @@ -193,7 +250,7 @@ class DbMixin(object): If no such Person exists, a new Person is added to the database. If set_gid then a new gramps_id is created, if not, None is used. """ - self.__check_from_handle(handle, transaction, Person, + self.__check_primary_from_handle(handle, transaction, Person, self.person_map, self.add_person, set_gid = set_gid) @@ -204,7 +261,7 @@ class DbMixin(object): If no such Source exists, a new Source is added to the database. If set_gid then a new gramps_id is created, if not, None is used. """ - self.__check_from_handle(handle, transaction, Source, + self.__check_primary_from_handle(handle, transaction, Source, self.source_map, self.add_source, set_gid=set_gid) @@ -215,7 +272,7 @@ class DbMixin(object): If no such Event exists, a new Event is added to the database. If set_gid then a new gramps_id is created, if not, None is used. """ - self.__check_from_handle(handle, transaction, Event, + self.__check_primary_from_handle(handle, transaction, Event, self.event_map, self.add_event, set_gid=set_gid) @@ -228,7 +285,7 @@ class DbMixin(object): If set_gid then a new gramps_id is created, if not, None is used. """ - self.__check_from_handle(handle, transaction, MediaObject, + self.__check_primary_from_handle(handle, transaction, MediaObject, self.media_map, self.add_object, set_gid=set_gid) @@ -239,7 +296,7 @@ class DbMixin(object): If no such Place exists, a new Place is added to the database. If set_gid then a new gramps_id is created, if not, None is used. """ - self.__check_from_handle(handle, transaction, Place, + self.__check_primary_from_handle(handle, transaction, Place, self.place_map, self.add_place, set_gid=set_gid) @@ -250,7 +307,7 @@ class DbMixin(object): If no such Family exists, a new Family is added to the database. If set_gid then a new gramps_id is created, if not, None is used. """ - self.__check_from_handle(handle, transaction, Family, + self.__check_primary_from_handle(handle, transaction, Family, self.family_map, self.add_family, set_gid=set_gid) @@ -262,7 +319,7 @@ class DbMixin(object): If no such Repository exists, a new Repository is added to the database. If set_gid then a new gramps_id is created, if not, None is used. """ - self.__check_from_handle(handle, transaction, Repository, + self.__check_primary_from_handle(handle, transaction, Repository, self.repository_map, self.add_repository, set_gid=set_gid) @@ -273,6 +330,15 @@ class DbMixin(object): If no such Note exists, a new Note is added to the database. If set_gid then a new gramps_id is created, if not, None is used. """ - self.__check_from_handle(handle, transaction, Note, + self.__check_primary_from_handle(handle, transaction, Note, self.note_map, self.add_note, set_gid=set_gid) + + def check_tag_from_handle(self, handle, transaction): + """ + Check whether a Tag with the passed handle exists in the database. + + If no such Tag exists, a new Tag is added to the database. + """ + self.__check_table_from_handle(handle, transaction, Tag, + self.tag_map, self.add_tag)