@treeviz/gedcom-parser 1.0.3 → 1.0.4
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.
- package/dist/classes/common.d.ts +5 -0
- package/dist/classes/common.d.ts.map +1 -1
- package/dist/classes/common.js +7 -0
- package/dist/classes/gedcom.d.ts +21 -0
- package/dist/classes/gedcom.d.ts.map +1 -1
- package/dist/classes/gedcom.js +292 -0
- package/dist/cli/commands/merge.d.ts.map +1 -1
- package/dist/cli/commands/merge.js +40 -72
- package/package.json +1 -1
package/dist/classes/common.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export declare class Common<T = string, I extends IdType = IdType> implements IC
|
|
|
27
27
|
get id(): I | undefined;
|
|
28
28
|
set value(value: T | undefined);
|
|
29
29
|
get value(): T | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Set the gedcom reference for this object
|
|
32
|
+
* @param gedcom - The GedComType to set as the gedcom reference
|
|
33
|
+
*/
|
|
34
|
+
setGedcom(gedcom: GedComType): void;
|
|
30
35
|
get originalValue(): T;
|
|
31
36
|
get ref(): Common<T, I> | undefined;
|
|
32
37
|
get main(): Common<string, IdType>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/classes/common.ts"],"names":[],"mappings":"AAMA,OAAO,EAEN,KAAK,MAAM,EACX,KAAK,QAAQ,EAEb,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,OAAO,MAAM,sBAAsB,CAAC;AAGhD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAI9B,qBAAa,MAAM,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,YAAW,OAAO,CAC5E,CAAC,EACD,CAAC,CACD;IACA,SAAS,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;IAC/B,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACrB,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,SAAS,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC3B,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;IAEvB,UAAU,UAAQ;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;gBAEN,MAAM,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAcvE,IAAI,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAKlC;IAED,IAAI,IAAI,IAAI,QAAQ,GAAG,SAAS,CAE/B;IAED,YAAY;IAMZ,IAAI,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,EASlC;IAED,IAAI,QAAQ,IAAI,MAAM,GAAG,SAAS,CAEjC;IAED,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,SAAS,EAEvB;IAED,IAAI,EAAE,IAJK,CAAC,GAAG,SAAS,CAMvB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,EAE7B;IAED,IAAI,KAAK,IAJQ,CAAC,GAAG,SAAS,CAM7B;
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/classes/common.ts"],"names":[],"mappings":"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"}
|
package/dist/classes/common.js
CHANGED
|
@@ -58,6 +58,13 @@ export class Common {
|
|
|
58
58
|
get value() {
|
|
59
59
|
return this._value;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Set the gedcom reference for this object
|
|
63
|
+
* @param gedcom - The GedComType to set as the gedcom reference
|
|
64
|
+
*/
|
|
65
|
+
setGedcom(gedcom) {
|
|
66
|
+
this._gedcom = gedcom;
|
|
67
|
+
}
|
|
61
68
|
// avoid to override
|
|
62
69
|
get originalValue() {
|
|
63
70
|
return this._value;
|
package/dist/classes/gedcom.d.ts
CHANGED
|
@@ -115,4 +115,25 @@ export declare const validateGedcomContent: (content?: string) => {
|
|
|
115
115
|
valid: boolean;
|
|
116
116
|
error?: string;
|
|
117
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* Merge two GEDCOM objects into a single result using a configurable matching strategy
|
|
120
|
+
* @param targetGedcom - The base GEDCOM (kept as the primary source)
|
|
121
|
+
* @param sourceGedcom - The GEDCOM to be merged into the target
|
|
122
|
+
* @param strategy - Matching strategy: "id" (default) to match by individual ID, or any MultiTag (e.g., "NAME", "BIRT.DATE") to match by that field's value
|
|
123
|
+
* @returns The merged GedComType with all individuals and families combined
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* // Merge by ID (individuals with same ID are considered the same person)
|
|
127
|
+
* const merged = await mergeGedcoms(target, source, "id");
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* // Merge by NAME (individuals with same name are considered the same person)
|
|
131
|
+
* const merged = await mergeGedcoms(target, source, "NAME");
|
|
132
|
+
*
|
|
133
|
+
* @remarks
|
|
134
|
+
* - Source individuals are always assigned new unique IDs to avoid conflicts
|
|
135
|
+
* - When individuals match by strategy, they are merged (data and relationships combined)
|
|
136
|
+
* - All family relationships (FAMS/FAMC) are preserved with updated ID references
|
|
137
|
+
*/
|
|
138
|
+
export declare const mergeGedcoms: (targetGedcom: GedComType, sourceGedcom: GedComType, strategy?: MultiTag | "id") => GedComType;
|
|
118
139
|
//# sourceMappingURL=gedcom.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gedcom.d.ts","sourceRoot":"","sources":["../../src/classes/gedcom.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gedcom.d.ts","sourceRoot":"","sources":["../../src/classes/gedcom.ts"],"names":[],"mappings":"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"}
|
package/dist/classes/gedcom.js
CHANGED
|
@@ -595,3 +595,295 @@ export const validateGedcomContent = (content) => {
|
|
|
595
595
|
// }
|
|
596
596
|
return { valid: true };
|
|
597
597
|
};
|
|
598
|
+
// Constants for merge operation
|
|
599
|
+
const MERGE_ID_INCREMENT_MULTIPLIER = 1000;
|
|
600
|
+
/**
|
|
601
|
+
* Helper function to convert a Common or List value to a string for comparison
|
|
602
|
+
*/
|
|
603
|
+
const valueToString = (value) => {
|
|
604
|
+
if (!value)
|
|
605
|
+
return "";
|
|
606
|
+
return value.toString?.() || String(value?.toValue?.() || "");
|
|
607
|
+
};
|
|
608
|
+
/**
|
|
609
|
+
* Merge two GEDCOM objects into a single result using a configurable matching strategy
|
|
610
|
+
* @param targetGedcom - The base GEDCOM (kept as the primary source)
|
|
611
|
+
* @param sourceGedcom - The GEDCOM to be merged into the target
|
|
612
|
+
* @param strategy - Matching strategy: "id" (default) to match by individual ID, or any MultiTag (e.g., "NAME", "BIRT.DATE") to match by that field's value
|
|
613
|
+
* @returns The merged GedComType with all individuals and families combined
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
* // Merge by ID (individuals with same ID are considered the same person)
|
|
617
|
+
* const merged = await mergeGedcoms(target, source, "id");
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* // Merge by NAME (individuals with same name are considered the same person)
|
|
621
|
+
* const merged = await mergeGedcoms(target, source, "NAME");
|
|
622
|
+
*
|
|
623
|
+
* @remarks
|
|
624
|
+
* - Source individuals are always assigned new unique IDs to avoid conflicts
|
|
625
|
+
* - When individuals match by strategy, they are merged (data and relationships combined)
|
|
626
|
+
* - All family relationships (FAMS/FAMC) are preserved with updated ID references
|
|
627
|
+
*/
|
|
628
|
+
export const mergeGedcoms = (targetGedcom, sourceGedcom, strategy = "id") => {
|
|
629
|
+
// Work directly with the target GEDCOM (no serialization needed)
|
|
630
|
+
const mergedGedcom = targetGedcom;
|
|
631
|
+
// Track ID mapping: source ID -> new ID in merged GEDCOM
|
|
632
|
+
const idMap = new Map();
|
|
633
|
+
// Track matching: sourceIndiId -> targetIndiId (for individuals that match by strategy)
|
|
634
|
+
const matchMap = new Map();
|
|
635
|
+
// Get source individuals and families
|
|
636
|
+
const sourceIndis = sourceGedcom.indis();
|
|
637
|
+
const sourceFams = sourceGedcom.fams();
|
|
638
|
+
const targetIndis = mergedGedcom.indis();
|
|
639
|
+
const targetFams = mergedGedcom.fams();
|
|
640
|
+
// Step 1: Identify matches and create ID mappings for individuals
|
|
641
|
+
sourceIndis?.forEach((sourceIndi) => {
|
|
642
|
+
if (!sourceIndi.id)
|
|
643
|
+
return;
|
|
644
|
+
let matchedTargetIndi;
|
|
645
|
+
if (strategy === "id") {
|
|
646
|
+
// Match by ID directly - check if target has this exact ID
|
|
647
|
+
matchedTargetIndi = targetIndis?.item(sourceIndi.id);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
// Match by specified MultiTag value
|
|
651
|
+
const sourceValueRaw = sourceIndi.get(strategy);
|
|
652
|
+
const sourceValue = valueToString(sourceValueRaw);
|
|
653
|
+
if (sourceValue) {
|
|
654
|
+
// Find target individual with same value
|
|
655
|
+
matchedTargetIndi = targetIndis?.find((targetIndi) => {
|
|
656
|
+
const targetValueRaw = targetIndi.get(strategy);
|
|
657
|
+
const targetValue = valueToString(targetValueRaw);
|
|
658
|
+
return targetValue && targetValue === sourceValue;
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (matchedTargetIndi && matchedTargetIndi.id) {
|
|
663
|
+
// Found a match - map source ID to existing target ID
|
|
664
|
+
// Note: This means source individual will merge into target individual
|
|
665
|
+
matchMap.set(sourceIndi.id, matchedTargetIndi.id);
|
|
666
|
+
idMap.set(sourceIndi.id, matchedTargetIndi.id);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
// No match - need to create a new unique ID
|
|
670
|
+
const baseId = sourceIndi.id;
|
|
671
|
+
let newId = baseId;
|
|
672
|
+
let counter = 1;
|
|
673
|
+
// Generate unique ID that doesn't conflict with target
|
|
674
|
+
while (targetIndis?.item(newId) || idMap.has(newId)) {
|
|
675
|
+
// Extract number from ID like @I123@ and increment
|
|
676
|
+
const numMatch = baseId.match(/\d+/);
|
|
677
|
+
const prefix = baseId.match(/^@[A-Z]+/)?.[0] || "@I";
|
|
678
|
+
const baseNum = numMatch ? parseInt(numMatch[0]) : 1;
|
|
679
|
+
newId = `${prefix}${baseNum + counter * MERGE_ID_INCREMENT_MULTIPLIER}@`;
|
|
680
|
+
counter++;
|
|
681
|
+
}
|
|
682
|
+
idMap.set(sourceIndi.id, newId);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
// Step 2: Map family IDs and match families with same members
|
|
686
|
+
const famMatchMap = new Map(); // source family ID -> target family ID
|
|
687
|
+
sourceFams?.forEach((sourceFam) => {
|
|
688
|
+
if (!sourceFam.id)
|
|
689
|
+
return;
|
|
690
|
+
// Get the member IDs for this source family
|
|
691
|
+
const sourceHusbId = sourceFam.HUSB?.value;
|
|
692
|
+
const sourceWifeId = sourceFam.WIFE?.value;
|
|
693
|
+
const sourceChildIds = sourceFam.CHIL?.toList()?.map(c => c.value).filter(Boolean) || [];
|
|
694
|
+
// Map to their final IDs (after individual matching by strategy)
|
|
695
|
+
const finalHusbId = sourceHusbId ? idMap.get(sourceHusbId) : undefined;
|
|
696
|
+
const finalWifeId = sourceWifeId ? idMap.get(sourceWifeId) : undefined;
|
|
697
|
+
const finalChildIds = sourceChildIds.map(id => idMap.get(id)).filter(Boolean);
|
|
698
|
+
// Try to find a matching family in target that has the same members (after remapping)
|
|
699
|
+
let matchedTargetFam;
|
|
700
|
+
targetFams?.forEach((targetFam) => {
|
|
701
|
+
if (matchedTargetFam)
|
|
702
|
+
return; // Already found a match
|
|
703
|
+
const targetHusbId = targetFam.HUSB?.value;
|
|
704
|
+
const targetWifeId = targetFam.WIFE?.value;
|
|
705
|
+
const targetChildIds = targetFam.CHIL?.toList()?.map(c => c.value).filter(Boolean) || [];
|
|
706
|
+
// Check if husband matches (either both undefined or same final ID)
|
|
707
|
+
const husbMatch = (!finalHusbId && !targetHusbId) || (finalHusbId === targetHusbId);
|
|
708
|
+
// Check if wife matches (either both undefined or same final ID)
|
|
709
|
+
const wifeMatch = (!finalWifeId && !targetWifeId) || (finalWifeId === targetWifeId);
|
|
710
|
+
// For a family to match, both husband and wife must match
|
|
711
|
+
// (if one is undefined in both, that's also a match)
|
|
712
|
+
if (husbMatch && wifeMatch) {
|
|
713
|
+
// Also check children overlap
|
|
714
|
+
const childOverlap = finalChildIds.filter(id => targetChildIds.includes(id)).length;
|
|
715
|
+
const totalUniqueChildren = new Set([...finalChildIds, ...targetChildIds]).size;
|
|
716
|
+
// Match if:
|
|
717
|
+
// 1. Both have no children, OR
|
|
718
|
+
// 2. At least 50% of children overlap
|
|
719
|
+
if (totalUniqueChildren === 0 || (childOverlap / totalUniqueChildren) >= 0.5) {
|
|
720
|
+
// Additionally require at least one spouse to be present (not both empty)
|
|
721
|
+
if (finalHusbId || finalWifeId || targetHusbId || targetWifeId) {
|
|
722
|
+
matchedTargetFam = targetFam;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
if (matchedTargetFam && matchedTargetFam.id) {
|
|
728
|
+
// Found a matching family - use the target family ID
|
|
729
|
+
famMatchMap.set(sourceFam.id, matchedTargetFam.id);
|
|
730
|
+
idMap.set(sourceFam.id, matchedTargetFam.id);
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
// No match - generate a new unique family ID
|
|
734
|
+
const baseId = sourceFam.id;
|
|
735
|
+
let newId = baseId;
|
|
736
|
+
let counter = 1;
|
|
737
|
+
// Generate unique family ID
|
|
738
|
+
while (targetFams?.item(newId) || idMap.has(newId)) {
|
|
739
|
+
const numMatch = baseId.match(/\d+/);
|
|
740
|
+
const prefix = baseId.match(/^@[A-Z]+/)?.[0] || "@F";
|
|
741
|
+
const baseNum = numMatch ? parseInt(numMatch[0]) : 1;
|
|
742
|
+
newId = `${prefix}${baseNum + counter * MERGE_ID_INCREMENT_MULTIPLIER}@`;
|
|
743
|
+
counter++;
|
|
744
|
+
}
|
|
745
|
+
idMap.set(sourceFam.id, newId);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
// Step 3: Clone and remap individuals from source
|
|
749
|
+
const clonedIndis = new Map();
|
|
750
|
+
sourceIndis?.forEach((sourceIndi) => {
|
|
751
|
+
if (!sourceIndi.id)
|
|
752
|
+
return;
|
|
753
|
+
const newId = idMap.get(sourceIndi.id);
|
|
754
|
+
if (!newId)
|
|
755
|
+
return;
|
|
756
|
+
// Clone the individual
|
|
757
|
+
const cloned = sourceIndi.clone(false);
|
|
758
|
+
cloned.id = newId;
|
|
759
|
+
cloned.setGedcom(mergedGedcom);
|
|
760
|
+
clonedIndis.set(sourceIndi.id, cloned);
|
|
761
|
+
});
|
|
762
|
+
// Step 4: Clone and remap families from source
|
|
763
|
+
const clonedFams = new Map();
|
|
764
|
+
sourceFams?.forEach((sourceFam) => {
|
|
765
|
+
if (!sourceFam.id)
|
|
766
|
+
return;
|
|
767
|
+
const newFamId = idMap.get(sourceFam.id);
|
|
768
|
+
if (!newFamId)
|
|
769
|
+
return;
|
|
770
|
+
// Clone the family
|
|
771
|
+
const clonedFam = sourceFam.clone(false);
|
|
772
|
+
clonedFam.id = newFamId;
|
|
773
|
+
clonedFam.setGedcom(mergedGedcom);
|
|
774
|
+
clonedFams.set(sourceFam.id, clonedFam);
|
|
775
|
+
});
|
|
776
|
+
// Step 5: Update FAMS and FAMC references in cloned individuals
|
|
777
|
+
clonedIndis.forEach((clonedIndi, originalId) => {
|
|
778
|
+
const sourceIndi = sourceIndis?.item(originalId);
|
|
779
|
+
if (!sourceIndi)
|
|
780
|
+
return;
|
|
781
|
+
// Clear and rebuild FAMS references with remapped IDs
|
|
782
|
+
clonedIndi.set("FAMS", undefined);
|
|
783
|
+
const sourceFAMS = sourceIndi.FAMS?.toList();
|
|
784
|
+
sourceFAMS?.forEach((famRef) => {
|
|
785
|
+
const oldFamId = famRef.value;
|
|
786
|
+
if (oldFamId) {
|
|
787
|
+
const newFamId = idMap.get(oldFamId);
|
|
788
|
+
if (newFamId) {
|
|
789
|
+
const newFamRef = createCommon(mergedGedcom, undefined, clonedIndi);
|
|
790
|
+
newFamRef.value = newFamId;
|
|
791
|
+
clonedIndi.assign("FAMS", newFamRef, true);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
// Clear and rebuild FAMC references with remapped IDs
|
|
796
|
+
clonedIndi.set("FAMC", undefined);
|
|
797
|
+
const sourceFAMC = sourceIndi.FAMC?.toList();
|
|
798
|
+
sourceFAMC?.forEach((famRef) => {
|
|
799
|
+
const oldFamId = famRef.value;
|
|
800
|
+
if (oldFamId) {
|
|
801
|
+
const newFamId = idMap.get(oldFamId);
|
|
802
|
+
if (newFamId) {
|
|
803
|
+
const newFamRef = createCommon(mergedGedcom, undefined, clonedIndi);
|
|
804
|
+
newFamRef.value = newFamId;
|
|
805
|
+
clonedIndi.assign("FAMC", newFamRef, true);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
});
|
|
810
|
+
// Step 6: Update HUSB, WIFE, and CHIL references in cloned families
|
|
811
|
+
clonedFams.forEach((clonedFam, originalFamId) => {
|
|
812
|
+
const sourceFam = sourceFams?.item(originalFamId);
|
|
813
|
+
if (!sourceFam)
|
|
814
|
+
return;
|
|
815
|
+
// Update HUSB reference
|
|
816
|
+
const sourceHusb = sourceFam.HUSB?.value;
|
|
817
|
+
if (sourceHusb) {
|
|
818
|
+
const newHusbId = idMap.get(sourceHusb);
|
|
819
|
+
if (newHusbId) {
|
|
820
|
+
const newHusbRef = createCommon(mergedGedcom, undefined, clonedFam);
|
|
821
|
+
newHusbRef.value = newHusbId;
|
|
822
|
+
clonedFam.set("HUSB", newHusbRef);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
// Update WIFE reference
|
|
826
|
+
const sourceWife = sourceFam.WIFE?.value;
|
|
827
|
+
if (sourceWife) {
|
|
828
|
+
const newWifeId = idMap.get(sourceWife);
|
|
829
|
+
if (newWifeId) {
|
|
830
|
+
const newWifeRef = createCommon(mergedGedcom, undefined, clonedFam);
|
|
831
|
+
newWifeRef.value = newWifeId;
|
|
832
|
+
clonedFam.set("WIFE", newWifeRef);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// Update CHIL references
|
|
836
|
+
clonedFam.set("CHIL", undefined);
|
|
837
|
+
const sourceChildren = sourceFam.CHIL?.toList();
|
|
838
|
+
sourceChildren?.forEach((childRef) => {
|
|
839
|
+
const oldChildId = childRef.value;
|
|
840
|
+
if (oldChildId) {
|
|
841
|
+
const newChildId = idMap.get(oldChildId);
|
|
842
|
+
if (newChildId) {
|
|
843
|
+
const newChildRef = createCommon(mergedGedcom, undefined, clonedFam);
|
|
844
|
+
newChildRef.value = newChildId;
|
|
845
|
+
clonedFam.assign("CHIL", newChildRef, true);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
// Step 7: Add or merge individuals into target GEDCOM
|
|
851
|
+
clonedIndis.forEach((clonedIndi, originalId) => {
|
|
852
|
+
const matchedTargetId = matchMap.get(originalId);
|
|
853
|
+
if (matchedTargetId) {
|
|
854
|
+
// This individual matches an existing one - merge data and relationships
|
|
855
|
+
const targetIndi = mergedGedcom.indis()?.item(matchedTargetId);
|
|
856
|
+
if (targetIndi) {
|
|
857
|
+
// Merge without overriding existing data
|
|
858
|
+
targetIndi.merge(clonedIndi, false);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
// This is a new individual - add it to merged GEDCOM
|
|
863
|
+
if (clonedIndi.id) {
|
|
864
|
+
mergedGedcom.indis()?.item(clonedIndi.id, clonedIndi);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
// Step 8: Add or merge families into target GEDCOM
|
|
869
|
+
clonedFams.forEach((clonedFam, originalFamId) => {
|
|
870
|
+
if (!clonedFam.id)
|
|
871
|
+
return;
|
|
872
|
+
const matchedTargetFamId = famMatchMap.get(originalFamId);
|
|
873
|
+
if (matchedTargetFamId) {
|
|
874
|
+
// This family matches an existing one - merge children and data
|
|
875
|
+
const targetFam = mergedGedcom.fams()?.item(matchedTargetFamId);
|
|
876
|
+
if (targetFam) {
|
|
877
|
+
// Merge family data without overriding
|
|
878
|
+
targetFam.merge(clonedFam, false);
|
|
879
|
+
// Note: Children from clonedFam are already added via merge above
|
|
880
|
+
// The CHIL references in clonedFam point to the correct remapped IDs
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
// This is a new family - add it to merged GEDCOM
|
|
885
|
+
mergedGedcom.fams()?.item(clonedFam.id, clonedFam);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
return mergedGedcom;
|
|
889
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/merge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/merge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyBpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoE3D"}
|
|
@@ -1,90 +1,58 @@
|
|
|
1
1
|
import { writeFileSync } from 'fs';
|
|
2
2
|
import GedcomTree from '../../utils/parser.js';
|
|
3
|
+
import { mergeGedcoms } from '../../classes/gedcom.js';
|
|
3
4
|
import { formatSuccess } from '../utils/formatters.js';
|
|
4
5
|
import { readGedcomFile, handleError } from '../utils/helpers.js';
|
|
6
|
+
/**
|
|
7
|
+
* Helper to get and validate the merge strategy
|
|
8
|
+
*/
|
|
9
|
+
function getMergeStrategy(options) {
|
|
10
|
+
if (options.dedupe) {
|
|
11
|
+
console.warn('Warning: --dedupe option is deprecated. Use --strategy NAME instead.');
|
|
12
|
+
return 'NAME';
|
|
13
|
+
}
|
|
14
|
+
return (options.strategy || 'id');
|
|
15
|
+
}
|
|
5
16
|
export function registerMergeCommand(program) {
|
|
6
17
|
program
|
|
7
18
|
.command('merge <files...>')
|
|
8
19
|
.description('Merge multiple GEDCOM files')
|
|
9
20
|
.requiredOption('-o, --output <file>', 'Output file path (required)')
|
|
10
|
-
.option('--dedupe', 'Attempt to detect and merge duplicates (
|
|
21
|
+
.option('--dedupe', 'Attempt to detect and merge duplicates (deprecated, use --strategy NAME)')
|
|
22
|
+
.option('--strategy <strategy>', 'Matching strategy: "id" (match by ID) or a tag like "NAME" (match by name). Default: "id"', 'id')
|
|
11
23
|
.action((files, options) => {
|
|
12
24
|
try {
|
|
13
25
|
if (files.length < 2) {
|
|
14
26
|
console.error('At least 2 files are required for merging');
|
|
15
27
|
process.exit(1);
|
|
16
28
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
while (seenIds.has(id)) {
|
|
45
|
-
id = `@I${idCounter++}@`;
|
|
46
|
-
}
|
|
47
|
-
seenIds.add(id);
|
|
48
|
-
// Store with original object (we'll need to update ID in output)
|
|
49
|
-
allIndividuals.push({ ...indi, newId: id });
|
|
50
|
-
});
|
|
51
|
-
// Add families with unique IDs
|
|
52
|
-
families.forEach(fam => {
|
|
53
|
-
let id = fam.id;
|
|
54
|
-
while (seenIds.has(id)) {
|
|
55
|
-
id = `@F${idCounter++}@`;
|
|
56
|
-
}
|
|
57
|
-
seenIds.add(id);
|
|
58
|
-
allFamilies.push({ ...fam, newId: id });
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
// Create merged GEDCOM
|
|
62
|
-
const lines = [];
|
|
63
|
-
lines.push('0 HEAD');
|
|
64
|
-
lines.push('1 SOUR gedcom-parser CLI');
|
|
65
|
-
lines.push('1 GEDC');
|
|
66
|
-
lines.push('2 VERS 5.5.1');
|
|
67
|
-
lines.push('1 CHAR UTF-8');
|
|
68
|
-
// Add all individuals
|
|
69
|
-
allIndividuals.forEach(indi => {
|
|
70
|
-
const raw = indi.raw?.() || '';
|
|
71
|
-
if (raw) {
|
|
72
|
-
// Replace old ID with new ID if needed
|
|
73
|
-
const updatedRaw = raw.replace(new RegExp(`^0 ${indi.id} INDI`, 'm'), `0 ${indi.newId} INDI`);
|
|
74
|
-
lines.push(...updatedRaw.split('\n').filter(line => line.trim()));
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
// Add all families
|
|
78
|
-
allFamilies.forEach(fam => {
|
|
79
|
-
const raw = fam.raw?.() || '';
|
|
80
|
-
if (raw) {
|
|
81
|
-
const updatedRaw = raw.replace(new RegExp(`^0 ${fam.id} FAM`, 'm'), `0 ${fam.newId} FAM`);
|
|
82
|
-
lines.push(...updatedRaw.split('\n').filter(line => line.trim()));
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
lines.push('0 TRLR');
|
|
86
|
-
writeFileSync(options.output, lines.join('\n'), 'utf-8');
|
|
87
|
-
console.log(formatSuccess(`Merged ${files.length} files (${allIndividuals.length} individuals, ${allFamilies.length} families) into ${options.output}`));
|
|
29
|
+
// For 2 files, use the new mergeGedcoms function
|
|
30
|
+
if (files.length === 2) {
|
|
31
|
+
const targetContent = readGedcomFile(files[0]);
|
|
32
|
+
const sourceContent = readGedcomFile(files[1]);
|
|
33
|
+
const { gedcom: targetGedcom } = GedcomTree.parse(targetContent);
|
|
34
|
+
const { gedcom: sourceGedcom } = GedcomTree.parse(sourceContent);
|
|
35
|
+
const strategy = getMergeStrategy(options);
|
|
36
|
+
const merged = mergeGedcoms(targetGedcom, sourceGedcom, strategy);
|
|
37
|
+
const mergedContent = merged.toGedcom();
|
|
38
|
+
writeFileSync(options.output, mergedContent, 'utf-8');
|
|
39
|
+
console.log(formatSuccess(`Merged 2 files using strategy "${strategy}" (${merged.indis()?.length} individuals, ${merged.fams()?.length} families) into ${options.output}`));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// For more than 2 files, use iterative merging
|
|
43
|
+
console.log(`Merging ${files.length} files iteratively...`);
|
|
44
|
+
let targetContent = readGedcomFile(files[0]);
|
|
45
|
+
let { gedcom: targetGedcom } = GedcomTree.parse(targetContent);
|
|
46
|
+
const strategy = getMergeStrategy(options);
|
|
47
|
+
for (let i = 1; i < files.length; i++) {
|
|
48
|
+
const sourceContent = readGedcomFile(files[i]);
|
|
49
|
+
const { gedcom: sourceGedcom } = GedcomTree.parse(sourceContent);
|
|
50
|
+
targetGedcom = mergeGedcoms(targetGedcom, sourceGedcom, strategy);
|
|
51
|
+
console.log(` Merged file ${i + 1}/${files.length}: ${files[i]}`);
|
|
52
|
+
}
|
|
53
|
+
const mergedContent = targetGedcom.toGedcom();
|
|
54
|
+
writeFileSync(options.output, mergedContent, 'utf-8');
|
|
55
|
+
console.log(formatSuccess(`Merged ${files.length} files (${targetGedcom.indis()?.length} individuals, ${targetGedcom.fams()?.length} families) into ${options.output}`));
|
|
88
56
|
}
|
|
89
57
|
catch (error) {
|
|
90
58
|
handleError(error, 'Failed to merge GEDCOM files');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeviz/gedcom-parser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Lightweight, pluggable GEDCOM parser for JavaScript/TypeScript with optional caching and place matching. Zero browser dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|