elliotchance/gedcom

View on GitHub
family_node.go

Summary

Maintainability
C
1 day
Test Coverage
package gedcom

import (
    "fmt"
    "time"
)

// FamilyNode represents a family.
type FamilyNode struct {
    *simpleDocumentNode
    cachedHusband, cachedWife bool
    husband                   *HusbandNode
    wife                      *WifeNode
}

func newFamilyNode(document *Document, pointer string, children ...Node) *FamilyNode {
    return &FamilyNode{
        newSimpleDocumentNode(document, TagFamily, "", pointer, children...),
        false, false, nil, nil,
    }
}

// If the node is nil the result will also be nil.
func (node *FamilyNode) Husband() (husband *HusbandNode) {
    if node == nil {
        return nil
    }

    if node.cachedHusband {
        return node.husband
    }

    defer func() {
        node.husband = husband
        node.cachedHusband = true
    }()

    possibleHusband := First(NodesWithTag(node, TagHusband))

    if IsNil(possibleHusband) {
        return nil
    }

    return possibleHusband.(*HusbandNode)
}

// If the node is nil the result will also be nil.
func (node *FamilyNode) Wife() (wife *WifeNode) {
    if node == nil {
        return nil
    }

    if node.cachedWife {
        return node.wife
    }

    defer func() {
        node.wife = wife
        node.cachedWife = true
    }()

    possibleWife := First(NodesWithTag(node, TagWife))

    if IsNil(possibleWife) {
        return nil
    }

    return possibleWife.(*WifeNode)
}

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

    children := ChildNodes{}

    for _, n := range NodesWithTag(node, TagChild) {
        children = append(children, n.(*ChildNode))
    }

    return children
}

// TODO: Needs tests
//
// If the node is nil the result will also be nil.
func (node *FamilyNode) HasChild(individual *IndividualNode) bool {
    if node == nil {
        return false
    }

    for _, n := range NodesWithTag(node, TagChild) {
        if n.Value() == "@"+individual.Pointer()+"@" {
            return true
        }
    }

    return false
}

// Similarity calculates the similarity between two families.
//
// The depth controls how many generations should be compared. A depth of 0 will
// only compare the husband/wife and not take into account any children. At the
// moment only a depth of 0 is supported. Any other depth will raise panic.
//
// The options.MaxYears allows the error margin on dates to be adjusted. See
// DefaultMaxYearsForSimilarity for more information.
func (node *FamilyNode) Similarity(other *FamilyNode, depth int, options SimilarityOptions) float64 {
    if depth != 0 {
        panic("depth can only be 0")
    }

    // It does not matter if any of the partners are nil, Similarity will handle
    // these gracefully.
    husband := node.Husband().Similarity(other.Husband(), options)
    wife := node.Wife().Similarity(other.Wife(), options)

    return (husband + wife) / 2
}

func (node *FamilyNode) addChild(value string) *ChildNode {
    n := newChildNode(node, value)
    node.AddNode(n)

    return n
}

func (node *FamilyNode) AddChild(individual *IndividualNode) *ChildNode {
    n := newChildNodeWithIndividual(node, individual)
    node.AddNode(n)

    return n
}

func (node *FamilyNode) SetHusband(individual *IndividualNode) *FamilyNode {
    if IsNil(individual) {
        husband := node.Husband().Individual()
        if IsNil(husband) {
            return node
        }
        nodes := husband.Nodes()
        
        for _, subNode := range nodes {
            if subNode.Tag() == TagFamilySpouse && subNode.Value() == node.Identifier() {
                husband.DeleteNode(subNode)
            }
        }
        
        DeleteNodesWithTag(node, TagHusband)
        node.husband = nil
        node.cachedHusband = true
        return node
    }
    
    n := NewNode(TagFamilySpouse, node.Identifier(), "")
    individual.AddNode(n)

    return node.SetHusbandPointer(individual.Pointer())
}

func (node *FamilyNode) SetWife(individual *IndividualNode) *FamilyNode {
    if IsNil(individual) {
        wife := node.Wife().Individual()
        if IsNil(wife) {
            return node
        }
        nodes := wife.Nodes()

        for _, subNode := range nodes {
            if subNode.Tag() == TagFamilySpouse && subNode.Value() == node.Identifier() {
                wife.DeleteNode(subNode)
            }
        }
        
        DeleteNodesWithTag(node, TagWife)
        node.wife = nil
        node.cachedWife = true
        return node
    }

    n := NewNode(TagFamilySpouse, node.Identifier(), "")
    individual.AddNode(n)
    
    return node.SetWifePointer(individual.Pointer())
}

func (node *FamilyNode) SetWifePointer(pointer string) *FamilyNode {
    wife := node.Wife()
    value := fmt.Sprintf("@%s@", pointer)
    if wife != nil {
        wife.value = value
    }

    node.AddNode(newNode(nil, node, TagWife, value, ""))
    node.cachedWife = false

    return node
}

func (node *FamilyNode) SetHusbandPointer(pointer string) *FamilyNode {
    husband := node.Husband()
    value := fmt.Sprintf("@%s@", pointer)
    if husband != nil {
        husband.value = value
    }

    husbandNode := newNode(nil, node, TagHusband, value, "")
    node.AddNode(husbandNode)
    node.cachedHusband = false

    return node
}

