GEPS 011: Tagging - Add XML import and export

svn: r15938
This commit is contained in:
Nick Hall 2010-09-29 22:25:52 +00:00
parent 8b675ed1f2
commit 6b6da7dafe
6 changed files with 364 additions and 160 deletions

View File

@ -24,15 +24,15 @@
-->
<!--
This is the Document Type Definition file for v1.3.0
This is the Document Type Definition file for v1.4.0
of the GRAMPS XML genealogy data format.
Please use the following formal public identifier to identify it:
"-//GRAMPS//DTD GRAMPS XML V1.3.0//EN"
"-//GRAMPS//DTD GRAMPS XML V1.4.0//EN"
For example:
<!DOCTYPE database PUBLIC "-//GRAMPS//DTD GRAMPS XML V1.3.0//EN"
"http://gramps-project.org/xml/1.3.0/grampsxml.dtd"
<!DOCTYPE database PUBLIC "-//GRAMPS//DTD GRAMPS XML V1.4.0//EN"
"http://gramps-project.org/xml/1.4.0/grampsxml.dtd"
[...]>
-->
@ -49,13 +49,14 @@ DATABASE
objects
repositories
notes
tags
bookmarks
-->
<!ELEMENT database (header, name-formats?, events?, people?, families?,
<!ELEMENT database (header, name-formats?, tags?, events?, people?, families?,
sources?, places?, objects?, repositories?, notes?,
bookmarks?, namemaps?)>
<!ATTLIST database xmlns CDATA #FIXED "http://gramps-project.org/xml/1.3.0/">
<!ATTLIST database xmlns CDATA #FIXED "http://gramps-project.org/xml/1.4.0/">
<!-- ************************************************************
HEADER
@ -98,7 +99,7 @@ PEOPLE
<!ELEMENT person (gender, name*, nick?, eventref*, lds_ord*,
objref*, address*, attribute*, url*, childof*,
parentin*, personref*, noteref*, sourceref*)>
parentin*, personref*, noteref*, sourceref*, tagref*)>
<!ATTLIST person
id CDATA #REQUIRED
handle ID #REQUIRED
@ -150,7 +151,8 @@ GENDER has values of M, F, or U.
>
<!ELEMENT address ((daterange|datespan|dateval|datestr)?, street?, city?,
county?,state?,country?,postal?,phone?,noteref*,sourceref*)>
county?, state?, country?, postal?, phone?, noteref*,
sourceref*)>
<!ATTLIST address priv (0|1) #IMPLIED>
<!ELEMENT street (#PCDATA)>
@ -238,7 +240,8 @@ PLACES
<!ELEMENT places (placeobj)*>
<!ELEMENT placeobj (ptitle?,coord?,location*,objref*,url*,noteref*,sourceref*)>
<!ELEMENT placeobj (ptitle?, coord?, location*, objref*, url*, noteref*,
sourceref*)>
<!ATTLIST placeobj
id CDATA #REQUIRED
handle ID #REQUIRED
@ -313,7 +316,7 @@ NOTES
<!ELEMENT notes (note)*>
<!ELEMENT note (text,tag*)>
<!ELEMENT note (text, style*)>
<!ATTLIST note
id CDATA #REQUIRED
handle ID #REQUIRED
@ -326,8 +329,8 @@ NOTES
<!ELEMENT text (#PCDATA)>
<!ELEMENT tag (range+)>
<!ATTLIST tag
<!ELEMENT style (range+)>
<!ATTLIST style
name (bold|italic|underline|fontface|fontsize|
fontcolor|highlight|superscript) #REQUIRED
value CDATA #IMPLIED
@ -339,6 +342,21 @@ NOTES
end CDATA #REQUIRED
>
<!-- ************************************************************
TAGS
-->
<!ELEMENT tags (tag)*>
<!ELEMENT tag EMPTY>
<!ATTLIST tag
handle ID #REQUIRED
name CDATA #REQUIRED
color CDATA #REQUIRED
priority CDATA #REQUIRED
change CDATA #REQUIRED
>
<!-- ************************************************************
BOOKMARKS
-->
@ -437,6 +455,11 @@ SHARED ELEMENTS
hlink IDREF #REQUIRED
>
<!ELEMENT tagref EMPTY>
<!ATTLIST tagref
hlink IDREF #REQUIRED
>
<!ELEMENT spage (#PCDATA)>
<!ELEMENT attribute (sourceref*, noteref*)>

View File

@ -31,7 +31,7 @@
<grammar
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
ns="http://gramps-project.org/xml/1.3.0/"
ns="http://gramps-project.org/xml/1.4.0/"
xmlns="http://relaxng.org/ns/structure/1.0">
<start><element name="database">
@ -53,6 +53,12 @@
</element></zeroOrMore>
</element></optional>
<optional><element name="tags">
<zeroOrMore><element name="tag">
<ref name="tag-content"/>
</element></zeroOrMore>
</element></optional>
<optional><element name="events">
<zeroOrMore><element name="event">
<ref name="event-content"/>
@ -129,15 +135,19 @@
<optional><element name="resemail"><text/></element></optional>
</define>
<define name="primary-object">
<attribute name="id"><text/></attribute>
<define name="table-object">
<attribute name="handle"><data type="ID"/></attribute>
<attribute name="change"><text/></attribute>
</define>
<define name="primary-object">
<ref name="table-object"/>
<attribute name="id"><text/></attribute>
<optional><attribute name="priv"><choice>
<value>0</value>
<value>1</value>
</choice></attribute></optional>
<optional><attribute name="marker"><text/></attribute></optional>
<attribute name="change"><text/></attribute>
</define>
<define name="person-content">
@ -188,6 +198,9 @@
<zeroOrMore><element name="sourceref">
<ref name="sourceref-content"/>
</element></zeroOrMore>
<zeroOrMore><element name="tagref">
<ref name="tagref-content"/>
</element></zeroOrMore>
</define>
<define name="child-rel">
@ -504,7 +517,7 @@
<define name="styledtext">
<element name="text"><text/></element>
<zeroOrMore><element name="tag">
<zeroOrMore><element name="style">
<attribute name="name"><choice>
<value>bold</value>
<value>italic</value>
@ -650,4 +663,15 @@
<text/>
</define>
<define name="tagref-content">
<attribute name="hlink"><data type="IDREF"/></attribute>
</define>
<define name="tag-content">
<ref name="table-object"/>
<attribute name="name"><text/></attribute>
<attribute name="color"><text/></attribute>
<attribute name="priority"><data type="integer"/></attribute>
</define>
</grammar>

View File

@ -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(" <tags>\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(" </tags>\n")
# Write primary objects
if event_len > 0:
self.g.write(" <events>\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(" </name-formats>\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 + '<tag name="%s"' % name)
self.g.write(' ' * index + '<style name="%s"' % name)
if value:
self.g.write(' value="%s"' % escxml(str(value)))
self.g.write('>\n')
@ -435,7 +459,7 @@ class GrampsXmlWriter(UpdateCallback):
self.g.write((' ' * (index + 1)) +
'<range start="%d" end="%d"/>\n' % (start, end))
self.g.write(' ' * index + '</tag>\n')
self.g.write(' ' * index + '</style>\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</person>\n" % sp)
def write_family(self,family,index=1):
@ -712,21 +740,36 @@ class GrampsXmlWriter(UpdateCallback):
% (sp,tagname, handle,extra_text,close_tag))
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')

View File

@ -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'])
@ -1335,6 +1347,42 @@ class GrampsParser(UpdateCallback):
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']),
int(attrs['end'])))

View File

@ -35,5 +35,5 @@
# Public Constants
#
#------------------------------------------------------------------------
GRAMPS_XML_VERSION = "1.3.0"
GRAMPS_XML_VERSION = "1.4.0"

View File

@ -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)