#
# 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 that tests that part of the merge process that influences other
objects than the objects merging.
"""

import unittest
import time
import sys
import os
sys.path.append(os.curdir)
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, USER_PLUGINS
import gen.lib
from gen.ggettext import sgettext as _

class CopiedDoc(object):
    """Context manager that creates a deep copy of an libxml-xml document."""
    def __init__(self, xmldoc):
        self.xmldoc = xmldoc
        self.copy = libxml2.readDoc(str(self.xmldoc), '', None,
                                    libxml2.XML_PARSE_NONET)

    def __enter__(self):
        return self.copy

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.copy.freeDoc()
        return False

class XpathContext(object):
    """Context manager that creates a libxml2 xpath context that allows
       evaluation of xpath expressions."""
    def __init__(self, xmldoc):
        self.xmldoc = xmldoc
        self.ctxt = self.xmldoc.xpathNewContext()
        self.ctxt.xpathRegisterNs('g', 'http://gramps-project.org/xml/%s/' %
                                  GRAMPS_XML_VERSION)

    def __enter__(self):
        return self.ctxt

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.ctxt.xpathFreeContext()
        return False

class BaseMergeCheck(unittest.TestCase):
    def base_setup(self):
        """Set up code needed by all tests."""
        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.basedoc = None
        self.base_str = """<?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)

    def tearDown(self):
        self.style.freeStylesheet()
        self.basedoc.freeDoc()

    def canonicalize(self, doctxt):
        """
        Return a canonicalized string representation

        :param doctxt: the text to bring in canonical form.
        :type doctxt: either a string or an Xml document.
        :returns: The text but in canonical form.
        :rtype: string
        """
        result = ''
        if type(doctxt) == type('string'):
            doc = libxml2.readDoc(doctxt, '', None, libxml2.XML_PARSE_NONET)
        elif isinstance(doctxt, libxml2.xmlDoc):
            doc = doctxt
        else:
            raise TypeError
        param = {}
        canonical_doc = self.style.applyStylesheet(doc, param)
        result = self.style.saveResultToString(canonical_doc)
        canonical_doc.freeDoc()
        if type(doctxt) == type('string'):
            doc.freeDoc()
        return result

    def do_test(self, phoenix_id, titanic_id, input_doc, expect_doc,
                test_error_str='', debug=False):
        """Do the merge and "assert" the result."""
        process = subprocess.Popen('python gramps.py -d .ImportXML '
                                   '--config=preferences.eprefix:DEFAULT '
                                   '-i - -f gramps -a tool '
                                   '-p "name=climerge,primary=%s,secondary=%s" '
                                   '-e - -f gramps' % (phoenix_id, titanic_id),
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE, shell=True)
        result_str, err_str = process.communicate(str(input_doc))
        if err_str:
            if test_error_str:
                self.assert_(test_error_str in err_str)
                return
        if debug:
            print 'input :', self.canonicalize(input_doc)
            print 'result:', self.canonicalize(result_str)
            print 'expect:', self.canonicalize(expect_doc)
        self.assertEqual(self.canonicalize(result_str),
                         self.canonicalize(expect_doc))

    def do_family_test(self, phoenix_id, titanic_id, father_h, mother_h,
                       input_doc, expect_doc, test_error_str='', debug=False):
        process = subprocess.Popen('python gramps.py -d .ImportXML '
                                   '--config=preferences.eprefix:DEFAULT '
                                   '-i - -f gramps -a tool '
                                   '-p "name=climerge,primary=%s,secondary=%s,father_h=%s,mother_h=%s" '
                                   '-e - -f gramps' % (phoenix_id, titanic_id, father_h, mother_h),
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE, shell=True)
        result_str, err_str = process.communicate(str(input_doc))
        if err_str:
            if test_error_str:
                self.assert_(test_error_str in err_str)
                return
        if debug:
            print 'input :', self.canonicalize(input_doc)
            print 'result:', self.canonicalize(result_str)
            print 'expect:', self.canonicalize(expect_doc)
        self.assertEqual(self.canonicalize(result_str),
                         self.canonicalize(expect_doc))

    def raw_contains(self, phoenix_id, titanic_id, input_doc, expect_str,
                test_error_str='', debug=False):
        process = subprocess.Popen('python gramps.py -d .ImportXML '
                                   '--config=preferences.eprefix:DEFAULT '
                                   '-i - -f gramps -a tool '
                                   '-p "name=climerge,primary=%s,secondary=%s" '
                                   '-e - -f raw' % (phoenix_id, titanic_id),
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE, shell=True)
        result_str, err_str = process.communicate(str(input_doc))
        if err_str:
            if test_error_str:
                self.assert_(test_error_str in err_str)
                return
        if debug:
            print 'input :', self.canonicalize(input_doc)
            print 'result:', result_str
            print 'expect:', expect_str
        # should I order/canonicalise things?
        self.assert_(expect_str in result_str)
