@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
@@ -1,5 +1,6 @@
1
1
  import { format, isValid, parse } from 'date-fns';
2
2
  import { debounce, set, get, uniqBy, unset, difference, intersectionBy, differenceBy } from 'lodash-es';
3
+ import JSZip from 'jszip';
3
4
 
4
5
  var __defProp = Object.defineProperty;
5
6
  var __export = (target, all) => {
@@ -82,8 +83,28 @@ __export(translators_exports, {
82
83
  });
83
84
 
84
85
  // src/constants/constants.ts
86
+ var APP_BRANDING = "TreeViz - The Family Tree Visualiser & Editor";
85
87
  var ID_REG = /^@[_a-zA-Z0-9]+@/;
86
88
  var ID_GETTER_REG = /^(?<at>@)?(?<letter>[A-Z])?/i;
89
+ var ORIG_HEAD_TAG = "_ORIGHEAD";
90
+
91
+ // src/utils/gedcom-trailer.ts
92
+ var TRAILER_LINE = /^0 TRLR\s*$/i;
93
+ var GEDCOM_TRAILER_TAG = "TRLR";
94
+ var isGedcomTrailerTag = (tag) => tag === GEDCOM_TRAILER_TAG;
95
+ var isGedcomTrailerLine = (line) => TRAILER_LINE.test(line);
96
+ var appendGedcomTrailer = (serialized) => {
97
+ const newline = serialized.includes("\r\n") ? "\r\n" : "\n";
98
+ const trimmed = serialized.replace(/(?:\r?\n)+$/, "");
99
+ if (!trimmed) {
100
+ return "0 TRLR";
101
+ }
102
+ const lastLine = trimmed.split(/\r?\n/).at(-1) ?? "";
103
+ if (isGedcomTrailerLine(lastLine)) {
104
+ return trimmed;
105
+ }
106
+ return `${trimmed}${newline}0 TRLR`;
107
+ };
87
108
  var List = class _List {
88
109
  // length = 0;
89
110
  constructor(items) {
@@ -459,11 +480,17 @@ var List = class _List {
459
480
  const gedcom = [];
460
481
  this.entries().forEach(([_, item]) => {
461
482
  const validTag = getValidTag(tag);
462
- const validKey = item.id;
463
- const validValue = validKey ? validTag : item.exportValue();
464
- gedcom.push(
465
- `${level} ${validKey || validTag}${validValue ? ` ${validValue}` : ""}`
466
- );
483
+ const xref = item.id;
484
+ const payload = item.exportValue?.();
485
+ if (xref) {
486
+ gedcom.push(
487
+ `${level} ${xref} ${validTag}${payload ? ` ${payload}` : ""}`
488
+ );
489
+ } else {
490
+ gedcom.push(
491
+ `${level} ${validTag}${payload ? ` ${payload}` : ""}`
492
+ );
493
+ }
467
494
  gedcom.push(
468
495
  ...item.toGedcomLines(
469
496
  validTag,
@@ -594,6 +621,9 @@ var Common = class _Common {
594
621
  delete this._value;
595
622
  }
596
623
  set(name, value) {
624
+ if (isGedcomTrailerTag(name)) {
625
+ return void 0;
626
+ }
597
627
  if (typeof value === "string") {
598
628
  const usedValue = createCommon(this._gedcom, void 0, this.main);
599
629
  usedValue.value = value;
@@ -604,6 +634,9 @@ var Common = class _Common {
604
634
  return get(this, name);
605
635
  }
606
636
  assign(name, value, unique = false) {
637
+ if (isGedcomTrailerTag(name)) {
638
+ return void 0;
639
+ }
607
640
  let curValue = this.get(name);
608
641
  if (curValue?.isListable) {
609
642
  if (curValue instanceof _Common) {
@@ -715,12 +748,14 @@ var Common = class _Common {
715
748
  );
716
749
  }
717
750
  standardizeObject(tag, options) {
718
- if (tag === "OBJE" && !options?.original && options?.obje?.standardize && options?.obje?.namespace && "standardizeMedia" in this) {
751
+ if (tag === "OBJE" && options?.obje?.standardize && "standardizeMedia" in this) {
719
752
  const standardize = this.standardizeMedia;
720
753
  return standardize.call(
721
754
  this,
722
755
  options.obje.namespace,
723
- options.obje.override
756
+ options.obje.override,
757
+ void 0,
758
+ options.gedcomVersion
724
759
  );
725
760
  }
726
761
  }
@@ -747,7 +782,7 @@ var Common = class _Common {
747
782
  if (typeof prop?.toObject === "function") {
748
783
  if (prop instanceof _Common) {
749
784
  json[validKey] = prop.toObject(validKey, options);
750
- } else {
785
+ } else if (prop instanceof List) {
751
786
  json = {
752
787
  ...json,
753
788
  ...prop.toObject(validKey, options)
@@ -805,8 +840,12 @@ var Common = class _Common {
805
840
  this.standardizeObject(tag, options);
806
841
  const validKeys = getValidKeys(this);
807
842
  const gedcom = [];
843
+ const isGedcom7Export = options?.gedcomVersion === "7.0";
808
844
  validKeys.forEach((key) => {
809
845
  const validKey = key;
846
+ if (isGedcom7Export && validKey === "CONC") {
847
+ return;
848
+ }
810
849
  const prop = this.get(validKey);
811
850
  if (typeof prop?.toGedcomLines === "function") {
812
851
  if (prop instanceof _Common) {
@@ -817,7 +856,7 @@ var Common = class _Common {
817
856
  gedcom.push(
818
857
  ...prop.toGedcomLines(validKey, level + 1, options)
819
858
  );
820
- } else {
859
+ } else if (prop instanceof List) {
821
860
  gedcom.push(
822
861
  ...prop.toGedcomLines(validKey, level, options)
823
862
  );
@@ -826,30 +865,47 @@ var Common = class _Common {
826
865
  });
827
866
  return gedcom;
828
867
  }
868
+ getHeadRecord() {
869
+ return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
870
+ }
871
+ getOriginalHeadRecord() {
872
+ return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
873
+ }
874
+ getSourceHeads() {
875
+ const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
876
+ return heads.filter((head, index) => {
877
+ return Boolean(head && heads.indexOf(head) === index);
878
+ });
879
+ }
880
+ getFromSourceHeads(path) {
881
+ for (const head of this.getSourceHeads()) {
882
+ const value = get(head, path);
883
+ if (value !== void 0 && value !== null && value !== "") {
884
+ return value;
885
+ }
886
+ }
887
+ return void 0;
888
+ }
889
+ sourStartsWith(prefix) {
890
+ return this.getSourceHeads().some((head) => {
891
+ const sour = get(head, "SOUR.value");
892
+ return !!sour?.toLowerCase()?.startsWith(prefix);
893
+ });
894
+ }
829
895
  isGenoPro() {
830
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
831
- const sour = get(head, "SOUR.value");
832
- return !!sour?.toLowerCase()?.startsWith("genopro");
896
+ return this.sourStartsWith("genopro");
833
897
  }
834
898
  isAhnenblatt() {
835
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
836
- const sour = get(head, "SOUR.value");
837
- return !!sour?.toLowerCase()?.startsWith("ahnenblatt");
899
+ return this.sourStartsWith("ahnenblatt");
838
900
  }
839
901
  isGeni() {
840
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
841
- const sour = get(head, "SOUR.value");
842
- return !!sour?.toLowerCase()?.startsWith("geni");
902
+ return this.sourStartsWith("geni");
843
903
  }
844
904
  isAncestry() {
845
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
846
- const sour = get(head, "SOUR.value");
847
- return !!sour?.toLowerCase()?.startsWith("ancestry");
905
+ return this.sourStartsWith("ancestry");
848
906
  }
849
907
  isMyHeritage() {
850
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
851
- const sour = get(head, "SOUR.value");
852
- return !!sour?.toLowerCase()?.startsWith("myheritage");
908
+ return this.sourStartsWith("myheritage");
853
909
  }
854
910
  /**
855
911
  * Get the source type as a string (for prefixing tree IDs and names)
@@ -866,22 +922,22 @@ var Common = class _Common {
866
922
  return void 0;
867
923
  }
868
924
  isFamilySearch() {
869
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
870
- const sourName = get(head, "SOUR.NAME.value");
871
- return sourName === "FamilySearch API";
925
+ return this.getSourceHeads().some((head) => {
926
+ const sourName = get(head, "SOUR.NAME.value");
927
+ return sourName === "FamilySearch API";
928
+ });
872
929
  }
873
930
  isGNO2GED() {
874
- const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
875
- const sour = get(head, "SOUR.value");
876
- return sour === "GNO2GED";
931
+ return this.getSourceHeads().some((head) => {
932
+ const sour = get(head, "SOUR.value");
933
+ return sour === "GNO2GED";
934
+ });
877
935
  }
878
936
  getAncestryTreeId() {
879
- const path = "HEAD.SOUR._TREE.RIN.value";
880
- return get(this, path) || get(this.getGedcom(), path);
937
+ return this.getFromSourceHeads("SOUR._TREE.RIN.value") || (get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value"));
881
938
  }
882
939
  getMyHeritageTreeId() {
883
- const path = "HEAD._EXPORTED_FROM_SITE_ID.value";
884
- return get(this, path) || get(this.getGedcom(), path);
940
+ 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
941
  }
886
942
  getTreeId() {
887
943
  if (this?.isAncestry()) {
@@ -915,8 +971,7 @@ var Common = class _Common {
915
971
  return this.getUniversalTreeId();
916
972
  }
917
973
  getAncestryTreeName() {
918
- const path = "HEAD.SOUR._TREE.value";
919
- return get(this, path) || get(this.getGedcom(), path);
974
+ return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
920
975
  }
921
976
  getMyHeritageTreeName() {
922
977
  const path = "HEAD.FILE.value";
@@ -926,14 +981,14 @@ var Common = class _Common {
926
981
  )?.groups?.tree;
927
982
  }
928
983
  getFamilySearchTreeId() {
929
- const rin = get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
984
+ const rin = this.getFromSourceHeads("SOUR._TREE.RIN.value") || get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
930
985
  if (rin) {
931
986
  return rin;
932
987
  }
933
988
  return "familysearch";
934
989
  }
935
990
  getFamilySearchTreeName() {
936
- const treeName = get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
991
+ const treeName = this.getFromSourceHeads("SOUR._TREE.value") || get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
937
992
  if (treeName) {
938
993
  return treeName;
939
994
  }
@@ -1025,11 +1080,11 @@ var Common = class _Common {
1025
1080
  const cleanName = fileName.replace(/\.ged$/i, "").replace(/_/g, " ");
1026
1081
  return `${prefix}${cleanName}`;
1027
1082
  }
1028
- const sourName = get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
1083
+ const sourName = this.getFromSourceHeads("SOUR.NAME.value") || get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
1029
1084
  if (sourName) {
1030
1085
  return `${prefix}${sourName}`;
1031
1086
  }
1032
- const sourValue = get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
1087
+ const sourValue = this.getFromSourceHeads("SOUR.value") || get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
1033
1088
  if (sourValue) {
1034
1089
  return `${prefix}${sourValue}`;
1035
1090
  }
@@ -1095,7 +1150,7 @@ var isOnlyMainProp = (key) => {
1095
1150
  };
1096
1151
  var isValidKey = (common, key) => {
1097
1152
  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);
1153
+ 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
1154
  };
1100
1155
  var getValidKeys = (common) => {
1101
1156
  return Object.keys(common).filter((key) => {
@@ -3182,7 +3237,11 @@ var STANDARD_QUALIFIERS = {
3182
3237
  BEF: "Bef.",
3183
3238
  BEFORE: "Bef.",
3184
3239
  AFT: "Aft.",
3185
- AFTER: "Aft."
3240
+ AFTER: "Aft.",
3241
+ CAL: "Abt.",
3242
+ // calculated ≈ about for display
3243
+ EST: "Abt."
3244
+ // estimated ≈ about for display
3186
3245
  };
3187
3246
  var EXPORT_QUALIFIERS = {
3188
3247
  "Abt.": "ABT",
@@ -3190,6 +3249,11 @@ var EXPORT_QUALIFIERS = {
3190
3249
  "Aft.": "AFT"
3191
3250
  };
3192
3251
  var GEDCOM_MONTHS = /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\b/g;
3252
+ var CALENDAR_PREFIX = /^(?<calendar>GREGORIAN|JULIAN|FRENCH_R|HEBREW)\b\s+/i;
3253
+ var isGedcomDateValueForm = (value) => (
3254
+ // Require space/end after the token so display forms like "Abt." are not opaque.
3255
+ /^(FROM|TO|BET|AFT|BEF|ABT|CAL|EST|INT)(\s|$)/i.test(value) || /\bAND\b/i.test(value)
3256
+ );
3193
3257
  var NOTE_MARKER = "####";
3194
3258
  var formatDateWithComponents = (date, hasDay, hasMonth, hasYear, noteValue, baseFormat = "dd MMM yyyy", locale) => {
3195
3259
  const hasNote = baseFormat.includes("NOTE");
@@ -3214,29 +3278,45 @@ var CommonDate = class extends Common {
3214
3278
  super(gedcom, id, main, parent);
3215
3279
  delete this.id;
3216
3280
  }
3281
+ get calendar() {
3282
+ return this._calendar;
3283
+ }
3217
3284
  set value(value) {
3218
3285
  if (value) {
3219
- const noteRegExp = /^(?<note>[a-zA-Z]+\.)/;
3220
- const noteMatch = value.match(noteRegExp)?.groups;
3221
3286
  let validValue = value;
3287
+ const calendarMatch = validValue.match(CALENDAR_PREFIX);
3288
+ if (calendarMatch?.groups?.calendar) {
3289
+ this._calendar = calendarMatch.groups.calendar.toUpperCase();
3290
+ validValue = validValue.slice(calendarMatch[0].length);
3291
+ } else {
3292
+ this._calendar = void 0;
3293
+ }
3294
+ const noteRegExp = /^(?<note>[a-zA-Z]+\.)/;
3295
+ const noteMatch = validValue.match(noteRegExp)?.groups;
3222
3296
  if (noteMatch?.note) {
3223
3297
  this.NOTE = this.NOTE || createCommon(this._gedcom, void 0, this.main);
3224
3298
  this.NOTE.value = noteMatch?.note;
3225
- validValue = value.replace(noteRegExp, "");
3299
+ validValue = validValue.replace(noteRegExp, "");
3226
3300
  } else {
3227
- const qualifierMatch = value.match(
3228
- /^(?<qualifier>ABT|ABOUT|BEF|BEFORE|AFT|AFTER)\b\s*/i
3301
+ const qualifierMatch = validValue.match(
3302
+ /^(?<qualifier>ABT|ABOUT|BEF|BEFORE|AFT|AFTER|CAL|EST)\b\s*/i
3229
3303
  );
3230
3304
  const qualifier = qualifierMatch?.groups?.qualifier?.toUpperCase();
3231
3305
  const note = qualifier ? STANDARD_QUALIFIERS[qualifier] : void 0;
3232
3306
  if (note) {
3233
3307
  this.NOTE = this.NOTE || createCommon(this._gedcom, void 0, this.main);
3234
3308
  this.NOTE.value = note;
3235
- validValue = value.slice(qualifierMatch[0].length);
3309
+ validValue = validValue.slice(qualifierMatch[0].length);
3236
3310
  }
3237
3311
  }
3238
3312
  const acceptedDate = this.isValidDateFormat(validValue);
3239
- if (acceptedDate) {
3313
+ if (isGedcomDateValueForm(validValue.trim())) {
3314
+ delete this.DAY;
3315
+ delete this.MONTH;
3316
+ delete this.YEAR;
3317
+ this._date = void 0;
3318
+ this._value = value;
3319
+ } else if (acceptedDate) {
3240
3320
  this.DAY = this.DAY || createCommon(this._gedcom, void 0, this.main);
3241
3321
  this.DAY.value = format(acceptedDate, "dd");
3242
3322
  this.MONTH = this.MONTH || createCommon(this._gedcom, void 0, this.main);
@@ -3339,6 +3419,15 @@ var CommonDate = class extends Common {
3339
3419
  );
3340
3420
  }
3341
3421
  exportValue() {
3422
+ if (typeof this._value === "string") {
3423
+ const withoutCalendar = this._value.replace(CALENDAR_PREFIX, "");
3424
+ if (isGedcomDateValueForm(withoutCalendar.trim())) {
3425
+ return this._value.replace(
3426
+ /^((?:GREGORIAN|JULIAN|FRENCH_R|HEBREW)\s+)?(FROM|TO|BET|AFT|BEF|ABT|CAL|EST|INT)\b/i,
3427
+ (_m, cal, q) => `${cal || ""}${q.toUpperCase()}`
3428
+ );
3429
+ }
3430
+ }
3342
3431
  const formatted = this.toValue("NOTE dd MMM yyyy", null);
3343
3432
  if (!formatted) {
3344
3433
  return formatted;
@@ -3346,10 +3435,14 @@ var CommonDate = class extends Common {
3346
3435
  const note = this.NOTE?.value?.trim();
3347
3436
  const qualifier = note ? EXPORT_QUALIFIERS[note] : void 0;
3348
3437
  const standardized = qualifier ? formatted.replace(note, qualifier) : formatted;
3349
- return standardized.replace(
3438
+ const withMonths = standardized.replace(
3350
3439
  GEDCOM_MONTHS,
3351
3440
  (month) => month.toUpperCase()
3352
3441
  );
3442
+ if (this._calendar && this._calendar !== "GREGORIAN") {
3443
+ return `${this._calendar} ${withMonths}`;
3444
+ }
3445
+ return withMonths;
3353
3446
  }
3354
3447
  toGedcomLines(tag, level = 0, options) {
3355
3448
  const internal = new RegExp(
@@ -3810,6 +3903,268 @@ var cacheDiscoveredPath = (gedcom, path) => {
3810
3903
  }
3811
3904
  };
3812
3905
 
3906
+ // src/utils/fact-edit.ts
3907
+ var SINGLETON_TAGS = /* @__PURE__ */ new Set(["BIRT", "DEAT", "SSN", "SEX", "DSCR"]);
3908
+ var firstRecord = (node) => {
3909
+ if (!node) {
3910
+ return void 0;
3911
+ }
3912
+ if (node instanceof List) {
3913
+ return node.index(0);
3914
+ }
3915
+ return node.index(0) ?? node;
3916
+ };
3917
+ var setDateOn = (gedcom, owner, parent, date) => {
3918
+ const trimmed = date.trim();
3919
+ if (!trimmed) {
3920
+ return;
3921
+ }
3922
+ let dateNode = parent.get("DATE");
3923
+ if (!dateNode || dateNode instanceof List) {
3924
+ dateNode = createCommonDate(gedcom, void 0, owner, parent);
3925
+ dateNode.type = "DATE";
3926
+ parent.set("DATE", dateNode);
3927
+ }
3928
+ dateNode.value = trimmed;
3929
+ };
3930
+ var fillFact = (gedcom, owner, event, input) => {
3931
+ if (input.value?.trim()) {
3932
+ event.value = input.value.trim();
3933
+ }
3934
+ if (input.type?.trim()) {
3935
+ event.set("TYPE", input.type.trim());
3936
+ }
3937
+ if (input.date) {
3938
+ setDateOn(gedcom, owner, event, input.date);
3939
+ }
3940
+ if (input.place?.trim()) {
3941
+ event.set("PLAC", input.place.trim());
3942
+ }
3943
+ if (input.note?.trim()) {
3944
+ event.set("NOTE", input.note.trim());
3945
+ }
3946
+ };
3947
+ var addIndividualFact = (indi, input) => {
3948
+ const gedcom = indi.getGedcom();
3949
+ if (!gedcom) {
3950
+ return void 0;
3951
+ }
3952
+ const tag = input.tag;
3953
+ let event = SINGLETON_TAGS.has(tag) ? firstRecord(indi.get(tag)) : void 0;
3954
+ if (!event) {
3955
+ event = createCommon(gedcom, void 0, indi);
3956
+ event.type = tag;
3957
+ if (SINGLETON_TAGS.has(tag)) {
3958
+ indi.set(tag, event);
3959
+ } else {
3960
+ indi.assign(tag, event);
3961
+ }
3962
+ }
3963
+ fillFact(gedcom, indi, event, input);
3964
+ return event;
3965
+ };
3966
+ var addFamilyFact = (fam, input) => {
3967
+ const gedcom = fam.getGedcom();
3968
+ if (!gedcom) {
3969
+ return void 0;
3970
+ }
3971
+ const event = createCommon(gedcom, void 0, fam);
3972
+ event.type = input.tag;
3973
+ fam.assign(input.tag, event);
3974
+ fillFact(gedcom, fam, event, input);
3975
+ return event;
3976
+ };
3977
+ var addNonEvent = (owner, input) => {
3978
+ const gedcom = owner.getGedcom();
3979
+ const eventType = input.event?.trim().toUpperCase();
3980
+ if (!gedcom || !eventType) {
3981
+ return void 0;
3982
+ }
3983
+ const no = createCommon(gedcom, void 0, owner);
3984
+ no.type = "NO";
3985
+ no.value = eventType;
3986
+ if (input.date?.trim()) {
3987
+ setDateOn(gedcom, owner, no, input.date);
3988
+ }
3989
+ if (input.note?.trim()) {
3990
+ no.set("NOTE", input.note.trim());
3991
+ }
3992
+ owner.assign("NO", no);
3993
+ return no;
3994
+ };
3995
+
3996
+ // src/utils/family-edit.ts
3997
+ var PEDIGREE_VALUES = [
3998
+ "birth" /* BIRTH */,
3999
+ "adopted" /* ADOPTED */,
4000
+ "foster" /* FOSTER */,
4001
+ "step" /* STEP */,
4002
+ "sealing" /* SEALING */
4003
+ ];
4004
+ var toGedcomPedigree = (value) => {
4005
+ const normalized = String(value || "birth" /* BIRTH */).toLowerCase();
4006
+ if (normalized === "biological" /* BIOLOGICAL */ || normalized === "birth" /* BIRTH */) {
4007
+ return "birth" /* BIRTH */;
4008
+ }
4009
+ const match = PEDIGREE_VALUES.find((item) => item === normalized);
4010
+ return match ?? "birth" /* BIRTH */;
4011
+ };
4012
+ var pointerIds = (tag) => {
4013
+ if (!tag) {
4014
+ return [];
4015
+ }
4016
+ if (tag instanceof List) {
4017
+ return tag.values().map((item) => item?.toValue?.()).filter((value2) => !!value2);
4018
+ }
4019
+ const value = tag.toValue?.();
4020
+ return value ? [String(value)] : [];
4021
+ };
4022
+ var firstPointerId = (tag) => pointerIds(tag)[0];
4023
+ var makePointer = (gedcom, parent, value, refType) => {
4024
+ const pointer = createCommon(gedcom, void 0, parent);
4025
+ pointer.value = value;
4026
+ pointer.refType = refType;
4027
+ return pointer;
4028
+ };
4029
+ var nextRecordId = (ids, prefix) => {
4030
+ let max = 0;
4031
+ ids.forEach((id) => {
4032
+ const match = String(id).match(/(\d+)/);
4033
+ const n = match ? Number.parseInt(match[1], 10) : 0;
4034
+ if (n > max) {
4035
+ max = n;
4036
+ }
4037
+ });
4038
+ return `@${prefix}${max + 1}@`;
4039
+ };
4040
+ var familyHasParent = (fam, indiId) => pointerIds(fam.get("HUSB")).includes(indiId) || pointerIds(fam.get("WIFE")).includes(indiId);
4041
+ var familyHasChild = (fam, indiId) => pointerIds(fam.get("CHIL")).includes(indiId);
4042
+ var spouseTagFor = (indi, fam) => {
4043
+ const husbId = firstPointerId(fam.get("HUSB"));
4044
+ const wifeId = firstPointerId(fam.get("WIFE"));
4045
+ if (husbId === indi.id) {
4046
+ return "HUSB";
4047
+ }
4048
+ if (wifeId === indi.id) {
4049
+ return "WIFE";
4050
+ }
4051
+ if (indi.isFemale()) {
4052
+ return wifeId && wifeId !== indi.id ? "HUSB" : "WIFE";
4053
+ }
4054
+ if (indi.isMale()) {
4055
+ return husbId && husbId !== indi.id ? "WIFE" : "HUSB";
4056
+ }
4057
+ return husbId ? "WIFE" : "HUSB";
4058
+ };
4059
+ var canAttachAsSpouse = (fam, indi) => {
4060
+ if (!indi.id) {
4061
+ return false;
4062
+ }
4063
+ if (familyHasParent(fam, indi.id)) {
4064
+ return true;
4065
+ }
4066
+ const tag = spouseTagFor(indi, fam);
4067
+ const occupied = firstPointerId(fam.get(tag));
4068
+ return !occupied || occupied === indi.id;
4069
+ };
4070
+ var pointerByValue = (tag, id) => {
4071
+ if (!tag) {
4072
+ return void 0;
4073
+ }
4074
+ const fromValues = tag.toValueList?.().item(id);
4075
+ if (fromValues) {
4076
+ return fromValues;
4077
+ }
4078
+ return tag.toList()?.values().find((item) => item?.toValue?.() === id);
4079
+ };
4080
+ var setChildPedigree = (fam, child, pedigree, parent) => {
4081
+ if (!fam.id || !child.id) {
4082
+ return;
4083
+ }
4084
+ const pedi = toGedcomPedigree(pedigree);
4085
+ const famc = pointerByValue(child.get("FAMC"), fam.id);
4086
+ if (famc) {
4087
+ famc.set("PEDI", pedi);
4088
+ }
4089
+ const chil = pointerByValue(fam.get("CHIL"), child.id);
4090
+ if (!chil) {
4091
+ return;
4092
+ }
4093
+ const setFather = !parent || parent.isMale() || parent.isUnknownSex();
4094
+ const setMother = !parent || parent.isFemale() || parent.isUnknownSex();
4095
+ if (setFather) {
4096
+ chil.set("_FREL", pedi);
4097
+ }
4098
+ if (setMother) {
4099
+ chil.set("_MREL", pedi);
4100
+ }
4101
+ };
4102
+ var attachSpouseToFamily = (fam, indi) => {
4103
+ const gedcom = fam.getGedcom() ?? indi.getGedcom();
4104
+ if (!indi.id || !fam.id || !gedcom) {
4105
+ return false;
4106
+ }
4107
+ if (!canAttachAsSpouse(fam, indi)) {
4108
+ return false;
4109
+ }
4110
+ const tag = spouseTagFor(indi, fam);
4111
+ if (firstPointerId(fam.get(tag)) !== indi.id) {
4112
+ fam.set(tag, makePointer(gedcom, fam, indi.id, "INDI"));
4113
+ }
4114
+ indi.assign("FAMS", makePointer(gedcom, indi, fam.id, "FAM"), true);
4115
+ return true;
4116
+ };
4117
+ var attachChildToFamily = (fam, child, pedigree, parent) => {
4118
+ const gedcom = fam.getGedcom() ?? child.getGedcom();
4119
+ if (!child.id || !fam.id || !gedcom) {
4120
+ return false;
4121
+ }
4122
+ if (!familyHasChild(fam, child.id)) {
4123
+ fam.assign("CHIL", makePointer(gedcom, fam, child.id, "INDI"), true);
4124
+ }
4125
+ const existingFamc = pointerByValue(child.get("FAMC"), fam.id);
4126
+ if (!existingFamc) {
4127
+ child.assign("FAMC", makePointer(gedcom, child, fam.id, "FAM"), true);
4128
+ }
4129
+ setChildPedigree(fam, child, pedigree, parent);
4130
+ return true;
4131
+ };
4132
+ var findReusableParentChildFamily = (parent, child) => {
4133
+ if (!parent.id || !child.id) {
4134
+ return void 0;
4135
+ }
4136
+ let reusableFamc;
4137
+ let already = false;
4138
+ child.getFamilies("FAMC")?.forEach((fam) => {
4139
+ if (!fam) {
4140
+ return;
4141
+ }
4142
+ if (familyHasParent(fam, parent.id) && familyHasChild(fam, child.id)) {
4143
+ already = true;
4144
+ return;
4145
+ }
4146
+ if (!reusableFamc && canAttachAsSpouse(fam, parent)) {
4147
+ reusableFamc = fam;
4148
+ }
4149
+ });
4150
+ if (already) {
4151
+ return "already";
4152
+ }
4153
+ if (reusableFamc) {
4154
+ return reusableFamc;
4155
+ }
4156
+ const parentFams = [];
4157
+ parent.getFamilies("FAMS")?.forEach((fam) => {
4158
+ if (fam) {
4159
+ parentFams.push(fam);
4160
+ }
4161
+ });
4162
+ if (parentFams.length === 1) {
4163
+ return parentFams[0];
4164
+ }
4165
+ return void 0;
4166
+ };
4167
+
3813
4168
  // src/utils/get-places.ts
3814
4169
  var getPlaces = (common, type = ["ALL" /* All */], maxLevel = 1, level = 0, mainKey) => {
3815
4170
  const types = Array.isArray(type) ? type : [type];
@@ -3874,16 +4229,58 @@ var getPlaces = (common, type = ["ALL" /* All */], maxLevel = 1, level = 0, main
3874
4229
  });
3875
4230
  return places;
3876
4231
  };
4232
+
4233
+ // src/utils/local-media.ts
4234
+ var localMediaResolver;
4235
+ var isRemoteOrEmbeddedMediaUrl = (path) => {
4236
+ if (!path) {
4237
+ return false;
4238
+ }
4239
+ return path.startsWith("data:") || path.startsWith("blob:") || /^https?:\/\//i.test(path);
4240
+ };
4241
+ var resolveLocalMediaUrl = async (path) => {
4242
+ if (!path) {
4243
+ return void 0;
4244
+ }
4245
+ if (isRemoteOrEmbeddedMediaUrl(path)) {
4246
+ return path;
4247
+ }
4248
+ const resolved = await localMediaResolver?.(path);
4249
+ return resolved;
4250
+ };
3877
4251
  var implemented = (type, ...args) => {
3878
4252
  };
3879
4253
 
3880
4254
  // src/utils/media-utils.ts
3881
4255
  var getFileExtension = (filename) => {
3882
- const match = filename.match(/\.([^.]+)$/);
3883
- return match ? match[1] : "";
4256
+ if (!filename) {
4257
+ return "";
4258
+ }
4259
+ if (filename.startsWith("data:")) {
4260
+ const mime = filename.slice(5).split(";")[0] || "";
4261
+ const subtype = (mime.split("/")[1] || "").toLowerCase();
4262
+ if (subtype === "jpeg") {
4263
+ return "jpg";
4264
+ }
4265
+ return subtype.split("+")[0] || "";
4266
+ }
4267
+ const withoutQuery = filename.split(/[?#]/)[0] || filename;
4268
+ const match = withoutQuery.match(/\.([^.]+)$/);
4269
+ return match ? match[1].toLowerCase() : "";
3884
4270
  };
3885
4271
  var isImageFormat = (format2) => {
3886
- if (!format2) return false;
4272
+ if (!format2) {
4273
+ return false;
4274
+ }
4275
+ let normalized = format2.toLowerCase().trim();
4276
+ if (normalized.startsWith("data:")) {
4277
+ normalized = getFileExtension(normalized);
4278
+ } else if (normalized.includes("/")) {
4279
+ normalized = normalized.split("/")[1]?.split("+")[0] || "";
4280
+ if (normalized === "jpeg") {
4281
+ normalized = "jpg";
4282
+ }
4283
+ }
3887
4284
  const imageFormats = [
3888
4285
  "jpg",
3889
4286
  "jpeg",
@@ -3895,7 +4292,11 @@ var isImageFormat = (format2) => {
3895
4292
  "tiff",
3896
4293
  "tif"
3897
4294
  ];
3898
- return imageFormats.includes(format2.toLowerCase());
4295
+ return imageFormats.includes(normalized);
4296
+ };
4297
+ var resolveObjeForm = (obje) => {
4298
+ const value = obje?.get?.("FILE.FORM")?.toValue?.() ?? obje?.get?.("FORM")?.toValue?.();
4299
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
3899
4300
  };
3900
4301
  var uniqueItemsCache = /* @__PURE__ */ new WeakMap();
3901
4302
  var setNestedGroup = (obj, key, value, uniqueCounting = true) => {
@@ -4440,6 +4841,34 @@ var Fam = class extends Common {
4440
4841
  getParents() {
4441
4842
  return this.getHusband().copy().merge(this.getWife());
4442
4843
  }
4844
+ hasParent(indi) {
4845
+ const indiId = typeof indi === "string" ? indi : indi?.id;
4846
+ return !!indiId && familyHasParent(this, indiId);
4847
+ }
4848
+ hasChild(indi) {
4849
+ const indiId = typeof indi === "string" ? indi : indi?.id;
4850
+ return !!indiId && familyHasChild(this, indiId);
4851
+ }
4852
+ canAddSpouse(indi) {
4853
+ return canAttachAsSpouse(this, indi);
4854
+ }
4855
+ addSpouse(indi) {
4856
+ return attachSpouseToFamily(this, indi);
4857
+ }
4858
+ addChild(child, pedigree, parent) {
4859
+ return attachChildToFamily(
4860
+ this,
4861
+ child,
4862
+ pedigree,
4863
+ parent
4864
+ );
4865
+ }
4866
+ addFact(input) {
4867
+ return addFamilyFact(this, input);
4868
+ }
4869
+ addNonEvent(input) {
4870
+ return addNonEvent(this, input);
4871
+ }
4443
4872
  toList() {
4444
4873
  return new Families().concat(
4445
4874
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -4512,19 +4941,51 @@ var ALLOWED_FACTS = [
4512
4941
  "OCCU",
4513
4942
  "OCCUPATIONS",
4514
4943
  "RESI",
4944
+ "BIRT",
4945
+ "DEAT",
4515
4946
  "BAPT",
4947
+ "BAPM",
4516
4948
  "CHRI",
4517
4949
  "CHR",
4950
+ "CHRA",
4518
4951
  "BURI",
4519
- "EDUC",
4952
+ "CREM",
4953
+ "ADOP",
4954
+ "BARM",
4955
+ "BASM",
4956
+ "BLES",
4957
+ "CONF",
4958
+ "FCOM",
4959
+ "ORDN",
4960
+ "NATU",
4961
+ "EMIG",
4962
+ "IMMI",
4963
+ "CENS",
4964
+ "PROB",
4965
+ "WILL",
4520
4966
  "GRAD",
4967
+ "RETI",
4968
+ "EDUC",
4521
4969
  "DIV",
4522
- "IMMI",
4523
4970
  "RELI",
4971
+ "CAST",
4972
+ "DSCR",
4973
+ "NATI",
4974
+ "PROP",
4975
+ "SSN",
4976
+ "TITL",
4977
+ "ADDR",
4978
+ "BAPL",
4979
+ "CONL",
4980
+ "ENDL",
4981
+ "SLGC",
4524
4982
  "_MILT",
4525
4983
  "_MILTID",
4526
4984
  "FACT",
4527
- "_ORIG"
4985
+ "_ORIG",
4986
+ "_WLNK",
4987
+ "WWW",
4988
+ "URL"
4528
4989
  ];
4529
4990
  var DISALLOWED_CUSTOM_FACTS = ["DNA Test", "Newspaper"];
4530
4991
  var CustomFactRenderers = {
@@ -5068,7 +5529,7 @@ var Indi = class extends Common {
5068
5529
  return result;
5069
5530
  }
5070
5531
  ancestryLink() {
5071
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5532
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5072
5533
  const tree = this.getAncestryTreeId();
5073
5534
  if (this.id) {
5074
5535
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -5080,7 +5541,7 @@ var Indi = class extends Common {
5080
5541
  async ancestryMedia(namespace) {
5081
5542
  const list = {};
5082
5543
  const objeList = this.get("OBJE")?.toList().copy();
5083
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5544
+ const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
5084
5545
  const tree = this.getAncestryTreeId();
5085
5546
  if (objeList) {
5086
5547
  await Promise.all(
@@ -5098,10 +5559,14 @@ var Indi = class extends Common {
5098
5559
  const clone = obje?.get("_CLON._OID")?.toValue();
5099
5560
  const mser = obje?.get("_MSER._LKID")?.toValue();
5100
5561
  let url = obje?.get("FILE")?.toValue();
5101
- const title = obje?.get("TITL")?.toValue() ?? "";
5102
- const type = obje?.get("FORM")?.toValue() ?? "raw";
5103
- const imgId = clone || mser;
5104
- if (!www || !tree || !this.id) {
5562
+ const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
5563
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
5564
+ let imgId = clone || mser;
5565
+ const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
5566
+ if (!hasEmbeddedOrLocalFile && (!www || !tree || !this.id)) {
5567
+ return;
5568
+ }
5569
+ if (!this.id) {
5105
5570
  return;
5106
5571
  }
5107
5572
  if (!namespace && !url) {
@@ -5119,13 +5584,16 @@ var Indi = class extends Common {
5119
5584
  }
5120
5585
  url = url || `https://${www}/mediaui-viewer/tree/${tree}/media/${media}`;
5121
5586
  }
5587
+ if (!imgId && hasEmbeddedOrLocalFile && url) {
5588
+ imgId = typeof media === "string" && media || `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}`;
5589
+ }
5122
5590
  if (url && imgId) {
5123
- const id = `${tree}-${this.id}-${imgId}`;
5591
+ const id = `${tree || "gedzip"}-${this.id}-${imgId}`;
5124
5592
  list[id] = {
5125
5593
  key,
5126
5594
  isPrimary,
5127
5595
  id,
5128
- tree,
5596
+ tree: tree || "gedzip",
5129
5597
  imgId,
5130
5598
  person: this.id,
5131
5599
  title,
@@ -5140,7 +5608,7 @@ var Indi = class extends Common {
5140
5608
  return list;
5141
5609
  }
5142
5610
  myheritageLink(poolId = 0) {
5143
- const www = this._gedcom?.HEAD?.SOUR?.CORP?.value?.toLowerCase().replace(/^www\./gi, "");
5611
+ const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
5144
5612
  const site = this.getMyHeritageTreeId();
5145
5613
  const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
5146
5614
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
@@ -5171,9 +5639,11 @@ var Indi = class extends Common {
5171
5639
  obje.standardizeMedia();
5172
5640
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
5173
5641
  const url = obje?.get("FILE")?.toValue();
5174
- const title = obje?.get("NOTE")?.toValue() ?? "";
5175
- const type = obje?.get("FORM")?.toValue() ?? "raw";
5176
- const imgId = obje?.get("_PHOTO_RIN")?.toValue();
5642
+ const title = obje?.get("FILE.NOTE")?.toValue() ?? obje?.get("NOTE")?.toValue() ?? "";
5643
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
5644
+ const photoRin = obje?.get("_PHOTO_RIN")?.toValue();
5645
+ const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
5646
+ const imgId = photoRin || (hasEmbeddedOrLocalFile && url ? `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}` : void 0);
5177
5647
  if (url && imgId) {
5178
5648
  const id = `${tree}-${this.id}-${imgId}`;
5179
5649
  list[id] = {
@@ -5296,10 +5766,10 @@ var Indi = class extends Common {
5296
5766
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
5297
5767
  const url = obje?.get("FILE")?.toValue();
5298
5768
  const title = obje?.get("TITL")?.toValue() ?? "";
5299
- const type = obje?.get("FORM")?.toValue() ?? "raw";
5769
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
5300
5770
  if (url) {
5301
5771
  const urlMatch = url.match(/\/([^/]+)\?hash=/);
5302
- const imgId = urlMatch?.[1] || `img-${index}-${Date.now().toString(36)}`;
5772
+ const imgId = urlMatch?.[1] || (url.startsWith("data:") ? `data-${index}-${getFileExtension(url) || "bin"}` : `img-${index}-${Date.now().toString(36)}`);
5303
5773
  const id = `geni-${geniId}-${imgId}`;
5304
5774
  list[id] = {
5305
5775
  isPrimary,
@@ -5336,10 +5806,10 @@ var Indi = class extends Common {
5336
5806
  obje.standardizeMedia();
5337
5807
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
5338
5808
  const url = obje?.get("FILE")?.toValue();
5339
- const title = obje?.get("TITL")?.toValue() ?? "";
5340
- const type = obje?.get("FORM")?.toValue() ?? "raw";
5809
+ const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
5810
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
5341
5811
  if (url) {
5342
- const imgId = `media-${index}-${url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
5812
+ const imgId = `media-${index}-${url.startsWith("data:") ? getFileExtension(url) || "bin" : url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
5343
5813
  const id = `${treeId}-${this.id}-${imgId}`;
5344
5814
  list[id] = {
5345
5815
  isPrimary,
@@ -5379,7 +5849,20 @@ var Indi = class extends Common {
5379
5849
  cacheKey
5380
5850
  );
5381
5851
  if (cached !== void 0) {
5382
- return cached;
5852
+ if (cached?.file && !isRemoteOrEmbeddedMediaUrl(cached.file)) {
5853
+ const resolvedCached = await resolveLocalMediaUrl(cached.file);
5854
+ if (resolvedCached && isRemoteOrEmbeddedMediaUrl(resolvedCached)) {
5855
+ const refreshed = { ...cached, file: resolvedCached };
5856
+ profilePictureCache(
5857
+ this._gedcom,
5858
+ cacheKey,
5859
+ refreshed
5860
+ );
5861
+ return refreshed;
5862
+ }
5863
+ } else {
5864
+ return cached;
5865
+ }
5383
5866
  }
5384
5867
  const mediaList = await this.multimedia(namespace);
5385
5868
  if (!mediaList) {
@@ -5391,39 +5874,40 @@ var Indi = class extends Common {
5391
5874
  return void 0;
5392
5875
  }
5393
5876
  const mediaArray = Object.values(mediaList);
5394
- const primaryMedia = mediaArray.find(
5395
- (media) => media.isPrimary && isImageFormat(media.contentType || getFileExtension(media.url))
5396
- );
5397
- if (primaryMedia) {
5398
- const result = {
5399
- file: primaryMedia.url,
5400
- form: primaryMedia.contentType,
5401
- title: primaryMedia.title,
5402
- isPrimary: true
5877
+ const toDisplayableProfile = async (media, isPrimary) => {
5878
+ const resolvedFile = await resolveLocalMediaUrl(media.url);
5879
+ if (!resolvedFile || !isRemoteOrEmbeddedMediaUrl(resolvedFile)) {
5880
+ return void 0;
5881
+ }
5882
+ return {
5883
+ file: resolvedFile,
5884
+ form: media.contentType,
5885
+ title: media.title,
5886
+ isPrimary
5403
5887
  };
5404
- profilePictureCache(this._gedcom, cacheKey, result);
5405
- return result;
5406
- }
5407
- if (onlyPrimary) {
5408
- profilePictureCache(
5409
- this._gedcom,
5410
- cacheKey,
5411
- void 0
5412
- );
5413
- return void 0;
5414
- }
5415
- const secondaryMedia = mediaArray.find(
5416
- (media) => isImageFormat(media.contentType || getFileExtension(media.url))
5888
+ };
5889
+ const candidates = mediaArray.filter(
5890
+ (media) => isImageFormat(
5891
+ media.contentType || media.url || getFileExtension(media.url)
5892
+ )
5417
5893
  );
5418
- if (secondaryMedia) {
5419
- const result = {
5420
- file: secondaryMedia.url,
5421
- form: secondaryMedia.contentType,
5422
- title: secondaryMedia.title,
5423
- isPrimary: false
5424
- };
5425
- profilePictureCache(this._gedcom, cacheKey, result);
5426
- return result;
5894
+ const ordered = [
5895
+ ...candidates.filter((media) => media.isPrimary),
5896
+ ...candidates.filter((media) => !media.isPrimary)
5897
+ ];
5898
+ for (const media of ordered) {
5899
+ if (onlyPrimary && !media.isPrimary) {
5900
+ break;
5901
+ }
5902
+ const result = await toDisplayableProfile(media, !!media.isPrimary);
5903
+ if (result) {
5904
+ profilePictureCache(
5905
+ this._gedcom,
5906
+ cacheKey,
5907
+ result
5908
+ );
5909
+ return result;
5910
+ }
5427
5911
  }
5428
5912
  profilePictureCache(
5429
5913
  this._gedcom,
@@ -5661,6 +6145,156 @@ var Indi = class extends Common {
5661
6145
  isUnknownSex() {
5662
6146
  return !this.isMale() && !this.isFemale();
5663
6147
  }
6148
+ addSpouse(other) {
6149
+ if (!this.id || !other?.id || this.id === other.id || !this._gedcom) {
6150
+ return void 0;
6151
+ }
6152
+ const existing = getFamilyWith(this, other.id);
6153
+ if (existing) {
6154
+ return existing;
6155
+ }
6156
+ const fam = this._gedcom.createFamily();
6157
+ if (!fam.addSpouse(this) || !fam.addSpouse(other)) {
6158
+ return void 0;
6159
+ }
6160
+ return fam;
6161
+ }
6162
+ addChild(child, pedigree) {
6163
+ if (!this.id || !child?.id || this.id === child.id || !this._gedcom) {
6164
+ return void 0;
6165
+ }
6166
+ const existing = findReusableParentChildFamily(
6167
+ this,
6168
+ child
6169
+ );
6170
+ if (existing === "already") {
6171
+ const fam2 = child.getFamilies("FAMC")?.find(
6172
+ (item) => !!item && item.hasParent(this.id) && item.hasChild(child.id)
6173
+ );
6174
+ if (fam2) {
6175
+ setChildPedigree(
6176
+ fam2,
6177
+ child,
6178
+ pedigree,
6179
+ this
6180
+ );
6181
+ }
6182
+ return fam2;
6183
+ }
6184
+ const fam = existing ?? this._gedcom.createFamily();
6185
+ if (!fam.addSpouse(this)) {
6186
+ return void 0;
6187
+ }
6188
+ if (!fam.addChild(child, pedigree, this)) {
6189
+ return void 0;
6190
+ }
6191
+ return fam;
6192
+ }
6193
+ addParent(parent, pedigree) {
6194
+ return parent.addChild(this, pedigree);
6195
+ }
6196
+ /**
6197
+ * Link an existing top-level OBJE record to this individual (`1 OBJE @On@`).
6198
+ */
6199
+ attachMultimedia(objeOrKey, options) {
6200
+ if (!this._gedcom || !this.id) {
6201
+ return void 0;
6202
+ }
6203
+ const obje = typeof objeOrKey === "string" ? this._gedcom.obje(objeOrKey) : objeOrKey;
6204
+ if (!obje?.id) {
6205
+ return void 0;
6206
+ }
6207
+ const already = this.get("OBJE")?.toList()?.values()?.some((item) => item?.toValue?.() === obje.id);
6208
+ if (!already) {
6209
+ const pointer = makePointer(
6210
+ this._gedcom,
6211
+ this,
6212
+ obje.id,
6213
+ "OBJE"
6214
+ );
6215
+ this.assign("OBJE", pointer);
6216
+ }
6217
+ if (options?.primary) {
6218
+ this.get("OBJE")?.toList()?.forEach((item) => {
6219
+ const target = item?.refType === "OBJE" ? this._gedcom?.obje(
6220
+ String(item.toValue())
6221
+ ) : item;
6222
+ if (!target) {
6223
+ return;
6224
+ }
6225
+ if (target.id === obje.id) {
6226
+ target.set("_PRIM", "Y");
6227
+ } else if (target.get("_PRIM")?.toValue() === "Y") {
6228
+ target.set("_PRIM", "N");
6229
+ }
6230
+ });
6231
+ obje.set("_PRIM", "Y");
6232
+ }
6233
+ return obje;
6234
+ }
6235
+ detachMultimedia(objeKey) {
6236
+ const list = this.get("OBJE")?.toList();
6237
+ if (!list) {
6238
+ return;
6239
+ }
6240
+ const toRemove = list.values().filter(
6241
+ (item) => item?.toValue?.() === objeKey || item?.id === objeKey
6242
+ );
6243
+ toRemove.forEach((item) => {
6244
+ if (item) {
6245
+ list.delete(item);
6246
+ }
6247
+ });
6248
+ if (list.length === 0) {
6249
+ this.remove("OBJE");
6250
+ }
6251
+ }
6252
+ /**
6253
+ * Create a multimedia record and attach it to this person.
6254
+ */
6255
+ attachMediaFromUrl(url, options) {
6256
+ if (!this._gedcom || !url) {
6257
+ return void 0;
6258
+ }
6259
+ const obje = this._gedcom.createMultimediaRecord({
6260
+ file: url,
6261
+ form: options?.form,
6262
+ title: options?.title,
6263
+ mediType: options?.mediType,
6264
+ primary: options?.primary,
6265
+ gedcomVersion: options?.gedcomVersion
6266
+ });
6267
+ return this.attachMultimedia(obje, { primary: options?.primary });
6268
+ }
6269
+ /**
6270
+ * Link a shared SNOTE record via `1 NOTE @Nn@` (GEDCOM 7).
6271
+ */
6272
+ attachSharedNote(noteOrKey) {
6273
+ if (!this._gedcom || !this.id) {
6274
+ return void 0;
6275
+ }
6276
+ const note = typeof noteOrKey === "string" ? this._gedcom.snote(noteOrKey) : noteOrKey;
6277
+ if (!note?.id) {
6278
+ return void 0;
6279
+ }
6280
+ const already = this.get("NOTE")?.toList()?.values()?.some((item) => item?.toValue?.() === note.id);
6281
+ if (!already) {
6282
+ const pointer = makePointer(
6283
+ this._gedcom,
6284
+ this,
6285
+ note.id,
6286
+ "SNOTE"
6287
+ );
6288
+ this.assign("NOTE", pointer);
6289
+ }
6290
+ return note;
6291
+ }
6292
+ addFact(input) {
6293
+ return addIndividualFact(this, input);
6294
+ }
6295
+ addNonEvent(input) {
6296
+ return addNonEvent(this, input);
6297
+ }
5664
6298
  getParentType(id) {
5665
6299
  let indi;
5666
6300
  if (typeof id === "string" || typeof id === "number") {
@@ -5701,7 +6335,7 @@ var Indi = class extends Common {
5701
6335
  if (!famChild) {
5702
6336
  const childIndi = this._gedcom?.indi(child.id);
5703
6337
  const childState = childIndi?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
5704
- childType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
6338
+ childType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
5705
6339
  } else {
5706
6340
  childType = famChild.get(usedRel)?.toValue() || "biological" /* BIOLOGICAL */;
5707
6341
  }
@@ -5861,7 +6495,9 @@ var Indi = class extends Common {
5861
6495
  }
5862
6496
  }
5863
6497
  if (kinship !== "child" || !breakAfterSpouse) {
5864
- indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(indi.getFathers().copy().merge(indi.getMothers())).forEach((relative) => {
6498
+ indi.getBiologicalFathers().copy().merge(indi.getBiologicalMothers()).merge(
6499
+ indi.getFathers().copy().merge(indi.getMothers())
6500
+ ).forEach((relative) => {
5865
6501
  if (!visited.has(relative)) {
5866
6502
  const currentRelation = indi.getParentType(relative);
5867
6503
  const isNonBiological = !!currentRelation && currentRelation !== "biological" /* BIOLOGICAL */;
@@ -6260,7 +6896,7 @@ var Indi = class extends Common {
6260
6896
  return;
6261
6897
  }
6262
6898
  const childState = child?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
6263
- const childType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
6899
+ const childType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
6264
6900
  if (childType?.toLowerCase() === filter.PEDI.toLowerCase()) {
6265
6901
  children.append(child);
6266
6902
  }
@@ -6368,7 +7004,7 @@ var Indi = class extends Common {
6368
7004
  const child = this.id && family.CHIL?.toValueList()?.item(this.id);
6369
7005
  if (!child) {
6370
7006
  const childState = this?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
6371
- const parentType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
7007
+ const parentType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
6372
7008
  if (parentType?.toLowerCase() === filter.PEDI.toLowerCase()) {
6373
7009
  parents.merge(family.getParents());
6374
7010
  }
@@ -6760,13 +7396,760 @@ var createIndi = (gedcom, id, main, parent) => {
6760
7396
  );
6761
7397
  };
6762
7398
 
7399
+ // src/utils/gedcom-version.ts
7400
+ var normalizeGedcomVersion = (value) => {
7401
+ const trimmed = value?.trim() ?? "";
7402
+ if (trimmed.startsWith("7")) {
7403
+ return "7.0";
7404
+ }
7405
+ return "5.5.1";
7406
+ };
7407
+ var GEDZIP_GEDCOM_ENTRY = "gedcom.ged";
7408
+ var sanitizePathSegment = (name) => name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^\.+/, "") || "file";
7409
+ var extensionFor = (contentType) => {
7410
+ const raw = (contentType || "bin").replace(/^\./, "").toLowerCase();
7411
+ if (raw.includes("/")) {
7412
+ return raw.split("/").pop() || "bin";
7413
+ }
7414
+ return raw || "bin";
7415
+ };
7416
+ var rewriteGedcomFilePaths = (gedcomText, urlToPath) => {
7417
+ const entries = urlToPath instanceof Map ? urlToPath : new Map(Object.entries(urlToPath));
7418
+ let text = gedcomText;
7419
+ entries.forEach((path, url) => {
7420
+ const escaped = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7421
+ text = text.replace(
7422
+ new RegExp(`(^|\\r?\\n)(\\d+ FILE )${escaped}`, "g"),
7423
+ `$1$2${path}`
7424
+ );
7425
+ });
7426
+ return text;
7427
+ };
7428
+ var assignMediaPaths = (mediaFiles) => {
7429
+ const urlToPath = /* @__PURE__ */ new Map();
7430
+ const usedPaths = /* @__PURE__ */ new Set([GEDZIP_GEDCOM_ENTRY]);
7431
+ const files = [];
7432
+ for (const file of mediaFiles) {
7433
+ if (!file.content || !file.url) {
7434
+ continue;
7435
+ }
7436
+ const ext = extensionFor(file.contentType);
7437
+ const base = sanitizePathSegment(
7438
+ file.imgId || file.title || file.id || "media"
7439
+ );
7440
+ let path = `media/${base}.${ext}`;
7441
+ let suffix = 1;
7442
+ while (usedPaths.has(path)) {
7443
+ path = `media/${base}-${suffix++}.${ext}`;
7444
+ }
7445
+ usedPaths.add(path);
7446
+ urlToPath.set(file.url, path);
7447
+ const marker = "base64,";
7448
+ const idx = file.content.indexOf(marker);
7449
+ if (idx >= 0) {
7450
+ files.push({
7451
+ path,
7452
+ url: file.url,
7453
+ content: file.content.substring(idx + marker.length),
7454
+ base64: true
7455
+ });
7456
+ } else {
7457
+ files.push({
7458
+ path,
7459
+ url: file.url,
7460
+ content: file.content,
7461
+ base64: false
7462
+ });
7463
+ }
7464
+ }
7465
+ return { urlToPath, files };
7466
+ };
7467
+ var buildGedzip = async (gedcomText, mediaFiles = []) => {
7468
+ const zip = new JSZip();
7469
+ const { urlToPath, files } = assignMediaPaths(mediaFiles);
7470
+ for (const file of files) {
7471
+ zip.file(file.path, file.content, { base64: file.base64 });
7472
+ }
7473
+ zip.file(GEDZIP_GEDCOM_ENTRY, rewriteGedcomFilePaths(gedcomText, urlToPath));
7474
+ return zip.generateAsync({ type: "uint8array" });
7475
+ };
7476
+
7477
+ // src/utils/gedcom7-enumerations.ts
7478
+ var GEDCOM7_SEX_VALUES = ["M", "F", "U", "X", "N"];
7479
+ var GEDCOM7_PEDI_VALUES = [
7480
+ "adopted",
7481
+ "birth",
7482
+ "foster",
7483
+ "sealing"
7484
+ ];
7485
+ var GEDCOM7_ROLE_VALUES = [
7486
+ "CHIL",
7487
+ "CLERGY",
7488
+ "FATH",
7489
+ "FRIEND",
7490
+ "GODP",
7491
+ "HUSB",
7492
+ "MOTH",
7493
+ "MULTIPLE",
7494
+ "NGHBR",
7495
+ "OFFICIATOR",
7496
+ "OTHER",
7497
+ "PARENT",
7498
+ "SPOU",
7499
+ "WIFE",
7500
+ "WITN"
7501
+ ];
7502
+ var GEDCOM7_RESN_VALUES = [
7503
+ "CONFIDENTIAL",
7504
+ "LOCKED",
7505
+ "PRIVACY"
7506
+ ];
7507
+ var isAllowed = (value, allowed) => allowed.includes(value);
7508
+ var moveToPhrase = (node, phrase) => {
7509
+ const previousValue = node.value;
7510
+ const previousPhrase = node.get("PHRASE");
7511
+ const previousPhraseValue = previousPhrase?.toValue();
7512
+ node.removeValue();
7513
+ if (previousPhrase) {
7514
+ previousPhrase.value = phrase;
7515
+ } else {
7516
+ const phraseNode = createCommon(node.getGedcom(), void 0, node.main);
7517
+ phraseNode.value = phrase;
7518
+ node.set("PHRASE", phraseNode);
7519
+ }
7520
+ return () => {
7521
+ if (previousValue === void 0) {
7522
+ node.removeValue();
7523
+ } else {
7524
+ node.value = previousValue;
7525
+ }
7526
+ if (previousPhrase) {
7527
+ previousPhrase.value = previousPhraseValue;
7528
+ } else {
7529
+ node.remove("PHRASE");
7530
+ }
7531
+ };
7532
+ };
7533
+ var normalizeEnum = (node, allowed, mode, restores) => {
7534
+ const value = node?.toValue();
7535
+ if (!node || typeof value !== "string" || !value.trim()) {
7536
+ return;
7537
+ }
7538
+ const normalized = mode === "upper" ? value.trim().toUpperCase() : value.trim().toLowerCase();
7539
+ if (isAllowed(normalized, allowed)) {
7540
+ if (value !== normalized) {
7541
+ const previous = node.value;
7542
+ node.value = normalized;
7543
+ restores.push(() => {
7544
+ node.value = previous;
7545
+ });
7546
+ }
7547
+ return;
7548
+ }
7549
+ restores.push(moveToPhrase(node, value.trim()));
7550
+ };
7551
+ var applyGedcom7Enumerations = (gedcom) => {
7552
+ const restores = [];
7553
+ gedcom.indis()?.forEach((indi) => {
7554
+ if (!indi) return;
7555
+ normalizeEnum(indi.get("SEX"), GEDCOM7_SEX_VALUES, "upper", restores);
7556
+ normalizeEnum(indi.get("RESN"), GEDCOM7_RESN_VALUES, "upper", restores);
7557
+ indi.get("FAMC")?.toList()?.forEach((famc) => {
7558
+ normalizeEnum(
7559
+ famc?.get("PEDI") ?? famc?.get("PEDT"),
7560
+ GEDCOM7_PEDI_VALUES,
7561
+ "lower",
7562
+ restores
7563
+ );
7564
+ });
7565
+ indi.get("ASSO")?.toList()?.forEach((asso) => {
7566
+ normalizeEnum(
7567
+ asso?.get("ROLE"),
7568
+ GEDCOM7_ROLE_VALUES,
7569
+ "upper",
7570
+ restores
7571
+ );
7572
+ });
7573
+ });
7574
+ gedcom.fams()?.forEach((fam) => {
7575
+ if (!fam) return;
7576
+ normalizeEnum(fam.get("RESN"), GEDCOM7_RESN_VALUES, "upper", restores);
7577
+ fam.get("ASSO")?.toList()?.forEach((asso) => {
7578
+ normalizeEnum(
7579
+ asso?.get("ROLE"),
7580
+ GEDCOM7_ROLE_VALUES,
7581
+ "upper",
7582
+ restores
7583
+ );
7584
+ });
7585
+ });
7586
+ return () => {
7587
+ for (let i = restores.length - 1; i >= 0; i -= 1) {
7588
+ restores[i]?.();
7589
+ }
7590
+ };
7591
+ };
7592
+
7593
+ // src/utils/gedcom7-dates.ts
7594
+ var GEDCOM7_CALENDARS = [
7595
+ "GREGORIAN",
7596
+ "JULIAN",
7597
+ "FRENCH_R",
7598
+ "HEBREW"
7599
+ ];
7600
+ var CALENDAR_PREFIX2 = /^(?<calendar>GREGORIAN|JULIAN|FRENCH_R|HEBREW)\b\s+/i;
7601
+ var parseGedcom7CalendarPrefix = (value) => {
7602
+ const match = value.match(CALENDAR_PREFIX2);
7603
+ if (!match?.groups?.calendar) {
7604
+ return { calendar: void 0, rest: value };
7605
+ }
7606
+ return {
7607
+ calendar: match.groups.calendar.toUpperCase(),
7608
+ rest: value.slice(match[0].length)
7609
+ };
7610
+ };
7611
+ var withGedcom7Calendar = (value, calendar) => {
7612
+ if (!calendar || calendar.toUpperCase() === "GREGORIAN") {
7613
+ return value;
7614
+ }
7615
+ const upper = calendar.toUpperCase();
7616
+ if (GEDCOM7_CALENDARS.includes(upper)) {
7617
+ const { rest } = parseGedcom7CalendarPrefix(value);
7618
+ return `${upper} ${rest}`.trim();
7619
+ }
7620
+ return value;
7621
+ };
7622
+ var applyGedcom7DatePhrases = (gedcom) => {
7623
+ const restores = [];
7624
+ const visitDate = (dateNode) => {
7625
+ if (!dateNode || typeof dateNode.toValue !== "function") {
7626
+ return;
7627
+ }
7628
+ const asDate = dateNode;
7629
+ const hasComponents = !!(asDate.DAY || asDate.MONTH || asDate.YEAR);
7630
+ const raw = dateNode.exportValue?.() ?? dateNode.toValue();
7631
+ if (typeof raw !== "string" || !raw.trim()) {
7632
+ return;
7633
+ }
7634
+ const { calendar, rest } = parseGedcom7CalendarPrefix(raw.trim());
7635
+ const looksStructured = hasComponents || /^(ABT|CAL|EST|BEF|AFT|FROM|TO|BET|INT)(\s|$)/i.test(rest) || /\bAND\b/i.test(rest) || /^\d{1,2}\s+[A-Z]{3}\s+\d{1,4}/i.test(rest) || /^[A-Z]{3}\s+\d{1,4}/i.test(rest) || /^\d{1,4}$/.test(rest);
7636
+ if (looksStructured) {
7637
+ if (calendar && calendar !== "GREGORIAN") {
7638
+ const previous = dateNode.value;
7639
+ const next = withGedcom7Calendar(rest, calendar);
7640
+ if (previous !== next) {
7641
+ dateNode.value = next;
7642
+ restores.push(() => {
7643
+ dateNode.value = previous;
7644
+ });
7645
+ }
7646
+ }
7647
+ return;
7648
+ }
7649
+ const previousValue = dateNode.value;
7650
+ const previousPhrase = dateNode.get("PHRASE");
7651
+ const previousPhraseValue = previousPhrase?.toValue();
7652
+ dateNode.removeValue();
7653
+ if (previousPhrase) {
7654
+ previousPhrase.value = raw.trim();
7655
+ } else {
7656
+ const phrase = createCommon(
7657
+ dateNode.getGedcom(),
7658
+ void 0,
7659
+ dateNode.main
7660
+ );
7661
+ phrase.value = raw.trim();
7662
+ dateNode.set("PHRASE", phrase);
7663
+ }
7664
+ restores.push(() => {
7665
+ if (previousValue === void 0) {
7666
+ dateNode.removeValue();
7667
+ } else {
7668
+ dateNode.value = previousValue;
7669
+ }
7670
+ if (previousPhrase) {
7671
+ previousPhrase.value = previousPhraseValue;
7672
+ } else {
7673
+ dateNode.remove("PHRASE");
7674
+ }
7675
+ });
7676
+ };
7677
+ const walk = (node) => {
7678
+ if (!node) return;
7679
+ node.get("DATE")?.toList()?.forEach((item) => visitDate(item));
7680
+ node.get("SDATE")?.toList()?.forEach((item) => visitDate(item));
7681
+ };
7682
+ gedcom.indis()?.forEach((indi) => {
7683
+ if (!indi) return;
7684
+ walk(indi);
7685
+ [
7686
+ "BIRT",
7687
+ "DEAT",
7688
+ "CHR",
7689
+ "BAPM",
7690
+ "BURI",
7691
+ "CREM",
7692
+ "ADOP",
7693
+ "EVEN",
7694
+ "RESI",
7695
+ "OCCU",
7696
+ "MARR"
7697
+ ].forEach((tag) => {
7698
+ indi.get(tag)?.toList()?.forEach((event) => walk(event));
7699
+ });
7700
+ });
7701
+ gedcom.fams()?.forEach((fam) => {
7702
+ if (!fam) return;
7703
+ ["MARR", "DIV", "ENGA", "ANUL", "EVEN"].forEach((tag) => {
7704
+ fam.get(tag)?.toList()?.forEach((event) => walk(event));
7705
+ });
7706
+ });
7707
+ return () => {
7708
+ for (let i = restores.length - 1; i >= 0; i -= 1) {
7709
+ restores[i]?.();
7710
+ }
7711
+ };
7712
+ };
7713
+
7714
+ // src/utils/uid.ts
7715
+ var createGedcomUid = () => {
7716
+ const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
7717
+ if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
7718
+ return cryptoObj.randomUUID();
7719
+ }
7720
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
7721
+ const rand = Math.random() * 16 | 0;
7722
+ const value = char === "x" ? rand : rand & 3 | 8;
7723
+ return value.toString(16);
7724
+ });
7725
+ };
7726
+
7727
+ // src/utils/gedcom7-metadata.ts
7728
+ var GEDCOM_MONTHS2 = [
7729
+ "JAN",
7730
+ "FEB",
7731
+ "MAR",
7732
+ "APR",
7733
+ "MAY",
7734
+ "JUN",
7735
+ "JUL",
7736
+ "AUG",
7737
+ "SEP",
7738
+ "OCT",
7739
+ "NOV",
7740
+ "DEC"
7741
+ ];
7742
+ var formatGedcom7Timestamp = (date = /* @__PURE__ */ new Date()) => {
7743
+ const day = String(date.getUTCDate());
7744
+ const month = GEDCOM_MONTHS2[date.getUTCMonth()];
7745
+ const year = String(date.getUTCFullYear());
7746
+ const hh = String(date.getUTCHours()).padStart(2, "0");
7747
+ const mm = String(date.getUTCMinutes()).padStart(2, "0");
7748
+ const ss = String(date.getUTCSeconds()).padStart(2, "0");
7749
+ return {
7750
+ date: `${day} ${month} ${year}`,
7751
+ time: `${hh}:${mm}:${ss}Z`
7752
+ };
7753
+ };
7754
+ var ensureMetaDate = (gedcom, record, tag, when, restores) => {
7755
+ if (record.get(tag)) {
7756
+ return;
7757
+ }
7758
+ const { date, time } = formatGedcom7Timestamp(when);
7759
+ const meta = createCommon(gedcom, void 0, record);
7760
+ meta.type = tag;
7761
+ const dateNode = createCommonDate(gedcom, void 0, record, meta);
7762
+ dateNode.type = "DATE";
7763
+ dateNode.value = date;
7764
+ dateNode.set("TIME", time);
7765
+ meta.set("DATE", dateNode);
7766
+ record.set(tag, meta);
7767
+ restores.push(() => {
7768
+ record.remove(tag);
7769
+ });
7770
+ };
7771
+ var ensureUid = (record, restores) => {
7772
+ const existing = record.get("UID")?.toValue();
7773
+ if (typeof existing === "string" && existing.trim()) {
7774
+ return;
7775
+ }
7776
+ const uid = createGedcomUid();
7777
+ record.set("UID", uid);
7778
+ restores.push(() => {
7779
+ record.remove("UID");
7780
+ });
7781
+ };
7782
+ var applyGedcom7RecordMetadata = (gedcom, options) => {
7783
+ const restores = [];
7784
+ const now = /* @__PURE__ */ new Date();
7785
+ const visit = (record) => {
7786
+ if (!record?.id) {
7787
+ return;
7788
+ }
7789
+ ensureUid(record, restores);
7790
+ ensureMetaDate(gedcom, record, "CREA", now, restores);
7791
+ ensureMetaDate(gedcom, record, "CHAN", now, restores);
7792
+ };
7793
+ gedcom.indis()?.forEach((indi) => visit(indi));
7794
+ gedcom.fams()?.forEach((fam) => visit(fam));
7795
+ gedcom.objes()?.forEach((obje) => visit(obje));
7796
+ gedcom.sours()?.forEach((sour) => visit(sour));
7797
+ gedcom.repos()?.forEach((repo) => visit(repo));
7798
+ gedcom.subms()?.forEach((subm) => visit(subm));
7799
+ gedcom.snotes()?.forEach((note) => visit(note));
7800
+ return () => {
7801
+ for (let i = restores.length - 1; i >= 0; i -= 1) {
7802
+ restores[i]?.();
7803
+ }
7804
+ };
7805
+ };
7806
+
7807
+ // src/utils/gedcom7-schema.ts
7808
+ var TREEVIZ_EXTENSION_BASE_URI = "https://treeviz.com/gedcom";
7809
+ var TREEVIZ_EXTENSION_URIS = {
7810
+ _ORIGHEAD: `${TREEVIZ_EXTENSION_BASE_URI}#_ORIGHEAD`,
7811
+ _TREE: `${TREEVIZ_EXTENSION_BASE_URI}#_TREE`,
7812
+ _TID: `${TREEVIZ_EXTENSION_BASE_URI}#_TID`,
7813
+ _PRIM: `${TREEVIZ_EXTENSION_BASE_URI}#_PRIM`,
7814
+ _FREL: `${TREEVIZ_EXTENSION_BASE_URI}#_FREL`,
7815
+ _MREL: `${TREEVIZ_EXTENSION_BASE_URI}#_MREL`,
7816
+ _SREL: `${TREEVIZ_EXTENSION_BASE_URI}#_SREL`,
7817
+ _UID: `${TREEVIZ_EXTENSION_BASE_URI}#_UID`,
7818
+ _WLNK: `${TREEVIZ_EXTENSION_BASE_URI}#_WLNK`,
7819
+ _MTTAG: `${TREEVIZ_EXTENSION_BASE_URI}#_MTTAG`,
7820
+ _MTCAT: `${TREEVIZ_EXTENSION_BASE_URI}#_MTCAT`,
7821
+ _CLON: `${TREEVIZ_EXTENSION_BASE_URI}#_CLON`,
7822
+ _MSER: `${TREEVIZ_EXTENSION_BASE_URI}#_MSER`,
7823
+ _OID: `${TREEVIZ_EXTENSION_BASE_URI}#_OID`,
7824
+ _LKID: `${TREEVIZ_EXTENSION_BASE_URI}#_LKID`,
7825
+ _PHOTO_RIN: `${TREEVIZ_EXTENSION_BASE_URI}#_PHOTO_RIN`,
7826
+ _EXPORTED_FROM_SITE_ID: `${TREEVIZ_EXTENSION_BASE_URI}#_EXPORTED_FROM_SITE_ID`,
7827
+ _FS_ID: `${TREEVIZ_EXTENSION_BASE_URI}#_FS_ID`,
7828
+ _FS_LINK: `${TREEVIZ_EXTENSION_BASE_URI}#_FS_LINK`,
7829
+ _FS_MATCH: `${TREEVIZ_EXTENSION_BASE_URI}#_FS_MATCH`,
7830
+ _FS_SOUR: `${TREEVIZ_EXTENSION_BASE_URI}#_FS_SOUR`,
7831
+ _IS_FS: `${TREEVIZ_EXTENSION_BASE_URI}#_IS_FS`,
7832
+ _LABEL: `${TREEVIZ_EXTENSION_BASE_URI}#_LABEL`,
7833
+ _MILT: `${TREEVIZ_EXTENSION_BASE_URI}#_MILT`,
7834
+ _MILTID: `${TREEVIZ_EXTENSION_BASE_URI}#_MILTID`,
7835
+ _ORIG: `${TREEVIZ_EXTENSION_BASE_URI}#_ORIG`,
7836
+ _ENV: `${TREEVIZ_EXTENSION_BASE_URI}#_ENV`,
7837
+ _TREEVIZ_SETTINGS: `${TREEVIZ_EXTENSION_BASE_URI}#_TREEVIZ_SETTINGS`,
7838
+ _TREEVIZ_VIEW: `${TREEVIZ_EXTENSION_BASE_URI}#_TREEVIZ_VIEW`,
7839
+ _TREEVIZ_DETECTIVE: `${TREEVIZ_EXTENSION_BASE_URI}#_TREEVIZ_DETECTIVE`,
7840
+ _TREEVIZ_DETECTIVE_STATE: `${TREEVIZ_EXTENSION_BASE_URI}#_TREEVIZ_DETECTIVE_STATE`
7841
+ };
7842
+ var parseTagDefinition = (payload) => {
7843
+ const trimmed = payload.trim();
7844
+ const space = trimmed.indexOf(" ");
7845
+ if (space <= 0) {
7846
+ return void 0;
7847
+ }
7848
+ const tag = trimmed.slice(0, space).trim();
7849
+ const uri = trimmed.slice(space + 1).trim();
7850
+ if (!tag || !uri) {
7851
+ return void 0;
7852
+ }
7853
+ return { tag, uri };
7854
+ };
7855
+ var getExtensionSchema = (gedcom) => {
7856
+ const map = /* @__PURE__ */ new Map();
7857
+ const schma = gedcom.get("HEAD")?.get("SCHMA");
7858
+ if (!schma) {
7859
+ return map;
7860
+ }
7861
+ schma.get("TAG")?.toList()?.forEach((tagNode) => {
7862
+ const payload = tagNode?.toValue();
7863
+ if (typeof payload !== "string") {
7864
+ return;
7865
+ }
7866
+ const parsed = parseTagDefinition(payload);
7867
+ if (parsed) {
7868
+ map.set(parsed.tag.toUpperCase(), parsed.uri);
7869
+ }
7870
+ });
7871
+ return map;
7872
+ };
7873
+ var ensureSchemaNode = (gedcom) => {
7874
+ const head = gedcom.get("HEAD");
7875
+ if (!head) {
7876
+ return void 0;
7877
+ }
7878
+ let schma = head.get("SCHMA");
7879
+ if (!schma) {
7880
+ schma = createCommon(gedcom, void 0, head);
7881
+ head.set("SCHMA", schma);
7882
+ }
7883
+ return schma;
7884
+ };
7885
+ var registerExtensionTag = (gedcom, tag, uri) => {
7886
+ const normalizedTag = tag.trim().toUpperCase();
7887
+ const normalizedUri = uri.trim();
7888
+ if (!normalizedTag.startsWith("_") || !normalizedUri) {
7889
+ return void 0;
7890
+ }
7891
+ const schma = ensureSchemaNode(gedcom);
7892
+ if (!schma) {
7893
+ return void 0;
7894
+ }
7895
+ const payload = `${normalizedTag} ${normalizedUri}`;
7896
+ const existingList = schma.get("TAG")?.toList();
7897
+ const existing = existingList?.values().find((node) => {
7898
+ const value = node?.toValue();
7899
+ return typeof value === "string" && value.trim().toUpperCase().startsWith(`${normalizedTag} `);
7900
+ });
7901
+ if (existing) {
7902
+ existing.value = payload;
7903
+ return existing;
7904
+ }
7905
+ const tagNode = createCommon(gedcom, void 0, schma);
7906
+ tagNode.value = payload;
7907
+ schma.assign("TAG", tagNode);
7908
+ return tagNode;
7909
+ };
7910
+ var ensureExtensionSchema = (gedcom, extraTags) => {
7911
+ const head = gedcom.get("HEAD");
7912
+ const hadSchma = !!head?.get("SCHMA");
7913
+ const previousPayloads = head?.get("SCHMA")?.get("TAG")?.toList()?.values().map((node) => ({
7914
+ node,
7915
+ value: node?.toValue()
7916
+ })) ?? [];
7917
+ const tags = /* @__PURE__ */ new Set([
7918
+ ...Object.keys(TREEVIZ_EXTENSION_URIS),
7919
+ ...(extraTags ?? []).map((tag) => tag.toUpperCase())
7920
+ ]);
7921
+ tags.forEach((tag) => {
7922
+ const uri = TREEVIZ_EXTENSION_URIS[tag] ?? `${TREEVIZ_EXTENSION_BASE_URI}#${tag}`;
7923
+ if (tag.startsWith("_")) {
7924
+ registerExtensionTag(gedcom, tag, uri);
7925
+ }
7926
+ });
7927
+ return () => {
7928
+ if (!head) {
7929
+ return;
7930
+ }
7931
+ if (!hadSchma) {
7932
+ head.remove("SCHMA");
7933
+ return;
7934
+ }
7935
+ const schma = head.get("SCHMA");
7936
+ const list = schma?.get("TAG")?.toList();
7937
+ const keep = new Set(
7938
+ previousPayloads.map(
7939
+ (item) => typeof item.value === "string" ? item.value : void 0
7940
+ ).filter((value) => !!value)
7941
+ );
7942
+ list?.values().forEach((node) => {
7943
+ const value = node?.toValue();
7944
+ if (typeof value === "string" && !keep.has(value) && node) {
7945
+ list.delete(node);
7946
+ }
7947
+ });
7948
+ previousPayloads.forEach(({ node, value }) => {
7949
+ if (node && value !== void 0) {
7950
+ node.value = value;
7951
+ }
7952
+ });
7953
+ };
7954
+ };
7955
+
6763
7956
  // package.json
6764
7957
  var package_default = {
6765
- version: "2.0.10"};
7958
+ version: "2.1.0"};
6766
7959
 
6767
7960
  // src/utils/get-product-details.ts
6768
7961
  var getVersion = () => package_default.version;
6769
7962
 
7963
+ // src/utils/multimedia.ts
7964
+ var inferMediaForm = (file, fallback = "bin") => {
7965
+ const withoutQuery = file.split("?")[0] || file;
7966
+ const ext = getFileExtension(withoutQuery);
7967
+ if (ext) {
7968
+ return ext.toLowerCase() === "jpeg" ? "jpg" : ext.toLowerCase();
7969
+ }
7970
+ return fallback;
7971
+ };
7972
+ var mergeMediaLists = (...lists) => {
7973
+ const merged = {};
7974
+ for (const list of lists) {
7975
+ if (!list) continue;
7976
+ Object.assign(merged, list);
7977
+ }
7978
+ return merged;
7979
+ };
7980
+
7981
+ // src/classes/note.ts
7982
+ var CommonNote = class extends Common {
7983
+ constructor(gedcom, id, main, parent) {
7984
+ super(gedcom, id, main, parent);
7985
+ if (!id) {
7986
+ delete this.id;
7987
+ }
7988
+ }
7989
+ set value(value) {
7990
+ const [note, ...contents] = value?.split(/\r?\n/) ?? [];
7991
+ this._value = note;
7992
+ if (contents.length) {
7993
+ const newContents = new List();
7994
+ contents.forEach((c, i) => {
7995
+ const newContent = createCommon(
7996
+ this._gedcom,
7997
+ void 0,
7998
+ this.main
7999
+ );
8000
+ newContent.id = `@U${i}@`;
8001
+ newContent.value = c;
8002
+ newContents.append(newContent);
8003
+ });
8004
+ const firstItem = newContents.index(0);
8005
+ if (firstItem) {
8006
+ if (newContents.length === 1) {
8007
+ this.set("CONT", firstItem);
8008
+ } else {
8009
+ this.set("CONT", newContents);
8010
+ }
8011
+ }
8012
+ }
8013
+ }
8014
+ get value() {
8015
+ const contents = this.get("CONT")?.toList().map((content) => content.value).join("\n");
8016
+ if (contents) {
8017
+ return `${this._value}
8018
+ ${contents}`;
8019
+ }
8020
+ return this._value;
8021
+ }
8022
+ exportValue() {
8023
+ return this._value;
8024
+ }
8025
+ };
8026
+ var createCommonNote = (gedcom, id, main, parent) => {
8027
+ return createProxy(
8028
+ new CommonNote(gedcom, id, main, parent)
8029
+ );
8030
+ };
8031
+
8032
+ // src/classes/obje.ts
8033
+ var Obje = class extends Common {
8034
+ standardizeMedia(namespace, override = true, urlGetter, gedcomVersion) {
8035
+ if (!this._gedcom) {
8036
+ return this;
8037
+ }
8038
+ const rin = this?.get("RIN")?.toValue();
8039
+ const clone = this?.get("_CLON._OID")?.toValue();
8040
+ const mser = this?.get("_MSER._LKID")?.toValue();
8041
+ const title = this?.get("FILE.TITL")?.toValue() ?? this?.get("TITL")?.toValue() ?? "";
8042
+ const note = this?.get("FILE.NOTE")?.toValue() ?? this?.get("NOTE")?.toValue() ?? "";
8043
+ const formRaw = this?.get("FILE.FORM")?.toValue() ?? this?.get("FORM")?.toValue();
8044
+ const mediType = this?.get("FILE.FORM.TYPE")?.toValue() ?? this?.get("FORM.TYPE")?.toValue() ?? this?.get("MEDI")?.toValue();
8045
+ const file = this?.get("FILE")?.toValue();
8046
+ const imgId = rin || clone || mser;
8047
+ const url = file || namespace && urlGetter?.(namespace, imgId);
8048
+ if (!url) {
8049
+ return this;
8050
+ }
8051
+ const version = normalizeGedcomVersion(
8052
+ gedcomVersion ?? this._gedcom.getGedcomVersion?.()
8053
+ );
8054
+ const form = formRaw || inferMediaForm(String(url));
8055
+ const newObject = createObje(this._gedcom, this.id, this.main);
8056
+ if (!override) {
8057
+ Object.assign(newObject, this);
8058
+ }
8059
+ const fileNode = createCommon(this._gedcom, void 0, this.main);
8060
+ fileNode.value = url;
8061
+ if (version === "7.0") {
8062
+ const formNode = createCommon(this._gedcom, void 0, fileNode);
8063
+ formNode.value = form;
8064
+ if (mediType) {
8065
+ formNode.set("TYPE", String(mediType));
8066
+ }
8067
+ fileNode.set("FORM", formNode);
8068
+ if (title) {
8069
+ fileNode.set("TITL", String(title));
8070
+ }
8071
+ if (note) {
8072
+ fileNode.set("NOTE", String(note));
8073
+ }
8074
+ newObject.set("FILE", fileNode);
8075
+ } else {
8076
+ newObject.set("FILE", fileNode);
8077
+ newObject.set("FORM", form);
8078
+ if (mediType) {
8079
+ newObject.set("MEDI", String(mediType));
8080
+ }
8081
+ if (title) {
8082
+ newObject.set("TITL", String(title));
8083
+ if (!note) {
8084
+ newObject.set("NOTE", String(title));
8085
+ }
8086
+ }
8087
+ if (note) {
8088
+ newObject.set("NOTE", String(note));
8089
+ if (!title) {
8090
+ newObject.set("TITL", String(note));
8091
+ }
8092
+ }
8093
+ }
8094
+ const prim = this.get("_PRIM")?.toValue();
8095
+ if (prim === "Y" || prim === "N") {
8096
+ newObject.set("_PRIM", prim);
8097
+ }
8098
+ const photoRin = this.get("_PHOTO_RIN")?.toValue();
8099
+ if (rin) {
8100
+ newObject.set("RIN", String(rin));
8101
+ }
8102
+ if (photoRin) {
8103
+ newObject.set("_PHOTO_RIN", String(photoRin));
8104
+ }
8105
+ const clonNode = this.get("_CLON");
8106
+ if (clonNode) {
8107
+ newObject.set("_CLON", clonNode);
8108
+ }
8109
+ const mserNode = this.get("_MSER");
8110
+ if (mserNode) {
8111
+ newObject.set("_MSER", mserNode);
8112
+ }
8113
+ const oidNode = this.get("_OID");
8114
+ if (oidNode && !newObject.get("_OID")) {
8115
+ newObject.set("_OID", oidNode);
8116
+ }
8117
+ const lkidNode = this.get("_LKID");
8118
+ if (lkidNode && !newObject.get("_LKID")) {
8119
+ newObject.set("_LKID", lkidNode);
8120
+ }
8121
+ if (override) {
8122
+ this.remove("FILE");
8123
+ this.remove("FORM");
8124
+ this.remove("MEDI");
8125
+ this.remove("TITL");
8126
+ this.remove("NOTE");
8127
+ Object.assign(this, newObject);
8128
+ return this;
8129
+ }
8130
+ return newObject;
8131
+ }
8132
+ };
8133
+ var createObje = (gedcom, id, main, parent) => {
8134
+ return createProxy(
8135
+ new Obje(gedcom, id, main, parent)
8136
+ );
8137
+ };
8138
+
8139
+ // src/classes/objes.ts
8140
+ var Objects = class _Objects extends List {
8141
+ copy() {
8142
+ const newList = new _Objects();
8143
+ this.entries().forEach(([key, value]) => {
8144
+ newList.item(key, value);
8145
+ });
8146
+ return newList;
8147
+ }
8148
+ except(item) {
8149
+ return this.copy().delete(item);
8150
+ }
8151
+ };
8152
+
6770
8153
  // src/classes/gedcom.ts
6771
8154
  var isObjectPatch = (value) => {
6772
8155
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -6962,6 +8345,189 @@ var GedCom = class extends Common {
6962
8345
  fams() {
6963
8346
  return this.getList("@@FAM");
6964
8347
  }
8348
+ nextIndiKey() {
8349
+ const ids = [];
8350
+ this.indis()?.forEach((_indi, id) => {
8351
+ ids.push(String(id));
8352
+ });
8353
+ return nextRecordId(ids, "I");
8354
+ }
8355
+ nextFamKey() {
8356
+ const ids = [];
8357
+ this.fams()?.forEach((_fam, id) => {
8358
+ ids.push(String(id));
8359
+ });
8360
+ return nextRecordId(ids, "F");
8361
+ }
8362
+ createIndividual() {
8363
+ const existing = this.indis();
8364
+ const indis = existing ?? new Individuals();
8365
+ if (!existing) {
8366
+ this.set("@@INDI", indis);
8367
+ }
8368
+ const id = this.nextIndiKey();
8369
+ const indi = createIndi(this, id);
8370
+ indi.type = "INDI";
8371
+ if (this.getGedcomVersion() === "7.0") {
8372
+ indi.set("UID", createGedcomUid());
8373
+ }
8374
+ indis.item(id, indi);
8375
+ return indi;
8376
+ }
8377
+ createFamily() {
8378
+ const existing = this.fams();
8379
+ const fams = existing ?? new Families();
8380
+ if (!existing) {
8381
+ this.set("@@FAM", fams);
8382
+ }
8383
+ const id = this.nextFamKey();
8384
+ const fam = createFam(this, id);
8385
+ fam.type = "FAM";
8386
+ if (this.getGedcomVersion() === "7.0") {
8387
+ fam.set("UID", createGedcomUid());
8388
+ }
8389
+ fams.item(id, fam);
8390
+ return fam;
8391
+ }
8392
+ nextObjeKey() {
8393
+ const ids = [];
8394
+ this.objes()?.forEach((_obje, id) => {
8395
+ ids.push(String(id));
8396
+ });
8397
+ return nextRecordId(ids, "O");
8398
+ }
8399
+ /**
8400
+ * Create a top-level multimedia record (`0 @On@ OBJE`) with FILE/FORM/TITL.
8401
+ * GEDCOM 7 uses nested `FILE → FORM [→ TYPE]`; 5.5.1 uses sibling FORM/TITL.
8402
+ */
8403
+ createMultimediaRecord(input) {
8404
+ const existing = this.objes();
8405
+ const objes = existing ?? new Objects();
8406
+ if (!existing) {
8407
+ this.set("@@OBJE", objes);
8408
+ }
8409
+ const id = this.nextObjeKey();
8410
+ const obje = createObje(this, id);
8411
+ obje.type = "OBJE";
8412
+ objes.item(id, obje);
8413
+ const version = normalizeGedcomVersion(
8414
+ input.gedcomVersion ?? this.getGedcomVersion()
8415
+ );
8416
+ const form = input.form || inferMediaForm(input.file);
8417
+ const fileNode = createCommon(this, void 0, obje);
8418
+ fileNode.value = input.file;
8419
+ if (version === "7.0") {
8420
+ const formNode = createCommon(
8421
+ this,
8422
+ void 0,
8423
+ fileNode
8424
+ );
8425
+ formNode.value = form;
8426
+ if (input.mediType) {
8427
+ formNode.set("TYPE", input.mediType);
8428
+ }
8429
+ fileNode.set("FORM", formNode);
8430
+ if (input.title) {
8431
+ fileNode.set("TITL", input.title);
8432
+ }
8433
+ obje.set("FILE", fileNode);
8434
+ } else {
8435
+ obje.set("FILE", fileNode);
8436
+ obje.set("FORM", form);
8437
+ if (input.mediType) {
8438
+ obje.set("MEDI", input.mediType);
8439
+ }
8440
+ if (input.title) {
8441
+ obje.set("TITL", input.title);
8442
+ }
8443
+ }
8444
+ if (input.primary) {
8445
+ obje.set("_PRIM", "Y");
8446
+ }
8447
+ if (normalizeGedcomVersion(
8448
+ input.gedcomVersion ?? this.getGedcomVersion()
8449
+ ) === "7.0") {
8450
+ obje.set("UID", createGedcomUid());
8451
+ }
8452
+ return obje;
8453
+ }
8454
+ nextSnoteKey() {
8455
+ const ids = [];
8456
+ this.snotes()?.forEach((_note, id) => {
8457
+ ids.push(String(id));
8458
+ });
8459
+ return nextRecordId(ids, "N");
8460
+ }
8461
+ snotes() {
8462
+ return this.getList("@@SNOTE");
8463
+ }
8464
+ snote(index) {
8465
+ return this.getMain(this.snotes(), index);
8466
+ }
8467
+ /**
8468
+ * Create a top-level shared note record (`0 @Nn@ SNOTE` for GEDCOM 7).
8469
+ */
8470
+ createSharedNote(text) {
8471
+ const existing = this.snotes();
8472
+ const notes = existing ?? new List();
8473
+ if (!existing) {
8474
+ this.set("@@SNOTE", notes);
8475
+ }
8476
+ const id = this.nextSnoteKey();
8477
+ const note = createCommonNote(this, id);
8478
+ note.type = "SNOTE";
8479
+ note.value = text;
8480
+ if (this.getGedcomVersion() === "7.0") {
8481
+ note.set("UID", createGedcomUid());
8482
+ }
8483
+ notes.item(id, note);
8484
+ return note;
8485
+ }
8486
+ registerExtensionTag(tag, uri) {
8487
+ return registerExtensionTag(this, tag, uri);
8488
+ }
8489
+ getExtensionSchema() {
8490
+ return getExtensionSchema(this);
8491
+ }
8492
+ ensureExtensionSchema(extraTags) {
8493
+ ensureExtensionSchema(this, extraTags);
8494
+ return getExtensionSchema(this);
8495
+ }
8496
+ /**
8497
+ * Collect multimedia descriptors via each individual's `multimedia()`.
8498
+ */
8499
+ async collectMultimedia(options) {
8500
+ const people = [];
8501
+ if (options?.indiIds?.length) {
8502
+ options.indiIds.forEach((id) => {
8503
+ const indi = this.indi(id);
8504
+ if (indi) {
8505
+ people.push(indi);
8506
+ }
8507
+ });
8508
+ } else {
8509
+ this.indis()?.forEach((indi) => {
8510
+ if (indi) {
8511
+ people.push(indi);
8512
+ }
8513
+ });
8514
+ }
8515
+ const lists = await Promise.all(
8516
+ people.map((indi) => indi.multimedia(options?.namespace))
8517
+ );
8518
+ return mergeMediaLists(...lists);
8519
+ }
8520
+ /**
8521
+ * Serialize as GEDCOM 7 and pack a FamilySearch GEDZIP (`.gdz`).
8522
+ * Media bytes must be supplied by the caller (cache/fetch outside the parser).
8523
+ */
8524
+ async toGedzip(options) {
8525
+ const text = this.toGedcom(void 0, 0, {
8526
+ ...options,
8527
+ gedcomVersion: "7.0"
8528
+ });
8529
+ return buildGedzip(text, options?.media ?? []);
8530
+ }
6965
8531
  objes() {
6966
8532
  return this.getList("@@OBJE");
6967
8533
  }
@@ -7046,18 +8612,70 @@ var GedCom = class extends Common {
7046
8612
  });
7047
8613
  return lists;
7048
8614
  }
7049
- getDownloadHeader() {
8615
+ getGedcomVersion() {
8616
+ const raw = this.get("HEAD")?.get("GEDC")?.get("VERS")?.toValue();
8617
+ return normalizeGedcomVersion(
8618
+ typeof raw === "string" ? raw : void 0
8619
+ );
8620
+ }
8621
+ /** Rewrite HEAD.GEDC (and CHAR/FORM) for the requested export version. */
8622
+ applyExportVersion(version) {
8623
+ const head = this.get("HEAD");
8624
+ if (!head) {
8625
+ return;
8626
+ }
8627
+ this.applyGedcomSpecToHead(head, normalizeGedcomVersion(version));
8628
+ }
8629
+ applyGedcomSpecToHead(head, version) {
8630
+ const previousGedc = head.get("GEDC");
8631
+ const previousForm = previousGedc?.get("FORM")?.toValue();
8632
+ const previousChar = head.get("CHAR")?.toValue();
8633
+ head.remove("GEDC");
8634
+ const gedc = head.set("GEDC", "");
8635
+ if (!gedc) {
8636
+ return;
8637
+ }
8638
+ gedc.set("VERS", version);
8639
+ if (version === "7.0") {
8640
+ head.remove("CHAR");
8641
+ return;
8642
+ }
8643
+ gedc.set(
8644
+ "FORM",
8645
+ typeof previousForm === "string" && previousForm ? previousForm : "LINEAGE-LINKED"
8646
+ );
8647
+ if (!head.get("CHAR")?.toValue()) {
8648
+ head.set(
8649
+ "CHAR",
8650
+ typeof previousChar === "string" && previousChar ? previousChar : "UTF-8"
8651
+ );
8652
+ }
8653
+ }
8654
+ getDownloadHeader(gedcomVersion) {
7050
8655
  const newHead = createCommon();
7051
8656
  Object.assign(newHead, this.get("HEAD") ?? {});
8657
+ this.applyGedcomSpecToHead(
8658
+ newHead,
8659
+ gedcomVersion ?? this.getGedcomVersion()
8660
+ );
8661
+ const existingSour = this.get("HEAD")?.get("SOUR");
8662
+ const existingTreeName = existingSour?.get("_TREE")?.toValue();
8663
+ const existingTreeUniqueId = existingSour?.get("_TID")?.toValue();
7052
8664
  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();
8665
+ newSour.value = APP_BRANDING;
8666
+ newSour.set("NAME", APP_BRANDING);
8667
+ newSour.set("VERS", getVersion());
8668
+ if (typeof existingTreeName === "string" && existingTreeName.trim()) {
8669
+ newSour.set("_TREE", existingTreeName.trim());
8670
+ }
8671
+ if (typeof existingTreeUniqueId === "string" && existingTreeUniqueId.trim()) {
8672
+ newSour.set("_TID", existingTreeUniqueId.trim());
8673
+ }
8674
+ const corp = createCommon();
8675
+ corp.value = APP_BRANDING;
8676
+ corp.set("WWW", "treeviz.com");
8677
+ corp.set("EMAIL", "info@treeviz.com");
8678
+ newSour.set("CORP", corp);
7061
8679
  newHead.set("SOUR", newSour);
7062
8680
  return newHead;
7063
8681
  }
@@ -7086,20 +8704,45 @@ var GedCom = class extends Common {
7086
8704
  }
7087
8705
  toGedcom(tag, level, options) {
7088
8706
  if (options?.super) {
7089
- return super.toGedcom(tag, level, options);
8707
+ return appendGedcomTrailer(super.toGedcom(tag, level, options));
7090
8708
  }
8709
+ const requestedVersion = options?.gedcomVersion;
8710
+ const exportVersion = requestedVersion ? normalizeGedcomVersion(requestedVersion) : this.getGedcomVersion();
8711
+ const exportOptions = {
8712
+ ...options,
8713
+ obje: {
8714
+ standardize: options?.obje?.standardize ?? (requestedVersion !== void 0 || !options?.original),
8715
+ override: options?.obje?.override,
8716
+ namespace: options?.obje?.namespace
8717
+ }
8718
+ };
7091
8719
  const newGedcom = createGedCom();
7092
- if (!options?.original) {
8720
+ Object.assign(newGedcom, this);
8721
+ if (!exportOptions.original) {
7093
8722
  Object.assign(newGedcom, {
7094
- HEAD: this.getDownloadHeader()
8723
+ HEAD: this.getDownloadHeader(exportVersion)
7095
8724
  });
7096
8725
  }
7097
- Object.assign(newGedcom, this);
7098
- if (options?.indis?.length) {
7099
- const newContent = this.getIndiRelatedLists(options.indis);
8726
+ if (exportOptions.indis?.length) {
8727
+ const newContent = this.getIndiRelatedLists(exportOptions.indis);
7100
8728
  Object.assign(newGedcom, newContent);
7101
8729
  }
7102
- return newGedcom.toGedcom(tag, level, { ...options, super: true });
8730
+ const restoreEnums = exportVersion === "7.0" ? applyGedcom7Enumerations(newGedcom) : void 0;
8731
+ const restoreDates = exportVersion === "7.0" ? applyGedcom7DatePhrases(newGedcom) : void 0;
8732
+ const restoreMetadata = exportVersion === "7.0" ? applyGedcom7RecordMetadata(newGedcom) : void 0;
8733
+ const restoreSchema = exportVersion === "7.0" ? ensureExtensionSchema(newGedcom) : void 0;
8734
+ try {
8735
+ return newGedcom.toGedcom(tag, level, {
8736
+ ...exportOptions,
8737
+ gedcomVersion: exportVersion,
8738
+ super: true
8739
+ });
8740
+ } finally {
8741
+ restoreSchema?.();
8742
+ restoreMetadata?.();
8743
+ restoreDates?.();
8744
+ restoreEnums?.();
8745
+ }
7103
8746
  }
7104
8747
  hasTag(tag) {
7105
8748
  const tagName = typeof tag === "string" ? tag : tag?.get("NAME")?.toValue();
@@ -7578,158 +9221,6 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
7578
9221
  return mergedGedcom;
7579
9222
  };
7580
9223
 
7581
- // src/classes/note.ts
7582
- var CommonNote = class extends Common {
7583
- constructor(gedcom, id, main, parent) {
7584
- super(gedcom, id, main, parent);
7585
- delete this.id;
7586
- }
7587
- set value(value) {
7588
- const [note, ...contents] = value?.split(/\r?\n/) ?? [];
7589
- this._value = note;
7590
- if (contents.length) {
7591
- const newContents = new List();
7592
- contents.forEach((c, i) => {
7593
- const newContent = createCommon(
7594
- this._gedcom,
7595
- void 0,
7596
- this.main
7597
- );
7598
- newContent.id = `@U${i}@`;
7599
- newContent.value = c;
7600
- newContents.append(newContent);
7601
- });
7602
- const firstItem = newContents.index(0);
7603
- if (firstItem) {
7604
- if (newContents.length === 1) {
7605
- this.set("CONT", firstItem);
7606
- } else {
7607
- this.set("CONT", newContents);
7608
- }
7609
- }
7610
- }
7611
- }
7612
- get value() {
7613
- const contents = this.get("CONT")?.toList().map((content) => content.value).join("\n");
7614
- if (contents) {
7615
- return `${this._value}
7616
- ${contents}`;
7617
- }
7618
- return this._value;
7619
- }
7620
- exportValue() {
7621
- return this._value;
7622
- }
7623
- };
7624
- var createCommonNote = (gedcom, id, main, parent) => {
7625
- return createProxy(
7626
- new CommonNote(gedcom, id, main, parent)
7627
- );
7628
- };
7629
-
7630
- // src/classes/obje.ts
7631
- var Obje = class extends Common {
7632
- standardizeMedia(namespace, override = true, urlGetter) {
7633
- if (!this._gedcom) {
7634
- return this;
7635
- }
7636
- const rin = this?.get("RIN")?.toValue();
7637
- const clone = this?.get("_CLON._OID")?.toValue();
7638
- const mser = this?.get("_MSER._LKID")?.toValue();
7639
- const title = this?.get("FILE.TITL")?.toValue() ?? this?.get("TITL")?.toValue() ?? "";
7640
- const note = this?.get("FILE.NOTE")?.toValue() ?? this?.get("NOTE")?.toValue() ?? "";
7641
- const form = this?.get("FILE.FORM")?.toValue();
7642
- const file = this?.get("FILE")?.toValue();
7643
- const imgId = rin || clone || mser;
7644
- const url = file || namespace && urlGetter?.(namespace, imgId);
7645
- if (!url) {
7646
- return this;
7647
- }
7648
- const newObject = createObje(this._gedcom, this.id, this.main);
7649
- if (!override) {
7650
- Object.assign(newObject, this);
7651
- }
7652
- if (form) {
7653
- if (typeof form === "string") {
7654
- const newForm = createCommon(
7655
- this._gedcom,
7656
- void 0,
7657
- this.main
7658
- );
7659
- newForm.value = form;
7660
- newObject.set("FORM", newForm);
7661
- } else {
7662
- newObject.set("FORM", form);
7663
- }
7664
- }
7665
- if (title) {
7666
- if (typeof title === "string") {
7667
- const newTitle = createCommon(
7668
- this._gedcom,
7669
- void 0,
7670
- this.main
7671
- );
7672
- newTitle.value = title;
7673
- newObject.set("TITL", newTitle);
7674
- if (!note) {
7675
- newObject.set("NOTE", newTitle);
7676
- }
7677
- } else {
7678
- newObject.set("TITL", title);
7679
- if (!note) {
7680
- newObject.set("NOTE", title);
7681
- }
7682
- }
7683
- }
7684
- if (note) {
7685
- if (typeof note === "string") {
7686
- const newNote = createCommon(
7687
- this._gedcom,
7688
- void 0,
7689
- this.main
7690
- );
7691
- newNote.value = note;
7692
- newObject.set("NOTE", newNote);
7693
- if (!title) {
7694
- newObject.set("TITL", newNote);
7695
- }
7696
- } else {
7697
- newObject.set("NOTE", note);
7698
- if (!title) {
7699
- newObject.set("TITL", note);
7700
- }
7701
- }
7702
- }
7703
- const newUrl = createCommon(this._gedcom, void 0, this.main);
7704
- newUrl.value = url;
7705
- newObject.set("FILE", newUrl);
7706
- if (override) {
7707
- Object.assign(this, newObject);
7708
- return this;
7709
- }
7710
- return newObject;
7711
- }
7712
- };
7713
- var createObje = (gedcom, id, main, parent) => {
7714
- return createProxy(
7715
- new Obje(gedcom, id, main, parent)
7716
- );
7717
- };
7718
-
7719
- // src/classes/objes.ts
7720
- var Objects = class _Objects extends List {
7721
- copy() {
7722
- const newList = new _Objects();
7723
- this.entries().forEach(([key, value]) => {
7724
- newList.item(key, value);
7725
- });
7726
- return newList;
7727
- }
7728
- except(item) {
7729
- return this.copy().delete(item);
7730
- }
7731
- };
7732
-
7733
9224
  // src/classes/repo.ts
7734
9225
  var Repo = class extends Common {
7735
9226
  };