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