@treeviz/gedcom-parser 2.0.12 → 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 (67) hide show
  1. package/README.md +236 -989
  2. package/dist/classes/common.d.ts +4 -4
  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 +2 -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 +39 -2
  10. package/dist/classes/gedcom.d.ts.map +1 -1
  11. package/dist/classes/index.js +1364 -318
  12. package/dist/classes/indi.d.ts +19 -2
  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 +1368 -313
  19. package/dist/constants/constants.d.ts.map +1 -1
  20. package/dist/constants/index.js +312 -73
  21. package/dist/factories/index.js +310 -71
  22. package/dist/index.d.ts +10 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1547 -288
  25. package/dist/interfaces/common.d.ts +6 -4
  26. package/dist/interfaces/common.d.ts.map +1 -1
  27. package/dist/interfaces/fam.d.ts +3 -0
  28. package/dist/interfaces/fam.d.ts.map +1 -1
  29. package/dist/interfaces/gedcom.d.ts +21 -1
  30. package/dist/interfaces/gedcom.d.ts.map +1 -1
  31. package/dist/interfaces/indi.d.ts +11 -1
  32. package/dist/interfaces/indi.d.ts.map +1 -1
  33. package/dist/kinship-translator/index.js +310 -71
  34. package/dist/structures/gedcom.d.ts +1 -0
  35. package/dist/structures/gedcom.d.ts.map +1 -1
  36. package/dist/types/types.d.ts +22 -4
  37. package/dist/types/types.d.ts.map +1 -1
  38. package/dist/utils/common-creator.d.ts.map +1 -1
  39. package/dist/utils/fact-edit.d.ts +12 -0
  40. package/dist/utils/fact-edit.d.ts.map +1 -1
  41. package/dist/utils/family-edit.d.ts +2 -2
  42. package/dist/utils/family-edit.d.ts.map +1 -1
  43. package/dist/utils/gedcom-version.d.ts +6 -0
  44. package/dist/utils/gedcom-version.d.ts.map +1 -0
  45. package/dist/utils/gedcom7-dates.d.ts +20 -0
  46. package/dist/utils/gedcom7-dates.d.ts.map +1 -0
  47. package/dist/utils/gedcom7-enumerations.d.ts +26 -0
  48. package/dist/utils/gedcom7-enumerations.d.ts.map +1 -0
  49. package/dist/utils/gedcom7-metadata.d.ts +19 -0
  50. package/dist/utils/gedcom7-metadata.d.ts.map +1 -0
  51. package/dist/utils/gedcom7-schema.d.ts +27 -0
  52. package/dist/utils/gedcom7-schema.d.ts.map +1 -0
  53. package/dist/utils/gedzip.d.ts +81 -0
  54. package/dist/utils/gedzip.d.ts.map +1 -0
  55. package/dist/utils/index.d.ts +9 -0
  56. package/dist/utils/index.d.ts.map +1 -1
  57. package/dist/utils/index.js +1547 -288
  58. package/dist/utils/local-media.d.ts +30 -0
  59. package/dist/utils/local-media.d.ts.map +1 -0
  60. package/dist/utils/media-utils.d.ts +10 -6
  61. package/dist/utils/media-utils.d.ts.map +1 -1
  62. package/dist/utils/multimedia.d.ts +38 -0
  63. package/dist/utils/multimedia.d.ts.map +1 -0
  64. package/dist/utils/parser.d.ts.map +1 -1
  65. package/dist/utils/uid.d.ts +6 -0
  66. package/dist/utils/uid.d.ts.map +1 -0
  67. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,YAAY,kDAAkD,CAAC;AAE5E;;;GAGG;AAEH,eAAO,MAAM,YAAY,QACqE,CAAC;AAC/F,eAAO,MAAM,QAAQ,QACsF,CAAC;AAC5G,eAAO,MAAM,MAAM,QAAqB,CAAC;AACzC,eAAO,MAAM,aAAa,QAAiC,CAAC;AAC5D,eAAO,MAAM,YAAY,QAAsB,CAAC;AAEhD,8EAA8E;AAC9E,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAAmB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,YAAY,kDAAkD,CAAC;AAE5E;;;GAGG;AAEH,eAAO,MAAM,YAAY,QAC2E,CAAC;AACrG,eAAO,MAAM,QAAQ,QACiG,CAAC;AACvH,eAAO,MAAM,MAAM,QAAqB,CAAC;AACzC,eAAO,MAAM,aAAa,QAAiC,CAAC;AAC5D,eAAO,MAAM,YAAY,QAAsB,CAAC;AAEhD,8EAA8E;AAC9E,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAAmB,CAAC"}
