(* Copyright (c) 1998-2007 INRIA *) open Def open Gwdb (** .gwo file header *) let magic_gwo = "GnWo000o" (* Option qui force a créé les clés des individus. De fait, *) (* si la clé est incomplète, on l'enregistre tout de même. *) let create_all_keys = ref false type key = { pk_first_name : string; pk_surname : string; pk_occ : int } (** Key to refer a person's definition *) (** Represents a person in .gw file. It could be either reference to a person (only key elements provided) or definition (all information provided). *) type somebody = | Undefined of key (** Reference to person *) | Defined of (iper, iper, string) gen_person (** Person's definition *) (** Blocks that could appear in .gw file. *) type gw_syntax = | Family of somebody gen_couple * sex * sex * (somebody * sex) list * (string gen_fam_event_name * cdate * string * string * string * string * (somebody * sex * witness_kind) list) list * ((iper, iper, string) gen_person, ifam, string) gen_family * (iper, iper, string) gen_person gen_descend (** Family definition block. Contains: - Family couple (father's and mother's definition/reference) - Father's sex - Mother's sex - List of witnesses definition/reference with their sex. - List of information about every family event (name, date, place, reason, source, notes and witnesses) - Family definition - Children (descendants) *) | Notes of key * string (** Block that defines personal notes. First element represents reference to person. Second is note's content. *) | Relations of somebody * sex * (somebody, string) gen_relation list (** Block that defines relations of a person with someone outisde of family block (like foster parents) (field {i rparents}). Contains: - Concerned person definition/reference - Sex of person - List of his relations. *) | Pevent of somebody * sex * (string gen_pers_event_name * cdate * string * string * string * string * (somebody * sex * witness_kind) list) list (** Block that defines events of a person. Specific to gwplus format. Contains: - Concerned person's definition/reference - Sex of person - List of information about every personal event (name, date, place, reason, source, notes and witnesses)*) | Bnotes of string * string (** Block that defines database notes and extended pages. First string represents name of extended page ("" for database notes, only one for file). Second is note's or page's content. *) | Wnotes of string * string (** Block that defines wizard notes. First string represents First string represents wizard's id. Second is note's content. *) (** {i .gw} file encoding *) type encoding = E_utf_8 | E_iso_8859_1 (** [copy_decode s i1 i2] decode the word delimited by [i1] and [i2] inside [s] by remplacing "\\" -> '\' and '_' -> ' ' *) let copy_decode s i1 i2 = let len = let rec loop len i = if i >= i2 then len else if i = i2 - 1 then len + 1 else if s.[i] = '\\' then loop (len + 1) (i + 2) else loop (len + 1) (i + 1) in loop 0 i1 in let rec loop_copy t i j = if i >= i2 then Bytes.unsafe_to_string t else if i = i2 - 1 && s.[i] <> '_' then ( Bytes.set t j s.[i]; Bytes.unsafe_to_string t) else let c, i = match s.[i] with | '_' -> (' ', i) | '\\' -> (s.[i + 1], i + 1) | x -> (x, i) in Bytes.set t j c; loop_copy t (succ i) (succ j) in loop_copy (Bytes.create len) i1 0 (** Return list of words inside the [str] *) let fields str = let rec loop beg i = if i < String.length str then match str.[i] with | ' ' | '\t' -> if beg = i then loop (succ beg) (succ i) else copy_decode str beg i :: loop (succ i) (succ i) | _ -> loop beg (succ i) else if beg = i then [] else [ copy_decode str beg i ] in loop 0 0 (** Removes spaces at the begining an at the end of string. *) let cut_space x = let len = String.length x in if len = 0 then x else if x = " " then "" else let start = if x.[0] = ' ' then 1 else 0 in let stop = if x.[len - 1] = ' ' then len - 1 else len in if start = 0 && stop = len then x else String.sub x start (stop - start) (** Returns field if its label [lab] is first element of [l] *) let get_field lab l = match l with | lab1 :: x :: l' when lab1 = lab -> (cut_space x, l') | _ -> ("", l) (** Parses [Def.date] from string that starts at pos [i] inside [s] *) let date_of_string s i = let champ i = let neg, i = if i < String.length s && s.[i] = '-' then (true, i + 1) else (false, i) in let rec loop i n = if i = String.length s then ((if neg then -n else n), i) else match s.[i] with | '0' .. '9' as c -> loop (succ i) ((10 * n) + Char.code c - Char.code '0') | _ -> ((if neg then -n else n), i) in loop i 0 in let skip_slash i = if i < String.length s && s.[i] = '/' then Some (succ i) else None in let precision, i = match s.[i] with | '~' -> (About, succ i) | '?' -> (Maybe, succ i) | '>' -> (After, succ i) | '<' -> (Before, succ i) | _ -> (Sure, i) in let undefined, year, i = let year, j = champ i in if j = i + 1 && s.[i] = '0' then (true, year, j) else (false, year, j) in let error n = failwith (Printf.sprintf "date_of_string%d %s" n s) in let dmy2 year2 i = match skip_slash i with | Some i -> ( let month2 = year2 in let year2, i = champ i in match skip_slash i with | Some i -> let day2 = month2 in let month2 = year2 in let year2, i = champ i in if month2 < 1 || month2 > 13 then error 2 else if day2 < 1 || day2 > 31 then error 3 else ((day2, month2, year2), i) | None -> if month2 < 1 || month2 > 13 then error 4 else ((0, month2, year2), i)) | None -> ((0, 0, year2), i) in let date = match skip_slash i with | Some i -> ( let month = year in let year, i = champ i in match skip_slash i with | Some i -> let day = month in let month = year in let year, i = champ i in (* if year = 0 then if i = String.length s then None else error 1 else *) if month < 1 || month > 13 then error 2 else if day < 1 || day > 31 then error 3 else let d = { day; month; year; prec = precision; delta = 0 } in Some (Dgreg (d, Dgregorian), i) | None -> if year = 0 then None else if month < 1 || month > 13 then error 4 else let d = { day = 0; month; year; prec = precision; delta = 0 } in Some (Dgreg (d, Dgregorian), i)) | None -> if undefined then if i = String.length s then None else if s.[i] = '(' && s.[String.length s - 1] = ')' then let txt = String.sub s (i + 1) (String.length s - i - 2) in let txt = cut_space txt in let txt = copy_decode txt 0 (String.length txt) in Some (Dtext txt, String.length s) else failwith ("date_of_string " ^ s) else let d = { day = 0; month = 0; year; prec = precision; delta = 0 } in Some (Dgreg (d, Dgregorian), i) in let date = match date with | Some ((Dgreg (d, cal) as dt), i) -> if i = String.length s then Some (dt, i) else if s.[i] = '|' then let year2, i = champ (succ i) in let (day2, month2, year2), i = dmy2 year2 i in let dmy2 = { day2; month2; year2; delta2 = 0 } in Some (Dgreg ({ d with prec = OrYear dmy2 }, cal), i) else if i + 1 < String.length s && s.[i] = '.' && s.[i + 1] = '.' then let year2, i = champ (i + 2) in let (day2, month2, year2), i = dmy2 year2 i in let dmy2 = { day2; month2; year2; delta2 = 0 } in Some (Dgreg ({ d with prec = YearInt dmy2 }, cal), i) else Some (dt, i) | Some ((Dtext _ as dt), i) -> Some (dt, i) | None -> None in let date = match date with | Some (Dgreg (d, _), i) -> ( if i = String.length s then Some (Dgreg (d, Dgregorian), i) else match s.[i] with | 'G' -> Some (Dgreg (d, Dgregorian), i + 1) | 'J' -> let d = Calendar.gregorian_of_julian d in Some (Dgreg (d, Djulian), i + 1) | 'F' -> let d = Calendar.gregorian_of_french d in Some (Dgreg (d, Dfrench), i + 1) | 'H' -> let d = Calendar.gregorian_of_hebrew d in Some (Dgreg (d, Dhebrew), i + 1) | _ -> Some (Dgreg (d, Dgregorian), i)) | d -> d in match date with | Some (dt, i) -> if i = String.length s then Some dt else error 5 | None -> None (** Line counter while reading .gw file *) let line_cnt = ref 0 (** Do not raise exception if syntax error occured. Instead print error information on stdout *) let no_fail = ref false (** Save path to the images *) let no_picture = ref false (** Read line from input channel. *) let input_line0 ic = let line = input_line ic in incr line_cnt; if String.length line > 0 && line.[String.length line - 1] = '\r' then String.sub line 0 (String.length line - 1) else line (** Read a line and convert it to [encoding]. *) let input_a_line (ic, encoding) = let line = input_line0 ic in match encoding with | E_utf_8 -> line | E_iso_8859_1 -> Mutil.utf_8_of_iso_8859_1 line (** Read a line. If line is empty or only contains a comment, then read next line *) let rec input_real_line ic = let x = input_a_line ic in if x = "" || x.[0] = '#' then input_real_line ic else x (** Parses person's birth date if it is present. *) let get_optional_birthdate l = match l with | x :: l' -> ( let i = 0 in if x.[i] = '!' then (None, l) else match x.[i] with | '~' | '?' | '<' | '>' | '-' | '0' .. '9' -> let d = date_of_string x i in (Some d, l') | _ -> (None, l)) | _ -> (None, l) (** Parses person's baptism date if it is present. *) let get_optional_baptdate l = match l with | x :: l' -> let i = 0 in if x.[i] = '!' then let i = succ i in match x.[i] with | '~' | '?' | '<' | '>' | '-' | '0' .. '9' -> let d = date_of_string x i in (Some d, l') | _ -> (None, l) else (None, l) | _ -> (None, l) (** Parse death information if present. *) let get_optional_deathdate l = match l with | "?" :: l' -> (Some DontKnowIfDead, l') | "mj" :: l' -> (Some DeadYoung, l') | "od" :: l' -> (Some OfCourseDead, l') | x :: l' -> let i = 0 in let dr, i = match x.[i] with | 'k' -> (Killed, i + 1) | 'm' -> (Murdered, i + 1) | 'e' -> (Executed, i + 1) | 's' -> (Disappeared, i + 1) | _ -> (Unspecified, i) in if i < String.length x then match x.[i] with | '~' | '?' | '>' | '<' | '-' | '0' .. '9' -> let d = match date_of_string x i with | None -> DeadDontKnowWhen | Some d -> Death (dr, Date.cdate_of_date d) in (Some d, l') | _ -> (None, l) else (None, l) | _ -> (None, l) (** Parse burial information if present. *) let get_burial l = match l with | "#buri" :: l -> ( match l with | x :: l' -> let i = 0 in let od, l = match x.[i] with | '~' | '?' | '>' | '<' | '-' | '0' .. '9' -> (date_of_string x i, l') | _ -> (None, l) in (Buried (Date.cdate_of_od od), l) | [] -> (Buried Date.cdate_None, l)) | "#crem" :: l -> ( match l with | x :: l' -> let i = 0 in let od, l = match x.[i] with | '~' | '?' | '>' | '<' | '-' | '0' .. '9' -> (date_of_string x i, l') | _ -> (None, l) in (Cremated (Date.cdate_of_od od), l) | [] -> (Cremated Date.cdate_None, l)) | _ -> (UnknownBurial, l) (** Parse sex of person *) let get_optional_sexe = function | "h" :: l -> (Male, l) | "f" :: l -> (Female, l) | l -> (Neuter, l) (** Parses int that starts at the position [i] inside [x]. Raises [Not_found] if integer isn't found. *) let make_int x = let rec loop found n i = if i = String.length x then if found then n else raise Not_found else match x.[i] with | '0' .. '9' as c -> loop true ((10 * n) + Char.code c - Char.code '0') (succ i) | _ -> raise Not_found in loop false 0 (** Parses person's first name and occurence number. Occurence number is 0 if not present. *) let get_fst_name str l = match l with | x :: l' -> ( match x.[0] with (*'a'..'z' | 'A'..'Z' | 'à'..'ÿ' | 'À'..'Ý' *) | 'a' .. 'z' | 'A' .. 'Z' | '\xE0' .. '\xFF' | '\xC0' .. '\xDD' | '[' | '0' .. '9' | '?' | ' ' -> let x = cut_space x in let x, occ = match String.rindex_opt x '.' with | Some i -> ( try (String.sub x 0 i, make_int x (succ i)) with Not_found -> (x, 0)) | None -> (x, 0) in (x, occ, l') | _ -> failwith str) | _ -> failwith str (** Parses person's first name aliases if they are present *) let rec get_fst_names_aliases str l = match l with | x :: l' -> if x.[0] = '{' && x.[String.length x - 1] = '}' then let n = String.sub x 1 (String.length x - 2) in let nl, l = get_fst_names_aliases str l' in (cut_space n :: nl, l) else ([], l) | [] -> ([], l) (** Parses person's surname aliases if they are present *) let rec get_surnames_aliases str l = match l with | "#salias" :: x :: l' -> let nl, l = get_surnames_aliases str l' in (cut_space x :: nl, l) | _ -> ([], l) (** Parses person's qualifiers if they are present *) let rec get_qualifiers str l = match l with | "#nick" :: x :: l' -> let nl, l = get_qualifiers str l' in (cut_space x :: nl, l) | _ -> ([], l) (** Parses person's aliases if they are present *) let rec get_aliases str l = match l with | "#alias" :: x :: l' -> let nl, l = get_aliases str l' in (cut_space x :: nl, l) | _ -> ([], l) (** [get_name l] parses a last name. Looks up first element of the list and returns a [(name,rest)] couple where [name] is a person's last name and [rest] is a tail of the list. If first element is [#nick], [#alias] start with '{' returns empty string and list unchanged. *) let get_name l = match l with | "#nick" :: _ | "#alias" :: _ -> ("", l) | x :: l' -> ( match x.[0] with | '{' -> ("", l) (*'a'..'z' | 'A'..'Z' | 'à'..'ÿ' | 'À'..'Ý' *) | 'a' .. 'z' | 'A' .. 'Z' | '\xE0' .. '\xFF' | '\xC0' .. '\xDD' | '0' .. '9' | '?' | ' ' -> (cut_space x, l') | _ -> ("", l)) | _ -> ("", l) (** Parses person's public name if present *) let get_pub_name l = match l with | x :: l' -> if x.[0] = '(' && x.[String.length x - 1] = ')' then let a = String.sub x 1 (String.length x - 2) in (cut_space a, l') else ("", l) | _ -> ("", l) (** Parses person's image path if present *) let get_image l = match l with | ("#image" | "#photo") :: x :: l' -> if !no_picture then ("", l') else (cut_space x, l') | _ -> ("", l) (** Parses person's occupation if present *) let get_occu l = match l with "#occu" :: x :: l' -> (cut_space x, l') | _ -> ("", l) (** Parses person's source if present *) let get_sources l = match l with "#src" :: x :: l' -> (cut_space x, l') | _ -> ("", l) (** Parses person's acces rights *) let get_access l = match l with | "#apubl" :: l' -> (Public, l') | "#apriv" :: l' -> (Private, l') | _ -> (IfTitles, l) (** Create [gen_title] from string *) let scan_title t = let next_field i = let rec loop s i = if i < String.length t then match t.[i] with | ':' -> (s, i + 1) | '\\' -> loop (s ^ String.make 1 t.[i + 1]) (i + 2) | c -> loop (s ^ String.make 1 c) (i + 1) else (s, i) in loop "" i in let i = 0 in let name, i = let s, i = next_field i in if i = String.length t then failwith t else (s, i) in let name = match name with "" -> Tnone | "*" -> Tmain | _ -> Tname name in let title, i = let s, i = next_field i in if t.[i - 1] <> ':' then failwith t else (s, i) in let place, i = next_field i in let date_start, i = let d, i = next_field i in ((if d = "" then None else date_of_string d 0), i) in let date_end, i = let d, i = next_field i in ((if d = "" then None else date_of_string d 0), i) in let nth, i = let d, i = next_field i in ((if d = "" then 0 else int_of_string d), i) in if i <> String.length t then failwith t else { t_name = name; t_ident = title; t_place = place; t_date_start = Date.cdate_of_od date_start; t_date_end = Date.cdate_of_od date_end; t_nth = nth; } (** Parses list of titles ([gen_title]) if they are present. *) let rec get_titles str l = match l with | x :: l' -> if x.[0] = '[' && x.[String.length x - 1] = ']' then let t = String.sub x 1 (String.length x - 2) in let t = scan_title t in let al, l' = get_titles str l' in ((if t.t_ident = "" then al else t :: al), l') else ([], l) | _ -> ([], l) (** Parses person's event name *) let get_pevent_name str l = match l with | "#birt" :: l' -> (Epers_Birth, l') | "#bapt" :: l' -> (Epers_Baptism, l') | "#deat" :: l' -> (Epers_Death, l') | "#buri" :: l' -> (Epers_Burial, l') | "#crem" :: l' -> (Epers_Cremation, l') | "#acco" :: l' -> (Epers_Accomplishment, l') | "#acqu" :: l' -> (Epers_Acquisition, l') | "#adhe" :: l' -> (Epers_Adhesion, l') | "#awar" :: l' -> (Epers_Decoration, l') | "#bapl" :: l' -> (Epers_BaptismLDS, l') | "#barm" :: l' -> (Epers_BarMitzvah, l') | "#basm" :: l' -> (Epers_BatMitzvah, l') | "#bles" :: l' -> (Epers_Benediction, l') | "#cens" :: l' -> (Epers_Recensement, l') | "#chgn" :: l' -> (Epers_ChangeName, l') | "#circ" :: l' -> (Epers_Circumcision, l') | "#conf" :: l' -> (Epers_Confirmation, l') | "#conl" :: l' -> (Epers_ConfirmationLDS, l') | "#degr" :: l' -> (Epers_Diploma, l') | "#demm" :: l' -> (Epers_DemobilisationMilitaire, l') | "#dist" :: l' -> (Epers_Distinction, l') | "#dotl" :: l' -> (Epers_DotationLDS, l') | "#educ" :: l' -> (Epers_Education, l') | "#elec" :: l' -> (Epers_Election, l') | "#emig" :: l' -> (Epers_Emigration, l') | "#endl" :: l' -> (Epers_Dotation, l') | "#exco" :: l' -> (Epers_Excommunication, l') | "#fcom" :: l' -> (Epers_FirstCommunion, l') | "#flkl" :: l' -> (Epers_FamilyLinkLDS, l') | "#fune" :: l' -> (Epers_Funeral, l') | "#grad" :: l' -> (Epers_Graduate, l') | "#hosp" :: l' -> (Epers_Hospitalisation, l') | "#illn" :: l' -> (Epers_Illness, l') | "#immi" :: l' -> (Epers_Immigration, l') | "#lpas" :: l' -> (Epers_ListePassenger, l') | "#mdis" :: l' -> (Epers_MilitaryDistinction, l') | "#mobm" :: l' -> (Epers_MobilisationMilitaire, l') | "#mpro" :: l' -> (Epers_MilitaryPromotion, l') | "#mser" :: l' -> (Epers_MilitaryService, l') | "#natu" :: l' -> (Epers_Naturalisation, l') | "#occu" :: l' -> (Epers_Occupation, l') | "#ordn" :: l' -> (Epers_Ordination, l') | "#prop" :: l' -> (Epers_Property, l') | "#resi" :: l' -> (Epers_Residence, l') | "#reti" :: l' -> (Epers_Retired, l') | "#slgc" :: l' -> (Epers_ScellentChildLDS, l') | "#slgp" :: l' -> (Epers_ScellentParentLDS, l') | "#slgs" :: l' -> (Epers_ScellentSpouseLDS, l') | "#vteb" :: l' -> (Epers_VenteBien, l') | "#will" :: l' -> (Epers_Will, l') | s :: l' -> if s.[0] = '#' then (Epers_Name (String.sub s 1 (String.length s - 1)), l') else failwith str | _ -> failwith str (** Parses family event name *) let get_fevent_name str l = match l with | "#marr" :: l' -> (Efam_Marriage, l') | "#nmar" :: l' -> (Efam_NoMarriage, l') | "#nmen" :: l' -> (Efam_NoMention, l') | "#enga" :: l' -> (Efam_Engage, l') | "#div" :: l' -> (Efam_Divorce, l') | "#sep" :: l' -> (Efam_Separated, l') | "#anul" :: l' -> (Efam_Annulation, l') | "#marb" :: l' -> (Efam_MarriageBann, l') | "#marc" :: l' -> (Efam_MarriageContract, l') | "#marl" :: l' -> (Efam_MarriageLicense, l') | "#pacs" :: l' -> (Efam_PACS, l') | "#resi" :: l' -> (Efam_Residence, l') | s :: l' -> if s.[0] = '#' then (Efam_Name (String.sub s 1 (String.length s - 1)), l') else failwith str | _ -> failwith str (** Parses event date if it is present. *) let get_optional_event_date l = match l with | x :: l' -> ( let i = 0 in if x.[i] = '!' then (None, l) else match x.[i] with | '~' | '?' | '<' | '>' | '-' | '0' .. '9' -> let d = date_of_string x i in (Some d, l') | _ -> (None, l)) | _ -> (None, l) (** Parse witness kind *) let get_event_witness_kind l = match l with | "#godp" :: l' -> (Witness_GodParent, l') | "#offi" :: l' -> (Witness_CivilOfficer, l') | "#reli" :: l' -> (Witness_ReligiousOfficer, l') | "#info" :: l' -> (Witness_Informant, l') | "#atte" :: l' -> (Witness_Attending, l') | "#ment" :: l' -> (Witness_Mentioned, l') | "#othe" :: l' -> (Witness_Other, l') | _ -> (Witness, l) (** Parses the line containing an information about relationship between parents within family and returns [((relk, fath_sex, moth_sex), mar, place, note, src, divorce, rest)]. [relk] i a relation kind between parents ([Def.relation_kind]), [fath_sex] and [moth_sex] is a sex of each parent, [mar] is a optional mariage date (if married), [place] is a marriage place if present, [note] is a mariage note if present, [src] is a mariage source if present, [divorce] is a divorce status [Def.divorce], [rest] is the rest of the line to parse *) let get_mar_date str = function | x :: l -> let mar, l = match x.[0] with | '+' -> ( (if String.length x > 1 then Date.cdate_of_od (date_of_string x 1) else Date.cdate_None), l ) | _ -> failwith str in let relation, l = let decode_sex v c l = let decode_sex i = match c.[i] with | 'm' -> Male | 'f' -> Female | '?' -> Neuter | _ -> failwith __LOC__ in try ((v, decode_sex 0, decode_sex 1), l) with _ -> ((v, Male, Female), c :: l) in match l with | "#nm" :: l' -> ((NotMarried, Male, Female), l') | "#eng" :: l' -> ((Engaged, Male, Female), l') | "#noment" :: c :: l' when String.length c = 2 -> decode_sex NoMention c l' | "#noment" :: l' -> ((NoMention, Male, Female), l') | "#nsck" :: c :: l' when String.length c = 2 -> decode_sex NoSexesCheckNotMarried c l' | "#nsckm" :: c :: l' when String.length c = 2 -> decode_sex NoSexesCheckMarried c l' | "#banns" :: c :: l' when String.length c = 2 -> decode_sex MarriageBann c l' | "#contract" :: c :: l' when String.length c = 2 -> decode_sex MarriageContract c l' | "#license" :: c :: l' when String.length c = 2 -> decode_sex MarriageLicense c l' | "#pacs" :: c :: l' when String.length c = 2 -> decode_sex Pacs c l' | "#residence" :: c :: l' when String.length c = 2 -> decode_sex Residence c l' | _ -> ((Married, Male, Female), l) in let place, l = get_field "#mp" l in let note, l = get_field "#mn" l in let src, l = get_field "#ms" l in let divorce, l = match l with | x :: l' when x.[0] = '-' -> if String.length x > 1 then (Divorced (Date.cdate_of_od (date_of_string x 1)), l') else (Divorced Date.cdate_None, l') | "#sep" :: l' -> (Separated, l') | _ -> (NotDivorced, l) in (relation, mar, place, note, src, divorce, l) | [] -> failwith str (** Read and return a line with list of words that appears on this line. If reading raises [Enf_of_file] returns [None] *) let read_line ic = try let str = input_real_line ic in Some (str, fields str) with End_of_file -> None (** Create a dummy [gen_person]. *) let create_person () = { (Mutil.empty_person "" "") with key_index = Gwdb.dummy_iper } (** Person is unknown (bogus definition) *) let bogus_def p n = p = "?" || n = "?" (** Parse the line and create person's [gen_person] definition. Doesn't modify following personal information: - Key - Parents - Related persons - Events - Notes If can't parse person's sources use [comm_psources] instead. If can't parse bithdate use [comm_birth_place] instead. *) let set_infos fn sn occ sex comm_psources comm_birth_place str u l = let first_names_aliases, l = get_fst_names_aliases str l in let surnames_aliases, l = get_surnames_aliases str l in let public_name, l = get_pub_name l in let image, l = get_image l in let qualifiers, l = get_qualifiers str l in let aliases, l = get_aliases str l in let titles, l = get_titles str l in let access, l = get_access l in let occupation, l = get_occu l in let psources, l = get_sources l in let naissance, l = get_optional_birthdate l in let birth_place, l = get_field "#bp" l in let birth_note, l = get_field "#bn" l in let birth_src, l = get_field "#bs" l in let baptism, l = get_optional_baptdate l in let baptism_place, l = let pp, l = get_field "#pp" l in (* if no baptism place then it's equals to birth place *) if pp = "" then get_field "#bp" l else (pp, l) in let bapt_note, l = get_field "#pn" l in let bapt_src, l = get_field "#ps" l in let mort, l = get_optional_deathdate l in let death_place, l = get_field "#dp" l in let death_note, l = get_field "#dn" l in let death_src, l = get_field "#ds" l in let mort = match (naissance, mort) with | None, _ | _, Some _ | Some None, _ -> ( match mort with Some m -> m | None -> DontKnowIfDead) | Some _, None -> NotDead in let naissance = match naissance with | None -> Date.cdate_None | Some x -> Date.cdate_of_od x in let baptism = match baptism with None -> Date.cdate_None | Some x -> Date.cdate_of_od x in let burial, l = get_burial l in let burial_place, l = get_field "#rp" l in let burial_note, l = get_field "#rn" l in let burial_src, l = get_field "#rs" l in let u = { first_name = fn; surname = sn; occ; rparents = u.rparents; related = u.related; sex; notes = u.notes; key_index = u.key_index; first_names_aliases; surnames_aliases; public_name; image; qualifiers; aliases; titles; access; occupation; psources = (if psources <> "" then psources else comm_psources); birth = naissance; birth_place = (if birth_place <> "" then birth_place else comm_birth_place); birth_note; birth_src; baptism; baptism_place; baptism_note = bapt_note; baptism_src = bapt_src; death = mort; death_place; death_note; death_src; burial; burial_place; burial_note; burial_src; pevents = u.pevents; } in (u, l) (** Parses the line containing a parent and returns [(somebody,np,rest)]. [somebody] is either [Defined p] if person's definiton was parsed ([p] regroups all personal information) either [Undefined k] if a reference to a person already defined was parsed ([k] is a key to find corresponding definition). [np] is a person's surname. [rest] is a rest of line to parse. Could be used to parse familial witnesses. *) let parse_parent str l = (* last name *) let np, l = get_name l in (* first name and occurence number *) let pp, op, l = get_fst_name str l in (* person is not defined as a child elsewhere (is defined here) *) let defined = if bogus_def pp np then true else match l with [] -> false | s :: _ when s.[0] = '+' -> false | _ -> true in if not defined then let key = { pk_first_name = pp; pk_surname = np; pk_occ = op } in (Undefined key, np, l) else let u = create_person () in let u, l = set_infos pp np op u.sex "" "" str u l in (Defined u, np, l) (** Parses the line containing a children and returns a person [gen_person] containing all extracted information. If a children definition doesn't provide surname then father's surname is used. ALso if it doesn't provide a children's birth place and source then it uses information provided by family definiton. *) let parse_child str surname sex csrc cbp l = let u = create_person () in let prenom, occ, l = get_fst_name str l in let nom, l = match l with | "?" :: _ -> get_name l | x :: _ -> ( match x.[0] with | '<' | '>' | '!' | '~' | '?' | '-' | '0' .. '9' | '{' | '#' -> (surname, l) | '(' | '[' -> ((if prenom = "" then "" else surname), l) | _ -> get_name l) | _ -> (surname, []) in set_infos prenom nom occ sex csrc cbp str u l (** Parse relation type [Def.gen_relation] with a person outside of family block (foster parents, god parent, etc.). *) let get_relation str = function | "-" :: x :: l -> ( let rtyp = match x with | "adop" | "adop:" -> Adoption | "reco" | "reco:" -> Recognition | "cand" | "cand:" -> CandidateParent | "godp" | "godp:" -> GodParent | "fost" | "fost:" -> FosterParent | _ -> failwith str in if String.length x = 5 && x.[4] = ':' then ( let fk, _, l = parse_parent str l in let l = match l with "+" :: l -> l | _ -> failwith str in let mk, _, l = parse_parent str l in if l <> [] then failwith str; { r_type = rtyp; r_fath = Some fk; r_moth = Some mk; r_sources = "" }) else match l with | "fath:" :: l -> let fk, _, l = parse_parent str l in if l <> [] then failwith str; { r_type = rtyp; r_fath = Some fk; r_moth = None; r_sources = "" } | "moth:" :: l -> let mk, _, l = parse_parent str l in if l <> [] then failwith str; { r_type = rtyp; r_fath = None; r_moth = Some mk; r_sources = "" } | _ -> failwith str) | _ -> failwith str (** Read notes of a person inside [note] block across multiple lines and concat them. *) let read_notes ic = let notes = try let rec loop = function | "end notes" -> "" | l -> l ^ "\n" ^ loop (input_a_line ic) in loop (input_a_line ic) with End_of_file -> failwith "end of file" in Mutil.strip_all_trailing_spaces notes (* from version 5.00 *) (** Read database notes across multiple lines and concat them. Stop reading when encounter [end_text] *) let read_notes_db ic end_txt = let notes = try let rec loop s = if s = end_txt then "" else let len = String.length s in let s = if len > 2 && s.[0] = ' ' && s.[1] = ' ' then String.sub s 2 (len - 2) else s in s ^ "\n" ^ loop (input_a_line ic) in loop (input_a_line ic) with End_of_file -> failwith "end of file" in Mutil.strip_all_trailing_spaces notes (** Parsing status of .gw block *) type 'a read_family = | F_some of 'a (** Read block inside .gw file *) | F_enc_utf_8 (** Read block that defines that file use utf-8 encoding *) | F_gw_plus (** Read block that defines that the file uses gwplus syntax *) | F_none (** Read end of the file *) | F_fail of string (** Exception while reading *) (** Read succesive family note lines and concat it. *) let loop_note line ic = let rec loop_note acc str = match fields str with | "note" :: tl -> let note = if tl = [] then "" else String.sub str (String.length "note" + 1) (String.length str - String.length "note" - 1) in loop_note (note :: acc) (input_a_line ic) | _ -> (String.concat "\n" (List.rev @@ ("" :: acc)), str) in loop_note [] line (** Parse witnesses across the lines and returns list of [(wit,wsex,wk)] where wit is a witness definition/reference, [wsex] is a sex of witness and [wk] is a kind of witness relationship to the family. *) let loop_witn line ic = let rec loop_witn acc str = match fields str with | ("wit" | "wit:") :: l -> let sex, l = match l with | "m:" :: l -> (Male, l) | "f:" :: l -> (Female, l) | l -> (Neuter, l) in let wkind, l = get_event_witness_kind l in let wk, _, l = parse_parent str l in if l <> [] then failwith str; loop_witn ((wk, sex, wkind) :: acc) (input_a_line ic) | _ -> (List.rev acc, str) in loop_witn [] line (** Read and parse a gw file block from [ic]. Returns also next line if it's not the end of the file. *) let read_family ic fname = function (* Block that defines that file use utf-8 encoding *) | Some (_, [ "encoding:"; "utf-8" ]) -> F_enc_utf_8 (* Block that defines that the file uses gwplus syntax *) | Some (_, [ "gwplus" ]) -> F_gw_plus (* Family block *) | Some (str, "fam" :: l) -> ( (* read father *) let fath_key, surname, l = parse_parent str l in (* read relation between parents *) let relation_ss, marriage, marr_place, marr_note, marr_src, divorce, l = get_mar_date str l in let relation, fath_sex, moth_sex = relation_ss in (* read mother *) let moth_key, _, l = parse_parent str l in if l <> [] then failwith str; let line = read_line ic in (* read list of witnesses with their sex (if exists) *) let witn, line = let rec loop = function | Some (str, ("wit" | "wit:") :: l) -> let sex, l = match l with | "m:" :: l -> (Male, l) | "f:" :: l -> (Female, l) | l -> (Neuter, l) in let wk, _, l = parse_parent str l in if l <> [] then failwith str; let witn, line = loop (read_line ic) in ((wk, sex) :: witn, line) | line -> ([], line) in loop line in (* read familial source if present *) let fsrc, line = match line with | Some (_, [ "src"; x ]) -> (cut_space x, read_line ic) | Some (str, "src" :: _) -> failwith str | _ -> ("", line) in (* read common children source if present *) let csrc, line = match line with | Some (_, [ "csrc"; x ]) -> (cut_space x, read_line ic) | Some (str, "csrc" :: _) -> failwith str | _ -> ("", line) in (* read common children birth place if present *) let cbp, line = match line with | Some (_, [ "cbp"; x ]) -> (cut_space x, read_line ic) | Some (str, "cbp" :: _) -> failwith str | _ -> ("", line) in (* create a couple *) let co = Adef.couple fath_key moth_key in (* read a family comments *) let comm, line = match line with | Some (str, "comm" :: _) -> let comm = String.sub str 5 (String.length str - 5) in (comm, read_line ic) | _ -> ("", line) in (* read family events *) let fevents, line = match line with | Some (_, "fevt" :: _) -> let fevents, line = let rec loop fevents = function | "end fevt" -> (fevents, read_line ic) | x -> let str, l = (x, fields x) in (* On récupère le nom, date, lieu, source, cause *) let name, l = get_fevent_name str l in let date, l = get_optional_event_date l in let place, l = get_field "#p" l in let cause, l = get_field "#c" l in let src, l = get_field "#s" l in let date = match date with | None -> Date.cdate_None | Some x -> Date.cdate_of_od x in if l <> [] then failwith str; (* On récupère les témoins *) let witn, line = loop_witn (input_a_line ic) ic in (* On récupère les notes *) let notes, line = loop_note line ic in let notes = Mutil.strip_all_trailing_spaces notes in let evt = (name, date, place, cause, src, notes, witn) in loop (evt :: fevents) line in loop [] (input_a_line ic) in (List.rev fevents, line) | _ -> ([], line) in match line with (* have children *) | Some (_, [ "beg" ]) -> let cles_enfants = let rec loop children = match read_line ic with | Some (str, "-" :: l) -> let sex, l = get_optional_sexe l in let child, l = parse_child str surname sex csrc cbp l in if l <> [] then failwith str else loop (child :: children) | Some (_, [ "end" ]) -> children | Some (str, _) -> failwith str | _ -> failwith "eof" in List.rev (loop []) in (* create a family definition (without witnesses, events and family index) *) let fo = { marriage; marriage_place = marr_place; marriage_note = marr_note; marriage_src = marr_src; witnesses = [||]; relation; divorce; fevents = []; comment = comm; origin_file = Filename.basename fname; fsources = fsrc; fam_index = Gwdb.dummy_ifam; } in let deo = { children = Array.of_list cles_enfants } in F_some ( Family (co, fath_sex, moth_sex, witn, fevents, fo, deo), read_line ic ) (* no children *) | line -> let fo = { marriage; marriage_place = marr_place; marriage_note = marr_note; marriage_src = marr_src; witnesses = [||]; relation; divorce; fevents = []; comment = comm; origin_file = Filename.basename fname; fsources = fsrc; fam_index = Gwdb.dummy_ifam; } in let deo = { children = [||] } in F_some (Family (co, fath_sex, moth_sex, witn, fevents, fo, deo), line) ) (* Database notes block *) | Some (_, [ "notes-db" ]) -> let notes = read_notes_db ic "end notes-db" in F_some (Bnotes ("", notes), read_line ic) (* Extended page block *) | Some (str, [ "page-ext"; _ ]) -> let p = let len = String.length "page-ext" + 1 in String.sub str len (String.length str - len) in let notes = read_notes_db ic "end page-ext" in F_some (Bnotes (p, notes), read_line ic) (* Used before version 5.00. Notes block *) | Some (_, [ "notes" ]) -> let notes = read_notes ic in F_some (Bnotes ("", notes), read_line ic) (* Notes block *) | Some (str, "notes" :: l) -> ( let surname, l = get_name l in let first_name, occ, l = get_fst_name str l in if l <> [] then failwith "str" else match read_line ic with | Some (_, [ "beg" ]) -> let notes = read_notes ic in let key = { pk_first_name = first_name; pk_surname = surname; pk_occ = occ } in F_some (Notes (key, notes), read_line ic) | Some (str, _) -> failwith str | None -> failwith "end of file") (* Wizard note block *) | Some (str, "wizard-note" :: _) -> let wizid = let len = String.length "wizard-note " in String.sub str len (String.length str - len) in let notes = read_notes_db ic "end wizard-note" in F_some (Wnotes (wizid, notes), read_line ic) (* Personal relation block *) | Some (str, "rel" :: l) -> ( (* get considered person *) let sb, _, l = parse_parent str l in let sex, l = match l with | "#h" :: l -> (Male, l) | "#f" :: l -> (Female, l) | l -> (Neuter, l) in if l <> [] then failwith "str" else match read_line ic with (* Read list of relations *) | Some (_, [ "beg" ]) -> let rl = try let rec loop = function | "end" -> [] | x -> get_relation x (fields x) :: loop (input_a_line ic) in loop (input_a_line ic) with End_of_file -> failwith "missing end rel" in F_some (Relations (sb, sex, rl), read_line ic) | Some (str, _) -> failwith str | None -> failwith "end of file") (* Person's events block *) | Some (str, "pevt" :: l) -> (* get considered person *) let sb, _, l = parse_parent str l in if l <> [] then failwith str else let pevents = let rec loop pevents = function | "end pevt" -> pevents | x -> let str, l = (x, fields x) in (* On récupère le nom, date, lieu, source, cause *) let name, l = get_pevent_name str l in let date, l = get_optional_event_date l in let place, l = get_field "#p" l in let cause, l = get_field "#c" l in let src, l = get_field "#s" l in let date = match date with | None -> Date.cdate_None | Some x -> Date.cdate_of_od x in if l <> [] then failwith str; (* On récupère les témoins *) let witn, line = loop_witn (input_a_line ic) ic in (* On récupère les notes *) let notes, line = loop_note line ic in let notes = Mutil.strip_all_trailing_spaces notes in let evt = (name, date, place, cause, src, notes, witn) in loop (evt :: pevents) line in loop [] (input_a_line ic) in let pevents = List.rev pevents in F_some (Pevent (sb, Neuter, pevents), read_line ic) | Some (str, _) -> failwith str (* End of the file *) | None -> F_none (** Read and return a block of .gw file. If [!no_fail] is disabled raises [Failure] exception. *) let read_family_1 ic fname line = if !no_fail then try read_family ic fname line with Failure str -> F_fail str else read_family ic fname line (** Compile .gw file and save result to corresponding .gwo *) let comp_families x = let out_file = Filename.chop_suffix x ".gw" ^ ".gwo" in line_cnt := 0; let oc = open_out_bin out_file in (try let ic = open_in x in (* write header *) output_string oc magic_gwo; (* write source filename *) output_value oc (x : string); let rec loop line encoding = match read_family_1 (ic, encoding) x line with | F_some (family, line) -> output_value oc (family : gw_syntax); loop line encoding | F_enc_utf_8 -> loop (read_line (ic, E_utf_8)) E_utf_8 | F_gw_plus -> create_all_keys := true; loop (read_line (ic, encoding)) encoding | F_none -> () | F_fail str -> Printf.printf "File \"%s\", line %d:\n" x !line_cnt; Printf.printf "Error: %s\n" str; flush stdout; loop (read_line (ic, encoding)) encoding in loop (read_line (ic, E_iso_8859_1)) E_iso_8859_1; close_in ic with e -> close_out oc; Mutil.rm out_file; raise e); close_out oc