Last active
June 27, 2024 19:11
-
-
Save nigjo/17281825e370cc8ea8916ef88c9e9c9e to your computer and use it in GitHub Desktop.
A Simple reader od gedcom data files in JavaScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2020 nigjo. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
* Original Gist: | |
* https://gist.github.com/nigjo/17281825e370cc8ea8916ef88c9e9c9e | |
*/ | |
'use strict'; | |
class Record | |
{ | |
/**@type int*/ level; | |
/**@type String*/ id; | |
/**@type String*/ tag; | |
/**@type String*/ value; | |
/** | |
* @type Record | |
*/ | |
parent; | |
/** | |
* Subrecords. | |
* @type Array | |
*/ | |
children; | |
/** | |
* | |
* @param {Record} other | |
* @returns {Record} | |
*/ | |
constructor(other) { | |
if (other) { | |
this.level = other.level; | |
this.id = other.id; | |
this.tag = other.tag; | |
this.value = other.value; | |
} | |
} | |
setLevel(level) { | |
this.level = level; | |
return this; | |
} | |
setId(id) { | |
this.id = id; | |
return this; | |
} | |
setTag(tag) { | |
this.tag = tag; | |
return this; | |
} | |
setValue(value) { | |
this.value = value; | |
return this; | |
} | |
toGedString() { | |
return ( | |
this.level | |
+ (this.id ? (' ' + this.id) : '') | |
+ ' ' + this.tag | |
+ (this.value ? (' ' + this.value) : '') | |
); | |
} | |
toLocaleString() { | |
return this.value ? this.value : ''; | |
} | |
/** | |
* add a subrecord to this record. | |
* | |
* @param {Record} child | |
* @returns {Record} this | |
*/ | |
add(child) | |
{ | |
if (!this.children) | |
{ | |
this.children = new Array(); | |
} | |
this.children.push(child); | |
child.parent = this; | |
return this; | |
} | |
/** | |
* | |
* @returns {Gedcom} | |
*/ | |
findGedcom() { | |
if (typeof this.context !== 'undefined') { | |
return this.context; | |
} else if (this.parent) | |
return this.parent.findGedcom(); | |
else { | |
return false; | |
} | |
} | |
/** | |
* @param {string} subrectype | |
* @returns {Record[]} | |
*/ | |
getSubRecords(subrectype) { | |
var result = new Array(); | |
if (this.children) { | |
for (var rec of this.children) { | |
if (rec.tag === subrectype) | |
result.push(rec); | |
} | |
} | |
return result; | |
} | |
/** | |
* @param {string} subrectype record type | |
* @returns {Record|false} record or false | |
*/ | |
getFirstSubRecord(subrectype) { | |
if (this.children) { | |
for (var rec of this.children) { | |
if (rec.tag === subrectype) { | |
return rec; | |
} | |
} | |
} | |
return false; | |
} | |
getPath(subrecPath) { | |
var subrec = this.getFirstSubRecord(subrecPath[0]); | |
if (subrec) { | |
if (subrecPath.length <= 1) { | |
return subrec; | |
} | |
if (!subrec.value || subrec.value[0] !== '@') { | |
return subrec.getPath(subrecPath.slice(1)); | |
} | |
var ref = this.findGedcom().getReference(subrec.value); | |
if (ref) { | |
return ref.getPath(subrecPath.slice(1)); | |
} | |
} | |
return false; | |
} | |
/** | |
* Find an Individual via a ID-reference of a subrecord. | |
* | |
* @param {String} subrectype | |
* @returns {Individual} the Individual or false if not found | |
*/ | |
getReferencedIndividual(subrectype) { | |
var subrec = this.getFirstSubRecord(subrectype); | |
if (subrec !== false) | |
return this.findGedcom().getIndividual(subrec.toLocaleString()); | |
return false; | |
} | |
getReferencedFamily(subrectype) { | |
var subrec = this.getFirstSubRecord(subrectype); | |
if (subrec !== false) | |
return this.findGedcom().getFamily(subrec.toLocaleString()); | |
return false; | |
} | |
getSubValue(subrectype, defaultvalue) { | |
var subrec = this.getFirstSubRecord(subrectype); | |
if (subrec !== false) | |
return subrec.toLocaleString(); | |
return defaultvalue; | |
} | |
} | |
class Individual extends Record { | |
constructor(other) { | |
super(other); | |
} | |
getIndiName() { | |
var name = this.getFirstSubRecord('NAME'); | |
if (name) { | |
return name.toLocaleString(); | |
} | |
return ''; | |
} | |
getParentFamily() { | |
return super.getReferencedFamily('FAMC'); | |
} | |
getFamily() { | |
return super.getReferencedFamily('FAMS'); | |
} | |
getFamilies() { | |
let famRecs = this.getSubRecords("FAMS"); | |
let results = new Array(); | |
for (let famRec of famRecs) { | |
let fam = this.findGedcom().getFamily(famRec.toLocaleString()); | |
if (fam) { | |
results.push(fam); | |
} | |
} | |
return results; | |
} | |
} | |
class Family extends Record { | |
constructor(other) { | |
super(other); | |
} | |
/** | |
* @returns {Individual} | |
*/ | |
getHusband() { | |
return this.getReferencedIndividual('HUSB'); | |
} | |
/** | |
* @returns {Individual} | |
*/ | |
getWife() { | |
return this.getReferencedIndividual('WIFE'); | |
} | |
getChildren() { | |
var childrefs = this.getSubRecords('CHIL'); | |
var children = new Array(); | |
var gedcom = this.findGedcom(); | |
for (var ref of childrefs) { | |
var child = gedcom.getIndividual(ref.toLocaleString()); | |
if (child !== false) { | |
children.push(child); | |
} | |
} | |
return children; | |
} | |
} | |
; | |
class GedDate extends Record { | |
static DATE_PATTERN = | |
/((?<mod>BEF|AFT|ABT|EST) )?((?<day>\d+) )?((?<month>[A-Z]{3}) ?)?(?<year>\d+)?/; | |
static MONTHS = { | |
JAN: 'Jan', FEB: "Feb", MAR: 'Mär', APR: 'Apr', MAY: 'Mai', JUN: 'Jun', | |
JUL: 'Jul', AUG: 'Aug', SEP: 'Sep', OCT: 'Okt', NOV: 'Nov', DEC: 'Dez'}; | |
static MODIFIERS = { | |
BEF: 'vor', AFT: 'nach', | |
ABT: 'um', EST: 'ungefähr' | |
} | |
constructor(other) { | |
super(other); | |
this.info = null; | |
} | |
/** | |
* @returns {Date|false} | |
*/ | |
getDayMonth() { | |
let info = this.getInfo(); | |
if (info | |
&& info.groups.day | |
&& info.groups.month) { | |
let date = new Date(); | |
date.setDate(parseInt(info.groups.day)); | |
date.setMonth(Object.keys(GedDate.MONTHS).indexOf(info.groups.month)); | |
date.setHours(12); | |
date.setMinutes(0); | |
date.setSeconds(0); | |
return date; | |
} | |
return false; | |
} | |
getInfo() { | |
if (this.info === null) { | |
this.info = false; | |
var val = super.toLocaleString(); | |
if (val.length > 0) { | |
this.info = val.match(GedDate.DATE_PATTERN); | |
} | |
} | |
return this.info; | |
} | |
/** | |
* @returns {integer|false} | |
*/ | |
getYear() { | |
let info = this.getInfo(); | |
if (info && info.groups.year) { | |
return info.groups.year; | |
} | |
return false; | |
} | |
toLocaleString() { | |
var val = super.toLocaleString(); | |
if (val.length > 0) { | |
let info = val.match(GedDate.DATE_PATTERN); | |
if (info) { | |
var result = ''; | |
if (info.groups.mod) { | |
result += GedDate.MODIFIERS[info.groups.mod] + ' '; | |
} | |
if (info.groups.day) { | |
result += info.groups.day + '.'; | |
} | |
if (info.groups.month) { | |
result += ' ' + GedDate.MONTHS[info.groups.month]; | |
} | |
if (info.groups.year) { | |
result += ' ' + info.groups.year; | |
} | |
return result.trim(); | |
} | |
} | |
return val; | |
} | |
} | |
; | |
/** | |
* Definiert eine Gedcom-Datei. | |
* | |
* @type Gedcom | |
*/ | |
class Gedcom | |
{ | |
/** | |
* The root record a level -1. | |
* | |
* @type Record | |
*/ | |
root; | |
constructor() { | |
this.root = new Record(); | |
this.root.level = -1; | |
this.root.tag = "gedcom"; | |
this.root.context = this; | |
} | |
/** | |
* @returns {Family[]} | |
*/ | |
getFamilies() { | |
return this.root.getSubRecords('FAM'); | |
} | |
/** | |
* @returns {Individual[]} | |
*/ | |
getIndividuals() { | |
return this.root.getSubRecords('INDI'); | |
} | |
getReference(refid) { | |
if (this.root.children) { | |
for (var rec of this.root.children) { | |
if (rec.id === refid) { | |
return rec; | |
} | |
} | |
} | |
return false; | |
} | |
/** | |
* find a family in file by Id. | |
* | |
* @param {String} famid | |
* @returns {Family} the found Item or false. | |
*/ | |
getFamily(famid) { | |
var rec = this.getReference(famid); | |
if (rec instanceof Family) | |
return rec; | |
return false; | |
} | |
/** | |
* | |
* @param {String} indiid | |
* @returns {Individual} | |
*/ | |
getIndividual(indiid) { | |
var rec = this.getReference(indiid); | |
if (rec instanceof Individual) | |
return rec; | |
return false; | |
} | |
static parseRec(line) { | |
/*@type int*/var level = parseInt(line.substring(0, line.indexOf(' '))); | |
/*@type String*/var rest = line.substring(line.indexOf(' ') + 1); | |
/*@type Item*/var next = new Record(); | |
next.level = level; | |
if (rest[0] === '@') | |
{ | |
next.id = rest.substring(0, rest.indexOf(' ')); | |
rest = rest.substring(next.id.length + 1); | |
} | |
if (rest.indexOf(' ') > 0) | |
{ | |
next.tag = rest.substring(0, rest.indexOf(' ')); | |
next.value = rest.substring(next.tag.length + 1); | |
} else | |
{ | |
next.tag = rest; | |
} | |
switch (next.tag) { | |
case 'FAM': | |
next = new Family(next); | |
break; | |
case 'INDI': | |
next = new Individual(next); | |
break; | |
case 'DATE': | |
next = new GedDate(next); | |
break; | |
} | |
return next; | |
} | |
/** | |
* Laed eine Gedcomdatei ein | |
* | |
* @param {String} infile Dateiinhalt als Text. | |
* @returns {Record} | |
*/ | |
load(infile) /*throws IOException*/ | |
{ | |
Gedcom.loadRecords(infile, rec => this.root.add(rec)); | |
return this.root; | |
} | |
/** | |
* Imports a GEDCOM file or records of a file. | |
* | |
* @param {type} infile filecontent as a string. The data must start with | |
* a level 0 "data line". | |
* @param {type} addRecord callback for adding a record. This is called every | |
* time a full record is found. This is a new level 0 line is found or the end | |
* of the filecontent is reached. | |
*/ | |
static loadRecords(infile, addRecord) { | |
var baseRecord = undefined; | |
for (let line of infile.split(/[\r\n]+/)) { | |
let next = Gedcom.parseRec(line); | |
if (next.level === 0) { | |
if (baseRecord) { | |
addRecord(baseRecord); | |
} | |
baseRecord = next; | |
var current = baseRecord; | |
} else { | |
if (next.level > current.level) { | |
current.add(next); | |
} else { | |
while (next.level <= current.level) | |
current = current.parent; | |
current.add(next); | |
} | |
current = next; | |
} | |
} | |
if (baseRecord) { | |
addRecord(baseRecord); | |
} | |
} | |
static printRec(rec) { | |
var content = rec.toGedString() + '\n'; | |
if (rec.children) { | |
for (var subrec of rec.children) { | |
content += Gedcom.printRec(subrec); | |
} | |
} | |
return content; | |
} | |
print() { | |
var content = ''; | |
for (var rec of this.root.children) { | |
content += Gedcom.printRec(rec); | |
} | |
return content; | |
} | |
} | |
//remove this line if you do not use JS-modules | |
export {Gedcom, Record, Family, Individual, GedDate}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment