@treeviz/gedcom-parser 2.1.16 → 2.1.18

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.
@@ -2416,6 +2416,30 @@ var mediaIdValue = (obje, path) => {
2416
2416
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
2417
2417
  };
2418
2418
  var resolveObjeMediaId = (obje) => mediaIdValue(obje, "RIN") || mediaIdValue(obje, "_CLON._OID") || mediaIdValue(obje, "_MSER._LKID") || mediaIdValue(obje, "_OID") || mediaIdValue(obje, "_LKID");
2419
+ var ANCESTRY_TREE_MEDIA_NAMESPACE = 1093;
2420
+ var ANCESTRY_NAMESPACE_IN_URL = /\/namespaces\/(\d+)\/media\//i;
2421
+ var parseAncestryNamespaceFromUrl = (url) => {
2422
+ if (!url) {
2423
+ return void 0;
2424
+ }
2425
+ const match = url.match(ANCESTRY_NAMESPACE_IN_URL);
2426
+ const namespace = match?.[1] ? Number(match[1]) : NaN;
2427
+ return Number.isFinite(namespace) ? namespace : void 0;
2428
+ };
2429
+ var isAncestryOriginObje = (obje) => {
2430
+ if (!obje) {
2431
+ return false;
2432
+ }
2433
+ const orig = mediaIdValue(obje, "_ORIG");
2434
+ if (orig?.toLowerCase() === "acom") {
2435
+ return true;
2436
+ }
2437
+ const file = mediaIdValue(obje, "FILE") ?? "";
2438
+ if (parseAncestryNamespaceFromUrl(file)) {
2439
+ return true;
2440
+ }
2441
+ return Boolean(resolveObjeMediaId(obje) && !file.trim());
2442
+ };
2419
2443
  var ANCESTRY_MEDIA_RETRIEVAL_PREFIX = "https://www.ancestry.com/api/media/retrieval/v2/image";
2420
2444
  var ANCESTRY_MEDIA_QUERY = "client=trees-mediaservice&imageQuality=hq";
2421
2445
  var ancestryMediaPath = (namespace, imgId) => `/namespaces/${namespace}/media/${imgId}?${ANCESTRY_MEDIA_QUERY}`;
@@ -2791,8 +2815,25 @@ var Common = class _Common {
2791
2815
  getHeadRecord() {
2792
2816
  return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
2793
2817
  }
2818
+ unwrapHeadRecord(node) {
2819
+ if (!node) {
2820
+ return void 0;
2821
+ }
2822
+ const common = node instanceof List ? node.index(0) : node;
2823
+ if (!common || typeof common.get !== "function") {
2824
+ return void 0;
2825
+ }
2826
+ const nested = common.get("HEAD");
2827
+ if (nested instanceof List) {
2828
+ return nested.index(0) ?? common;
2829
+ }
2830
+ if (nested instanceof _Common) {
2831
+ return nested;
2832
+ }
2833
+ return common;
2834
+ }
2794
2835
  getOriginalHeadRecord() {
2795
- return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
2836
+ return this.unwrapHeadRecord(get(this, ORIG_HEAD_TAG)) || this.unwrapHeadRecord(get(this.getGedcom(), ORIG_HEAD_TAG)) || this.unwrapHeadRecord(get(this.getHeadRecord(), ORIG_HEAD_TAG));
2796
2837
  }
2797
2838
  getSourceHeads() {
2798
2839
  const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
@@ -2800,9 +2841,23 @@ var Common = class _Common {
2800
2841
  return Boolean(head && heads.indexOf(head) === index);
2801
2842
  });
2802
2843
  }
