@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
@@ -292,11 +292,31 @@ var cacheDiscoveredPath = (gedcom, path) => {
292
292
  };
293
293
 
294
294
  // src/constants/constants.ts
295
+ var APP_BRANDING = "TreeViz - The Family Tree Visualiser & Editor";
295
296
  var REF_LINE_REG = /^0 (?:(@[_a-zA-Z0-9]*@) )?(?<type>_[_A-Z][_a-zA-Z]{2,}|_?[A-Z][a-zA-Z]{2,}) ?(?<value>.*)/gm;
296
297
  var LINE_REG = /(?<indent>^[0-9]) (?:(@[_a-zA-Z0-9]*@) )?(?<type>_[_A-Z][_a-zA-Z]{2,}|_?[A-Z][a-zA-Z]{2,}) ?(?<value>.*)/;
297
298
  var ID_REG = /^@[_a-zA-Z0-9]+@/;
298
299
  var ID_GETTER_REG = /^(?<at>@)?(?<letter>[A-Z])?/i;
300
+ var ORIG_HEAD_TAG = "_ORIGHEAD";
299
301
  var MAX_FILE_SIZE_TO_SYNC = 1024 * 1024 * 20;
302
+
303
+ // src/utils/gedcom-trailer.ts
304
+ var TRAILER_LINE = /^0 TRLR\s*$/i;
305
+ var GEDCOM_TRAILER_TAG = "TRLR";
306
+ var isGedcomTrailerTag = (tag) => tag === GEDCOM_TRAILER_TAG;
307
+ var isGedcomTrailerLine = (line) => TRAILER_LINE.test(line);
308
+ var appendGedcomTrailer = (serialized) => {
309
+ const newline = serialized.includes("\r\n") ? "\r\n" : "\n";
310
+ const trimmed = serialized.replace(/(?:\r?\n)+$/, "");
311
+ if (!trimmed) {
312
+ return "0 TRLR";
313
+ }
314
+ const lastLine = trimmed.split(/\r?\n/).at(-1) ?? "";
315
+ if (isGedcomTrailerLine(lastLine)) {
316
+ return trimmed;
317
+ }
318
+ return `${trimmed}${newline}0 TRLR`;
319
+ };
300
320
  var List = class _List {
301
321
  // length = 0;
302
322
  constructor(items) {
@@ -807,6 +827,9 @@ var Common = class _Common {
807
827
  delete this._value;
808
828
  }
809
829
  set(name, value) {
830
+ if (isGedcomTrailerTag(name)) {
831
+ return void 0;
832
+ }
810
833
  if (typeof value === "string") {
811
834
  const usedValue = createCommon(this._gedcom, void 0, this.main);
812
835
  usedValue.value = value;
@@ -817,6 +840,9 @@ var Common = class _Common {
817
840
  return get(this, name);
818
841
  }
819
842
  assign(name, value, unique = false) {
843
+ if (isGedcomTrailerTag(name)) {
844
+ return void 0;
845
+ }
820
846
  let curValue = this.get(name);
821
847
  if (curValue?.isListable) {
822
848
  if (curValue instanceof _Common) {
@@ -1039,30 +1065,47 @@ var Common = class _Common {
1039
1065
  });
1040
1066
  return gedcom;
1041
1067
  }
1068
+ getHeadRecord() {
1069
+ return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1070
+ }
1071
+ getOriginalHeadRecord() {
1072
+ return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
1073
+ }
1074
+ getSourceHeads() {
1075
+ const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
1076
+ return heads.filter((head, index) => {
1077
+ return Boolean(head && heads.indexOf(head) === index);
1078
+ });
1079
+ }
1080
+ getFromSourceHeads(path) {
1081
+ for (const head of this.getSourceHeads()) {
1082
+ const value = get(head, path);
1083
+ if (value !== void 0 && value !== null && value !== "") {
1084
+ return value;
1085
+ }
1086
+ }
1087
+ return void 0;
1088
+ }
1089
+ sourStartsWith(prefix) {
1090
+ return this.getSourceHeads().some((head) => {
1091
+ const sour = get(head, "SOUR.value");
1092
+ return !!sour?.toLowerCase()?.startsWith(prefix);
1093
+ });
1094
+ }
1042
1095
  isGenoPro() {
1043
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1044
- const sour = get(head, "SOUR.value");
1045
- return !!sour?.toLowerCase()?.startsWith("genopro");
1096
+ return this.sourStartsWith("genopro");
1046
1097
  }
1047
1098
  isAhnenblatt() {
1048
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1049
- const sour = get(head, "SOUR.value");
1050
- return !!sour?.toLowerCase()?.startsWith("ahnenblatt");
1099
+ return this.sourStartsWith("ahnenblatt");
1051
1100
  }
1052
1101
  isGeni() {
1053
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1054
- const sour = get(head, "SOUR.value");
1055
- return !!sour?.toLowerCase()?.startsWith("geni");
1102
+ return this.sourStartsWith("geni");
1056
1103
  }
1057
1104
  isAncestry() {
1058
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1059
- const sour = get(head, "SOUR.value");
1060
- return !!sour?.toLowerCase()?.startsWith("ancestry");
1105
+ return this.sourStartsWith("ancestry");
1061
1106
  }
1062
1107
  isMyHeritage() {
1063
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1064
- const sour = get(head, "SOUR.value");
1065
- return !!sour?.toLowerCase()?.startsWith("myheritage");
1108
+ return this.sourStartsWith("myheritage");
1066
1109
  }
1067
1110
  /**
1068
1111
  * Get the source type as a string (for prefixing tree IDs and names)
@@ -1079,22 +1122,22 @@ var Common = class _Common {
1079
1122
  return void 0;
1080
1123
  }
1081
1124
  isFamilySearch() {
1082
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1083
- const sourName = get(head, "SOUR.NAME.value");
1084
- return sourName === "FamilySearch API";
1125
+ return this.getSourceHeads().some((head) => {
1126
+ const sourName = get(head, "SOUR.NAME.value");
1127
+ return sourName === "FamilySearch API";
1128
+ });
1085
1129
  }
1086
1130
  isGNO2GED() {
1087
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
1088
- const sour = get(head, "SOUR.value");
1089
- return sour === "GNO2GED";
1131
+ return this.getSourceHeads().some((head) => {
1132
+ const sour = get(head, "SOUR.value");
1133
+ return sour === "GNO2GED";
1134
+ });
1090
1135
  }
1091
1136
  getAncestryTreeId() {
1092
- const path = "HEAD.SOUR._TREE.RIN.value";
1093
- return get(this, path) || get(this.getGedcom(), path);
1137
+ return this.getFromSourceHeads("SOUR._TREE.RIN.value") || (get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value"));
1094
1138
  }
1095
1139
  getMyHeritageTreeId() {
1096
- const path = "HEAD._EXPORTED_FROM_SITE_ID.value";
1097
- return get(this, path) || get(this.getGedcom(), path);
1140
+ 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"));
1098
1141
  }
1099
1142
  getTreeId() {
1100
1143
  if (this?.isAncestry()) {
@@ -1128,8 +1171,7 @@ var Common = class _Common {
1128
1171
  return this.getUniversalTreeId();
1129
1172
  }
1130
1173
  getAncestryTreeName() {
1131
- const path = "HEAD.SOUR._TREE.value";
1132
- return get(this, path) || get(this.getGedcom(), path);
1174
+ return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
1133
1175
  }
1134
1176
  getMyHeritageTreeName() {
1135
1177
  const path = "HEAD.FILE.value";
@@ -1139,14 +1181,14 @@ var Common = class _Common {
1139
1181
  )?.groups?.tree;
1140
1182
  }
1141
1183
  getFamilySearchTreeId() {
1142
- const rin = get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
1184
+ const rin = this.getFromSourceHeads("SOUR._TREE.RIN.value") || get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
1143
1185
  if (rin) {
1144
1186
  return rin;
1145
1187
  }
1146
1188
  return "familysearch";
1147
1189
  }
1148
1190
  getFamilySearchTreeName() {
1149
- const treeName = get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
1191
+ const treeName = this.getFromSourceHeads("SOUR._TREE.value") || get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
1150
1192
  if (treeName) {
1151
1193
  return treeName;
1152
1194
  }
@@ -1238,11 +1280,11 @@ var Common = class _Common {
1238
1280
  const cleanName = fileName.replace(/\.ged$/i, "").replace(/_/g, " ");
1239
1281
  return `${prefix}${cleanName}`;
1240
1282
  }
1241
- const sourName = get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
1283
+ const sourName = this.getFromSourceHeads("SOUR.NAME.value") || get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
1242
1284
  if (sourName) {
1243
1285
  return `${prefix}${sourName}`;
1244
1286
  }
1245
- const sourValue = get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
1287
+ const sourValue = this.getFromSourceHeads("SOUR.value") || get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
1246
1288
  if (sourValue) {
1247
1289
  return `${prefix}${sourValue}`;
1248
1290
  }
@@ -1308,7 +1350,7 @@ var isOnlyMainProp = (key) => {
1308
1350
  };
1309
1351
  var isValidKey = (common, key) => {
1310
1352
  const prop = get(common, key);
1311
- 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);
1353
+ 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);
1312
1354
  };
1313
1355
  var getValidKeys = (common) => {
1314
1356
  return Object.keys(common).filter((key) => {
@@ -1802,6 +1844,334 @@ var dateFormatter = (indi, showMarriages = false, showDays = false, showPlaces =
1802
1844
  };
1803
1845
  };
1804
1846
 
1847
+ // src/utils/fact-edit.ts
1848
+ var SINGLETON_TAGS = /* @__PURE__ */ new Set(["BIRT", "DEAT", "SSN", "SEX", "DSCR"]);
1849
+ var firstRecord = (node) => {
1850
+ if (!node) {
1851
+ return void 0;
1852
+ }
1853
+ if (node instanceof List) {
1854
+ return node.index(0);
1855
+ }
1856
+ return node.index(0) ?? node;
1857
+ };
1858
+ var setDateOn = (gedcom, owner, parent, date) => {
1859
+ const trimmed = date.trim();
1860
+ if (!trimmed) {
1861
+ return;
1862
+ }
1863
+ let dateNode = parent.get("DATE");
1864
+ if (!dateNode || dateNode instanceof List) {
1865
+ dateNode = createCommonDate(gedcom, void 0, owner, parent);
1866
+ dateNode.type = "DATE";
1867
+ parent.set("DATE", dateNode);
1868
+ }
1869
+ dateNode.value = trimmed;
1870
+ };
1871
+ var fillFact = (gedcom, owner, event, input) => {
1872
+ if (input.value?.trim()) {
1873
+ event.value = input.value.trim();
1874
+ }
1875
+ if (input.type?.trim()) {
1876
+ event.set("TYPE", input.type.trim());
1877
+ }
1878
+ if (input.date) {
1879
+ setDateOn(gedcom, owner, event, input.date);
1880
+ }
1881
+ if (input.place?.trim()) {
1882
+ event.set("PLAC", input.place.trim());
1883
+ }
1884
+ if (input.note?.trim()) {
1885
+ event.set("NOTE", input.note.trim());
1886
+ }
1887
+ };
1888
+ var updateFactRecord = (owner, event, input) => {
1889
+ const gedcom = owner.getGedcom();
1890
+ if (!gedcom) {
1891
+ return event;
1892
+ }
1893
+ const asOwner = owner;
1894
+ if (input.value !== void 0) {
1895
+ const trimmed = input.value.trim();
1896
+ if (trimmed) {
1897
+ event.value = trimmed;
1898
+ } else {
1899
+ event.removeValue();
1900
+ }
1901
+ }
1902
+ if (input.type !== void 0) {
1903
+ const trimmed = input.type.trim();
1904
+ if (trimmed) {
1905
+ event.set("TYPE", trimmed);
1906
+ } else {
1907
+ event.remove("TYPE");
1908
+ }
1909
+ }
1910
+ if (input.date !== void 0) {
1911
+ const trimmed = input.date.trim();
1912
+ if (trimmed) {
1913
+ setDateOn(gedcom, asOwner, event, trimmed);
1914
+ } else {
1915
+ event.remove("DATE");
1916
+ }
1917
+ }
1918
+ if (input.place !== void 0) {
1919
+ const trimmed = input.place.trim();
1920
+ if (trimmed) {
1921
+ event.set("PLAC", trimmed);
1922
+ } else {
1923
+ event.remove("PLAC");
1924
+ }
1925
+ }
1926
+ if (input.note !== void 0) {
1927
+ const trimmed = input.note.trim();
1928
+ if (trimmed) {
1929
+ event.set("NOTE", trimmed);
1930
+ } else {
1931
+ event.remove("NOTE");
1932
+ }
1933
+ }
1934
+ return event;
1935
+ };
1936
+ var setFamilyMarriage = (fam, input) => {
1937
+ const existing = firstRecord(fam.get("MARR"));
1938
+ if (existing) {
1939
+ return updateFactRecord(fam, existing, {
1940
+ date: input.date,
1941
+ place: input.place
1942
+ });
1943
+ }
1944
+ return addFamilyFact(fam, {
1945
+ tag: "MARR",
1946
+ date: input.date,
1947
+ place: input.place
1948
+ });
1949
+ };
1950
+ var addIndividualFact = (indi, input) => {
1951
+ const gedcom = indi.getGedcom();
1952
+ if (!gedcom) {
1953
+ return void 0;
1954
+ }
1955
+ const tag = input.tag;
1956
+ let event = SINGLETON_TAGS.has(tag) ? firstRecord(indi.get(tag)) : void 0;
1957
+ if (!event) {
1958
+ event = createCommon(gedcom, void 0, indi);
1959
+ event.type = tag;
1960
+ if (SINGLETON_TAGS.has(tag)) {
1961
+ indi.set(tag, event);
1962
+ } else {
1963
+ indi.assign(tag, event);
1964
+ }
1965
+ }
1966
+ fillFact(gedcom, indi, event, input);
1967
+ return event;
1968
+ };
1969
+ var addFamilyFact = (fam, input) => {
1970
+ const gedcom = fam.getGedcom();
1971
+ if (!gedcom) {
1972
+ return void 0;
1973
+ }
1974
+ const event = createCommon(gedcom, void 0, fam);
1975
+ event.type = input.tag;
1976
+ fam.assign(input.tag, event);
1977
+ fillFact(gedcom, fam, event, input);
1978
+ return event;
1979
+ };
1980
+ var setCauseOfDeath = (indi, text) => {
1981
+ const trimmed = text.trim();
1982
+ if (!trimmed) {
1983
+ return void 0;
1984
+ }
1985
+ const death = firstRecord(indi.get("DEAT")) || addIndividualFact(indi, { tag: "DEAT", value: "Y" });
1986
+ if (!death) {
1987
+ return void 0;
1988
+ }
1989
+ death.set("CAUS", trimmed);
1990
+ return death;
1991
+ };
1992
+ var setDeceased = (indi, deceased) => {
1993
+ if (!deceased) {
1994
+ indi.remove("DEAT");
1995
+ return;
1996
+ }
1997
+ if (firstRecord(indi.get("DEAT"))) {
1998
+ return;
1999
+ }
2000
+ addIndividualFact(indi, { tag: "DEAT", value: "Y" });
2001
+ };
2002
+
2003
+ // src/utils/family-edit.ts
2004
+ var PEDIGREE_VALUES = [
2005
+ "birth" /* BIRTH */,
2006
+ "adopted" /* ADOPTED */,
2007
+ "foster" /* FOSTER */,
2008
+ "step" /* STEP */,
2009
+ "sealing" /* SEALING */
2010
+ ];
2011
+ var toGedcomPedigree = (value) => {
2012
+ const normalized = String(value || "birth" /* BIRTH */).toLowerCase();
2013
+ if (normalized === "biological" /* BIOLOGICAL */ || normalized === "birth" /* BIRTH */) {
2014
+ return "birth" /* BIRTH */;
2015
+ }
2016
+ const match = PEDIGREE_VALUES.find((item) => item === normalized);
2017
+ return match ?? "birth" /* BIRTH */;
2018
+ };
2019
+ var pointerIds = (tag) => {
2020
+ if (!tag) {
2021
+ return [];
2022
+ }
2023
+ if (tag instanceof List) {
2024
+ return tag.values().map((item) => item?.toValue?.()).filter((value2) => !!value2);
2025
+ }
2026
+ const value = tag.toValue?.();
2027
+ return value ? [String(value)] : [];
2028
+ };
2029
+ var firstPointerId = (tag) => pointerIds(tag)[0];
2030
+ var makePointer = (gedcom, parent, value, refType) => {
2031
+ const pointer = createCommon(gedcom, void 0, parent);
2032
+ pointer.value = value;
2033
+ pointer.refType = refType;
2034
+ return pointer;
2035
+ };
2036
+ var nextRecordId = (ids, prefix) => {
2037
+ let max = 0;
2038
+ ids.forEach((id) => {
2039
+ const match = String(id).match(/(\d+)/);
2040
+ const n = match ? Number.parseInt(match[1], 10) : 0;
2041
+ if (n > max) {
2042
+ max = n;
2043
+ }
2044
+ });
2045
+ return `@${prefix}${max + 1}@`;
2046
+ };
2047
+ var familyHasParent = (fam, indiId) => pointerIds(fam.get("HUSB")).includes(indiId) || pointerIds(fam.get("WIFE")).includes(indiId);
2048
+ var familyHasChild = (fam, indiId) => pointerIds(fam.get("CHIL")).includes(indiId);
2049
+ var spouseTagFor = (indi, fam) => {
2050
+ const husbId = firstPointerId(fam.get("HUSB"));
2051
+ const wifeId = firstPointerId(fam.get("WIFE"));
2052
+ if (husbId === indi.id) {
2053
+ return "HUSB";
2054
+ }
2055
+ if (wifeId === indi.id) {
2056
+ return "WIFE";
2057
+ }
2058
+ if (indi.isFemale()) {
2059
+ return wifeId && wifeId !== indi.id ? "HUSB" : "WIFE";
2060
+ }
2061
+ if (indi.isMale()) {
2062
+ return husbId && husbId !== indi.id ? "WIFE" : "HUSB";
2063
+ }
2064
+ return husbId ? "WIFE" : "HUSB";
2065
+ };
2066
+ var canAttachAsSpouse = (fam, indi) => {
2067
+ if (!indi.id) {
2068
+ return false;
2069
+ }
2070
+ if (familyHasParent(fam, indi.id)) {
2071
+ return true;
2072
+ }
2073
+ const tag = spouseTagFor(indi, fam);
2074
+ const occupied = firstPointerId(fam.get(tag));
2075
+ return !occupied || occupied === indi.id;
2076
+ };
2077
+ var pointerByValue = (tag, id) => {
2078
+ if (!tag) {
2079
+ return void 0;
2080
+ }
2081
+ const fromValues = tag.toValueList?.().item(id);
2082
+ if (fromValues) {
2083
+ return fromValues;
2084
+ }
2085
+ return tag.toList()?.values().find((item) => item?.toValue?.() === id);
2086
+ };
2087
+ var setChildPedigree = (fam, child, pedigree, parent) => {
2088
+ if (!fam.id || !child.id) {
2089
+ return;
2090
+ }
2091
+ const pedi = toGedcomPedigree(pedigree);
2092
+ const famc = pointerByValue(child.get("FAMC"), fam.id);
2093
+ if (famc) {
2094
+ famc.set("PEDI", pedi);
2095
+ }
2096
+ const chil = pointerByValue(fam.get("CHIL"), child.id);
2097
+ if (!chil) {
2098
+ return;
2099
+ }
2100
+ const setFather = !parent || parent.isMale() || parent.isUnknownSex();
2101
+ const setMother = !parent || parent.isFemale() || parent.isUnknownSex();
2102
+ if (setFather) {
2103
+ chil.set("_FREL", pedi);
2104
+ }
2105
+ if (setMother) {
2106
+ chil.set("_MREL", pedi);
2107
+ }
2108
+ };
2109
+ var attachSpouseToFamily = (fam, indi) => {
2110
+ const gedcom = fam.getGedcom() ?? indi.getGedcom();
2111
+ if (!indi.id || !fam.id || !gedcom) {
2112
+ return false;
2113
+ }
2114
+ if (!canAttachAsSpouse(fam, indi)) {
2115
+ return false;
2116
+ }
2117
+ const tag = spouseTagFor(indi, fam);
2118
+ if (firstPointerId(fam.get(tag)) !== indi.id) {
2119
+ fam.set(tag, makePointer(gedcom, fam, indi.id, "INDI"));
2120
+ }
2121
+ indi.assign("FAMS", makePointer(gedcom, indi, fam.id, "FAM"), true);
2122
+ return true;
2123
+ };
2124
+ var attachChildToFamily = (fam, child, pedigree, parent) => {
2125
+ const gedcom = fam.getGedcom() ?? child.getGedcom();
2126
+ if (!child.id || !fam.id || !gedcom) {
2127
+ return false;
2128
+ }
2129
+ if (!familyHasChild(fam, child.id)) {
2130
+ fam.assign("CHIL", makePointer(gedcom, fam, child.id, "INDI"), true);
2131
+ }
2132
+ const existingFamc = pointerByValue(child.get("FAMC"), fam.id);
2133
+ if (!existingFamc) {
2134
+ child.assign("FAMC", makePointer(gedcom, child, fam.id, "FAM"), true);
2135
+ }
2136
+ setChildPedigree(fam, child, pedigree, parent);
2137
+ return true;
2138
+ };
2139
+ var findReusableParentChildFamily = (parent, child) => {
2140
+ if (!parent.id || !child.id) {
2141
+ return void 0;
2142
+ }
2143
+ let reusableFamc;
2144
+ let already = false;
2145
+ child.getFamilies("FAMC")?.forEach((fam) => {
2146
+ if (!fam) {
2147
+ return;
2148
+ }
2149
+ if (familyHasParent(fam, parent.id) && familyHasChild(fam, child.id)) {
2150
+ already = true;
2151
+ return;
2152
+ }
2153
+ if (!reusableFamc && canAttachAsSpouse(fam, parent)) {
2154
+ reusableFamc = fam;
2155
+ }
2156
+ });
2157
+ if (already) {
2158
+ return "already";
2159
+ }
2160
+ if (reusableFamc) {
2161
+ return reusableFamc;
2162
+ }
2163
+ const parentFams = [];
2164
+ parent.getFamilies("FAMS")?.forEach((fam) => {
2165
+ if (fam) {
2166
+ parentFams.push(fam);
2167
+ }
2168
+ });
2169
+ if (parentFams.length === 1) {
2170
+ return parentFams[0];
2171
+ }
2172
+ return void 0;
2173
+ };
2174
+
1805
2175
  // src/utils/get-places.ts
1806
2176
  var PlaceType = /* @__PURE__ */ ((PlaceType2) => {
1807
2177
  PlaceType2["All"] = "ALL";
@@ -2445,6 +2815,31 @@ var Fam = class extends Common {
2445
2815
  getParents() {
2446
2816
  return this.getHusband().copy().merge(this.getWife());
2447
2817
  }
2818
+ hasParent(indi) {
2819
+ const indiId = typeof indi === "string" ? indi : indi?.id;
2820
+ return !!indiId && familyHasParent(this, indiId);
2821
+ }
2822
+ hasChild(indi) {
2823
+ const indiId = typeof indi === "string" ? indi : indi?.id;
2824
+ return !!indiId && familyHasChild(this, indiId);
2825
+ }
2826
+ canAddSpouse(indi) {
2827
+ return canAttachAsSpouse(this, indi);
2828
+ }
2829
+ addSpouse(indi) {
2830
+ return attachSpouseToFamily(this, indi);
2831
+ }
2832
+ addChild(child, pedigree, parent) {
2833
+ return attachChildToFamily(
2834
+ this,
2835
+ child,
2836
+ pedigree,
2837
+ parent
2838
+ );
2839
+ }
2840
+ addFact(input) {
2841
+ return addFamilyFact(this, input);
2842
+ }
2448
2843
  toList() {
2449
2844
  return new Families().concat(
2450
2845
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2550,19 +2945,51 @@ var ALLOWED_FACTS = [
2550
2945
  "OCCU",
2551
2946
  "OCCUPATIONS",
2552
2947
  "RESI",
2948
+ "BIRT",
2949
+ "DEAT",
2553
2950
  "BAPT",
2951
+ "BAPM",
2554
2952
  "CHRI",
2555
2953
  "CHR",
2954
+ "CHRA",
2556
2955
  "BURI",
2557
- "EDUC",
2956
+ "CREM",
2957
+ "ADOP",
2958
+ "BARM",
2959
+ "BASM",
2960
+ "BLES",
2961
+ "CONF",
2962
+ "FCOM",
2963
+ "ORDN",
2964
+ "NATU",
2965
+ "EMIG",
2966
+ "IMMI",
2967
+ "CENS",
2968
+ "PROB",
2969
+ "WILL",
2558
2970
  "GRAD",
2971
+ "RETI",
2972
+ "EDUC",
2559
2973
  "DIV",
2560
- "IMMI",
2561
2974
  "RELI",
2975
+ "CAST",
2976
+ "DSCR",
2977
+ "NATI",
2978
+ "PROP",
2979
+ "SSN",
2980
+ "TITL",
2981
+ "ADDR",
2982
+ "BAPL",
2983
+ "CONL",
2984
+ "ENDL",
2985
+ "SLGC",
2562
2986
  "_MILT",
2563
2987
  "_MILTID",
2564
2988
  "FACT",
2565
- "_ORIG"
2989
+ "_ORIG",
2990
+ "_WLNK",
2991
+ "WWW",
2992
+ "URL"
2566
2993
  ];
2567
2994
  var DISALLOWED_CUSTOM_FACTS = ["DNA Test", "Newspaper"];
2568
2995
  var CustomFactRenderers = {
@@ -3106,7 +3533,7 @@ var Indi = class extends Common {
3106
3533
  return result;
3107
3534
  }
3108
3535
  ancestryLink() {
3109
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3536
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3110
3537
  const tree = this.getAncestryTreeId();
3111
3538
  if (this.id) {
3112
3539
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -3118,7 +3545,7 @@ var Indi = class extends Common {
3118
3545
  async ancestryMedia(namespace) {
3119
3546
  const list = {};
3120
3547
  const objeList = this.get("OBJE")?.toList().copy();
3121
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3548
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3122
3549
  const tree = this.getAncestryTreeId();
3123
3550
  if (objeList) {
3124
3551
  await Promise.all(
@@ -3178,7 +3605,7 @@ var Indi = class extends Common {
3178
3605
  return list;
3179
3606
  }
3180
3607
  myheritageLink(poolId = 0) {
3181
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.value?.toLowerCase().replace(/^www\./gi, "");
3608
+ const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
3182
3609
  const site = this.getMyHeritageTreeId();
3183
3610
  const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
3184
3611
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
@@ -3699,6 +4126,57 @@ var Indi = class extends Common {
3699
4126
  isUnknownSex() {
3700
4127
  return !this.isMale() && !this.isFemale();
3701
4128
  }
4129
+ addSpouse(other) {
4130
+ if (!this.id || !other?.id || this.id === other.id || !this._gedcom) {
4131
+ return void 0;
4132
+ }
4133
+ const existing = getFamilyWith(this, other.id);
4134
+ if (existing) {
4135
+ return existing;
4136
+ }
4137
+ const fam = this._gedcom.createFamily();
4138
+ if (!fam.addSpouse(this) || !fam.addSpouse(other)) {
4139
+ return void 0;
4140
+ }
4141
+ return fam;
4142
+ }
4143
+ addChild(child, pedigree) {
4144
+ if (!this.id || !child?.id || this.id === child.id || !this._gedcom) {
4145
+ return void 0;
4146
+ }
4147
+ const existing = findReusableParentChildFamily(
4148
+ this,
4149
+ child
4150
+ );
4151
+ if (existing === "already") {
4152
+ const fam2 = child.getFamilies("FAMC")?.find(
4153
+ (item) => !!item && item.hasParent(this.id) && item.hasChild(child.id)
4154
+ );
4155
+ if (fam2) {
4156
+ setChildPedigree(
4157
+ fam2,
4158
+ child,
4159
+ pedigree,
4160
+ this
4161
+ );
4162
+ }
4163
+ return fam2;
4164
+ }
4165
+ const fam = existing ?? this._gedcom.createFamily();
4166
+ if (!fam.addSpouse(this)) {
4167
+ return void 0;
4168
+ }
4169
+ if (!fam.addChild(child, pedigree, this)) {
4170
+ return void 0;
4171
+ }
4172
+ return fam;
4173
+ }
4174
+ addParent(parent, pedigree) {
4175
+ return parent.addChild(this, pedigree);
4176
+ }
4177
+ addFact(input) {
4178
+ return addIndividualFact(this, input);
4179
+ }
3702
4180
  getParentType(id) {
3703
4181
  let indi;
3704
4182
  if (typeof id === "string" || typeof id === "number") {
@@ -3899,7 +4377,9 @@ var Indi = class extends Common {
3899
4377
  }
3900
4378
  }
3901
4379
  if (kinship !== "child" || !breakAfterSpouse) {
3902
- indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(indi.getFathers().copy().merge(indi.getMothers())).forEach((relative) => {
4380
+ indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(
4381
+ indi.getFathers().copy().merge(indi.getMothers())
4382
+ ).forEach((relative) => {
3903
4383
  if (!visited.has(relative)) {
3904
4384
  const currentRelation = indi.getParentType(relative);
3905
4385
  const isNonBiological = !!currentRelation && currentRelation !== "biological" /* BIOLOGICAL */;
@@ -6998,7 +7478,7 @@ var isCommonDate = (value) => {
6998
7478
  // package.json
6999
7479
  var package_default = {
7000
7480
  name: "@treeviz/gedcom-parser",
7001
- version: "2.0.10"};
7481
+ version: "2.0.12"};
7002
7482
 
7003
7483
  // src/utils/get-product-details.ts
7004
7484
  var isDevelopment = () => {
@@ -7202,6 +7682,44 @@ var GedCom = class extends Common {
7202
7682
  fams() {
7203
7683
  return this.getList("@@FAM");
7204
7684
  }
7685
+ nextIndiKey() {
7686
+ const ids = [];
7687
+ this.indis()?.forEach((_indi, id) => {
7688
+ ids.push(String(id));
7689
+ });
7690
+ return nextRecordId(ids, "I");
7691
+ }
7692
+ nextFamKey() {
7693
+ const ids = [];
7694
+ this.fams()?.forEach((_fam, id) => {
7695
+ ids.push(String(id));
7696
+ });
7697
+ return nextRecordId(ids, "F");
7698
+ }
7699
+ createIndividual() {
7700
+ const existing = this.indis();
7701
+ const indis = existing ?? new Individuals();
7702
+ if (!existing) {
7703
+ this.set("@@INDI", indis);
7704
+ }
7705
+ const id = this.nextIndiKey();
7706
+ const indi = createIndi(this, id);
7707
+ indi.type = "INDI";
7708
+ indis.item(id, indi);
7709
+ return indi;
7710
+ }
7711
+ createFamily() {
7712
+ const existing = this.fams();
7713
+ const fams = existing ?? new Families();
7714
+ if (!existing) {
7715
+ this.set("@@FAM", fams);
7716
+ }
7717
+ const id = this.nextFamKey();
7718
+ const fam = createFam(this, id);
7719
+ fam.type = "FAM";
7720
+ fams.item(id, fam);
7721
+ return fam;
7722
+ }
7205
7723
  objes() {
7206
7724
  return this.getList("@@OBJE");
7207
7725
  }
@@ -7289,15 +7807,24 @@ var GedCom = class extends Common {
7289
7807
  getDownloadHeader() {
7290
7808
  const newHead = createCommon();
7291
7809
  Object.assign(newHead, this.get("HEAD") ?? {});
7810
+ const existingSour = this.get("HEAD")?.get("SOUR");
7811
+ const existingTreeName = existingSour?.get("_TREE")?.toValue();
7812
+ const existingTreeUniqueId = existingSour?.get("_TID")?.toValue();
7292
7813
  const newSour = createCommon();
7293
- newSour.set("CORP", createCommon());
7294
- newSour.set("CORP.WWW", createCommon());
7295
- newSour.set("NAME", createCommon());
7296
- newSour.set("VERS", createCommon());
7297
- newSour.CORP.value = "TreeViz - The Family Tree Visualiser";
7298
- newSour.CORP.WWW.value = "treeviz.com";
7299
- newSour.NAME.value = "TreeViz - The Family Tree Visualiser";
7300
- newSour.VERS.value = getVersion();
7814
+ newSour.value = APP_BRANDING;
7815
+ newSour.set("NAME", APP_BRANDING);
7816
+ newSour.set("VERS", getVersion());
7817
+ if (typeof existingTreeName === "string" && existingTreeName.trim()) {
7818
+ newSour.set("_TREE", existingTreeName.trim());
7819
+ }
7820
+ if (typeof existingTreeUniqueId === "string" && existingTreeUniqueId.trim()) {
7821
+ newSour.set("_TID", existingTreeUniqueId.trim());
7822
+ }
7823
+ const corp = createCommon();
7824
+ corp.value = APP_BRANDING;
7825
+ corp.set("WWW", "treeviz.com");
7826
+ corp.set("EMAIL", "info@treeviz.com");
7827
+ newSour.set("CORP", corp);
7301
7828
  newHead.set("SOUR", newSour);
7302
7829
  return newHead;
7303
7830
  }
@@ -7326,15 +7853,17 @@ var GedCom = class extends Common {
7326
7853
  }
7327
7854
  toGedcom(tag, level, options) {
7328
7855
  if (options?.super) {
7329
- return super.toGedcom(tag, level, options);
7856
+ return appendGedcomTrailer(
7857
+ super.toGedcom(tag, level, options)
7858
+ );
7330
7859
  }
7331
7860
  const newGedcom = createGedCom();
7861
+ Object.assign(newGedcom, this);
7332
7862
  if (!options?.original) {
7333
7863
  Object.assign(newGedcom, {
7334
7864
  HEAD: this.getDownloadHeader()
7335
7865
  });
7336
7866
  }
7337
- Object.assign(newGedcom, this);
7338
7867
  if (options?.indis?.length) {
7339
7868
  const newContent = this.getIndiRelatedLists(options.indis);
7340
7869
  Object.assign(newGedcom, newContent);
@@ -7864,9 +8393,7 @@ var GedcomTree = {
7864
8393
  }, {}) : {};
7865
8394
  const links = new Individuals();
7866
8395
  const tags = [];
7867
- let lines = `${content.replace(/\n0 TRLR(\r?\n)*$/, "")}
7868
- 0 TRLR
7869
- `.split(/\r?\n/).reduce((acc, line) => {
8396
+ let lines = content.split(/\r?\n/).reduce((acc, line) => {
7870
8397
  const lineMatch = line.match(LINE_REG);
7871
8398
  if (lineMatch) {
7872
8399
  const lineIndent = Number(lineMatch?.groups?.indent ?? 0);
@@ -7879,6 +8406,13 @@ var GedcomTree = {
7879
8406
  linesAcc._hasFile = false;
7880
8407
  return acc;
7881
8408
  }
8409
+ if (lineIndent === 0 && lineType === "TRLR") {
8410
+ if (linesAcc._inHead && filename && !linesAcc._hasFile) {
8411
+ acc.push(`1 FILE ${filename}`);
8412
+ }
8413
+ linesAcc._inHead = false;
8414
+ return acc;
8415
+ }
7882
8416
  if (lineIndent === 0 && linesAcc._inHead) {
7883
8417
  if (filename && !linesAcc._hasFile) {
7884
8418
  acc.push(`1 FILE ${filename}`);
@@ -7933,8 +8467,9 @@ var GedcomTree = {
7933
8467
  ).replace(/0\s(@[^@]+@)(\sSOUR[^\n]*\n)([123456789]\s.+\n)*/, "").replace(/0 HEAD(.*)\n/, "0 HEAD$1\n1 _IS_PURGED true\n");
7934
8468
  lines = linesJoined.split("\n");
7935
8469
  }
8470
+ lines = lines.filter((line) => !isGedcomTrailerLine(line));
7936
8471
  lines.forEach((line, index) => {
7937
- if (line.length === 0) {
8472
+ if (line.length === 0 || isGedcomTrailerLine(line)) {
7938
8473
  return;
7939
8474
  }
7940
8475
  const match = reg.exec(line);
@@ -8071,5 +8606,20 @@ if (isDev) {
8071
8606
  globalThis.GedcomTree = GedcomTree;
8072
8607
  }
8073
8608
  var parser_default = GedcomTree;
8609
+ var createEmptyGedcom = (options) => {
8610
+ const { gedcom } = GedcomTree.parse(
8611
+ `0 HEAD
8612
+ 1 GEDC
8613
+ 2 VERS 5.5.1
8614
+ 2 FORM LINEAGE-LINKED
8615
+ 1 CHAR UTF-8
8616
+ 0 TRLR`,
8617
+ { filename: options?.filename }
8618
+ );
8619
+ if (!gedcom) {
8620
+ throw new Error("Failed to create empty GEDCOM");
8621
+ }
8622
+ return gedcom;
8623
+ };
8074
8624
 
8075
- export { ACCEPTED_DATE_FORMATS, ACCEPTED_DATE_FORMATS_REGEX, parser_default as GedcomTree, PlaceType, cacheDiscoveredPath, commonDateFormatter, create, dateFormatter, extractSeparationYears, extractSplitPoints, findMatchingRangeForSplitRange, fromTuple, generateSplitRanges, getAllProp, getFamilyWith, getFileExtension, getName, getPlaceParts, getPlaces, getRawSize, getVersion, hungarianOrdinalize, implemented, inRange, initializeCache, isDevelopment, isImageFormat, isIntersectedRange, isRangeContained, marriageDateFormatter, nameFormatter, notImplemented, noteDateFormatter, ordinalize, parseRangeBounds, pathCache, placeTranslator, profilePictureCache, relativesCache, resetRelativesCache, setNestedGroup, splitOverlappingRanges, splitRange };
8625
+ export { ACCEPTED_DATE_FORMATS, ACCEPTED_DATE_FORMATS_REGEX, GEDCOM_TRAILER_TAG, parser_default as GedcomTree, PEDIGREE_VALUES, PlaceType, addFamilyFact, addIndividualFact, appendGedcomTrailer, attachChildToFamily, attachSpouseToFamily, cacheDiscoveredPath, canAttachAsSpouse, commonDateFormatter, create, createEmptyGedcom, dateFormatter, extractSeparationYears, extractSplitPoints, familyHasChild, familyHasParent, findMatchingRangeForSplitRange, findReusableParentChildFamily, firstPointerId, fromTuple, generateSplitRanges, getAllProp, getFamilyWith, getFileExtension, getName, getPlaceParts, getPlaces, getRawSize, getVersion, hungarianOrdinalize, implemented, inRange, initializeCache, isDevelopment, isGedcomTrailerLine, isGedcomTrailerTag, isImageFormat, isIntersectedRange, isRangeContained, makePointer, marriageDateFormatter, nameFormatter, nextRecordId, notImplemented, noteDateFormatter, ordinalize, parseRangeBounds, pathCache, placeTranslator, pointerIds, profilePictureCache, relativesCache, resetRelativesCache, setCauseOfDeath, setChildPedigree, setDeceased, setFamilyMarriage, setNestedGroup, splitOverlappingRanges, splitRange, spouseTagFor, toGedcomPedigree, updateFactRecord };