@treeviz/gedcom-parser 2.1.28 → 2.1.30

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.
@@ -221,6 +221,100 @@ var objeXrefKey = (obje, fallback = 0) => {
221
221
  return `@O${fallback}@`;
222
222
  };
223
223
  var objeNoteValue = (obje) => mediaIdValue(obje, "FILE.NOTE") || mediaIdValue(obje, "NOTE") || "";
224
+ var FS_WEB_RE = /familysearch\.org/i;
225
+ var FS_ARK_ID_RE = /^(?:ark:\/[^/]+\/)?([123]:[12]:[A-Za-z0-9-]+)$/i;
226
+ var listItems = (list) => {
227
+ if (!list) {
228
+ return [];
229
+ }
230
+ if (typeof list.values === "function") {
231
+ return list.values();
232
+ }
233
+ if (typeof list.forEach === "function") {
234
+ const items = [];
235
+ list.forEach((item) => items.push(item));
236
+ return items;
237
+ }
238
+ return [];
239
+ };
240
+ var toList = (node) => {
241
+ if (!node) {
242
+ return void 0;
243
+ }
244
+ const maybe = node;
245
+ if (typeof maybe.toList === "function") {
246
+ return maybe.toList();
247
+ }
248
+ if (typeof maybe.values === "function" || typeof maybe.forEach === "function") {
249
+ return maybe;
250
+ }
251
+ return void 0;
252
+ };
253
+ var normalizeFsWebUrl = (url) => {
254
+ const trimmed = url.trim();
255
+ if (!trimmed || !FS_WEB_RE.test(trimmed)) {
256
+ return void 0;
257
+ }
258
+ if (/\/dist\.jpg|\/service\/records\/storage\/|\/platform\//i.test(trimmed)) {
259
+ return void 0;
260
+ }
261
+ return trimmed;
262
+ };
263
+ var familySearchMediaOriginUrl = (obje, indi) => {
264
+ if (!obje) {
265
+ return void 0;
266
+ }
267
+ const resolved = obje.ref && typeof obje.toValue?.() === "string" ? obje.ref : obje;
268
+ const directWww = mediaIdValue(resolved, "WWW") || mediaIdValue(obje, "WWW");
269
+ const fromWww = directWww ? normalizeFsWebUrl(directWww) : void 0;
270
+ if (fromWww) {
271
+ return fromWww;
272
+ }
273
+ const objeKey = objeXrefKey(obje);
274
+ const sourItems = listItems(toList(indi?.get?.("SOUR")));
275
+ for (const sour of sourItems) {
276
+ if (!sour) continue;
277
+ const sourWww = mediaIdValue(sour, "WWW");
278
+ const normalized = sourWww ? normalizeFsWebUrl(sourWww) : void 0;
279
+ if (!normalized) continue;
280
+ const sourIsFs = mediaIdValue(sour, "_IS_FS") === "Y" || FS_WEB_RE.test(normalized);
281
+ if (!sourIsFs) continue;
282
+ let linksThisObje = false;
283
+ for (const ref of listItems(toList(sour.get?.("OBJE")))) {
284
+ if (ref && objeXrefKey(ref) === objeKey) {
285
+ linksThisObje = true;
286
+ break;
287
+ }
288
+ }
289
+ const sourFsId = mediaIdValue(sour, "_FS_ID");
290
+ const objeFsId = mediaIdValue(resolved, "_FS_ID") || mediaIdValue(obje, "_FS_ID");
291
+ if (linksThisObje || sourFsId && objeFsId && sourFsId === objeFsId) {
292
+ return normalized;
293
+ }
294
+ }
295
+ const isFs = mediaIdValue(resolved, "_IS_FS") === "Y" || mediaIdValue(obje, "_IS_FS") === "Y";
296
+ const fsId = mediaIdValue(resolved, "_FS_ID") || mediaIdValue(obje, "_FS_ID");
297
+ if (!isFs || !fsId) {
298
+ return void 0;
299
+ }
300
+ const arkMatch = fsId.match(FS_ARK_ID_RE);
301
+ if (arkMatch?.[1]) {
302
+ return `https://www.familysearch.org/ark:/61903/${arkMatch[1]}`;
303
+ }
304
+ if (/^\d+$/.test(fsId)) {
305
+ return `https://www.familysearch.org/photos/artifacts/${fsId}`;
306
+ }
307
+ for (const sour of sourItems) {
308
+ if (!sour) continue;
309
+ if (mediaIdValue(sour, "_FS_ID") !== fsId) continue;
310
+ const sourWww = mediaIdValue(sour, "WWW");
311
+ const normalized = sourWww ? normalizeFsWebUrl(sourWww) : void 0;
312
+ if (normalized) {
313
+ return normalized;
314
+ }
315
+ }
316
+ return void 0;
317
+ };
224
318
 
225
319
  // src/classes/common.ts
