home *** CD-ROM | disk | FTP | other *** search
/ ftp.cs.arizona.edu / ftp.cs.arizona.edu.tar / ftp.cs.arizona.edu / icon / historic / v941.tgz / icon.v941src.tar / icon.v941src / ipl / procs / gedcom.icn < prev    next >
Text File  |  2002-03-25  |  11KB  |  418 lines

  1. ############################################################################
  2. #
  3. #    File:     gedcom.icn
  4. #
  5. #    Subject:  Procedures for reading GEDCOM files
  6. #
  7. #    Author:   Gregg M. Townsend
  8. #
  9. #    Date:     March 25, 2002
  10. #
  11. ############################################################################
  12. #
  13. #   This file is in the public domain.
  14. #
  15. ############################################################################
  16. #
  17. #    These procedures read and interpret GEDCOM files, a standard
  18. #    format for genealogy databases.
  19. #
  20. ############################################################################
  21. #
  22. #    gedload(f) loads GEDCOM data from file f and returns a gedcom
  23. #    record containing the following fields:
  24. #        tree    root of tree of gednode records
  25. #        id        table of labeled nodes, indexed by @ID@
  26. #        fam        list of FAM nodes (marriages)
  27. #        ind        list of INDI nodes (individuals)
  28. #
  29. #    The tree is composed of gednode records R containing these fields:
  30. #        level    level
  31. #        id        ID (label), including @...@ delimiters
  32. #        tag        tag
  33. #        data    data
  34. #        lnum    line number
  35. #        parent    parent node in tree
  36. #        ref        referenced node, if any
  37. #        sub        sub-entry list
  38. #        hcode    unique hashcode, if INDI node
  39. #
  40. #    gedwalk(tree) generates the nodes of the tree in preorder.
  41. #
  42. #    Three procedures find descendants of a node based on a sequence
  43. #    of identifying tag strings:
  44. #        gedsub(R, tag...) generates subnodes specified by tag sequence
  45. #        gedval(R, tag...) generates data values of those subnodes
  46. #        gedref(R, tag...) generates nodes referenced by those subnodes
  47. #
  48. #    Three procedures extract a person's name from an INDI record:
  49. #        gedfnf(R)    produces "John Quincy Adams" form
  50. #        gedlnf(R)    produces "Adams, John Quincy" form
  51. #        gednmf(R,f)    produces an arbitrary format, substituting
  52. #            prefix, firstname, lastname, suffix for
  53. #            "P", "F", "L", "S" (respectively) in f
  54. #
  55. #    geddate(R) finds the DATE subnode of a node and returns a string
  56. #    of at least 12 characters in a standard form such as "11 Jul 1767"
  57. #    or "abt 1810".  It is assumed that the input is in English.
  58. #
  59. #    gedyear(R) returns the year from the DATE subnode of a node.
  60. #
  61. #    gedfind(g,s) generates the individuals under gedcom record g
  62. #    that are named by s, a string of whitespace-separated words.
  63. #    gedfind() generates each INDI node for which every word of s
  64. #    is matched by either a word of the individual's name or by
  65. #    the birth year.  Matching is case-insensitive.
  66. #
  67. ############################################################################
  68.  
  69. record gedcom(
  70.    tree,    # tree of data records
  71.    id,        # table of labeled nodes, indexed by @ID@
  72.    fam,        # list of FAM nodes
  73.    ind        # list of INDI nodes
  74. )
  75.  
  76. record gednode(
  77.    level,    # level
  78.    id,        # ID (label), including @...@ delimiters
  79.    tag,        # tag
  80.    data,    # data
  81.    lnum,    # line number
  82.    parent,    # parent node in tree
  83.    ref,        # referenced node, if any
  84.    sub,        # sub-entry list
  85.    hcode    # hashcode, if INDI node
  86. )
  87.  
  88. $define WHITESPACE ' \t\n\r'
  89.  
  90.  
  91.  
  92. #  gedload(f) -- load GEDCOM data from file f, returning gedcom record.
  93.  
  94. procedure gedload(f)        #: load GEDCOM data from file f
  95.    local line, lnum, r, curr
  96.    local root, id, fam, ind
  97.    local hset, h1, h2, c
  98.  
  99.    lnum := 0
  100.    root := curr := gednode(-1, , "ROOT", "", lnum, , , [])
  101.    id := table()
  102.    fam := []
  103.    ind := []
  104.  
  105.    while line := read(f) do {
  106.       lnum +:= 1
  107.       if *line = 0 then
  108.          next
  109.  
  110.       if not (r := gedscan(line)) then {
  111.          write(&errout, "ERR, line ", lnum, ": ", line)
  112.          next
  113.       }
  114.       r.lnum := lnum
  115.       r.sub := []
  116.  
  117.       if r.tag == "CONC" then {        # continuation line (no \n)
  118.          curr.data ||:= r.data
  119.          next
  120.          }
  121.       if r.tag == "CONT" then {        # continuation line (with \n)
  122.          curr.data ||:= "\n" || r.data
  123.          next
  124.          }
  125.  
  126.       while curr.level >= r.level do
  127.          curr := curr.parent
  128.       put(curr.sub, r)
  129.       r.parent := curr
  130.       curr := r
  131.  
  132.       id[\r.id] := r
  133.       case r.tag of {
  134.          "FAM":  put(fam, r)
  135.          "INDI":  put(ind, r)
  136.       }
  137.    }
  138.  
  139.    every r := gedwalk(root) do
  140.       r.ref := id[r.data]
  141.  
  142.    hset := set()
  143.    every r := !ind do {
  144.       h1 := h2 := gedhi(r)
  145.       every c := !"123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" do
  146.          if member(hset, h2) then
  147.             h2 := h1 || c    # add disambiguating suffix if needed
  148.          else
  149.             break
  150.       insert(hset, r.hcode := h2)
  151.       }
  152.  
  153.    return gedcom(root, id, fam, ind)
  154. end
  155.  
  156.  
  157.  
  158. #  gedscan(f) -- scan one line of a GEDCOM record, returning gednode record
  159.  
  160. procedure gedscan(s)        # (internal procedure)
  161.    local level, id, tag, data
  162.    static alnum
  163.    initial alnum := &letters ++ &digits ++ '_'
  164.  
  165.    s ? {
  166.       tab(many(WHITESPACE))
  167.       level := tab(many(&digits)) | fail
  168.       tab(many(WHITESPACE))
  169.       if id := (="@" || tab(upto('@') + 1)) then
  170.          tab(many(WHITESPACE))
  171.       tag := tab(many(alnum)) | fail
  172.       tab(many(WHITESPACE))
  173.       data := tab(0)
  174.       return gednode(level, id, tag, data)
  175.       }
  176. end
  177.  
  178.  
  179.  
  180. #  gedwalk(r) -- walk GEDCOM tree, generating nodes in preorder
  181.  
  182. procedure gedwalk(r)        #: generate GEDCOM tree nodes in preorder
  183.    suspend r | gedwalk(!r.sub)
  184.    fail
  185. end
  186.  
  187.  
  188.  
  189. #  gedsub(r, field...) -- generate subrecords with given tags
  190. #  gedval(r, field...) -- generate values of subrecords with given tags
  191. #  gedref(r, field...) -- generate nodes referenced by given tags
  192.  
  193. procedure gedsub(r, f[])    #: find subrecords
  194.    local tag, x
  195.  
  196.    tag := get(f) | fail
  197.    every x := !r.sub do {
  198.       if x.tag == tag then
  199.          if *f > 0 then
  200.             suspend gedsub ! push(f, x)
  201.          else
  202.             suspend x
  203.    }
  204. end
  205.  
  206. procedure gedval(a[])        #: find subrecord values
  207.    suspend (gedsub ! a).data
  208. end
  209.  
  210. procedure gedref(a[])        #: find referenced nodes
  211.    suspend \(gedsub ! a).ref
  212. end
  213.  
  214.  
  215.  
  216. #  gedfnf(r) -- get name from individual record, first name first
  217.  
  218. procedure gedfnf(r)        #: get first name first
  219.    return gednmf(r, "P F L S")
  220. end
  221.  
  222.  
  223.  
  224. #  gedlnf(r) -- get name from individual record, last name first
  225.  
  226. procedure gedlnf(r)        #: get last name first
  227.    local s
  228.    s := gednmf(r, "L, P F S")
  229.    s ? {
  230.       =", "
  231.       return tab(0)
  232.       }
  233. end
  234.  
  235.  
  236.  
  237. #  gednmf(r, f) -- general name formatter
  238. #
  239. #  substitutes the first name, last name, prefix, and suffix
  240. #  for the letters F, L, P, S respectively in string f.
  241. #  multiple spaces are suppressed.
  242.  
  243. procedure gednmf(r, f)        #: format name
  244.    local c, s, prefix, first, last, suffix
  245.  
  246.    prefix := gedval(r, "TITL" | "NPFX") | gedval(r, "NAME", "NPFX")
  247.    s := gedval(r, "NAME") | fail
  248.    s ? {
  249.       first := trim(tab(upto('/') | 0))
  250.       ="/"
  251.       last := trim(tab(upto('/') | 0))
  252.       ="/"
  253.       suffix := gedval(r, "NSFX") | ("" ~== tab(0))
  254.    }
  255.    s := ""
  256.    f ? {
  257.       while s ||:= tab(upto('PFLS ')) do {
  258.          while c := tab(any('PFLS ')) do {
  259.             s ||:= case c of {
  260.                "P": \prefix
  261.                "F": \first
  262.                "L": \last
  263.                "S": \suffix
  264.                " ": s[-1] ~== " "
  265.                }
  266.             }
  267.          }
  268.       s ||:= tab(0)
  269.       }
  270.    return trim(s)
  271. end
  272.  
  273.  
  274.  
  275. #  geddate(r) -- get date from record in standard form
  276.  
  277. procedure geddate(r)        #: get canonical date
  278.    local s, t, w
  279.    static ftab
  280.    initial {
  281.       ftab := table()
  282.       ftab["JAN"] := "Jan";  ftab["FEB"] := "Feb"; ftab["MAR"] := "Mar"
  283.       ftab["APR"] := "Apr";  ftab["MAY"] := "May"; ftab["JUN"] := "Jun"
  284.       ftab["JUL"] := "Jul";  ftab["AUG"] := "Aug"; ftab["SEP"] := "Sep"
  285.       ftab["OCT"] := "Oct";  ftab["NOV"] := "Nov"; ftab["DEC"] := "Dec"
  286.       ftab["ABT"] := "abt";  ftab["BEF"] := "bef"; ftab["AFT"] := "aft"
  287.       ftab["CAL"] := "cal";  ftab["EST"] := "est"
  288.       }
  289.  
  290.    s := trim(gedval(r, "DATE"), WHITESPACE) | fail
  291.    t := ""
  292.  
  293.    s ? while not pos(0) do {
  294.       tab(many(WHITESPACE))
  295.       w := tab(upto(WHITESPACE) | 0)
  296.       t ||:= " " || (\ftab[w] | w)
  297.    }
  298.  
  299.    if *t > 13 then
  300.       return t[2:0]
  301.    else
  302.       return right(t, 12)
  303. end
  304.  
  305.  
  306.  
  307. #  gedyear(r) -- get year from event record
  308.  
  309. procedure gedyear(r)        #: get year
  310.    local d, y
  311.  
  312.    d := gedval(r, "DATE") | fail
  313.    d ? while tab(upto(&digits)) do
  314.       if (y := tab(many(&digits)) \ 1) >= 1000 then
  315.          return y
  316. end
  317.  
  318.  
  319.  
  320. #  gedhi -- generate hashcode for individual record
  321. #
  322. #  The hashcode uses two initials, final digits of birth year,
  323. #  and a 3-letter hashing of the full name and birthdate fields.
  324.  
  325. procedure gedhi(r)        # (internal procedure)
  326.    local s, name, bdate, bd
  327.    static lc, uc
  328.    initial {
  329.       uc := string(&ucase)
  330.       lc := string(&lcase)
  331.       }
  332.  
  333.    s := ""
  334.    name := gedval(r, "NAME") | ""
  335.    name ? {
  336.       # prefer initial of nickname; else skip unused firstname in parens
  337.       tab(upto('"') + 1) | (="(" & tab(upto(')') + 1))
  338.       tab(any(' \t'))
  339.       s ||:= tab(any(&letters)) | "X"        # first initial
  340.       tab(upto('/') + 1)
  341.       tab(any(' \t'))
  342.       s ||:= tab(any(&letters)) | "X"        # second initial
  343.    }
  344.  
  345.    bdate := geddate(gedsub(r, "BIRT")) | ""
  346.    bd := bdate[-2:0] | "00"
  347.    if not (bd ? (tab(many(&digits)) & pos(0))) then
  348.       bd := "99" 
  349.    s ||:= bd || gedh3a(name || bdate)
  350.    return map(s, lc, uc)
  351. end
  352.  
  353.  
  354.  
  355. #  gedh3a(s) -- hash arbitrary string into three alphabetic characters
  356.  
  357. procedure gedh3a(s)        # (internal procedure)
  358.    local n, d1, d2, d3, c
  359.  
  360.    n := 0
  361.    every c := !map(s) do
  362.       if not upto(' \t\f\r\n', c) then
  363.          n := 37 * n + ord(c) - 32
  364.    d1 := 97 + (n / 676) % 26
  365.    d2 := 97 + (n / 26) % 26
  366.    d3 := 97 + n % 26
  367.    return char(d1) || char(d2) || char(d3)
  368. end
  369.  
  370.  
  371.  
  372. #  gedfind(g, s) -- find records by name from gedcom record
  373. #
  374. #  g is a gedcom record; s is a string of whitespace-separated words.
  375. #  gedfind() generates each INDI node for which every word of s
  376. #  is matched by either a word of the individual's name or by
  377. #  the birth year.  Matching is case-insensitive.
  378.  
  379. procedure gedfind(g, s)        #: find individual by name
  380.    local r
  381.    
  382.    every r := !g.ind do 
  383.       if gedmatch(r, s) then
  384.          suspend r
  385. end
  386.  
  387.  
  388. #  gedmatch(r, s) -- match record against name
  389. #
  390. #  s is a string of words to match name field and/or birth year.
  391. #  Matching is case sensitive.
  392.  
  393. procedure gedmatch(r, s)    # (internal procedure)
  394.    local w
  395.  
  396.    every w := gedlcw(s) do
  397.       (w == (gedlcw(gedval(r, "NAME")) | gedyear(gedsub(r, "BIRT")))) | fail
  398.    return r
  399. end
  400.  
  401.  
  402.  
  403. #  gedlcw(s, c) -- generate words from string s separated by chars from c
  404. #
  405. #  words are mapped to lower-case to allow case-insensitive comparisons
  406.  
  407. procedure gedlcw(s, c)        # (internal procedure)
  408.    /c := '/ \t\r\n\v\f'
  409.    map(s) ? {
  410.       tab(many(c))
  411.       while not pos(0) do {
  412.          suspend tab(upto(c) | 0) \ 1
  413.          tab(many(c))
  414.          }
  415.       }
  416.    fail
  417. end
  418.