elliotchance/gedcom

View on GitHub
individual_node.go

Summary

Maintainability
D
1 day
Test Coverage
package gedcom

import (
    "fmt"
    "strings"
    "time"
)

// IndividualNode represents a person.
type IndividualNode struct {
    *simpleDocumentNode
    cachedFamilies, cachedSpouses bool
    families                      FamilyNodes
    spouses                       []*IndividualNode
    cachedUniqueIDs               *StringSet
}

// SpouseChildren connects a single spouse to a set of children. The children
// may appear under multiple spouses. This is only useful when in combination
// with an individual (that would be the other spouse).
//
// The spouse can be nil, indicating that the spouse it not known for the
// assigned children. You should not assume that you can also recover the other
// spouse from one of the keys in this map as the map is valid to be empty or to
// only contain a nil key.
type SpouseChildren map[*IndividualNode]ChildNodes

func newIndividualNode(document *Document, pointer string, children ...Node) *IndividualNode {
    return &IndividualNode{
        newSimpleDocumentNode(document, TagIndividual, "", pointer, children...),
        false, false, nil, nil, nil,
    }
}

// TODO: Needs tests
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Name() *NameNode {
    if node == nil {
        return nil
    }

    nameTag := First(NodesWithTag(node, TagName))
    if nameTag != nil {
        return nameTag.(*NameNode)
    }

    return nil
}

// If the node is nil the result will also be nil.
func (node *IndividualNode) Names() []*NameNode {
    if node == nil {
        return nil
    }

    nameTags := NodesWithTag(node, TagName)
    names := make([]*NameNode, len(nameTags))

    for i, name := range nameTags {
        names[i] = name.(*NameNode)
    }

    return names
}

// If the node is nil the result will be SexUnknown.
func (node *IndividualNode) Sex() *SexNode {
    n := First(NodesWithTag(node, TagSex))
    if IsNil(n) {
        return nil
    }

    return n.(*SexNode)
}

// TODO: needs tests
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Spouses() (spouses IndividualNodes) {
    if node == nil {
        return nil
    }

    if node.cachedSpouses {
        return node.spouses
    }

    defer func() {
        node.spouses = spouses
        node.cachedSpouses = true
    }()

    spouses = IndividualNodes{}

    for _, family := range node.document.Families() {
        husband := family.Husband()
        wife := family.Wife()

        // We only care about families that have both parties (otherwise there
        // is no spouse to add).
        if husband == nil || wife == nil {
            continue
        }

        if husband.IsIndividual(node) {
            spouses = append(spouses, wife.Individual())
        }

        if wife.IsIndividual(node) {
            spouses = append(spouses, husband.Individual())
        }
    }

    return spouses
}

// TODO: needs tests
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Families() (families FamilyNodes) {
    if node == nil {
        return nil
    }

    if node.cachedFamilies {
        return node.families
    }

    defer func() {
        node.families = families
        node.cachedFamilies = true
    }()

    families = FamilyNodes{}

    for _, family := range node.document.Families() {
        hasChild := family.HasChild(node)
        isHusband := family.Husband().IsIndividual(node)
        isWife := family.Wife().IsIndividual(node)

        if hasChild || isHusband || isWife {
            families = append(families, family)
        }
    }

    return families
}

// TODO: needs tests
func (node *IndividualNode) Is(individual *IndividualNode) bool {
    if node == nil {
        return false
    }

    if individual == nil {
        return false
    }

    leftPointer := node.Pointer()
    rightPointer := individual.Pointer()

    return leftPointer == rightPointer
}

// TODO: needs tests
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) FamilyWithSpouse(spouse *IndividualNode) *FamilyNode {
    if node == nil {
        return nil
    }

    for _, family := range node.document.Families() {
        a := family.Husband().IsIndividual(node) && family.Wife().IsIndividual(spouse)
        b := family.Wife().IsIndividual(node) && family.Husband().IsIndividual(spouse)

        if a || b {
            return family
        }
    }

    return nil
}

