elliotchance / gedcom

👪 A Go library and CLI tools for encoding, decoding, traversing, merging, comparing, querying and publishing GEDCOM files.
MIT License
94 stars 21 forks source link

(documentation?) Cannot get Individuals().ByUniqueIdentifier() to work #318

Closed sarnobat closed 3 years ago

sarnobat commented 3 years ago

I'm new to Golang and I must be doing something wrong. I'm not able to find sample code or documentation to help. Some assistance would be great.

Test Case

Full code

rohidekar.ged testcase.go testcase (native binary)

source

i := document.Individuals().ByUniqueIdentifier((*individual.UniqueIdentifiers()).String());

run instructions

(after removing the .txt suffixes):

go run testcase.go

or run the native binary (if you are using Mac OS X)

testcase

error output:

i is nil so this gets printed:

Cannot get individual by ID: (d2dbf014-f1ec-464e-b4dc-3da7300cb799)
elliotchance commented 3 years ago

Hi @sarnobat, from your previous issue #317 you should know that converting the entire document to CSV and back again isn't possible because the structure of a gendom doesn't flatten in that way. However, there is a solution for this with a modified workflow:

  1. Convert gedcom individuals to CSV with all the fields you want.
  2. Edit/save the CSV.
  3. Update the existing gedcom by copying all the CSV details back in.

This is all untested and just from memory, but since your learning go it should be a good enough starting point.

// Usage:
//   testdata tocsv        # Export originalGedcomFile to csvFile
//   testdata update       # Apply csvFile to originalGedcomFile, save as updatedGedcomFile
//
// IMPORTANT: The process relies on the order of the individuals remaining the
// same (so no adding/removing individuals either). This can be improved by
// using a unique ID instead of a position.

package main

import (
    "fmt"
    "github.com/elliotchance/gedcom"
    "os"
    "encoding/csv"
)

const originalGedcomFile = "rohidekar.ged"
const csvFile = "rohidekar.csv"

// We don't want to override the original in case something goes wrong.
const updatedGedcomFile = "rohidekar-2.ged" 

func main() {
  // In any case we need to read the original gedcom.
  document, err := gedcom.NewDocumentFromGEDCOMFile(originalGedcomFile)
  if err != nil {
    panic(err)
  }

  switch os.Args[1] {
    case "tocsv":
      toCSV(document)
    case "update":
      updateGedcom(document)
    default:
      panic(os.Args[1])
  }
}

func check(err error) {
  if err != nil {
    panic(err)
  }
}

func toCSV(document *gedcom.Document) {
  file, err := os.Create(csvFile)
  check(err)
  defer file.Close()

  writer := csv.NewWriter(file)
  defer writer.Flush()

  for _, individual := range document.Individuals() {
    // Note: There can be multiple dates. This just uses the first.
    // https://pkg.go.dev/github.com/elliotchance/gedcom#IndividualNode.Births
    birthDate, birthPlace := individual.Birth()

    err := writer.Write([]string{
      // Note: There are more name parts and individuals can have multiple
      // names. This just uses the first.
      // https://pkg.go.dev/github.com/elliotchance/gedcom#NameNode
      // https://pkg.go.dev/github.com/elliotchance/gedcom#IndividualNode.Names
      individual.Name().GivenName(),
      individual.Name().Surname(),

      birthDate.String(),
      birthPlace.String(),
    })
    check(err)
  }
}

func updateGedcom(originalDocument *gedcom.Document) {
  file, err := os.Open(csvFile)
  check(err)
  defer f.Close()

  lines, err := csv.NewReader(f).ReadAll()
  check(err)

  individuals := originalDocument.Individuals()
  for index, line := range lines {
    givenName, surname, birthDate, birthPlace := line[0], line[1], line[2], line[3]

    // Note: Not all of these will have data so you will need to use Add
    // functions in these cases:
    // https://pkg.go.dev/github.com/elliotchance/gedcom#IndividualNode

    // Note: This destroys all the existing name parts when creating a new name.
    individuals[i].Names[0] = gedcom.NewNameNode(
      gedcom.NewNode(gedcom.TagGivenName, givenName),
      gedcom.NewNode(gedcom.TagSurname, surname),
    )

    individuals[i].Births[0] = gedcom.NewBirthNode(
      gedcom.NewDateNode(birthDate),
      gedcom.NewPlaceNode(birthPlace),
    )
  }

  // Save into a new gedcom file so the original isn't modified.
  err := ioutil.WriteFile(updatedGedcomFile, []byte(originalDocument.String()), 0644)
  check(err)
}
sarnobat commented 3 years ago

Thanks for the detailed info, I will come back to this.

Do you have any info on the UniqueIdentifier fetching? I'd like to attempt a smaller program first.

elliotchance commented 3 years ago

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.

https://pkg.go.dev/github.com/elliotchance/gedcom#IndividualNode.UniqueIdentifiers

Example:

for _, individual := range document.Individuals() {
  fmt.Println(individual.Name(), individual.UniqueIdentifiers())
}
sarnobat commented 3 years ago

I'm not sure that answers my question, let me try to explain: I'm able to get the identifier given the individual object. But I'm not able to get the individual object given the UniqueIdentifier - it returns 0 when I call Individuals().ByUniqueIdentifier("XYZ123"). That's what my code sample is intended to demonstrate.

Is there a way to get an individual's object if you know the UniqueIdentifier (apart from iterating over the entire set)?

elliotchance commented 3 years ago

Yes, that should work. If you are not getting an individual back it's because:

  1. None of the individuals have that unique identifer. UIDs are case sensitive and the pointers (thats the ID in the gedcom) are not unique identifiers, use ByPointer instead.
  2. You are trying to use a value from a node that gedcom doesn't know is a unique identifier.

If either of those do not solve your issue, then show me the value you are using and the gedcom file line that its supposed to be matching.

sarnobat commented 3 years ago

I'm struggling to find time to play with this a bit more. I'll close it and reopen when I have further info so that it doesn't take attention away from other issues in your list. Thanks for the help so far.