@treeviz/gedcom-parser 2.0.10 → 2.1.0

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 (71) hide show
  1. package/README.md +236 -989
  2. package/dist/classes/common.d.ts +10 -5
  3. package/dist/classes/common.d.ts.map +1 -1
  4. package/dist/classes/date.d.ts +2 -0
  5. package/dist/classes/date.d.ts.map +1 -1
  6. package/dist/classes/fam.d.ts +10 -1
  7. package/dist/classes/fam.d.ts.map +1 -1
  8. package/dist/classes/fams.d.ts.map +1 -1
  9. package/dist/classes/gedcom.d.ts +45 -4
  10. package/dist/classes/gedcom.d.ts.map +1 -1
  11. package/dist/classes/index.js +1770 -279
  12. package/dist/classes/indi.d.ts +24 -1
  13. package/dist/classes/indi.d.ts.map +1 -1
  14. package/dist/classes/list.d.ts.map +1 -1
  15. package/dist/classes/note.d.ts.map +1 -1
  16. package/dist/classes/obje.d.ts +2 -1
  17. package/dist/classes/obje.d.ts.map +1 -1
  18. package/dist/cli/index.js +2802 -1296
  19. package/dist/constants/constants.d.ts +7 -0
  20. package/dist/constants/constants.d.ts.map +1 -1
  21. package/dist/constants/index.js +1599 -989
  22. package/dist/factories/index.js +719 -110
  23. package/dist/index.d.ts +12 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3837 -2028
  26. package/dist/interfaces/common.d.ts +6 -4
  27. package/dist/interfaces/common.d.ts.map +1 -1
  28. package/dist/interfaces/fam.d.ts +10 -1
  29. package/dist/interfaces/fam.d.ts.map +1 -1
  30. package/dist/interfaces/gedcom.d.ts +25 -1
  31. package/dist/interfaces/gedcom.d.ts.map +1 -1
  32. package/dist/interfaces/indi.d.ts +15 -1
  33. package/dist/interfaces/indi.d.ts.map +1 -1
  34. package/dist/kinship-translator/index.js +719 -110
  35. package/dist/structures/gedcom.d.ts +2 -0
  36. package/dist/structures/gedcom.d.ts.map +1 -1
  37. package/dist/types/types.d.ts +32 -4
  38. package/dist/types/types.d.ts.map +1 -1
  39. package/dist/utils/common-creator.d.ts.map +1 -1
  40. package/dist/utils/fact-edit.d.ts +34 -0
  41. package/dist/utils/fact-edit.d.ts.map +1 -0
  42. package/dist/utils/family-edit.d.ts +23 -0
  43. package/dist/utils/family-edit.d.ts.map +1 -0
  44. package/dist/utils/gedcom-trailer.d.ts +6 -0
  45. package/dist/utils/gedcom-trailer.d.ts.map +1 -0
  46. package/dist/utils/gedcom-version.d.ts +6 -0
  47. package/dist/utils/gedcom-version.d.ts.map +1 -0
  48. package/dist/utils/gedcom7-dates.d.ts +20 -0
  49. package/dist/utils/gedcom7-dates.d.ts.map +1 -0
  50. package/dist/utils/gedcom7-enumerations.d.ts +26 -0
  51. package/dist/utils/gedcom7-enumerations.d.ts.map +1 -0
  52. package/dist/utils/gedcom7-metadata.d.ts +19 -0
  53. package/dist/utils/gedcom7-metadata.d.ts.map +1 -0
  54. package/dist/utils/gedcom7-schema.d.ts +27 -0
  55. package/dist/utils/gedcom7-schema.d.ts.map +1 -0
  56. package/dist/utils/gedzip.d.ts +81 -0
  57. package/dist/utils/gedzip.d.ts.map +1 -0
  58. package/dist/utils/index.d.ts +13 -1
  59. package/dist/utils/index.d.ts.map +1 -1
  60. package/dist/utils/index.js +2225 -416
  61. package/dist/utils/local-media.d.ts +30 -0
  62. package/dist/utils/local-media.d.ts.map +1 -0
  63. package/dist/utils/media-utils.d.ts +10 -6
  64. package/dist/utils/media-utils.d.ts.map +1 -1
  65. package/dist/utils/multimedia.d.ts +38 -0
  66. package/dist/utils/multimedia.d.ts.map +1 -0
  67. package/dist/utils/parser.d.ts +3 -0
  68. package/dist/utils/parser.d.ts.map +1 -1
  69. package/dist/utils/uid.d.ts +6 -0
  70. package/dist/utils/uid.d.ts.map +1 -0
  71. package/package.json +2 -1
