UNPKG

gedcom-d3

Version:

A GEDCOM parser, which translates GEDCOM (.ged) files into D3 capable JSON. This package is specifically designed to prepare data for use in https://github.com/mister-blanket/kin-cloud

626 lines (531 loc) 15.3 kB
const d3ize = tree => { const notes = tree.filter(hasTag('NOTE')); let surnameList = [] const peopleNodes = tree .filter(hasTag('INDI')) .map(p => ( toNode(p, notes, surnameList ))); const families = tree.filter(hasTag('FAM')); const links = families.reduce((memo, family) => { return memo.concat(familyLinks(family, peopleNodes)); }, []); assignFy(peopleNodes, links); return { nodes: peopleNodes, links: links, families: families, surnameList: surnameList }; } // Tag search function const hasTag = val => { return node => { return node.tag === val; }; } // Data search function const hasData = val => { return node => { return node.data === val; }; } // ID search function const hasID = val => { return node => { return node.id === val; }; } const assignFy = (peopleNodes, links) => { // YOB known let yesyob = peopleNodes.filter(p => { return p.yob !== '?' && !isNaN(+p.yob); }) yesyob.forEach(p => p.fy = +(p.yob)); // YOB unknown let noyob = peopleNodes.filter(p => { return p.yob === '?'; }); let count = 10; // Cycle through list, adding fy until all complete while (noyob.length > 0 && count > 0) { let tempnoyob = noyob.slice(); tempnoyob.forEach((p, index) => { // Build array of family let tpFamily = []; links.forEach(link => { if (link.source == p.id) { tpFamily.push({pRole: 'source', pType: link.sourceType, other: link.target, oType: link.targetType}); } else if (link.target == p.id) { tpFamily.push({pRole: 'target', pType: link.targetType, other: link.source, oType: link.sourceType}); }; }); // Check family for YOB tpFamily.forEach(member => { // USE SOME() INSTEAD OF FOREACH!!! peopleNodes.forEach(person => { // USE SOME() INSTEAD OF FOREACH!!! if (person.id == member.other && person.fy !== undefined) { // Person is source if (member.pRole === 'source') { // Person is husband if (member.pType === 'HUSB' && member.oType === 'WIFE') { p.fy = +person.fy - 3; // Person is father } else if (member.pType === 'HUSB' && member.oType === 'CHIL') { p.fy = +person.fy - 30; // Person is mother } else if (member.pType === 'WIFE') { p.fy = +person.fy - 27; } // Person is target } else if (member.pRole === 'target') { // Person is wife if (member.pType === 'WIFE' && member.oType === 'HUSB') { p.fy = +person.fy + 3; // Person is child of father } else if (member.pType === 'CHIL' && member.oType === 'HUSB') { p.fy = +person.fy + 30; // Person is child of mother } else if (member.pType === 'CHIL' && member.oType === 'WIFE') { p.fy = +person.fy + 27; } } } }); }); if (p.fy !== undefined) { noyob.splice(index,index +1); } }) count -= 1; } const convertFy = (peopleNodes) => { const fyRatio = peopleNodes => { if (peopleNodes.length <= 50) { return 3; } else if (peopleNodes.length > 50 && peopleNodes.length <= 150) { return 4; } else if (peopleNodes.length > 150 && peopleNodes.length <= 250) { return 5; } else if (peopleNodes.length > 250){ return 6; } }; let allFy = []; peopleNodes.forEach(p => { if (p.fy !== undefined) { p.fy = p.fy * fyRatio(peopleNodes); allFy.push(p.fy); } }) let total = 0; allFy.forEach(fy => total += fy); let average = total/allFy.length; peopleNodes.forEach(p => { if (p.fy !== undefined) { p.fy = -(p.fy - average); } }); } convertFy(peopleNodes); } // Get title const getTitle = p => { const title = (p.tree.filter(hasTag('TITL')) || []); if (title.length > 0) { return title[title.length -1].data; } } // Get full name const getName = p => { let nameNode = (p.tree.filter(hasTag('NAME')) || [])[0]; if (nameNode) { return nameNode.data.replace(/\//g, ''); } else { return '?'; } } // Get first name const getFirstName = p => { // Find 'NAME' tag const nameNode = (p.tree.filter(hasTag('NAME')) || [])[0]; if (nameNode) { // Find 'GIVN' tag let firstNameNode = (nameNode.tree.filter(hasTag('GIVN')) || [])[0]; if (firstNameNode) { // Remove middle name if (firstNameNode.data.search(' ') !== -1) { return firstNameNode.data.slice(0, firstNameNode.data.search(' ')); } else { return firstNameNode.data; } } else { return '?'; } } else { return '?'; } } // Get surname const getSurname = p => { // Find 'NAME' tag const nameNode = (p.tree.filter(hasTag('NAME')) || [])[0]; if (nameNode) { // Find 'SURN' tag const surnameNode = (nameNode.tree.filter(hasTag('SURN')) || [])[0]; // If surname listed if (surnameNode) { // Remove alternate surnames if (surnameNode.data.search(',') !== -1) { return surnameNode.data.slice(0, surnameNode.data.search(',')); } else { return surnameNode.data; } // Derive surname from name } else { nameArr = nameNode.data.split(' '); // Look for forward slashes let isSlashes = nameArr.some(str => str[0] === "/"); if (isSlashes) { return nameArr.find(str => str[0] === "/").replace(/\//g, ''); // no slashes, use final item in array } else { nameArr[nameArr.length -1] = nameArr[nameArr.length -1].replace(/\//g, '') return nameArr.length > 1 ? nameArr[nameArr.length -1] : "Hrm" } } } else { return '?'; } } // Get gender const getGender = p => { // Find 'SEX' tag let genderNode = (p.tree.filter(hasTag('SEX')) || [])[0]; if (genderNode) { return genderNode.data; } else { return 'Unknown'; } } // Get date of birth const getDOB = p => { // Find 'BIRT' tag let dobNode = (p.tree.filter(hasTag('BIRT')) || [])[0]; if (dobNode) { // Find 'DATE' tag let dateNode = (dobNode.tree.filter(hasTag('DATE')) || [])[0]; if (dateNode) { return dateNode.data; } else { return '?'; } } else { return '?'; } } // Get year of birth const getYOB = p => { // Find 'BIRT' tag let dobNode = (p.tree.filter(hasTag('BIRT')) || [])[0]; if (dobNode) { // Find 'DATE' tag let dateNode = (dobNode.tree.filter(hasTag('DATE')) || [])[0]; if (dateNode) { return dateNode.data.slice(-4); } else { return '?'; } } else { return '?'; } } // Get place of birth const getPOB = p => { // Find 'BIRT' tag let pobNode = (p.tree.filter(hasTag('BIRT')) || [])[0]; if (pobNode) { // Find 'DATE' tag let placeNode = (pobNode.tree.filter(hasTag('PLAC')) || [])[0]; if (placeNode) { return placeNode.data; } else { return ''; } } else { return ''; } } // Get date of death const getDOD = p => { // Find 'DEAT' tag let dobNode = (p.tree.filter(hasTag('BIRT')) || [])[0]; let dodNode = (p.tree.filter(hasTag('DEAT')) || [])[0]; if (dodNode) { // Find 'DATE' tag let dateNode = (dodNode.tree.filter(hasTag('DATE')) || [])[0]; if (dateNode) { return dateNode.data; } else { return '?'; } } else if (dobNode) { let dateNode = (dobNode.tree.filter(hasTag('DATE')) || [])[0]; if (dateNode) { return dateNode.data.slice(-4) + 100; } else { return '?'; } } else { return 'Present'; } } // Get year of death const getYOD = p => { let thisYear = new Date().getFullYear(); // Find 'DEAT' tag let dobNode = (p.tree.filter(hasTag('BIRT')) || [])[0]; let dodNode = (p.tree.filter(hasTag('DEAT')) || [])[0]; // If DEATH tag if (dodNode) { // Find 'DATE' tag let dateNode = (dodNode.tree.filter(hasTag('DATE')) || [])[0]; // If death date listed if (dateNode) { return dateNode.data.slice(-4); } else { return '?'; } // BIRT tag, but no DEAT tag } else if (dobNode && !dodNode) { let dateNode = (dobNode.tree.filter(hasTag('DATE')) || [])[0]; // If DOB listed if (dateNode) { // If born > 100 yrs ago, call dead if (dateNode.data.slice(-4) < (thisYear - 100)) { return '?'; } else { return 'Present'; } } else { return '?'; } // no DEAT or BIRT tag } else { return '?'; } } // Get place of birth const getPOD = p => { // Find 'BIRT' tag let podNode = (p.tree.filter(hasTag('DEAT')) || [])[0]; if (podNode) { // Find 'DATE' tag let placeNode = (podNode.tree.filter(hasTag('PLAC')) || [])[0]; if (placeNode) { return placeNode.data; } else { return ''; } } else { return ''; } } // Get relatives const getFamilies = p => { let families = []; let pediInfo; // If child let familyNode1 = (p.tree.filter(hasTag('FAMC')) || []); if (familyNode1) { for (let i = 0; i < familyNode1.length; i++) { if (familyNode1[i].tree.length > 0) { // Get pedigree info if (familyNode1[i].tree[0].tag == 'PEDI') { pediInfo = {frel: familyNode1[i].tree[0].data, mrel: familyNode1[i].tree[0].data} } else if (familyNode1[i].tree[0].tag == '_FREL') { pediInfo = {frel: familyNode1[i].tree[0].data, mrel: familyNode1[i].tree[1].data} } } families.push({id: familyNode1[i].data, pedi: pediInfo}); } } let familyNode2 = (p.tree.filter(hasTag('FAMS')) || []); if (familyNode2) { for (let i = 0; i < familyNode2.length; i++) { families.push({id:familyNode2[i].data}); } } return families; } // Get color const getColor = (p, surnameList) => { const colorList = [ '#ff7f50', // coral '#00b4ff', // sky blue '#fac641', // mexican egg yolk '#8a9b0f', // olive '#a7dbd8', // sea foam '#a37e58', // light brown '#ec4913', // burnt orange '#a27dbd', // soft royal purple '#11644d', // forest '#b3347c', // magenta '#359668', // grass & sage '#fab8b4', // soft pink '#6de627', // neon green '#ecd078', // tangerine '#bfcff7', // ligt purple blue '#e08e79', // blush '#c44d58', // rouge '#c4ffeb', // light sea foam '#a6b890', // olive sage '#aaaaaa', // light blue grey '#ffd3b5', // peach '#826942', // chocolate '#d4ee5e', // lime '#ecfc85', // light yellow '#666666', // off white '#ffa1c3', // newborn pink '#6541a3', // royal purple '#75616b', // dry wine '#71cfde', // baby foam '#e0e0e0', // light grey ]; // If color description listed in GEDCOM const dscr = (p.tree.filter(hasTag('DSCR')) || [])[0]; const foundName = surnameList.find(sName => sName.surname === p.surname); // If surname already in list if (foundName) { foundName.count = foundName.count +1; } else { surnameList.push({ surname: p.surname, count: 1, color: colorList[surnameList.length % colorList.length] }) } // surnameList.color = surnameList.length % colorList.length}); // If color listed assign that if (dscr) { return dscr.data; // else assign color from colorList } else { return surnameList.find(sName => sName.surname === p.surname).color; } } // Get person notes const getNotes = p => { return p.tree.filter(hasTag('NOTE')); } // Get Bio const getBio = (p, notes) => { if (p.notes.length != 0) { let bio = ''; // Notes for person p.notes.forEach(personNote => { // personNote.data points to NOTE object if (notes.length > 0) { notes.forEach(note => { if (personNote.data === note.pointer) { bio += note.data; // Concat broken up note if (note.tree.length > 0) { note.tree.forEach(fragment => bio += fragment.data) } } }); // personNote.data is actual note } else { bio += personNote.data; } }); return bio; } } const getFy = p => { if(p.yob === '?') { return 0; } else { return +(-p.yob * 3 + 6000); } } const toNode = (p, notes, surnameList) => { p.id = p.pointer; p.title = getTitle(p); p.name = getName(p); p.firstName = getFirstName(p); p.surname = getSurname(p); p.gender = getGender(p); p.dob = getDOB(p); p.yob = getYOB(p); p.pob = getPOB(p); p.dod = getDOD(p); p.yod = getYOD(p); p.pod = getPOD(p); p.families = getFamilies(p); p.color = getColor(p, surnameList); p.notes = getNotes(p); p.bio = getBio(p, notes); return p; } const familyLinks = (family, peopleNodes) => { let memberLinks = []; let maritalStatus = null; let pedigree; // Filter only individual objects from family tree let memberSet = family.tree.filter(function(member) { return member.tag && (member.tag === 'HUSB' || member.tag === 'WIFE' || member.tag === 'CHIL'); }) // Filter marital status events family.tree.filter(event => { if (event.tag === 'DIV' || event.tag === 'MARR') { if (maritalStatus !== 'DIV') { maritalStatus = event.tag; } } }) // Iterate over each member of set to connect with other members while (memberSet.length > 1) { for (let i = 1; i < memberSet.length; i++) { // Exclude sibling relationships if (memberSet[0].tag != 'CHIL') { // If marital status listed if (memberSet[0].tag == 'HUSB' && memberSet[i].tag == 'WIFE') { memberLinks.push({ "source": memberSet[0].data, "target": memberSet[i].data, "sourceType": memberSet[0].tag, "targetType": memberSet[i].tag, "type": maritalStatus }) } else { // Filter pedigree info function getPedigree(personID, parentType, relInfo) { // GRAMPS let person = peopleNodes.filter(hasID(personID)); let personFamily = person[0].families.filter(hasID(family.pointer)); if (parentType == 'HUSB') { if (personFamily[0].pedi) { return personFamily[0].pedi.frel; } else if (relInfo.some(parent => parent.tag === "_FREL")) { return relInfo.find(parent => parent.tag === "_FREL").data; } } else { if (personFamily[0].pedi) { return personFamily[0].pedi.mrel; } else if (relInfo.some(parent => parent.tag === "_MREL")) { return relInfo.find(parent => parent.tag === "_MREL").data; } } } memberLinks.push({ "source": memberSet[0].data, "target": memberSet[i].data, "sourceType": memberSet[0].tag, "targetType": memberSet[i].tag, "type": getPedigree(memberSet[i].data, memberSet[0].tag, memberSet[i].tree) }) } } } memberSet.splice(0,1); } return memberLinks; } module.exports = d3ize;