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)