@@ -1846,6 +1846,11 @@ var inRange = (year, range, trueIfNoYear = false) => {
1846
1846
  // src/constants/constants.ts
1847
1847
  var ID_REG = /^@[_a-zA-Z0-9]+@/;
1848
1848
  var ID_GETTER_REG = /^(?<at>@)?(?<letter>[A-Z])?/i;
1849
+ var ORIG_HEAD_TAG = "_ORIGHEAD";
1850
+
1851
+ // src/utils/gedcom-trailer.ts
1852
+ var GEDCOM_TRAILER_TAG = "TRLR";
1853
+ var isGedcomTrailerTag = (tag) => tag === GEDCOM_TRAILER_TAG;
1849
1854
  var List = class _List {
1850
1855
  // length = 0;
1851
1856
  constructor(items) {
@@ -2221,11 +2226,17 @@ var List = class _List {
2221
2226
  const gedcom = [];
2222
2227
  this.entries().forEach(([_, item]) => {
2223
2228
  const validTag = getValidTag(tag);
2224
- const validKey = item.id;
2225
- const validValue = validKey ? validTag : item.exportValue();
2226
- gedcom.push(
2227
- `${level} ${validKey || validTag}${validValue ? ` ${validValue}` : ""}`
2228
- );
2229
+ const xref = item.id;
2230
+ const payload = item.exportValue?.();
2231
+ if (xref) {
2232
+ gedcom.push(
2233
+ `${level} ${xref} ${validTag}${payload ? ` ${payload}` : ""}`
2234
+ );
2235
+ } else {
2236
+ gedcom.push(
2237
+ `${level} ${validTag}${payload ? ` ${payload}` : ""}`
2238
+ );
2239
+ }
2229
2240
  gedcom.push(
2230
2241
  ...item.toGedcomLines(
2231
2242
  validTag,
@@ -2356,6 +2367,9 @@ var Common = class _Common {
2356
2367
  delete this._value;
2357
2368
  }
2358
2369
  set(name, value) {
2370
+ if (isGedcomTrailerTag(name)) {
2371
+ return void 0;
2372
+ }
2359
2373
  if (typeof value === "string") {
2360
2374
  const usedValue = createCommon(this._gedcom, void 0, this.main);
2361
2375
  usedValue.value = value;
@@ -2366,6 +2380,9 @@ var Common = class _Common {
2366
2380
  return get(this, name);
2367
2381
  }
2368
2382
  assign(name, value, unique = false) {
2383
+ if (isGedcomTrailerTag(name)) {
2384
+ return void 0;
2385
+ }
2369
2386
  let curValue = this.get(name);
2370
2387
  if (curValue?.isListable) {
2371
2388
  if (curValue instanceof _Common) {
@@ -2477,12 +2494,14 @@ var Common = class _Common {
2477
2494
  );
2478
2495
  }
2479
2496
  standardizeObject(tag, options) {
2480
- if (tag === "OBJE" && !options?.original && options?.obje?.standardize && options?.obje?.namespace && "standardizeMedia" in this) {
2497
+ if (tag === "OBJE" && options?.obje?.standardize && "standardizeMedia" in this) {
2481
2498
  const standardize = this.standardizeMedia;
2482
2499
  return standardize.call(
2483
2500
  this,
2484
2501
  options.obje.namespace,
2485
- options.obje.override
2502
+ options.obje.override,
2503
+ void 0,
2504
+ options.gedcomVersion
2486
2505
  );
2487
2506
  }
2488
2507
  }
@@ -2509,7 +2528,7 @@ var Common = class _Common {
2509
2528
  if (typeof prop?.toObject === "function") {
2510
2529
  if (prop instanceof _Common) {
2511
2530
  json[validKey] = prop.toObject(validKey, options);
2512
- } else {
2531
+ } else if (prop instanceof List) {
2513
2532
  json = {
2514
2533
  ...json,
2515
2534
  ...prop.toObject(validKey, options)
@@ -2567,8 +2586,12 @@ var Common = class _Common {
2567
2586
  this.standardizeObject(tag, options);
2568
2587
  const validKeys = getValidKeys(this);
2569
2588
  const gedcom = [];
2589
+ const isGedcom7Export = options?.gedcomVersion === "7.0";
2570
2590
  validKeys.forEach((key) => {
2571
2591
  const validKey = key;
2592
+ if (isGedcom7Export && validKey === "CONC") {
2593
+ return;
2594
+ }
2572
2595
  const prop = this.get(validKey);
2573
2596
  if (typeof prop?.toGedcomLines === "function") {
2574
2597
  if (prop instanceof _Common) {
@@ -2579,7 +2602,7 @@ var Common = class _Common {
2579
2602
  gedcom.push(
2580
2603
  ...prop.toGedcomLines(validKey, level + 1, options)
2581
2604
  );
2582
- } else {
2605
+ } else if (prop instanceof List) {
2583
2606
  gedcom.push(
2584
2607
  ...prop.toGedcomLines(validKey, level, options)
2585
2608
  );
@@ -2588,30 +2611,47 @@ var Common = class _Common {
2588
2611
  });
2589
2612
  return gedcom;
2590
2613
  }
2614
+ getHeadRecord() {
2615
+ return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2616
+ }
2617
+ getOriginalHeadRecord() {
2618
+ return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
2619
+ }
2620
+ getSourceHeads() {
2621
+ const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
2622
+ return heads.filter((head, index) => {
2623
+ return Boolean(head && heads.indexOf(head) === index);
2624
+ });
2625
+ }
2626
+ getFromSourceHeads(path) {
2627
+ for (const head of this.getSourceHeads()) {
2628
+ const value = get(head, path);
2629
+ if (value !== void 0 && value !== null && value !== "") {
2630
+ return value;
2631
+ }
2632
+ }
2633
+ return void 0;
2634
+ }
2635
+ sourStartsWith(prefix) {
2636
+ return this.getSourceHeads().some((head) => {
2637
+ const sour = get(head, "SOUR.value");
2638
+ return !!sour?.toLowerCase()?.startsWith(prefix);
2639
+ });
2640
+ }
2591
2641
  isGenoPro() {
2592
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2593
- const sour = get(head, "SOUR.value");
2594
- return !!sour?.toLowerCase()?.startsWith("genopro");
2642
+ return this.sourStartsWith("genopro");
2595
2643
  }
2596
2644
  isAhnenblatt() {
2597
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2598
- const sour = get(head, "SOUR.value");
2599
- return !!sour?.toLowerCase()?.startsWith("ahnenblatt");
2645
+ return this.sourStartsWith("ahnenblatt");
2600
2646
  }
2601
2647
  isGeni() {
2602
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2603
- const sour = get(head, "SOUR.value");
2604
- return !!sour?.toLowerCase()?.startsWith("geni");
2648
+ return this.sourStartsWith("geni");
2605
2649
  }
2606
2650
  isAncestry() {
2607
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2608
- const sour = get(head, "SOUR.value");
2609
- return !!sour?.toLowerCase()?.startsWith("ancestry");
2651
+ return this.sourStartsWith("ancestry");
2610
2652
  }
2611
2653
  isMyHeritage() {
2612
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2613
- const sour = get(head, "SOUR.value");
2614
- return !!sour?.toLowerCase()?.startsWith("myheritage");
2654
+ return this.sourStartsWith("myheritage");
2615
2655
  }
2616
2656
  /**
2617
2657
  * Get the source type as a string (for prefixing tree IDs and names)
@@ -2628,22 +2668,22 @@ var Common = class _Common {
2628
2668
  return void 0;
2629
2669
  }
2630
2670
  isFamilySearch() {
2631
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2632
- const sourName = get(head, "SOUR.NAME.value");
2633
- return sourName === "FamilySearch API";
2671
+ return this.getSourceHeads().some((head) => {
2672
+ const sourName = get(head, "SOUR.NAME.value");
2673
+ return sourName === "FamilySearch API";
2674
+ });
2634
2675
  }
2635
2676
  isGNO2GED() {
2636
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2637
- const sour = get(head, "SOUR.value");
2638
- return sour === "GNO2GED";
2677
+ return this.getSourceHeads().some((head) => {
2678
+ const sour = get(head, "SOUR.value");
2679
+ return sour === "GNO2GED";
2680
+ });
2639
2681
  }
2640
2682
  getAncestryTreeId() {
2641
- const path = "HEAD.SOUR._TREE.RIN.value";
2642
- return get(this, path) || get(this.getGedcom(), path);
2683
+ return this.getFromSourceHeads("SOUR._TREE.RIN.value") || (get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value"));
2643
2684
  }
2644
2685
  getMyHeritageTreeId() {
2645
- const path = "HEAD._EXPORTED_FROM_SITE_ID.value";
2646
- return get(this, path) || get(this.getGedcom(), path);
2686
+ 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"));
2647
2687
  }
2648
2688
  getTreeId() {
2649
2689
  if (this?.isAncestry()) {
@@ -2677,8 +2717,7 @@ var Common = class _Common {
2677
2717
  return this.getUniversalTreeId();
2678
2718
  }
2679
2719
  getAncestryTreeName() {
2680
- const path = "HEAD.SOUR._TREE.value";
2681
- return get(this, path) || get(this.getGedcom(), path);
2720
+ return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
2682
2721
  }
2683
2722
  getMyHeritageTreeName() {
2684
2723
  const path = "HEAD.FILE.value";
@@ -2688,14 +2727,14 @@ var Common = class _Common {
2688
2727
  )?.groups?.tree;
2689
2728
  }
2690
2729
  getFamilySearchTreeId() {
2691
- const rin = get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
2730
+ const rin = this.getFromSourceHeads("SOUR._TREE.RIN.value") || get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
2692
2731
  if (rin) {
2693
2732
  return rin;
2694
2733
  }
2695
2734
  return "familysearch";
2696
2735
  }
2697
2736
  getFamilySearchTreeName() {
2698
- const treeName = get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
2737
+ const treeName = this.getFromSourceHeads("SOUR._TREE.value") || get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
2699
2738
  if (treeName) {
2700
2739
  return treeName;
2701
2740
  }
@@ -2787,11 +2826,11 @@ var Common = class _Common {
2787
2826
  const cleanName = fileName.replace(/\.ged$/i, "").replace(/_/g, " ");
2788
2827
  return `${prefix}${cleanName}`;
2789
2828
  }
2790
- const sourName = get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
2829
+ const sourName = this.getFromSourceHeads("SOUR.NAME.value") || get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
2791
2830
  if (sourName) {
2792
2831
  return `${prefix}${sourName}`;
2793
2832
  }
2794
- const sourValue = get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
2833
+ const sourValue = this.getFromSourceHeads("SOUR.value") || get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
2795
2834
  if (sourValue) {
2796
2835
  return `${prefix}${sourValue}`;
2797
2836
  }
@@ -2857,7 +2896,7 @@ var isOnlyMainProp = (key) => {
2857
2896
  };
2858
2897
  var isValidKey = (common, key) => {
2859
2898
  const prop = get(common, key);
2860
- 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);
2899
+ 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);
2861
2900
  };
2862
2901
  var getValidKeys = (common) => {
2863
2902
  return Object.keys(common).filter((key) => {
@@ -2904,7 +2943,11 @@ var STANDARD_QUALIFIERS = {
2904
2943
  BEF: "Bef.",
2905
2944
  BEFORE: "Bef.",
2906
2945
  AFT: "Aft.",
2907
- AFTER: "Aft."
2946
+ AFTER: "Aft.",
2947
+ CAL: "Abt.",
2948
+ // calculated ≈ about for display
2949
+ EST: "Abt."
2950
+ // estimated ≈ about for display
2908
2951
  };
2909
2952
  var EXPORT_QUALIFIERS = {
2910
2953
  "Abt.": "ABT",
@@ -2912,6 +2955,11 @@ var EXPORT_QUALIFIERS = {
2912
2955
  "Aft.": "AFT"
2913
2956
  };
2914
2957
  var GEDCOM_MONTHS = /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\b/g;
2958
+ var CALENDAR_PREFIX = /^(?<calendar>GREGORIAN|JULIAN|FRENCH_R|HEBREW)\b\s+/i;
2959
+ var isGedcomDateValueForm = (value) => (
2960
+ // Require space/end after the token so display forms like "Abt." are not opaque.
2961
+ /^(FROM|TO|BET|AFT|BEF|ABT|CAL|EST|INT)(\s|$)/i.test(value) || /\bAND\b/i.test(value)
2962
+ );
2915
2963
  var NOTE_MARKER = "####";
2916
2964
  var formatDateWithComponents = (date, hasDay, hasMonth, hasYear, noteValue, baseFormat = "dd MMM yyyy", locale) => {
2917
2965
  const hasNote = baseFormat.includes("NOTE");
@@ -2936,29 +2984,45 @@ var CommonDate = class extends Common {
2936
2984
  super(gedcom, id, main, parent);
2937
2985
  delete this.id;
2938
2986
  }
2987
+ get calendar() {
2988
+ return this._calendar;
2989
+ }
2939
2990
  set value(value) {
2940
2991
  if (value) {
2941
- const noteRegExp = /^(?<note>[a-zA-Z]+\.)/;
2942
- const noteMatch = value.match(noteRegExp)?.groups;
2943
2992
  let validValue = value;
2993
+ const calendarMatch = validValue.match(CALENDAR_PREFIX);
2994
+ if (calendarMatch?.groups?.calendar) {
2995
+ this._calendar = calendarMatch.groups.calendar.toUpperCase();
2996
+ validValue = validValue.slice(calendarMatch[0].length);
2997
+ } else {
2998
+ this._calendar = void 0;
2999
+ }
3000
+ const noteRegExp = /^(?<note>[a-zA-Z]+\.)/;
3001
+ const noteMatch = validValue.match(noteRegExp)?.groups;
2944
3002
  if (noteMatch?.note) {
2945
3003
  this.NOTE = this.NOTE || createCommon(this._gedcom, void 0, this.main);
2946
3004
  this.NOTE.value = noteMatch?.note;
2947
- validValue = value.replace(noteRegExp, "");
3005
+ validValue = validValue.replace(noteRegExp, "");
2948
3006
  } else {
2949
- const qualifierMatch = value.match(
2950
- /^(?<qualifier>ABT|ABOUT|BEF|BEFORE|AFT|AFTER)\b\s*/i
3007
+ const qualifierMatch = validValue.match(
3008
+ /^(?<qualifier>ABT|ABOUT|BEF|BEFORE|AFT|AFTER|CAL|EST)\b\s*/i
2951
3009
  );
2952
3010
  const qualifier = qualifierMatch?.groups?.qualifier?.toUpperCase();
2953
3011
  const note = qualifier ? STANDARD_QUALIFIERS[qualifier] : void 0;
2954
3012
  if (note) {
2955
3013
  this.NOTE = this.NOTE || createCommon(this._gedcom, void 0, this.main);
2956
3014
  this.NOTE.value = note;
2957
- validValue = value.slice(qualifierMatch[0].length);
3015
+ validValue = validValue.slice(qualifierMatch[0].length);
2958
3016
  }
2959
3017
  }
2960
3018
  const acceptedDate = this.isValidDateFormat(validValue);
2961
- if (acceptedDate) {
3019
+ if (isGedcomDateValueForm(validValue.trim())) {
3020
+ delete this.DAY;
3021
+ delete this.MONTH;
3022
+ delete this.YEAR;
3023
+ this._date = void 0;
3024
+ this._value = value;
3025
+ } else if (acceptedDate) {
2962
3026
  this.DAY = this.DAY || createCommon(this._gedcom, void 0, this.main);
2963
3027
  this.DAY.value = format(acceptedDate, "dd");
2964
3028
  this.MONTH = this.MONTH || createCommon(this._gedcom, void 0, this.main);
@@ -3061,6 +3125,15 @@ var CommonDate = class extends Common {
3061
3125
  );
3062
3126
  }
3063
3127
  exportValue() {
3128
+ if (typeof this._value === "string") {
3129
+ const withoutCalendar = this._value.replace(CALENDAR_PREFIX, "");
3130
+ if (isGedcomDateValueForm(withoutCalendar.trim())) {
3131
+ return this._value.replace(
3132
+ /^((?:GREGORIAN|JULIAN|FRENCH_R|HEBREW)\s+)?(FROM|TO|BET|AFT|BEF|ABT|CAL|EST|INT)\b/i,
3133
+ (_m, cal, q) => `${cal || ""}${q.toUpperCase()}`
3134
+ );
3135
+ }
3136
+ }
3064
3137
  const formatted = this.toValue("NOTE dd MMM yyyy", null);
3065
3138
  if (!formatted) {
3066
3139
  return formatted;
@@ -3068,10 +3141,14 @@ var CommonDate = class extends Common {
3068
3141
  const note = this.NOTE?.value?.trim();
3069
3142
  const qualifier = note ? EXPORT_QUALIFIERS[note] : void 0;
3070
3143
  const standardized = qualifier ? formatted.replace(note, qualifier) : formatted;
3071
- return standardized.replace(
3144
+ const withMonths = standardized.replace(
3072
3145
  GEDCOM_MONTHS,
3073
3146
  (month) => month.toUpperCase()
3074
3147
  );
3148
+ if (this._calendar && this._calendar !== "GREGORIAN") {
3149
+ return `${this._calendar} ${withMonths}`;
3150
+ }
3151
+ return withMonths;
3075
3152
  }
3076
3153
  toGedcomLines(tag, level = 0, options) {
3077
3154
  const internal = new RegExp(
@@ -3532,6 +3609,257 @@ var cacheDiscoveredPath = (gedcom, path) => {
3532
3609
  }
3533
3610
  };
3534
3611
 
3612
+ // src/utils/fact-edit.ts
3613
+ var SINGLETON_TAGS = /* @__PURE__ */ new Set(["BIRT", "DEAT", "SSN", "SEX", "DSCR"]);
3614
+ var firstRecord = (node) => {
3615
+ if (!node) {
3616
+ return void 0;
3617
+ }
3618
+ if (node instanceof List) {
3619
+ return node.index(0);
3620
+ }
3621
+ return node.index(0) ?? node;
3622
+ };
3623
+ var setDateOn = (gedcom, owner, parent, date) => {
3624
+ const trimmed = date.trim();
3625
+ if (!trimmed) {
3626
+ return;
3627
+ }
3628
+ let dateNode = parent.get("DATE");
3629
+ if (!dateNode || dateNode instanceof List) {
3630
+ dateNode = createCommonDate(gedcom, void 0, owner, parent);
3631
+ dateNode.type = "DATE";
3632
+ parent.set("DATE", dateNode);
3633
+ }
3634
+ dateNode.value = trimmed;
3635
+ };
3636
+ var fillFact = (gedcom, owner, event, input) => {
3637
+ if (input.value?.trim()) {
3638
+ event.value = input.value.trim();
3639
+ }
3640
+ if (input.type?.trim()) {
3641
+ event.set("TYPE", input.type.trim());
3642
+ }
3643
+ if (input.date) {
3644
+ setDateOn(gedcom, owner, event, input.date);
3645
+ }
3646
+ if (input.place?.trim()) {
3647
+ event.set("PLAC", input.place.trim());
3648
+ }
3649
+ if (input.note?.trim()) {
3650
+ event.set("NOTE", input.note.trim());
3651
+ }
3652
+ };
3653
+ var addIndividualFact = (indi, input) => {
3654
+ const gedcom = indi.getGedcom();
3655
+ if (!gedcom) {
3656
+ return void 0;
3657
+ }
3658
+ const tag = input.tag;
3659
+ let event = SINGLETON_TAGS.has(tag) ? firstRecord(indi.get(tag)) : void 0;
3660
+ if (!event) {
3661
+ event = createCommon(gedcom, void 0, indi);
3662
+ event.type = tag;
3663
+ if (SINGLETON_TAGS.has(tag)) {
3664
+ indi.set(tag, event);
3665
+ } else {
3666
+ indi.assign(tag, event);
3667
+ }
3668
+ }
3669
+ fillFact(gedcom, indi, event, input);
3670
+ return event;
3671
+ };
3672
+ var addFamilyFact = (fam, input) => {
3673
+ const gedcom = fam.getGedcom();
3674
+ if (!gedcom) {
3675
+ return void 0;
3676
+ }
3677
+ const event = createCommon(gedcom, void 0, fam);
3678
+ event.type = input.tag;
3679
+ fam.assign(input.tag, event);
3680
+ fillFact(gedcom, fam, event, input);
3681
+ return event;
3682
+ };
3683
+ var addNonEvent = (owner, input) => {
3684
+ const gedcom = owner.getGedcom();
3685
+ const eventType = input.event?.trim().toUpperCase();
3686
+ if (!gedcom || !eventType) {
3687
+ return void 0;
3688
+ }
3689
+ const no = createCommon(gedcom, void 0, owner);
3690
+ no.type = "NO";
3691
+ no.value = eventType;
3692
+ if (input.date?.trim()) {
3693
+ setDateOn(gedcom, owner, no, input.date);
3694
+ }
3695
+ if (input.note?.trim()) {
3696
+ no.set("NOTE", input.note.trim());
3697
+ }
3698
+ owner.assign("NO", no);
3699
+ return no;
3700
+ };
3701
+
3702
+ // src/utils/family-edit.ts
3703
+ var PEDIGREE_VALUES = [
3704
+ "birth" /* BIRTH */,
3705
+ "adopted" /* ADOPTED */,
3706
+ "foster" /* FOSTER */,
3707
+ "step" /* STEP */,
3708
+ "sealing" /* SEALING */
3709
+ ];
3710
+ var toGedcomPedigree = (value) => {
3711
+ const normalized = String(value || "birth" /* BIRTH */).toLowerCase();
3712
+ if (normalized === "biological" /* BIOLOGICAL */ || normalized === "birth" /* BIRTH */) {
3713
+ return "birth" /* BIRTH */;
3714
+ }
3715
+ const match = PEDIGREE_VALUES.find((item) => item === normalized);
3716
+ return match ?? "birth" /* BIRTH */;
3717
+ };
3718
+ var pointerIds = (tag) => {
3719
+ if (!tag) {
3720
+ return [];
3721
+ }
3722
+ if (tag instanceof List) {
3723
+ return tag.values().map((item) => item?.toValue?.()).filter((value2) => !!value2);
3724
+ }
3725
+ const value = tag.toValue?.();
3726
+ return value ? [String(value)] : [];
3727
+ };
3728
+ var firstPointerId = (tag) => pointerIds(tag)[0];
3729
+ var makePointer = (gedcom, parent, value, refType) => {
3730
+ const pointer = createCommon(gedcom, void 0, parent);
3731
+ pointer.value = value;
3732
+ pointer.refType = refType;
3733
+ return pointer;
3734
+ };
3735
+ var familyHasParent = (fam, indiId) => pointerIds(fam.get("HUSB")).includes(indiId) || pointerIds(fam.get("WIFE")).includes(indiId);
3736
+ var familyHasChild = (fam, indiId) => pointerIds(fam.get("CHIL")).includes(indiId);
3737
+ var spouseTagFor = (indi, fam) => {
3738
+ const husbId = firstPointerId(fam.get("HUSB"));
3739
+ const wifeId = firstPointerId(fam.get("WIFE"));
3740
+ if (husbId === indi.id) {
3741
+ return "HUSB";
3742
+ }
3743
+ if (wifeId === indi.id) {
3744
+ return "WIFE";
3745
+ }
3746
+ if (indi.isFemale()) {
3747
+ return wifeId && wifeId !== indi.id ? "HUSB" : "WIFE";
3748
+ }
3749
+ if (indi.isMale()) {
3750
+ return husbId && husbId !== indi.id ? "WIFE" : "HUSB";
3751
+ }
3752
+ return husbId ? "WIFE" : "HUSB";
3753
+ };
3754
+ var canAttachAsSpouse = (fam, indi) => {
3755
+ if (!indi.id) {
3756
+ return false;
3757
+ }
3758
+ if (familyHasParent(fam, indi.id)) {
3759
+ return true;
3760
+ }
3761
+ const tag = spouseTagFor(indi, fam);
3762
+ const occupied = firstPointerId(fam.get(tag));
3763
+ return !occupied || occupied === indi.id;
3764
+ };
3765
+ var pointerByValue = (tag, id) => {
3766
+ if (!tag) {
3767
+ return void 0;
3768
+ }
3769
+ const fromValues = tag.toValueList?.().item(id);
3770
+ if (fromValues) {
3771
+ return fromValues;
3772
+ }
3773
+ return tag.toList()?.values().find((item) => item?.toValue?.() === id);
3774
+ };
3775
+ var setChildPedigree = (fam, child, pedigree, parent) => {
3776
+ if (!fam.id || !child.id) {
3777
+ return;
3778
+ }
3779
+ const pedi = toGedcomPedigree(pedigree);
3780
+ const famc = pointerByValue(child.get("FAMC"), fam.id);
3781
+ if (famc) {
3782
+ famc.set("PEDI", pedi);
3783
+ }
3784
+ const chil = pointerByValue(fam.get("CHIL"), child.id);
3785
+ if (!chil) {
3786
+ return;
3787
+ }
3788
+ const setFather = !parent || parent.isMale() || parent.isUnknownSex();
3789
+ const setMother = !parent || parent.isFemale() || parent.isUnknownSex();
3790
+ if (setFather) {
3791
+ chil.set("_FREL", pedi);
3792
+ }
3793
+ if (setMother) {
3794
+ chil.set("_MREL", pedi);
3795
+ }
3796
+ };
3797
+ var attachSpouseToFamily = (fam, indi) => {
3798
+ const gedcom = fam.getGedcom() ?? indi.getGedcom();
3799
+ if (!indi.id || !fam.id || !gedcom) {
3800
+ return false;
3801
+ }
3802
+ if (!canAttachAsSpouse(fam, indi)) {
3803
+ return false;
3804
+ }
3805
+ const tag = spouseTagFor(indi, fam);
3806
+ if (firstPointerId(fam.get(tag)) !== indi.id) {
3807
+ fam.set(tag, makePointer(gedcom, fam, indi.id, "INDI"));
3808
+ }
3809
+ indi.assign("FAMS", makePointer(gedcom, indi, fam.id, "FAM"), true);
3810
+ return true;
3811
+ };
3812
+ var attachChildToFamily = (fam, child, pedigree, parent) => {
3813
+ const gedcom = fam.getGedcom() ?? child.getGedcom();
3814
+ if (!child.id || !fam.id || !gedcom) {
3815
+ return false;
3816
+ }
3817
+ if (!familyHasChild(fam, child.id)) {
3818
+ fam.assign("CHIL", makePointer(gedcom, fam, child.id, "INDI"), true);
3819
+ }
3820
+ const existingFamc = pointerByValue(child.get("FAMC"), fam.id);
3821
+ if (!existingFamc) {
3822
+ child.assign("FAMC", makePointer(gedcom, child, fam.id, "FAM"), true);
3823
+ }
3824
+ setChildPedigree(fam, child, pedigree, parent);
3825
+ return true;
3826
+ };
3827
+ var findReusableParentChildFamily = (parent, child) => {
3828
+ if (!parent.id || !child.id) {
3829
+ return void 0;
3830
+ }
3831
+ let reusableFamc;
3832
+ let already = false;
3833
+ child.getFamilies("FAMC")?.forEach((fam) => {
3834
+ if (!fam) {
3835
+ return;
3836
+ }
3837
+ if (familyHasParent(fam, parent.id) && familyHasChild(fam, child.id)) {
3838
+ already = true;
3839
+ return;
3840
+ }
3841
+ if (!reusableFamc && canAttachAsSpouse(fam, parent)) {
3842
+ reusableFamc = fam;
3843
+ }
3844
+ });
3845
+ if (already) {
3846
+ return "already";
3847
+ }
3848
+ if (reusableFamc) {
3849
+ return reusableFamc;
3850
+ }
3851
+ const parentFams = [];
3852
+ parent.getFamilies("FAMS")?.forEach((fam) => {
3853
+ if (fam) {
3854
+ parentFams.push(fam);
3855
+ }
3856
+ });
3857
+ if (parentFams.length === 1) {
3858
+ return parentFams[0];
3859
+ }
3860
+ return void 0;
3861
+ };
3862
+
3535
3863
  // src/utils/get-places.ts
3536
3864
  var getPlaces = (common, type = ["ALL" /* All */], maxLevel = 1, level = 0, mainKey) => {
3537
3865
  const types = Array.isArray(type) ? type : [type];
@@ -3596,16 +3924,58 @@ var getPlaces = (common, type = ["ALL" /* All */], maxLevel = 1, level = 0, main
3596
3924
  });
3597
3925
  return places;
3598
3926
  };
3927
+
3928
+ // src/utils/local-media.ts
3929
+ var localMediaResolver;
3930
+ var isRemoteOrEmbeddedMediaUrl = (path) => {
3931
+ if (!path) {
3932
+ return false;
3933
+ }
3934
+ return path.startsWith("data:") || path.startsWith("blob:") || /^https?:\/\//i.test(path);
3935
+ };
3936
+ var resolveLocalMediaUrl = async (path) => {
3937
+ if (!path) {
3938
+ return void 0;
3939
+ }
3940
+ if (isRemoteOrEmbeddedMediaUrl(path)) {
3941
+ return path;
3942
+ }
3943
+ const resolved = await localMediaResolver?.(path);
3944
+ return resolved;
3945
+ };
3599
3946
  var implemented = (type, ...args) => {
3600
3947
  };
3601
3948
 
3602
3949
  // src/utils/media-utils.ts
3603
3950
  var getFileExtension = (filename) => {
3604
- const match = filename.match(/\.([^.]+)$/);
3605
- return match ? match[1] : "";
3951
+ if (!filename) {
3952
+ return "";
3953
+ }
3954
+ if (filename.startsWith("data:")) {
3955
+ const mime = filename.slice(5).split(";")[0] || "";
3956
+ const subtype = (mime.split("/")[1] || "").toLowerCase();
3957
+ if (subtype === "jpeg") {
3958
+ return "jpg";
3959
+ }
3960
+ return subtype.split("+")[0] || "";
3961
+ }
3962
+ const withoutQuery = filename.split(/[?#]/)[0] || filename;
3963
+ const match = withoutQuery.match(/\.([^.]+)$/);
3964
+ return match ? match[1].toLowerCase() : "";
3606
3965
  };
3607
3966
  var isImageFormat = (format2) => {
3608
- if (!format2) return false;
3967
+ if (!format2) {
3968
+ return false;
3969
+ }
3970
+ let normalized = format2.toLowerCase().trim();
3971
+ if (normalized.startsWith("data:")) {
3972
+ normalized = getFileExtension(normalized);
3973
+ } else if (normalized.includes("/")) {
3974
+ normalized = normalized.split("/")[1]?.split("+")[0] || "";
3975
+ if (normalized === "jpeg") {
3976
+ normalized = "jpg";
3977
+ }
3978
+ }
3609
3979
  const imageFormats = [
3610
3980
  "jpg",
3611
3981
  "jpeg",
@@ -3617,7 +3987,11 @@ var isImageFormat = (format2) => {
3617
3987
  "tiff",
3618
3988
  "tif"
3619
3989
  ];
3620
- return imageFormats.includes(format2.toLowerCase());
3990
+ return imageFormats.includes(normalized);
3991
+ };
3992
+ var resolveObjeForm = (obje) => {
3993
+ const value = obje?.get?.("FILE.FORM")?.toValue?.() ?? obje?.get?.("FORM")?.toValue?.();
3994
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
3621
3995
  };
3622
3996
  var uniqueItemsCache = /* @__PURE__ */ new WeakMap();
3623
3997
  var setNestedGroup = (obj, key, value, uniqueCounting = true) => {
@@ -4162,6 +4536,34 @@ var Fam = class extends Common {
4162
4536
  getParents() {
4163
4537
  return this.getHusband().copy().merge(this.getWife());
4164
4538
  }
4539
+ hasParent(indi) {
4540
+ const indiId = typeof indi === "string" ? indi : indi?.id;
4541
+ return !!indiId && familyHasParent(this, indiId);
4542
+ }
4543
+ hasChild(indi) {
4544
+ const indiId = typeof indi === "string" ? indi : indi?.id;
4545
+ return !!indiId && familyHasChild(this, indiId);
4546
+ }
4547
+ canAddSpouse(indi) {
4548
+ return canAttachAsSpouse(this, indi);
4549
+ }
4550
+ addSpouse(indi) {
4551
+ return attachSpouseToFamily(this, indi);
4552
+ }
4553
+ addChild(child, pedigree, parent) {
4554
+ return attachChildToFamily(
4555
+ this,
4556
+ child,
4557
+ pedigree,
4558
+ parent
4559
+ );
4560
+ }
4561
+ addFact(input) {
4562
+ return addFamilyFact(this, input);
4563
+ }
4564
+ addNonEvent(input) {
4565
+ return addNonEvent(this, input);
4566
+ }
4165
4567
  toList() {
4166
4568
  return new Families().concat(
4167
4569
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -4262,19 +4664,51 @@ var ALLOWED_FACTS = [
4262
4664
  "OCCU",
4263
4665
  "OCCUPATIONS",
4264
4666
  "RESI",
4667
+ "BIRT",
4668
+ "DEAT",
4265
4669
  "BAPT",
4670
+ "BAPM",
4266
4671
  "CHRI",
4267
4672
  "CHR",
4673
+ "CHRA",
4268
4674
  "BURI",
4269
- "EDUC",
4675
+ "CREM",
4676
+ "ADOP",
4677
+ "BARM",
4678
+ "BASM",
4679
+ "BLES",
4680
+ "CONF",
4681
+ "FCOM",
4682
+ "ORDN",
4683
+ "NATU",
4684
+ "EMIG",
4685
+ "IMMI",
4686
+ "CENS",
4687
+ "PROB",
4688
+ "WILL",
4270
4689
  "GRAD",
4690
+ "RETI",
4691
+ "EDUC",
4271
4692
  "DIV",
4272
- "IMMI",
4273
4693
  "RELI",
4694
+ "CAST",
4695
+ "DSCR",
4696
+ "NATI",
4697
+ "PROP",
4698
+ "SSN",
4699
+ "TITL",
4700
+ "ADDR",
4701
+ "BAPL",
4702
+ "CONL",
4703
+ "ENDL",
4704
+ "SLGC",
4274
4705
  "_MILT",
4275
4706
  "_MILTID",
4276
4707
  "FACT",
4277
- "_ORIG"
4708
+ "_ORIG",
4709
+ "_WLNK",
4710
+ "WWW",
4711
+ "URL"
4278
4712
  ];
4279
4713
  var DISALLOWED_CUSTOM_FACTS = ["DNA Test", "Newspaper"];
4280
4714
  var CustomFactRenderers = {
@@ -4818,7 +5252,7 @@ var Indi = class extends Common {
4818
5252
  return result;
4819
5253
  }
4820
5254
  ancestryLink() {
4821
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5255
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
4822
5256
  const tree = this.getAncestryTreeId();
4823
5257
  if (this.id) {
4824
5258
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -4830,7 +5264,7 @@ var Indi = class extends Common {
4830
5264
  async ancestryMedia(namespace) {
4831
5265
  const list = {};
4832
5266
  const objeList = this.get("OBJE")?.toList().copy();
4833
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5267
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
4834
5268
  const tree = this.getAncestryTreeId();
4835
5269
  if (objeList) {
4836
5270
  await Promise.all(
@@ -4848,10 +5282,14 @@ var Indi = class extends Common {
4848
5282
  const clone = obje?.get("_CLON._OID")?.toValue();
4849
5283
  const mser = obje?.get("_MSER._LKID")?.toValue();
4850
5284
  let url = obje?.get("FILE")?.toValue();
4851
- const title = obje?.get("TITL")?.toValue() ?? "";
4852
- const type = obje?.get("FORM")?.toValue() ?? "raw";
4853
- const imgId = clone || mser;
4854
- if (!www || !tree || !this.id) {
5285
+ const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
5286
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
5287
+ let imgId = clone || mser;
5288
+ const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
5289
+ if (!hasEmbeddedOrLocalFile && (!www || !tree || !this.id)) {
5290
+ return;
5291
+ }
5292
+ if (!this.id) {
4855
5293
  return;
4856
5294
  }
4857
5295
  if (!namespace && !url) {
@@ -4869,13 +5307,16 @@ var Indi = class extends Common {
4869
5307
  }
4870
5308
  url = url || `https://${www}/mediaui-viewer/tree/${tree}/media/${media}`;
4871
5309
  }
5310
+ if (!imgId && hasEmbeddedOrLocalFile && url) {
5311
+ imgId = typeof media === "string" && media || `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}`;
5312
+ }
4872
5313
  if (url && imgId) {
4873
- const id = `${tree}-${this.id}-${imgId}`;
5314
+ const id = `${tree || "gedzip"}-${this.id}-${imgId}`;
4874
5315
  list[id] = {
4875
5316
  key,
4876
5317
  isPrimary,
4877
5318
  id,
4878
- tree,
5319
+ tree: tree || "gedzip",
4879
5320
  imgId,
4880
5321
  person: this.id,
4881
5322
  title,
@@ -4890,7 +5331,7 @@ var Indi = class extends Common {
4890
5331
  return list;
4891
5332
  }
4892
5333
  myheritageLink(poolId = 0) {
4893
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.value?.toLowerCase().replace(/^www\./gi, "");
5334
+ const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
4894
5335
  const site = this.getMyHeritageTreeId();
4895
5336
  const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
4896
5337
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
@@ -4921,9 +5362,11 @@ var Indi = class extends Common {
4921
5362
  obje.standardizeMedia();
4922
5363
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
4923
5364
  const url = obje?.get("FILE")?.toValue();
4924
- const title = obje?.get("NOTE")?.toValue() ?? "";
4925
- const type = obje?.get("FORM")?.toValue() ?? "raw";
4926
- const imgId = obje?.get("_PHOTO_RIN")?.toValue();
5365
+ const title = obje?.get("FILE.NOTE")?.toValue() ?? obje?.get("NOTE")?.toValue() ?? "";
5366
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
5367
+ const photoRin = obje?.get("_PHOTO_RIN")?.toValue();
5368
+ const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
5369
+ const imgId = photoRin || (hasEmbeddedOrLocalFile && url ? `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}` : void 0);
4927
5370
  if (url && imgId) {
4928
5371
  const id = `${tree}-${this.id}-${imgId}`;
4929
5372
  list[id] = {
@@ -5046,10 +5489,10 @@ var Indi = class extends Common {
5046
5489
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
5047
5490
  const url = obje?.get("FILE")?.toValue();
5048
5491
  const title = obje?.get("TITL")?.toValue() ?? "";
5049
- const type = obje?.get("FORM")?.toValue() ?? "raw";
5492
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
5050
5493
  if (url) {
5051
5494
  const urlMatch = url.match(/\/([^/]+)\?hash=/);
5052
- const imgId = urlMatch?.[1] || `img-${index}-${Date.now().toString(36)}`;
5495
+ const imgId = urlMatch?.[1] || (url.startsWith("data:") ? `data-${index}-${getFileExtension(url) || "bin"}` : `img-${index}-${Date.now().toString(36)}`);
5053
5496
  const id = `geni-${geniId}-${imgId}`;
5054
5497
  list[id] = {
5055
5498
  isPrimary,
@@ -5086,10 +5529,10 @@ var Indi = class extends Common {
5086
5529
  obje.standardizeMedia();
5087
5530
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
5088
5531
  const url = obje?.get("FILE")?.toValue();
5089
- const title = obje?.get("TITL")?.toValue() ?? "";
5090
- const type = obje?.get("FORM")?.toValue() ?? "raw";
5532
+ const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
5533
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
5091
5534
  if (url) {
5092
- const imgId = `media-${index}-${url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
5535
+ const imgId = `media-${index}-${url.startsWith("data:") ? getFileExtension(url) || "bin" : url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
5093
5536
  const id = `${treeId}-${this.id}-${imgId}`;
5094
5537
  list[id] = {
5095
5538
  isPrimary,
@@ -5129,7 +5572,20 @@ var Indi = class extends Common {
5129
5572
  cacheKey
5130
5573
  );
5131
5574
  if (cached !== void 0) {
5132
- return cached;
5575
+ if (cached?.file && !isRemoteOrEmbeddedMediaUrl(cached.file)) {
5576
+ const resolvedCached = await resolveLocalMediaUrl(cached.file);
5577
+ if (resolvedCached && isRemoteOrEmbeddedMediaUrl(resolvedCached)) {
5578
+ const refreshed = { ...cached, file: resolvedCached };
5579
+ profilePictureCache(
5580
+ this._gedcom,
5581
+ cacheKey,
5582
+ refreshed
5583
+ );
5584
+ return refreshed;
5585
+ }
5586
+ } else {
5587
+ return cached;
5588
+ }
5133
5589
  }
5134
5590
  const mediaList = await this.multimedia(namespace);
5135
5591
  if (!mediaList) {
@@ -5141,39 +5597,40 @@ var Indi = class extends Common {
5141
5597
  return void 0;
5142
5598
  }
5143
5599
  const mediaArray = Object.values(mediaList);
5144
- const primaryMedia = mediaArray.find(
5145
- (media) => media.isPrimary && isImageFormat(media.contentType || getFileExtension(media.url))
5146
- );
5147
- if (primaryMedia) {
5148
- const result = {
5149
- file: primaryMedia.url,
5150
- form: primaryMedia.contentType,
5151
- title: primaryMedia.title,
5152
- isPrimary: true
5600
+ const toDisplayableProfile = async (media, isPrimary) => {
5601
+ const resolvedFile = await resolveLocalMediaUrl(media.url);
5602
+ if (!resolvedFile || !isRemoteOrEmbeddedMediaUrl(resolvedFile)) {
5603
+ return void 0;
5604
+ }
5605
+ return {
5606
+ file: resolvedFile,
5607
+ form: media.contentType,
5608
+ title: media.title,
5609
+ isPrimary
5153
5610
  };
5154
- profilePictureCache(this._gedcom, cacheKey, result);
5155
- return result;
5156
- }
5157
- if (onlyPrimary) {
5158
- profilePictureCache(
5159
- this._gedcom,
5160
- cacheKey,
5161
- void 0
5162
- );
5163
- return void 0;
5164
- }
5165
- const secondaryMedia = mediaArray.find(
5166
- (media) => isImageFormat(media.contentType || getFileExtension(media.url))
5611
+ };
5612
+ const candidates = mediaArray.filter(
5613
+ (media) => isImageFormat(
5614
+ media.contentType || media.url || getFileExtension(media.url)
5615
+ )
5167
5616
  );
5168
- if (secondaryMedia) {
5169
- const result = {
5170
- file: secondaryMedia.url,
5171
- form: secondaryMedia.contentType,
5172
- title: secondaryMedia.title,
5173
- isPrimary: false
5174
- };
5175
- profilePictureCache(this._gedcom, cacheKey, result);
5176
- return result;
5617
+ const ordered = [
5618
+ ...candidates.filter((media) => media.isPrimary),
5619
+ ...candidates.filter((media) => !media.isPrimary)
5620
+ ];
5621
+ for (const media of ordered) {
5622
+ if (onlyPrimary && !media.isPrimary) {
5623
+ break;
5624
+ }
5625
+ const result = await toDisplayableProfile(media, !!media.isPrimary);
5626
+ if (result) {
5627
+ profilePictureCache(
5628
+ this._gedcom,
5629
+ cacheKey,
5630
+ result
5631
+ );
5632
+ return result;
5633
+ }
5177
5634
  }
5178
5635
  profilePictureCache(
5179
5636
  this._gedcom,
@@ -5411,6 +5868,156 @@ var Indi = class extends Common {
5411
5868
  isUnknownSex() {
5412
5869
  return !this.isMale() && !this.isFemale();
5413
5870
  }
5871
+ addSpouse(other) {
5872
+ if (!this.id || !other?.id || this.id === other.id || !this._gedcom) {
5873
+ return void 0;
5874
+ }
5875
+ const existing = getFamilyWith(this, other.id);
5876
+ if (existing) {
5877
+ return existing;
5878
+ }
5879
+ const fam = this._gedcom.createFamily();
5880
+ if (!fam.addSpouse(this) || !fam.addSpouse(other)) {
5881
+ return void 0;
5882
+ }
5883
+ return fam;
5884
+ }
5885
+ addChild(child, pedigree) {
5886
+ if (!this.id || !child?.id || this.id === child.id || !this._gedcom) {
5887
+ return void 0;
5888
+ }
5889
+ const existing = findReusableParentChildFamily(
5890
+ this,
5891
+ child
5892
+ );
5893
+ if (existing === "already") {
5894
+ const fam2 = child.getFamilies("FAMC")?.find(
5895
+ (item) => !!item && item.hasParent(this.id) && item.hasChild(child.id)
5896
+ );
5897
+ if (fam2) {
5898
+ setChildPedigree(
5899
+ fam2,
5900
+ child,
5901
+ pedigree,
5902
+ this
5903
+ );
5904
+ }
5905
+ return fam2;
5906
+ }
5907
+ const fam = existing ?? this._gedcom.createFamily();
5908
+ if (!fam.addSpouse(this)) {
5909
+ return void 0;
5910
+ }
5911
+ if (!fam.addChild(child, pedigree, this)) {
5912
+ return void 0;
5913
+ }
5914
+ return fam;
5915
+ }
5916
+ addParent(parent, pedigree) {
5917
+ return parent.addChild(this, pedigree);
5918
+ }
5919
+ /**
5920
+ * Link an existing top-level OBJE record to this individual (`1 OBJE @On@`).
5921
+ */
5922
+ attachMultimedia(objeOrKey, options) {
5923
+ if (!this._gedcom || !this.id) {
5924
+ return void 0;
5925
+ }
5926
+ const obje = typeof objeOrKey === "string" ? this._gedcom.obje(objeOrKey) : objeOrKey;
5927
+ if (!obje?.id) {
5928
+ return void 0;
5929
+ }
5930
+ const already = this.get("OBJE")?.toList()?.values()?.some((item) => item?.toValue?.() === obje.id);
5931
+ if (!already) {
5932
+ const pointer = makePointer(
5933
+ this._gedcom,
5934
+ this,
5935
+ obje.id,
5936
+ "OBJE"
5937
+ );
5938
+ this.assign("OBJE", pointer);
5939
+ }
5940
+ if (options?.primary) {
5941
+ this.get("OBJE")?.toList()?.forEach((item) => {
5942
+ const target = item?.refType === "OBJE" ? this._gedcom?.obje(
5943
+ String(item.toValue())
5944
+ ) : item;
5945
+ if (!target) {
5946
+ return;
5947
+ }
5948
+ if (target.id === obje.id) {
5949
+ target.set("_PRIM", "Y");
5950
+ } else if (target.get("_PRIM")?.toValue() === "Y") {
5951
+ target.set("_PRIM", "N");
5952
+ }
5953
+ });
5954
+ obje.set("_PRIM", "Y");
5955
+ }
5956
+ return obje;
5957
+ }
5958
+ detachMultimedia(objeKey) {
5959
+ const list = this.get("OBJE")?.toList();
5960
+ if (!list) {
5961
+ return;
5962
+ }
5963
+ const toRemove = list.values().filter(
5964
+ (item) => item?.toValue?.() === objeKey || item?.id === objeKey
5965
+ );
5966
+ toRemove.forEach((item) => {
5967
+ if (item) {
5968
+ list.delete(item);
5969
+ }
5970
+ });
5971
+ if (list.length === 0) {
5972
+ this.remove("OBJE");
5973
+ }
5974
+ }
5975
+ /**
5976
+ * Create a multimedia record and attach it to this person.
5977
+ */
5978
+ attachMediaFromUrl(url, options) {
5979
+ if (!this._gedcom || !url) {
5980
+ return void 0;
5981
+ }
5982
+ const obje = this._gedcom.createMultimediaRecord({
5983
+ file: url,
5984
+ form: options?.form,
5985
+ title: options?.title,
5986
+ mediType: options?.mediType,
5987
+ primary: options?.primary,
5988
+ gedcomVersion: options?.gedcomVersion
5989
+ });
5990
+ return this.attachMultimedia(obje, { primary: options?.primary });
5991
+ }
5992
+ /**
5993
+ * Link a shared SNOTE record via `1 NOTE @Nn@` (GEDCOM 7).
5994
+ */
5995
+ attachSharedNote(noteOrKey) {
5996
+ if (!this._gedcom || !this.id) {
5997
+ return void 0;
5998
+ }
5999
+ const note = typeof noteOrKey === "string" ? this._gedcom.snote(noteOrKey) : noteOrKey;
6000
+ if (!note?.id) {
6001
+ return void 0;
6002
+ }
6003
+ const already = this.get("NOTE")?.toList()?.values()?.some((item) => item?.toValue?.() === note.id);
6004
+ if (!already) {
6005
+ const pointer = makePointer(
6006
+ this._gedcom,
6007
+ this,
6008
+ note.id,
6009
+ "SNOTE"
6010
+ );
6011
+ this.assign("NOTE", pointer);
6012
+ }
6013
+ return note;
6014
+ }
6015
+ addFact(input) {
6016
+ return addIndividualFact(this, input);
6017
+ }
6018
+ addNonEvent(input) {
6019
+ return addNonEvent(this, input);
6020
+ }
5414
6021
  getParentType(id) {
5415
6022
  let indi;
5416
6023
  if (typeof id === "string" || typeof id === "number") {
@@ -5451,7 +6058,7 @@ var Indi = class extends Common {
5451
6058
  if (!famChild) {
5452
6059
  const childIndi = this._gedcom?.indi(child.id);
5453
6060
  const childState = childIndi?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
5454
- childType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
6061
+ childType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
5455
6062
  } else {
5456
6063
  childType = famChild.get(usedRel)?.toValue() || "biological" /* BIOLOGICAL */;
5457
6064
  }
@@ -5611,7 +6218,9 @@ var Indi = class extends Common {
5611
6218
  }
5612
6219
  }
5613
6220
  if (kinship !== "child" || !breakAfterSpouse) {
5614
- indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(indi.getFathers().copy().merge(indi.getMothers())).forEach((relative) => {
6221
+ indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(
6222
+ indi.getFathers().copy().merge(indi.getMothers())
6223
+ ).forEach((relative) => {
5615
6224
  if (!visited.has(relative)) {
5616
6225
  const currentRelation = indi.getParentType(relative);
5617
6226
  const isNonBiological = !!currentRelation && currentRelation !== "biological" /* BIOLOGICAL */;
@@ -6010,7 +6619,7 @@ var Indi = class extends Common {
6010
6619
  return;
6011
6620
  }
6012
6621
  const childState = child?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
6013
- const childType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
6622
+ const childType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
6014
6623
  if (childType?.toLowerCase() === filter.PEDI.toLowerCase()) {
6015
6624
  children.append(child);
6016
6625
  }
@@ -6118,7 +6727,7 @@ var Indi = class extends Common {
6118
6727
  const child = this.id && family.CHIL?.toValueList()?.item(this.id);
6119
6728
  if (!child) {
6120
6729
  const childState = this?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
6121
- const parentType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
6730
+ const parentType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
6122
6731
  if (parentType?.toLowerCase() === filter.PEDI.toLowerCase()) {
6123
6732
  parents.merge(family.getParents());
6124
6733
  }