226
320
  var Common = class _Common {
@@ -2864,6 +2958,14 @@ var mergeMediaListsByObjeKey = (...lists) => {
2864
2958
  for (const [id, item] of Object.entries(list)) {
2865
2959
  const objeKey = item.key ? String(item.key) : "";
2866
2960
  if (objeKey && seen.has(objeKey)) {
2961
+ if (item.originUrl) {
2962
+ const existing = Object.values(merged).find(
2963
+ (entry) => entry.key === objeKey
2964
+ );
2965
+ if (existing && !existing.originUrl) {
2966
+ existing.originUrl = item.originUrl;
2967
+ }
2968
+ }
2867
2969
  continue;
2868
2970
  }
2869
2971
  merged[id] = item;
@@ -4206,6 +4308,10 @@ var Indi = class extends Common {
4206
4308
  }
4207
4309
  if (url && imgId) {
4208
4310
  const id = `${tree || "gedzip"}-${this.id}-${imgId}`;
4311
+ const originUrl = familySearchMediaOriginUrl(
4312
+ objeRef ?? obje,
4313
+ this
4314
+ );
4209
4315
  list[id] = {
4210
4316
  key,
4211
4317
  isPrimary,
@@ -4217,7 +4323,8 @@ var Indi = class extends Common {
4217
4323
  note,
4218
4324
  url,
4219
4325
  contentType: type,
4220
- downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
4326
+ downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`,
4327
+ ...originUrl ? { originUrl } : {}
4221
4328
  };
4222
4329
  }
4223
4330
  })
@@ -4437,18 +4544,39 @@ var Indi = class extends Common {
4437
4544
  if (!this.id) {
4438
4545
  return list;
4439
4546
  }
4440
- const objeList = this.get("OBJE")?.toList().copy();
4547
+ let objeList = this.get("OBJE")?.toList().copy();
4548
+ const sourList = this.get("SOUR")?.toList();
4549
+ sourList?.forEach((sour) => {
4550
+ const sourObje = sour?.get("OBJE")?.toList();
4551
+ if (!sourObje?.length) {
4552
+ return;
4553
+ }
4554
+ if (!objeList) {
4555
+ objeList = sourObje.copy();
4556
+ } else {
4557
+ objeList.merge(sourObje);
4558
+ }
4559
+ });
4441
4560
  if (!objeList || objeList.length === 0) {
4442
4561
  return list;
4443
4562
  }
4444
4563
  const rfn = this.get("RFN")?.toValue();
4445
4564
  const treeId = this.getUniversalTreeId() || rfn || "universal";
4565
+ const seenFsIds = /* @__PURE__ */ new Set();
4446
4566
  objeList.forEach((obje, index) => {
4447
4567
  if (!obje) {
4448
4568
  return;
4449
4569
  }
4450
4570
  const key = objeXrefKey(obje, index);
4451
4571
  obje.standardizeMedia();
4572
+ const fsId = obje?.get("_FS_ID")?.toValue?.();
4573
+ if (typeof fsId === "string" && fsId.trim()) {
4574
+ const normalized = fsId.trim();
4575
+ if (seenFsIds.has(normalized)) {
4576
+ return;
4577
+ }
4578
+ seenFsIds.add(normalized);
4579
+ }
4452
4580
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
4453
4581
  const url = obje?.get("FILE")?.toValue();
4454
4582
  const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
@@ -4456,6 +4584,7 @@ var Indi = class extends Common {
4456
4584
  if (url) {
4457
4585
  const imgId = `media-${index}-${url.startsWith("data:") ? getFileExtension(url) || "bin" : url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
4458
4586
  const id = `${treeId}-${this.id}-${imgId}`;
4587
+ const originUrl = familySearchMediaOriginUrl(obje, this);
4459
4588
  list[id] = {
4460
4589
  isPrimary,
4461
4590
  key,
@@ -4467,7 +4596,8 @@ var Indi = class extends Common {
4467
4596
  note: objeNoteValue(obje),
4468
4597
  url,
4469
4598
  contentType: type,
4470
- downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
4599
+ downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`,
4600
+ ...originUrl ? { originUrl } : {}
4471
4601
  };
4472
4602
  }
4473
4603
  });
@@ -4493,7 +4623,23 @@ var Indi = class extends Common {
4493
4623
  lists.push(this.geniMedia());
4494
4624
  }
4495
4625
  lists.push(this.universalMedia());
4496
- return mergeMediaListsByObjeKey(...lists);
4626
+ const merged = mergeMediaListsByObjeKey(...lists);
4627
+ const seenFsIds = /* @__PURE__ */ new Set();
4628
+ const deduped = {};
4629
+ for (const [id, media] of Object.entries(merged ?? {})) {
4630
+ const xref = media.key ? String(media.key) : "";
4631
+ const top = xref && this._gedcom ? this._gedcom.obje(xref) : void 0;
4632
+ const fsId = top?.get("_FS_ID")?.toValue?.();
4633
+ if (typeof fsId === "string" && fsId.trim()) {
4634
+ const normalized = fsId.trim();
4635
+ if (seenFsIds.has(normalized)) {
4636
+ continue;
4637
+ }
4638
+ seenFsIds.add(normalized);
4639
+ }
4640
+ deduped[id] = media;
4641
+ }
4642
+ return deduped;
4497
4643
  }
4498
4644
  async getProfilePicture(namespace, onlyPrimary = true) {
4499
4645
  if (!this.id) {
package/dist/index.js CHANGED
@@ -134,6 +134,100 @@ var objeXrefKey = (obje, fallback = 0) => {
134
134
  return `@O${fallback}@`;
135
135
  };
136
136
  var objeNoteValue = (obje) => mediaIdValue(obje, "FILE.NOTE") || mediaIdValue(obje, "NOTE") || "";
137
+ var FS_WEB_RE = /familysearch\.org/i;
138
+ var FS_ARK_ID_RE = /^(?:ark:\/[^/]+\/)?([123]:[12]:[A-Za-z0-9-]+)$/i;
139
+ var listItems = (list) => {
140
+ if (!list) {
141
+ return [];
142
+ }
143
+ if (typeof list.values === "function") {
144
+ return list.values();
145
+ }
146
+ if (typeof list.forEach === "function") {
147
+ const items = [];
148
+ list.forEach((item) => items.push(item));
149
+ return items;
150
+ }
151
+ return [];
152
+ };
153
+ var toList = (node) => {
154
+ if (!node) {
155
+ return void 0;
156
+ }
157
+ const maybe = node;
158
+ if (typeof maybe.toList === "function") {
159
+ return maybe.toList();
160
+ }
161
+ if (typeof maybe.values === "function" || typeof maybe.forEach === "function") {
162
+ return maybe;
163
+ }
164
+ return void 0;
165
+ };
166
+ var normalizeFsWebUrl = (url) => {
167
+ const trimmed = url.trim();
168
+ if (!trimmed || !FS_WEB_RE.test(trimmed)) {
169
+ return void 0;
170
+ }
171
+ if (/\/dist\.jpg|\/service\/records\/storage\/|\/platform\//i.test(trimmed)) {
172
+ return void 0;
173
+ }
174
+ return trimmed;
175
+ };
176
+ var familySearchMediaOriginUrl = (obje, indi) => {
177
+ if (!obje) {
178
+ return void 0;
179
+ }
180
+ const resolved = obje.ref && typeof obje.toValue?.() === "string" ? obje.ref : obje;
181
+ const directWww = mediaIdValue(resolved, "WWW") || mediaIdValue(obje, "WWW");
182
+ const fromWww = directWww ? normalizeFsWebUrl(directWww) : void 0;
183
+ if (fromWww) {
184
+ return fromWww;
185
+ }
186
+ const objeKey = objeXrefKey(obje);
187
+ const sourItems = listItems(toList(indi?.get?.("SOUR")));
188
+ for (const sour of sourItems) {
189
+ if (!sour) continue;
190
+ const sourWww = mediaIdValue(sour, "WWW");
191
+ const normalized = sourWww ? normalizeFsWebUrl(sourWww) : void 0;
192
+ if (!normalized) continue;
193
+ const sourIsFs = mediaIdValue(sour, "_IS_FS") === "Y" || FS_WEB_RE.test(normalized);
194
+ if (!sourIsFs) continue;
195
+ let linksThisObje = false;
196
+ for (const ref of listItems(toList(sour.get?.("OBJE")))) {
197
+ if (ref && objeXrefKey(ref) === objeKey) {
198
+ linksThisObje = true;
199
+ break;
200
+ }
201
+ }
202
+ const sourFsId = mediaIdValue(sour, "_FS_ID");
203
+ const objeFsId = mediaIdValue(resolved, "_FS_ID") || mediaIdValue(obje, "_FS_ID");
204
+ if (linksThisObje || sourFsId && objeFsId && sourFsId === objeFsId) {
205
+ return normalized;
206
+ }
207
+ }
208
+ const isFs = mediaIdValue(resolved, "_IS_FS") === "Y" || mediaIdValue(obje, "_IS_FS") === "Y";
209
+ const fsId = mediaIdValue(resolved, "_FS_ID") || mediaIdValue(obje, "_FS_ID");
210
+ if (!isFs || !fsId) {
211
+ return void 0;
212
+ }
213
+ const arkMatch = fsId.match(FS_ARK_ID_RE);
214
+ if (arkMatch?.[1]) {
215
+ return `https://www.familysearch.org/ark:/61903/${arkMatch[1]}`;
216
+ }
217
+ if (/^\d+$/.test(fsId)) {
218
+ return `https://www.familysearch.org/photos/artifacts/${fsId}`;
219
+ }
220
+ for (const sour of sourItems) {
221
+ if (!sour) continue;
222
+ if (mediaIdValue(sour, "_FS_ID") !== fsId) continue;
223
+ const sourWww = mediaIdValue(sour, "WWW");
224
+ const normalized = sourWww ? normalizeFsWebUrl(sourWww) : void 0;
225
+ if (normalized) {
226
+ return normalized;
227
+ }
228
+ }
229
+ return void 0;
230
+ };
137
231
  var List = class _List {
138
232
  // length = 0;
139
233
  constructor(items) {
@@ -2540,6 +2634,14 @@ var mergeMediaListsByObjeKey = (...lists) => {
2540
2634
  for (const [id, item] of Object.entries(list)) {
2541
2635
  const objeKey = item.key ? String(item.key) : "";
2542
2636
  if (objeKey && seen.has(objeKey)) {
2637
+ if (item.originUrl) {
2638
+ const existing = Object.values(merged).find(
2639
+ (entry) => entry.key === objeKey
2640
+ );
2641
+ if (existing && !existing.originUrl) {
2642
+ existing.originUrl = item.originUrl;
2643
+ }
2644
+ }
2543
2645
  continue;
2544
2646
  }
2545
2647
  merged[id] = item;
@@ -3795,6 +3897,10 @@ var Indi = class extends Common {
3795
3897
  }
3796
3898
  if (url && imgId) {
3797
3899
  const id = `${tree || "gedzip"}-${this.id}-${imgId}`;
3900
+ const originUrl = familySearchMediaOriginUrl(
3901
+ objeRef ?? obje,
3902
+ this
3903
+ );
3798
3904
  list[id] = {
3799
3905
  key,
3800
3906
  isPrimary,
@@ -3806,7 +3912,8 @@ var Indi = class extends Common {
3806
3912
  note,
3807
3913
  url,
3808
3914
  contentType: type,
3809
- downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
3915
+ downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`,
3916
+ ...originUrl ? { originUrl } : {}
3810
3917
  };
3811
3918
  }
3812
3919
  })
@@ -4026,18 +4133,39 @@ var Indi = class extends Common {
4026
4133
  if (!this.id) {
4027
4134
  return list;
4028
4135
  }
4029
- const objeList = this.get("OBJE")?.toList().copy();
4136
+ let objeList = this.get("OBJE")?.toList().copy();
4137
+ const sourList = this.get("SOUR")?.toList();
4138
+ sourList?.forEach((sour) => {
4139
+ const sourObje = sour?.get("OBJE")?.toList();
4140
+ if (!sourObje?.length) {
4141
+ return;
4142
+ }
4143
+ if (!objeList) {
4144
+ objeList = sourObje.copy();
4145
+ } else {
4146
+ objeList.merge(sourObje);
4147
+ }
4148
+ });
4030
4149
  if (!objeList || objeList.length === 0) {
4031
4150
  return list;
4032
4151
  }
4033
4152
  const rfn = this.get("RFN")?.toValue();
4034
4153
  const treeId = this.getUniversalTreeId() || rfn || "universal";
4154
+ const seenFsIds = /* @__PURE__ */ new Set();
4035
4155
  objeList.forEach((obje, index) => {
4036
4156
  if (!obje) {
4037
4157
  return;
4038
4158
  }
4039
4159
  const key = objeXrefKey(obje, index);
4040
4160
  obje.standardizeMedia();
4161
+ const fsId = obje?.get("_FS_ID")?.toValue?.();
4162
+ if (typeof fsId === "string" && fsId.trim()) {
4163
+ const normalized = fsId.trim();
4164
+ if (seenFsIds.has(normalized)) {
4165
+ return;
4166
+ }
4167
+ seenFsIds.add(normalized);
4168
+ }
4041
4169
  const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
4042
4170
  const url = obje?.get("FILE")?.toValue();
4043
4171
  const title = obje?.get("FILE.TITL")?.toValue() ?? obje?.get("TITL")?.toValue() ?? "";
@@ -4045,6 +4173,7 @@ var Indi = class extends Common {
4045
4173
  if (url) {
4046
4174
  const imgId = `media-${index}-${url.startsWith("data:") ? getFileExtension(url) || "bin" : url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
4047
4175
  const id = `${treeId}-${this.id}-${imgId}`;
4176
+ const originUrl = familySearchMediaOriginUrl(obje, this);
4048
4177
  list[id] = {
4049
4178
  isPrimary,
4050
4179
  key,
@@ -4056,7 +4185,8 @@ var Indi = class extends Common {
4056
4185
  note: objeNoteValue(obje),
4057
4186
  url,
4058
4187
  contentType: type,
4059
- downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
4188
+ downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`,
4189
+ ...originUrl ? { originUrl } : {}
4060
4190
  };
4061
4191
  }
4062
4192
  });
@@ -4082,7 +4212,23 @@ var Indi = class extends Common {
4082
4212
  lists.push(this.geniMedia());
4083
4213
  }
4084
4214
  lists.push(this.universalMedia());
4085
- return mergeMediaListsByObjeKey(...lists);
4215
+ const merged = mergeMediaListsByObjeKey(...lists);
4216
+ const seenFsIds = /* @__PURE__ */ new Set();
4217
+ const deduped = {};
4218
+ for (const [id, media] of Object.entries(merged ?? {})) {
4219
+ const xref = media.key ? String(media.key) : "";
4220
+ const top = xref && this._gedcom ? this._gedcom.obje(xref) : void 0;
4221
+ const fsId = top?.get("_FS_ID")?.toValue?.();
4222
+ if (typeof fsId === "string" && fsId.trim()) {
4223
+ const normalized = fsId.trim();
4224
+ if (seenFsIds.has(normalized)) {
4225
+ continue;
4226
+ }
4227
+ seenFsIds.add(normalized);
4228
+ }
4229
+ deduped[id] = media;
4230
+ }
4231
+ return deduped;
4086
4232
  }
4087
4233
  async getProfilePicture(namespace, onlyPrimary = true) {
4088
4234
  if (!this.id) {
@@ -10194,7 +10340,7 @@ var collectExtensionTagsFrom = (node, into = /* @__PURE__ */ new Set()) => {
10194
10340
  // package.json
10195
10341
  var package_default = {
10196
10342
  name: "@treeviz/gedcom-parser",
10197
- version: "2.1.28"};
10343
+ version: "2.1.30"};
10198
10344
 
10199
10345
  // src/utils/get-product-details.ts
10200
10346
  var isDevelopment = () => {
@@ -11270,26 +11416,47 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11270
11416
  const mergedGedcom = targetGedcom;
11271
11417
  const idMap = /* @__PURE__ */ new Map();
11272
11418
  const matchMap = /* @__PURE__ */ new Map();
11419
+ const reusedTopLevelIds = /* @__PURE__ */ new Set();
11273
11420
  const isXref = (value) => !!value && /^@[A-Za-z0-9_]+@$/.test(value.trim());
11421
+ const recordFsId = (item) => {
11422
+ const node = item;
11423
+ const value = node?.get?.("_FS_ID")?.toValue?.();
11424
+ return typeof value === "string" ? value.trim() : "";
11425
+ };
11274
11426
  const allocateTopLevelIds = (sourceList, targetList, prefix) => {
11275
11427
  const used = /* @__PURE__ */ new Set();
11276
- targetList?.forEach((_item, id) => {
11428
+ const byFsId = /* @__PURE__ */ new Map();
11429
+ targetList?.forEach((item, id) => {
11277
11430
  used.add(String(id));
11431
+ const fsId = recordFsId(item);
11432
+ if (fsId && !byFsId.has(fsId)) {
11433
+ byFsId.set(fsId, String(id));
11434
+ }
11278
11435
  });
11279
11436
  idMap.forEach((mapped) => {
11280
11437
  used.add(String(mapped));
11281
11438
  });
11282
- sourceList?.forEach((_item, sourceId) => {
11439
+ sourceList?.forEach((item, sourceId) => {
11283
11440
  const sid = String(sourceId);
11284
11441
  if (!sid || idMap.has(sid)) {
11285
11442
  return;
11286
11443
  }
11444
+ const fsId = recordFsId(item);
11445
+ if (fsId && byFsId.has(fsId)) {
11446
+ const existingId = byFsId.get(fsId);
11447
+ idMap.set(sid, existingId);
11448
+ reusedTopLevelIds.add(sid);
11449
+ return;
11450
+ }
11287
11451
  let newId = sid;
11288
11452
  if (used.has(sid)) {
11289
11453
  newId = nextRecordId([...used], prefix);
11290
11454
  }
11291
11455
  used.add(newId);
11292
11456
  idMap.set(sid, newId);
11457
+ if (fsId) {
11458
+ byFsId.set(fsId, newId);
11459
+ }
11293
11460
  });
11294
11461
  };
11295
11462
  const remapIndiPointers = (clonedIndi, sourceIndi, tag) => {
@@ -11309,6 +11476,9 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11309
11476
  }
11310
11477
  const pointer = createCommon(mergedGedcom, void 0, clonedIndi);
11311
11478
  pointer.value = newVal;
11479
+ if (tag === "OBJE" || tag === "SOUR") {
11480
+ pointer.refType = tag;
11481
+ }
11312
11482
  clonedIndi.assign(tag, pointer, true);
11313
11483
  });
11314
11484
  };
@@ -11420,10 +11590,39 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11420
11590
  clonedFam.setGedcom(mergedGedcom);
11421
11591
  clonedFams.set(sourceFam.id, clonedFam);
11422
11592
  });
11593
+ const fillMissingTag = (target, source, tag) => {
11594
+ const current = target.get(tag)?.toValue?.();
11595
+ if (current !== void 0 && current !== null && String(current).trim()) {
11596
+ return;
11597
+ }
11598
+ const next = source.get(tag)?.toValue?.();
11599
+ if (typeof next === "string" && next.trim()) {
11600
+ target.set(tag, next);
11601
+ }
11602
+ };
11603
+ const isBinaryMediaUrl = (url) => /\.(jpe?g|png|gif|webp|tif{1,2}|pdf)(\?|$)/i.test(url) || /dist\.jpg/i.test(url);
11423
11604
  sourceGedcom.objes()?.forEach((sourceObje, sourceId) => {
11424
11605
  if (!sourceObje || !sourceId) return;
11606
+ const sid = String(sourceId);
11425
11607
  const newId = idMap.get(sourceId);
11426
11608
  if (!newId) return;
11609
+ if (reusedTopLevelIds.has(sid)) {
11610
+ const existingObje = mergedGedcom.obje(newId);
11611
+ if (existingObje) {
11612
+ fillMissingTag(existingObje, sourceObje, "WWW");
11613
+ fillMissingTag(existingObje, sourceObje, "TITL");
11614
+ const curFile = String(
11615
+ existingObje.get("FILE")?.toValue?.() ?? ""
11616
+ ).trim();
11617
+ const nextFile = String(
11618
+ sourceObje.get("FILE")?.toValue?.() ?? ""
11619
+ ).trim();
11620
+ if (nextFile && (!curFile || isBinaryMediaUrl(nextFile) && !isBinaryMediaUrl(curFile))) {
11621
+ existingObje.set("FILE", nextFile);
11622
+ }
11623
+ }
11624
+ return;
11625
+ }
11427
11626
  const cloned = sourceObje.clone(false);
11428
11627
  cloned.id = newId;
11429
11628
  cloned.setGedcom(mergedGedcom);
@@ -11436,11 +11635,35 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11436
11635
  });
11437
11636
  sourceGedcom.sours()?.forEach((sourceSour, sourceId) => {
11438
11637
  if (!sourceSour || !sourceId) return;
11638
+ const sid = String(sourceId);
11439
11639
  const newId = idMap.get(sourceId);
11440
11640
  if (!newId) return;
11641
+ if (reusedTopLevelIds.has(sid)) {
11642
+ const existingSour = mergedGedcom.sour(newId);
11643
+ if (existingSour) {
11644
+ fillMissingTag(existingSour, sourceSour, "WWW");
11645
+ fillMissingTag(existingSour, sourceSour, "TITL");
11646
+ fillMissingTag(existingSour, sourceSour, "PAGE");
11647
+ }
11648
+ return;
11649
+ }
11441
11650
  const cloned = sourceSour.clone(false);
11442
11651
  cloned.id = newId;
11443
11652
  cloned.setGedcom(mergedGedcom);
11653
+ const sourObjes = cloned.get("OBJE")?.toList();
11654
+ if (sourObjes?.length) {
11655
+ cloned.remove("OBJE");
11656
+ sourObjes.forEach((ref) => {
11657
+ const oldVal = String(ref?.toValue?.() ?? "").trim();
11658
+ if (!oldVal) return;
11659
+ const newVal = isXref(oldVal) ? idMap.get(oldVal) : oldVal;
11660
+ if (!newVal) return;
11661
+ const pointer = createCommon(mergedGedcom, void 0, cloned);
11662
+ pointer.value = newVal;
11663
+ pointer.refType = "OBJE";
11664
+ cloned.assign("OBJE", pointer, true);
11665
+ });
11666
+ }
11444
11667
  const existing = mergedGedcom.sours();
11445
11668
  const list = existing ?? new Sources();
11446
11669
  if (!existing) {
@@ -11464,6 +11687,7 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11464
11687
  clonedIndi
11465
11688
  );
11466
11689
  newFamRef.value = newFamId;
11690
+ newFamRef.refType = "FAM";
11467
11691
  clonedIndi.assign("FAMS", newFamRef, true);
11468
11692
  }
11469
11693
  }
@@ -11481,6 +11705,7 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11481
11705
  clonedIndi
11482
11706
  );
11483
11707
  newFamRef.value = newFamId;
11708
+ newFamRef.refType = "FAM";
11484
11709
  clonedIndi.assign("FAMC", newFamRef, true);
11485
11710
  }
11486
11711
  }
@@ -11501,6 +11726,7 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11501
11726
  clonedFam
11502
11727
  );
11503
11728
  newHusbRef.value = newHusbId;
11729
+ newHusbRef.refType = "INDI";
11504
11730
  clonedFam.set("HUSB", newHusbRef);
11505
11731
  }
11506
11732
  }
@@ -11514,6 +11740,7 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11514
11740
  clonedFam
11515
11741
  );
11516
11742
  newWifeRef.value = newWifeId;
11743
+ newWifeRef.refType = "INDI";
11517
11744
  clonedFam.set("WIFE", newWifeRef);
11518
11745
  }
11519
11746
  }
@@ -11530,6 +11757,7 @@ var mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
11530
11757
  clonedFam
11531
11758
  );
11532
11759
  newChildRef.value = newChildId;
11760
+ newChildRef.refType = "INDI";
11533
11761
  clonedFam.assign("CHIL", newChildRef, true);
11534
11762
  }
11535
11763
  }
@@ -11791,6 +12019,9 @@ var GedcomTree = {
11791
12019
  }, []);
11792
12020
  let linesJoined = lines.join("\n");
11793
12021
  if (purgeOversized && typeof maxFileSizeToPurge === "number" && !linesJoined.includes("1 _IS_PURGED true") && getRawSize(linesJoined) > maxFileSizeToPurge) {
12022
+ if (!linesJoined.endsWith("\n")) {
12023
+ linesJoined += "\n";
12024
+ }
11794
12025
  linesJoined = linesJoined.replace(
11795
12026
  /1\s(NOTE|OBJE|SOUR)([^\n]*\n)([23456789]\s.+\n)*/gm,
11796
12027
  ""
@@ -11952,4 +12183,4 @@ var createEmptyGedcom = (options) => {
11952
12183
  return gedcom;
11953
12184
  };
11954
12185
 
11955
- export { ACCEPTED_DATE_FORMATS, ACCEPTED_DATE_FORMATS_REGEX, ADOPTED, ANCESTRY_MEDIA_RETRIEVAL_PREFIX, ANCESTRY_TREE_MEDIA_NAMESPACE, APP_BRANDING, BIOLOGICAL, BIRTH, BIRTH_ASC, BIRTH_DESC, CREATED_AT_TAG, Common, CommonDate, CommonName, CommonNote, CustomTags, DATE_ASC, DATE_DESC, DEATH_ASC, DEATH_DESC, DEFAULT, DEFAULT_GEDCOM_EXPORT_VERSION, EVERY, Existed, FEMALE, FOSTER, FRIEND, Fam, Families, GEDCOM7_CALENDARS, GEDCOM7_PEDI_VALUES, GEDCOM7_RESN_VALUES, GEDCOM7_ROLE_VALUES, GEDCOM7_SEX_VALUES, GEDCOM_EXPORT_VERSIONS, GEDZIP_EXTENSION, GEDZIP_GEDCOM_ENTRY, GEDZIP_MIME, GedCom, parser_default as GedcomTree, ID_GETTER_REG, ID_REG, ID_SPLIT_REG, Indi, Individuals, KinshipTranslator, KinshipTranslatorBasic, KinshipTranslatorDe as KinshipTranslatorDE, KinshipTranslatorEn as KinshipTranslatorEN, KinshipTranslatorEs as KinshipTranslatorES, KinshipTranslatorFr as KinshipTranslatorFR, KinshipTranslatorHU, KinshipTranslatorJa as KinshipTranslatorJA, KinshipTranslatorRu as KinshipTranslatorRU, KinshipTranslatorZh as KinshipTranslatorZH, LINE_REG, List, MALE, ORIG_HEAD_TAG, OTHER, Obje, Objects, PARTNER, PEDIGREE_VALUES, PartnerType, PlaceType, REF_LINE_REG, Range, RelationType, Repo, Repositories, SEALING, SINGLE, SPOUSE, STEP, Sour, Sources, Subm, Submitters, TREEVIZ_EXTENSION_BASE_URI, TREEVIZ_EXTENSION_URIS, UNKOWN, addFamilyFact, addIndividualFact, addNonEvent, ancestryMediaFileUrl, applyGedcom7DatePhrases, applyGedcom7Enumerations, applyGedcom7RecordMetadata, attachChildToFamily, attachSpouseToFamily, buildGedzip, buildGedzipBlob, bytesToDataUrl, cacheDiscoveredPath, canAttachAsSpouse, collectExtensionTagsFrom, commonDateFormatter, consolidateChildIntoFamily, coupleKeyForFam, create, createCommon, createCommonDate, createCommonName, createCommonNote, createEmptyGedcom, createFam, createGedCom, createGedcomUid, createIndi, createObje, createProxy, createRepo, createSour, createSubm, dateFormatter, parser_default as default, deleteIndividual, downloadGedzipMedia, ensureExtensionSchema, extractGedzip, extractGedzipGedcom, extractSeparationYears, extractSplitPoints, familyHasChild, familyHasParent, findDegenerateFamiliesForIndi, findDuplicateCoupleFamiliesForIndi, findMatchingRangeForSplitRange, findReusableParentChildFamily, firstPointerId, formatGedcom7Timestamp, fromTuple, generateSplitRanges, getAllProp, getBirthAsc, getCacheManagerFactory, getDateLocale, getExtensionSchema, getFamilyWith, getFileExtension, getI18n, getKinshipTranslatorClass, getListTag, getMarriageAsc, getMarriageAscAndBirth, getMarriageAscAndChildBirth, getName, getNameAsc, getNameAscAndBirth, getNameDesc, getPlaceParserProvider, getPlaceParts, getPlaceTranslatorProvider, getPlaces, getRawSize, getValidKey, getValidKeys, getValidTag, getVersion, hungarianOrdinalize, i18n, idGetter, implemented, inRange, inferMediaForm, initializeCache, isAncestryOriginObje, isBiologicalRelation, isCommonDate, isDegenerateFamily, isDevelopment, isGedcom7, isGedcomString, isGedzipContainer, isId, isImageFormat, isIntersectedRange, isObjePointer, isOnlyMainProp, isRangeContained, isRemoteOrEmbeddedMediaUrl, isValidKey, makePointer, marriageDateFormatter, mergeDuplicateCoupleFamilies, mergeGedcoms, mergeMediaLists, mergeMediaListsByObjeKey, nameFormatter, nextRecordId, normalizeGedcomVersion, notImplemented, noteDateFormatter, objeNoteValue, objeXrefKey, ordinalize, parseAncestryNamespaceFromUrl, parseGedcom7CalendarPrefix, parseRangeBounds, pathCache, placeTranslator, pointerIds, profilePictureCache, pruneEmptyFamilies, pruneFamiliesByIds, registerExtensionTag, relativesCache, remountGedzipMediaAsDataUrls, removePointerByValue, resetCacheManagerFactory, resetDateLocaleProvider, resetI18nProvider, resetKinshipTranslatorClass, resetPlaceParserProvider, resetPlaceTranslatorProvider, resetRelativesCache, resolveLocalMediaUrl, resolveMediaContent, resolveObjeForm, resolveObjeMediaId, rewriteGedcomFilePaths, setCacheManagerFactory, setCauseOfDeath, setChildPedigree, setDateLocaleProvider, setDeceased, setFamilyMarriage, setI18nProvider, setKinshipTranslatorClass, setLocalMediaResolver, setMediaContentResolver, setNestedGroup, setPlaceParserProvider, setPlaceTranslatorProvider, splitOverlappingRanges, splitRange, spouseTagFor, stampCreatedAt, toGedcomPedigree, translators_exports as translators, unlinkRelative, updateFactRecord, validateGedcomContent, withGedcom7Calendar };
12186
+ export { ACCEPTED_DATE_FORMATS, ACCEPTED_DATE_FORMATS_REGEX, ADOPTED, ANCESTRY_MEDIA_RETRIEVAL_PREFIX, ANCESTRY_TREE_MEDIA_NAMESPACE, APP_BRANDING, BIOLOGICAL, BIRTH, BIRTH_ASC, BIRTH_DESC, CREATED_AT_TAG, Common, CommonDate, CommonName, CommonNote, CustomTags, DATE_ASC, DATE_DESC, DEATH_ASC, DEATH_DESC, DEFAULT, DEFAULT_GEDCOM_EXPORT_VERSION, EVERY, Existed, FEMALE, FOSTER, FRIEND, Fam, Families, GEDCOM7_CALENDARS, GEDCOM7_PEDI_VALUES, GEDCOM7_RESN_VALUES, GEDCOM7_ROLE_VALUES, GEDCOM7_SEX_VALUES, GEDCOM_EXPORT_VERSIONS, GEDZIP_EXTENSION, GEDZIP_GEDCOM_ENTRY, GEDZIP_MIME, GedCom, parser_default as GedcomTree, ID_GETTER_REG, ID_REG, ID_SPLIT_REG, Indi, Individuals, KinshipTranslator, KinshipTranslatorBasic, KinshipTranslatorDe as KinshipTranslatorDE, KinshipTranslatorEn as KinshipTranslatorEN, KinshipTranslatorEs as KinshipTranslatorES, KinshipTranslatorFr as KinshipTranslatorFR, KinshipTranslatorHU, KinshipTranslatorJa as KinshipTranslatorJA, KinshipTranslatorRu as KinshipTranslatorRU, KinshipTranslatorZh as KinshipTranslatorZH, LINE_REG, List, MALE, ORIG_HEAD_TAG, OTHER, Obje, Objects, PARTNER, PEDIGREE_VALUES, PartnerType, PlaceType, REF_LINE_REG, Range, RelationType, Repo, Repositories, SEALING, SINGLE, SPOUSE, STEP, Sour, Sources, Subm, Submitters, TREEVIZ_EXTENSION_BASE_URI, TREEVIZ_EXTENSION_URIS, UNKOWN, addFamilyFact, addIndividualFact, addNonEvent, ancestryMediaFileUrl, applyGedcom7DatePhrases, applyGedcom7Enumerations, applyGedcom7RecordMetadata, attachChildToFamily, attachSpouseToFamily, buildGedzip, buildGedzipBlob, bytesToDataUrl, cacheDiscoveredPath, canAttachAsSpouse, collectExtensionTagsFrom, commonDateFormatter, consolidateChildIntoFamily, coupleKeyForFam, create, createCommon, createCommonDate, createCommonName, createCommonNote, createEmptyGedcom, createFam, createGedCom, createGedcomUid, createIndi, createObje, createProxy, createRepo, createSour, createSubm, dateFormatter, parser_default as default, deleteIndividual, downloadGedzipMedia, ensureExtensionSchema, extractGedzip, extractGedzipGedcom, extractSeparationYears, extractSplitPoints, familyHasChild, familyHasParent, familySearchMediaOriginUrl, findDegenerateFamiliesForIndi, findDuplicateCoupleFamiliesForIndi, findMatchingRangeForSplitRange, findReusableParentChildFamily, firstPointerId, formatGedcom7Timestamp, fromTuple, generateSplitRanges, getAllProp, getBirthAsc, getCacheManagerFactory, getDateLocale, getExtensionSchema, getFamilyWith, getFileExtension, getI18n, getKinshipTranslatorClass, getListTag, getMarriageAsc, getMarriageAscAndBirth, getMarriageAscAndChildBirth, getName, getNameAsc, getNameAscAndBirth, getNameDesc, getPlaceParserProvider, getPlaceParts, getPlaceTranslatorProvider, getPlaces, getRawSize, getValidKey, getValidKeys, getValidTag, getVersion, hungarianOrdinalize, i18n, idGetter, implemented, inRange, inferMediaForm, initializeCache, isAncestryOriginObje, isBiologicalRelation, isCommonDate, isDegenerateFamily, isDevelopment, isGedcom7, isGedcomString, isGedzipContainer, isId, isImageFormat, isIntersectedRange, isObjePointer, isOnlyMainProp, isRangeContained, isRemoteOrEmbeddedMediaUrl, isValidKey, makePointer, marriageDateFormatter, mergeDuplicateCoupleFamilies, mergeGedcoms, mergeMediaLists, mergeMediaListsByObjeKey, nameFormatter, nextRecordId, normalizeGedcomVersion, notImplemented, noteDateFormatter, objeNoteValue, objeXrefKey, ordinalize, parseAncestryNamespaceFromUrl, parseGedcom7CalendarPrefix, parseRangeBounds, pathCache, placeTranslator, pointerIds, profilePictureCache, pruneEmptyFamilies, pruneFamiliesByIds, registerExtensionTag, relativesCache, remountGedzipMediaAsDataUrls, removePointerByValue, resetCacheManagerFactory, resetDateLocaleProvider, resetI18nProvider, resetKinshipTranslatorClass, resetPlaceParserProvider, resetPlaceTranslatorProvider, resetRelativesCache, resolveLocalMediaUrl, resolveMediaContent, resolveObjeForm, resolveObjeMediaId, rewriteGedcomFilePaths, setCacheManagerFactory, setCauseOfDeath, setChildPedigree, setDateLocaleProvider, setDeceased, setFamilyMarriage, setI18nProvider, setKinshipTranslatorClass, setLocalMediaResolver, setMediaContentResolver, setNestedGroup, setPlaceParserProvider, setPlaceTranslatorProvider, splitOverlappingRanges, splitRange, spouseTagFor, stampCreatedAt, toGedcomPedigree, translators_exports as translators, unlinkRelative, updateFactRecord, validateGedcomContent, withGedcom7Calendar };
@@ -24,6 +24,8 @@ export type MediaList = Record<string, {
24
24
  url: string;
25
25
  contentType: string;
26
26
  downloadName: string;
27
+ /** Vendor / FamilySearch web page for this memory or source (open in browser). */
28
+ originUrl?: string;
27
29
  }>;
28
30
  interface IIndi extends Common<string, IndiKey> {
29
31
  getBirthDate: (showDays?: boolean, shortNote?: boolean, showNote?: boolean) => string | undefined;