// TODO: needs tests
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) FamilyWithUnknownSpouse() *FamilyNode {
    if node == nil {
        return nil
    }

    for _, family := range node.document.Families() {
        a := family.Husband().IsIndividual(node) && family.Wife() == nil
        b := family.Wife().IsIndividual(node) && family.Husband() == nil

        if a || b {
            return family
        }
    }

    return nil
}

// IsLiving determines if the individual is living.
//
// If no death node exists, but the estimated birth date is found to be older
// than Document.MaxLivingAge then the individual will not be considered living.
//
// The default value for MaxLivingAge is 100 and can be modified on the document
// attached to this node. A MaxLivingAge of 0 means that it will only consider
// the individual to be not living if there is an explicit Death event.
//
// If there is no document attached DefaultMaxLivingAge will be used.
//
// If the node is nil the result will always be false.
func (node *IndividualNode) IsLiving() bool {
    if node == nil {
        return false
    }

    deaths := node.Deaths()
    if len(deaths) > 0 {
        return false
    }

    maxLivingAge := node.Document().MaxLivingAge
    if maxLivingAge == 0 {
        return true
    }

    nowYear := float64(time.Now().Year())
    birthDate, _ := node.EstimatedBirthDate()
    birthYear := Years(birthDate)
    age := nowYear - birthYear

    return birthYear == 0 || age <= maxLivingAge
}

// Births returns zero or more birth events for the individual.
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Births() (nodes []*BirthNode) {
    if node == nil {
        return nil
    }

    for _, n := range NodesWithTag(node, TagBirth) {
        nodes = append(nodes, n.(*BirthNode))
    }

    return
}

// Baptisms returns zero or more baptism events for the individual. The baptisms
// do not include LDS baptisms.
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Baptisms() []*BaptismNode {
    nodes := NodesWithTag(node, TagBaptism)

    return nodes.CastTo((*BaptismNode)(nil)).([]*BaptismNode)
}

// Deaths returns zero or more death events for the individual. It is common for
// individuals to not have a death event if the death date is not known. If you
// need to check if an individual is living you should use IsLiving().
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Deaths() []*DeathNode {
    nodes := NodesWithTag(node, TagDeath)

    return nodes.CastTo((*DeathNode)(nil)).([]*DeathNode)
}

// Burials returns zero or more burial events for the individual.
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Burials() []*BurialNode {
    nodes := NodesWithTag(node, TagBurial)

    return nodes.CastTo((*BurialNode)(nil)).([]*BurialNode)
}

// Parents returns the families for which this individual is a child. There may
// be zero or more parents for an individual. The families returned will all
// reference this individual as child. However the father, mother or both may
// not exist.
//
// It is also possible to have duplicate families. That is, families that have
// the same husband and wife combinations if these families are defined in the
// GEDCOM file.
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Parents() FamilyNodes {
    if node == nil {
        return nil
    }

    parents := FamilyNodes{}

    for _, family := range node.Families() {
        if family.HasChild(node) {
            parents = append(parents, family)
        }
    }

    return parents
}

// SpouseChildren maps the known spouses to their children. The spouse will be
// nil if the other parent is not known for some or all of the children.
// Children can appear under multiple spouses.
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) SpouseChildren() SpouseChildren {
    spouseChildren := SpouseChildren{}

    for _, family := range node.Families() {
        if !family.HasChild(node) {
            var spouse *IndividualNode

            switch {
            case family.Husband().IsIndividual(node):
                if wife := family.Wife(); wife != nil {
                    spouse = family.Wife().Individual()
                }

            case family.Wife().IsIndividual(node):
                if husband := family.Husband(); husband != nil {
                    spouse = husband.Individual()
                }
            }

            familyWithSpouse := node.FamilyWithSpouse(spouse)
            var children ChildNodes
            if familyWithSpouse != nil {
                children = familyWithSpouse.Children()
            }
            spouseChildren[spouse] = children

            // Find children with unknown spouse.
            unknownSpouseFamily := node.FamilyWithUnknownSpouse()
            if unknownSpouseFamily != nil {
                spouseChildren[nil] = unknownSpouseFamily.Children()
            }
        }
    }

    return spouseChildren
}

