-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathdocument.go
368 lines (296 loc) · 8.66 KB
/
document.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
// Traversing a Document
//
// On top of the raw document is a powerful API that takes care of the complex
// traversing of the Document. Here is a simple example:
//
// for _, individual := range document.Individuals() {
// fmt.Println(individual.Name().String())
// }
//
// Some of the nodes in a GEDCOM file have been replaced with more function rich
// types, such as names, dates, families and more.
package gedcom
import (
"bytes"
"os"
"strings"
"sync"
)
const (
// DefaultMaxLivingAge is used when creating a new document. See
// Document.MaxLivingAge for a full description.
DefaultMaxLivingAge = 100.0
// DefaultMaxMarriageAge is the default age in which a spouse will begin to
// produce MarriedTooOldWarnings.
DefaultMaxMarriageAge = 100.0
// DefaultMinMarriageAge is the default minimum age that a spouse is allowed
// to be married.
DefaultMinMarriageAge = 16.0
)
// Document represents a whole GEDCOM document. It is possible for a
// Document to contain zero Nodes, this means the GEDCOM file was empty. It
// may also (and usually) contain several Nodes.
//
// You should not instantiate a Document yourself because there are sensible
// defaults and cache that need to be setup. Use one of the NewDocument
// constructors instead.
type Document struct {
// HasBOM controls if the encoded stream will start with the Byte Order
// Mark.
//
// This is not recommended by the UTF-8 standard and many applications will
// have problems reading the data. However, streams that were decoded
// containing the BOM will retain it so that the re-encoded stream is as
// compatible and similar to the original stream as possible.
//
// Also see Decoder.consumeOptionalBOM().
HasBOM bool
// MaxLivingAge is used by Individual.IsLiving to determine if an individual
// without a DeathNode should be considered living.
//
// 100 is chosen as a reasonable default. If you set it to 0 then an
// individual will never be considered dead without a DeathNode.
MaxLivingAge float64
// nodes is private because we need to track changes.
nodes Nodes
// pointerCache is setup once when the document is created.
pointerCache sync.Map // map[string]Node
families FamilyNodes
}
// String will render the entire GEDCOM document.
//
// It is a shorthand for the more proper GEDCOMString() function.
func (doc *Document) String() string {
return doc.GEDCOMString(0)
}
// GEDCOMString will render the entire GEDCOM document.
func (doc *Document) GEDCOMString(indent int) string {
if doc == nil {
return ""
}
buf := bytes.NewBufferString("")
encoder := NewEncoder(buf, doc)
encoder.startIndent = indent
err := encoder.Encode()
if err != nil {
return ""
}
return buf.String()
}
// Individuals returns all of the people in the document.
func (doc *Document) Individuals() IndividualNodes {
individuals := IndividualNodes{}
for _, node := range doc.Nodes() {
if n, ok := node.(*IndividualNode); ok {
individuals = append(individuals, n)
}
}
return individuals
}
func (doc *Document) buildPointerCache() {
doc.pointerCache = sync.Map{}
for _, node := range doc.Nodes() {
pointer := node.Pointer()
if pointer != "" {
doc.pointerCache.Store(pointer, node)
}
}
}
// NodeByPointer returns the Node for a pointer value.
//
// If the pointer does not exist nil is returned.
func (doc *Document) NodeByPointer(ptr string) Node {
node, ok := doc.pointerCache.Load(ptr)
if !ok {
return nil
}
return node.(Node)
}
// Families returns the family entities in the document.
func (doc *Document) Families() (families FamilyNodes) {
if doc.families != nil {
return doc.families
}
defer func() {
doc.families = families
}()
families = FamilyNodes{}
for _, node := range doc.Nodes() {
if n, ok := node.(*FamilyNode); ok {
families = append(families, n)
}
}
return families
}
// TODO: needs tests
func (doc *Document) Places() map[*PlaceNode]Node {
places := map[*PlaceNode]Node{}
for _, node := range doc.Nodes() {
extractPlaces(node, places)
}
return places
}
func extractPlaces(n Node, dest map[*PlaceNode]Node) {
for _, node := range n.Nodes() {
if place, ok := node.(*PlaceNode); ok {
// The place points to the parent node which is the thing that the
// place is describing.
dest[place] = n
} else {
extractPlaces(node, dest)
}
}
}
// TODO: Needs tests
func (doc *Document) Sources() []*SourceNode {
sources := []*SourceNode{}
for _, node := range doc.Nodes() {
if n, ok := node.(*SourceNode); ok {
sources = append(sources, n)
}
}
return sources
}
// AddNode appends a node to the document.
//
// If the node is nil this function has no effect.
//
// If the node already exists it will be added again. This will cause problems
// with duplicate references.
func (doc *Document) AddNode(node Node) {
if !IsNil(node) {
doc.nodes = append(doc.nodes, node)
doc.addPointerToCache(node)
}
}
func (doc *Document) addPointerToCache(node Node) {
pointer := node.Pointer()
if pointer != "" {
doc.pointerCache.Store(pointer, node)
}
// Clear cache.
switch node.Tag() {
case TagFamily:
doc.families = nil
}
}
// Nodes returns the root nodes for the document.
//
// It is important that the slice returned is not manually manipulated (such as
// appending) because it may cause the internal cache to all out of sync. You
// may manipulate the nodes themselves.
func (doc *Document) Nodes() Nodes {
return doc.nodes
}
// NewDocumentFromGEDCOMFile returns a decoded Document from the provided file.
//
// If the file does not exist, be read or parse then an error is returned and
// the document will be nil.
func NewDocumentFromGEDCOMFile(path string) (*Document, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
decoder := NewDecoder(file)
return decoder.Decode()
}
// NewDocumentFromString creates a document from a string containing GEDCOM
// data.
//
// An error is returned if a line cannot be parsed.
func NewDocumentFromString(gedcom string) (*Document, error) {
decoder := NewDecoder(strings.NewReader(gedcom))
return decoder.Decode()
}
// NewDocument returns an empty document.
func NewDocument() *Document {
return &Document{
MaxLivingAge: DefaultMaxLivingAge,
}
}
// NewDocumentWithNodes creates a new document with the provided root nodes.
func NewDocumentWithNodes(nodes Nodes) *Document {
document := NewDocument()
document.nodes = nodes
document.buildPointerCache()
return document
}
func (doc *Document) nonIndividuals() Nodes {
nodes := Nodes{}
for _, node := range doc.Nodes() {
if _, ok := node.(*IndividualNode); !ok {
nodes = append(nodes, node)
}
}
return nodes
}
func (doc *Document) SetNodes(nodes Nodes) {
doc.nodes = nodes
}
func individuals(doc *Document) IndividualNodes {
if doc == nil {
return nil
}
return doc.Individuals()
}
func nonIndividuals(doc *Document) Nodes {
if doc == nil {
return nil
}
return doc.nonIndividuals()
}
func (doc *Document) AddIndividual(pointer string, children ...Node) *IndividualNode {
node := newIndividualNode(doc, pointer, children...)
doc.AddNode(node)
// I know this is a bad option, but it's an easy option. If we add a new
// individual we need to reset any cache on the individuals. Rather than
// work out which individuals need to be reset we just do them all.
for _, individual := range doc.Individuals() {
individual.resetCache()
}
return node
}
func (doc *Document) AddFamily(pointer string) *FamilyNode {
node := newFamilyNode(doc, pointer)
doc.AddNode(node)
// I know this is a bad option, but it's an easy option. If we add a new
// individual we need to reset any cache on the individuals. Rather than
// work out which individuals need to be reset we just do them all.
for _, family := range doc.Families() {
family.resetCache()
}
return node
}
func (doc *Document) AddFamilyWithHusbandAndWife(pointer string, husband, wife *IndividualNode) *FamilyNode {
familyNode := doc.AddFamily(pointer)
familyNode.SetHusband(husband)
familyNode.SetWife(wife)
return familyNode
}
func (doc *Document) DeleteNode(node Node) (didDelete bool) {
doc.nodes, didDelete = doc.nodes.deleteNode(node)
return
}
func (doc *Document) Warnings() (warnings Warnings) {
context := WarningContext{}
for _, node := range doc.nodes {
if individual, ok := node.(*IndividualNode); ok {
context.Individual = individual
context.Family = nil
}
if family, ok := node.(*FamilyNode); ok {
context.Individual = nil
context.Family = family
}
Filter(node, doc, func(node Node) (newNode Node, traverseChildren bool) {
if warner, ok := node.(Warner); ok {
for _, warning := range warner.Warnings() {
warning.SetContext(context)
warnings = append(warnings, warning)
}
}
return node, true
})
}
return
}