-
Notifications
You must be signed in to change notification settings - Fork 21
/
nodes.go
169 lines (139 loc) · 3.91 KB
/
nodes.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package gedcom
import (
"reflect"
"sync"
)
type Nodes []Node
// nodeCache is used by NodesWithTag. Even though the lookup of child tags are
// fairly inexpensive it happens a lot and its common for the same paths to be
// looked up many time. Especially when doing larger task like comparing GEDCOM
// files.
var nodeCache = &sync.Map{} // map[Node]map[Tag]Nodes{}
func NewNodes(ns interface{}) (nodes Nodes) {
v := reflect.ValueOf(ns)
for i := 0; i < v.Len(); i++ {
nodes = append(nodes, v.Index(i).Interface().(Node))
}
return
}
// NodesWithTag returns the zero or more nodes that have a specific GEDCOM tag.
// If the provided node is nil then an empty slice will always be returned.
//
// If the node is nil the result will also be nil.
func NodesWithTag(node Node, tag Tag) (result Nodes) {
if v1, ok1 := nodeCache.Load(node); ok1 {
if v2, ok2 := v1.(*sync.Map).Load(tag); ok2 {
return v2.(Nodes)
}
}
defer func() {
if v1, ok := nodeCache.Load(node); ok {
v1.(*sync.Map).Store(tag, result)
} else {
nodeCache.Store(node, &sync.Map{})
}
}()
if IsNil(node) {
return nil
}
nodes := Nodes{}
n := node.Nodes()
for _, node := range n {
if node.Tag().Is(tag) {
nodes = append(nodes, node)
}
}
return nodes
}
// NodesWithTagPath return all of the nodes that have an exact tag path. The
// number of nodes returned can be zero and tag must match the tag path
// completely and exactly.
//
// birthPlaces := NodesWithTagPath(individual, TagBirth, TagPlace)
//
// If the node is nil the result will also be nil.
func NodesWithTagPath(node Node, tagPath ...Tag) Nodes {
if IsNil(node) {
return nil
}
if len(tagPath) == 0 {
return Nodes{}
}
return nodesWithTagPath(node, tagPath...)
}
func nodesWithTagPath(node Node, tagPath ...Tag) Nodes {
if len(tagPath) == 0 {
return Nodes{node}
}
matches := Nodes{}
for _, next := range NodesWithTag(node, tagPath[0]) {
matches = append(matches, nodesWithTagPath(next, tagPath[1:]...)...)
}
return matches
}
// HasNestedNode checks if node contains lookingFor at any depth. If node and
// lookingFor are the same false is returned. If either node or lookingFor is
// nil then false is always returned.
//
// Nodes are matched by reference, not value so nodes that represent exactly the
// same value will not be considered equal.
func HasNestedNode(node Node, lookingFor Node) bool {
if node == nil || lookingFor == nil {
return false
}
for _, node := range node.Nodes() {
if node == lookingFor || HasNestedNode(node, lookingFor) {
return true
}
}
return false
}
// CastTo creates a slice of a more specific node type.
//
// All Nodes must be the same type and the same as the provided t.
func (nodes Nodes) CastTo(t interface{}) interface{} {
size := len(nodes)
nodeType := reflect.TypeOf(t)
sliceType := reflect.SliceOf(nodeType)
slice := reflect.MakeSlice(sliceType, size, size)
for i, node := range nodes {
value := reflect.ValueOf(node)
slice.Index(i).Set(value)
}
return slice.Interface()
}
func castNodesWithTag(node Node, tag Tag, t interface{}) interface{} {
return NodesWithTag(node, tag).CastTo(t)
}
func DeleteNodesWithTag(node Node, tag Tag) {
for _, n := range node.Nodes() {
if n.Tag().Is(tag) {
node.DeleteNode(n)
}
}
}
// FlattenAll works as Flatten with multiple inputs that are returned as a
// single slice.
//
// If any of the nodes are nil they will be ignored.
//
// The document parameter is required for copying some nodes like individuals
// that need a document context. These new nodes will be attached to the
// provided document.
func (nodes Nodes) FlattenAll(document *Document, result Nodes) {
for _, node := range nodes {
if IsNil(node) {
continue
}
result = append(result, Flatten(document, node)...)
}
return
}
func (nodes Nodes) deleteNode(n Node) (Nodes, bool) {
for i, node2 := range nodes {
if node2 == n {
return append(nodes[:i], nodes[i+1:]...), true
}
}
return nodes, false
}