import type { FamilyMember, FamilyTreeData } from "@/types/family" import { parseGedcom } from "read-gedcom" /** * 将 FamilyTreeData 导出为 GEDCOM 格式 */ export function exportToGedcom(treeData: FamilyTreeData): string { const lines: string[] = [] // GEDCOM Header lines.push("0 HEAD") lines.push("1 SOUR Chinese Family Tree") lines.push("2 VERS 1.0") lines.push("2 NAME 华夏谱") lines.push("1 DEST ANY") lines.push("1 DATE " + new Date().toISOString().split('T')[0].replace(/-/g, ' ')) lines.push("1 CHAR UTF-8") lines.push("1 GEDC") lines.push("2 VERS 5.5.1") lines.push("2 FORM LINEAGE-LINKED") const members = Object.values(treeData.members) // 个人记录 (INDI) members.forEach(member => { lines.push(`0 @I${member.id}@ INDI`) lines.push(`1 NAME ${member.fullName}`) lines.push(`2 GIVN ${member.givenName}`) lines.push(`2 SURN ${member.surname}`) if (member.courtesyName) { lines.push(`2 _AKA ${member.courtesyName}`) } if (member.gender === 'MALE') { lines.push("1 SEX M") } else if (member.gender === 'FEMALE') { lines.push("1 SEX F") } // 出生信息 if (member.birthDate) { lines.push("1 BIRT") lines.push(`2 DATE ${formatGedcomDate(member.birthDate)}`) if (member.birthPlace) { lines.push(`2 PLAC ${member.birthPlace}`) } } // 逝世信息 if (member.deathDate) { lines.push("1 DEAT") lines.push(`2 DATE ${formatGedcomDate(member.deathDate)}`) if (member.burialPlace) { lines.push(`2 PLAC ${member.burialPlace}`) } } // 籍贯 if (member.ancestralHome) { lines.push("1 RESI") lines.push(`2 PLAC ${member.ancestralHome}`) } // 备注 if (member.bio) { lines.push("1 NOTE " + member.bio.replace(/\n/g, "\n1 CONT ")) } // 家庭关系 - 作为子女 if (member.fatherId || member.motherId) { const famId = `F${member.fatherId || member.motherId}_${member.id}` lines.push(`1 FAMC @${famId}@`) } // 家庭关系 - 作为配偶 if (member.spouseIds.length > 0) { member.spouseIds.forEach(spouseId => { const famId = `F${member.id}_${spouseId}` lines.push(`1 FAMS @${famId}@`) }) } }) // 家庭记录 (FAM) const processedFamilies = new Set() members.forEach(member => { // 创建父母家庭 if (member.fatherId || member.motherId) { const famId = `F${member.fatherId || member.motherId}_${member.id}` if (!processedFamilies.has(famId)) { lines.push(`0 @${famId}@ FAM`) if (member.fatherId) { lines.push(`1 HUSB @I${member.fatherId}@`) } if (member.motherId) { lines.push(`1 WIFE @I${member.motherId}@`) } // 查找所有同父母的子女 const siblings = members.filter(m => m.fatherId === member.fatherId && m.motherId === member.motherId ) siblings.forEach(child => { lines.push(`1 CHIL @I${child.id}@`) }) processedFamilies.add(famId) } } // 创建配偶家庭 if (member.spouseIds.length > 0) { member.spouseIds.forEach(spouseId => { const famId = member.gender === 'MALE' ? `F${member.id}_${spouseId}` : `F${spouseId}_${member.id}` if (!processedFamilies.has(famId)) { lines.push(`0 @${famId}@ FAM`) if (member.gender === 'MALE') { lines.push(`1 HUSB @I${member.id}@`) lines.push(`1 WIFE @I${spouseId}@`) } else { lines.push(`1 HUSB @I${spouseId}@`) lines.push(`1 WIFE @I${member.id}@`) } // 添加子女 member.childrenIds.forEach(childId => { lines.push(`1 CHIL @I${childId}@`) }) processedFamilies.add(famId) } }) } }) // GEDCOM Trailer lines.push("0 TRLR") return lines.join("\n") } /** * 从 GEDCOM 格式导入数据 */ export function importFromGedcom(gedcomText: string): FamilyTreeData { const parsed = parseGedcom(gedcomText as any) const members: Record = {} let rootId: string | undefined // 第一遍:创建所有个人 const records = Array.isArray(parsed) ? parsed : (parsed as any).tree || [] records.forEach((record: any) => { if (record.tag === 'INDI') { const id = record.pointer.replace('@I', '').replace('@', '') let fullName = '' let givenName = '' let surname = '' let courtesyName: string | undefined let gender: 'MALE' | 'FEMALE' | 'OTHER' = 'OTHER' let birthDate: string | undefined let deathDate: string | undefined let birthPlace: string | undefined let burialPlace: string | undefined let ancestralHome: string | undefined let bio: string | undefined record.tree.forEach((field: any) => { if (field.tag === 'NAME') { fullName = field.data.replace(/\//g, '').trim() field.tree?.forEach((subField: any) => { if (subField.tag === 'GIVN') givenName = subField.data if (subField.tag === 'SURN') surname = subField.data if (subField.tag === '_AKA') courtesyName = subField.data }) } if (field.tag === 'SEX') { gender = field.data === 'M' ? 'MALE' : field.data === 'F' ? 'FEMALE' : 'OTHER' } if (field.tag === 'BIRT') { field.tree?.forEach((subField: any) => { if (subField.tag === 'DATE') birthDate = parseGedcomDate(subField.data) if (subField.tag === 'PLAC') birthPlace = subField.data }) } if (field.tag === 'DEAT') { field.tree?.forEach((subField: any) => { if (subField.tag === 'DATE') deathDate = parseGedcomDate(subField.data) if (subField.tag === 'PLAC') burialPlace = subField.data }) } if (field.tag === 'RESI') { field.tree?.forEach((subField: any) => { if (subField.tag === 'PLAC') ancestralHome = subField.data }) } if (field.tag === 'NOTE') { bio = field.data } }) members[id] = { id, surname: surname || '未知', givenName: givenName || fullName, fullName: fullName || `${surname}${givenName}`, gender, generation: 1, // 将在第二遍中计算 courtesyName, birthDate, deathDate, birthPlace, burialPlace, ancestralHome, bio, spouseIds: [], childrenIds: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), } } }) // 第二遍:建立关系 records.forEach((record: any) => { if (record.tag === 'FAM') { let husbId: string | undefined let wifeId: string | undefined const childIds: string[] = [] record.tree.forEach((field: any) => { if (field.tag === 'HUSB') { husbId = field.data.replace('@I', '').replace('@', '') } if (field.tag === 'WIFE') { wifeId = field.data.replace('@I', '').replace('@', '') } if (field.tag === 'CHIL') { childIds.push(field.data.replace('@I', '').replace('@', '')) } }) // 设置配偶关系 if (husbId && wifeId) { if (members[husbId] && !members[husbId].spouseIds.includes(wifeId)) { members[husbId].spouseIds.push(wifeId) } if (members[wifeId] && !members[wifeId].spouseIds.includes(husbId)) { members[wifeId].spouseIds.push(husbId) } } // 设置父母-子女关系 childIds.forEach(childId => { if (members[childId]) { if (husbId) { members[childId].fatherId = husbId if (members[husbId] && !members[husbId].childrenIds.includes(childId)) { members[husbId].childrenIds.push(childId) } } if (wifeId) { members[childId].motherId = wifeId if (members[wifeId] && !members[wifeId].childrenIds.includes(childId)) { members[wifeId].childrenIds.push(childId) } } } }) } }) // 计算世代并找到根节点 const calculateGeneration = (id: string, visited = new Set()): number => { if (visited.has(id)) return 1 // 防止循环引用 visited.add(id) const member = members[id] if (!member) return 1 if (!member.fatherId && !member.motherId) { return 1 // 根节点 } const fatherGen = member.fatherId ? calculateGeneration(member.fatherId, visited) : 0 const motherGen = member.motherId ? calculateGeneration(member.motherId, visited) : 0 return Math.max(fatherGen, motherGen) + 1 } Object.keys(members).forEach(id => { members[id].generation = calculateGeneration(id) }) // 找到第一代作为根节点 rootId = Object.keys(members).find(id => members[id].generation === 1) return { members, rootId, } } // 辅助函数:格式化日期为 GEDCOM 格式 function formatGedcomDate(isoDate: string): string { const date = new Date(isoDate) const months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}` } // 辅助函数:解析 GEDCOM 日期为 ISO 格式 function parseGedcomDate(gedcomDate: string): string { const months: Record = { JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11 } const parts = gedcomDate.trim().split(' ') if (parts.length === 3) { const day = parseInt(parts[0]) const month = months[parts[1].toUpperCase()] const year = parseInt(parts[2]) if (!isNaN(day) && month !== undefined && !isNaN(year)) { return new Date(year, month, day).toISOString().split('T')[0] } } // 如果只有年份 if (parts.length === 1 && !isNaN(parseInt(parts[0]))) { return `${parts[0]}-01-01` } return new Date().toISOString().split('T')[0] }