@treeviz/gedcom-parser 2.1.17 → 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.
@@ -165,6 +165,30 @@ var mediaIdValue = (obje, path) => {
165
165
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
166
166
  };
167
167
  var resolveObjeMediaId = (obje) => mediaIdValue(obje, "RIN") || mediaIdValue(obje, "_CLON._OID") || mediaIdValue(obje, "_MSER._LKID") || mediaIdValue(obje, "_OID") || mediaIdValue(obje, "_LKID");
168
+ var ANCESTRY_TREE_MEDIA_NAMESPACE = 1093;
169
+ var ANCESTRY_NAMESPACE_IN_URL = /\/namespaces\/(\d+)\/media\//i;
170
+ var parseAncestryNamespaceFromUrl = (url) => {
171
+ if (!url) {
172
+ return void 0;
173
+ }
174
+ const match = url.match(ANCESTRY_NAMESPACE_IN_URL);
175
+ const namespace = match?.[1] ? Number(match[1]) : NaN;
176
+ return Number.isFinite(namespace) ? namespace : void 0;
177
+ };
178
+ var isAncestryOriginObje = (obje) => {
179
+ if (!obje) {
180
+ return false;
181
+ }
182
+ const orig = mediaIdValue(obje, "_ORIG");
183
+ if (orig?.toLowerCase() === "acom") {
184
+ return true;
185
+ }
186
+ const file = mediaIdValue(obje, "FILE") ?? "";
187
+ if (parseAncestryNamespaceFromUrl(file)) {
188
+ return true;
189
+ }
190
+ return Boolean(resolveObjeMediaId(obje) && !file.trim());
191
+ };
168
192
  var ANCESTRY_MEDIA_RETRIEVAL_PREFIX = "https://www.ancestry.com/api/media/retrieval/v2/image";
169
193
  var ANCESTRY_MEDIA_QUERY = "client=trees-mediaservice&imageQuality=hq";
170
194
  var ancestryMediaPath = (namespace, imgId) => `/namespaces/${namespace}/media/${imgId}?${ANCESTRY_MEDIA_QUERY}`;
@@ -540,8 +564,25 @@ var Common = class _Common {
540
564
  getHeadRecord() {
541
565
  return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
542
566
  }
567
+ unwrapHeadRecord(node) {
568
+ if (!node) {
569
+ return void 0;
570
+ }
571
+ const common = node instanceof List ? node.index(0) : node;
572
+ if (!common || typeof common.get !== "function") {
573
+ return void 0;
574
+ }
575
+ const nested = common.get("HEAD");
576
+ if (nested instanceof List) {
577
+ return nested.index(0) ?? common;
578
+ }
579
+ if (nested instanceof _Common) {
580
+ return nested;
581
+ }
582
+ return common;
583
+ }
543
584
  getOriginalHeadRecord() {
544
- return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
585
+ return this.unwrapHeadRecord(get(this, ORIG_HEAD_TAG)) || this.unwrapHeadRecord(get(this.getGedcom(), ORIG_HEAD_TAG)) || this.unwrapHeadRecord(get(this.getHeadRecord(), ORIG_HEAD_TAG));
545
586
  }
546
587
  getSourceHeads() {
547
588
  const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
@@ -549,9 +590,23 @@ var Common = class _Common {
549
590
  return Boolean(head && heads.indexOf(head) === index);
550
591
  });
551
592
  }