@@ -9,8 +9,8 @@ var __export = (target, all) => {
9
9
 
10
10
  // src/constants/constants.ts
11
11
  var APP_BRANDING = "TreeViz - The Family Tree Visualiser & Editor";
12
- var REF_LINE_REG = /^0 (?:(@[_a-zA-Z0-9]*@) )?(?<type>_[_A-Z][_a-zA-Z]{2,}|_?[A-Z][a-zA-Z]{2,}) ?(?<value>.*)/gm;
13
- var LINE_REG = /(?<indent>^[0-9]) (?:(@[_a-zA-Z0-9]*@) )?(?<type>_[_A-Z][_a-zA-Z]{2,}|_?[A-Z][a-zA-Z]{2,}) ?(?<value>.*)/;
12
+ var REF_LINE_REG = /^0 (?:(@[_a-zA-Z0-9]*@) )?(?<type>_[_A-Z][_a-zA-Z0-9]{2,}|_?[A-Z][A-Za-z0-9]{2,}) ?(?<value>.*)/gm;
13
+ var LINE_REG = /(?<indent>^[0-9]{1,2}) (?:(@[_a-zA-Z0-9]*@) )?(?<type>_[_A-Z][_a-zA-Z0-9]{2,}|_?[A-Z][A-Za-z0-9]{2,}) ?(?<value>.*)/;
14
14
  var ID_REG = /^@[_a-zA-Z0-9]+@/;
15
15
  var ID_GETTER_REG = /^(?<at>@)?(?<letter>[A-Z])?/i;
16
16
  var ID_SPLIT_REG = /^@[_a-zA-Z0-9]+@:/;
@@ -924,11 +924,17 @@ var List = class _List {
924
924
  const gedcom = [];
925
925
  this.entries().forEach(([_, item]) => {
926
926
  const validTag = getValidTag(tag);
927
- const validKey = item.id;
928
- const validValue = validKey ? validTag : item.exportValue();
929
- gedcom.push(
930
- `${level} ${validKey || validTag}${validValue ? ` ${validValue}` : ""}`
931
- );
927
+ const xref = item.id;
928
+ const payload = item.exportValue?.();
929
+ if (xref) {
930
+ gedcom.push(
931
+ `${level} ${xref} ${validTag}${payload ? ` ${payload}` : ""}`
932
+ );
933
+ } else {
934
+ gedcom.push(
935
+ `${level} ${validTag}${payload ? ` ${payload}` : ""}`
936
+ );
937
+ }
932
938
  gedcom.push(
933
939
  ...item.toGedcomLines(
934
940
  validTag,
@@ -1186,12 +1192,14 @@ var Common = class _Common {
1186
1192
  );
1187
1193
  }
1188
1194
  standardizeObject(tag, options) {
1189
- if (tag === "OBJE" && !options?.original && options?.obje?.standardize && options?.obje?.namespace && "standardizeMedia" in this) {
1195
+ if (tag === "OBJE" && options?.obje?.standardize && "standardizeMedia" in this) {
1190
1196
  const standardize = this.standardizeMedia;
1191
1197
  return standardize.call(
1192
1198
  this,
1193
1199
  options.obje.namespace,
1194
- options.obje.override
1200
+ options.obje.override,
1201
+ void 0,
1202
+ options.gedcomVersion
1195
1203
  );
1196
1204
  }
1197
1205
  }
@@ -1218,7 +1226,7 @@ var Common = class _Common {
1218
1226
  if (typeof prop?.toObject === "function") {
1219
1227
  if (prop instanceof _Common) {
1220
1228
  json[validKey] = prop.toObject(validKey, options);
1221
- } else {
1229
+ } else if (prop instanceof List) {
1222
1230
  json = {
1223
1231
  ...json,
1224
1232
  ...prop.toObject(validKey, options)
@@ -1276,8 +1284,12 @@ var Common = class _Common {
1276
1284
  this.standardizeObject(tag, options);
1277
1285
  const validKeys = getValidKeys(this);
1278
1286
  const gedcom = [];
1287
+ const isGedcom7Export = options?.gedcomVersion === "7.0";
1279
1288
  validKeys.forEach((key) => {
1280
1289
  const validKey = key;
1290
+ if (isGedcom7Export && validKey === "CONC") {
1291
+ return;
1292
+ }
1281
1293
  const prop = this.get(validKey);
1282
1294
  if (typeof prop?.toGedcomLines === "function") {
1283
1295
  if (prop instanceof _Common) {
@@ -1288,7 +1300,7 @@ var Common = class _Common {
1288
1300
  gedcom.push(
1289
1301
  ...prop.toGedcomLines(validKey, level + 1, options)
1290
1302
  );
1291
- } else {
1303
+ } else if (prop instanceof List) {
1292
1304
  gedcom.push(
1293
1305
  ...prop.toGedcomLines(validKey, level, options)
1294
1306
  );
@@ -1688,6 +1700,24 @@ var addFamilyFact = (fam, input) => {
1688
1700
  fillFact(gedcom, fam, event, input);
1689
1701
  return event;
1690
1702
  };
1703
+ var addNonEvent = (owner, input) => {
1704
+ const gedcom = owner.getGedcom();
1705
+ const eventType = input.event?.trim().toUpperCase();
1706
+ if (!gedcom || !eventType) {
1707
+ return void 0;
1708
+ }
1709
+ const no = createCommon(gedcom, void 0, owner);
1710
+ no.type = "NO";
1711
+ no.value = eventType;
1712
+ if (input.date?.trim()) {
1713
+ setDateOn(gedcom, owner, no, input.date);
1714
+ }
1715
+ if (input.note?.trim()) {
1716
+ no.set("NOTE", input.note.trim());
1717
+ }
1718
+ owner.assign("NO", no);
1719
+ return no;
1720
+ };
1691
1721
 
1692
1722
  // src/utils/family-edit.ts
1693
1723
  var PEDIGREE_VALUES = [
@@ -1922,16 +1952,58 @@ var getPlaces = (common, type = ["ALL" /* All */], maxLevel = 1, level = 0, main
1922
1952
  });
1923
1953
  return places;
1924
1954
  };
1955
+
1956
+ // src/utils/local-media.ts
1957
+ var localMediaResolver;
1958
+ var isRemoteOrEmbeddedMediaUrl = (path) => {
1959
+ if (!path) {
1960
+ return false;
1961
+ }
1962
+ return path.startsWith("data:") || path.startsWith("blob:") || /^https?:\/\//i.test(path);
1963
+ };
1964
+ var resolveLocalMediaUrl = async (path) => {
1965
+ if (!path) {
1966
+ return void 0;
1967
+ }
1968
+ if (isRemoteOrEmbeddedMediaUrl(path)) {
1969
+ return path;
1970
+ }
1971
+ const resolved = await localMediaResolver?.(path);
1972
+ return resolved;
1973
+ };
1925
1974
  var implemented = (type, ...args) => {
1926
1975
  };
1927
1976
 
1928
1977
  // src/utils/media-utils.ts
1929
1978
  var getFileExtension = (filename) => {
1930
- const match = filename.match(/\.([^.]+)$/);
1931
- return match ? match[1] : "";
1979
+ if (!filename) {
1980
+ return "";
1981
+ }
1982
+ if (filename.startsWith("data:")) {
1983
+ const mime = filename.slice(5).split(";")[0] || "";
1984
+ const subtype = (mime.split("/")[1] || "").toLowerCase();
1985
+ if (subtype === "jpeg") {
1986
+ return "jpg";
1987
+ }
1988
+ return subtype.split("+")[0] || "";
1989
+ }
1990
+ const withoutQuery = filename.split(/[?#]/)[0] || filename;
1991
+ const match = withoutQuery.match(/\.([^.]+)$/);
1992
+ return match ? match[1].toLowerCase() : "";
1932
1993
  };
1933
1994
  var isImageFormat = (format2) => {
1934
- if (!format2) return false;
1995
+ if (!format2) {
1996
+ return false;
1997
+ }
1998
+ let normalized = format2.toLowerCase().trim();
1999
+ if (normalized.startsWith("data:")) {
2000
+ normalized = getFileExtension(normalized);
2001
+ } else if (normalized.includes("/")) {
2002
+ normalized = normalized.split("/")[1]?.split("+")[0] || "";
2003
+ if (normalized === "jpeg") {
2004
+ normalized = "jpg";
2005
+ }
2006
+ }
1935
2007
  const imageFormats = [
1936
2008
  "jpg",
1937
2009
  "jpeg",
@@ -1943,7 +2015,11 @@ var isImageFormat = (format2) => {
1943
2015
  "tiff",
1944
2016
  "tif"
1945
2017
  ];
1946
- return imageFormats.includes(format2.toLowerCase());
2018
+ return imageFormats.includes(normalized);
2019
+ };
2020
+ var resolveObjeForm = (obje) => {
2021
+ const value = obje?.get?.("FILE.FORM")?.toValue?.() ?? obje?.get?.("FORM")?.toValue?.();
2022
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1947
2023
  };
1948
2024
  var uniqueItemsCache = /* @__PURE__ */ new WeakMap();
1949
2025
  var setNestedGroup = (obj, key, value, uniqueCounting = true) => {
@@ -2513,6 +2589,9 @@ var Fam = class extends Common {
2513
2589
  addFact(input) {
2514
2590
  return addFamilyFact(this, input);
2515
2591
  }
2592
+ addNonEvent(input) {
2593
+ return addNonEvent(this, input);
2594
+ }
2516
2595
  toList() {
2517
2596
  return new Families().concat(
2518
2597
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -3231,10 +3310,14 @@ var Indi = class extends Common {
3231
3310
  const clone = obje?.get("_CLON._OID")?.toValue();
3232
3311
  const mser = obje?.get("_MSER._LKID")?.toValue();
3233
3312
  let url = obje?.get("FILE")?.toValue();
3234
- const title = obje?.get("TITL")?.toValue() ?? "";
3235
- const type = obje?.get("FORM")?.toValue() ?? "raw";
3236
- const imgId = clone || mser;
3237
- if (!www || !tree || !this.id) {
3313
+ const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
3314
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3315
+ let imgId = clone || mser;
3316
+ const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
3317
+ if (!hasEmbeddedOrLocalFile && (!www || !tree || !this.id)) {
3318
+ return;
3319
+ }
3320
+ if (!this.id) {
3238
3321
  return;
3239
3322
  }
3240
3323
  if (!namespace && !url) {
@@ -3252,13 +3335,16 @@ var Indi = class extends Common {
3252
3335
  }
3253
3336
  url = url || `https://${www}/mediaui-viewer/tree/${tree}/media/${media}`;
3254
3337
  }
3338
+ if (!imgId && hasEmbeddedOrLocalFile && url) {
3339
+ imgId = typeof media === "string" && media || `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}`;
3340
+ }
3255
3341
  if (url && imgId) {
3256
- const id = `${tree}-${this.id}-${imgId}`;
3342
+ const id = `${tree || "gedzip"}-${this.id}-${imgId}`;
3257
3343
  list[id] = {
3258
3344
  key,
3259
3345
  isPrimary,
3260
3346
  id,
3261
- tree,
3347
+ tree: tree || "gedzip",
3262
3348
  imgId,
3263
3349
  person: this.id,
3264
3350
  title,
@@ -3304,9 +3390,11 @@ var Indi = class extends Common {
3304
3390
  obje.standardizeMedia();
3305
3391
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
3306
3392
  const url = obje?.get("FILE")?.toValue();
3307
- const title = obje?.get("NOTE")?.toValue() ?? "";
3308
- const type = obje?.get("FORM")?.toValue() ?? "raw";
3309
- const imgId = obje?.get("_PHOTO_RIN")?.toValue();
3393
+ const title = obje?.get("FILE.NOTE")?.toValue() ?? obje?.get("NOTE")?.toValue() ?? "";
3394
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3395
+ const photoRin = obje?.get("_PHOTO_RIN")?.toValue();
3396
+ const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
3397
+ const imgId = photoRin || (hasEmbeddedOrLocalFile && url ? `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}` : void 0);
3310
3398
  if (url && imgId) {
3311
3399
  const id = `${tree}-${this.id}-${imgId}`;
3312
3400
  list[id] = {
@@ -3429,10 +3517,10 @@ var Indi = class extends Common {
3429
3517
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
3430
3518
  const url = obje?.get("FILE")?.toValue();
3431
3519
  const title = obje?.get("TITL")?.toValue() ?? "";
3432
- const type = obje?.get("FORM")?.toValue() ?? "raw";
3520
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3433
3521
  if (url) {
3434
3522
  const urlMatch = url.match(/\/([^/]+)\?hash=/);
3435
- const imgId = urlMatch?.[1] || `img-${index}-${Date.now().toString(36)}`;
3523
+ const imgId = urlMatch?.[1] || (url.startsWith("data:") ? `data-${index}-${getFileExtension(url) || "bin"}` : `img-${index}-${Date.now().toString(36)}`);
3436
3524
  const id = `geni-${geniId}-${imgId}`;
3437
3525
  list[id] = {
3438
3526
  isPrimary,
@@ -3469,10 +3557,10 @@ var Indi = class extends Common {
3469
3557
  obje.standardizeMedia();
3470
3558
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
3471
3559
  const url = obje?.get("FILE")?.toValue();
3472
- const title = obje?.get("TITL")?.toValue() ?? "";
3473
- const type = obje?.get("FORM")?.toValue() ?? "raw";
3560
+ const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
3561
+ const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3474
3562
  if (url) {
3475
- const imgId = `media-${index}-${url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
3563
+ const imgId = `media-${index}-${url.startsWith("data:") ? getFileExtension(url) || "bin" : url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
3476
3564
  const id = `${treeId}-${this.id}-${imgId}`;
3477
3565
  list[id] = {
3478
3566
  isPrimary,
@@ -3512,7 +3600,20 @@ var Indi = class extends Common {
3512
3600
  cacheKey
3513
3601
  );
3514
3602
  if (cached !== void 0) {
3515
- return cached;
3603
+ if (cached?.file && !isRemoteOrEmbeddedMediaUrl(cached.file)) {
3604
+ const resolvedCached = await resolveLocalMediaUrl(cached.file);
3605
+ if (resolvedCached && isRemoteOrEmbeddedMediaUrl(resolvedCached)) {
3606
+ const refreshed = { ...cached, file: resolvedCached };
3607
+ profilePictureCache(
3608
+ this._gedcom,
3609
+ cacheKey,
3610
+ refreshed
3611
+ );
3612
+ return refreshed;
3613
+ }
3614
+ } else {
3615
+ return cached;
3616
+ }
3516
3617
  }
3517
3618
  const mediaList = await this.multimedia(namespace);
3518
3619
  if (!mediaList) {
@@ -3524,39 +3625,40 @@ var Indi = class extends Common {
3524
3625
  return void 0;
3525
3626
  }
3526
3627
  const mediaArray = Object.values(mediaList);
3527
- const primaryMedia = mediaArray.find(
3528
- (media) => media.isPrimary && isImageFormat(media.contentType || getFileExtension(media.url))
3529
- );
3530
- if (primaryMedia) {
3531
- const result = {
3532
- file: primaryMedia.url,
3533
- form: primaryMedia.contentType,
3534
- title: primaryMedia.title,
3535
- isPrimary: true
3628
+ const toDisplayableProfile = async (media, isPrimary) => {
3629
+ const resolvedFile = await resolveLocalMediaUrl(media.url);
3630
+ if (!resolvedFile || !isRemoteOrEmbeddedMediaUrl(resolvedFile)) {
3631
+ return void 0;
3632
+ }
3633
+ return {
3634
+ file: resolvedFile,
3635
+ form: media.contentType,
3636
+ title: media.title,
3637
+ isPrimary
3536
3638
  };
3537
- profilePictureCache(this._gedcom, cacheKey, result);
3538
- return result;
3539
- }
3540
- if (onlyPrimary) {
3541
- profilePictureCache(
3542
- this._gedcom,
3543
- cacheKey,
3544
- void 0
3545
- );
3546
- return void 0;
3547
- }
3548
- const secondaryMedia = mediaArray.find(
3549
- (media) => isImageFormat(media.contentType || getFileExtension(media.url))
3639
+ };
3640
+ const candidates = mediaArray.filter(
3641
+ (media) => isImageFormat(
3642
+ media.contentType || media.url || getFileExtension(media.url)
3643
+ )
3550
3644
  );
3551
- if (secondaryMedia) {
3552
- const result = {
3553
- file: secondaryMedia.url,
3554
- form: secondaryMedia.contentType,
3555
- title: secondaryMedia.title,
3556
- isPrimary: false
3557
- };
3558
- profilePictureCache(this._gedcom, cacheKey, result);
3559
- return result;
3645
+ const ordered = [
3646
+ ...candidates.filter((media) => media.isPrimary),
3647
+ ...candidates.filter((media) => !media.isPrimary)
3648
+ ];
3649
+ for (const media of ordered) {
3650
+ if (onlyPrimary && !media.isPrimary) {
3651
+ break;
3652
+ }
3653
+ const result = await toDisplayableProfile(media, !!media.isPrimary);
3654
+ if (result) {
3655
+ profilePictureCache(
3656
+ this._gedcom,
3657
+ cacheKey,
3658
+ result
3659
+ );
3660
+ return result;
3661
+ }
3560
3662
  }
3561
3663
  profilePictureCache(
3562
3664
  this._gedcom,
@@ -3842,9 +3944,108 @@ var Indi = class extends Common {
3842
3944
  addParent(parent, pedigree) {
3843
3945
  return parent.addChild(this, pedigree);
3844
3946
  }
3947
+ /**
3948
+ * Link an existing top-level OBJE record to this individual (`1 OBJE @On@`).
3949
+ */
3950
+ attachMultimedia(objeOrKey, options) {
3951
+ if (!this._gedcom || !this.id) {
3952
+ return void 0;
3953
+ }
3954
+ const obje = typeof objeOrKey === "string" ? this._gedcom.obje(objeOrKey) : objeOrKey;
3955
+ if (!obje?.id) {
3956
+ return void 0;
3957
+ }
3958
+ const already = this.get("OBJE")?.toList()?.values()?.some((item) => item?.toValue?.() === obje.id);
3959
+ if (!already) {
3960
+ const pointer = makePointer(
3961
+ this._gedcom,
3962
+ this,
3963
+ obje.id,
3964
+ "OBJE"
3965
+ );
3966
+ this.assign("OBJE", pointer);
3967
+ }
3968
+ if (options?.primary) {
3969
+ this.get("OBJE")?.toList()?.forEach((item) => {
3970
+ const target = item?.refType === "OBJE" ? this._gedcom?.obje(
3971
+ String(item.toValue())
3972
+ ) : item;
3973
+ if (!target) {
3974
+ return;
3975
+ }
3976
+ if (target.id === obje.id) {
3977
+ target.set("_PRIM", "Y");
3978
+ } else if (target.get("_PRIM")?.toValue() === "Y") {
3979
+ target.set("_PRIM", "N");
3980
+ }
3981
+ });
3982
+ obje.set("_PRIM", "Y");
3983
+ }
3984
+ return obje;
3985
+ }
3986
+ detachMultimedia(objeKey) {
3987
+ const list = this.get("OBJE")?.toList();
3988
+ if (!list) {
3989
+ return;
3990
+ }
3991
+ const toRemove = list.values().filter(
3992
+ (item) => item?.toValue?.() === objeKey || item?.id === objeKey
3993
+ );
3994
+ toRemove.forEach((item) => {
3995
+ if (item) {
3996
+ list.delete(item);
3997
+ }
3998
+ });
3999
+ if (list.length === 0) {
4000
+ this.remove("OBJE");
4001
+ }
4002
+ }
4003
+ /**
4004
+ * Create a multimedia record and attach it to this person.
4005
+ */
4006
+ attachMediaFromUrl(url, options) {
4007
+ if (!this._gedcom || !url) {
4008
+ return void 0;
4009
+ }
4010
+ const obje = this._gedcom.createMultimediaRecord({
4011
+ file: url,
4012
+ form: options?.form,
4013
+ title: options?.title,
4014
+ mediType: options?.mediType,
4015
+ primary: options?.primary,
4016
+ gedcomVersion: options?.gedcomVersion
4017
+ });
4018
+ return this.attachMultimedia(obje, { primary: options?.primary });
4019
+ }
4020
+ /**
4021
+ * Link a shared SNOTE record via `1 NOTE @Nn@` (GEDCOM 7).
4022
+ */
4023
+ attachSharedNote(noteOrKey) {
4024
+ if (!this._gedcom || !this.id) {
4025
+ return void 0;
4026
+ }
4027
+ const note = typeof noteOrKey === "string" ? this._gedcom.snote(noteOrKey) : noteOrKey;
4028
+ if (!note?.id) {
4029
+ return void 0;
4030
+ }
4031
+ const already = this.get("NOTE")?.toList()?.values()?.some((item) => item?.toValue?.() === note.id);
4032
+ if (!already) {
4033
+ const pointer = makePointer(
4034
+ this._gedcom,
4035
+ this,
4036
+ note.id,
4037
+ "SNOTE"
4038
+ );
4039
+ this.assign("NOTE", pointer);
4040
+ }
4041
+ return note;
4042
+ }
3845
4043
  addFact(input) {
3846
4044
  return addIndividualFact(this, input);
3847
4045
  }
4046
+ addNonEvent(input) {
4047
+ return addNonEvent(this, input);
4048
+ }
3848
4049
  getParentType(id) {
3849
4050
  let indi;
3850
4051
  if (typeof id === "string" || typeof id === "number") {
@@ -3885,7 +4086,7 @@ var Indi = class extends Common {
3885
4086
  if (!famChild) {
3886
4087
  const childIndi = this._gedcom?.indi(child.id);
3887
4088
  const childState = childIndi?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
3888
- childType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
4089
+ childType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
3889
4090
  } else {
3890
4091
  childType = famChild.get(usedRel)?.toValue() || "biological" /* BIOLOGICAL */;
3891
4092
  }
@@ -4446,7 +4647,7 @@ var Indi = class extends Common {
4446
4647
  return;
4447
4648
  }
4448
4649
  const childState = child?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
4449
- const childType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
4650
+ const childType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
4450
4651
  if (childType?.toLowerCase() === filter.PEDI.toLowerCase()) {
4451
4652
  children.append(child);
4452
4653
  }
@@ -4554,7 +4755,7 @@ var Indi = class extends Common {
4554
4755
  const child = this.id && family.CHIL?.toValueList()?.item(this.id);
4555
4756
  if (!child) {
4556
4757
  const childState = this?.get("FAMC")?.toList()?.filter((item) => item.value === family.id).index(0);
4557
- const parentType = childState?.get("PEDI")?.toValue() || "biological" /* BIOLOGICAL */;
4758
+ const parentType = (childState?.get("PEDI") ?? childState?.get("PEDT"))?.toValue() || "biological" /* BIOLOGICAL */;
4558
4759
  if (parentType?.toLowerCase() === filter.PEDI.toLowerCase()) {
4559
4760
  parents.merge(family.getParents());
4560
4761
  }
@@ -6753,7 +6954,11 @@ var STANDARD_QUALIFIERS = {
6753
6954
  BEF: "Bef.",
6754
6955
  BEFORE: "Bef.",
6755
6956
  AFT: "Aft.",
6756
- AFTER: "Aft."
6957
+ AFTER: "Aft.",
6958
+ CAL: "Abt.",
6959
+ // calculated ≈ about for display
6960
+ EST: "Abt."
6961
+ // estimated ≈ about for display
6757
6962
  };
6758
6963
  var EXPORT_QUALIFIERS = {
6759
6964
  "Abt.": "ABT",
@@ -6761,6 +6966,11 @@ var EXPORT_QUALIFIERS = {
6761
6966
  "Aft.": "AFT"
6762
6967
  };
6763
6968
  var GEDCOM_MONTHS = /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\b/g;
6969
+ var CALENDAR_PREFIX = /^(?<calendar>GREGORIAN|JULIAN|FRENCH_R|HEBREW)\b\s+/i;
6970
+ var isGedcomDateValueForm = (value) => (
6971
+ // Require space/end after the token so display forms like "Abt." are not opaque.
6972
+ /^(FROM|TO|BET|AFT|BEF|ABT|CAL|EST|INT)(\s|$)/i.test(value) || /\bAND\b/i.test(value)
6973
+ );
6764
6974
  var NOTE_MARKER = "####";
6765
6975
  var formatDateWithComponents = (date, hasDay, hasMonth, hasYear, noteValue, baseFormat = "dd MMM yyyy", locale) => {
6766
6976
  const hasNote = baseFormat.includes("NOTE");
@@ -6785,29 +6995,45 @@ var CommonDate = class extends Common {
6785
6995
  super(gedcom, id, main, parent);
6786
6996
  delete this.id;
6787
6997
  }
6998
+ get calendar() {
6999
+ return this._calendar;
7000
+ }
6788
7001
  set value(value) {
6789
7002
  if (value) {
6790
- const noteRegExp = /^(?<note>[a-zA-Z]+\.)/;
6791
- const noteMatch = value.match(noteRegExp)?.groups;
6792
7003
  let validValue = value;
7004
+ const calendarMatch = validValue.match(CALENDAR_PREFIX);
7005
+ if (calendarMatch?.groups?.calendar) {
7006
+ this._calendar = calendarMatch.groups.calendar.toUpperCase();
7007
+ validValue = validValue.slice(calendarMatch[0].length);
7008
+ } else {
7009
+ this._calendar = void 0;
7010
+ }
7011
+ const noteRegExp = /^(?<note>[a-zA-Z]+\.)/;
7012
+ const noteMatch = validValue.match(noteRegExp)?.groups;
6793
7013
  if (noteMatch?.note) {
6794
7014
  this.NOTE = this.NOTE || createCommon(this._gedcom, void 0, this.main);
6795
7015
  this.NOTE.value = noteMatch?.note;
6796
- validValue = value.replace(noteRegExp, "");
7016
+ validValue = validValue.replace(noteRegExp, "");
6797
7017
  } else {
6798
- const qualifierMatch = value.match(
6799
- /^(?<qualifier>ABT|ABOUT|BEF|BEFORE|AFT|AFTER)\b\s*/i
7018
+ const qualifierMatch = validValue.match(
7019
+ /^(?<qualifier>ABT|ABOUT|BEF|BEFORE|AFT|AFTER|CAL|EST)\b\s*/i
6800
7020
  );
6801
7021
  const qualifier = qualifierMatch?.groups?.qualifier?.toUpperCase();
6802
7022
  const note = qualifier ? STANDARD_QUALIFIERS[qualifier] : void 0;
6803
7023
  if (note) {
6804
7024
  this.NOTE = this.NOTE || createCommon(this._gedcom, void 0, this.main);
6805
7025
  this.NOTE.value = note;
6806
- validValue = value.slice(qualifierMatch[0].length);
7026
+ validValue = validValue.slice(qualifierMatch[0].length);
6807
7027
  }
6808
7028
  }
6809
7029
  const acceptedDate = this.isValidDateFormat(validValue);
6810
- if (acceptedDate) {
7030
+ if (isGedcomDateValueForm(validValue.trim())) {
7031
+ delete this.DAY;
7032
+ delete this.MONTH;
7033
+ delete this.YEAR;
7034
+ this._date = void 0;
7035
+ this._value = value;
7036
+ } else if (acceptedDate) {
6811
7037
  this.DAY = this.DAY || createCommon(this._gedcom, void 0, this.main);
6812
7038
  this.DAY.value = format(acceptedDate, "dd");
6813
7039
  this.MONTH = this.MONTH || createCommon(this._gedcom, void 0, this.main);
@@ -6910,6 +7136,15 @@ var CommonDate = class extends Common {
6910
7136
  );
6911
7137
  }
6912
7138
  exportValue() {
7139
+ if (typeof this._value === "string") {
7140
+ const withoutCalendar = this._value.replace(CALENDAR_PREFIX, "");
7141
+ if (isGedcomDateValueForm(withoutCalendar.trim())) {
7142
+ return this._value.replace(
7143
+ /^((?:GREGORIAN|JULIAN|FRENCH_R|HEBREW)\s+)?(FROM|TO|BET|AFT|BEF|ABT|CAL|EST|INT)\b/i,
7144
+ (_m, cal, q) => `${cal || ""}${q.toUpperCase()}`
7145
+ );
7146
+ }
7147
+ }
6913
7148
  const formatted = this.toValue("NOTE dd MMM yyyy", null);
6914
7149
  if (!formatted) {
6915
7150
  return formatted;
@@ -6917,10 +7152,14 @@ var CommonDate = class extends Common {
6917
7152
  const note = this.NOTE?.value?.trim();
6918
7153
  const qualifier = note ? EXPORT_QUALIFIERS[note] : void 0;
6919
7154
  const standardized = qualifier ? formatted.replace(note, qualifier) : formatted;
6920
- return standardized.replace(
7155
+ const withMonths = standardized.replace(
6921
7156
  GEDCOM_MONTHS,
6922
7157
  (month) => month.toUpperCase()
6923
7158
  );
7159
+ if (this._calendar && this._calendar !== "GREGORIAN") {
7160
+ return `${this._calendar} ${withMonths}`;
7161
+ }
7162
+ return withMonths;
6924
7163
  }
6925
7164
  toGedcomLines(tag, level = 0, options) {
6926
7165
  const internal = new RegExp(