Commit Diff


commit - 186e25e363cc5141e5c4de336651c65de5286f14
commit + 81f143c77add0d9e9070d0b01a6d7b9a41f1b782
blob - 1d20566508158f85104851cd60641ac9b54823d6
blob + 75118586eeef575a0d9faf7551983150cc74e480
--- README.md
+++ README.md
@@ -6,12 +6,12 @@ Status](https://travis-ci.org/ligurio/gedcom-tools.svg
 Gedcom is a format for storing genealogical information designed by The Church
 of Jesus Christ of Latter-Day Saints (http://www.lds.org).
 
-## `gedcom2sql.go`
+### Импорт файла GEDCOM в SQLite
 
 This module allows to converting data in GEDCOM format to SQLite DB.
 
 ```sql
-$ gedcom2sql -file sergeyb.gedcom
+$ gedcom -file sergeyb.gedcom
 $ sqlite3 sergeyb.sqlite
 SQLite version 3.20.1 2017-08-24 16:21:36
 Enter ".help" for usage hints.
@@ -34,12 +34,10 @@ sqlite> -- "Total number of birth dates:"
 sqlite> SELECT COUNT(*) FROM person_st WHERE birt_date IS NOT "";
 ```
 
-## `gedcom2errors.go`
+### Проверка файла GEDCOM на ошибки.
 
-Проверка файла GEDCOM на ошибки.
-
 ```
-$ gedcom2errors -file samples/bronte.ged
+$ gedcom -file samples/bronte.ged
 
 Person (I0003) Maria /Brontë/
 EI112: person has no family pointers
@@ -64,18 +62,30 @@ Family (F003)
 EP106: child doesn't inherit father's surname
 ```
 
-## `vk2gedcom.go`
+### Родословная из социальной сети
 
 Приложение строит генеалогическое дерево на основании данных из социальных
 сетей. Пока реализована поддержка только для ВКонтакте.
 
-## `gedcom2timenet.go`
+Facebook:
 
-Визуализация GEDCOM.
+- https://developers.facebook.com/docs/graph-api/reference
+- [Заполнить](https://www.facebook.com/help/1557948767777120): Профиль -> Информация -> Семейные отношения
 
-## `pedigree.ipynb`
+VK API:
 
-Исследование родословной с помощью Jupiter Notebook.
+- Методы: [account.getProfileInfo](https://vk.com/dev/account.getProfileInfo), [users.get](https://vk.com/dev/users.get)
+- Частотные ограничения: "К методам API ВКонтакте можно обращаться не чаще 3 раз в секунду."
+- Ошибки: https://vk.com/dev/errors
+- [Приложение](https://vk.com/editapp?id=6130272&section=options)
+- Аккаунты для отладки:
+  - [id181554010](https://vk.com/id181554010)
+  - [id144273654](https://vk.com/id144273654)
+  - [id233293686](https://vk.com/id233293686)
+  - [denis.ivanov1988](https://vk.com/denis.ivanov1988)
+  - [am.ivanov](https://vk.com/am.ivanov)
+  - [id140022470](https://vk.com/id140022470)
+  - [id1107203](https://vk.com/id1107203)
 
 ## Полезные инструменты
 
blob - 48961e042a749f2dc3a48dcb24f31e1b52d861ec (mode 644)
blob + /dev/null
--- book.py
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/local/bin/env python
-
-# - https://imgur.com/a/VMHPdEr
-# - https://doasimdoingblog.com/category/family-history-binder/
-# - https://bencrowder.net/blog/2014/experimental-family-pedigree/
-# - https://bencrowder.net/blog/2014/experimental-pedigree-chart/
-# - https://bencrowder.net/blog/2011/family-group-record-redesigns/
blob - 6f8effcc98ba58c6640857d2db328b188b2f4ce2 (mode 644)
blob + /dev/null
--- cmd/gedcom2errors/gedcom2errors.go
+++ /dev/null
@@ -1,735 +0,0 @@
-package main
-
-import (
-	"bytes"
-	"flag"
-	"fmt"
-	"github.com/iand/gedcom"
-	"io/ioutil"
-	"os"
-	"regexp"
-	"strings"
-	"time"
-)
-
-const (
-	OLDAGE = 99
-	WEDDER = 3
-	MAYDEC = 5
-	YNGMAR = 20
-	OLDMAR = 80
-	LNGWDW = 80
-	OLDUNM = 67
-	FECMOM = 45
-	OLDMOM = 55
-	YNGMOM = 16
-	CSPACE = 1
-	CBSPAN = 1
-)
-
-type fn_person func(person *gedcom.IndividualRecord) bool
-type fn_family func(family *gedcom.FamilyRecord) bool
-
-func rule_I100(person *gedcom.IndividualRecord) bool {
-	var birt_date, deat_date time.Time
-	birt_date = eventDate(person, "BIRT")
-	if birt_date.IsZero() {
-		return true
-	}
-	deat_date = eventDate(person, "DEAT")
-	if deat_date.IsZero() {
-		return true
-	}
-	if deat_date.Sub(birt_date) > OLDAGE {
-		fmt.Println(deat_date, birt_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I101(person *gedcom.IndividualRecord) bool {
-	var birt_date, bap_date time.Time
-	birt_date = eventDate(person, "BIRT")
-	if birt_date.IsZero() {
-		return true
-	}
-	bap_date = eventDate(person, "BAP")
-	if bap_date.IsZero() {
-		return true
-	}
-	if bap_date.Sub(birt_date) > 0 {
-		fmt.Println(birt_date, bap_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I102(person *gedcom.IndividualRecord) bool {
-	var birt_date, deat_date time.Time
-	birt_date = eventDate(person, "BIRT")
-	if birt_date.IsZero() {
-		return true
-	}
-	deat_date = eventDate(person, "DEAT")
-	if deat_date.IsZero() {
-		return true
-	}
-	if deat_date.Sub(birt_date) < 0 {
-		fmt.Println(birt_date, deat_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I103(person *gedcom.IndividualRecord) bool {
-	var deat_date, buri_date time.Time
-	deat_date = eventDate(person, "DEAT")
-	if deat_date.IsZero() {
-		return true
-	}
-	buri_date = eventDate(person, "BURI")
-	if buri_date.IsZero() {
-		return true
-	}
-	if deat_date.Sub(buri_date) < 0 {
-		fmt.Println(deat_date, buri_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I104(person *gedcom.IndividualRecord) bool {
-	var deat_date, bap_date time.Time
-	deat_date = eventDate(person, "DEAT")
-	if deat_date.IsZero() {
-		return true
-	}
-	bap_date = eventDate(person, "BAP")
-	if bap_date.IsZero() {
-		return true
-	}
-	if deat_date.Sub(bap_date) < 0 {
-		fmt.Println(deat_date, bap_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I105(person *gedcom.IndividualRecord) bool {
-	var bap_date, buri_date time.Time
-	bap_date = eventDate(person, "BAP")
-	if bap_date.IsZero() {
-		return true
-	}
-	buri_date = eventDate(person, "BURI")
-	if buri_date.IsZero() {
-		return true
-	}
-	if bap_date.Sub(buri_date) < 0 {
-		fmt.Println(buri_date, bap_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I106(person *gedcom.IndividualRecord) bool {
-	var buri_date, deat_date time.Time
-	deat_date = eventDate(person, "DEAT")
-	if deat_date.IsZero() {
-		return true
-	}
-	buri_date = eventDate(person, "BURI")
-	if buri_date.IsZero() {
-		return true
-	}
-	if deat_date.Sub(buri_date) > 0 {
-		fmt.Println(buri_date, deat_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I107(person *gedcom.IndividualRecord) bool {
-	var bap_date, birt_date time.Time
-	bap_date = eventDate(person, "BAP")
-	if bap_date.IsZero() {
-		return true
-	}
-	birt_date = eventDate(person, "BIRT")
-	if birt_date.IsZero() {
-		return true
-	}
-	if bap_date.Sub(birt_date) >= 1 {
-		fmt.Println(bap_date, birt_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I108(person *gedcom.IndividualRecord) bool {
-	var deat_date, buri_date time.Time
-	deat_date = eventDate(person, "DEAT")
-	if deat_date.IsZero() {
-		return true
-	}
-	buri_date = eventDate(person, "BURI")
-	if buri_date.IsZero() {
-		return true
-	}
-	if deat_date.Sub(buri_date) >= 1 {
-		fmt.Println(deat_date, buri_date)
-		return false
-	}
-
-	return true
-}
-
-func rule_I109(person *gedcom.IndividualRecord) bool {
-	if person.Sex == "" {
-		return false
-	}
-
-	return true
-}
-
-func rule_I110(person *gedcom.IndividualRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_I111(person *gedcom.IndividualRecord) bool {
-	if len(person.Parents) > 1 {
-		return false
-	}
-
-	return true
-}
-
-func rule_I112(person *gedcom.IndividualRecord) bool {
-	if len(person.Family) == 0 {
-		return false
-	}
-
-	return true
-}
-
-func rule_F100(family *gedcom.FamilyRecord) bool {
-	if family.Husband == nil {
-		return false
-	}
-	if family.Wife == nil {
-		return false
-	}
-	if len(family.Child) == 0 {
-		return false
-	}
-
-	return true
-}
-
-func rule_F101(family *gedcom.FamilyRecord) bool {
-	if family.Husband == nil {
-		return false
-	}
-	if family.Wife == nil {
-		return false
-	}
-
-	return true
-}
-
-func rule_F102(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_F103(family *gedcom.FamilyRecord) bool {
-	if family.Husband == nil {
-		return false
-	}
-
-	return true
-}
-
-func rule_F104(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_F105(family *gedcom.FamilyRecord) bool {
-	if family.Wife == nil {
-		return false
-	}
-
-	return true
-}
-
-func rule_F106(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_F107(family *gedcom.FamilyRecord) bool {
-	if len(family.Child) == 0 {
-		return false
-	}
-
-	return true
-}
-
-func rule_F108(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_F109(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_F110(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M100(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M101(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M102(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M103(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M104(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M105(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M106(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M107(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M108(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M109(family *gedcom.FamilyRecord) bool {
-	if family.Husband.Sex == family.Wife.Sex {
-		return false
-	}
-
-	return true
-}
-
-func rule_M110(family *gedcom.FamilyRecord) bool {
-	if family.Husband.Sex == "F" {
-		return false
-	}
-
-	return true
-}
-
-func rule_M111(family *gedcom.FamilyRecord) bool {
-	if family.Wife.Sex == "M" {
-		return false
-	}
-
-	return true
-}
-
-func rule_M112(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M113(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M114(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_M115(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_P100(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_P101(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_P102(family *gedcom.FamilyRecord) bool {
-	var mother_birt_date, child_birt_date time.Time
-	mother_birt_date = eventDate(family.Wife, "BIRT")
-	for _, child := range family.Child {
-		child_birt_date = eventDate(child, "BIRT")
-		if mother_birt_date.Sub(child_birt_date) < 0 {
-			return false
-		}
-	}
-
-	return true
-}
-
-func rule_P103(family *gedcom.FamilyRecord) bool {
-	var mother_birt_date, child_birt_date time.Time
-	mother_birt_date = eventDate(family.Wife, "BIRT")
-	if mother_birt_date.IsZero() {
-		return true
-	}
-	for _, child := range family.Child {
-		child_birt_date = eventDate(child, "BIRT")
-		if child_birt_date.IsZero() {
-			continue
-		}
-		if mother_birt_date.Sub(child_birt_date) < YNGMOM {
-			fmt.Println(mother_birt_date, child_birt_date)
-			return false
-		}
-	}
-
-	return true
-}
-
-func rule_P104(family *gedcom.FamilyRecord) bool {
-	var mother_deat_date, child_birt_date time.Time
-	mother_deat_date = eventDate(family.Wife, "DEAT")
-	if mother_deat_date.IsZero() {
-		return true
-	}
-	for _, child := range family.Child {
-		child_birt_date = eventDate(child, "BIRT")
-		if child_birt_date.IsZero() {
-			continue
-		}
-		if mother_deat_date == child_birt_date {
-			fmt.Println(mother_deat_date, child_birt_date)
-			return false
-		}
-	}
-
-	return true
-}
-
-func rule_P105(family *gedcom.FamilyRecord) bool {
-	var father_deat_date, child_birt_date time.Time
-	father_deat_date = eventDate(family.Husband, "DEAT")
-	if father_deat_date.IsZero() {
-		return true
-	}
-	for _, child := range family.Child {
-		child_birt_date = eventDate(child, "BIRT")
-		if child_birt_date.IsZero() {
-			continue
-		}
-		if father_deat_date == child_birt_date {
-			fmt.Println(father_deat_date, child_birt_date)
-			return false
-		}
-	}
-
-	return true
-}
-
-func rule_P106(family *gedcom.FamilyRecord) bool {
-	var father_surname []string
-	var child_surname []string
-	surname_re := regexp.MustCompile(`.+ /(.+)/`)
-	for _, name := range family.Husband.Name {
-		surname := surname_re.FindStringSubmatch(name.Name)
-		if surname[1] != "" {
-			father_surname = append(father_surname, surname[1])
-		}
-	}
-	for _, child := range family.Child {
-		for _, name := range child.Name {
-			surname := surname_re.FindStringSubmatch(name.Name)
-			if surname[1] != "" {
-				child_surname = append(child_surname, surname[1])
-			}
-		}
-	}
-
-	for _, surname := range child_surname {
-		if !contains(father_surname, surname) {
-			fmt.Println(father_surname, surname)
-			return false
-		}
-	}
-
-	return true
-}
-
-func rule_C100(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_C101(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_C102(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_C103(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_C104(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func rule_C105(family *gedcom.FamilyRecord) bool {
-	/* FIXME */
-	return true
-}
-
-func eventDate(person *gedcom.IndividualRecord, eventTag string) time.Time {
-	var date time.Time
-	for _, event := range person.Event {
-		if event.Tag == eventTag {
-			date, _ = parse_date_string(event.Date)
-		}
-	}
-
-	return date
-}
-
-func parse_date_string(date string) (time.Time, error) {
-	// "2006-01-02T15:04:05.000Z"
-	layout := "02 JAN 2006"
-	t, err := time.Parse(layout, date)
-	if err == nil {
-		return t, nil
-	}
-
-	return time.Time{}, err
-}
-
-func contains(s []string, e string) bool {
-	for _, a := range s {
-		if a == e {
-			return true
-		}
-	}
-	return false
-}
-
-func PrintIndividualRecord(record *gedcom.IndividualRecord) {
-	fmt.Printf("Person (%s)", record.Xref)
-	if len(record.Name) > 0 {
-		fmt.Printf(" %s\n", record.Name[0].Name)
-	} else {
-		fmt.Printf("\n")
-	}
-}
-
-func PrintFamilyRecord(record *gedcom.FamilyRecord) {
-	fmt.Printf("Family (%s)\n", record.Xref)
-}
-
-func main() {
-	/*
-		persons born after they were married
-		persons born after their children
-		persons having similarly named children
-		persons having an ancestral loop
-
-		Persons Born After One of Their Parents Died
-		Persons Born After One of Their Parents Was Buried
-		Persons Baptized After Being Buried
-		Persons Baptized After Being Married
-		Persons Married After Being Buried
-		Persons Baptized After They Died
-		Persons Married After They Died
-		Persons Died Before Children Were Born
-		Persons Died After Being Buried
-		Persons Buried Before Having Children
-		Persons Baptized Past the Age of 5
-		Persons Married Before the Age of 14
-		Wives Married After the Age of 50
-		Persons Living Past the Age of 100
-		Persons at Least 30 Years Older Than Their Spouse
-		Persons Having a Degree of Kinship of 4 or Less
-		Persons Having a Non-biological Parent
-		Persons Whose Birth Dates Could Not Be Estimated
-		Persons with Invalid Dates
-		Families Having Swapped Spouses
-	*/
-
-	// rules are borrowed from gigatrees.com and lifelines
-	person_rules := map[string]fn_person{
-		"EI100: person's age at death is older than _oldage_": rule_I100,
-		"EI101: person is baptized before birth":              rule_I101,
-		"EI102: person dies before birth":                     rule_I102,
-		"EI103: person is buried before birth":                rule_I103,
-		"EI104: person dies before baptism":                   rule_I104,
-		"EI105: person is buried before baptism":              rule_I105,
-		"EI106: person is buried before death":                rule_I106,
-		"EI107: person is baptised after birth year":          rule_I107,
-		"EI108: person is buried after death year":            rule_I108,
-		"EI109: person has unkown gender":                     rule_I109,
-		"EI110: person has ambiguous gender":                  rule_I110,
-		"EI111: person has multiple parentage":                rule_I111,
-		"EI112: person has no family pointers":                rule_I112,
-	}
-
-	family_rules := map[string]fn_family{
-		"EF100: family has no members":             rule_F100,
-		"EF101: family has no parents":             rule_F101,
-		"EF102: husband missing pointer to family": rule_F102,
-		"EF103: family missing pointer to husband": rule_F103,
-		"EF104: wife missing pointer to family":    rule_F104,
-		"EF105: family missing pointer to wife":    rule_F105,
-		"EF106: child missing pointer to family":   rule_F106,
-		"EF107: family missing pointer to child":   rule_F107,
-		"EF108: family has multiple husbands":      rule_F108,
-		"EF109: family has multiple wives":         rule_F109,
-		"EF110: child is in family multiple times": rule_F110,
-		// marriage rules
-		"EM100: person marries before birth":                                            rule_M100,
-		"EM101: person marries after death":                                             rule_M101,
-		"EM102: person has more than WEDDER spouses":                                    rule_M102,
-		"EM103: person marries someone more than MAYDEC years older":                    rule_M103,
-		"EM104: person marries younger than YNGMAR":                                     rule_M104,
-		"EM105: person marries older than OLDMAR":                                       rule_M105,
-		"EM106: marriage out of order":                                                  rule_M106,
-		"EM107: marriage before birth from previous marriage":                           rule_M107,
-		"EM108: marriage after birth from subsequent marriage":                          rule_M108,
-		"EM109: homosexual marriage":                                                    rule_M109,
-		"EM110: person is a female husband":                                             rule_M110,
-		"EM111: person is a male wife":                                                  rule_M111,
-		"EM112: person was a widow(er) longer than LNGWDW years":                        rule_M112,
-		"EM113: person lived more than OLDUNM years and never married":                  rule_M113,
-		"EM114: person has multiple marriages, this one with no spouse and no children": rule_M114,
-		"EM115: person has same surname as spouse":                                      rule_M115,
-		// parentage rules
-		"EP100: mother has more than FECMOM children":                    rule_P100,
-		"EP101: mother is older than OLDMOM at time of birth of child":   rule_P101,
-		"EP102: child is born before mother":                             rule_P102,
-		"EP103: mother is younger than YNGMOM at time of birth of child": rule_P103,
-		"EP104: mother is dead at birth of child":                        rule_P104,
-		"EP105: father is dead at birth of child":                        rule_P105,
-		"EP106: child doesn't inherit father's surname":                  rule_P106,
-		// children rules
-		"EC100: child is born out of order with respect to a previous child": rule_C100,
-		"EC101: child is born in the same year as a previous child":          rule_C101,
-		"EC102: child is born more than CSPACE years after previous child":   rule_C102,
-		"EC103: children's births span more than CBSPAN years":               rule_C103,
-		"EC104: child is born before parent's marriage":                      rule_C104,
-		"EC105: child has same given name as sibling":                        rule_C105,
-	}
-
-	flag.Usage = func() {
-		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
-		fmt.Fprintf(os.Stderr, "\nTool for checking of GEDCOM file for errors.")
-		fmt.Fprintf(os.Stderr, "\n\nFlags:\n")
-		flag.PrintDefaults()
-	}
-
-	var gedfile = flag.String("file", "", "GEDCOM filename")
-	var ignorelist = flag.String("ignore", "", "rules ignore list")
-	var verbose = flag.Bool("verbose", false, "verbose mode")
-
-	flag.Parse()
-
-	if *gedfile == "" {
-		flag.Usage()
-		os.Exit(1)
-	}
-
-	var ignores []string
-	if len(*ignorelist) != 0 {
-		ignores = strings.Split(*ignorelist, ",")
-		fmt.Println("These rules are ignored:", ignores)
-	}
-
-	data, _ := ioutil.ReadFile(*gedfile)
-	d := gedcom.NewDecoder(bytes.NewReader(data))
-	g, _ := d.Decode()
-
-	if *verbose {
-		fmt.Printf("Found %d persons:\n\n", len(g.Individual))
-	}
-
-	found_errors := false
-	for _, record := range g.Individual {
-		for err_msg, fn := range person_rules {
-			if contains(ignores, err_msg) {
-				continue
-			}
-			if !fn(record) {
-				PrintIndividualRecord(record)
-				fmt.Println(err_msg)
-				found_errors = true
-			}
-		}
-	}
-
-	if *verbose {
-		fmt.Printf("\nFound %d families:\n\n", len(g.Family))
-	}
-	for _, record := range g.Family {
-		for err_msg, fn := range family_rules {
-			if contains(ignores, err_msg) {
-				continue
-			}
-			if !fn(record) {
-				PrintFamilyRecord(record)
-				fmt.Println(err_msg)
-				found_errors = true
-			}
-		}
-	}
-
-	if found_errors {
-		os.Exit(1)
-	}
-}
blob - /dev/null
blob + 5628d80db9ca2c2143297b47e0c6448a7ac8fb9f (mode 644)
--- /dev/null
+++ cmd/book.go
@@ -0,0 +1,7 @@
+/*
+- https://imgur.com/a/VMHPdEr
+- https://doasimdoingblog.com/category/family-history-binder/
+- https://bencrowder.net/blog/2014/experimental-family-pedigree/
+- https://bencrowder.net/blog/2014/experimental-pedigree-chart/
+- https://bencrowder.net/blog/2011/family-group-record-redesigns/
+*/
blob - b4946220f9cad5ef517507986e12a71f2e84cf87 (mode 644)
blob + /dev/null
--- cmd/gedcom2sql/gedcom2sql.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// Gramps SQL Database Scheme
-// https://gramps-project.org/wiki/index.php/Gramps_SQL_Database)
-
-package main
-
-import (
-	"bytes"
-	"database/sql"
-	"flag"
-	"fmt"
-	"github.com/iand/gedcom"
-	_ "github.com/mattn/go-sqlite3"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-)
-
-func checkErr(err error) {
-	if err != nil {
-		panic(err)
-	}
-}
-
-const (
-	sql_table = `
-        DROP TABLE IF EXISTS famchild;
-		CREATE TABLE famchild (
-		   famID varchar(40) NOT NULL DEFAULT '',
-		   child varchar(40) NOT NULL DEFAULT '',
-		   PRIMARY KEY (famID, child)
-		);
-
-		DROP TABLE IF EXISTS family;
-		CREATE TABLE family (
-		   famID varchar(40) NOT NULL DEFAULT '',
-		   husband varchar(40) DEFAULT NULL,
-		   wife varchar(40) DEFAULT NULL,
-		   marr_date varchar(255) DEFAULT NULL,
-		   marr_plac varchar(255) DEFAULT NULL,
-		   marr_sour varchar(255) DEFAULT NULL,
-		   marb_date varchar(255) DEFAULT NULL,
-		   marb_plac varchar(255) DEFAULT NULL,
-		   marb_sour varchar(255) DEFAULT NULL,
-		   PRIMARY KEY (famID)
-		);
-
-		DROP TABLE IF EXISTS person_st;
-		CREATE TABLE person_st (
-		   persID varchar(40) NOT NULL DEFAULT '',
-		   name varchar(255) DEFAULT NULL,
-		   vorname varchar(255) DEFAULT NULL,
-		   marname varchar(255) DEFAULT NULL,
-		   sex char(1) DEFAULT NULL,
-		   birt_date varchar(255) DEFAULT NULL,
-		   birt_plac varchar(255) DEFAULT NULL,
-		   birt_sour varchar(255) DEFAULT NULL,
-		   taufe_date varchar(255) DEFAULT NULL,
-		   taufe_plac varchar(255) DEFAULT NULL,
-		   taufe_sour varchar(255) DEFAULT NULL,
-		   deat_date varchar(255) DEFAULT NULL,
-		   deat_plac varchar(255) DEFAULT NULL,
-		   deat_sour varchar(255) DEFAULT NULL,
-		   buri_date varchar(255) DEFAULT NULL,
-		   buri_plac varchar(255) DEFAULT NULL,
-		   buri_sour varchar(255) DEFAULT NULL,
-		   occupation varchar(255) DEFAULT NULL,
-		   occu_date varchar(255) DEFAULT NULL,
-		   occu_plac varchar(255) DEFAULT NULL,
-		   occu_sour varchar(255) DEFAULT NULL,
-		   religion varchar(80) DEFAULT NULL,
-		   confi_date varchar(255) DEFAULT NULL,
-		   confi_plac varchar(255) DEFAULT NULL,
-		   confi_sour varchar(255) DEFAULT NULL,
-		   note longtext,
-		   PRIMARY KEY (persID)
-		);
-        `
-)
-
-func InitDB(filepath string) *sql.DB {
-	db, err := sql.Open("sqlite3", filepath)
-	if err != nil {
-		panic(err)
-	}
-	if db == nil {
-		panic("db nil")
-	}
-	return db
-}
-
-func CreateTable(db *sql.DB) {
-	_, err := db.Exec(sql_table)
-	if err != nil {
-		panic(err)
-	}
-}
-
-func main() {
-
-	flag.Usage = func() {
-		fmt.Println("\ngedcom2sql is a tool for conversion of GEDCOM to SQLite.")
-		fmt.Println("\nUsage:")
-		flag.PrintDefaults()
-	}
-
-	var gedfile = flag.String("file", "", "GEDCOM filename")
-	var verbose = flag.Bool("verbose", false, "verbose mode")
-
-	flag.Parse()
-
-	if *gedfile == "" {
-		flag.Usage()
-		os.Exit(1)
-	}
-
-	/*
-			if len(g.Header.SourceSystem.Version) > 0 {
-				println(g.Header.SourceSystem.Version, g.Header.SourceSystem.Id)
-		    }
-			if len(g.Header.SourceSystem.Id) > 0 {
-				println(g.Header.SourceSystem.Id)
-		    }
-	*/
-
-	var dbname = strings.TrimSuffix(*gedfile, filepath.Ext(*gedfile)) + ".sqlite"
-	db := InitDB(dbname)
-	defer db.Close()
-	CreateTable(db)
-
-	data, _ := ioutil.ReadFile(*gedfile)
-	d := gedcom.NewDecoder(bytes.NewReader(data))
-	g, _ := d.Decode()
-
-	if *verbose {
-		fmt.Println("\nPersons (Xref, Name, Sex):")
-		fmt.Println("--------------------------")
-	}
-	println("Found persons:", len(g.Individual))
-	for _, rec := range g.Individual {
-		if len(rec.Name) > 0 {
-			if *verbose {
-				fmt.Printf("%s, %s, %s\n", rec.Xref, rec.Name[0].Name, rec.Sex)
-			}
-			var birt_date, deat_date, buri_date string
-			var birt_plac, deat_plac, buri_plac string
-			for _, event := range rec.Event {
-				if *verbose {
-					fmt.Printf("\t%s, %s, %s\n", event.Tag, event.Date, event.Place.Name)
-				}
-				if event.Tag == "BIRT" {
-					birt_date = event.Date
-					birt_plac = event.Place.Name
-				}
-				if event.Tag == "DEAT" {
-					deat_date = event.Date
-					deat_plac = event.Place.Name
-				}
-				if event.Tag == "DEAT" {
-					buri_date = event.Date
-					buri_plac = event.Place.Name
-				}
-			}
-			stmt, err := db.Prepare("INSERT INTO person_st (persID, name, birt_date, birt_plac, deat_date, deat_plac, buri_date, buri_plac, sex) values(?, ?, ?, ?, ?, ?, ?, ?, ?)")
-			checkErr(err)
-			_, err = stmt.Exec(rec.Xref, rec.Name[0].Name, birt_date, birt_plac, deat_date, deat_plac, buri_date, buri_plac, rec.Sex)
-			checkErr(err)
-		}
-	}
-
-	if *verbose {
-		fmt.Println("\nFamilies (Xref, Husband, Wife):")
-		fmt.Println("-------------------------------")
-	}
-	println("Found families:", len(g.Family))
-	for _, rec := range g.Family {
-		if *verbose {
-			fmt.Printf("%s\n", rec.Xref)
-			fmt.Printf("%s, %s, %s\n", rec.Xref, rec.Husband.Xref, rec.Wife.Xref)
-		}
-		stmt, err := db.Prepare("INSERT INTO family (famID, husband, wife) values(?, ?, ?)")
-		checkErr(err)
-		_, err = stmt.Exec(rec.Xref, rec.Husband.Xref, rec.Wife.Xref)
-		checkErr(err)
-
-		for _, child := range rec.Child {
-			if *verbose {
-				fmt.Printf("\t%s, %s\n", rec.Xref, child.Xref)
-			}
-			stmt, err := db.Prepare("INSERT INTO famchild (famID, child) values(?, ?)")
-			checkErr(err)
-			_, err = stmt.Exec(rec.Xref, child.Xref)
-			checkErr(err)
-		}
-	}
-	println("Database path:", dbname)
-}
blob - 90b0125dda69ca11bfb5f7b944d8bbd8f1aa0302 (mode 644)
blob + /dev/null
--- cmd/gedcom2sql/gramps-db.sql
+++ /dev/null
@@ -1,34 +0,0 @@
-CREATE TABLE person (handle VARCHAR(50) PRIMARY KEY NOT NULL, given_name TEXT, surname TEXT, blob_data BLOB))
-CREATE TABLE family (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
-CREATE TABLE source (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
-CREATE TABLE citation (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
-CREATE TABLE event (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
-CREATE TABLE media (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
-CREATE TABLE place (handle VARCHAR(50) PRIMARY KEY NOT NULL, enclosed_by VARCHAR(50), blob_data BLOB)
-CREATE TABLE repository (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
-CREATE TABLE note (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
-CREATE TABLE tag (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
-CREATE TABLE reference (obj_handle VARCHAR(50), obj_class TEXT, ref_handle VARCHAR(50), ref_class TEXT)
-CREATE TABLE name_group (name VARCHAR(50) PRIMARY KEY NOT NULL, grouping TEXT)
-CREATE TABLE metadata (setting VARCHAR(50) PRIMARY KEY NOT NULL, value BLOB)
-CREATE TABLE gender_stats (given_name TEXT, female INTEGER, male INTEGER, unknown INTEGER)
-
-CREATE INDEX person_gramps_id ON person(gramps_id)
-CREATE INDEX person_surname ON person(surname)
-CREATE INDEX person_given_name ON person(given_name)
-CREATE INDEX source_title ON source(title)
-CREATE INDEX source_gramps_id ON source(gramps_id)
-CREATE INDEX citation_page ON citation(page)
-CREATE INDEX citation_gramps_id ON citation(gramps_id)
-CREATE INDEX media_desc ON media(desc)
-CREATE INDEX media_gramps_id ON media(gramps_id)
-CREATE INDEX place_title ON place(title)
-CREATE INDEX place_enclosed_by ON place(enclosed_by)
-CREATE INDEX place_gramps_id ON place(gramps_id)
-CREATE INDEX tag_name ON tag(name)
-CREATE INDEX reference_ref_handle ON reference(ref_handle)
-CREATE INDEX family_gramps_id ON family(gramps_id)
-CREATE INDEX event_gramps_id ON event(gramps_id)
-CREATE INDEX repository_gramps_id ON repository(gramps_id)
-CREATE INDEX note_gramps_id ON note(gramps_id)
-CREATE INDEX reference_obj_handle ON reference(obj_handle)
blob - abfd333976b35671b037df6835ce79ae02087198 (mode 644)
blob + /dev/null
--- cmd/gedcom2sql/gramps-db1.sql
+++ /dev/null
@@ -1,224 +0,0 @@
-CREATE TABLE note (
-                  handle CHARACTER(25) PRIMARY KEY,
-                  gid    CHARACTER(25),
-                  text   TEXT,
-                  format INTEGER,
-                  note_type1   INTEGER,
-                  note_type2   TEXT,
-                  change INTEGER,
-                  private BOOLEAN);
-CREATE TABLE name (
-                  handle CHARACTER(25) PRIMARY KEY,
-                  primary_name BOOLEAN,
-                  private BOOLEAN, 
-                  first_name TEXT, 
-                  suffix TEXT, 
-                  title TEXT, 
-                  name_type0 INTEGER, 
-                  name_type1 TEXT, 
-                  group_as TEXT, 
-                  sort_as INTEGER,
-                  display_as INTEGER, 
-                  call TEXT,
-                  nick TEXT,
-                  famnick TEXT);
-CREATE TABLE surname (
-                  handle CHARACTER(25),
-                  surname TEXT, 
-                  prefix TEXT, 
-                  primary_surname BOOLEAN, 
-                  origin_type0 INTEGER,
-                  origin_type1 TEXT,
-                  connector TEXT);
-CREATE INDEX idx_surname_handle ON 
-                  surname(handle);
-CREATE TABLE date (
-                  handle CHARACTER(25) PRIMARY KEY,
-                  calendar INTEGER, 
-                  modifier INTEGER, 
-                  quality INTEGER,
-                  day1 INTEGER, 
-                  month1 INTEGER, 
-                  year1 INTEGER, 
-                  slash1 BOOLEAN,
-                  day2 INTEGER, 
-                  month2 INTEGER, 
-                  year2 INTEGER, 
-                  slash2 BOOLEAN,
-                  text TEXT, 
-                  sortval INTEGER, 
-                  newyear INTEGER);
-CREATE TABLE person (
-                  handle CHARACTER(25) PRIMARY KEY,
-                  gid CHARACTER(25), 
-                  gender INTEGER, 
-                  death_ref_handle TEXT, 
-                  birth_ref_handle TEXT, 
-                  change INTEGER, 
-                  private BOOLEAN);
-CREATE TABLE family (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 gid CHARACTER(25), 
-                 father_handle CHARACTER(25), 
-                 mother_handle CHARACTER(25), 
-                 the_type0 INTEGER, 
-                 the_type1 TEXT, 
-                 change INTEGER, 
-                 private BOOLEAN);
-CREATE TABLE place (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 gid CHARACTER(25), 
-                 title TEXT, 
-                 value TEXT, 
-                 the_type0 INTEGER,
-                 the_type1 TEXT,
-                 code TEXT,
-                 long TEXT, 
-                 lat TEXT, 
-                 lang TEXT, 
-                 change INTEGER, 
-                 private BOOLEAN);
-CREATE TABLE place_ref (
-                   handle             CHARACTER(25) PRIMARY KEY,
-                   from_place_handle  CHARACTER(25),
-                   to_place_handle    CHARACTER(25));
-CREATE TABLE place_name (
-                  handle        CHARACTER(25) PRIMARY KEY,
-                  from_handle   CHARACTER(25),
-                  value         CHARACTER(25),
-                  lang          CHARACTER(25));
-CREATE TABLE event (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 gid CHARACTER(25), 
-                 the_type0 INTEGER, 
-                 the_type1 TEXT, 
-                 description TEXT, 
-                 change INTEGER, 
-                 private BOOLEAN);
-CREATE TABLE citation (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 gid CHARACTER(25), 
-                 confidence INTEGER,
-                 page CHARACTER(25),
-                 source_handle CHARACTER(25),
-                 change INTEGER,
-                 private BOOLEAN);
-CREATE TABLE source (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 gid CHARACTER(25), 
-                 title TEXT, 
-                 author TEXT, 
-                 pubinfo TEXT, 
-                 abbrev TEXT, 
-                 change INTEGER,
-                 private BOOLEAN);
-CREATE TABLE media (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 gid CHARACTER(25), 
-                 path TEXT, 
-                 mime TEXT, 
-                 desc TEXT,
-                 checksum INTEGER,
-                 change INTEGER, 
-                 private BOOLEAN);
-CREATE TABLE repository_ref (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 ref CHARACTER(25), 
-                 call_number TEXT, 
-                 source_media_type0 INTEGER,
-                 source_media_type1 TEXT,
-                 private BOOLEAN);
-CREATE TABLE repository (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 gid CHARACTER(25), 
-                 the_type0 INTEGER, 
-                 the_type1 TEXT,
-                 name TEXT, 
-                 change INTEGER, 
-                 private BOOLEAN);
-CREATE TABLE link (
-                 from_type CHARACTER(25), 
-                 from_handle CHARACTER(25), 
-                 to_type CHARACTER(25), 
-                 to_handle CHARACTER(25));
-CREATE INDEX idx_link_to ON 
-                  link(from_type, from_handle, to_type);
-CREATE TABLE markup (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 markup0 INTEGER, 
-                 markup1 TEXT, 
-                 value INTEGER, 
-                 start_stop_list TEXT);
-CREATE TABLE event_ref (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 ref CHARACTER(25), 
-                 role0 INTEGER, 
-                 role1 TEXT, 
-                 private BOOLEAN);
-CREATE TABLE person_ref (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 description TEXT,
-                 private BOOLEAN);
-CREATE TABLE child_ref (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 ref CHARACTER(25), 
-                 frel0 INTEGER,
-                 frel1 CHARACTER(25),
-                 mrel0 INTEGER,
-                 mrel1 CHARACTER(25),
-                 private BOOLEAN);
-CREATE TABLE lds (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 type INTEGER, 
-                 place CHARACTER(25), 
-                 famc CHARACTER(25), 
-                 temple TEXT, 
-                 status INTEGER, 
-                 private BOOLEAN);
-CREATE TABLE media_ref (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 ref CHARACTER(25),
-                 role0 INTEGER,
-                 role1 INTEGER,
-                 role2 INTEGER,
-                 role3 INTEGER,
-                 private BOOLEAN);
-CREATE TABLE address (
-                handle CHARACTER(25) PRIMARY KEY,
-                private BOOLEAN);
-CREATE TABLE location (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 street TEXT, 
-                 locality TEXT,
-                 city TEXT, 
-                 county TEXT, 
-                 state TEXT, 
-                 country TEXT, 
-                 postal TEXT, 
-                 phone TEXT,
-                 parish TEXT);
-CREATE TABLE attribute (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 the_type0 INTEGER, 
-                 the_type1 TEXT, 
-                 value TEXT, 
-                 private BOOLEAN);
-CREATE TABLE url (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 path TEXT, 
-                 desc TXT, 
-                 type0 INTEGER,
-                 type1 TEXT,                  
-                 private BOOLEAN);
-CREATE TABLE datamap (
-                 from_handle CHARACTER(25),
-                 the_type0 INTEGER,
-                 the_type1 TEXT,
-                 value_field TXT,
-                 private BOOLEAN);
-CREATE TABLE tag (
-                 handle CHARACTER(25) PRIMARY KEY,
-                 name TEXT,
-                 color TEXT,
-                 priority INTEGER,
-                 change INTEGER);
blob - 091fd192efe5262161839f835a5fd38a79cbd8bf (mode 644)
blob + /dev/null
--- cmd/gedcom2sql/rootsmagic-db.sql
+++ /dev/null
@@ -1,65 +0,0 @@
-CREATE TABLE ConfigTable (RecID INTEGER PRIMARY KEY, RecType INTEGER, Title TEXT, DataRec BLOB );
-CREATE INDEX idxRecType ON ConfigTable (RecType);
-CREATE TABLE PersonTable (PersonID INTEGER PRIMARY KEY, UniqueID TEXT, Sex INTEGER, EditDate FLOAT, ParentID INTEGER, SpouseID INTEGER, Color INTEGER, Relate1 INTEGER, Relate2 INTEGER, Flags INTEGER, Living INTEGER, IsPrivate INTEGER, Proof INTEGER, Bookmark INTEGER, Note BLOB );
-CREATE TABLE FamilyTable (FamilyID INTEGER PRIMARY KEY, FatherID INTEGER, MotherID INTEGER, ChildID INTEGER, HusbOrder INTEGER, WifeOrder INTEGER, IsPrivate INTEGER, Proof INTEGER, SpouseLabel INTEGER, FatherLabel INTEGER, MotherLabel INTEGER, Note BLOB );
-CREATE INDEX idxFamilyFatherID ON FamilyTable (FatherID);
-CREATE INDEX idxFamilyMotherID ON FamilyTable (MotherID);
-CREATE TABLE ChildTable (RecID INTEGER PRIMARY KEY, ChildID INTEGER, FamilyID INTEGER, RelFather INTEGER, RelMother INTEGER, ChildOrder INTEGER, IsPrivate INTEGER, ProofFather INTEGER, ProofMother INTEGER, Note BLOB );
-CREATE INDEX idxChildID ON ChildTable (ChildID);
-CREATE INDEX idxChildFamilyID ON ChildTable (FamilyID);
-CREATE INDEX idxChildOrder ON ChildTable (ChildOrder);
-CREATE TABLE EventTable (EventID INTEGER PRIMARY KEY, EventType INTEGER, OwnerType INTEGER, OwnerID INTEGER, FamilyID INTEGER, PlaceID INTEGER, SiteID INTEGER, Date TEXT, SortDate INTEGER, IsPrimary INTEGER, IsPrivate INTEGER, Proof INTEGER, Status INTEGER, EditDate FLOAT, Sentence BLOB, Details BLOB, Note BLOB );
-CREATE INDEX idxOwnerEvent ON EventTable (OwnerID,EventType);
-CREATE INDEX idxOwnerDate ON EventTable (OwnerID,SortDate);
-CREATE TABLE AddressTable (AddressID INTEGER PRIMARY KEY, AddressType INTEGER, Name TEXT COLLATE RMNOCASE, Street1 TEXT, Street2 TEXT, City TEXT, State TEXT, Zip TEXT, Country TEXT, Phone1 TEXT, Phone2 TEXT, Fax TEXT, Email TEXT, URL TEXT, Latitude INTEGER, Longitude INTEGER, Note BLOB );
-CREATE INDEX idxAddressName ON AddressTable (Name);
-CREATE TABLE FactTypeTable (FactTypeID INTEGER PRIMARY KEY, OwnerType INTEGER, Name TEXT COLLATE RMNOCASE, Abbrev TEXT, GedcomTag TEXT, UseValue INTEGER, UseDate INTEGER, UsePlace INTEGER, Sentence BLOB, Flags INTEGER );
-CREATE INDEX idxFactTypeName ON FactTypeTable (Name);
-CREATE INDEX idxFactTypeAbbrev ON FactTypeTable (Abbrev);
-CREATE INDEX idxFactTypeGedcomTag ON FactTypeTable (GedcomTag);
-CREATE TABLE MultimediaTable (MediaID INTEGER PRIMARY KEY, MediaType INTEGER, MediaPath TEXT, MediaFile TEXT COLLATE RMNOCASE, URL TEXT, Thumbnail BLOB, Caption TEXT COLLATE RMNOCASE, RefNumber TEXT COLLATE RMNOCASE, Date TEXT, SortDate INTEGER, Description BLOB );
-CREATE INDEX idxMediaFile ON MultimediaTable (MediaFile);
-CREATE INDEX idxMediaURL ON MultimediaTable (URL);
-CREATE TABLE MediaLinkTable (LinkID INTEGER PRIMARY KEY, MediaID INTEGER, OwnerType INTEGER, OwnerID INTEGER, IsPrimary INTEGER, Include1 INTEGER, Include2 INTEGER, Include3 INTEGER, Include4 INTEGER, SortOrder INTEGER, RectLeft INTEGER, RectTop INTEGER, RectRight INTEGER, RectBottom INTEGER, Note TEXT, Caption TEXT COLLATE RMNOCASE, RefNumber TEXT COLLATE RMNOCASE, Date TEXT, SortDate INTEGER, Description BLOB );
-CREATE INDEX idxMediaOwnerID ON MediaLinkTable (OwnerID);
-CREATE INDEX idxMediaCaption ON MediaLinkTable (Caption);
-CREATE TABLE NameTable (NameID INTEGER PRIMARY KEY, OwnerID INTEGER, Surname TEXT COLLATE RMNOCASE, Given TEXT COLLATE RMNOCASE, Prefix TEXT COLLATE RMNOCASE, Suffix TEXT COLLATE RMNOCASE, Nickname TEXT COLLATE RMNOCASE, NameType INTEGER, Date TEXT, SortDate INTEGER, IsPrimary INTEGER, IsPrivate INTEGER, Proof INTEGER, EditDate FLOAT, Sentence BLOB, Note BLOB, BirthYear INTEGER, DeathYear INTEGER );
-CREATE INDEX idxNameOwnerID ON NameTable (OwnerID);
-CREATE INDEX idxSurname ON NameTable (Surname);
-CREATE INDEX idxGiven ON NameTable (Given);
-CREATE INDEX idxSurnameGiven ON NameTable (Surname, Given, BirthYear, DeathYear);
-CREATE INDEX idxNamePrimary ON NameTable (IsPrimary);
-CREATE TABLE PlaceTable (PlaceID INTEGER PRIMARY KEY, PlaceType INTEGER, Name TEXT COLLATE RMNOCASE, Abbrev TEXT, Normalized TEXT, Latitude INTEGER, Longitude INTEGER, LatLongExact INTEGER, MasterID INTEGER, Note BLOB );
-CREATE INDEX idxPlaceName ON PlaceTable (Name);
-CREATE INDEX idxPlaceAbbrev ON PlaceTable (Abbrev);
-CREATE TABLE ResearchTable (TaskID INTEGER PRIMARY KEY, TaskType INTEGER, OwnerID INTEGER, OwnerType INTEGER, RefNumber TEXT, Name TEXT COLLATE RMNOCASE, Status INTEGER, Priority INTEGER, Date1 TEXT, Date2 TEXT, Date3 TEXT, SortDate1 INTEGER, SortDate2 INTEGER, SortDate3 INTEGER, Filename TEXT, Details BLOB );
-CREATE INDEX idxResearchOwnerID ON ResearchTable (OwnerID);
-CREATE INDEX idxResearchName ON ResearchTable (Name);
-CREATE TABLE ResearchItemTable (ItemID INTEGER PRIMARY KEY, LogID INTEGER, Date TEXT, SortDate INTEGER, RefNumber TEXT, Repository TEXT, Goal TEXT, Source TEXT, Result TEXT );
-CREATE INDEX idxResearchItemLogID ON ResearchItemTable (LogID);
-CREATE TABLE SourceTable (SourceID INTEGER PRIMARY KEY, Name TEXT COLLATE RMNOCASE, RefNumber TEXT, ActualText TEXT, Comments TEXT, IsPrivate INTEGER, TemplateID INTEGER, Fields BLOB );
-CREATE INDEX idxSourceName ON SourceTable (Name);
-CREATE TABLE CitationTable (CitationID INTEGER PRIMARY KEY, OwnerType INTEGER, SourceID INTEGER, OwnerID INTEGER, Quality TEXT, IsPrivate INTEGER, Comments BLOB, ActualText BLOB, RefNumber TEXT, Flags INTEGER, Fields BLOB );
-CREATE INDEX idxCitationSourceID ON CitationTable (SourceID);
-CREATE INDEX idxCitationOwnerID ON CitationTable (OwnerID);
-CREATE TABLE AddressLinkTable (LinkID INTEGER PRIMARY KEY, OwnerType INTEGER, AddressID INTEGER, OwnerID INTEGER, AddressNum INTEGER, Details TEXT );
-CREATE TABLE WitnessTable (WitnessID INTEGER PRIMARY KEY, EventID INTEGER, PersonID INTEGER, WitnessOrder INTEGER, Role INTEGER, Sentence TEXT, Note BLOB, Given TEXT COLLATE RMNOCASE, Surname TEXT COLLATE RMNOCASE, Prefix TEXT COLLATE RMNOCASE, Suffix TEXT COLLATE RMNOCASE );
-CREATE INDEX idxWitnessEventID ON WitnessTable (EventID);
-CREATE INDEX idxWitnessPersonID ON WitnessTable (PersonID);
-CREATE TABLE LinkTable (LinkID INTEGER PRIMARY KEY, extSystem INTEGER, LinkType INTEGER, rmID INTEGER, extID TEXT, Modified INTEGER, extVersion TEXT, extDate FLOAT, Status INTEGER, Note BLOB );
-CREATE INDEX idxLinkRmId ON LinkTable (rmID);
-CREATE INDEX idxLinkExtId ON LinkTable (extID);
-CREATE TABLE LinkAncestryTable (LinkID INTEGER PRIMARY KEY, extSystem INTEGER, LinkType INTEGER, rmID INTEGER, extID TEXT, Modified INTEGER, extVersion TEXT, extDate FLOAT, Status INTEGER, Note BLOB );
-CREATE INDEX idxLinkAncestryRmId ON LinkAncestryTable (rmID);
-CREATE INDEX idxLinkAncestryExtId ON LinkAncestryTable (extID);
-CREATE TABLE RoleTable (RoleID INTEGER PRIMARY KEY, RoleName TEXT COLLATE RMNOCASE, EventType INTEGER, RoleType INTEGER, Sentence TEXT );
-CREATE INDEX idxRoleEventType ON RoleTable (EventType);
-CREATE TABLE GroupTable (RecID INTEGER PRIMARY KEY, GroupID INTEGER, StartID INTEGER, EndID INTEGER );
-CREATE TABLE ExclusionTable (RecID INTEGER PRIMARY KEY, ExclusionType INTEGER, ID1 INTEGER, ID2 INTEGER );
-CREATE UNIQUE INDEX idxExclusionIndex ON ExclusionTable (ExclusionType, ID1, ID2);
-CREATE TABLE SourceTemplateTable (TemplateID INTEGER PRIMARY KEY, Name TEXT COLLATE RMNOCASE, Description TEXT, Favorite INTEGER, Category TEXT, Footnote TEXT, ShortFootnote TEXT, Bibliography TEXT, FieldDefs BLOB );
-CREATE INDEX idxSourceTemplateName ON SourceTemplateTable (Name);
-CREATE TABLE LabelTable (LabelID INTEGER PRIMARY KEY, LabelType INTEGER, LabelValue INTEGER, LabelName TEXT COLLATE RMNOCASE, Description TEXT );
-CREATE INDEX idxLabelType ON LabelTable (LabelType);
-CREATE TABLE URLTable (LinkID INTEGER PRIMARY KEY, OwnerType INTEGER, OwnerID INTEGER, LinkType INTEGER, Name TEXT, URL TEXT, Note BLOB );
-CREATE INDEX idxURLOwnerID ON URLTable (OwnerID);
blob - /dev/null
blob + 21230540c4fd1c74ccdb0de4412b2d2c5e9b1ba5 (mode 644)
--- /dev/null
+++ cmd/errors.go
@@ -0,0 +1,720 @@
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"github.com/iand/gedcom"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strings"
+	"time"
+)
+
+const (
+	OLDAGE = 99
+	WEDDER = 3
+	MAYDEC = 5
+	YNGMAR = 20
+	OLDMAR = 80
+	LNGWDW = 80
+	OLDUNM = 67
+	FECMOM = 45
+	OLDMOM = 55
+	YNGMOM = 16
+	CSPACE = 1
+	CBSPAN = 1
+)
+
+type fn_person func(person *gedcom.IndividualRecord) bool
+type fn_family func(family *gedcom.FamilyRecord) bool
+
+func rule_I100(person *gedcom.IndividualRecord) bool {
+	var birt_date, deat_date time.Time
+	birt_date = eventDate(person, "BIRT")
+	if birt_date.IsZero() {
+		return true
+	}
+	deat_date = eventDate(person, "DEAT")
+	if deat_date.IsZero() {
+		return true
+	}
+	if deat_date.Sub(birt_date) > OLDAGE {
+		fmt.Println(deat_date, birt_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I101(person *gedcom.IndividualRecord) bool {
+	var birt_date, bap_date time.Time
+	birt_date = eventDate(person, "BIRT")
+	if birt_date.IsZero() {
+		return true
+	}
+	bap_date = eventDate(person, "BAP")
+	if bap_date.IsZero() {
+		return true
+	}
+	if bap_date.Sub(birt_date) > 0 {
+		fmt.Println(birt_date, bap_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I102(person *gedcom.IndividualRecord) bool {
+	var birt_date, deat_date time.Time
+	birt_date = eventDate(person, "BIRT")
+	if birt_date.IsZero() {
+		return true
+	}
+	deat_date = eventDate(person, "DEAT")
+	if deat_date.IsZero() {
+		return true
+	}
+	if deat_date.Sub(birt_date) < 0 {
+		fmt.Println(birt_date, deat_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I103(person *gedcom.IndividualRecord) bool {
+	var deat_date, buri_date time.Time
+	deat_date = eventDate(person, "DEAT")
+	if deat_date.IsZero() {
+		return true
+	}
+	buri_date = eventDate(person, "BURI")
+	if buri_date.IsZero() {
+		return true
+	}
+	if deat_date.Sub(buri_date) < 0 {
+		fmt.Println(deat_date, buri_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I104(person *gedcom.IndividualRecord) bool {
+	var deat_date, bap_date time.Time
+	deat_date = eventDate(person, "DEAT")
+	if deat_date.IsZero() {
+		return true
+	}
+	bap_date = eventDate(person, "BAP")
+	if bap_date.IsZero() {
+		return true
+	}
+	if deat_date.Sub(bap_date) < 0 {
+		fmt.Println(deat_date, bap_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I105(person *gedcom.IndividualRecord) bool {
+	var bap_date, buri_date time.Time
+	bap_date = eventDate(person, "BAP")
+	if bap_date.IsZero() {
+		return true
+	}
+	buri_date = eventDate(person, "BURI")
+	if buri_date.IsZero() {
+		return true
+	}
+	if bap_date.Sub(buri_date) < 0 {
+		fmt.Println(buri_date, bap_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I106(person *gedcom.IndividualRecord) bool {
+	var buri_date, deat_date time.Time
+	deat_date = eventDate(person, "DEAT")
+	if deat_date.IsZero() {
+		return true
+	}
+	buri_date = eventDate(person, "BURI")
+	if buri_date.IsZero() {
+		return true
+	}
+	if deat_date.Sub(buri_date) > 0 {
+		fmt.Println(buri_date, deat_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I107(person *gedcom.IndividualRecord) bool {
+	var bap_date, birt_date time.Time
+	bap_date = eventDate(person, "BAP")
+	if bap_date.IsZero() {
+		return true
+	}
+	birt_date = eventDate(person, "BIRT")
+	if birt_date.IsZero() {
+		return true
+	}
+	if bap_date.Sub(birt_date) >= 1 {
+		fmt.Println(bap_date, birt_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I108(person *gedcom.IndividualRecord) bool {
+	var deat_date, buri_date time.Time
+	deat_date = eventDate(person, "DEAT")
+	if deat_date.IsZero() {
+		return true
+	}
+	buri_date = eventDate(person, "BURI")
+	if buri_date.IsZero() {
+		return true
+	}
+	if deat_date.Sub(buri_date) >= 1 {
+		fmt.Println(deat_date, buri_date)
+		return false
+	}
+
+	return true
+}
+
+func rule_I109(person *gedcom.IndividualRecord) bool {
+	if person.Sex == "" {
+		return false
+	}
+
+	return true
+}
+
+func rule_I110(person *gedcom.IndividualRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_I111(person *gedcom.IndividualRecord) bool {
+	if len(person.Parents) > 1 {
+		return false
+	}
+
+	return true
+}
+
+func rule_I112(person *gedcom.IndividualRecord) bool {
+	if len(person.Family) == 0 {
+		return false
+	}
+
+	return true
+}
+
+func rule_F100(family *gedcom.FamilyRecord) bool {
+	if family.Husband == nil {
+		return false
+	}
+	if family.Wife == nil {
+		return false
+	}
+	if len(family.Child) == 0 {
+		return false
+	}
+
+	return true
+}
+
+func rule_F101(family *gedcom.FamilyRecord) bool {
+	if family.Husband == nil {
+		return false
+	}
+	if family.Wife == nil {
+		return false
+	}
+
+	return true
+}
+
+func rule_F102(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_F103(family *gedcom.FamilyRecord) bool {
+	if family.Husband == nil {
+		return false
+	}
+
+	return true
+}
+
+func rule_F104(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_F105(family *gedcom.FamilyRecord) bool {
+	if family.Wife == nil {
+		return false
+	}
+
+	return true
+}
+
+func rule_F106(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_F107(family *gedcom.FamilyRecord) bool {
+	if len(family.Child) == 0 {
+		return false
+	}
+
+	return true
+}
+
+func rule_F108(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_F109(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_F110(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M100(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M101(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M102(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M103(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M104(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M105(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M106(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M107(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M108(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M109(family *gedcom.FamilyRecord) bool {
+	if family.Husband.Sex == family.Wife.Sex {
+		return false
+	}
+
+	return true
+}
+
+func rule_M110(family *gedcom.FamilyRecord) bool {
+	if family.Husband.Sex == "F" {
+		return false
+	}
+
+	return true
+}
+
+func rule_M111(family *gedcom.FamilyRecord) bool {
+	if family.Wife.Sex == "M" {
+		return false
+	}
+
+	return true
+}
+
+func rule_M112(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M113(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M114(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_M115(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_P100(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_P101(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_P102(family *gedcom.FamilyRecord) bool {
+	var mother_birt_date, child_birt_date time.Time
+	mother_birt_date = eventDate(family.Wife, "BIRT")
+	for _, child := range family.Child {
+		child_birt_date = eventDate(child, "BIRT")
+		if mother_birt_date.Sub(child_birt_date) < 0 {
+			return false
+		}
+	}
+
+	return true
+}
+
+func rule_P103(family *gedcom.FamilyRecord) bool {
+	var mother_birt_date, child_birt_date time.Time
+	mother_birt_date = eventDate(family.Wife, "BIRT")
+	if mother_birt_date.IsZero() {
+		return true
+	}
+	for _, child := range family.Child {
+		child_birt_date = eventDate(child, "BIRT")
+		if child_birt_date.IsZero() {
+			continue
+		}
+		if mother_birt_date.Sub(child_birt_date) < YNGMOM {
+			fmt.Println(mother_birt_date, child_birt_date)
+			return false
+		}
+	}
+
+	return true
+}
+
+func rule_P104(family *gedcom.FamilyRecord) bool {
+	var mother_deat_date, child_birt_date time.Time
+	mother_deat_date = eventDate(family.Wife, "DEAT")
+	if mother_deat_date.IsZero() {
+		return true
+	}
+	for _, child := range family.Child {
+		child_birt_date = eventDate(child, "BIRT")
+		if child_birt_date.IsZero() {
+			continue
+		}
+		if mother_deat_date == child_birt_date {
+			fmt.Println(mother_deat_date, child_birt_date)
+			return false
+		}
+	}
+
+	return true
+}
+
+func rule_P105(family *gedcom.FamilyRecord) bool {
+	var father_deat_date, child_birt_date time.Time
+	father_deat_date = eventDate(family.Husband, "DEAT")
+	if father_deat_date.IsZero() {
+		return true
+	}
+	for _, child := range family.Child {
+		child_birt_date = eventDate(child, "BIRT")
+		if child_birt_date.IsZero() {
+			continue
+		}
+		if father_deat_date == child_birt_date {
+			fmt.Println(father_deat_date, child_birt_date)
+			return false
+		}
+	}
+
+	return true
+}
+
+func rule_P106(family *gedcom.FamilyRecord) bool {
+	var father_surname []string
+	var child_surname []string
+	surname_re := regexp.MustCompile(`.+ /(.+)/`)
+	for _, name := range family.Husband.Name {
+		surname := surname_re.FindStringSubmatch(name.Name)
+		if surname[1] != "" {
+			father_surname = append(father_surname, surname[1])
+		}
+	}
+	for _, child := range family.Child {
+		for _, name := range child.Name {
+			surname := surname_re.FindStringSubmatch(name.Name)
+			if surname[1] != "" {
+				child_surname = append(child_surname, surname[1])
+			}
+		}
+	}
+
+	for _, surname := range child_surname {
+		if !contains(father_surname, surname) {
+			fmt.Println(father_surname, surname)
+			return false
+		}
+	}
+
+	return true
+}
+
+func rule_C100(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_C101(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_C102(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_C103(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_C104(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func rule_C105(family *gedcom.FamilyRecord) bool {
+	/* FIXME */
+	return true
+}
+
+func eventDate(person *gedcom.IndividualRecord, eventTag string) time.Time {
+	var date time.Time
+	for _, event := range person.Event {
+		if event.Tag == eventTag {
+			date, _ = parse_date_string(event.Date)
+		}
+	}
+
+	return date
+}
+
+func parse_date_string(date string) (time.Time, error) {
+	// "2006-01-02T15:04:05.000Z"
+	layout := "02 JAN 2006"
+	t, err := time.Parse(layout, date)
+	if err == nil {
+		return t, nil
+	}
+
+	return time.Time{}, err
+}
+
+func contains(s []string, e string) bool {
+	for _, a := range s {
+		if a == e {
+			return true
+		}
+	}
+	return false
+}
+
+func PrintIndividualRecord(record *gedcom.IndividualRecord) {
+	fmt.Printf("Person (%s)", record.Xref)
+	if len(record.Name) > 0 {
+		fmt.Printf(" %s\n", record.Name[0].Name)
+	} else {
+		fmt.Printf("\n")
+	}
+}
+
+func PrintFamilyRecord(record *gedcom.FamilyRecord) {
+	fmt.Printf("Family (%s)\n", record.Xref)
+}
+
+var (
+	/*
+		persons born after they were married
+		persons born after their children
+		persons having similarly named children
+		persons having an ancestral loop
+
+		Persons Born After One of Their Parents Died
+		Persons Born After One of Their Parents Was Buried
+		Persons Baptized After Being Buried
+		Persons Baptized After Being Married
+		Persons Married After Being Buried
+		Persons Baptized After They Died
+		Persons Married After They Died
+		Persons Died Before Children Were Born
+		Persons Died After Being Buried
+		Persons Buried Before Having Children
+		Persons Baptized Past the Age of 5
+		Persons Married Before the Age of 14
+		Wives Married After the Age of 50
+		Persons Living Past the Age of 100
+		Persons at Least 30 Years Older Than Their Spouse
+		Persons Having a Degree of Kinship of 4 or Less
+		Persons Having a Non-biological Parent
+		Persons Whose Birth Dates Could Not Be Estimated
+		Persons with Invalid Dates
+		Families Having Swapped Spouses
+	*/
+
+	// rules are borrowed from gigatrees.com and lifelines
+	person_rules = map[string]fn_person{
+		"EI100: person's age at death is older than _oldage_": rule_I100,
+		"EI101: person is baptized before birth":              rule_I101,
+		"EI102: person dies before birth":                     rule_I102,
+		"EI103: person is buried before birth":                rule_I103,
+		"EI104: person dies before baptism":                   rule_I104,
+		"EI105: person is buried before baptism":              rule_I105,
+		"EI106: person is buried before death":                rule_I106,
+		"EI107: person is baptised after birth year":          rule_I107,
+		"EI108: person is buried after death year":            rule_I108,
+		"EI109: person has unkown gender":                     rule_I109,
+		"EI110: person has ambiguous gender":                  rule_I110,
+		"EI111: person has multiple parentage":                rule_I111,
+		"EI112: person has no family pointers":                rule_I112,
+	}
+
+	family_rules = map[string]fn_family{
+		"EF100: family has no members":             rule_F100,
+		"EF101: family has no parents":             rule_F101,
+		"EF102: husband missing pointer to family": rule_F102,
+		"EF103: family missing pointer to husband": rule_F103,
+		"EF104: wife missing pointer to family":    rule_F104,
+		"EF105: family missing pointer to wife":    rule_F105,
+		"EF106: child missing pointer to family":   rule_F106,
+		"EF107: family missing pointer to child":   rule_F107,
+		"EF108: family has multiple husbands":      rule_F108,
+		"EF109: family has multiple wives":         rule_F109,
+		"EF110: child is in family multiple times": rule_F110,
+		// marriage rules
+		"EM100: person marries before birth":                                            rule_M100,
+		"EM101: person marries after death":                                             rule_M101,
+		"EM102: person has more than WEDDER spouses":                                    rule_M102,
+		"EM103: person marries someone more than MAYDEC years older":                    rule_M103,
+		"EM104: person marries younger than YNGMAR":                                     rule_M104,
+		"EM105: person marries older than OLDMAR":                                       rule_M105,
+		"EM106: marriage out of order":                                                  rule_M106,
+		"EM107: marriage before birth from previous marriage":                           rule_M107,
+		"EM108: marriage after birth from subsequent marriage":                          rule_M108,
+		"EM109: homosexual marriage":                                                    rule_M109,
+		"EM110: person is a female husband":                                             rule_M110,
+		"EM111: person is a male wife":                                                  rule_M111,
+		"EM112: person was a widow(er) longer than LNGWDW years":                        rule_M112,
+		"EM113: person lived more than OLDUNM years and never married":                  rule_M113,
+		"EM114: person has multiple marriages, this one with no spouse and no children": rule_M114,
+		"EM115: person has same surname as spouse":                                      rule_M115,
+		// parentage rules
+		"EP100: mother has more than FECMOM children":                    rule_P100,
+		"EP101: mother is older than OLDMOM at time of birth of child":   rule_P101,
+		"EP102: child is born before mother":                             rule_P102,
+		"EP103: mother is younger than YNGMOM at time of birth of child": rule_P103,
+		"EP104: mother is dead at birth of child":                        rule_P104,
+		"EP105: father is dead at birth of child":                        rule_P105,
+		"EP106: child doesn't inherit father's surname":                  rule_P106,
+		// children rules
+		"EC100: child is born out of order with respect to a previous child": rule_C100,
+		"EC101: child is born in the same year as a previous child":          rule_C101,
+		"EC102: child is born more than CSPACE years after previous child":   rule_C102,
+		"EC103: children's births span more than CBSPAN years":               rule_C103,
+		"EC104: child is born before parent's marriage":                      rule_C104,
+		"EC105: child has same given name as sibling":                        rule_C105,
+	}
+)
+
+func printErrors(fn *string, ignoresList *string, verbose bool) {
+
+	var ignores []string
+	if len(*ignorelist) != 0 {
+		ignores = strings.Split(*ignorelist, ",")
+		fmt.Println("These rules are ignored:", ignores)
+	}
+
+	data, _ := ioutil.ReadFile(*gedfile)
+	d := gedcom.NewDecoder(bytes.NewReader(data))
+	g, _ := d.Decode()
+
+	if *verbose {
+		fmt.Printf("Found %d persons:\n\n", len(g.Individual))
+	}
+
+	found_errors := false
+	for _, record := range g.Individual {
+		for err_msg, fn := range person_rules {
+			if contains(ignores, err_msg) {
+				continue
+			}
+			if !fn(record) {
+				PrintIndividualRecord(record)
+				fmt.Println(err_msg)
+				found_errors = true
+			}
+		}
+	}
+
+	if *verbose {
+		fmt.Printf("\nFound %d families:\n\n", len(g.Family))
+	}
+	for _, record := range g.Family {
+		for err_msg, fn := range family_rules {
+			if contains(ignores, err_msg) {
+				continue
+			}
+			if !fn(record) {
+				PrintFamilyRecord(record)
+				fmt.Println(err_msg)
+				found_errors = true
+			}
+		}
+	}
+
+	if found_errors {
+		os.Exit(1)
+	}
+}
blob - 2eabf1fcdb2022410ae860d6658f462bb725d05a (mode 644)
blob + /dev/null
--- cmd/gedcom2timenet/gedcom2timenet.go
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * timenet
- *   - http://vis.stanford.edu/papers/timenets
- *   - http://vis.berkeley.edu/courses/cs294-10-sp10/wiki/images/f/f2/Family_Tree_Visualization_-_Final_Paper.pdf
- * timeline
- *   - https://github.com/davorg/svg-timeline-genealogy
- * graphviz:
- *   - https://github.com/adrienverge/familytreemaker
- *   - https://github.com/vmiklos/ged2dot
- *   - моя реализация
- * [GEPS 030: New Visualization Techniques](https://www.gramps-project.org/wiki/index.php/GEPS_030:_New_Visualization_Techniques)
- * [Geneaquilts](https://aviz.fr/geneaquilts/)
- * https://github.com/nicolaskruchten/genealogy
- */
-
-package main
-
-import (
-	"github.com/ajstarks/svgo"
-	"os"
-)
-
-func main() {
-	width := 500
-	height := 500
-	canvas := svg.New(os.Stdout)
-	canvas.Start(width, height)
-	canvas.Circle(width/2, height/2, 100)
-	canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white")
-	canvas.End()
-}
blob - /dev/null
blob + 2a2c4f1c3c2295c6bc110c43037e4bbe5f4272ae (mode 644)
--- /dev/null
+++ cmd/main.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+)
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
+		fmt.Fprintf(os.Stderr, "\nGEDCOM tools.")
+		fmt.Fprintf(os.Stderr, "\n\nFlags:\n")
+		flag.PrintDefaults()
+	}
+
+	var gedfile = flag.String("file", "", "GEDCOM filename")
+	var ignorelist = flag.String("ignore", "", "rules ignore list")
+	var verbose = flag.Bool("verbose", false, "verbose mode")
+	var startPersonId = flag.String("id", "", "person id")
+
+	flag.Parse()
+
+	if *gedfile == "" {
+		flag.Usage()
+		os.Exit(1)
+	}
+
+	// printErrors()
+
+	if *personId == "" {
+		flag.Usage()
+		os.Exit(1)
+	}
+
+	// getSocialTree(personId *string)
+
+	// drawTimenet()
+}
blob - 95166ae9c5f92873cce43a99c5de13548a5011a1 (mode 644)
blob + /dev/null
--- cmd/vk2gedcom/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-### `vk2gedcom.go`
-
-Приложение строит генеалогическое дерево на основании данных из социальных
-сетей. Пока реализована поддержка только для ВКонтакте.
-
-Facebook:
-
-- https://developers.facebook.com/docs/graph-api/reference
-- [Заполнить](https://www.facebook.com/help/1557948767777120): Профиль -> Информация -> Семейные отношения
-
-VK API:
-
-- Методы: [account.getProfileInfo](https://vk.com/dev/account.getProfileInfo), [users.get](https://vk.com/dev/users.get)
-- Частотные ограничения: "К методам API ВКонтакте можно обращаться не чаще 3 раз в секунду."
-- Ошибки: https://vk.com/dev/errors
-- [Приложение](https://vk.com/editapp?id=6130272&section=options)
-- Аккаунты для отладки:
-  - [id181554010](https://vk.com/id181554010)
-  - [id144273654](https://vk.com/id144273654)
-  - [id233293686](https://vk.com/id233293686)
-  - [denis.ivanov1988](https://vk.com/denis.ivanov1988)
-  - [am.ivanov](https://vk.com/am.ivanov)
-  - [id140022470](https://vk.com/id140022470)
-  - [id1107203](https://vk.com/id1107203)
blob - 1f99cfae42baf5597a80791abf7acb63e25a8163 (mode 644)
blob + /dev/null
--- cmd/vk2gedcom/vk2gedcom.go
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
-
-Example: ./socialtree -id 233293686
-
-TreeWalk https://gist.github.com/abhat/71332461951830f9a0f5
-https://gist.github.com/zyxar/2317744
-https://golang.org/doc/play/tree.go
-https://github.com/alonsovidales/go_graph
-
-FIXME:
-		id181554010 (зацикливается)
-		id140022470 (зацикливается)
-
-*/
-
-package main
-
-import (
-	"encoding/json"
-	"flag"
-	"fmt"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"os"
-	"strconv"
-)
-
-type RawResponse struct {
-	Person []json.RawMessage `json:"response"`
-}
-
-type Person struct {
-	ID              int        `json:"id"`
-	FirstName       string     `json:"first_name"`
-	LastName        string     `json:"last_name"`
-	Sex             int        `json:"sex"`
-	Bdate           string     `json:"bdate"`
-	Photo_50        string     `json:"photo_50"`
-	Relation        int        `json:"relation"`
-	Relatives       []Relative `json:"relatives"`
-	RelationPartner RelPartner `json:"relation_partner"`
-	Generation      int
-}
-
-type RelPartner struct {
-	ID   int    `json:"id"`
-	Name string `json:"name"`
-}
-
-type Relative struct {
-	ID   int    `json:"id"`   // идентификатор пользователя
-	Type string `json:"type"` // тип родственной связи
-	Name string `json:"name"` // имя родственника
-}
-
-func ValueInSlice(n int, list []int) bool {
-	for _, i := range list {
-		if i == n {
-			return true
-		}
-	}
-	return false
-}
-
-func process_person(person Person) []Person {
-
-	var tree []Person
-	var p Person
-	if len(person.Relatives) != 0 {
-		for _, r := range person.Relatives {
-			p = Person{}
-			log.Println(r.ID, r.Type, r.Name)
-			id := strconv.Itoa(r.ID)
-			if r.ID > 0 {
-				log.Println("Person is in VK", id)
-				p = get_profile(id)
-			} else {
-				log.Println("Person is absent in VK", id)
-				p.FirstName = r.Name
-				p.LastName = r.Name
-			}
-
-			switch r.Type {
-			case "child":
-				p.Generation = person.Generation - 1
-			case "sibling":
-				p.Generation = person.Generation
-			case "parent":
-				p.Generation = person.Generation + 1
-			case "grandparent":
-				p.Generation = person.Generation - 2
-			case "grandchild":
-				p.Generation = person.Generation + 2
-			}
-			tree = append(tree, p)
-			process_person(p)
-		}
-	}
-
-	relation := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
-	if ValueInSlice(person.Relation, relation) {
-		log.Println("no spouse")
-	} else {
-		p = Person{}
-		id := strconv.Itoa(person.RelationPartner.ID)
-		p = get_profile(id)
-		tree = append(tree, p)
-		process_person(p)
-	}
-
-	return tree
-}
-
-func get_profile(id string) Person {
-
-	url := "https://api.vk.com/method/users.get?user_ids="
-	url = url + id
-	url = url + "&fields=first_name,last_name,photo_50,relatives,relation,bdate,sex&v=5.67"
-
-	res, err := http.Get(url)
-	if err != nil {
-		panic(err.Error())
-	}
-
-	body, err := ioutil.ReadAll(res.Body)
-	if err != nil {
-		panic(err.Error())
-	}
-
-	var raw RawResponse
-	err = json.Unmarshal(body, &raw)
-	if err != nil {
-		log.Fatal("Error parsing json: ", err)
-	}
-
-	var person Person
-	err = json.Unmarshal(raw.Person[0], &person)
-	if err != nil {
-		log.Fatal("Error parsing json: ", err)
-	}
-
-	var sex string
-	switch person.Sex {
-	case 1:
-		sex = "Ж"
-	case 2:
-		sex = "М"
-	default:
-		sex = "неизвестно"
-	}
-
-	log.Println(person.FirstName, person.LastName, sex, person.Bdate)
-	log.Println("\t", person.Relatives)
-
-	return person
-}
-
-func main() {
-
-	flag.Usage = func() {
-		fmt.Println("\nsocialtree is a tool for getting relations via VK.")
-		fmt.Println("\nUsage:")
-		flag.PrintDefaults()
-	}
-
-	var person_id = flag.String("id", "", "person id")
-
-	flag.Parse()
-
-	if *person_id == "" {
-		flag.Usage()
-		os.Exit(1)
-	}
-
-	var Tree []Person
-
-	person := get_profile(*person_id)
-	person.Generation = 0
-	Tree = append(Tree, person)
-	Tree = append(Tree, process_person(person)...) // new
-
-	/*
-		var p Person
-		if len(person.Relatives) != 0 {
-			for _, r := range person.Relatives {
-				p = Person{}
-				log.Println(r.ID, r.Type, r.Name)
-				id := strconv.Itoa(r.ID)
-				if r.ID > 0 {
-					log.Println("Person is in VK", id)
-					p = get_profile(id)
-				} else {
-					log.Println("Person is absent in VK", id)
-					p.FirstName = r.Name
-					p.LastName = r.Name
-				}
-
-				switch r.Type {
-				case "child":
-					p.Generation = person.Generation - 1
-				case "sibling":
-					p.Generation = person.Generation
-				case "parent":
-					p.Generation = person.Generation + 1
-				case "grandparent":
-					p.Generation = person.Generation - 2
-				case "grandchild":
-					p.Generation = person.Generation + 2
-				}
-				Tree = append(Tree, p)
-				process_person(p)
-			}
-		}
-
-		relation := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
-		if ValueInSlice(person.Relation, relation) {
-			log.Println("no spouse")
-		} else {
-			p = Person{}
-			id := strconv.Itoa(person.RelationPartner.ID)
-			p = get_profile(id)
-			Tree = append(Tree, p)
-			process_person(p)
-		}
-	*/
-
-	prettyJSON, err := json.MarshalIndent(Tree, "", "  ")
-	if err != nil {
-		panic(err)
-	}
-	log.Println(string(prettyJSON))
-}
blob - /dev/null
blob + 2cda0844ca5cf071844c2bda1db4514e74970524 (mode 644)
--- /dev/null
+++ cmd/social.go
@@ -0,0 +1,219 @@
+/*
+
+Example: ./socialtree -id 233293686
+
+TreeWalk https://gist.github.com/abhat/71332461951830f9a0f5
+https://gist.github.com/zyxar/2317744
+https://golang.org/doc/play/tree.go
+https://github.com/alonsovidales/go_graph
+
+FIXME:
+		id181554010 (зацикливается)
+		id140022470 (зацикливается)
+
+*/
+
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"strconv"
+)
+
+type RawResponse struct {
+	Person []json.RawMessage `json:"response"`
+}
+
+type Person struct {
+	ID              int        `json:"id"`
+	FirstName       string     `json:"first_name"`
+	LastName        string     `json:"last_name"`
+	Sex             int        `json:"sex"`
+	Bdate           string     `json:"bdate"`
+	Photo_50        string     `json:"photo_50"`
+	Relation        int        `json:"relation"`
+	Relatives       []Relative `json:"relatives"`
+	RelationPartner RelPartner `json:"relation_partner"`
+	Generation      int
+}
+
+type RelPartner struct {
+	ID   int    `json:"id"`
+	Name string `json:"name"`
+}
+
+type Relative struct {
+	ID   int    `json:"id"`   // идентификатор пользователя
+	Type string `json:"type"` // тип родственной связи
+	Name string `json:"name"` // имя родственника
+}
+
+func ValueInSlice(n int, list []int) bool {
+	for _, i := range list {
+		if i == n {
+			return true
+		}
+	}
+	return false
+}
+
+func process_person(person Person) []Person {
+
+	var tree []Person
+	var p Person
+	if len(person.Relatives) != 0 {
+		for _, r := range person.Relatives {
+			p = Person{}
+			log.Println(r.ID, r.Type, r.Name)
+			id := strconv.Itoa(r.ID)
+			if r.ID > 0 {
+				log.Println("Person is in VK", id)
+				p = get_profile(id)
+			} else {
+				log.Println("Person is absent in VK", id)
+				p.FirstName = r.Name
+				p.LastName = r.Name
+			}
+
+			switch r.Type {
+			case "child":
+				p.Generation = person.Generation - 1
+			case "sibling":
+				p.Generation = person.Generation
+			case "parent":
+				p.Generation = person.Generation + 1
+			case "grandparent":
+				p.Generation = person.Generation - 2
+			case "grandchild":
+				p.Generation = person.Generation + 2
+			}
+			tree = append(tree, p)
+			process_person(p)
+		}
+	}
+
+	relation := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
+	if ValueInSlice(person.Relation, relation) {
+		log.Println("no spouse")
+	} else {
+		p = Person{}
+		id := strconv.Itoa(person.RelationPartner.ID)
+		p = get_profile(id)
+		tree = append(tree, p)
+		process_person(p)
+	}
+
+	return tree
+}
+
+func get_profile(id string) Person {
+
+	url := "https://api.vk.com/method/users.get?user_ids="
+	url = url + id
+	url = url + "&fields=first_name,last_name,photo_50,relatives,relation,bdate,sex&v=5.67"
+
+	res, err := http.Get(url)
+	if err != nil {
+		panic(err.Error())
+	}
+
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		panic(err.Error())
+	}
+
+	var raw RawResponse
+	err = json.Unmarshal(body, &raw)
+	if err != nil {
+		log.Fatal("Error parsing json: ", err)
+	}
+
+	var person Person
+	err = json.Unmarshal(raw.Person[0], &person)
+	if err != nil {
+		log.Fatal("Error parsing json: ", err)
+	}
+
+	var sex string
+	switch person.Sex {
+	case 1:
+		sex = "Ж"
+	case 2:
+		sex = "М"
+	default:
+		sex = "неизвестно"
+	}
+
+	log.Println(person.FirstName, person.LastName, sex, person.Bdate)
+	log.Println("\t", person.Relatives)
+
+	return person
+}
+
+func getSocialTree(personId *string) {
+
+	var Tree []Person
+
+	person := get_profile(*personId)
+	person.Generation = 0
+	Tree = append(Tree, person)
+	Tree = append(Tree, process_person(person)...) // new
+
+	/*
+		var p Person
+		if len(person.Relatives) != 0 {
+			for _, r := range person.Relatives {
+				p = Person{}
+				log.Println(r.ID, r.Type, r.Name)
+				id := strconv.Itoa(r.ID)
+				if r.ID > 0 {
+					log.Println("Person is in VK", id)
+					p = get_profile(id)
+				} else {
+					log.Println("Person is absent in VK", id)
+					p.FirstName = r.Name
+					p.LastName = r.Name
+				}
+
+				switch r.Type {
+				case "child":
+					p.Generation = person.Generation - 1
+				case "sibling":
+					p.Generation = person.Generation
+				case "parent":
+					p.Generation = person.Generation + 1
+				case "grandparent":
+					p.Generation = person.Generation - 2
+				case "grandchild":
+					p.Generation = person.Generation + 2
+				}
+				Tree = append(Tree, p)
+				process_person(p)
+			}
+		}
+
+		relation := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
+		if ValueInSlice(person.Relation, relation) {
+			log.Println("no spouse")
+		} else {
+			p = Person{}
+			id := strconv.Itoa(person.RelationPartner.ID)
+			p = get_profile(id)
+			Tree = append(Tree, p)
+			process_person(p)
+		}
+	*/
+
+	prettyJSON, err := json.MarshalIndent(Tree, "", "  ")
+	if err != nil {
+		panic(err)
+	}
+
+	log.Println(string(prettyJSON))
+}
blob - /dev/null
blob + b4946220f9cad5ef517507986e12a71f2e84cf87 (mode 644)
--- /dev/null
+++ cmd/sql.go
@@ -0,0 +1,197 @@
+// Gramps SQL Database Scheme
+// https://gramps-project.org/wiki/index.php/Gramps_SQL_Database)
+
+package main
+
+import (
+	"bytes"
+	"database/sql"
+	"flag"
+	"fmt"
+	"github.com/iand/gedcom"
+	_ "github.com/mattn/go-sqlite3"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+func checkErr(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
+
+const (
+	sql_table = `
+        DROP TABLE IF EXISTS famchild;
+		CREATE TABLE famchild (
+		   famID varchar(40) NOT NULL DEFAULT '',
+		   child varchar(40) NOT NULL DEFAULT '',
+		   PRIMARY KEY (famID, child)
+		);
+
+		DROP TABLE IF EXISTS family;
+		CREATE TABLE family (
+		   famID varchar(40) NOT NULL DEFAULT '',
+		   husband varchar(40) DEFAULT NULL,
+		   wife varchar(40) DEFAULT NULL,
+		   marr_date varchar(255) DEFAULT NULL,
+		   marr_plac varchar(255) DEFAULT NULL,
+		   marr_sour varchar(255) DEFAULT NULL,
+		   marb_date varchar(255) DEFAULT NULL,
+		   marb_plac varchar(255) DEFAULT NULL,
+		   marb_sour varchar(255) DEFAULT NULL,
+		   PRIMARY KEY (famID)
+		);
+
+		DROP TABLE IF EXISTS person_st;
+		CREATE TABLE person_st (
+		   persID varchar(40) NOT NULL DEFAULT '',
+		   name varchar(255) DEFAULT NULL,
+		   vorname varchar(255) DEFAULT NULL,
+		   marname varchar(255) DEFAULT NULL,
+		   sex char(1) DEFAULT NULL,
+		   birt_date varchar(255) DEFAULT NULL,
+		   birt_plac varchar(255) DEFAULT NULL,
+		   birt_sour varchar(255) DEFAULT NULL,
+		   taufe_date varchar(255) DEFAULT NULL,
+		   taufe_plac varchar(255) DEFAULT NULL,
+		   taufe_sour varchar(255) DEFAULT NULL,
+		   deat_date varchar(255) DEFAULT NULL,
+		   deat_plac varchar(255) DEFAULT NULL,
+		   deat_sour varchar(255) DEFAULT NULL,
+		   buri_date varchar(255) DEFAULT NULL,
+		   buri_plac varchar(255) DEFAULT NULL,
+		   buri_sour varchar(255) DEFAULT NULL,
+		   occupation varchar(255) DEFAULT NULL,
+		   occu_date varchar(255) DEFAULT NULL,
+		   occu_plac varchar(255) DEFAULT NULL,
+		   occu_sour varchar(255) DEFAULT NULL,
+		   religion varchar(80) DEFAULT NULL,
+		   confi_date varchar(255) DEFAULT NULL,
+		   confi_plac varchar(255) DEFAULT NULL,
+		   confi_sour varchar(255) DEFAULT NULL,
+		   note longtext,
+		   PRIMARY KEY (persID)
+		);
+        `
+)
+
+func InitDB(filepath string) *sql.DB {
+	db, err := sql.Open("sqlite3", filepath)
+	if err != nil {
+		panic(err)
+	}
+	if db == nil {
+		panic("db nil")
+	}
+	return db
+}
+
+func CreateTable(db *sql.DB) {
+	_, err := db.Exec(sql_table)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func main() {
+
+	flag.Usage = func() {
+		fmt.Println("\ngedcom2sql is a tool for conversion of GEDCOM to SQLite.")
+		fmt.Println("\nUsage:")
+		flag.PrintDefaults()
+	}
+
+	var gedfile = flag.String("file", "", "GEDCOM filename")
+	var verbose = flag.Bool("verbose", false, "verbose mode")
+
+	flag.Parse()
+
+	if *gedfile == "" {
+		flag.Usage()
+		os.Exit(1)
+	}
+
+	/*
+			if len(g.Header.SourceSystem.Version) > 0 {
+				println(g.Header.SourceSystem.Version, g.Header.SourceSystem.Id)
+		    }
+			if len(g.Header.SourceSystem.Id) > 0 {
+				println(g.Header.SourceSystem.Id)
+		    }
+	*/
+
+	var dbname = strings.TrimSuffix(*gedfile, filepath.Ext(*gedfile)) + ".sqlite"
+	db := InitDB(dbname)
+	defer db.Close()
+	CreateTable(db)
+
+	data, _ := ioutil.ReadFile(*gedfile)
+	d := gedcom.NewDecoder(bytes.NewReader(data))
+	g, _ := d.Decode()
+
+	if *verbose {
+		fmt.Println("\nPersons (Xref, Name, Sex):")
+		fmt.Println("--------------------------")
+	}
+	println("Found persons:", len(g.Individual))
+	for _, rec := range g.Individual {
+		if len(rec.Name) > 0 {
+			if *verbose {
+				fmt.Printf("%s, %s, %s\n", rec.Xref, rec.Name[0].Name, rec.Sex)
+			}
+			var birt_date, deat_date, buri_date string
+			var birt_plac, deat_plac, buri_plac string
+			for _, event := range rec.Event {
+				if *verbose {
+					fmt.Printf("\t%s, %s, %s\n", event.Tag, event.Date, event.Place.Name)
+				}
+				if event.Tag == "BIRT" {
+					birt_date = event.Date
+					birt_plac = event.Place.Name
+				}
+				if event.Tag == "DEAT" {
+					deat_date = event.Date
+					deat_plac = event.Place.Name
+				}
+				if event.Tag == "DEAT" {
+					buri_date = event.Date
+					buri_plac = event.Place.Name
+				}
+			}
+			stmt, err := db.Prepare("INSERT INTO person_st (persID, name, birt_date, birt_plac, deat_date, deat_plac, buri_date, buri_plac, sex) values(?, ?, ?, ?, ?, ?, ?, ?, ?)")
+			checkErr(err)
+			_, err = stmt.Exec(rec.Xref, rec.Name[0].Name, birt_date, birt_plac, deat_date, deat_plac, buri_date, buri_plac, rec.Sex)
+			checkErr(err)
+		}
+	}
+
+	if *verbose {
+		fmt.Println("\nFamilies (Xref, Husband, Wife):")
+		fmt.Println("-------------------------------")
+	}
+	println("Found families:", len(g.Family))
+	for _, rec := range g.Family {
+		if *verbose {
+			fmt.Printf("%s\n", rec.Xref)
+			fmt.Printf("%s, %s, %s\n", rec.Xref, rec.Husband.Xref, rec.Wife.Xref)
+		}
+		stmt, err := db.Prepare("INSERT INTO family (famID, husband, wife) values(?, ?, ?)")
+		checkErr(err)
+		_, err = stmt.Exec(rec.Xref, rec.Husband.Xref, rec.Wife.Xref)
+		checkErr(err)
+
+		for _, child := range rec.Child {
+			if *verbose {
+				fmt.Printf("\t%s, %s\n", rec.Xref, child.Xref)
+			}
+			stmt, err := db.Prepare("INSERT INTO famchild (famID, child) values(?, ?)")
+			checkErr(err)
+			_, err = stmt.Exec(rec.Xref, child.Xref)
+			checkErr(err)
+		}
+	}
+	println("Database path:", dbname)
+}
blob - /dev/null
blob + 90b0125dda69ca11bfb5f7b944d8bbd8f1aa0302 (mode 644)
--- /dev/null
+++ cmd/static/gramps-db.sql
@@ -0,0 +1,34 @@
+CREATE TABLE person (handle VARCHAR(50) PRIMARY KEY NOT NULL, given_name TEXT, surname TEXT, blob_data BLOB))
+CREATE TABLE family (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
+CREATE TABLE source (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
+CREATE TABLE citation (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
+CREATE TABLE event (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
+CREATE TABLE media (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
+CREATE TABLE place (handle VARCHAR(50) PRIMARY KEY NOT NULL, enclosed_by VARCHAR(50), blob_data BLOB)
+CREATE TABLE repository (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
+CREATE TABLE note (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
+CREATE TABLE tag (handle VARCHAR(50) PRIMARY KEY NOT NULL, blob_data BLOB)
+CREATE TABLE reference (obj_handle VARCHAR(50), obj_class TEXT, ref_handle VARCHAR(50), ref_class TEXT)
+CREATE TABLE name_group (name VARCHAR(50) PRIMARY KEY NOT NULL, grouping TEXT)
+CREATE TABLE metadata (setting VARCHAR(50) PRIMARY KEY NOT NULL, value BLOB)
+CREATE TABLE gender_stats (given_name TEXT, female INTEGER, male INTEGER, unknown INTEGER)
+
+CREATE INDEX person_gramps_id ON person(gramps_id)
+CREATE INDEX person_surname ON person(surname)
+CREATE INDEX person_given_name ON person(given_name)
+CREATE INDEX source_title ON source(title)
+CREATE INDEX source_gramps_id ON source(gramps_id)
+CREATE INDEX citation_page ON citation(page)
+CREATE INDEX citation_gramps_id ON citation(gramps_id)
+CREATE INDEX media_desc ON media(desc)
+CREATE INDEX media_gramps_id ON media(gramps_id)
+CREATE INDEX place_title ON place(title)
+CREATE INDEX place_enclosed_by ON place(enclosed_by)
+CREATE INDEX place_gramps_id ON place(gramps_id)
+CREATE INDEX tag_name ON tag(name)
+CREATE INDEX reference_ref_handle ON reference(ref_handle)
+CREATE INDEX family_gramps_id ON family(gramps_id)
+CREATE INDEX event_gramps_id ON event(gramps_id)
+CREATE INDEX repository_gramps_id ON repository(gramps_id)
+CREATE INDEX note_gramps_id ON note(gramps_id)
+CREATE INDEX reference_obj_handle ON reference(obj_handle)
blob - /dev/null
blob + abfd333976b35671b037df6835ce79ae02087198 (mode 644)
--- /dev/null
+++ cmd/static/gramps-db1.sql
@@ -0,0 +1,224 @@
+CREATE TABLE note (
+                  handle CHARACTER(25) PRIMARY KEY,
+                  gid    CHARACTER(25),
+                  text   TEXT,
+                  format INTEGER,
+                  note_type1   INTEGER,
+                  note_type2   TEXT,
+                  change INTEGER,
+                  private BOOLEAN);
+CREATE TABLE name (
+                  handle CHARACTER(25) PRIMARY KEY,
+                  primary_name BOOLEAN,
+                  private BOOLEAN, 
+                  first_name TEXT, 
+                  suffix TEXT, 
+                  title TEXT, 
+                  name_type0 INTEGER, 
+                  name_type1 TEXT, 
+                  group_as TEXT, 
+                  sort_as INTEGER,
+                  display_as INTEGER, 
+                  call TEXT,
+                  nick TEXT,
+                  famnick TEXT);
+CREATE TABLE surname (
+                  handle CHARACTER(25),
+                  surname TEXT, 
+                  prefix TEXT, 
+                  primary_surname BOOLEAN, 
+                  origin_type0 INTEGER,
+                  origin_type1 TEXT,
+                  connector TEXT);
+CREATE INDEX idx_surname_handle ON 
+                  surname(handle);
+CREATE TABLE date (
+                  handle CHARACTER(25) PRIMARY KEY,
+                  calendar INTEGER, 
+                  modifier INTEGER, 
+                  quality INTEGER,
+                  day1 INTEGER, 
+                  month1 INTEGER, 
+                  year1 INTEGER, 
+                  slash1 BOOLEAN,
+                  day2 INTEGER, 
+                  month2 INTEGER, 
+                  year2 INTEGER, 
+                  slash2 BOOLEAN,
+                  text TEXT, 
+                  sortval INTEGER, 
+                  newyear INTEGER);
+CREATE TABLE person (
+                  handle CHARACTER(25) PRIMARY KEY,
+                  gid CHARACTER(25), 
+                  gender INTEGER, 
+                  death_ref_handle TEXT, 
+                  birth_ref_handle TEXT, 
+                  change INTEGER, 
+                  private BOOLEAN);
+CREATE TABLE family (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 gid CHARACTER(25), 
+                 father_handle CHARACTER(25), 
+                 mother_handle CHARACTER(25), 
+                 the_type0 INTEGER, 
+                 the_type1 TEXT, 
+                 change INTEGER, 
+                 private BOOLEAN);
+CREATE TABLE place (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 gid CHARACTER(25), 
+                 title TEXT, 
+                 value TEXT, 
+                 the_type0 INTEGER,
+                 the_type1 TEXT,
+                 code TEXT,
+                 long TEXT, 
+                 lat TEXT, 
+                 lang TEXT, 
+                 change INTEGER, 
+                 private BOOLEAN);
+CREATE TABLE place_ref (
+                   handle             CHARACTER(25) PRIMARY KEY,
+                   from_place_handle  CHARACTER(25),
+                   to_place_handle    CHARACTER(25));
+CREATE TABLE place_name (
+                  handle        CHARACTER(25) PRIMARY KEY,
+                  from_handle   CHARACTER(25),
+                  value         CHARACTER(25),
+                  lang          CHARACTER(25));
+CREATE TABLE event (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 gid CHARACTER(25), 
+                 the_type0 INTEGER, 
+                 the_type1 TEXT, 
+                 description TEXT, 
+                 change INTEGER, 
+                 private BOOLEAN);
+CREATE TABLE citation (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 gid CHARACTER(25), 
+                 confidence INTEGER,
+                 page CHARACTER(25),
+                 source_handle CHARACTER(25),
+                 change INTEGER,
+                 private BOOLEAN);
+CREATE TABLE source (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 gid CHARACTER(25), 
+                 title TEXT, 
+                 author TEXT, 
+                 pubinfo TEXT, 
+                 abbrev TEXT, 
+                 change INTEGER,
+                 private BOOLEAN);
+CREATE TABLE media (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 gid CHARACTER(25), 
+                 path TEXT, 
+                 mime TEXT, 
+                 desc TEXT,
+                 checksum INTEGER,
+                 change INTEGER, 
+                 private BOOLEAN);
+CREATE TABLE repository_ref (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 ref CHARACTER(25), 
+                 call_number TEXT, 
+                 source_media_type0 INTEGER,
+                 source_media_type1 TEXT,
+                 private BOOLEAN);
+CREATE TABLE repository (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 gid CHARACTER(25), 
+                 the_type0 INTEGER, 
+                 the_type1 TEXT,
+                 name TEXT, 
+                 change INTEGER, 
+                 private BOOLEAN);
+CREATE TABLE link (
+                 from_type CHARACTER(25), 
+                 from_handle CHARACTER(25), 
+                 to_type CHARACTER(25), 
+                 to_handle CHARACTER(25));
+CREATE INDEX idx_link_to ON 
+                  link(from_type, from_handle, to_type);
+CREATE TABLE markup (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 markup0 INTEGER, 
+                 markup1 TEXT, 
+                 value INTEGER, 
+                 start_stop_list TEXT);
+CREATE TABLE event_ref (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 ref CHARACTER(25), 
+                 role0 INTEGER, 
+                 role1 TEXT, 
+                 private BOOLEAN);
+CREATE TABLE person_ref (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 description TEXT,
+                 private BOOLEAN);
+CREATE TABLE child_ref (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 ref CHARACTER(25), 
+                 frel0 INTEGER,
+                 frel1 CHARACTER(25),
+                 mrel0 INTEGER,
+                 mrel1 CHARACTER(25),
+                 private BOOLEAN);
+CREATE TABLE lds (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 type INTEGER, 
+                 place CHARACTER(25), 
+                 famc CHARACTER(25), 
+                 temple TEXT, 
+                 status INTEGER, 
+                 private BOOLEAN);
+CREATE TABLE media_ref (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 ref CHARACTER(25),
+                 role0 INTEGER,
+                 role1 INTEGER,
+                 role2 INTEGER,
+                 role3 INTEGER,
+                 private BOOLEAN);
+CREATE TABLE address (
+                handle CHARACTER(25) PRIMARY KEY,
+                private BOOLEAN);
+CREATE TABLE location (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 street TEXT, 
+                 locality TEXT,
+                 city TEXT, 
+                 county TEXT, 
+                 state TEXT, 
+                 country TEXT, 
+                 postal TEXT, 
+                 phone TEXT,
+                 parish TEXT);
+CREATE TABLE attribute (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 the_type0 INTEGER, 
+                 the_type1 TEXT, 
+                 value TEXT, 
+                 private BOOLEAN);
+CREATE TABLE url (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 path TEXT, 
+                 desc TXT, 
+                 type0 INTEGER,
+                 type1 TEXT,                  
+                 private BOOLEAN);
+CREATE TABLE datamap (
+                 from_handle CHARACTER(25),
+                 the_type0 INTEGER,
+                 the_type1 TEXT,
+                 value_field TXT,
+                 private BOOLEAN);
+CREATE TABLE tag (
+                 handle CHARACTER(25) PRIMARY KEY,
+                 name TEXT,
+                 color TEXT,
+                 priority INTEGER,
+                 change INTEGER);
blob - /dev/null
blob + 091fd192efe5262161839f835a5fd38a79cbd8bf (mode 644)
--- /dev/null
+++ cmd/static/rootsmagic-db.sql
@@ -0,0 +1,65 @@
+CREATE TABLE ConfigTable (RecID INTEGER PRIMARY KEY, RecType INTEGER, Title TEXT, DataRec BLOB );
+CREATE INDEX idxRecType ON ConfigTable (RecType);
+CREATE TABLE PersonTable (PersonID INTEGER PRIMARY KEY, UniqueID TEXT, Sex INTEGER, EditDate FLOAT, ParentID INTEGER, SpouseID INTEGER, Color INTEGER, Relate1 INTEGER, Relate2 INTEGER, Flags INTEGER, Living INTEGER, IsPrivate INTEGER, Proof INTEGER, Bookmark INTEGER, Note BLOB );
+CREATE TABLE FamilyTable (FamilyID INTEGER PRIMARY KEY, FatherID INTEGER, MotherID INTEGER, ChildID INTEGER, HusbOrder INTEGER, WifeOrder INTEGER, IsPrivate INTEGER, Proof INTEGER, SpouseLabel INTEGER, FatherLabel INTEGER, MotherLabel INTEGER, Note BLOB );
+CREATE INDEX idxFamilyFatherID ON FamilyTable (FatherID);
+CREATE INDEX idxFamilyMotherID ON FamilyTable (MotherID);
+CREATE TABLE ChildTable (RecID INTEGER PRIMARY KEY, ChildID INTEGER, FamilyID INTEGER, RelFather INTEGER, RelMother INTEGER, ChildOrder INTEGER, IsPrivate INTEGER, ProofFather INTEGER, ProofMother INTEGER, Note BLOB );
+CREATE INDEX idxChildID ON ChildTable (ChildID);
+CREATE INDEX idxChildFamilyID ON ChildTable (FamilyID);
+CREATE INDEX idxChildOrder ON ChildTable (ChildOrder);
+CREATE TABLE EventTable (EventID INTEGER PRIMARY KEY, EventType INTEGER, OwnerType INTEGER, OwnerID INTEGER, FamilyID INTEGER, PlaceID INTEGER, SiteID INTEGER, Date TEXT, SortDate INTEGER, IsPrimary INTEGER, IsPrivate INTEGER, Proof INTEGER, Status INTEGER, EditDate FLOAT, Sentence BLOB, Details BLOB, Note BLOB );
+CREATE INDEX idxOwnerEvent ON EventTable (OwnerID,EventType);
+CREATE INDEX idxOwnerDate ON EventTable (OwnerID,SortDate);
+CREATE TABLE AddressTable (AddressID INTEGER PRIMARY KEY, AddressType INTEGER, Name TEXT COLLATE RMNOCASE, Street1 TEXT, Street2 TEXT, City TEXT, State TEXT, Zip TEXT, Country TEXT, Phone1 TEXT, Phone2 TEXT, Fax TEXT, Email TEXT, URL TEXT, Latitude INTEGER, Longitude INTEGER, Note BLOB );
+CREATE INDEX idxAddressName ON AddressTable (Name);
+CREATE TABLE FactTypeTable (FactTypeID INTEGER PRIMARY KEY, OwnerType INTEGER, Name TEXT COLLATE RMNOCASE, Abbrev TEXT, GedcomTag TEXT, UseValue INTEGER, UseDate INTEGER, UsePlace INTEGER, Sentence BLOB, Flags INTEGER );
+CREATE INDEX idxFactTypeName ON FactTypeTable (Name);
+CREATE INDEX idxFactTypeAbbrev ON FactTypeTable (Abbrev);
+CREATE INDEX idxFactTypeGedcomTag ON FactTypeTable (GedcomTag);
+CREATE TABLE MultimediaTable (MediaID INTEGER PRIMARY KEY, MediaType INTEGER, MediaPath TEXT, MediaFile TEXT COLLATE RMNOCASE, URL TEXT, Thumbnail BLOB, Caption TEXT COLLATE RMNOCASE, RefNumber TEXT COLLATE RMNOCASE, Date TEXT, SortDate INTEGER, Description BLOB );
+CREATE INDEX idxMediaFile ON MultimediaTable (MediaFile);
+CREATE INDEX idxMediaURL ON MultimediaTable (URL);
+CREATE TABLE MediaLinkTable (LinkID INTEGER PRIMARY KEY, MediaID INTEGER, OwnerType INTEGER, OwnerID INTEGER, IsPrimary INTEGER, Include1 INTEGER, Include2 INTEGER, Include3 INTEGER, Include4 INTEGER, SortOrder INTEGER, RectLeft INTEGER, RectTop INTEGER, RectRight INTEGER, RectBottom INTEGER, Note TEXT, Caption TEXT COLLATE RMNOCASE, RefNumber TEXT COLLATE RMNOCASE, Date TEXT, SortDate INTEGER, Description BLOB );
+CREATE INDEX idxMediaOwnerID ON MediaLinkTable (OwnerID);
+CREATE INDEX idxMediaCaption ON MediaLinkTable (Caption);
+CREATE TABLE NameTable (NameID INTEGER PRIMARY KEY, OwnerID INTEGER, Surname TEXT COLLATE RMNOCASE, Given TEXT COLLATE RMNOCASE, Prefix TEXT COLLATE RMNOCASE, Suffix TEXT COLLATE RMNOCASE, Nickname TEXT COLLATE RMNOCASE, NameType INTEGER, Date TEXT, SortDate INTEGER, IsPrimary INTEGER, IsPrivate INTEGER, Proof INTEGER, EditDate FLOAT, Sentence BLOB, Note BLOB, BirthYear INTEGER, DeathYear INTEGER );
+CREATE INDEX idxNameOwnerID ON NameTable (OwnerID);
+CREATE INDEX idxSurname ON NameTable (Surname);
+CREATE INDEX idxGiven ON NameTable (Given);
+CREATE INDEX idxSurnameGiven ON NameTable (Surname, Given, BirthYear, DeathYear);
+CREATE INDEX idxNamePrimary ON NameTable (IsPrimary);
+CREATE TABLE PlaceTable (PlaceID INTEGER PRIMARY KEY, PlaceType INTEGER, Name TEXT COLLATE RMNOCASE, Abbrev TEXT, Normalized TEXT, Latitude INTEGER, Longitude INTEGER, LatLongExact INTEGER, MasterID INTEGER, Note BLOB );
+CREATE INDEX idxPlaceName ON PlaceTable (Name);
+CREATE INDEX idxPlaceAbbrev ON PlaceTable (Abbrev);
+CREATE TABLE ResearchTable (TaskID INTEGER PRIMARY KEY, TaskType INTEGER, OwnerID INTEGER, OwnerType INTEGER, RefNumber TEXT, Name TEXT COLLATE RMNOCASE, Status INTEGER, Priority INTEGER, Date1 TEXT, Date2 TEXT, Date3 TEXT, SortDate1 INTEGER, SortDate2 INTEGER, SortDate3 INTEGER, Filename TEXT, Details BLOB );
+CREATE INDEX idxResearchOwnerID ON ResearchTable (OwnerID);
+CREATE INDEX idxResearchName ON ResearchTable (Name);
+CREATE TABLE ResearchItemTable (ItemID INTEGER PRIMARY KEY, LogID INTEGER, Date TEXT, SortDate INTEGER, RefNumber TEXT, Repository TEXT, Goal TEXT, Source TEXT, Result TEXT );
+CREATE INDEX idxResearchItemLogID ON ResearchItemTable (LogID);
+CREATE TABLE SourceTable (SourceID INTEGER PRIMARY KEY, Name TEXT COLLATE RMNOCASE, RefNumber TEXT, ActualText TEXT, Comments TEXT, IsPrivate INTEGER, TemplateID INTEGER, Fields BLOB );
+CREATE INDEX idxSourceName ON SourceTable (Name);
+CREATE TABLE CitationTable (CitationID INTEGER PRIMARY KEY, OwnerType INTEGER, SourceID INTEGER, OwnerID INTEGER, Quality TEXT, IsPrivate INTEGER, Comments BLOB, ActualText BLOB, RefNumber TEXT, Flags INTEGER, Fields BLOB );
+CREATE INDEX idxCitationSourceID ON CitationTable (SourceID);
+CREATE INDEX idxCitationOwnerID ON CitationTable (OwnerID);
+CREATE TABLE AddressLinkTable (LinkID INTEGER PRIMARY KEY, OwnerType INTEGER, AddressID INTEGER, OwnerID INTEGER, AddressNum INTEGER, Details TEXT );
+CREATE TABLE WitnessTable (WitnessID INTEGER PRIMARY KEY, EventID INTEGER, PersonID INTEGER, WitnessOrder INTEGER, Role INTEGER, Sentence TEXT, Note BLOB, Given TEXT COLLATE RMNOCASE, Surname TEXT COLLATE RMNOCASE, Prefix TEXT COLLATE RMNOCASE, Suffix TEXT COLLATE RMNOCASE );
+CREATE INDEX idxWitnessEventID ON WitnessTable (EventID);
+CREATE INDEX idxWitnessPersonID ON WitnessTable (PersonID);
+CREATE TABLE LinkTable (LinkID INTEGER PRIMARY KEY, extSystem INTEGER, LinkType INTEGER, rmID INTEGER, extID TEXT, Modified INTEGER, extVersion TEXT, extDate FLOAT, Status INTEGER, Note BLOB );
+CREATE INDEX idxLinkRmId ON LinkTable (rmID);
+CREATE INDEX idxLinkExtId ON LinkTable (extID);
+CREATE TABLE LinkAncestryTable (LinkID INTEGER PRIMARY KEY, extSystem INTEGER, LinkType INTEGER, rmID INTEGER, extID TEXT, Modified INTEGER, extVersion TEXT, extDate FLOAT, Status INTEGER, Note BLOB );
+CREATE INDEX idxLinkAncestryRmId ON LinkAncestryTable (rmID);
+CREATE INDEX idxLinkAncestryExtId ON LinkAncestryTable (extID);
+CREATE TABLE RoleTable (RoleID INTEGER PRIMARY KEY, RoleName TEXT COLLATE RMNOCASE, EventType INTEGER, RoleType INTEGER, Sentence TEXT );
+CREATE INDEX idxRoleEventType ON RoleTable (EventType);
+CREATE TABLE GroupTable (RecID INTEGER PRIMARY KEY, GroupID INTEGER, StartID INTEGER, EndID INTEGER );
+CREATE TABLE ExclusionTable (RecID INTEGER PRIMARY KEY, ExclusionType INTEGER, ID1 INTEGER, ID2 INTEGER );
+CREATE UNIQUE INDEX idxExclusionIndex ON ExclusionTable (ExclusionType, ID1, ID2);
+CREATE TABLE SourceTemplateTable (TemplateID INTEGER PRIMARY KEY, Name TEXT COLLATE RMNOCASE, Description TEXT, Favorite INTEGER, Category TEXT, Footnote TEXT, ShortFootnote TEXT, Bibliography TEXT, FieldDefs BLOB );
+CREATE INDEX idxSourceTemplateName ON SourceTemplateTable (Name);
+CREATE TABLE LabelTable (LabelID INTEGER PRIMARY KEY, LabelType INTEGER, LabelValue INTEGER, LabelName TEXT COLLATE RMNOCASE, Description TEXT );
+CREATE INDEX idxLabelType ON LabelTable (LabelType);
+CREATE TABLE URLTable (LinkID INTEGER PRIMARY KEY, OwnerType INTEGER, OwnerID INTEGER, LinkType INTEGER, Name TEXT, URL TEXT, Note BLOB );
+CREATE INDEX idxURLOwnerID ON URLTable (OwnerID);
blob - /dev/null
blob + cf9fe4aefb1e56364fed1bda630c880af8fb5131 (mode 644)
--- /dev/null
+++ cmd/timenet.go
@@ -0,0 +1,31 @@
+/*
+ * timenet
+ *   - http://vis.stanford.edu/papers/timenets
+ *   - http://vis.berkeley.edu/courses/cs294-10-sp10/wiki/images/f/f2/Family_Tree_Visualization_-_Final_Paper.pdf
+ * timeline
+ *   - https://github.com/davorg/svg-timeline-genealogy
+ * graphviz:
+ *   - https://github.com/adrienverge/familytreemaker
+ *   - https://github.com/vmiklos/ged2dot
+ *   - моя реализация
+ * [GEPS 030: New Visualization Techniques](https://www.gramps-project.org/wiki/index.php/GEPS_030:_New_Visualization_Techniques)
+ * [Geneaquilts](https://aviz.fr/geneaquilts/)
+ * https://github.com/nicolaskruchten/genealogy
+ */
+
+package main
+
+import (
+	"github.com/ajstarks/svgo"
+	"os"
+)
+
+func drawTimenet() {
+	width := 500
+	height := 500
+	canvas := svg.New(os.Stdout)
+	canvas.Start(width, height)
+	canvas.Circle(width/2, height/2, 100)
+	canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white")
+	canvas.End()
+}