// LDSBaptisms returns zero or more LDS baptism events for the individual. These
// are not to be confused with Baptisms().
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) LDSBaptisms() Nodes {
    return NodesWithTag(node, TagLDSBaptism)
}

// EstimatedBirthDate attempts to find the exact or approximate birth date of an
// individual. It does this by looking at the births, baptisms and LDS baptisms.
// If any of them contain a date then the lowest date value is returned based on
// the Years() value which takes in account the full date range.
//
// This logic is loosely based off the idea that if the birth date is not known
// that a baptism usually happens when the individual is quite young (and
// therefore close to the their birth date).
//
// It is worth noting that since EstimatedBirthDate returns the lowest possible
// date that an exact birth date will be ignored if another event happens in a
// range before that. For example, if an individual has a birth date of
// "9 Feb 1983" but the Baptism was "9 Jan 1983" then the Baptism is returned.
// This data must be wrong in either case but EstimatedBirthDate cannot make a
// sensible decision in this case so it always returned the earliest date.
//
// EstimatedBirthDate is useful when comparing individuals where the exact dates
// are less important that attempting to serve approximate information for
// comparison. You almost certainly do not want to use the EstimatedBirthDate
// value for anything meaningful aside from comparisons.
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) EstimatedBirthDate() (date *DateNode, isBirthEvent bool) {
    births := Dates(NewNodes(node.Births())...)
    if len(births) > 0 {
        return births.Minimum(), true
    }

    baptisms := Dates(NewNodes(Compound(node.Baptisms(), node.LDSBaptisms()))...)
    if len(baptisms) > 0 {
        return baptisms.Minimum(), false
    }

    return nil, false
}

// EstimatedDeathDate attempts to find the exact or approximate death date of an
// individual. It does this by returning the earliest death date based on the
// value of Years(). If there are no death dates then it will attempt to return
// the minimum burial date.
//
// This logic is loosely based off the idea that if the death date is not known
// that a burial usually happens a short time after the death of the individual.
//
// It is worth noting that EstimatedDeathDate will always return a death date if
// one is present before falling back to a possibly more specific burial date.
// One example of this might be a death date that has a large range such as
// "1983 - 1993". The burial may be a much more specific date like "Apr 1985".
// This almost certainly indicates that the death date was around early 1985,
// however the larger death date range will still be returned.
//
// EstimatedDeathDate is useful when comparing individuals where the exact dates
// are less important that attempting to serve approximate information for
// comparison. You almost certainly do not want to use the EstimatedDeathDate
// value for anything meaningful aside from comparisons.
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) EstimatedDeathDate() (date *DateNode, isDeathEvent bool) {
    deaths := Dates(NewNodes(node.Deaths())...)
    if len(deaths) > 0 {
        return deaths.Minimum(), true
    }
    burials := Dates(NewNodes(node.Burials())...)
    if len(burials) > 0 {
        return burials.Minimum(), false
    }

    // TODO: It might be good to include the probates as well?

    return nil, false
}