2844
+ sourceHeadField(head, path) {
2845
+ const normalized = path.endsWith(".value") ? path.slice(0, -".value".length) : path;
2846
+ const node = head.get(normalized);
2847
+ if (!node) {
2848
+ return void 0;
2849
+ }
2850
+ if (node instanceof List) {
2851
+ return node.index(0)?.toValue();
2852
+ }
2853
+ if (typeof node.toValue === "function") {
2854
+ return node.toValue();
2855
+ }
2856
+ return void 0;
2857
+ }
2803
2858
  getFromSourceHeads(path) {
2804
2859
  for (const head of this.getSourceHeads()) {
2805
- const value = get(head, path);
2860
+ const value = this.sourceHeadField(head, path);
2806
2861
  if (value !== void 0 && value !== null && value !== "") {
2807
2862
  return value;
2808
2863
  }
@@ -2811,8 +2866,8 @@ var Common = class _Common {
2811
2866
  }
2812
2867
  sourStartsWith(prefix) {
2813
2868
  return this.getSourceHeads().some((head) => {
2814
- const sour = get(head, "SOUR.value");
2815
- return !!sour?.toLowerCase()?.startsWith(prefix);
2869
+ const sour = this.sourceHeadField(head, "SOUR");
2870
+ return typeof sour === "string" && sour.toLowerCase().startsWith(prefix);
2816
2871
  });
2817
2872
  }
2818
2873
  isGenoPro() {
@@ -2830,6 +2885,51 @@ var Common = class _Common {
2830
2885
  isMyHeritage() {
2831
2886
  return this.sourStartsWith("myheritage");
2832
2887
  }
2888
+ /**
2889
+ * GEDCOM root for Ancestry media helpers.
2890
+ * `this._gedcom` when called on a child record; `this` when called on the file root.
2891
+ *
2892
+ * Cannot use `instanceof GedCom`: `GedComType` is a type alias (erased at runtime),
2893
+ * and importing the `GedCom` class here would cycle (`GedCom extends Common`).
2894
+ * The root is the only Common with an `objes()` method.
2895
+ */
2896
+ hostGedcom() {
2897
+ const linked = this.getGedcom();
2898
+ if (linked) {
2899
+ return linked;
2900
+ }
2901
+ if ("objes" in this && typeof this.objes === "function") {
2902
+ return this;
2903
+ }
2904
+ return void 0;
2905
+ }
2906
+ /** True when the file still has Ancestry tree media (`_ORIG acom`, `_OID`, retrieval URL). */
2907
+ hasAncestryOriginMedia() {
2908
+ const objes = this.hostGedcom()?.objes?.();
2909
+ return objes?.values().some((obje) => isAncestryOriginObje(obje)) ?? false;
2910
+ }
2911
+ /**
2912
+ * Namespace for Ancestry tree photos. Prefer a `namespaces/{id}` FILE URL
2913
+ * (TreeViz GEDCOM 7 / GEDZIP export), otherwise 1093 for member-tree media.
2914
+ */
2915
+ getAncestryMediaNamespace() {
2916
+ const objes = this.hostGedcom()?.objes?.();
2917
+ if (objes) {
2918
+ for (const obje of objes.values()) {
2919
+ const file = obje?.get("FILE")?.toValue();
2920
+ const fromUrl = parseAncestryNamespaceFromUrl(
2921
+ typeof file === "string" ? file : void 0
2922
+ );
2923
+ if (fromUrl) {
2924
+ return fromUrl;
2925
+ }
2926
+ }
2927
+ }
2928
+ if (this.isAncestry() || this.hasAncestryOriginMedia()) {
2929
+ return ANCESTRY_TREE_MEDIA_NAMESPACE;
2930
+ }
2931
+ return void 0;
2932
+ }
2833
2933
  /**
2834
2934
  * Get the source type as a string (for prefixing tree IDs and names)
2835
2935
  * Returns the detected source type or undefined if unknown
@@ -2846,13 +2946,13 @@ var Common = class _Common {
2846
2946
  }
2847
2947
  isFamilySearch() {
2848
2948
  return this.getSourceHeads().some((head) => {
2849
- const sourName = get(head, "SOUR.NAME.value");
2949
+ const sourName = this.sourceHeadField(head, "SOUR.NAME");
2850
2950
  return sourName === "FamilySearch API";
2851
2951
  });
2852
2952
  }
2853
2953
  isGNO2GED() {
2854
2954
  return this.getSourceHeads().some((head) => {
2855
- const sour = get(head, "SOUR.value");
2955
+ const sour = this.sourceHeadField(head, "SOUR");
2856
2956
  return sour === "GNO2GED";
2857
2957
  });
2858
2958
  }
@@ -2897,8 +2997,7 @@ var Common = class _Common {
2897
2997
  return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
2898
2998
  }
2899
2999
  getMyHeritageTreeName() {
2900
- const path = "HEAD.FILE.value";
2901
- const treeDetails = get(this, path) || get(this.getGedcom(), path);
3000
+ const treeDetails = this.getFromSourceHeads("FILE.value") || (get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value"));
2902
3001
  return treeDetails?.match(
2903
3002
  /Exported by MyHeritage.com from (?<tree>.+) in.+$/
2904
3003
  )?.groups?.tree;
@@ -2915,7 +3014,7 @@ var Common = class _Common {
2915
3014
  if (treeName) {
2916
3015
  return treeName;
2917
3016
  }
2918
- const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
3017
+ const fileName = this.getFromSourceHeads("FILE.value") || get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
2919
3018
  return fileName || "FamilySearch Import";
2920
3019
  }
2921
3020
  getAhnenblattTreeId() {
@@ -4995,6 +5094,28 @@ var resolveLocalMediaUrl = async (path, context) => {
4995
5094
  };
4996
5095
  var implemented = (type, ...args) => {
4997
5096
  };
5097
+
5098
+ // src/utils/multimedia.ts
5099
+ var mergeMediaListsByObjeKey = (...lists) => {
5100
+ const merged = {};
5101
+ const seen = /* @__PURE__ */ new Set();
5102
+ for (const list of lists) {
5103
+ if (!list) {
5104
+ continue;
5105
+ }
5106
+ for (const [id, item] of Object.entries(list)) {
5107
+ const objeKey = item.key ? String(item.key) : "";
5108
+ if (objeKey && seen.has(objeKey)) {
5109
+ continue;
5110
+ }
5111
+ merged[id] = item;
5112
+ if (objeKey) {
5113
+ seen.add(objeKey);
5114
+ }
5115
+ }
5116
+ }
5117
+ return merged;
5118
+ };
4998
5119
  var uniqueItemsCache = /* @__PURE__ */ new WeakMap();
4999
5120
  var setNestedGroup = (obj, key, value, uniqueCounting = true) => {
5000
5121
  const parts = Array.isArray(key) ? key : key.split(/,\s*/);
@@ -6254,7 +6375,10 @@ var Indi = class extends Common {
6254
6375
  return result;
6255
6376
  }
6256
6377
  ancestryLink() {
6257
- const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
6378
+ let www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
6379
+ if (!www || /treeviz/i.test(String(www))) {
6380
+ www = "www.ancestry.com";
6381
+ }
6258
6382
  const tree = this.getAncestryTreeId();
6259
6383
  if (this.id) {
6260
6384
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -6266,14 +6390,15 @@ var Indi = class extends Common {
6266
6390
  async ancestryMedia(namespace) {
6267
6391
  const list = {};
6268
6392
  const objeList = this.get("OBJE")?.toList().copy();
6269
- const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
6393
+ this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
6270
6394
  const tree = this.getAncestryTreeId();
6395
+ const usedNamespace = namespace || this.getAncestryMediaNamespace() || ANCESTRY_TREE_MEDIA_NAMESPACE;
6271
6396
  if (objeList) {
6272
6397
  await Promise.all(
6273
6398
  objeList.map(async (objeRef) => {
6274
6399
  const key = objeXrefKey(objeRef, 0);
6275
6400
  const obje = objeRef?.standardizeMedia(
6276
- namespace,
6401
+ usedNamespace,
6277
6402
  true,
6278
6403
  ancestryMediaFileUrl
6279
6404
  );
@@ -6285,27 +6410,9 @@ var Indi = class extends Common {
6285
6410
  const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
6286
6411
  let imgId = media;
6287
6412
  const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
6288
- if (!hasEmbeddedOrLocalFile && (!www || !tree || !this.id)) {
6289
- return;
6290
- }
6291
6413
  if (!this.id) {
6292
6414
  return;
6293
6415
  }
6294
- if (!namespace && !url) {
6295
- try {
6296
- if (media) {
6297
- const mediaDetailsResponse = await fetch(
6298
- `https://www.ancestry.com/api/media/viewer/v2/trees/${tree}/media?id=${media}`
6299
- );
6300
- const mediaDetails = await mediaDetailsResponse.json();
6301
- if (mediaDetails.url) {
6302
- url = `${mediaDetails.url}&imageQuality=hq`;
6303
- }
6304
- }
6305
- } catch (_e) {
6306
- }
6307
- url = url || `https://${www}/mediaui-viewer/tree/${tree}/media/${media}`;
6308
- }
6309
6416
  if (!imgId && hasEmbeddedOrLocalFile && url) {
6310
6417
  imgId = typeof media === "string" && media || `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}`;
6311
6418
  }
@@ -6331,9 +6438,12 @@ var Indi = class extends Common {
6331
6438
  return list;
6332
6439
  }
6333
6440
  myheritageLink(poolId = 0) {
6334
- const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
6441
+ let www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
6442
+ if (!www || /treeviz/i.test(www)) {
6443
+ www = "myheritage.com";
6444
+ }
6335
6445
  const site = this.getMyHeritageTreeId();
6336
- const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
6446
+ const file = (this.getFromSourceHeads("FILE.value") || this._gedcom?.HEAD?.get("FILE")?.toValue())?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
6337
6447
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
6338
6448
  if (normalizedFile && this.id) {
6339
6449
  const id = Number(this.id.replace(/@|I/g, "")) + poolId;
@@ -6554,16 +6664,26 @@ var Indi = class extends Common {
6554
6664
  return list;
6555
6665
  }
6556
6666
  async multimedia(namespace) {
6557
- if (this?.isAncestry()) {
6558
- return await this.ancestryMedia(namespace);
6667
+ const lists = [];
6668
+ const ownObjes = this.get("OBJE")?.toList()?.values() ?? [];
6669
+ const ownAncestryMedia = ownObjes.some(
6670
+ (obje) => !!obje && (isAncestryOriginObje(obje) || isAncestryOriginObje(obje.ref))
6671
+ );
6672
+ if (this.isAncestry() || ownAncestryMedia) {
6673
+ lists.push(
6674
+ await this.ancestryMedia(
6675
+ namespace || this.getAncestryMediaNamespace()
6676
+ )
6677
+ );
6559
6678
  }
6560
6679
  if (this?.isMyHeritage()) {
6561
- return this.myheritageMedia();
6680
+ lists.push(this.myheritageMedia());
6562
6681
  }
6563
6682
  if (this?.isGeni()) {
6564
- return this.geniMedia();
6683
+ lists.push(this.geniMedia());
6565
6684
  }
6566
- return this.universalMedia();
6685
+ lists.push(this.universalMedia());
6686
+ return mergeMediaListsByObjeKey(...lists);
6567
6687
  }
6568
6688
  async getProfilePicture(namespace, onlyPrimary = true) {
6569
6689
  if (!this.id) {