@treeviz/gedcom-parser 2.0.10 → 2.0.12

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.
Files changed (40) hide show
  1. package/dist/classes/common.d.ts +6 -1
  2. package/dist/classes/common.d.ts.map +1 -1
  3. package/dist/classes/fam.d.ts +9 -1
  4. package/dist/classes/fam.d.ts.map +1 -1
  5. package/dist/classes/gedcom.d.ts +6 -2
  6. package/dist/classes/gedcom.d.ts.map +1 -1
  7. package/dist/classes/index.js +495 -50
  8. package/dist/classes/indi.d.ts +6 -0
  9. package/dist/classes/indi.d.ts.map +1 -1
  10. package/dist/cli/index.js +3383 -2932
  11. package/dist/constants/constants.d.ts +7 -0
  12. package/dist/constants/constants.d.ts.map +1 -1
  13. package/dist/constants/index.js +1287 -916
  14. package/dist/factories/index.js +409 -39
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +5327 -4777
  18. package/dist/interfaces/fam.d.ts +7 -1
  19. package/dist/interfaces/fam.d.ts.map +1 -1
  20. package/dist/interfaces/gedcom.d.ts +4 -0
  21. package/dist/interfaces/gedcom.d.ts.map +1 -1
  22. package/dist/interfaces/indi.d.ts +5 -1
  23. package/dist/interfaces/indi.d.ts.map +1 -1
  24. package/dist/kinship-translator/index.js +409 -39
  25. package/dist/structures/gedcom.d.ts +1 -0
  26. package/dist/structures/gedcom.d.ts.map +1 -1
  27. package/dist/types/types.d.ts +10 -0
  28. package/dist/types/types.d.ts.map +1 -1
  29. package/dist/utils/fact-edit.d.ts +22 -0
  30. package/dist/utils/fact-edit.d.ts.map +1 -0
  31. package/dist/utils/family-edit.d.ts +23 -0
  32. package/dist/utils/family-edit.d.ts.map +1 -0
  33. package/dist/utils/gedcom-trailer.d.ts +6 -0
  34. package/dist/utils/gedcom-trailer.d.ts.map +1 -0
  35. package/dist/utils/index.d.ts +4 -1
  36. package/dist/utils/index.d.ts.map +1 -1
  37. package/dist/utils/index.js +605 -55
  38. package/dist/utils/parser.d.ts +3 -0
  39. package/dist/utils/parser.d.ts.map +1 -1
  40. package/package.json +1 -1
@@ -82,8 +82,28 @@ __export(translators_exports, {
82
82
  });
83
83
 
84
84
  // src/constants/constants.ts
85
+ var APP_BRANDING = "TreeViz - The Family Tree Visualiser & Editor";
85
86
  var ID_REG = /^@[_a-zA-Z0-9]+@/;
86
87
  var ID_GETTER_REG = /^(?<at>@)?(?<letter>[A-Z])?/i;