// Similarity calculates how similar two individuals are. The returned value
// will be between 0.0 and 1.0 where 1.0 means an exact match.
//
// You should prefer SurroundingSimilarity, a more advanced checker that uses
// this function as part of it's ultimate analysis.
//
// The similarity is based off three equally weighted components, the
// individuals name, estimated birth and estimated death date and is calculated
// as follows:
//
//   similarity = (nameSimilarity + birthSimilarity + deathSimilarity) / 3.0
//
// Individual names are compared with the StringSimilarity function that does
// not consider the punctuation and extra spacing.
//
// An individual may have more than one name, if this is the case then each name
// is checked and the highest matching combination is used.
//
// The birth and death dates use the EstimatedBirthDate and EstimatedDeathDate
// functions respectively. These functions are allowed to make some estimates
// when critical information like the birth date does not exist so there is more
// data to include in the comparison.
//
// Both dates are compared with the DateNode.Similarity function, which also
// returns a value of 0.0 to 1.0. To put simply the dates must existing within
// an error margin (for example, 10 years in either direction). Higher scores
// are awarded to dates that are more relatively closer to each other on a
// parabola. See DateNode.Similarity for a full explanation of how it deals with
// approximate dates and date ranges.
//
// It is safe to use Similarity when one or both of the individuals are nil.
// This will always result in a 0.5. It is a 0.5 not because it is a partial
// match but that a positive or negative match cannot be determined. This is
// important when Similarity is used is more extensive similarity calculations
// as to not unnecessarily skew the results.
//
// The options.MaxYears allows the error margin on dates to be adjusted. See
// DefaultMaxYearsForSimilarity for more information.
func (node *IndividualNode) Similarity(other *IndividualNode, options SimilarityOptions) float64 {
    if node == nil || other == nil {
        return 0.5
    }

    // Compare the matrix of names.
    nameSimilarity := 0.0

    for _, name1 := range node.Names() {
        for _, name2 := range other.Names() {
            similarity := StringSimilarity(name1.String(), name2.String(),
                options.JaroBoostThreshold, options.JaroPrefixSize)

            if similarity > nameSimilarity {
                nameSimilarity = similarity
            }
        }
    }

    // Compare the dates.
    leftEstimatedBirthDate, _ := node.EstimatedBirthDate()
    rightEstimatedBirthDate, _ := other.EstimatedBirthDate()
    birthSimilarity := leftEstimatedBirthDate.
        Similarity(rightEstimatedBirthDate, options.MaxYears)

    leftEstimatedDeathDate, _ := node.EstimatedDeathDate()
    rightEstimatedDeathDate, _ := other.EstimatedDeathDate()
    deathSimilarity := leftEstimatedDeathDate.
        Similarity(rightEstimatedDeathDate, options.MaxYears)

    // Final calculation.
    nameSimilarityRatio := nameSimilarity * options.NameToDateRatio
    avgBirthDeathSimilarity := (birthSimilarity + deathSimilarity) / 2.0
    inverseRatio := 1.0 - options.NameToDateRatio

    return nameSimilarityRatio + avgBirthDeathSimilarity*inverseRatio
}

