'BIRT', 'birth' => 'BIRT', 'mariage' => 'MARR', 'marriage' => 'MARR', 'deces' => 'DEAT', 'décès' => 'DEAT', 'death' => 'DEAT', ]; public function __construct( private readonly DateConversionService $dates, ) {} public function exportSource(Source $source): string { $source->load('sourceType.fields', 'releves.createur'); return $this->buildGedcom($source->releves, $source->nom, $source->sourceType->nom); } public function exportReleves(Collection $releves, string $titre = 'Export'): string { return $this->buildGedcom($releves, $titre); } private function buildGedcom(Collection $releves, string $sourceName, string $sourceTypeName = ''): string { $lines = []; $lines[] = $this->header($sourceName); $sourceTag = '@S1@'; $famId = 0; $indiId = 0; // Détecter le type d'événement depuis le nom du type de source $eventType = $this->detectEventType($sourceTypeName ?: $sourceName); $individuals = []; $families = []; foreach ($releves as $releve) { $data = $releve->data ?? []; $indiId++; $indiTag = "@I{$indiId}@"; $nom = $data['nom'] ?? ''; $prenom = $data['prenom'] ?? $data['prenom_epoux'] ?? $data['prenom_epouse'] ?? ''; // ── Individu principal ────────────────────────────────────────── $indi = []; $indi[] = "0 {$indiTag} INDI"; if ($nom || $prenom) { $indi[] = "1 NAME {$prenom} /{$nom}/"; if ($prenom) $indi[] = "1 GIVN {$prenom}"; if ($nom) $indi[] = "1 SURN {$nom}"; } // Événement principal $dateField = $data['date_evenement'] ?? $data['date_naissance'] ?? $data['date_mariage'] ?? null; $gedDate = is_array($dateField) ? $this->dates->toGedcomDate($dateField) : null; $lieu = $data['lieu_naissance'] ?? $data['lieu_evenement'] ?? $data['lieu_mariage'] ?? null; $indi[] = "1 {$eventType}"; if ($gedDate) $indi[] = "2 DATE {$gedDate}"; if ($lieu) $indi[] = "2 PLAC {$lieu}"; // Référence à la source $indi[] = "1 SOUR {$sourceTag}"; if (isset($data['numero_acte'])) { $indi[] = "2 PAGE Acte n°{$data['numero_acte']}"; } // Notes (champs non mappés) $noteFields = ['note', 'observation', 'remarque']; foreach ($noteFields as $nf) { if (!empty($data[$nf])) { foreach ($this->wrapNote($data[$nf]) as $noteLine) { $indi[] = $noteLine; } } } $individuals[] = implode("\n", $indi); // ── Famille (père/mère ou époux/épouse) ───────────────────────── $hasFather = !empty($data['nom_pere']) || !empty($data['prenom_pere']); $hasMother = !empty($data['nom_mere']) || !empty($data['prenom_mere']); $isMarriage = $eventType === 'MARR'; if ($hasFather || $hasMother || $isMarriage) { $famId++; $famTag = "@F{$famId}@"; $fam = []; $fam[] = "0 {$famTag} FAM"; if ($isMarriage) { // Pour un mariage, créer époux et épouse séparément if (!empty($data['nom_epoux']) || !empty($data['prenom_epoux'])) { $indiId++; $epouxTag = "@I{$indiId}@"; $individuals[] = implode("\n", [ "0 {$epouxTag} INDI", "1 NAME {$data['prenom_epoux']} /{$data['nom_epoux']}/", "1 FAMS {$famTag}", "1 SOUR {$sourceTag}", ]); $fam[] = "1 HUSB {$epouxTag}"; } if (!empty($data['nom_epouse']) || !empty($data['prenom_epouse'])) { $indiId++; $epouseTag = "@I{$indiId}@"; $individuals[] = implode("\n", [ "0 {$epouseTag} INDI", "1 NAME {$data['prenom_epouse']} /{$data['nom_epouse']}/", "1 FAMS {$famTag}", "1 SOUR {$sourceTag}", ]); $fam[] = "1 WIFE {$epouseTag}"; } $fam[] = "1 MARR"; if ($gedDate) $fam[] = "2 DATE {$gedDate}"; if ($lieu) $fam[] = "2 PLAC {$lieu}"; // Lier l'individu principal comme enfant si besoin } else { // Naissance/décès : père et mère if ($hasFather) { $indiId++; $pereTag = "@I{$indiId}@"; $individuals[] = implode("\n", [ "0 {$pereTag} INDI", "1 NAME " . ($data['prenom_pere'] ?? '') . " /" . ($data['nom_pere'] ?? '') . "/", "1 FAMS {$famTag}", "1 SOUR {$sourceTag}", ]); $fam[] = "1 HUSB {$pereTag}"; } if ($hasMother) { $indiId++; $mereTag = "@I{$indiId}@"; $individuals[] = implode("\n", [ "0 {$mereTag} INDI", "1 NAME " . ($data['prenom_mere'] ?? '') . " /" . ($data['nom_mere'] ?? '') . "/", "1 FAMS {$famTag}", "1 SOUR {$sourceTag}", ]); $fam[] = "1 WIFE {$mereTag}"; } $fam[] = "1 CHIL {$indiTag}"; // Lier l'enfant à sa famille $indi[] = "1 FAMC {$famTag}"; } $families[] = implode("\n", $fam); } } foreach ($individuals as $indi) { $lines[] = $indi; } foreach ($families as $fam) { $lines[] = $fam; } // Enregistrement SOURCE $lines[] = implode("\n", [ "0 {$sourceTag} SOUR", "1 TITL {$sourceName}", "1 AUTH MesRelevés", ]); $lines[] = "0 TRLR"; return implode("\n", $lines) . "\n"; } private function header(string $sourceName): string { $date = now()->format('d M Y'); $time = now()->format('H:i:s'); return implode("\n", [ '0 HEAD', '1 SOUR MESRELEVES', '2 NAME MesRelevés', '2 VERS 1.0', '1 DEST ANY', "1 DATE {$date}", "2 TIME {$time}", '1 GEDC', '2 VERS 5.5.1', '2 FORM LINEAGE-LINKED', '1 CHAR UTF-8', "1 FILE {$sourceName}.ged", '1 LANG French', ]); } private function detectEventType(string $name): string { $lower = mb_strtolower($name); foreach (self::FIELD_EVENT_MAP as $keyword => $tag) { if (str_contains($lower, $keyword)) { return $tag; } } return 'EVEN'; } /** Découpe une note longue en lignes GEDCOM (max 248 chars par ligne) */ private function wrapNote(string $text): array { $lines = []; $chunks = mb_str_split($text, 248); foreach ($chunks as $i => $chunk) { $lines[] = ($i === 0 ? '1 NOTE ' : '2 CONT ') . $chunk; } return $lines; } }