@treeviz/familysearch-sdk 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6416,6 +6416,64 @@ function transformSourceUrl(url) {
6416
6416
  }
6417
6417
  return `${baseUrl}/${arkId}`;
6418
6418
  }
6419
+ function sanitizeGedcomValue(value) {
6420
+ return value.replace(/[\r\n\t]+/g, " ").trim();
6421
+ }
6422
+ var GEDCOM_MONTHS = [
6423
+ "JAN",
6424
+ "FEB",
6425
+ "MAR",
6426
+ "APR",
6427
+ "MAY",
6428
+ "JUN",
6429
+ "JUL",
6430
+ "AUG",
6431
+ "SEP",
6432
+ "OCT",
6433
+ "NOV",
6434
+ "DEC"
6435
+ ];
6436
+ function formatSimpleFormalDate(value) {
6437
+ const match = value.match(/^([+-])(\d{1,6})(?:-(\d{2}))?(?:-(\d{2}))?/);
6438
+ if (!match) return void 0;
6439
+ const [, sign, yearPart, monthPart, dayPart] = match;
6440
+ if (sign === "-") return void 0;
6441
+ const year = String(parseInt(yearPart, 10));
6442
+ if (!monthPart) return year;
6443
+ const month = GEDCOM_MONTHS[parseInt(monthPart, 10) - 1];
6444
+ if (!month) return void 0;
6445
+ if (!dayPart) return `${month} ${year}`;
6446
+ return `${parseInt(dayPart, 10)} ${month} ${year}`;
6447
+ }
6448
+ function convertFormalDateToGedcom(formal) {
6449
+ if (!formal) return void 0;
6450
+ let value = formal.trim();
6451
+ let approximate = false;
6452
+ if (value.startsWith("A")) {
6453
+ approximate = true;
6454
+ value = value.slice(1);
6455
+ }
6456
+ if (value.includes("/")) {
6457
+ const slashIndex = value.indexOf("/");
6458
+ const start = value.slice(0, slashIndex);
6459
+ const end = value.slice(slashIndex + 1);
6460
+ const startDate = start ? formatSimpleFormalDate(start) : void 0;
6461
+ const endDate = end ? formatSimpleFormalDate(end) : void 0;
6462
+ if (start && !startDate) return void 0;
6463
+ if (end && !endDate) return void 0;
6464
+ if (startDate && endDate) return `BET ${startDate} AND ${endDate}`;
6465
+ if (startDate) return `FROM ${startDate}`;
6466
+ if (endDate) return `TO ${endDate}`;
6467
+ return void 0;
6468
+ }
6469
+ const simple = formatSimpleFormalDate(value);
6470
+ if (!simple) return void 0;
6471
+ return approximate ? `ABT ${simple}` : simple;
6472
+ }
6473
+ function resolveFactDate(date) {
6474
+ if (!date) return void 0;
6475
+ return convertFormalDateToGedcom(date.formal) || date.original;
6476
+ }
6419
6477
  function extractName(person) {
6420
6478
  if (!person) return null;
6421
6479
  if (person.names?.[0]?.nameForms?.[0]) {
@@ -6423,8 +6481,14 @@ function extractName(person) {
6423
6481
  const parts = nameForm.parts || [];
6424
6482
  const given = parts.find((p) => p.type?.includes("Given"))?.value || "";
6425
6483
  const surname = parts.find((p) => p.type?.includes("Surname"))?.value || "";
6426
- if (given || surname) {
6427
- return `${given} /${surname}/`.trim();
6484
+ if (given && surname) {
6485
+ return `${given} /${surname}/`;
6486
+ }
6487
+ if (surname) {
6488
+ return `/${surname}/`;
6489
+ }
6490
+ if (given) {
6491
+ return given;
6428
6492
  }
6429
6493
  if (nameForm.fullText) {
6430
6494
  return nameForm.fullText;
@@ -6454,11 +6518,9 @@ function extractFact(person, factType) {
6454
6518
  const factTypeUrl = `http://gedcomx.org/${factType === "BIRTH" ? "Birth" : "Death"}`;
6455
6519
  const fact = person.facts?.find((f) => f.type === factTypeUrl);
6456
6520
  if (fact) {
6457
- if (fact.date?.formal || fact.date?.original) {
6458
- result.date = (fact.date?.formal || fact.date?.original)?.replace(
6459
- /^(-|\+)/,
6460
- ""
6461
- );
6521
+ const resolvedDate = resolveFactDate(fact.date);
6522
+ if (resolvedDate) {
6523
+ result.date = resolvedDate;
6462
6524
  }
6463
6525
  if (fact.place?.original) {
6464
6526
  result.place = fact.place.original;
@@ -6511,25 +6573,30 @@ function convertFactToGedcom(fact) {
6511
6573
  };
6512
6574
  const gedcomTag = typeMap[fact.type] || "EVEN";
6513
6575
  if (gedcomTag === "OCCU" && fact.value) {
6514
- lines.push(`1 OCCU ${fact.value}`);
6576
+ lines.push(`1 OCCU ${sanitizeGedcomValue(fact.value)}`);
6515
6577
  } else {
6516
6578
  lines.push(`1 ${gedcomTag}`);
6517
6579
  }
6580
+ if (gedcomTag === "EVEN") {
6581
+ const typeName = fact.type.split("/").pop();
6582
+ if (typeName) {
6583
+ lines.push(`2 TYPE ${sanitizeGedcomValue(typeName)}`);
6584
+ }
6585
+ }
6518
6586
  if (fact.links?.conclusion?.href) {
6519
6587
  const webUrl = transformFamilySearchUrl(fact.links.conclusion.href);
6520
6588
  lines.push(`2 WWW ${webUrl}`);
6521
6589
  lines.push(`3 _IS_FS Y`);
6522
6590
  }
6523
- if (fact.date?.formal || fact.date?.original) {
6524
- lines.push(
6525
- `2 DATE ${(fact.date?.formal || fact.date?.original)?.replace(/^(-|\+)/, "")}`
6526
- );
6591
+ const resolvedDate = resolveFactDate(fact.date);
6592
+ if (resolvedDate) {
6593
+ lines.push(`2 DATE ${sanitizeGedcomValue(resolvedDate)}`);
6527
6594
  }
6528
6595
  if (fact.place?.original) {
6529
- lines.push(`2 PLAC ${fact.place.original}`);
6596
+ lines.push(`2 PLAC ${sanitizeGedcomValue(fact.place.original)}`);
6530
6597
  }
6531
6598
  if (fact.value && gedcomTag !== "OCCU") {
6532
- lines.push(`2 NOTE ${fact.value}`);
6599
+ lines.push(`2 NOTE ${sanitizeGedcomValue(fact.value)}`);
6533
6600
  }
6534
6601
  return lines;
6535
6602
  }
@@ -6549,14 +6616,14 @@ function convertToGedcom(pedigreeData, options = {}) {
6549
6616
  lines.push("2 VERS 1.0");
6550
6617
  lines.push("2 NAME FamilySearch API");
6551
6618
  if (treeName) {
6552
- lines.push(`2 _TREE ${treeName}`);
6619
+ lines.push(`2 _TREE ${sanitizeGedcomValue(treeName)}`);
6553
6620
  const randomRIN = Math.floor(Math.random() * 1e8);
6554
6621
  lines.push(`3 RIN ${randomRIN}`);
6555
6622
  }
6556
6623
  lines.push("1 DEST ANY");
6557
6624
  lines.push("1 DATE " + formatDateForGedcom(/* @__PURE__ */ new Date()));
6558
6625
  lines.push("1 SUBM @SUBM1@");
6559
- lines.push("1 FILE " + treeName);
6626
+ lines.push("1 FILE " + sanitizeGedcomValue(treeName));
6560
6627
  lines.push("1 GEDC");
6561
6628
  lines.push("2 VERS 5.5");
6562
6629
  lines.push("2 FORM LINEAGE-LINKED");
@@ -6578,20 +6645,22 @@ function convertToGedcom(pedigreeData, options = {}) {
6578
6645
  const sourceId = `@S${sourceCounter++}@`;
6579
6646
  lines.push(`0 ${sourceId} SOUR`);
6580
6647
  const title = source.titles?.[0]?.value || "FamilySearch Source";
6581
- lines.push(`1 TITL ${title}`);
6648
+ lines.push(`1 TITL ${sanitizeGedcomValue(title)}`);
6582
6649
  if (source.citations?.[0]?.value) {
6583
- lines.push(`1 TEXT ${source.citations[0].value}`);
6650
+ lines.push(`1 TEXT ${sanitizeGedcomValue(source.citations[0].value)}`);
6584
6651
  }
6585
6652
  if (source.about) {
6586
6653
  const webUrl = transformSourceUrl(source.about);
6587
6654
  lines.push(`1 WWW ${webUrl}`);
6588
6655
  }
6589
6656
  if (source.resourceType) {
6590
- lines.push(`1 NOTE Resource Type: ${source.resourceType}`);
6657
+ lines.push(
6658
+ `1 NOTE Resource Type: ${sanitizeGedcomValue(source.resourceType)}`
6659
+ );
6591
6660
  }
6592
6661
  });
6593
6662
  const fsSourMap = /* @__PURE__ */ new Map();
6594
- let fsSourCounter = 1;
6663
+ let fsSourCounter = sourceCounter;
6595
6664
  pedigreeData.persons.forEach((person) => {
6596
6665
  if (person.sources?.persons?.[0]?.sources) {
6597
6666
  const sourceDescMap = /* @__PURE__ */ new Map();
@@ -6641,20 +6710,22 @@ function convertToGedcom(pedigreeData, options = {}) {
6641
6710
  lines.push(`1 _IS_FS Y`);
6642
6711
  lines.push(`1 _FS_ID ${id}`);
6643
6712
  if (source.titles?.[0]?.value) {
6644
- lines.push(`1 TITL ${source.titles[0].value}`);
6713
+ lines.push(`1 TITL ${sanitizeGedcomValue(source.titles[0].value)}`);
6645
6714
  }
6646
6715
  if (source.citations?.[0]?.value) {
6647
- lines.push(`1 TEXT ${source.citations[0].value}`);
6716
+ lines.push(`1 TEXT ${sanitizeGedcomValue(source.citations[0].value)}`);
6648
6717
  }
6649
6718
  if (source.about) {
6650
6719
  const webUrl = transformSourceUrl(source.about);
6651
6720
  lines.push(`1 WWW ${webUrl}`);
6652
6721
  }
6653
6722
  if (source.resourceType) {
6654
- lines.push(`1 NOTE Resource Type: ${source.resourceType}`);
6723
+ lines.push(
6724
+ `1 NOTE Resource Type: ${sanitizeGedcomValue(source.resourceType)}`
6725
+ );
6655
6726
  }
6656
6727
  notes.forEach((note) => {
6657
- lines.push(`1 NOTE ${note}`);
6728
+ lines.push(`1 NOTE ${sanitizeGedcomValue(note)}`);
6658
6729
  });
6659
6730
  });
6660
6731
  const fsMatchMap = /* @__PURE__ */ new Map();
@@ -6674,7 +6745,7 @@ function convertToGedcom(pedigreeData, options = {}) {
6674
6745
  lines.push(`1 _IS_FS Y`);
6675
6746
  lines.push(`1 _FS_ID ${id}`);
6676
6747
  if (entry.title) {
6677
- lines.push(`1 TITL ${entry.title}`);
6748
+ lines.push(`1 TITL ${sanitizeGedcomValue(entry.title)}`);
6678
6749
  }
6679
6750
  if (entry.content?.score !== void 0) {
6680
6751
  lines.push(`1 SCORE ${entry.content.score}`);
@@ -6695,7 +6766,7 @@ function convertToGedcom(pedigreeData, options = {}) {
6695
6766
  }
6696
6767
  if (matchInfo.status) {
6697
6768
  const statusName = matchInfo.status.includes("/") ? matchInfo.status.split("/").pop() : matchInfo.status;
6698
- lines.push(`1 NOTE Status: ${statusName}`);
6769
+ lines.push(`1 NOTE Status: ${sanitizeGedcomValue(statusName || "")}`);
6699
6770
  }
6700
6771
  }
6701
6772
  lines.push(`1 TYPE ${collectionType}`);
@@ -6707,42 +6778,52 @@ function convertToGedcom(pedigreeData, options = {}) {
6707
6778
  if (entry.content?.gedcomx?.persons && entry.content.gedcomx.persons.length > 0) {
6708
6779
  const matchedPerson = entry.content.gedcomx.persons[0];
6709
6780
  if (matchedPerson.display?.name) {
6710
- lines.push(`1 TEXT ${matchedPerson.display.name}`);
6781
+ lines.push(`1 TEXT ${sanitizeGedcomValue(matchedPerson.display.name)}`);
6711
6782
  }
6712
6783
  if (matchedPerson.display?.lifespan) {
6713
6784
  lines.push(
6714
- `1 NOTE Lifespan: ${matchedPerson.display.lifespan}`
6785
+ `1 NOTE Lifespan: ${sanitizeGedcomValue(matchedPerson.display.lifespan)}`
6715
6786
  );
6716
6787
  }
6717
6788
  if (matchedPerson.display?.birthDate || matchedPerson.display?.birthPlace) {
6718
- const birthInfo = [
6719
- matchedPerson.display.birthDate,
6720
- matchedPerson.display.birthPlace
6721
- ].filter(Boolean).join(", ");
6789
+ const birthInfo = sanitizeGedcomValue(
6790
+ [
6791
+ matchedPerson.display.birthDate,
6792
+ matchedPerson.display.birthPlace
6793
+ ].filter(Boolean).join(", ")
6794
+ );
6722
6795
  if (birthInfo) {
6723
6796
  lines.push(`1 NOTE Birth: ${birthInfo}`);
6724
6797
  }
6725
6798
  }
6726
6799
  if (matchedPerson.display?.deathDate || matchedPerson.display?.deathPlace) {
6727
- const deathInfo = [
6728
- matchedPerson.display.deathDate,
6729
- matchedPerson.display.deathPlace
6730
- ].filter(Boolean).join(", ");
6800
+ const deathInfo = sanitizeGedcomValue(
6801
+ [
6802
+ matchedPerson.display.deathDate,
6803
+ matchedPerson.display.deathPlace
6804
+ ].filter(Boolean).join(", ")
6805
+ );
6731
6806
  if (deathInfo) {
6732
6807
  lines.push(`1 NOTE Death: ${deathInfo}`);
6733
6808
  }
6734
6809
  }
6735
6810
  if (matchedPerson.display?.gender) {
6736
- lines.push(`1 NOTE Gender: ${matchedPerson.display.gender}`);
6811
+ lines.push(
6812
+ `1 NOTE Gender: ${sanitizeGedcomValue(matchedPerson.display.gender)}`
6813
+ );
6737
6814
  }
6738
6815
  }
6739
6816
  if (entry.content?.sourceDescription) {
6740
6817
  const sourceDesc = entry.content.sourceDescription;
6741
6818
  if (sourceDesc.citations?.[0]?.value) {
6742
- lines.push(`1 NOTE Citation: ${sourceDesc.citations[0].value}`);
6819
+ lines.push(
6820
+ `1 NOTE Citation: ${sanitizeGedcomValue(sourceDesc.citations[0].value)}`
6821
+ );
6743
6822
  }
6744
6823
  if (sourceDesc.resourceType) {
6745
- lines.push(`1 NOTE Resource Type: ${sourceDesc.resourceType}`);
6824
+ lines.push(
6825
+ `1 NOTE Resource Type: ${sanitizeGedcomValue(sourceDesc.resourceType)}`
6826
+ );
6746
6827
  }
6747
6828
  if (sourceDesc.about) {
6748
6829
  const webUrl = transformSourceUrl(sourceDesc.about);
@@ -6985,7 +7066,7 @@ function convertToGedcom(pedigreeData, options = {}) {
6985
7066
  const personData = person.fullDetails?.persons?.[0] || person;
6986
7067
  const name = extractName(personData);
6987
7068
  if (name) {
6988
- lines.push(`1 NAME ${name}`);
7069
+ lines.push(`1 NAME ${sanitizeGedcomValue(name)}`);
6989
7070
  }
6990
7071
  const gender = extractGender(personData);
6991
7072
  if (gender) {
@@ -6995,20 +7076,20 @@ function convertToGedcom(pedigreeData, options = {}) {
6995
7076
  if (birth) {
6996
7077
  lines.push("1 BIRT");
6997
7078
  if (birth.date) {
6998
- lines.push(`2 DATE ${birth.date}`);
7079
+ lines.push(`2 DATE ${sanitizeGedcomValue(birth.date)}`);
6999
7080
  }
7000
7081
  if (birth.place) {
7001
- lines.push(`2 PLAC ${birth.place}`);
7082
+ lines.push(`2 PLAC ${sanitizeGedcomValue(birth.place)}`);
7002
7083
  }
7003
7084
  }
7004
7085
  const death = extractFact(personData, "DEATH");
7005
7086
  if (death) {
7006
7087
  lines.push("1 DEAT");
7007
7088
  if (death.date) {
7008
- lines.push(`2 DATE ${death.date}`);
7089
+ lines.push(`2 DATE ${sanitizeGedcomValue(death.date)}`);
7009
7090
  }
7010
7091
  if (death.place) {
7011
- lines.push(`2 PLAC ${death.place}`);
7092
+ lines.push(`2 PLAC ${sanitizeGedcomValue(death.place)}`);
7012
7093
  }
7013
7094
  }
7014
7095
  personData.facts?.forEach((fact) => {
@@ -7020,8 +7101,7 @@ function convertToGedcom(pedigreeData, options = {}) {
7020
7101
  if (includeNotes && person.notes?.persons?.[0]?.notes) {
7021
7102
  person.notes.persons[0].notes.forEach((note) => {
7022
7103
  if (note.text) {
7023
- const noteText = note.text.replace(/\n/g, " ");
7024
- lines.push(`1 NOTE ${noteText}`);
7104
+ lines.push(`1 NOTE ${sanitizeGedcomValue(note.text)}`);
7025
7105
  }
7026
7106
  });
7027
7107
  }
@@ -7039,7 +7119,7 @@ function convertToGedcom(pedigreeData, options = {}) {
7039
7119
  sourceRef.qualifiers.forEach((qualifier) => {
7040
7120
  if (qualifier.name && qualifier.value) {
7041
7121
  lines.push(
7042
- `2 NOTE ${qualifier.name}: ${qualifier.value}`
7122
+ `2 NOTE ${sanitizeGedcomValue(`${qualifier.name}: ${qualifier.value}`)}`
7043
7123
  );
7044
7124
  }
7045
7125
  });
@@ -7193,31 +7273,36 @@ function addMarriageFacts(lines, relationships, person1, person2) {
7193
7273
  return [a, b].sort().join("-") === relKey;
7194
7274
  });
7195
7275
  if (!rel) return;
7276
+ const isMarriageEvent = (type) => !!type && (type.includes("Marriage") || type.includes("Divorce"));
7196
7277
  const marriageFacts = [];
7197
7278
  if (rel.facts && Array.isArray(rel.facts)) {
7198
7279
  rel.facts.forEach((f) => {
7199
- if (f.type?.includes("Marriage")) {
7280
+ if (isMarriageEvent(f.type)) {
7200
7281
  marriageFacts.push(f);
7201
7282
  }
7202
7283
  });
7203
7284
  }
7204
7285
  if (rel.details?.facts && Array.isArray(rel.details.facts)) {
7205
7286
  marriageFacts.push(
7206
- ...rel.details.facts.filter((f) => f.type?.includes("Marriage"))
7287
+ ...rel.details.facts.filter((f) => isMarriageEvent(f.type))
7207
7288
  );
7208
7289
  }
7209
7290
  if (rel.details?.persons && Array.isArray(rel.details.persons)) {
7210
7291
  rel.details.persons.forEach((p) => {
7211
7292
  if (p.facts && Array.isArray(p.facts)) {
7212
7293
  p.facts.forEach((f) => {
7213
- if (f.type?.includes("Marriage")) {
7294
+ if (isMarriageEvent(f.type)) {
7214
7295
  marriageFacts.push(f);
7215
7296
  }
7216
7297
  });
7217
7298
  }
7218
7299
  });
7219
7300
  }
7301
+ const seenFacts = /* @__PURE__ */ new Set();
7220
7302
  marriageFacts.forEach((mf) => {
7303
+ const key = `${mf.type}|${resolveFactDate(mf.date) || ""}|${mf.place?.original || ""}`;
7304
+ if (seenFacts.has(key)) return;
7305
+ seenFacts.add(key);
7221
7306
  const factLines = convertFactToGedcom(mf);
7222
7307
  factLines.forEach((line) => lines.push(line));
7223
7308
  });