// SurroundingSimilarity is a more advanced version of Similarity.
// SurroundingSimilarity also takes into account the immediate surrounding
// family. That is the parents, spouses and children have separate metrics
// calculated so they can be interpreted differently or together.
//
// Checking for surrounding family is critical for calculating the similarity of
// individuals that would otherwise be considered the same because of similar
// names and dates in large family trees.
//
// SurroundingSimilarity returns a structure of the same name, but really it
// calculates four discreet similarities:
//
// 1. IndividualSimilarity: This is the same as Individual.Similarity().
//
// 2. ParentsSimilarity: The similarity of the fathers and mothers of the
// individual. Each missing parent will be given 0.5. If both parents are
// missing the parent similarity will also be 0.5.
//
// An individual can have zero or more pairs of parents, but only a single
// ParentsSimilarity is returned. The ParentsSimilarity is the highest value
// when each of the parents are compared with the other parents of the other
// individual.
//
// 3. SpousesSimilarity: The similarity of the spouses is compared with
// IndividualNodes.Similarity() which is designed to compare several individuals
// at once. It also handles comparing a different number of individuals on
// either side.
//
// 4. ChildrenSimilarity: Children are also compared with
// IndividualNodes.Similarity() but without respect to their parents (which in
// this case would be the current individual and likely one of their spouses).
// It is done this way as to not skew the results if any particular parent is
// unknown or the child is connected to a different spouse.
//
// The options.MaxYears allows the error margin on dates to be adjusted. See
// DefaultMaxYearsForSimilarity for more information.
//
// The options.MinimumSimilarity is used when comparing slices of individuals.
// In this case that means for the spouses and children. A higher value makes
// the matching more strict. See DefaultMinimumSimilarity for more information.
func (node *IndividualNode) SurroundingSimilarity(other *IndividualNode, options SimilarityOptions, forceFullCalculation bool) (s *SurroundingSimilarity) {
    // Individual, spouse and children similarity only needs to be calculated
    // once. The parents similarity will be calculated from the matrix below.
    individualSimilarity := node.Similarity(other, options)

    // Comparing individuals is extremely expensive because of the matrix of
    // comparisons and the individual comparisons themselves need to utilise a
    // lot of surrounding data.
    //
    // The individual similarity weight is by far the greatest, at 80% by
    // default. This means we can in most cases avoid calculating the parents,
    // spouses and children if we know that even in the perfect case the result
    // similarity would not be above the minimum threshold.
    //
    // Consider the formula:
    //
    //   (IndividualSimilarity * IndividualWeight) +
    //   (ParentsSimilarity * ParentsWeight) +
    //   (SpousesSimilarity * SpousesWeight) +
    //   (ChildrenSimilarity * ChildrenWeight) > MinimumWeight
    //
    // Isolating the individual similarity (which is the only thing we have
    // calculated):
    //
    //   (IndividualSimilarity * IndividualWeight) >
    //   MinimumWeight -
    //   (ParentsSimilarity * ParentsWeight) -
    //   (SpousesSimilarity * SpousesWeight) -
    //   (ChildrenSimilarity * ChildrenWeight)
    //
    // Now, lets assume that we have perfect matches (1.0) for all other
    // elements:
    //
    //   (IndividualSimilarity * IndividualWeight) >
    //   MinimumWeight - ParentsWeight - SpousesWeight - ChildrenWeight
    //
    // If that statement is not true, there is no need to proceed because the
    // individuals will never be considered a match given the MinimumWeight.
    //
    // A higher MinimumWeight means that less work will actually need to be done
    // because there will be less possible candidates.
    //
    // ghost:ignore
    if !forceFullCalculation && options.canSkipExtraProcessing(individualSimilarity) {
        return NewSurroundingSimilarity(0, 0, 0, 0)
    }

    spousesSimilarity := node.Spouses().Similarity(other.Spouses(), options)
    childrenSimilarity := node.Children().Individuals().
        Similarity(other.Children().Individuals(), options)

    s = NewSurroundingSimilarity(
        0.0, // Parents. Filled in later.
        individualSimilarity,
        spousesSimilarity,
        childrenSimilarity,
    )
    s.Options = options

    didFindParents := false
    for _, parents1 := range node.Parents() {
        for _, parents2 := range other.Parents() {
            didFindParents = true

            // depth of 0 means only the wife/husband is compared.
            similarity := parents1.Similarity(parents2, 0, options)

            if similarity > s.ParentsSimilarity {
                s.ParentsSimilarity = similarity
            }
        }
    }

    if !didFindParents {
        s.ParentsSimilarity = 0.5
    }

    return
}

// TODO: Needs tests
//
// If the node is nil the result will also be nil.
func (node *IndividualNode) Children() ChildNodes {
    children := ChildNodes{}

    for _, family := range node.Families() {
        if !family.HasChild(node) {
            children = append(children, family.Children()...)
        }
    }

    return children
}

// AllEvents returns zero or more events of any kind for the individual.
//
// This is not to be confused with the EventNode.
func (node *IndividualNode) AllEvents() (nodes Nodes) {
    for _, n := range node.Nodes() {
        if n.Tag().IsEvent() {
            nodes = append(nodes, n)
        }
    }

    return
}

// Birth returns the first values for the date and place of the birth events.
func (node *IndividualNode) Birth() (*DateNode, *PlaceNode) {
    birthNodes := Compound(node.Births())

    return DateAndPlace(birthNodes...)
}

// Death returns the first values for the date and place of the death events.
func (node *IndividualNode) Death() (*DateNode, *PlaceNode) {
    deathNodes := Compound(node.Deaths())

    return DateAndPlace(deathNodes...)
}

// Baptism returns the first values for the date and place of the baptism
// events.
func (node *IndividualNode) Baptism() (*DateNode, *PlaceNode) {
    baptismNodes := Compound(node.Baptisms())

    return DateAndPlace(baptismNodes...)
}

