Repository: elliotchance/gedcom
Pull request #299 (2020-02-02 12:27:01)

Added "-no-duplicate-names"
Also provides RemoveDuplicateNamesFilter filter function, and fixes a bug with returning children from a filter function being mistakenly truncated.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/elliotchance/gedcom/299)
<!-- Reviewable:end -->


codecov-io: # [Codecov](https://codecov.io/gh/elliotchance/gedcom/pull/299?src=pr&el=h1) Report
> Merging [#299](https://codecov.io/gh/elliotchance/gedcom/pull/299?src=pr&el=desc) into [master](https://codecov.io/gh/elliotchance/gedcom/commit/33ed4b939dc6944dbede3c199f50de00d45fe17c?src=pr&el=desc) will **increase** coverage by `0.44%`.
> The diff coverage is `90.47%`.

[![Impacted file tree graph](https://codecov.io/gh/elliotchance/gedcom/pull/299/graphs/tree.svg?width=650&token=gtwWzggLwp&height=150&src=pr)](https://codecov.io/gh/elliotchance/gedcom/pull/299?src=pr&el=tree)

```diff
@@           Coverage Diff            @@
##           master   #299      +/-   ##
========================================
+ Coverage   71.56%    72%   +0.44%     
========================================
  Files         201    201              
  Lines        6094   6115      +21     
========================================
+ Hits         4361   4403      +42     
+ Misses       1610   1584      -26     
- Partials      123    128       +5
```


| [Impacted Files](https://codecov.io/gh/elliotchance/gedcom/pull/299?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [filter\_flags.go](https://codecov.io/gh/elliotchance/gedcom/pull/299/diff?src=pr&el=tree#diff-ZmlsdGVyX2ZsYWdzLmdv) | `36.76% <66.66%> (+36.76%)` | :arrow_up: |
| [filter.go](https://codecov.io/gh/elliotchance/gedcom/pull/299/diff?src=pr&el=tree#diff-ZmlsdGVyLmdv) | `95.6% <94.44%> (-0.29%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/elliotchance/gedcom/pull/299?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/elliotchance/gedcom/pull/299?src=pr&el=footer). Last update [33ed4b9...a4e39e8](https://codecov.io/gh/elliotchance/gedcom/pull/299?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/filter.go b/filter.go
index 3c7f520..d04d6e9 100644
--- a/filter.go
+++ b/filter.go
@@ -88,6 +88,10 @@ func filter(root Node, fn FilterFunction, entityMap entityMap) Node {
 				result.AddNode(newNode)
 			}
 		}
+	} else {
+		for _, child := range newRoot.Nodes() {
+			result.AddNode(child)
+		}
 	}
 
 	return result
@@ -212,3 +216,31 @@ func RemoveEmptyDeathTagFilter() FilterFunction {
 		return node, true
 	}
 }
+
+func RemoveDuplicateNamesFilter() FilterFunction {
+	return func(node Node) (Node, bool) {
+		if individual, ok := node.(*IndividualNode); ok {
+			newIndividual := newIndividualNode(individual.Document(),
+				individual.Pointer())
+			names := map[string]bool{}
+
+			for _, n := range individual.children {
+				if name, isName := n.(*NameNode); isName {
+					nameString := name.String()
+					if names[nameString] == true {
+						continue
+					}
+
+					names[nameString] = true
+				}
+				newIndividual.AddNode(n)
+			}
+
+			return newIndividual, false
+		}
+
+		// Individuals can only exist on the root level so there's no need to
+		// recurse.
+		return node, true
+	}
+}
diff --git a/filter_flags.go b/filter_flags.go
index 7dfe9ca..0dfe616 100644
--- a/filter_flags.go
+++ b/filter_flags.go
@@ -7,16 +7,17 @@ import (
 
 type FilterFlags struct {
 	// Specific exclusions.
-	NoEvents      bool
-	NoResidences  bool
-	NoPlaces      bool
-	NoSources     bool
-	NoMaps        bool
-	NoChanges     bool
-	NoObjects     bool
-	NoLabels      bool
-	NoCensuses    bool
-	NoEmptyDeaths bool
+	NoEvents         bool
+	NoResidences     bool
+	NoPlaces         bool
+	NoSources        bool
+	NoMaps           bool
+	NoChanges        bool
+	NoObjects        bool
+	NoLabels         bool
+	NoCensuses       bool
+	NoEmptyDeaths    bool
+	NoDuplicateNames bool
 
 	// Only vitals (name, birth, baptism, death and burial).
 	OnlyVitals bool
@@ -41,6 +42,7 @@ func (ff *FilterFlags) SetupCLI() {
 	flag.BoolVar(&ff.NoObjects, "no-objects", false, "Exclude objects.")
 	flag.BoolVar(&ff.NoLabels, "no-labels", false, "Exclude labels.")
 	flag.BoolVar(&ff.NoCensuses, "no-censuses", false, "Exclude censuses.")
+	flag.BoolVar(&ff.NoDuplicateNames, "no-duplicate-names", false, "Exclude names that are duplicates.")
 
 	flag.BoolVar(&ff.NoEmptyDeaths, "no-empty-deaths", false, util.CLIDescription(
 		`Remove death nodes (DEAT) that do not have children. This is caused by
@@ -103,6 +105,12 @@ func (ff *FilterFlags) FilterFunctions() []FilterFunction {
 		filters = append(filters, OfficialTagFilter())
 	}
 
+	// This must be before NameFormat because NameFormat may simplify/destroy
+	// NAME nodes.
+	if ff.NoDuplicateNames {
+		filters = append(filters, RemoveDuplicateNamesFilter())
+	}
+
 	if ff.NameFormat != "unmodified" {
 		format, _ := NewNameFormatByName(ff.NameFormat)
 		filters = append(filters, SimpleNameFilter(format))


Test patch:
diff --git a/filter_flags_test.go b/filter_flags_test.go
new file mode 100644
index 0000000..ee6641c
--- /dev/null
+++ b/filter_flags_test.go
@@ -0,0 +1,51 @@
+package gedcom_test
+
+import (
+	"github.com/elliotchance/gedcom"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestFilterFlags_Filter(t *testing.T) {
+	t.Run("NoDuplicateNames", func(t *testing.T) {
+		doc1 := gedcom.NewDocument()
+		doc1.AddIndividual("P1",
+			gedcom.NewNameNode("Bob /Smith/"),
+			gedcom.NewNameNode("Jane /Smith/"),
+			gedcom.NewNameNode("Bob /Smith/"))
+
+		doc2 := gedcom.NewDocument()
+		doc2.AddIndividual("P1",
+			gedcom.NewNameNode("Bob /Smith/"),
+			gedcom.NewNameNode("Jane /Smith/"))
+
+		ff := &gedcom.FilterFlags{
+			NoDuplicateNames: true,
+			NameFormat:       "unmodified",
+		}
+
+		assert.Equal(t, doc2.Nodes()[0].GEDCOMString(0),
+			ff.Filter(doc1.Nodes()[0]).GEDCOMString(0))
+	})
+
+	t.Run("NoDuplicateNamesWithModifiedNames", func(t *testing.T) {
+		doc1 := gedcom.NewDocument()
+		doc1.AddIndividual("P1",
+			gedcom.NewNameNode("Bob /Smith/"),
+			gedcom.NewNameNode("Jane /Smith/"),
+			gedcom.NewNameNode("Bob /Smith/"))
+
+		doc2 := gedcom.NewDocument()
+		doc2.AddIndividual("P1",
+			gedcom.NewNameNode("Bob /Smith/"),
+			gedcom.NewNameNode("Jane /Smith/"))
+
+		ff := &gedcom.FilterFlags{
+			NoDuplicateNames: true,
+			NameFormat:       string(gedcom.NameFormatGEDCOM),
+		}
+
+		assert.Equal(t, doc2.Nodes()[0].GEDCOMString(0),
+			ff.Filter(doc1.Nodes()[0]).GEDCOMString(0))
+	})
+}
diff --git a/filter_test.go b/filter_test.go
index f63a16d..881814a 100644
--- a/filter_test.go
+++ b/filter_test.go
@@ -33,6 +33,13 @@ func TestFilter(t *testing.T) {
 1 NAME Elliot /Chance/
 1 BIRT
 2 DATE 6 MAY 1989
+`,
+		},
+		{
+			filter: func(node gedcom.Node) (gedcom.Node, bool) {
+				return node.ShallowCopy(), false
+			},
+			expected: `0 @P1@ INDI
 `,
 		},
 		{
@@ -40,6 +47,22 @@ func TestFilter(t *testing.T) {
 				return node, false
 			},
 			expected: `0 @P1@ INDI
+1 NAME Elliot /Chance/
+1 BIRT
+2 DATE 6 MAY 1989
+`,
+		},
+		{
+			filter: func(node gedcom.Node) (gedcom.Node, bool) {
+				if node.Tag().Is(gedcom.TagIndividual) {
+					// false means it will not traverse children, since an
+					// individual can never be inside of another individual.
+					return node.ShallowCopy(), false
+				}
+
+				return nil, false
+			},
+			expected: `0 @P1@ INDI
 `,
 		},
 		{
@@ -53,6 +76,9 @@ func TestFilter(t *testing.T) {
 				return nil, false
 			},
 			expected: `0 @P1@ INDI
+1 NAME Elliot /Chance/
+1 BIRT
+2 DATE 6 MAY 1989
 `,
 		},
 		{