593
+ sourceHeadField(head, path) {
594
+ const normalized = path.endsWith(".value") ? path.slice(0, -".value".length) : path;
595
+ const node = head.get(normalized);
596
+ if (!node) {
597
+ return void 0;
598
+ }
599
+ if (node instanceof List) {
600
+ return node.index(0)?.toValue();
601
+ }
602
+ if (typeof node.toValue === "function") {
603
+ return node.toValue();
604
+ }
605
+ return void 0;
606
+ }
552
607
  getFromSourceHeads(path) {
553
608
  for (const head of this.getSourceHeads()) {
554
- const value = get(head, path);
609
+ const value = this.sourceHeadField(head, path);
555
610
  if (value !== void 0 && value !== null && value !== "") {
556
611
  return value;
557
612
  }
@@ -560,8 +615,8 @@ var Common = class _Common {
560
615
  }
561
616
  sourStartsWith(prefix) {
562
617
  return this.getSourceHeads().some((head) => {
563
- const sour = get(head, "SOUR.value");
564
- return !!sour?.toLowerCase()?.startsWith(prefix);
618
+ const sour = this.sourceHeadField(head, "SOUR");
619
+ return typeof sour === "string" && sour.toLowerCase().startsWith(prefix);
565
620
  });
566
621
  }
567
622
  isGenoPro() {
@@ -579,6 +634,51 @@ var Common = class _Common {
579
634
  isMyHeritage() {
580
635
  return this.sourStartsWith("myheritage");
581
636
  }
637
+ /**
638
+ * GEDCOM root for Ancestry media helpers.
639
+ * `this._gedcom` when called on a child record; `this` when called on the file root.
640
+ *
641
+ * Cannot use `instanceof GedCom`: `GedComType` is a type alias (erased at runtime),
642
+ * and importing the `GedCom` class here would cycle (`GedCom extends Common`).
643
+ * The root is the only Common with an `objes()` method.
644
+ */
645
+ hostGedcom() {
646
+ const linked = this.getGedcom();
647
+ if (linked) {
648
+ return linked;
649
+ }
650
+ if ("objes" in this && typeof this.objes === "function") {
651
+ return this;
652
+ }
653
+ return void 0;
654
+ }
655
+ /** True when the file still has Ancestry tree media (`_ORIG acom`, `_OID`, retrieval URL). */
656
+ hasAncestryOriginMedia() {
657
+ const objes = this.hostGedcom()?.objes?.();
658
+ return objes?.values().some((obje) => isAncestryOriginObje(obje)) ?? false;
659
+ }
660
+ /**
661
+ * Namespace for Ancestry tree photos. Prefer a `namespaces/{id}` FILE URL
662
+ * (TreeViz GEDCOM 7 / GEDZIP export), otherwise 1093 for member-tree media.
663
+ */
664
+ getAncestryMediaNamespace() {
665
+ const objes = this.hostGedcom()?.objes?.();
666
+ if (objes) {
667
+ for (const obje of objes.values()) {
668
+ const file = obje?.get("FILE")?.toValue();
669
+ const fromUrl = parseAncestryNamespaceFromUrl(
670
+ typeof file === "string" ? file : void 0
671
+ );
672
+ if (fromUrl) {
673
+ return fromUrl;
674
+ }
675
+ }
676
+ }
677
+ if (this.isAncestry() || this.hasAncestryOriginMedia()) {
678
+ return ANCESTRY_TREE_MEDIA_NAMESPACE;
679
+ }
680
+ return void 0;
681
+ }
582
682
  /**
583
683
  * Get the source type as a string (for prefixing tree IDs and names)
584
684
  * Returns the detected source type or undefined if unknown
@@ -595,13 +695,13 @@ var Common = class _Common {
595
695
  }
596
696
  isFamilySearch() {
597
697
  return this.getSourceHeads().some((head) => {
598
- const sourName = get(head, "SOUR.NAME.value");
698
+ const sourName = this.sourceHeadField(head, "SOUR.NAME");
599
699
  return sourName === "FamilySearch API";
600
700
  });
601
701
  }
602
702
  isGNO2GED() {
603
703
  return this.getSourceHeads().some((head) => {
604
- const sour = get(head, "SOUR.value");
704
+ const sour = this.sourceHeadField(head, "SOUR");
605
705
  return sour === "GNO2GED";
606
706
  });
607
707
  }
@@ -646,8 +746,7 @@ var Common = class _Common {
646
746
  return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
647
747
  }
648
748
  getMyHeritageTreeName() {
649
- const path = "HEAD.FILE.value";
650
- const treeDetails = get(this, path) || get(this.getGedcom(), path);
749
+ const treeDetails = this.getFromSourceHeads("FILE.value") || (get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value"));
651
750
  return treeDetails?.match(
652
751
  /Exported by MyHeritage.com from (?<tree>.+) in.+$/
653
752
  )?.groups?.tree;
@@ -664,7 +763,7 @@ var Common = class _Common {
664
763
  if (treeName) {
665
764
  return treeName;
666
765
  }
667
- const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
766
+ const fileName = this.getFromSourceHeads("FILE.value") || get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
668
767
  return fileName || "FamilySearch Import";
669
768
  }
670
769
  getAhnenblattTreeId() {
@@ -2745,6 +2844,28 @@ var resolveLocalMediaUrl = async (path, context) => {
2745
2844
  var implemented = (type, ...args) => {
2746
2845
  };
2747
2846
 
2847
+ // src/utils/multimedia.ts
2848
+ var mergeMediaListsByObjeKey = (...lists) => {
2849
+ const merged = {};
2850
+ const seen = /* @__PURE__ */ new Set();
2851
+ for (const list of lists) {
2852
+ if (!list) {
2853
+ continue;
2854
+ }
2855
+ for (const [id, item] of Object.entries(list)) {
2856
+ const objeKey = item.key ? String(item.key) : "";
2857
+ if (objeKey && seen.has(objeKey)) {
2858
+ continue;
2859
+ }
2860
+ merged[id] = item;
2861
+ if (objeKey) {
2862
+ seen.add(objeKey);
2863
+ }
2864
+ }
2865
+ }
2866
+ return merged;
2867
+ };
2868
+
2748
2869
  // src/factories/place-parser-provider.ts
2749
2870
  var placeParserProvider;
2750
2871
  var setPlaceParserProvider = (parser) => {
@@ -4031,7 +4152,10 @@ var Indi = class extends Common {
4031
4152
  return result;
4032
4153
  }
4033
4154
  ancestryLink() {
4034
- const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
4155
+ let www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
4156
+ if (!www || /treeviz/i.test(String(www))) {
4157
+ www = "www.ancestry.com";
4158
+ }
4035
4159
  const tree = this.getAncestryTreeId();
4036
4160
  if (this.id) {
4037
4161
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -4043,14 +4167,15 @@ var Indi = class extends Common {
4043
4167
  async ancestryMedia(namespace) {
4044
4168
  const list = {};
4045
4169
  const objeList = this.get("OBJE")?.toList().copy();
4046
- const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
4170
+ this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
4047
4171
  const tree = this.getAncestryTreeId();
4172
+ const usedNamespace = namespace || this.getAncestryMediaNamespace() || ANCESTRY_TREE_MEDIA_NAMESPACE;
4048
4173
  if (objeList) {
4049
4174
  await Promise.all(
4050
4175
  objeList.map(async (objeRef) => {
4051
4176
  const key = objeXrefKey(objeRef, 0);
4052
4177
  const obje = objeRef?.standardizeMedia(
4053
- namespace,
4178
+ usedNamespace,
4054
4179
  true,
4055
4180
  ancestryMediaFileUrl
4056
4181
  );
@@ -4062,27 +4187,9 @@ var Indi = class extends Common {
4062
4187
  const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
4063
4188
  let imgId = media;
4064
4189
  const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
4065
- if (!hasEmbeddedOrLocalFile && (!www || !tree || !this.id)) {
4066
- return;
4067
- }
4068
4190
  if (!this.id) {
4069
4191
  return;
4070
4192
  }
4071
- if (!namespace && !url) {
4072
- try {
4073
- if (media) {
4074
- const mediaDetailsResponse = await fetch(
4075
- `https://www.ancestry.com/api/media/viewer/v2/trees/${tree}/media?id=${media}`
4076
- );
4077
- const mediaDetails = await mediaDetailsResponse.json();
4078
- if (mediaDetails.url) {
4079
- url = `${mediaDetails.url}&imageQuality=hq`;
4080
- }
4081
- }
4082
- } catch (_e) {
4083
- }
4084
- url = url || `https://${www}/mediaui-viewer/tree/${tree}/media/${media}`;
4085
- }
4086
4193
  if (!imgId && hasEmbeddedOrLocalFile && url) {
4087
4194
  imgId = typeof media === "string" && media || `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}`;
4088
4195
  }
@@ -4108,9 +4215,12 @@ var Indi = class extends Common {
4108
4215
  return list;
4109
4216
  }
4110
4217
  myheritageLink(poolId = 0) {
4111
- const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
4218
+ let www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
4219
+ if (!www || /treeviz/i.test(www)) {
4220
+ www = "myheritage.com";
4221
+ }
4112
4222
  const site = this.getMyHeritageTreeId();
4113
- const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
4223
+ const file = (this.getFromSourceHeads("FILE.value") || this._gedcom?.HEAD?.get("FILE")?.toValue())?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
4114
4224
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
4115
4225
  if (normalizedFile && this.id) {
4116
4226
  const id = Number(this.id.replace(/@|I/g, "")) + poolId;
@@ -4331,16 +4441,26 @@ var Indi = class extends Common {
4331
4441
  return list;
4332
4442
  }
4333
4443
  async multimedia(namespace) {
4334
- if (this?.isAncestry()) {
4335
- return await this.ancestryMedia(namespace);
4444
+ const lists = [];
4445
+ const ownObjes = this.get("OBJE")?.toList()?.values() ?? [];
4446
+ const ownAncestryMedia = ownObjes.some(
4447
+ (obje) => !!obje && (isAncestryOriginObje(obje) || isAncestryOriginObje(obje.ref))
4448
+ );
4449
+ if (this.isAncestry() || ownAncestryMedia) {
4450
+ lists.push(
4451
+ await this.ancestryMedia(
4452
+ namespace || this.getAncestryMediaNamespace()
4453
+ )
4454
+ );
4336
4455
  }
4337
4456
  if (this?.isMyHeritage()) {
4338
- return this.myheritageMedia();
4457
+ lists.push(this.myheritageMedia());
4339
4458
  }
4340
4459
  if (this?.isGeni()) {
4341
- return this.geniMedia();
4460
+ lists.push(this.geniMedia());
4342
4461
  }
4343
- return this.universalMedia();
4462
+ lists.push(this.universalMedia());
4463
+ return mergeMediaListsByObjeKey(...lists);
4344
4464
  }
4345
4465
  async getProfilePicture(namespace, onlyPrimary = true) {
4346
4466
  if (!this.id) {