// Burial returns the first values for the date and place of the burial events.
func (node *IndividualNode) Burial() (*DateNode, *PlaceNode) {
    burialNodes := Compound(node.Burials())

    return DateAndPlace(burialNodes...)
}

// Age returns the best estimate minimum and maximum age of the individual if
// they are still living or their approximate age range at the time of their
// death.
//
// Age will use EstimatedBirthDate which will allow it to be more resilient if a
// birth date is strictly missing but this means that the value returned from
// Age may not always be exact. See IsEstimate and other fields returned in
// either Age return values.
//
// If the birth or death date is a range, such as "Between 1945 and 1947" the
// minimum and maximum possible ages will be returned.
//
// If the birth or death date is not an exact value then the ages will be
// estimates. See Age.IsEstimate.
//
// If no estimated birth date can be determined then Age.IsKnown will be false.
//
// IsLiving will be used to determine if the individual is still living which
// includes a maximum possible age when no death elements exist. However, in the
// case that the individual is not living then EstimatedDeathDate will be used
// to try and estimate the age at the time of death instead of simply using the
// maximum possible age (which is 100 by default).
func (node *IndividualNode) Age() (Age, Age) {
    now := NewDateRangeWithNow()
    startAge, endAge := node.ageAt(now)

    // Unlike AgeAt, we always want to trim back to the death date.
    if startAge.Constraint == AgeConstraintAfterDeath {
        estimatedBirthDate, _ := node.EstimatedBirthDate()
        estimatedDeathDate, _ := node.EstimatedDeathDate()
        ageInYears := estimatedDeathDate.Years() - estimatedBirthDate.Years()

        startAge = NewAgeWithYears(ageInYears, false, AgeConstraintAfterDeath)
        endAge = NewAgeWithYears(ageInYears, false, AgeConstraintAfterDeath)
    }

    return startAge, endAge
}

// AgeAt follows the same logic as Age but uses an event as the comparison
// instead of the current time.
//
// If there is more than one date associated with the event or a date contains a
// range the minimum and maximum values will be used to return the full range.
func (node *IndividualNode) AgeAt(event Node) (Age, Age) {
    dates := Dates(event).StripZero()

    if len(dates) > 0 {
        dateRange := dates.Range()

        return node.ageAt(dateRange)
    }

    // We cannot determine the date of the event so we would not know what age
    // they were at the time.
    return NewUnknownAge(), NewUnknownAge()
}

func (node *IndividualNode) ageAt(at DateRange) (Age, Age) {
    estimatedBirthDate, isBirthEvent := node.EstimatedBirthDate()

    // If we have no idea when they are born we cannot proceed with any age
    // estimate.
    if !estimatedBirthDate.IsValid() {
        return NewUnknownAge(), NewUnknownAge()
    }

    estimatedDeathDate, isDeathEvent := node.EstimatedDeathDate()
    birthRange := estimatedBirthDate.DateRange()
    startAge, endAge := at.Sub(birthRange).Age()

    // There are some cases where the endAge can be before the startAge in some
    // combinations of constraints. If this is the case we swap them to make the
    // output more sensible.
    if startAge.IsAfter(endAge) {
        startAge, endAge = endAge, startAge
    }

    isEstimate := !isBirthEvent || !isDeathEvent ||
        !birthRange.IsExact() || !at.IsExact()
    startAge.IsEstimate = isEstimate
    endAge.IsEstimate = isEstimate

    switch {
    case at.IsBefore(estimatedBirthDate.DateRange()):
        startAge.Constraint = AgeConstraintBeforeBirth
        endAge.Constraint = AgeConstraintBeforeBirth

    case at.IsAfter(estimatedDeathDate.DateRange()) && estimatedDeathDate != nil:
        startAge.Constraint = AgeConstraintAfterDeath
        endAge.Constraint = AgeConstraintAfterDeath

    default:
        startAge.Constraint = AgeConstraintLiving
        endAge.Constraint = AgeConstraintLiving
    }

    return startAge, endAge
}