func (node *FamilyNode) resetCache() {
    node.cachedHusband = false
    node.cachedWife = false
    node.husband = nil
    node.wife = nil
}

func (node *FamilyNode) childrenBornBeforeParentsWarnings() (warnings Warnings) {
    fatherBirth, _ := node.Husband().Individual().Birth()
    motherBirth, _ := node.Wife().Individual().Birth()

    for _, child := range node.Children() {
        childBirth, _ := child.Individual().Birth()
        if !childBirth.IsValid() {
            continue
        }

        if fatherBirth.IsValid() && childBirth.IsBefore(fatherBirth) {
            warning := NewChildBornBeforeParentWarning(
                node.Husband().Individual(),
                child,
            )
            warnings = append(warnings, warning)
        }

        if motherBirth.IsValid() && childBirth.IsBefore(motherBirth) {
            warning := NewChildBornBeforeParentWarning(
                node.Wife().Individual(),
                child,
            )
            warnings = append(warnings, warning)
        }
    }

    return
}

func (node *FamilyNode) siblingsBornTooCloseWarnings() (warnings Warnings) {
    pairs := IndividualNodePairs{}
    nineMonths := time.Duration(274 * 24 * time.Hour)
    twoDays := time.Duration(2 * 24 * time.Hour)

    for _, child1 := range node.Children() {
        child1Birth, _ := child1.Individual().Birth()

        // If the date range is greater than 9 months we do not have enough
        // accuracy, so bail out.
        if child1Birth.DateRange().Duration().Duration >= nineMonths {
            continue
        }

        for _, child2 := range node.Children() {
            // Exclude matching siblings to themselves. Technically we do not
            // need to do this check because children born on the same day would
            // be considered twins. However, its better to have it here for
            // completeness.
            if child1.Individual().Is(child2.Individual()) {
                continue
            }

            child2Birth, _ := child2.Individual().Birth()
            min, max, err := child1Birth.Sub(child2Birth)
            if err != nil {
                continue
            }

            // If the date range is greater than 9 months we do not have enough
            // accuracy, so bail out.
            if child2Birth.DateRange().Duration().Duration >= nineMonths {
                continue
            }

            // Twins or greater multiples may be born in the same day. We allow
            // for two days to compensate for rounding. Also it's possible for
            // multiple children to be born on either side of the midnight
            // barrier.
            if min.Duration < twoDays {
                continue
            }

            if min.Duration < nineMonths || max.Duration < nineMonths {
                pair := &IndividualNodePair{
                    Left:  child1.Individual(),
                    Right: child2.Individual(),
                }
                if !pairs.Has(pair) {
                    warning := NewSiblingsBornTooCloseWarning(
                        child1,
                        child2,
                    )
                    warnings = append(warnings, warning)

                    pairs = append(pairs, pair)
                }
            }
        }
    }

    return
}

func (node *FamilyNode) appendMarriedOutOfRange(warnings Warnings, age Age, spouse *IndividualNode) Warnings {
    if age.IsKnown && age.Years() < DefaultMinMarriageAge {
        warning := NewMarriedOutOfRangeWarning(
            node,
            spouse,
            age.Years(),
            "young",
        )
        warnings = append(warnings, warning)
    }

    if age.Years() > DefaultMaxMarriageAge {
        warning := NewMarriedOutOfRangeWarning(
            node,
            spouse,
            age.Years(),
            "old",
        )
        warnings = append(warnings, warning)
    }

    return warnings
}

func (node *FamilyNode) marriedOutOfRange() (warnings Warnings) {
    marriages := NodesWithTag(node, TagMarriage)

    for _, marriage := range marriages {
        if husband := node.Husband().Individual(); husband != nil {
            _, maxAge := husband.AgeAt(marriage)
            warnings = node.appendMarriedOutOfRange(warnings, maxAge, husband)
        }

        if wife := node.Wife().Individual(); wife != nil {
            _, maxAge := wife.AgeAt(marriage)
            warnings = node.appendMarriedOutOfRange(warnings, maxAge, wife)
        }
    }

    return
}

func (node *FamilyNode) inversePartnerWarnings() (warnings Warnings) {
    husband := node.Husband().Individual()
    wife := node.Wife().Individual()

    // We only consider the case when both spouses exist, have sexes and they
    // are exactly opposites. We do not want to catch same sex partnerships, of
    // which GEDCOM has no reasonable way to encode this.
    switch {
    case husband.Sex().IsFemale() && wife.Sex().IsMale():
        warning := NewInverseSpousesWarning(node, husband, wife)
        warnings = append(warnings, warning)
    }

    return
}

func (node *FamilyNode) Warnings() (warnings Warnings) {
    warnings = append(warnings, node.childrenBornBeforeParentsWarnings()...)
    warnings = append(warnings, node.siblingsBornTooCloseWarnings()...)
    warnings = append(warnings, node.marriedOutOfRange()...)
    warnings = append(warnings, node.inversePartnerWarnings()...)

    return
}

func (node *FamilyNode) String() string {
    symbol := "—"

    switch {
    case len(NodesWithTag(node, TagDivorce)) > 0:
        symbol = "⚮"

    case len(NodesWithTag(node, TagMarriage)) > 0:
        symbol = "⚭"
    }

    return fmt.Sprintf("%s %s %s", node.Husband().String(),
        symbol, node.Wife().String())
}