#-------------------------------------------------------------------------
#
# PersonCheck class
#
#-------------------------------------------------------------------------
class PersonCheck(BaseMergeCheck):
    """Class that checks what the influence is of merger of other primary
    objects on persons."""
    def setUp(self):
        self.base_setup()
        base_str = """
        <events>
          <event handle="_e0000" id="E0000">
            <type>Birth</type>
            <description>Event 0</description>
          </event>
          <event handle="_e0001" id="E0001">
            <type>Birth</type>
            <description>Event 1</description>
          </event>
        </events>
        <people>
          <person handle="_i0000" id="I0000">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 0</surname>
            </name>
            <eventref hlink="_e0000" role="Primary"/>
            <lds_ord type="baptism">
              <place hlink="_p0000"/>
            </lds_ord>
            <objref hlink="_o0000"/>
            <noteref hlink="_n0000"/>
            <sourceref hlink="_s0000">
              <spage>p.10</spage>
            </sourceref>
          </person>
          <person handle="_i0001" id="I0001">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 1</surname>
            </name>
            <eventref hlink="_e0001" role="Primary"/>
            <lds_ord type="baptism">
              <place hlink="_p0001"/>
            </lds_ord>
            <objref hlink="_o0001"/>
            <noteref hlink="_n0001"/>
            <sourceref hlink="_s0001">
              <spage>p.11</spage>
            </sourceref>
          </person>
        </people>
        <sources>
          <source handle="_s0000" id="S0000">
            <stitle>Source 0</stitle>
          </source>
          <source handle="_s0001" id="S0001">
            <stitle>Source 1</stitle>
          </source>
        </sources>
        <places>
          <placeobj handle="_p0000" id="P0000">
            <ptitle>Place 0</ptitle>
          </placeobj>
          <placeobj handle="_p0001" id="P0001">
            <ptitle>Place 1</ptitle>
          </placeobj>
        </places>
        <objects>
          <object handle="_o0000" id="O0000">
            <file src="image0.jpg" mime="image/jpeg" description="Image 0"/>
          </object>
          <object handle="_o0001" id="O0001">
            <file src="image1.jpg" mime="image/jpeg" description="Image 1"/>
          </object>
        </objects>
        <notes>
          <note handle="_n0000" id="N0000" type="Event Note">
            <text>Note 0</text>
          </note>
          <note handle="_n0001" id="N0001" type="Event Note">
            <text>Note 1</text>
          </note>
        </notes>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_event_merge(self):
        """Merge two events"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                eventref = ctxt.xpathEval(
                        "//g:person[@handle='_i0001']/g:eventref")[0]
                eventref.setProp('hlink', '_e0000')
                event = ctxt.xpathEval("//g:event[@handle='_e0001']")[0]
                event.unlinkNode()
                event.freeNode()
                self.do_test('E0000', 'E0001', self.basedoc, expect)

    def test_place_merge(self):
        """Merge two places"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                place = ctxt.xpathEval(
                        "//g:person[@handle='_i0001']/g:lds_ord/g:place")[0]
                place.setProp('hlink', '_p0000')
                placeobj = ctxt.xpathEval("//g:placeobj[@handle='_p0001']")[0]
                placeobj.unlinkNode()
                placeobj.freeNode()
                self.do_test('P0000', 'P0001', self.basedoc, expect)

    def test_source_merge(self):
        """Merge two sources"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                srcref = ctxt.xpathEval(
                        "//g:person[@handle='_i0001']/g:sourceref")[0]
                srcref.setProp('hlink', '_s0000')
                source = ctxt.xpathEval("//g:source[@handle='_s0001']")[0]
                source.unlinkNode()
                source.freeNode()
                self.do_test('S0000', 'S0001', self.basedoc, expect)

    def test_media_merge(self):
        """Merge two media objects"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                objref = ctxt.xpathEval(
                        "//g:person[@handle='_i0001']/g:objref")[0]
                objref.setProp('hlink', '_o0000')
                object_ = ctxt.xpathEval("//g:object[@handle='_o0001']")[0]
                object_.unlinkNode()
                object_.freeNode()
                self.do_test('O0000', 'O0001', self.basedoc, expect)

    def test_note_merge(self):
        """Merge two notes"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                noteref = ctxt.xpathEval(
                        "//g:person[@handle='_i0001']/g:noteref")[0]
                noteref.setProp('hlink', '_n0000')
                note = ctxt.xpathEval("//g:note[@handle='_n0001']")[0]
                note.unlinkNode()
                note.freeNode()
                self.do_test('N0000', 'N0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# FamilyCheck class
#
#-------------------------------------------------------------------------
class FamilyCheck(BaseMergeCheck):
    """Class that checks what the influence is of merger of other primary
    objects on families."""
    def setUp(self):
        self.base_setup()
        base_str = """
        <events>
          <event handle="_e0000" id="E0000">
            <type>Birth</type>
            <description>Event 0</description>
          </event>
          <event handle="_e0001" id="E0001">
            <type>Birth</type>
            <description>Event 1</description>
          </event>
        </events>
        <families>
          <family handle="_f0000" id="F0000">
            <rel type="Married"/>
            <eventref hlink="_e0000" role="Family"/>
            <lds_ord type="sealed_to_spouse">
              <place hlink="_p0000"/>
            </lds_ord>
            <objref hlink="_o0000"/>
            <noteref hlink="_n0000"/>
            <sourceref hlink="_s0000"/>
          </family>
          <family handle="_f0001" id="F0001">
            <rel type="Married"/>
            <eventref hlink="_e0001" role="Family"/>
            <lds_ord type="sealed_to_spouse">
              <place hlink="_p0001"/>
            </lds_ord>
            <objref hlink="_o0001"/>
            <noteref hlink="_n0001"/>
            <sourceref hlink="_s0001"/>
          </family>
        </families>
        <sources>
          <source handle="_s0000" id="S0000">
            <stitle>Source 0</stitle>
          </source>
          <source handle="_s0001" id="S0001">
            <stitle>Source 1</stitle>
          </source>
        </sources>
        <places>
          <placeobj handle="_p0000" id="P0000">
            <ptitle>Place 0</ptitle>
          </placeobj>
          <placeobj handle="_p0001" id="P0001">
            <ptitle>Place 1</ptitle>
          </placeobj>
        </places>
        <objects>
          <object handle="_o0000" id="O0000">
            <file src="image0.jpg" mime="image/jpeg" description="Image 0"/>
          </object>
          <object handle="_o0001" id="O0001">
            <file src="image1.jpg" mime="image/jpeg" description="Image 1"/>
          </object>
        </objects>
        <notes>
          <note handle="_n0000" id="N0000" type="Event Note">
            <text>Note 0</text>
          </note>
          <note handle="_n0001" id="N0001" type="Event Note">
            <text>Note 1</text>
          </note>
        </notes>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_event_merge(self):
        """Merge two events"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                eventref = ctxt.xpathEval(
                        "//g:family[@handle='_f0001']/g:eventref")[0]
                eventref.setProp('hlink', '_e0000')
                event = ctxt.xpathEval("//g:event[@handle='_e0001']")[0]
                event.unlinkNode()
                event.freeNode()
                self.do_test('E0000', 'E0001', self.basedoc, expect)

    def test_place_merge(self):
        """Merge two places"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                place = ctxt.xpathEval(
                        "//g:family[@handle='_f0001']/g:lds_ord/g:place")[0]
                place.setProp('hlink', '_p0000')
                placeobj = ctxt.xpathEval("//g:placeobj[@handle='_p0001']")[0]
                placeobj.unlinkNode()
                placeobj.freeNode()
                self.do_test('P0000', 'P0001', self.basedoc, expect)

    def test_source_merge(self):
        """Merge two sources"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                srcref = ctxt.xpathEval(
                        "//g:family[@handle='_f0001']/g:sourceref")[0]
                srcref.setProp('hlink', '_s0000')
                source = ctxt.xpathEval("//g:source[@handle='_s0001']")[0]
                source.unlinkNode()
                source.freeNode()
                self.do_test('S0000', 'S0001', self.basedoc, expect)

    def test_media_merge(self):
        """Merge two media objects"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                objref = ctxt.xpathEval(
                        "//g:family[@handle='_f0001']/g:objref")[0]
                objref.setProp('hlink', '_o0000')
                object_ = ctxt.xpathEval("//g:object[@handle='_o0001']")[0]
                object_.unlinkNode()
                object_.freeNode()
                self.do_test('O0000', 'O0001', self.basedoc, expect)

    def test_note_merge(self):
        """Merge two notes"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                noteref = ctxt.xpathEval(
                        "//g:family[@handle='_f0001']/g:noteref")[0]
                noteref.setProp('hlink', '_n0000')
                note = ctxt.xpathEval("//g:note[@handle='_n0001']")[0]
                note.unlinkNode()
                note.freeNode()
                self.do_test('N0000', 'N0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# EventCheck class
#
#-------------------------------------------------------------------------
class EventCheck(BaseMergeCheck):
    """Class that checks what the influence is of merger of other primary
    objects on events."""
    def setUp(self):
        self.base_setup()
        base_str = """
        <events>
          <event handle="_e0000" id="E0000">
            <type>Birth</type>
            <place hlink="_p0000"/>
            <description>Event 0</description>
            <noteref hlink="_n0000"/>
            <sourceref hlink="_s0000"/>
            <objref hlink="_o0000"/>
          </event>
          <event handle="_e0001" id="E0001">
            <type>Birth</type>
            <place hlink="_p0001"/>
            <description>Event 1</description>
            <noteref hlink="_n0001"/>
            <sourceref hlink="_s0001"/>
            <objref hlink="_o0001"/>
          </event>
        </events>
        <sources>
          <source handle="_s0000" id="S0000">
            <stitle>Source 0</stitle>
          </source>
          <source handle="_s0001" id="S0001">
            <stitle>Source 1</stitle>
          </source>
        </sources>
        <places>
          <placeobj handle="_p0000" id="P0000">
            <ptitle>Place 0</ptitle>
          </placeobj>
          <placeobj handle="_p0001" id="P0001">
            <ptitle>Place 1</ptitle>
          </placeobj>
        </places>
        <objects>
          <object handle="_o0000" id="O0000">
            <file src="image0.jpg" mime="image/jpeg" description="Image 0"/>
          </object>
          <object handle="_o0001" id="O0001">
            <file src="image1.jpg" mime="image/jpeg" description="Image 1"/>
          </object>
        </objects>
        <notes>
          <note handle="_n0000" id="N0000" type="Event Note">
            <text>Note 0</text>
          </note>
          <note handle="_n0001" id="N0001" type="Event Note">
            <text>Note 1</text>
          </note>
        </notes>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_place_merge(self):
        """Merge two places"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                place = ctxt.xpathEval(
                        "//g:event[@handle='_e0001']/g:place")[0]
                place.setProp('hlink', '_p0000')
                placeobj = ctxt.xpathEval("//g:placeobj[@handle='_p0001']")[0]
                placeobj.unlinkNode()
                placeobj.freeNode()
                self.do_test('P0000', 'P0001', self.basedoc, expect)

    def test_source_merge(self):
        """Merge two sources"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                srcref = ctxt.xpathEval(
                        "//g:event[@handle='_e0001']/g:sourceref")[0]
                srcref.setProp('hlink', '_s0000')
                source = ctxt.xpathEval("//g:source[@handle='_s0001']")[0]
                source.unlinkNode()
                source.freeNode()
                self.do_test('S0000', 'S0001', self.basedoc, expect)

    def test_media_merge(self):
        """Merge two media objects"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                objref = ctxt.xpathEval(
                        "//g:event[@handle='_e0001']/g:objref")[0]
                objref.setProp('hlink', '_o0000')
                object_ = ctxt.xpathEval("//g:object[@handle='_o0001']")[0]
                object_.unlinkNode()
                object_.freeNode()
                self.do_test('O0000', 'O0001', self.basedoc, expect)

    def test_note_merge(self):
        """Merge two notes"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                noteref = ctxt.xpathEval(
                        "//g:event[@handle='_e0001']/g:noteref")[0]
                noteref.setProp('hlink', '_n0000')
                note = ctxt.xpathEval("//g:note[@handle='_n0001']")[0]
                note.unlinkNode()
                note.freeNode()
                self.do_test('N0000', 'N0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# PlaceCheck class
#
#-------------------------------------------------------------------------
class PlaceCheck(BaseMergeCheck):
    """Class that checks what the influence is of merger of other primary
    objects on places."""
    def setUp(self):
        self.base_setup()
        base_str = """
        <sources>
          <source handle="_s0000" id="S0000">
            <stitle>Source 0</stitle>
          </source>
          <source handle="_s0001" id="S0001">
            <stitle>Source 1</stitle>
          </source>
        </sources>
        <places>
          <placeobj handle="_p0000" id="P0000">
            <ptitle>Place 0</ptitle>
            <objref hlink="_o0000"/>
            <noteref hlink="_n0000"/>
            <sourceref hlink="_s0000"/>
          </placeobj>
          <placeobj handle="_p0001" id="P0001">
            <ptitle>Place 1</ptitle>
            <objref hlink="_o0001"/>
            <noteref hlink="_n0001"/>
            <sourceref hlink="_s0001"/>
          </placeobj>
        </places>
        <objects>
          <object handle="_o0000" id="O0000">
            <file src="image0.jpg" mime="image/jpeg" description="Image 0"/>
          </object>
          <object handle="_o0001" id="O0001">
            <file src="image1.jpg" mime="image/jpeg" description="Image 1"/>
          </object>
        </objects>
        <notes>
          <note handle="_n0000" id="N0000" type="Event Note">
            <text>Note 0</text>
          </note>
          <note handle="_n0001" id="N0001" type="Event Note">
            <text>Note 1</text>
          </note>
        </notes>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_source_merge(self):
        """Merge two sources"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                srcref = ctxt.xpathEval(
                        "//g:placeobj[@handle='_p0001']/g:sourceref")[0]
                srcref.setProp('hlink', '_s0000')
                source = ctxt.xpathEval("//g:source[@handle='_s0001']")[0]
                source.unlinkNode()
                source.freeNode()
                self.do_test('S0000', 'S0001', self.basedoc, expect)

    def test_media_merge(self):
        """Merge two media objects"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                objref = ctxt.xpathEval(
                        "//g:placeobj[@handle='_p0001']/g:objref")[0]
                objref.setProp('hlink', '_o0000')
                object_ = ctxt.xpathEval("//g:object[@handle='_o0001']")[0]
                object_.unlinkNode()
                object_.freeNode()
                self.do_test('O0000', 'O0001', self.basedoc, expect)

    def test_note_merge(self):
        """Merge two notes"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                noteref = ctxt.xpathEval(
                        "//g:placeobj[@handle='_p0001']/g:noteref")[0]
                noteref.setProp('hlink', '_n0000')
                note = ctxt.xpathEval("//g:note[@handle='_n0001']")[0]
                note.unlinkNode()
                note.freeNode()
                self.do_test('N0000', 'N0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# SourceCheck class
#
#-------------------------------------------------------------------------
class SourceCheck(BaseMergeCheck):
    """Class that checks what the influence is of merger of other primary
    objects on sources."""
    def setUp(self):
        self.base_setup()
        base_str = """
        <sources>
          <source handle="_s0000" id="S0000">
            <stitle>Source 0</stitle>
            <noteref hlink="_n0000"/>
            <objref hlink="_o0000"/>
            <reporef hlink="_r0000" medium="Book"/>
          </source>
          <source handle="_s0001" id="S0001">
            <stitle>Source 1</stitle>
            <noteref hlink="_n0001"/>
            <objref hlink="_o0001"/>
            <reporef hlink="_r0001" medium="Book"/>
          </source>
        </sources>
        <objects>
          <object handle="_o0000" id="O0000">
            <file src="image0.jpg" mime="image/jpeg" description="Image 0"/>
          </object>
          <object handle="_o0001" id="O0001">
            <file src="image1.jpg" mime="image/jpeg" description="Image 1"/>
          </object>
        </objects>
        <repositories>
          <repository handle="_r0000" id="R0000">
            <rname>Repo 0</rname>
            <type>Library</type>
          </repository>
          <repository handle="_r0001" id="R0001">
            <rname>Repo 1</rname>
            <type>Library</type>
          </repository>
        </repositories>
        <notes>
          <note handle="_n0000" id="N0000" type="Event Note">
            <text>Note 0</text>
          </note>
          <note handle="_n0001" id="N0001" type="Event Note">
            <text>Note 1</text>
          </note>
        </notes>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    #def test_source_merge(self): SEE special cases.

    def test_repo_merge(self):
        """Merge two repositories"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                # Adjust the repository reference in expectation.
                reporef = ctxt.xpathEval(
                        "//g:source[@handle='_s0001']/g:reporef")[0]
                reporef.setProp('hlink', '_r0000')
                # Remove one repository in expectation.
                repo = ctxt.xpathEval("//g:repository[@handle='_r0001']")[0]
                repo.unlinkNode()
                repo.freeNode()
                # Do the actual merger and comparison.
                self.do_test('R0000', 'R0001', self.basedoc, expect)

    def test_media_merge(self):
        """Merge two media objects"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                objref = ctxt.xpathEval(
                        "//g:source[@handle='_s0001']/g:objref")[0]
                objref.setProp('hlink', '_o0000')
                object_ = ctxt.xpathEval("//g:object[@handle='_o0001']")[0]
                object_.unlinkNode()
                object_.freeNode()
                self.do_test('O0000', 'O0001', self.basedoc, expect)

    def test_note_merge(self):
        """Merge two notes"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                noteref = ctxt.xpathEval(
                        "//g:source[@handle='_s0001']/g:noteref")[0]
                noteref.setProp('hlink', '_n0000')
                note = ctxt.xpathEval("//g:note[@handle='_n0001']")[0]
                note.unlinkNode()
                note.freeNode()
                self.do_test('N0000', 'N0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# RepoCheck class
#
#-------------------------------------------------------------------------
class RepoCheck(BaseMergeCheck):
    """Class that checks what the influence is of merger of other primary
    objects on repositories."""
    def setUp(self):
        self.base_setup()
        base_str = """
        <sources>
          <source handle="_s0000" id="S0000">
            <stitle>Source 0</stitle>
          </source>
          <source handle="_s0001" id="S0001">
            <stitle>Source 1</stitle>
          </source>
        </sources>
        <repositories>
          <repository handle="_r0000" id="R0000">
            <rname>Repo 0</rname>
            <type>Library</type>
            <address>
              <city>Amsterdam</city>
              <sourceref hlink="_s0000"/>
            </address>
            <noteref hlink="_n0000"/>
          </repository>
          <repository handle="_r0001" id="R0001">
            <rname>Repo 1</rname>
            <type>Library</type>
            <address>
              <city>Rotterdam</city>
              <sourceref hlink="_s0001"/>
            </address>
            <noteref hlink="_n0001"/>
          </repository>
        </repositories>
        <notes>
          <note handle="_n0000" id="N0000" type="Event Note">
            <text>Note 0</text>
          </note>
          <note handle="_n0001" id="N0001" type="Event Note">
            <text>Note 1</text>
          </note>
        </notes>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_source_merge(self):
        """Merge two sources"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                srcref = ctxt.xpathEval(
                    "//g:repository[@handle='_r0001']/g:address/g:sourceref")[0]
                srcref.setProp('hlink', '_s0000')
                source = ctxt.xpathEval("//g:source[@handle='_s0001']")[0]
                source.unlinkNode()
                source.freeNode()
                self.do_test('S0000', 'S0001', self.basedoc, expect)

    def test_note_merge(self):
        """Merge two notes"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                noteref = ctxt.xpathEval(
                        "//g:repository[@handle='_r0001']/g:noteref")[0]
                noteref.setProp('hlink', '_n0000')
                note = ctxt.xpathEval("//g:note[@handle='_n0001']")[0]
                note.unlinkNode()
                note.freeNode()
                self.do_test('N0000', 'N0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# MediaCheck class
#
#-------------------------------------------------------------------------
class MediaCheck(BaseMergeCheck):
    """Class that checks what the influence is of merger of other primary
    objects on media objects."""
    def setUp(self):
        self.base_setup()
        base_str = """
        <sources>
          <source handle="_s0000" id="S0000">
            <stitle>Source 0</stitle>
          </source>
          <source handle="_s0001" id="S0001">
            <stitle>Source 1</stitle>
          </source>
        </sources>
        <objects>
          <object handle="_o0000" id="O0000">
            <file src="image0.jpg" mime="image/jpeg" description="Image 0"/>
            <noteref hlink="_n0000"/>
            <sourceref hlink="_s0000"/>
          </object>
          <object handle="_o0001" id="O0001">
            <file src="image1.jpg" mime="image/jpeg" description="Image 1"/>
            <noteref hlink="_n0001"/>
            <sourceref hlink="_s0001"/>
          </object>
        </objects>
        <notes>
          <note handle="_n0000" id="N0000" type="Event Note">
            <text>Note 0</text>
          </note>
          <note handle="_n0001" id="N0001" type="Event Note">
            <text>Note 1</text>
          </note>
        </notes>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_source_merge(self):
        """Merge two sources"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                srcref = ctxt.xpathEval(
                        "//g:object[@handle='_o0001']/g:sourceref")[0]
                srcref.setProp('hlink', '_s0000')
                source = ctxt.xpathEval("//g:source[@handle='_s0001']")[0]
                source.unlinkNode()
                source.freeNode()
                self.do_test('S0000', 'S0001', self.basedoc, expect)

    def test_note_merge(self):
        """Merge two notes"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                noteref = ctxt.xpathEval(
                        "//g:object[@handle='_o0001']/g:noteref")[0]
                noteref.setProp('hlink', '_n0000')
                note = ctxt.xpathEval("//g:note[@handle='_n0001']")[0]
                note.unlinkNode()
                note.freeNode()
                self.do_test('N0000', 'N0001', self.basedoc, expect)

#=========================================================================
#
# Special cases
#
#=========================================================================

class SourceSourceCheck(BaseMergeCheck):
    def setUp(self):
        self.base_setup()
        base_str = """
        <sources>
          <source handle="_s0000" id="S0000">
            <stitle>Source 0</stitle>
            <objref hlink="_o0000">
              <sourceref hlink="_s0002"/>
            </objref>
          </source>
          <source handle="_s0001" id="S0001">
            <stitle>Source 1</stitle>
            <objref hlink="_o0001">
              <sourceref hlink="_s0003"/>
            </objref>
          </source>
          <source handle="_s0002" id="S0002">
            <stitle>Source 2</stitle>
          </source>
          <source handle="_s0003" id="S0003">
            <stitle>Source 3</stitle>
          </source>
        </sources>
        <objects>
          <object handle="_o0000" id="O0000">
            <file src="image0.jpg" mime="image/jpeg" description="Image 0"/>
          </object>
          <object handle="_o0001" id="O0001">
            <file src="image1.jpg" mime="image/jpeg" description="Image 1"/>
          </object>
        </objects>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_source_merge(self):
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                srcrefs = ctxt.xpathEval(
                        "//g:source[@handle='_s0001']/g:objref/g:sourceref")
                srcrefs[0].setProp('hlink', '_s0002')
                sources = ctxt.xpathEval("//g:source[@handle='_s0003']")
                sources[0].unlinkNode()
                sources[0].freeNode()
                self.do_test('S0002', 'S0003', self.basedoc, expect)

    def test_source_cross_merge(self):
        with XpathContext(self.basedoc) as input_ctxt:
            input_srcrefs = input_ctxt.xpathEval(
                    "//g:source/g:objref/g:sourceref")
            input_srcrefs[0].setProp('hlink', '_s0001')
            input_srcrefs[1].setProp('hlink', '_s0000')
            rmsrc = input_ctxt.xpathEval("//g:source[@handle='_s0002']")
            rmsrc[0].unlinkNode()
            rmsrc[0].freeNode()
            rmsrc = input_ctxt.xpathEval("//g:source[@handle='_s0003']")
            rmsrc[0].unlinkNode()
            rmsrc[0].freeNode()
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    srcrefs = ctxt.xpathEval(
                        "//g:source[@handle='_s0000']/g:objref/g:sourceref")
                    srcrefs[0].setProp('hlink', '_s0000')
                    # add objref
                    objref = ctxt.xpathEval("//g:source[@handle='_s0000']/g:objref")
                    objref2 = ctxt.xpathEval("//g:source[@handle='_s0001']/g:objref")
                    objref[0].addNextSibling(objref2[0])
                    # remove source
                    sources = ctxt.xpathEval("//g:source[@handle='_s0001']")
                    sources[0].unlinkNode()
                    sources[0].freeNode()
                    self.do_test('S0000', 'S0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# BirthCheck class
#
#-------------------------------------------------------------------------
class BirthCheck(BaseMergeCheck):
    def setUp(self):
        self.base_setup()
        base_str = """
        <events>
          <event handle="_e0000" id="E0000">
            <type>Baptism</type>
            <description>Event 0</description>
          </event>
          <event handle="_e0001" id="E0001">
            <type>Birth</type>
            <description>Event 1</description>
          </event>
          <event handle="_e0002" id="E0002">
            <type>Death</type>
            <description>Event 2</description>
          </event>
        </events>
        <people>
          <person handle="_i0000" id="I0000">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 0</surname>
            </name>
            <eventref hlink="_e0000" role="Primary"/>
            <eventref hlink="_e0001" role="Primary"/>
            <eventref hlink="_e0002" role="Primary"/>
          </person>
        </people>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)
        surname = gen.lib.Surname()
        surname.set_surname(u"Person 0")
        name = gen.lib.Name()
        name.add_surname(surname)
        self.expect_str = "person: i0000 ('i0000', 'I0000', 1, %s, [], " % str(
                          name.serialize())

    def test_birth_loss(self):
        # check that birth_ref_index is -1
        expect_str = self.expect_str + "1, -1"
        self.raw_contains('E0000', 'E0001', self.basedoc, expect_str)

    def test_second_birth(self):
        # check that birth _ref_index is 2
        with XpathContext(self.basedoc) as ctxt:
            events = ctxt.xpathEval("//g:events")
            second_birth = events[0].newChild(None, 'event', None)
            second_birth.newProp('handle', '_e0003')
            second_birth.newProp('id', 'E0003')
            second_birth.newChild(None, 'type', 'Birth')
            second_birth.newChild(None, 'description', 'Event 3')
            person = ctxt.xpathEval("//g:person[@handle='_i0000']")
            eventref = person[0].newChild(None, 'eventref', None)
            eventref.newProp('hlink', '_e0003')
            eventref.newProp('role', 'Primary')
            expect_str = self.expect_str + "1, 2"
            self.raw_contains('E0000', 'E0001', self.basedoc, expect_str)

    def test_death_merge(self):
        # check that death_ref_index is -1
        expect_str = self.expect_str + "-1, 1"
        self.raw_contains('E0000', 'E0002', self.basedoc, expect_str)

    def test_second_death(self):
        # check that death _ref_index is 2
        with XpathContext(self.basedoc) as ctxt:
            events = ctxt.xpathEval("//g:events")
            second_death = events[0].newChild(None, 'event', None)
            second_death.newProp('handle', '_e0003')
            second_death.newProp('id', 'E0003')
            second_death.newChild(None, 'type', 'Death')
            second_death.newChild(None, 'description', 'Event 3')
            person = ctxt.xpathEval("//g:person[@handle='_i0000']")
            eventref = person[0].newChild(None, 'eventref', None)
            eventref.newProp('hlink', '_e0003')
            eventref.newProp('role', 'Primary')
            expect_str = self.expect_str + "2, 1"
            self.raw_contains('E0000', 'E0002', self.basedoc, expect_str)

#-------------------------------------------------------------------------
#
# PersonPersonCheck class
#
#-------------------------------------------------------------------------
class PersonPersonCheck(BaseMergeCheck):
    def setUp(self):
        self.base_setup()
        base_str = """
        <people>
          <person handle="_i0000" id="I0000">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 0</surname>
            </name>
          </person>
          <person handle="_i0001" id="I0001">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 1</surname>
            </name>
          </person>
        </people>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_person_merge(self):
        """There is a person not involved in merger that references Titanic."""
        with XpathContext(self.basedoc) as input_ctxt:
            people = input_ctxt.xpathEval("//g:people")[0]
            person = people.newChild(None, 'person', None)
            person.newProp('handle', '_i0002')
            person.newProp('id', 'I0002')
            person.newChild(None, 'gender', 'M')
            name = person.newChild(None, 'name', None)
            name.newProp('type', 'Birth Name')
            name.newChild(None, 'surname', 'Person 2')
            personref = person.newChild(None, 'personref', None)
            personref.newProp('hlink', '_i0001')
            personref.newProp('rel', 'Neighbour')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    personref = ctxt.xpathEval("//g:personref")[0]
                    personref.setProp('hlink', '_i0000')
                    person = ctxt.xpathEval("//g:person[@handle='_i0000']")[0]
                    altname = person.newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 1')
                    attr = person.newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0001')
                    person = ctxt.xpathEval("//g:person[@handle='_i0001']")[0]
                    person.unlinkNode()
                    person.freeNode()
                    self.do_test('I0000', 'I0001', self.basedoc, expect)

    def test_person_cross(self):
        """Phoenix has ref to Titanic and vice versa"""
        with XpathContext(self.basedoc) as input_ctxt:
            persons = input_ctxt.xpathEval("//g:person")
            personref = persons[0].newChild(None, 'personref', None)
            personref.newProp('hlink', '_i0001')
            personref.newProp('rel','Neighbour East')
            personref = persons[1].newChild(None, 'personref', None)
            personref.newProp('hlink', '_i0000')
            personref.newProp('rel','Neighbour West')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:

                    personref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:personref")[0]
                    personref.setProp('hlink', '_i0000')
                    person = ctxt.xpathEval("//g:person[@handle='_i0000']")[0]
                    altname = person.newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 1')
                    attr = person.newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0001')
                    attr.addNextSibling(personref) # restore order of elements
                    personref = person.newChild(None, 'personref', None)
                    personref.newProp('hlink', '_i0000')
                    personref.newProp('rel', 'Neighbour West')
                    person = ctxt.xpathEval("//g:person[@handle='_i0001']")[0]
                    person.unlinkNode()
                    person.freeNode()
                    self.do_test('I0000', 'I0001', self.basedoc, expect)

    def test_person_self(self):
        """Titanic references itself"""
        with XpathContext(self.basedoc) as input_ctxt:
            person = input_ctxt.xpathEval("//g:person[@handle='_i0001']")[0]
            personref = person.newChild(None, 'personref', None)
            personref.newProp('hlink', '_i0001')
            personref.newProp('rel', 'Neighbour')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    person = ctxt.xpathEval("//g:person[@handle='_i0000']")[0]
                    altname = person.newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 1')
                    attr = person.newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0001')
                    personref = person.newChild(None, 'personref', None)
                    personref.newProp('hlink', '_i0000')
                    personref.newProp('rel', 'Neighbour')
                    person = ctxt.xpathEval("//g:person[@handle='_i0001']")[0]
                    person.unlinkNode()
                    person.freeNode()
                    self.do_test('I0000', 'I0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# ParentFamilyPesronCheck class
#
#-------------------------------------------------------------------------
class ParentFamilyPersonCheck(BaseMergeCheck):
    def setUp(self):
        self.base_setup()
        base_str = """
        <people>
          <person handle="_i0000" id="I0000">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 0</surname>
            </name>
            <childof hlink="_f0000"/>
          </person>
          <person handle="_i0001" id="I0001">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 1</surname>
            </name>
            <childof hlink="_f0001"/>
          </person>
        </people>
        <families>
          <family handle="_f0000" id="F0000">
            <rel type="Unknown"/>
            <childref hlink="_i0000"/>
          </family>
          <family handle="_f0001" id="F0001">
            <rel type="Unknown"/>
            <childref hlink="_i0001"/>
          </family>
        </families>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_person_merge(self):
        """Merge two persons that are children in some family"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                childref = ctxt.xpathEval("//g:family[@handle='_f0001']/g:childref")[0]
                childref.setProp('hlink', '_i0000')

                persons = ctxt.xpathEval("//g:person")
                altname = persons[0].newChild(None, 'name', None)
                altname.newProp('alt', '1')
                altname.newProp('type', 'Birth Name')
                altname.newChild(None, 'surname', 'Person 1')
                attr = persons[0].newChild(None, 'attribute', None)
                attr.newProp('type', 'Merged Gramps ID')
                attr.newProp('value', 'I0001')
                childof = ctxt.xpathEval("//g:person[@handle='_i0000']/g:childof")[0]
                attr.addNextSibling(childof) # restore order of elements
                childof = persons[0].newChild(None, 'childof', None)
                childof.newProp('hlink', '_f0001')
                persons[1].unlinkNode()
                persons[1].freeNode()
                self.do_test('I0000', 'I0001', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# FamilyPersonCheck class
#
#-------------------------------------------------------------------------
class FamilyPersonCheck(BaseMergeCheck):
    """Merge two persons that are parents in families"""
    def setUp(self):
        self.base_setup()
        base_str = """
        <people>
          <person handle="_i0000" id="I0000">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 0</surname>
            </name>
            <parentin hlink="_f0000"/>
          </person>
          <person handle="_i0001" id="I0001">
            <gender>F</gender>
            <name type="Birth Name">
              <surname>Person 1</surname>
            </name>
            <parentin hlink="_f0000"/>
          </person>
          <person handle="_i0002" id="I0002">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 2</surname>
            </name>
          </person>
          <person handle="_i0003" id="I0003">
            <gender>F</gender>
            <name type="Birth Name">
              <surname>Person 3</surname>
            </name>
          </person>
        </people>
        <families>
          <family handle="_f0000" id="F0000">
            <rel type="Unknown"/>
            <father hlink="_i0000"/>
            <mother hlink="_i0001"/>
          </family>
          <family handle="_f0001" id="F0001">
            <rel type="Unknown"/>
          </family>
        </families>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_titanic_no_fam(self):
        "Test merge of two persons where titanic is not a parent in a family"
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                persons = ctxt.xpathEval("//g:person")
                altname = persons[0].newChild(None, 'name', None)
                altname.newProp('alt', '1')
                altname.newProp('type', 'Birth Name')
                altname.newChild(None, 'surname', 'Person 2')
                attr = persons[0].newChild(None, 'attribute', None)
                attr.newProp('type', 'Merged Gramps ID')
                attr.newProp('value', 'I0002')
                parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                attr.addNextSibling(parentref) # restore order of elements
                persons[2].unlinkNode()
                persons[2].freeNode()
                self.do_test('I0000', 'I0002', self.basedoc, expect)

    def test_no_fam_merge(self):
        """Test merge of two persons, both parents in a family, but such that
        the families will not merge."""
        with XpathContext(self.basedoc) as input_ctxt:
            person = input_ctxt.xpathEval("//g:person[@handle='_i0002']")[0]
            parentin = person.newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            father = family.newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    parentin = persons[0].newChild(None, 'parentin', None)
                    parentin.newProp('hlink', '_f0001')
                    father = ctxt.xpathEval("//g:family[@handle='_f0001']/g:father")[0]
                    father.setProp('hlink', '_i0000')
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    self.do_test('I0000', 'I0002', self.basedoc, expect)

    def test_multi_rel(self):
        """Merge two persons where titanic has multiple family relationships
        with his partner, this should raise an error."""
        with XpathContext(self.basedoc) as input_ctxt:
            persons = input_ctxt.xpathEval("//g:person")
            parentin = persons[2].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            parentin = persons[3].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            parentin = persons[2].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0002')
            parentin = persons[3].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0002')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            father = family.newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            mother = family.newChild(None, 'mother', None)
            mother.newProp('hlink', '_i0003')
            families = input_ctxt.xpathEval("//g:families")[0]
            family = families.newChild(None, 'family', None)
            family.newProp('handle', '_f0002')
            family.newProp('id', 'F0002')
            rel = family.newChild(None, 'rel', None)
            rel.newProp('type', 'Married')
            father = family.newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            mother = family.newChild(None, 'mother', None)
            mother.newProp('hlink', '_i0003')
            with CopiedDoc(self.basedoc) as expect:
                self.do_test('I0000', 'I0002', self.basedoc, expect,
                        test_error_str=_("A person with multiple relations "
                        "with the same spouse is about to be merged. This is "
                        "beyond the capabilities of the merge routine. The "
                        "merge is aborted.")) 

    def test_merge_fam(self):
        """Merge two persons such that also the families in which they are
        parents get merged."""
        with XpathContext(self.basedoc) as input_ctxt:
            persons = input_ctxt.xpathEval("//g:person")
            parentin = persons[1].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            parentin = persons[2].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            father = family.newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            mother = family.newChild(None, 'mother', None)
            mother.newProp('hlink', '_i0001')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                    family.unlinkNode()
                    family.freeNode()
                    parentin = ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[1]
                    parentin.unlinkNode()
                    parentin.freeNode()
                    self.do_test('I0000', 'I0002', self.basedoc, expect)

    def test_fam_none_merge(self):
        """Merge two persons, both father in families without mothers."""
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            mother = input_ctxt.xpathEval("//g:family[@handle='_f0000']/g:mother")[0]
            mother.unlinkNode()
            mother.freeNode()
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            father = family.newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            person = input_ctxt.xpathEval("//g:person[@handle='_i0002']")[0]
            parentin = person.newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                    family.unlinkNode()
                    family.freeNode()
                    self.do_test('I0000', 'I0002', self.basedoc, expect)

    # Can't think of a testcase that would merge multiple families.

    def test_fam_mother_merge(self):
        """Merge two persons that are mothers in their respective families."""
        with XpathContext(self.basedoc) as input_ctxt:
            persons = input_ctxt.xpathEval("//g:person")
            parentin = persons[0].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            parentin = persons[3].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            father = family.newChild(None, 'father', None)
            father.newProp('hlink', '_i0000')
            mother = family.newChild(None, 'mother', None)
            mother.newProp('hlink', '_i0003')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[1].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 3')
                    attr = persons[1].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0003')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[3].unlinkNode()
                    persons[3].freeNode()
                    family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                    family.unlinkNode()
                    family.freeNode()
                    parentin = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[1]
                    parentin.unlinkNode()
                    parentin.freeNode()
                    self.do_test('I0001', 'I0003', self.basedoc, expect)

    def test_childref_notyet(self):
        """Merge two people leading to merger of families that have children."""
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            mother = input_ctxt.xpathEval("//g:family[@handle='_f0000']/g:mother")[0]
            mother.unlinkNode()
            mother.freeNode()
            persons = input_ctxt.xpathEval("//g:person")
            childof = persons[1].newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0000')
            families = input_ctxt.xpathEval("//g:family")
            childref = families[0].newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0001')
            parentin = persons[2].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            father = families[1].newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            childof = persons[3].newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0001')
            childref = families[1].newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0003')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    families = ctxt.xpathEval("//g:family")
                    families[1].unlinkNode()
                    families[1].freeNode()
                    childof = ctxt.xpathEval("//g:person[@handle='_i0003']/g:childof")[0]
                    childof.setProp('hlink', '_f0000')
                    childref = families[0].newChild(None, 'childref', None)
                    childref.newProp('hlink', '_i0003')
                    self.do_test('I0000', 'I0002', self.basedoc, expect)

    def test_childref_already(self):
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            mother = input_ctxt.xpathEval("//g:family[@handle='_f0000']/g:mother")[0]
            mother.unlinkNode()
            mother.freeNode()
            persons = input_ctxt.xpathEval("//g:person")
            childof = persons[1].newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0000')
            childof = persons[1].newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0001')
            families = input_ctxt.xpathEval("//g:family")
            childref = families[0].newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0001')
            parentin = persons[2].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            father = families[1].newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            childref = families[1].newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0001')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    families = ctxt.xpathEval("//g:family")
                    families[1].unlinkNode()
                    families[1].freeNode()
                    childof = ctxt.xpathEval("//g:person[@handle='_i0001']/g:childof")
                    childof[1].unlinkNode()
                    childof[1].freeNode()
                    self.do_test('I0000', 'I0002', self.basedoc, expect)

    def test_ldsord(self):
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            mother = input_ctxt.xpathEval("//g:family[@handle='_f0000']/g:mother")[0]
            mother.unlinkNode()
            mother.freeNode()
            persons = input_ctxt.xpathEval("//g:person")
            parentin = persons[2].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            father = family.newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            ldsord = persons[3].newChild(None, 'lds_ord', None)
            ldsord.newProp('type', 'sealed_to_parents')
            sealedto = ldsord.newChild(None, 'sealed_to', None)
            sealedto.newProp('hlink', '_f0001')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    families = ctxt.xpathEval("//g:family")
                    families[1].unlinkNode()
                    families[1].freeNode()
                    sealedto = ctxt.xpathEval("//g:sealed_to")[0]
                    sealedto.setProp('hlink', '_f0000')
                    self.do_test('I0000', 'I0002', self.basedoc, expect)

    # This test fails because the assigment of family ids shifts F0000 to F0001
    # and F0001 to F0002!
    #def test_ldsord_cross(self):
    #    with XpathContext(self.basedoc) as input_ctxt:
    #        parentin = input_ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
    #        parentin.unlinkNode()
    #        parentin.freeNode()
    #        mother = input_ctxt.xpathEval("//g:family[@handle='_f0000']/g:mother")[0]
    #        mother.unlinkNode()
    #        mother.freeNode()
    #        persons = input_ctxt.xpathEval("//g:person")
    #        parentin = persons[2].newChild(None, 'parentin', None)
    #        parentin.newProp('hlink', '_f0001')
    #        family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
    #        father = family.newChild(None, 'father', None)
    #        father.newProp('hlink', '_i0002')
    #        ldsord = persons[0].newChild(None, 'lds_ord', None)
    #        ldsord.newProp('type', 'sealed_to_parents')
    #        sealedto = ldsord.newChild(None, 'sealed_to', None)
    #        sealedto.newProp('hlink', '_f0001')
    #        with CopiedDoc(self.basedoc) as expect:
    #            with XpathContext(expect) as ctxt:
    #                persons = ctxt.xpathEval("//g:person")
    #                altname = persons[0].newChild(None, 'name', None)
    #                altname.newProp('alt', '1')
    #                altname.newProp('type', 'Birth Name')
    #                altname.newChild(None, 'surname', 'Person 2')
    #                ldsord = ctxt.xpathEval("//g:lds_ord")[0]
    #                altname.addNextSibling(ldsord) # restore order of elements
    #                attr = persons[0].newChild(None, 'attribute', None)
    #                attr.newProp('type', 'Merged Gramps ID')
    #                attr.newProp('value', 'I0002')
    #                parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
    #                attr.addNextSibling(parentref) # restore order of elements
    #                persons[2].unlinkNode()
    #                persons[2].freeNode()
    #                families = ctxt.xpathEval("//g:family")
    #                families[1].unlinkNode()
    #                families[1].freeNode()
    #                sealedto = ctxt.xpathEval("//g:sealed_to")[0]
    #                sealedto.setProp('hlink', '_f0000')
    #                self.do_test('I0000', 'I0002', self.basedoc, expect)

    def test_ldsord_self(self):
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            mother = input_ctxt.xpathEval("//g:family[@handle='_f0000']/g:mother")[0]
            mother.unlinkNode()
            mother.freeNode()
            persons = input_ctxt.xpathEval("//g:person")
            parentin = persons[2].newChild(None, 'parentin', None)
            parentin.newProp('hlink', '_f0001')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            father = family.newChild(None, 'father', None)
            father.newProp('hlink', '_i0002')
            ldsord = persons[2].newChild(None, 'lds_ord', None)
            ldsord.newProp('type', 'sealed_to_parents')
            sealedto = ldsord.newChild(None, 'sealed_to', None)
            sealedto.newProp('hlink', '_f0001')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    ldsord = ctxt.xpathEval("//g:lds_ord")[0]
                    altname.addNextSibling(ldsord) # restore order of elements
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    families = ctxt.xpathEval("//g:family")
                    families[1].unlinkNode()
                    families[1].freeNode()
                    sealedto = ctxt.xpathEval("//g:sealed_to")[0]
                    sealedto.setProp('hlink', '_f0000')
                    self.do_test('I0000', 'I0002', self.basedoc, expect)

#-------------------------------------------------------------------------
#
# FamilyMergeCheck class
#
#-------------------------------------------------------------------------
class FamilyMergeCheck(BaseMergeCheck):
    def setUp(self):
        self.base_setup()
        base_str = """
        <people>
          <person handle="_i0000" id="I0000">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 0</surname>
            </name>
            <parentin hlink="_f0000"/>
          </person>
          <person handle="_i0001" id="I0001">
            <gender>F</gender>
            <name type="Birth Name">
              <surname>Person 1</surname>
            </name>
            <parentin hlink="_f0000"/>
          </person>
          <person handle="_i0002" id="I0002">
            <gender>M</gender>
            <name type="Birth Name">
              <surname>Person 2</surname>
            </name>
            <parentin hlink="_f0001"/>
          </person>
          <person handle="_i0003" id="I0003">
            <gender>F</gender>
            <name type="Birth Name">
              <surname>Person 3</surname>
            </name>
            <parentin hlink="_f0001"/>
          </person>
        </people>
        <families>
          <family handle="_f0000" id="F0000">
            <rel type="Unknown"/>
            <father hlink="_i0000"/>
            <mother hlink="_i0001"/>
          </family>
          <family handle="_f0001" id="F0001">
            <rel type="Unknown"/>
            <father hlink="_i0002"/>
            <mother hlink="_i0003"/>
          </family>
        </families>
        </database>"""
        self.basedoc = libxml2.readDoc(self.base_str + base_str, '', None,
                                       libxml2.XML_PARSE_NONET)

    def test_father_son_merge(self):
        """Merge two families where the fathers have a father-son relationship
        so that an error is raised."""
        with XpathContext(self.basedoc) as input_ctxt:
            person = input_ctxt.xpathEval("//g:person[@handle='_i0002']")[0]
            childof = person.newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0000')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0000']")[0]
            childref = family.newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0002')
            with CopiedDoc(self.basedoc) as expect:
                self.do_family_test('F0000', 'F0001', 'i0000', 'i0001',
                        self.basedoc, expect, test_error_str=_("A parent and "
                        "child cannot be merged. To merge these people, you "
                        "must first break the relationship between them."))

    def test_child_parent_merge_no_father(self):
        """Merge two families where the phoenix family has no father and
        the father of the titanic family is a child of the phoenix family."""
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0002']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            father = input_ctxt.xpathEval("//g:family[@handle='_f0001']/g:father")[0]
            father.unlinkNode()
            father.freeNode()
            person = input_ctxt.xpathEval("//g:person[@handle='_i0000']")[0]
            childof = person.newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0001')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            childref = family.newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0000')
            with CopiedDoc(self.basedoc) as expect:
                self.do_family_test('F0000', 'F0001', 'i0000', 'i0001',
                        self.basedoc, expect, test_error_str=_("A parent and "
                        "child cannot be merged. To merge these people, you "
                        "must first break the relationship between them."))

    def test_child_parent_merge_no_father_swapped(self):
        """Merge two families where the phoenix family has no father and
        the father of the titanic family, which is the phoenix-father, is a
        child of the phoenix family."""
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            father = input_ctxt.xpathEval("//g:family[@handle='_f0000']/g:father")[0]
            father.unlinkNode()
            father.freeNode()
            person = input_ctxt.xpathEval("//g:person[@handle='_i0002']")[0]
            childof = person.newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0000')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0000']")[0]
            childref = family.newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0002')
            with CopiedDoc(self.basedoc) as expect:
                self.do_family_test('F0000', 'F0001', 'i0002', 'i0001',
                        self.basedoc, expect, test_error_str=_("A parent and "
                        "child cannot be merged. To merge these people, you "
                        "must first break the relationship between them."))

    def test_regular_merge(self):
        """Merge two families succesfully"""
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                persons = ctxt.xpathEval("//g:person")
                altname = persons[0].newChild(None, 'name', None)
                altname.newProp('alt', '1')
                altname.newProp('type', 'Birth Name')
                altname.newChild(None, 'surname', 'Person 2')
                attr = persons[0].newChild(None, 'attribute', None)
                attr.newProp('type', 'Merged Gramps ID')
                attr.newProp('value', 'I0002')
                parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                attr.addNextSibling(parentref) # restore order of elements
                persons[2].unlinkNode()
                persons[2].freeNode()
                altname = persons[1].newChild(None, 'name', None)
                altname.newProp('alt', '1')
                altname.newProp('type', 'Birth Name')
                altname.newChild(None, 'surname', 'Person 3')
                attr = persons[1].newChild(None, 'attribute', None)
                attr.newProp('type', 'Merged Gramps ID')
                attr.newProp('value', 'I0003')
                parentref = ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
                attr.addNextSibling(parentref) # restore order of elements
                persons[3].unlinkNode()
                persons[3].freeNode()
                family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                family.unlinkNode()
                family.freeNode()
                self.do_family_test('F0000', 'F0001', 'i0000', 'i0001', self.basedoc, expect)

    def test_father_swapped(self):
        "Merge two families where the phoenix-father is of the titanic family."
        with CopiedDoc(self.basedoc) as expect:
            with XpathContext(expect) as ctxt:
                persons = ctxt.xpathEval("//g:person")
                altname = persons[2].newChild(None, 'name', None)
                altname.newProp('alt', '1')
                altname.newProp('type', 'Birth Name')
                altname.newChild(None, 'surname', 'Person 0')
                attr = persons[2].newChild(None, 'attribute', None)
                attr.newProp('type', 'Merged Gramps ID')
                attr.newProp('value', 'I0000')
                parentref = ctxt.xpathEval("//g:person[@handle='_i0002']/g:parentin")[0]
                parentref.setProp('hlink', '_f0000')
                attr.addNextSibling(parentref) # restore order of elements
                persons[0].unlinkNode()
                persons[0].freeNode()
                altname = persons[1].newChild(None, 'name', None)
                altname.newProp('alt', '1')
                altname.newProp('type', 'Birth Name')
                altname.newChild(None, 'surname', 'Person 3')
                attr = persons[1].newChild(None, 'attribute', None)
                attr.newProp('type', 'Merged Gramps ID')
                attr.newProp('value', 'I0003')
                parentref = ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
                attr.addNextSibling(parentref) # restore order of elements
                persons[3].unlinkNode()
                persons[3].freeNode()
                family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                family.unlinkNode()
                family.freeNode()
                father = ctxt.xpathEval("//g:family[@handle='_f0000']/g:father")[0]
                father.setProp('hlink', '_i0002')
                self.do_family_test('F0000', 'F0001', 'i0002', 'i0001', self.basedoc, expect)

    #def test_mother_swapped(self):

    def test_no_father(self):
        """Merge two families, where one family has not father"""
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0002']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            father = input_ctxt.xpathEval("//g:family[@handle='_f0001']/g:father")[0]
            father.unlinkNode()
            father.freeNode()
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[1].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 3')
                    attr = persons[1].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0003')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[3].unlinkNode()
                    persons[3].freeNode()
                    family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                    family.unlinkNode()
                    family.freeNode()
                    self.do_family_test('F0000', 'F0001', 'i0000', 'i0001',
                                        self.basedoc, expect)

    def test_no_mother_swapped(self):
        """Merge two families where one family has no mother and the
        phoenix-mother is from the titanic family."""
        with XpathContext(self.basedoc) as input_ctxt:
            parentin = input_ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
            parentin.unlinkNode()
            parentin.freeNode()
            mother = input_ctxt.xpathEval("//g:family[@handle='_f0000']/g:mother")[0]
            mother.unlinkNode()
            mother.freeNode()
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    parentin = ctxt.xpathEval("//g:person[@handle='_i0003']/g:parentin")[0]
                    parentin.setProp('hlink', '_f0000')
                    family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                    family.unlinkNode()
                    family.freeNode()
                    family = ctxt.xpathEval("//g:family[@handle='_f0000']")[0]
                    mother = family.newChild(None, 'mother', None)
                    mother.newProp('hlink', '_i0003')
                    self.do_family_test('F0000', 'F0001', 'i0000', 'i0003',
                                        self.basedoc, expect)

    #def test_no_parents(self):

    def test_childref_notyet(self):
        """Merge two families with non-duplicate child references."""
        with XpathContext(self.basedoc) as input_ctxt:
            people = input_ctxt.xpathEval("//g:people")[0]
            person = people.newChild(None, 'person', None)
            person.newProp('handle', '_i0004')
            person.newProp('id', '_I0004')
            person.newChild(None, 'gender', 'M')
            name = person.newChild(None, 'name', None)
            name.newProp('type', 'Birth Name')
            name.newChild(None, 'surname', 'Person 4')
            childof = person.newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0001')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            childref = family.newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0004')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    altname = persons[1].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 3')
                    attr = persons[1].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0003')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[3].unlinkNode()
                    persons[3].freeNode()
                    family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                    family.unlinkNode()
                    family.freeNode()
                    childof = ctxt.xpathEval("//g:person[@handle='_i0004']/g:childof")[0]
                    childof.setProp('hlink', '_f0000')
                    family = ctxt.xpathEval("//g:family[@handle='_f0000']")[0]
                    childref = family.newChild(None, 'childref', None)
                    childref.newProp('hlink', '_i0004')
                    self.do_family_test('F0000', 'F0001', 'i0000', 'i0001',
                                        self.basedoc, expect)

    def test_childref_already(self):
        """Merge two families with duplicate child references."""
        with XpathContext(self.basedoc) as input_ctxt:
            people = input_ctxt.xpathEval("//g:people")[0]
            person = people.newChild(None, 'person', None)
            person.newProp('handle', '_i0004')
            person.newProp('id', '_I0004')
            person.newChild(None, 'gender', 'M')
            name = person.newChild(None, 'name', None)
            name.newProp('type', 'Birth Name')
            name.newChild(None, 'surname', 'Person 4')
            childof = person.newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0000')
            childof = person.newChild(None, 'childof', None)
            childof.newProp('hlink', '_f0001')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0000']")[0]
            childref = family.newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0004')
            family = input_ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
            childref = family.newChild(None, 'childref', None)
            childref.newProp('hlink', '_i0004')
            with CopiedDoc(self.basedoc) as expect:
                with XpathContext(expect) as ctxt:
                    persons = ctxt.xpathEval("//g:person")
                    altname = persons[0].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 2')
                    attr = persons[0].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0002')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[2].unlinkNode()
                    persons[2].freeNode()
                    altname = persons[1].newChild(None, 'name', None)
                    altname.newProp('alt', '1')
                    altname.newProp('type', 'Birth Name')
                    altname.newChild(None, 'surname', 'Person 3')
                    attr = persons[1].newChild(None, 'attribute', None)
                    attr.newProp('type', 'Merged Gramps ID')
                    attr.newProp('value', 'I0003')
                    parentref = ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
                    attr.addNextSibling(parentref) # restore order of elements
                    persons[3].unlinkNode()
                    persons[3].freeNode()
                    family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
                    family.unlinkNode()
                    family.freeNode()
                    childof = ctxt.xpathEval("//g:person[@handle='_i0004']/g:childof")[1]
                    childof.unlinkNode()
                    childof.freeNode()
                    self.do_family_test('F0000', 'F0001', 'i0000', 'i0001',
                                        self.basedoc, expect)

    # this test fails because the families get IDs F0001 and F0002!
    #def test_ldsord(self):
    #    """Merge two families where one person has a reference to the
    #    titanic family."""
    #    with XpathContext(self.basedoc) as input_ctxt:
    #        person = input_ctxt.xpathEval("//g:person[@handle='_i0000']")[0]
    #        ldsord = person.newChild(None, 'lds_ord', None)
    #        ldsord.newProp('type', 'sealed_to_parents')
    #        sealedto = ldsord.newChild(None, 'sealed_to', None)
    #        sealedto.newProp('hlink', '_f0001')
    #        parentin = input_ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
    #        ldsord.addNextSibling(parentin)
    #        with CopiedDoc(self.basedoc) as expect:
    #            with XpathContext(expect) as ctxt:
    #                persons = ctxt.xpathEval("//g:person")
    #                altname = persons[0].newChild(None, 'name', None)
    #                altname.newProp('alt', '1')
    #                altname.newProp('type', 'Birth Name')
    #                altname.newChild(None, 'surname', 'Person 2')
    #                ldsord = ctxt.xpathEval("//g:lds_ord")[0]
    #                altname.addNextSibling(ldsord) # restore order of elements
    #                attr = persons[0].newChild(None, 'attribute', None)
    #                attr.newProp('type', 'Merged Gramps ID')
    #                attr.newProp('value', 'I0002')
    #                parentref = ctxt.xpathEval("//g:person[@handle='_i0000']/g:parentin")[0]
    #                attr.addNextSibling(parentref) # restore order of elements
    #                persons[2].unlinkNode()
    #                persons[2].freeNode()
    #                altname = persons[1].newChild(None, 'name', None)
    #                altname.newProp('alt', '1')
    #                altname.newProp('type', 'Birth Name')
    #                altname.newChild(None, 'surname', 'Person 3')
    #                attr = persons[1].newChild(None, 'attribute', None)
    #                attr.newProp('type', 'Merged Gramps ID')
    #                attr.newProp('value', 'I0003')
    #                parentref = ctxt.xpathEval("//g:person[@handle='_i0001']/g:parentin")[0]
    #                attr.addNextSibling(parentref) # restore order of elements
    #                persons[3].unlinkNode()
    #                persons[3].freeNode()
    #                family = ctxt.xpathEval("//g:family[@handle='_f0001']")[0]
    #                family.unlinkNode()
    #                family.freeNode()
    #                sealedto = ctxt.xpathEval("//g:sealed_to")[0]
    #                sealedto.setProp('hlink', '_f0000')
    #                self.do_family_test('F0000', 'F0001', 'i0000', 'i0001',
    #                                    self.basedoc, expect)


if __name__ == "__main__":
    if not os.path.isdir(os.path.join(USER_PLUGINS, 'CliMerge')):
        print >> sys.stderr, 'This program needs the third party "CliMerge" plugin.'
        sys.exit(1)
    if not os.path.isdir(os.path.join(USER_PLUGINS, 'ExportRaw')):
        print >> sys.stderr, 'This program needs the third party "ExportRaw" plugin.'
        sys.exit(1)
    unittest.main()