88
+ var ORIG_HEAD_TAG = "_ORIGHEAD";
89
+
90
+ // src/utils/gedcom-trailer.ts
91
+ var TRAILER_LINE = /^0 TRLR\s*$/i;
92
+ var GEDCOM_TRAILER_TAG = "TRLR";
93
+ var isGedcomTrailerTag = (tag) => tag === GEDCOM_TRAILER_TAG;
94
+ var isGedcomTrailerLine = (line) => TRAILER_LINE.test(line);
95
+ var appendGedcomTrailer = (serialized) => {
96
+ const newline = serialized.includes("\r\n") ? "\r\n" : "\n";
97
+ const trimmed = serialized.replace(/(?:\r?\n)+$/, "");
98
+ if (!trimmed) {
99
+ return "0 TRLR";
100
+ }
101
+ const lastLine = trimmed.split(/\r?\n/).at(-1) ?? "";
102
+ if (isGedcomTrailerLine(lastLine)) {
103
+ return trimmed;
104
+ }
105
+ return `${trimmed}${newline}0 TRLR`;
106
+ };
87
107
  var List = class _List {
88
108
  // length = 0;
89
109
  constructor(items) {
@@ -594,6 +614,9 @@ var Common = class _Common {
594
614
  delete this._value;
595
615
  }
596
616
  set(name, value) {
617
+ if (isGedcomTrailerTag(name)) {
618
+ return void 0;
619
+ }
597
620
  if (typeof value === "string") {
598
621
  const usedValue = createCommon(this._gedcom, void 0, this.main);
599
622
  usedValue.value = value;
@@ -604,6 +627,9 @@ var Common = class _Common {
604
627
  return get(this, name);
605
628
  }
606
629
  assign(name, value, unique = false) {
630
+ if (isGedcomTrailerTag(name)) {
631
+ return void 0;
632
+ }
607
633
  let curValue = this.get(name);
608
634
  if (curValue?.isListable) {
609
635
  if (curValue instanceof _Common) {
@@ -826,30 +852,47 @@ var Common = class _Common {
826
852
  });
827
853
  return gedcom;
828
854
  }
855
+ getHeadRecord() {
856
+ return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
857
+ }
858
+ getOriginalHeadRecord() {
859
+ return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
860
+ }
861
+ getSourceHeads() {
862
+ const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
863
+ return heads.filter((head, index) => {
864
+ return Boolean(head && heads.indexOf(head) === index);
865
+ });
866
+ }
867
+ getFromSourceHeads(path) {
868
+ for (const head of this.getSourceHeads()) {
869
+ const value = get(head, path);
870
+ if (value !== void 0 && value !== null && value !== "") {
871
+ return value;
872
+ }
873
+ }
874
+ return void 0;
875
+ }
876
+ sourStartsWith(prefix) {
877
+ return this.getSourceHeads().some((head) => {
878
+ const sour = get(head, "SOUR.value");
879
+ return !!sour?.toLowerCase()?.startsWith(prefix);
880
+ });
881
+ }
829
882
  isGenoPro() {
830
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
831
- const sour = get(head, "SOUR.value");
832
- return !!sour?.toLowerCase()?.startsWith("genopro");
883
+ return this.sourStartsWith("genopro");
833
884
  }
834
885
  isAhnenblatt() {
835
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
836
- const sour = get(head, "SOUR.value");
837
- return !!sour?.toLowerCase()?.startsWith("ahnenblatt");
886
+ return this.sourStartsWith("ahnenblatt");
838
887
  }
839
888
  isGeni() {
840
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
841
- const sour = get(head, "SOUR.value");
842
- return !!sour?.toLowerCase()?.startsWith("geni");
889
+ return this.sourStartsWith("geni");
843
890
  }
844
891
  isAncestry() {
845
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
846
- const sour = get(head, "SOUR.value");
847
- return !!sour?.toLowerCase()?.startsWith("ancestry");
892
+ return this.sourStartsWith("ancestry");
848
893
  }
849
894
  isMyHeritage() {
850
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
851
- const sour = get(head, "SOUR.value");
852
- return !!sour?.toLowerCase()?.startsWith("myheritage");
895
+ return this.sourStartsWith("myheritage");
853
896
  }
854
897
  /**
855
898
  * Get the source type as a string (for prefixing tree IDs and names)
@@ -866,22 +909,22 @@ var Common = class _Common {
866
909
  return void 0;
867
910
  }
868
911
  isFamilySearch() {
869
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
870
- const sourName = get(head, "SOUR.NAME.value");
871
- return sourName === "FamilySearch API";
912
+ return this.getSourceHeads().some((head) => {
913
+ const sourName = get(head, "SOUR.NAME.value");
914
+ return sourName === "FamilySearch API";
915
+ });
872
916
  }
873
917
  isGNO2GED() {
874
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
875
- const sour = get(head, "SOUR.value");
876
- return sour === "GNO2GED";
918
+ return this.getSourceHeads().some((head) => {
919
+ const sour = get(head, "SOUR.value");
920
+ return sour === "GNO2GED";
921
+ });
877
922
  }
878
923
  getAncestryTreeId() {
879
- const path = "HEAD.SOUR._TREE.RIN.value";
880
- return get(this, path) || get(this.getGedcom(), path);
924
+ return this.getFromSourceHeads("SOUR._TREE.RIN.value") || (get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value"));
881
925
  }
882
926
  getMyHeritageTreeId() {
883
- const path = "HEAD._EXPORTED_FROM_SITE_ID.value";
884
- return get(this, path) || get(this.getGedcom(), path);
927
+ return this.getFromSourceHeads("_EXPORTED_FROM_SITE_ID.value") || (get(this, "HEAD._EXPORTED_FROM_SITE_ID.value") || get(this.getGedcom(), "HEAD._EXPORTED_FROM_SITE_ID.value"));
885
928
  }
886
929
  getTreeId() {
887
930
  if (this?.isAncestry()) {
@@ -915,8 +958,7 @@ var Common = class _Common {
915
958
  return this.getUniversalTreeId();
916
959
  }
917
960
  getAncestryTreeName() {
918
- const path = "HEAD.SOUR._TREE.value";
919
- return get(this, path) || get(this.getGedcom(), path);
961
+ return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
920
962
  }
921
963
  getMyHeritageTreeName() {
922
964
  const path = "HEAD.FILE.value";
@@ -926,14 +968,14 @@ var Common = class _Common {
926
968
  )?.groups?.tree;
927
969
  }
928
970
  getFamilySearchTreeId() {
929
- const rin = get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
971
+ const rin = this.getFromSourceHeads("SOUR._TREE.RIN.value") || get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
930
972
  if (rin) {
931
973
  return rin;
932
974
  }
933
975
  return "familysearch";
934
976
  }
935
977
  getFamilySearchTreeName() {
936
- const treeName = get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
978
+ const treeName = this.getFromSourceHeads("SOUR._TREE.value") || get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
937
979
  if (treeName) {
938
980
  return treeName;
939
981
  }
@@ -1025,11 +1067,11 @@ var Common = class _Common {
1025
1067
  const cleanName = fileName.replace(/\.ged$/i, "").replace(/_/g, " ");
1026
1068
  return `${prefix}${cleanName}`;
1027
1069
  }
1028
- const sourName = get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
1070
+ const sourName = this.getFromSourceHeads("SOUR.NAME.value") || get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
1029
1071
  if (sourName) {
1030
1072
  return `${prefix}${sourName}`;
1031
1073
  }
1032
- const sourValue = get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
1074
+ const sourValue = this.getFromSourceHeads("SOUR.value") || get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
1033
1075
  if (sourValue) {
1034
1076
  return `${prefix}${sourValue}`;
1035
1077
  }
@@ -1095,7 +1137,7 @@ var isOnlyMainProp = (key) => {
1095
1137
  };
1096
1138
  var isValidKey = (common, key) => {
1097
1139
  const prop = get(common, key);
1098
- return key !== "_gedcom" && key !== "_main" && key !== "_parent" && key !== "_uniqueId" && key !== "_refs" && key !== "_type" && (key === "id" || key === "_id" || key === "value" || key === "_value" || prop instanceof Common || prop instanceof List);
1140
+ return key !== "_gedcom" && key !== "_main" && key !== "_parent" && key !== "_uniqueId" && key !== "_refs" && key !== "_type" && !(typeof key === "string" && isGedcomTrailerTag(key)) && (key === "id" || key === "_id" || key === "value" || key === "_value" || prop instanceof Common || prop instanceof List);
1099
1141
  };
1100
1142
  var getValidKeys = (common) => {
1101
1143
  return Object.keys(common).filter((key) => {
@@ -3810,6 +3852,250 @@ var cacheDiscoveredPath = (gedcom, path) => {
3810
3852
  }
3811
3853
  };
3812
3854
 
3855
+ // src/utils/fact-edit.ts
3856
+ var SINGLETON_TAGS = /* @__PURE__ */ new Set(["BIRT", "DEAT", "SSN", "SEX", "DSCR"]);
3857
+ var firstRecord = (node) => {
3858
+ if (!node) {
3859
+ return void 0;
3860
+ }
3861
+ if (node instanceof List) {
3862
+ return node.index(0);
3863
+ }
3864
+ return node.index(0) ?? node;
3865
+ };
3866
+ var setDateOn = (gedcom, owner, parent, date) => {
3867
+ const trimmed = date.trim();
3868
+ if (!trimmed) {
3869
+ return;
3870
+ }
3871
+ let dateNode = parent.get("DATE");
3872
+ if (!dateNode || dateNode instanceof List) {
3873
+ dateNode = createCommonDate(gedcom, void 0, owner, parent);
3874
+ dateNode.type = "DATE";
3875
+ parent.set("DATE", dateNode);
3876
+ }
3877
+ dateNode.value = trimmed;
3878
+ };
3879
+ var fillFact = (gedcom, owner, event, input) => {
3880
+ if (input.value?.trim()) {
3881
+ event.value = input.value.trim();
3882
+ }
3883
+ if (input.type?.trim()) {
3884
+ event.set("TYPE", input.type.trim());
3885
+ }
3886
+ if (input.date) {
3887
+ setDateOn(gedcom, owner, event, input.date);
3888
+ }
3889
+ if (input.place?.trim()) {
3890
+ event.set("PLAC", input.place.trim());
3891
+ }
3892
+ if (input.note?.trim()) {
3893
+ event.set("NOTE", input.note.trim());
3894
+ }
3895
+ };
3896
+ var addIndividualFact = (indi, input) => {
3897
+ const gedcom = indi.getGedcom();
3898
+ if (!gedcom) {
3899
+ return void 0;
3900
+ }
3901
+ const tag = input.tag;
3902
+ let event = SINGLETON_TAGS.has(tag) ? firstRecord(indi.get(tag)) : void 0;
3903
+ if (!event) {
3904
+ event = createCommon(gedcom, void 0, indi);
3905
+ event.type = tag;
3906
+ if (SINGLETON_TAGS.has(tag)) {
3907
+ indi.set(tag, event);
3908
+ } else {
3909
+ indi.assign(tag, event);
3910
+ }
3911
+ }
3912
+ fillFact(gedcom, indi, event, input);
3913
+ return event;
3914
+ };
3915
+ var addFamilyFact = (fam, input) => {
3916
+ const gedcom = fam.getGedcom();
3917
+ if (!gedcom) {
3918
+ return void 0;
3919
+ }
3920
+ const event = createCommon(gedcom, void 0, fam);
3921
+ event.type = input.tag;
3922
+ fam.assign(input.tag, event);
3923
+ fillFact(gedcom, fam, event, input);
3924
+ return event;
3925
+ };
3926
+
3927
+ // src/utils/family-edit.ts
3928
+ var PEDIGREE_VALUES = [
3929
+ "birth" /* BIRTH */,
3930
+ "adopted" /* ADOPTED */,
3931
+ "foster" /* FOSTER */,
3932
+ "step" /* STEP */,
3933
+ "sealing" /* SEALING */
3934
+ ];
3935
+ var toGedcomPedigree = (value) => {
3936
+ const normalized = String(value || "birth" /* BIRTH */).toLowerCase();
3937
+ if (normalized === "biological" /* BIOLOGICAL */ || normalized === "birth" /* BIRTH */) {
3938
+ return "birth" /* BIRTH */;
3939
+ }
3940
+ const match = PEDIGREE_VALUES.find((item) => item === normalized);
3941
+ return match ?? "birth" /* BIRTH */;
3942
+ };
3943
+ var pointerIds = (tag) => {
3944
+ if (!tag) {
3945
+ return [];
3946
+ }
3947
+ if (tag instanceof List) {
3948
+ return tag.values().map((item) => item?.toValue?.()).filter((value2) => !!value2);
3949
+ }
3950
+ const value = tag.toValue?.();
3951
+ return value ? [String(value)] : [];
3952
+ };
3953
+ var firstPointerId = (tag) => pointerIds(tag)[0];
3954
+ var makePointer = (gedcom, parent, value, refType) => {
3955
+ const pointer = createCommon(gedcom, void 0, parent);
3956
+ pointer.value = value;
3957
+ pointer.refType = refType;
3958
+ return pointer;
3959
+ };
3960
+ var nextRecordId = (ids, prefix) => {
3961
+ let max = 0;
3962
+ ids.forEach((id) => {
3963
+ const match = String(id).match(/(\d+)/);
3964
+ const n = match ? Number.parseInt(match[1], 10) : 0;
3965
+ if (n > max) {
3966
+ max = n;
3967
+ }
3968
+ });
3969
+ return `@${prefix}${max + 1}@`;
3970
+ };
3971
+ var familyHasParent = (fam, indiId) => pointerIds(fam.get("HUSB")).includes(indiId) || pointerIds(fam.get("WIFE")).includes(indiId);
3972
+ var familyHasChild = (fam, indiId) => pointerIds(fam.get("CHIL")).includes(indiId);
3973
+ var spouseTagFor = (indi, fam) => {
3974
+ const husbId = firstPointerId(fam.get("HUSB"));
3975
+ const wifeId = firstPointerId(fam.get("WIFE"));
3976
+ if (husbId === indi.id) {
3977
+ return "HUSB";
3978
+ }
3979
+ if (wifeId === indi.id) {
3980
+ return "WIFE";
3981
+ }
3982
+ if (indi.isFemale()) {
3983
+ return wifeId && wifeId !== indi.id ? "HUSB" : "WIFE";
3984
+ }
3985
+ if (indi.isMale()) {
3986
+ return husbId && husbId !== indi.id ? "WIFE" : "HUSB";
3987
+ }
3988
+ return husbId ? "WIFE" : "HUSB";
3989
+ };
3990
+ var canAttachAsSpouse = (fam, indi) => {
3991
+ if (!indi.id) {
3992
+ return false;
3993
+ }
3994
+ if (familyHasParent(fam, indi.id)) {
3995
+ return true;
3996
+ }
3997
+ const tag = spouseTagFor(indi, fam);
3998
+ const occupied = firstPointerId(fam.get(tag));
3999
+ return !occupied || occupied === indi.id;
4000
+ };
4001
+ var pointerByValue = (tag, id) => {
4002
+ if (!tag) {
4003
+ return void 0;
4004
+ }
4005
+ const fromValues = tag.toValueList?.().item(id);
4006
+ if (fromValues) {
4007
+ return fromValues;
4008
+ }
4009
+ return tag.toList()?.values().find((item) => item?.toValue?.() === id);
4010
+ };
4011
+ var setChildPedigree = (fam, child, pedigree, parent) => {
4012
+ if (!fam.id || !child.id) {
4013
+ return;
4014
+ }
4015
+ const pedi = toGedcomPedigree(pedigree);
4016
+ const famc = pointerByValue(child.get("FAMC"), fam.id);
4017
+ if (famc) {
4018
+ famc.set("PEDI", pedi);
4019
+ }
4020
+ const chil = pointerByValue(fam.get("CHIL"), child.id);
4021
+ if (!chil) {
4022
+ return;
4023
+ }
4024
+ const setFather = !parent || parent.isMale() || parent.isUnknownSex();
4025
+ const setMother = !parent || parent.isFemale() || parent.isUnknownSex();
4026
+ if (setFather) {
4027
+ chil.set("_FREL", pedi);
4028
+ }
4029
+ if (setMother) {
4030
+ chil.set("_MREL", pedi);
4031
+ }
4032
+ };
4033
+ var attachSpouseToFamily = (fam, indi) => {
4034
+ const gedcom = fam.getGedcom() ?? indi.getGedcom();
4035
+ if (!indi.id || !fam.id || !gedcom) {
4036
+ return false;
4037
+ }
4038
+ if (!canAttachAsSpouse(fam, indi)) {
4039
+ return false;
4040
+ }
4041
+ const tag = spouseTagFor(indi, fam);
4042
+ if (firstPointerId(fam.get(tag)) !== indi.id) {
4043
+ fam.set(tag, makePointer(gedcom, fam, indi.id, "INDI"));
4044
+ }
4045
+ indi.assign("FAMS", makePointer(gedcom, indi, fam.id, "FAM"), true);
4046
+ return true;
4047
+ };
4048
+ var attachChildToFamily = (fam, child, pedigree, parent) => {
4049
+ const gedcom = fam.getGedcom() ?? child.getGedcom();
4050
+ if (!child.id || !fam.id || !gedcom) {
4051
+ return false;
4052
+ }
4053
+ if (!familyHasChild(fam, child.id)) {
4054
+ fam.assign("CHIL", makePointer(gedcom, fam, child.id, "INDI"), true);
4055
+ }
4056
+ const existingFamc = pointerByValue(child.get("FAMC"), fam.id);
4057
+ if (!existingFamc) {
4058
+ child.assign("FAMC", makePointer(gedcom, child, fam.id, "FAM"), true);
4059
+ }
4060
+ setChildPedigree(fam, child, pedigree, parent);
4061
+ return true;
4062
+ };
4063
+ var findReusableParentChildFamily = (parent, child) => {
4064
+ if (!parent.id || !child.id) {
4065
+ return void 0;
4066
+ }
4067
+ let reusableFamc;
4068
+ let already = false;
4069
+ child.getFamilies("FAMC")?.forEach((fam) => {
4070
+ if (!fam) {
4071
+ return;
4072
+ }
4073
+ if (familyHasParent(fam, parent.id) && familyHasChild(fam, child.id)) {
4074
+ already = true;
4075
+ return;
4076
+ }
4077
+ if (!reusableFamc && canAttachAsSpouse(fam, parent)) {
4078
+ reusableFamc = fam;
4079
+ }
4080
+ });
4081
+ if (already) {
4082
+ return "already";
4083
+ }
4084
+ if (reusableFamc) {
4085
+ return reusableFamc;
4086
+ }
4087
+ const parentFams = [];
4088
+ parent.getFamilies("FAMS")?.forEach((fam) => {
4089
+ if (fam) {
4090
+ parentFams.push(fam);
4091
+ }
4092
+ });
4093
+ if (parentFams.length === 1) {
4094
+ return parentFams[0];
4095
+ }
4096
+ return void 0;
4097
+ };
4098
+
3813
4099
  // src/utils/get-places.ts
3814
4100
  var getPlaces = (common, type = ["ALL" /* All */], maxLevel = 1, level = 0, mainKey) => {
3815
4101
  const types = Array.isArray(type) ? type : [type];
@@ -4440,6 +4726,31 @@ var Fam = class extends Common {
4440
4726
  getParents() {
4441
4727
  return this.getHusband().copy().merge(this.getWife());
4442
4728
  }
4729
+ hasParent(indi) {
4730
+ const indiId = typeof indi === "string" ? indi : indi?.id;
4731
+ return !!indiId && familyHasParent(this, indiId);
4732
+ }
4733
+ hasChild(indi) {
4734
+ const indiId = typeof indi === "string" ? indi : indi?.id;
4735
+ return !!indiId && familyHasChild(this, indiId);
4736
+ }
4737
+ canAddSpouse(indi) {
4738
+ return canAttachAsSpouse(this, indi);
4739
+ }
4740
+ addSpouse(indi) {
4741
+ return attachSpouseToFamily(this, indi);
4742
+ }
4743
+ addChild(child, pedigree, parent) {
4744
+ return attachChildToFamily(
4745
+ this,
4746
+ child,
4747
+ pedigree,
4748
+ parent
4749
+ );
4750
+ }
4751
+ addFact(input) {
4752
+ return addFamilyFact(this, input);
4753
+ }
4443
4754
  toList() {
4444
4755
  return new Families().concat(
4445
4756
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -4512,19 +4823,51 @@ var ALLOWED_FACTS = [
4512
4823
  "OCCU",
4513
4824
  "OCCUPATIONS",
4514
4825
  "RESI",
4826
+ "BIRT",
4827
+ "DEAT",
4515
4828
  "BAPT",
4829
+ "BAPM",
4516
4830
  "CHRI",
4517
4831
  "CHR",
4832
+ "CHRA",
4518
4833
  "BURI",
4519
- "EDUC",
4834
+ "CREM",
4835
+ "ADOP",
4836
+ "BARM",
4837
+ "BASM",
4838
+ "BLES",
4839
+ "CONF",
4840
+ "FCOM",
4841
+ "ORDN",
4842
+ "NATU",
4843
+ "EMIG",
4844
+ "IMMI",
4845
+ "CENS",
4846
+ "PROB",
4847
+ "WILL",
4520
4848
  "GRAD",
4849
+ "RETI",
4850
+ "EDUC",
4521
4851
  "DIV",
4522
- "IMMI",
4523
4852
  "RELI",
4853
+ "CAST",
4854
+ "DSCR",
4855
+ "NATI",
4856
+ "PROP",
4857
+ "SSN",
4858
+ "TITL",
4859
+ "ADDR",
4860
+ "BAPL",
4861
+ "CONL",
4862
+ "ENDL",
4863
+ "SLGC",
4524
4864
  "_MILT",
4525
4865
  "_MILTID",
4526
4866
  "FACT",
4527
- "_ORIG"
4867
+ "_ORIG",
4868
+ "_WLNK",
4869
+ "WWW",
4870
+ "URL"
4528
4871
  ];
4529
4872
  var DISALLOWED_CUSTOM_FACTS = ["DNA Test", "Newspaper"];
4530
4873
  var CustomFactRenderers = {
@@ -5068,7 +5411,7 @@ var Indi = class extends Common {
5068
5411
  return result;
5069
5412
  }
5070
5413
  ancestryLink() {
5071
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5414
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5072
5415
  const tree = this.getAncestryTreeId();
5073
5416
  if (this.id) {
5074
5417
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -5080,7 +5423,7 @@ var Indi = class extends Common {
5080
5423
  async ancestryMedia(namespace) {
5081
5424
  const list = {};
5082
5425
  const objeList = this.get("OBJE")?.toList().copy();
5083
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5426
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5084
5427
  const tree = this.getAncestryTreeId();
5085
5428
  if (objeList) {
5086
5429
  await Promise.all(
@@ -5140,7 +5483,7 @@ var Indi = class extends Common {
5140
5483
  return list;
5141
5484
  }
5142
5485
  myheritageLink(poolId = 0) {
5143
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.value?.toLowerCase().replace(/^www\./gi, "");
5486
+ const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
5144
5487
  const site = this.getMyHeritageTreeId();
5145
5488
  const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
5146
5489
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
@@ -5661,6 +6004,57 @@ var Indi = class extends Common {
5661
6004
  isUnknownSex() {
5662
6005
  return !this.isMale() && !this.isFemale();
5663
6006
  }
6007
+ addSpouse(other) {
6008
+ if (!this.id || !other?.id || this.id === other.id || !this._gedcom) {
6009
+ return void 0;
6010
+ }
6011
+ const existing = getFamilyWith(this, other.id);
6012
+ if (existing) {
6013
+ return existing;
6014
+ }
6015
+ const fam = this._gedcom.createFamily();
6016
+ if (!fam.addSpouse(this) || !fam.addSpouse(other)) {
6017
+ return void 0;
6018
+ }
6019
+ return fam;
6020
+ }
6021
+ addChild(child, pedigree) {
6022
+ if (!this.id || !child?.id || this.id === child.id || !this._gedcom) {
6023
+ return void 0;
6024
+ }
6025
+ const existing = findReusableParentChildFamily(
6026
+ this,
6027
+ child
6028
+ );
6029
+ if (existing === "already") {
6030
+ const fam2 = child.getFamilies("FAMC")?.find(
6031
+ (item) => !!item && item.hasParent(this.id) && item.hasChild(child.id)
6032
+ );
6033
+ if (fam2) {
6034
+ setChildPedigree(
6035
+ fam2,
6036
+ child,
6037
+ pedigree,
6038
+ this
6039
+ );
6040
+ }
6041
+ return fam2;
6042
+ }
6043
+ const fam = existing ?? this._gedcom.createFamily();
6044
+ if (!fam.addSpouse(this)) {
6045
+ return void 0;
6046
+ }
6047
+ if (!fam.addChild(child, pedigree, this)) {
6048
+ return void 0;
6049
+ }
6050
+ return fam;
6051
+ }
6052
+ addParent(parent, pedigree) {
6053
+ return parent.addChild(this, pedigree);
6054
+ }
6055
+ addFact(input) {
6056
+ return addIndividualFact(this, input);
6057
+ }
5664
6058
  getParentType(id) {
5665
6059
  let indi;
5666
6060
  if (typeof id === "string" || typeof id === "number") {
@@ -5861,7 +6255,9 @@ var Indi = class extends Common {
5861
6255
  }
5862
6256
  }
5863
6257
  if (kinship !== "child" || !breakAfterSpouse) {
5864
- indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(indi.getFathers().copy().merge(indi.getMothers())).forEach((relative) => {
6258
+ indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(
6259
+ indi.getFathers().copy().merge(indi.getMothers())
6260
+ ).forEach((relative) => {
5865
6261
  if (!visited.has(relative)) {
5866
6262
  const currentRelation = indi.getParentType(relative);
5867
6263
  const isNonBiological = !!currentRelation && currentRelation !== "biological" /* BIOLOGICAL */;
@@ -6762,7 +7158,7 @@ var createIndi = (gedcom, id, main, parent) => {
6762
7158
 
6763
7159
  // package.json
6764
7160
  var package_default = {
6765
- version: "2.0.10"};
7161
+ version: "2.0.12"};
6766
7162
 
6767
7163
  // src/utils/get-product-details.ts
6768
7164
  var getVersion = () => package_default.version;
@@ -6962,6 +7358,44 @@ var GedCom = class extends Common {
6962
7358
  fams() {
6963
7359
  return this.getList("@@FAM");
6964
7360
  }
7361
+ nextIndiKey() {
7362
+ const ids = [];
7363
+ this.indis()?.forEach((_indi, id) => {
7364
+ ids.push(String(id));
7365
+ });
7366
+ return nextRecordId(ids, "I");
7367
+ }
7368
+ nextFamKey() {
7369
+ const ids = [];
7370
+ this.fams()?.forEach((_fam, id) => {
7371
+ ids.push(String(id));
7372
+ });
7373
+ return nextRecordId(ids, "F");
7374
+ }
7375
+ createIndividual() {
7376
+ const existing = this.indis();
7377
+ const indis = existing ?? new Individuals();
7378
+ if (!existing) {
7379
+ this.set("@@INDI", indis);
7380
+ }
7381
+ const id = this.nextIndiKey();
7382
+ const indi = createIndi(this, id);
7383
+ indi.type = "INDI";
7384
+ indis.item(id, indi);
7385
+ return indi;
7386
+ }
7387
+ createFamily() {
7388
+ const existing = this.fams();
7389
+ const fams = existing ?? new Families();
7390
+ if (!existing) {
7391
+ this.set("@@FAM", fams);
7392
+ }
7393
+ const id = this.nextFamKey();
7394
+ const fam = createFam(this, id);
7395
+ fam.type = "FAM";
7396
+ fams.item(id, fam);
7397
+ return fam;
7398
+ }
6965
7399
  objes() {
6966
7400
  return this.getList("@@OBJE");
6967
7401
  }
@@ -7049,15 +7483,24 @@ var GedCom = class extends Common {
7049
7483
  getDownloadHeader() {
7050
7484
  const newHead = createCommon();
7051
7485
  Object.assign(newHead, this.get("HEAD") ?? {});
7486
+ const existingSour = this.get("HEAD")?.get("SOUR");
7487
+ const existingTreeName = existingSour?.get("_TREE")?.toValue();
7488
+ const existingTreeUniqueId = existingSour?.get("_TID")?.toValue();
7052
7489
  const newSour = createCommon();
7053
- newSour.set("CORP", createCommon());
7054
- newSour.set("CORP.WWW", createCommon());
7055
- newSour.set("NAME", createCommon());
7056
- newSour.set("VERS", createCommon());
7057
- newSour.CORP.value = "TreeViz - The Family Tree Visualiser";
7058
- newSour.CORP.WWW.value = "treeviz.com";
7059
- newSour.NAME.value = "TreeViz - The Family Tree Visualiser";
7060
- newSour.VERS.value = getVersion();
7490
+ newSour.value = APP_BRANDING;
7491
+ newSour.set("NAME", APP_BRANDING);
7492
+ newSour.set("VERS", getVersion());
7493
+ if (typeof existingTreeName === "string" && existingTreeName.trim()) {
7494
+ newSour.set("_TREE", existingTreeName.trim());
7495
+ }
7496
+ if (typeof existingTreeUniqueId === "string" && existingTreeUniqueId.trim()) {
7497
+ newSour.set("_TID", existingTreeUniqueId.trim());
7498
+ }
7499
+ const corp = createCommon();
7500
+ corp.value = APP_BRANDING;
7501
+ corp.set("WWW", "treeviz.com");
7502
+ corp.set("EMAIL", "info@treeviz.com");
7503
+ newSour.set("CORP", corp);
7061
7504
  newHead.set("SOUR", newSour);
7062
7505
  return newHead;
7063
7506
  }
@@ -7086,15 +7529,17 @@ var GedCom = class extends Common {
7086
7529
  }
7087
7530
  toGedcom(tag, level, options) {
7088
7531
  if (options?.super) {
7089
- return super.toGedcom(tag, level, options);
7532
+ return appendGedcomTrailer(
7533
+ super.toGedcom(tag, level, options)
7534
+ );
7090
7535
  }
7091
7536
  const newGedcom = createGedCom();
7537
+ Object.assign(newGedcom, this);
7092
7538
  if (!options?.original) {
7093
7539
  Object.assign(newGedcom, {
7094
7540
  HEAD: this.getDownloadHeader()
7095
7541
  });
7096
7542
  }
7097
- Object.assign(newGedcom, this);
7098
7543
  if (options?.indis?.length) {
7099
7544
  const newContent = this.getIndiRelatedLists(options.indis);
7100
7545
  Object.assign(newGedcom, newContent);