Skip to content

Instantly share code, notes, and snippets.

@nigjo
Last active June 27, 2024 19:11
Show Gist options
  • Save nigjo/17281825e370cc8ea8916ef88c9e9c9e to your computer and use it in GitHub Desktop.
Save nigjo/17281825e370cc8ea8916ef88c9e9c9e to your computer and use it in GitHub Desktop.
A Simple reader od gedcom data files in JavaScript
/*
* 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