// String returns a human-readable representation of the individual like:
//
//   (no name) (b. Aft. 1983)
//   Bob Smith (b. 1943)
//   John Chance
//   Jane Doe (b. 3 Apr 1923, bur. Abt. 1943)
//
// Ideally it will use birth (b.) and death (d.) if available. However, it will
// fall back to the baptism (bap.) or burial (bur.) respectively.
func (node *IndividualNode) String() string {
    name := String(node.Name())
    if name == "" {
        name = "(no name)"
    }

    dateParts := []string{}

    if birth, _ := node.Birth(); birth != nil && birth.DateRange().IsValid() {
        dateParts = append(dateParts, fmt.Sprintf("b. %s", birth.String()))
    } else if baptism, _ := node.Baptism(); baptism != nil && baptism.DateRange().IsValid() {
        dateParts = append(dateParts, fmt.Sprintf("bap. %s", baptism.String()))
    }

    if death, _ := node.Death(); death != nil && death.DateRange().IsValid() {
        dateParts = append(dateParts, fmt.Sprintf("d. %s", death.String()))
    } else if burial, _ := node.Burial(); burial != nil && burial.DateRange().IsValid() {
        dateParts = append(dateParts, fmt.Sprintf("bur. %s", burial.String()))
    }

    if len(dateParts) == 0 {
        return name
    }

    return fmt.Sprintf("%s (%s)", name, strings.Join(dateParts, ", "))
}

func (node *IndividualNode) FamilySearchIDs() (nodes []*FamilySearchIDNode) {
    if node == nil {
        return nil
    }

    for _, tag := range FamilySearchIDNodeTags() {
        for _, n := range NodesWithTag(node, tag) {
            nodes = append(nodes, n.(*FamilySearchIDNode))
        }
    }

    return
}

func (node *IndividualNode) UniqueIDs() (nodes []*UniqueIDNode) {
    if node == nil {
        return nil
    }

    for _, n := range NodesWithTag(node, UnofficialTagUniqueID) {
        nodes = append(nodes, n.(*UniqueIDNode))
    }

    return
}

// UniqueIdentifiers returns any strings that can be used to uniquely this
// individual in this document, but also between documents. For this reason the
// individual pointer is not included.
//
// It's quite possible to receive an empty set, but it could also contain
// commonly unique identifiers such as the FamilySearch ID or UUID generated by
// some applications.
func (node *IndividualNode) UniqueIdentifiers() *StringSet {
    if node.cachedUniqueIDs == nil {
        node.cachedUniqueIDs = NewStringSet()

        for _, id := range node.UniqueIDs() {
            if uuid, err := id.UUID(); err == nil {
                node.cachedUniqueIDs.Add(uuid.String())
            }
        }

        for _, id := range node.FamilySearchIDs() {
            node.cachedUniqueIDs.Add(id.String())
        }
    }

    return node.cachedUniqueIDs
}

func (node *IndividualNode) resetCache() {
    node.cachedFamilies = false
    node.cachedSpouses = false
    node.families = nil
    node.spouses = nil
    node.cachedUniqueIDs = nil
}

func (node *IndividualNode) AddName(name string) *IndividualNode {
    node.AddNode(NewNameNode(name))

    return node
}

func (node *IndividualNode) AddBirthDate(birthDate string) *IndividualNode {
    existingBirth := First(node.Births())
    if existingBirth == nil {
        existingBirth = NewBirthNode("")
        node.AddNode(existingBirth)
    }

    existingBirth.AddNode(NewDateNode(birthDate))

    return node
}

// SetSex adds or replaces tge gender of an individual. You should use one of
// the SexMale, SexFemale or SexUnknown constants.
func (node *IndividualNode) SetSex(sex string) *IndividualNode {
    existingSex := First(NodesWithTag(node, TagSex))
    if existingSex == nil {
        node.AddNode(NewNode(TagSex, string(sex), ""))
    } else {
        existingSex.RawSimpleNode().value = string(sex)
    }

    return node
}

func (node *IndividualNode) AddBurialDate(burialDate string) *IndividualNode {
    existingBurial := First(node.Burials())
    if existingBurial == nil {
        existingBurial = NewBurialNode("")
        node.AddNode(existingBurial)
    }

    existingBurial.AddNode(NewDateNode(burialDate))

    return node
}

func (node *IndividualNode) AddBaptismDate(baptismDate string) *IndividualNode {
    existingBaptism := First(node.Baptisms())
    if existingBaptism == nil {
        existingBaptism = NewBaptismNode("")
        node.AddNode(existingBaptism)
    }

    existingBaptism.AddNode(NewDateNode(baptismDate))

    return node
}

type eventAndDate struct {
    Event Node
    Date  *DateNode
}

func (node *IndividualNode) incorrectEventOrderWarnings() (warnings Warnings) {
    // Event order describes the boundaries of groups of events. That is to say
    // that any baptism or LDS baptism events must be after a birth event but
    // also much be before the any death event.
    eventOrder := []*struct {
        Tags   []Tag
        Events []eventAndDate
    }{
        {
            Tags: []Tag{TagBirth},
        },
        {
            Tags: []Tag{TagBaptism, TagLDSBaptism},
        },
        {
            Tags: []Tag{TagDeath},
        },
        {
            Tags: []Tag{TagBurial},
        },
    }

    // Collect all of the dates. This just makes it easier to compare later.
    // Each of the dates found in events are partnered with the original event
    // as we will need both if it turns into a warning.
    for _, group := range eventOrder {
        for _, tag := range group.Tags {
            nodes := NodesWithTag(node, tag)
            for _, node := range nodes {
                for _, date := range NodesWithTag(node, TagDate) {
                    group.Events = append(group.Events, eventAndDate{
                        Event: node,
                        Date:  date.(*DateNode),
                    })
                }
            }
        }
    }

    // Check the order. This is an iterative approach where each group is
    // compared with all of the events that proceed it.
    for i, group := range eventOrder {
        for _, event := range group.Events {
            for _, futureGroup := range eventOrder[i+1:] {
                for _, futureEvent := range futureGroup.Events {
                    if event.Date.IsValid() &&
                        futureEvent.Date.IsValid() {
                        comparison := futureEvent.Date.DateRange().
                            Compare(event.Date.DateRange())

                        if comparison == DateRangeComparisonEntirelyBefore {
                            warning := NewIncorrectEventOrderWarning(
                                futureEvent.Event, futureEvent.Date.DateRange(),
                                event.Event, event.Date.DateRange(),
                            )
                            warning.SetContext(WarningContext{
                                Individual: node,
                            })
                            warnings = append(warnings, warning)
                        }
                    }
                }
            }
        }
    }

    return
}

func (node *IndividualNode) tooOldWarnings() (warnings Warnings) {
    estimatedDeathDate, _ := node.EstimatedDeathDate()
    _, max := node.Age()
    if max.Years() > DefaultMaxLivingAge && estimatedDeathDate != nil {
        warnings = Warnings{
            NewIndividualTooOldWarning(node, max.Years()),
        }
    }

    return
}

func (node *IndividualNode) multipleSexesWarnings() Warnings {
    sexes := castNodesWithTag(node, TagSex, (*SexNode)(nil)).([]*SexNode)
    if len(sexes) > 1 {
        return Warnings{
            NewMultipleSexesWarning(node, sexes),
        }
    }

    return nil
}

func (node *IndividualNode) Warnings() (warnings Warnings) {
    warnings = append(warnings, node.incorrectEventOrderWarnings()...)
    warnings = append(warnings, node.tooOldWarnings()...)
    warnings = append(warnings, node.multipleSexesWarnings()...)

    return
}

func (node *IndividualNode) AddDeathDate(deathDate string) *IndividualNode {
    existingDeath := First(node.Deaths())
    if existingDeath == nil {
        existingDeath = NewDeathNode("")
        node.AddNode(existingDeath)
    }

    existingDeath.AddNode(NewDateNode(deathDate))

    return node
}