Files
Geneweb/lib/advSearchOk.ml
2024-03-05 22:01:20 +01:00

621 lines
22 KiB
OCaml

(* Copyright (c) 1998-2007 INRIA *)
open Config
open Def
open Gwdb
open Util
let get_number var key env = p_getint env (var ^ "_" ^ key)
let reconstitute_date_dmy conf var =
match get_number var "yyyy" conf.env with
| Some y -> (
match get_number var "mm" conf.env with
| Some m -> (
match get_number var "dd" conf.env with
| Some d ->
if d >= 1 && d <= 31 && m >= 1 && m <= 12 then
Some { day = d; month = m; year = y; prec = Sure; delta = 0 }
else None
| None ->
if m >= 1 && m <= 12 then
Some { day = 0; month = m; year = y; prec = Sure; delta = 0 }
else None)
| None -> Some { day = 0; month = 0; year = y; prec = Sure; delta = 0 })
| None -> None
let reconstitute_date conf var =
match reconstitute_date_dmy conf var with
| Some d -> Some (Dgreg (d, Dgregorian))
| None -> None
let rec skip_spaces x i =
if i = String.length x then i
else if String.unsafe_get x i = ' ' then skip_spaces x (i + 1)
else i
let rec skip_no_spaces x i =
if i = String.length x then i
else if String.unsafe_get x i != ' ' then skip_no_spaces x (i + 1)
else i
let string_incl x y =
let rec loop j_ini =
if j_ini = String.length y then false
else
let rec loop1 i j =
if i = String.length x then
if j = String.length y then true
else String.unsafe_get y j = ' ' || String.unsafe_get y (j - 1) = ' '
else if
j < String.length y && String.unsafe_get x i = String.unsafe_get y j
then loop1 (i + 1) (j + 1)
else loop (skip_spaces y (skip_no_spaces y j_ini))
in
loop1 0 j_ini
in
loop 0
let abbrev_lower x = Name.abbrev (Name.lower x)
(* Get the field name of an event criteria depending of the search type. *)
let get_event_field_name gets event_criteria event_name search_type =
if search_type <> "OR" then event_name ^ "_" ^ event_criteria
else if "on" = gets ("event_" ^ event_name) then event_criteria
else ""
(*
Search for other persons in the base matching with the provided infos.
On search semantic:
Search can be set to be exact on the first name and/or the surname,
if no first name or surname is provided then the search ignores the
parameter in both the exact and the loose case.
- When search is loose it is only necessary for each name atom (name atoms
for "Jean-Pierre" are: [Jean] [Pierre]) to be found at least once in another
person's name atoms in the base.
- When search is exact, it is necessary for each atom to be found exactly the
number of times it occurs in the given name but order is not considered for
a person from the base to match. (ie. "Pierre-Jean de Bourbon de Vallois" matches
with "Jean Pierre de Vallois de Bourbon" but not with "Jean de Bourbon")
*)
let advanced_search conf base max_answers =
let hs = Hashtbl.create 73 in
let hss = Hashtbl.create 73 in
let hd = Hashtbl.create 73 in
let getd x =
try Hashtbl.find hd x
with Not_found ->
let v =
(reconstitute_date conf (x ^ "1"), reconstitute_date conf (x ^ "2"))
in
Hashtbl.add hd x v;
v
in
let gets x =
try Hashtbl.find hs x
with Not_found ->
let v = match p_getenv conf.env x with Some v -> v | None -> "" in
Hashtbl.add hs x v;
v
in
let getss x =
let y = gets x in
if y <> "" then [ y ]
else
match Hashtbl.find_opt hss @@ x with
| Some v -> v
| None ->
let rec loop acc i =
let k = x ^ "_" ^ string_of_int i in
match p_getenv conf.env k with
| Some v -> loop (if v <> "" then v :: acc else acc) (i + 1)
| None -> acc
in
let v = loop [] 1 in
Hashtbl.add hss x v;
v
in
let fn_list =
List.map
(fun s -> List.map Name.lower @@ Name.split_fname s)
(getss "first_name")
in
let sn_list =
List.map
(fun s -> List.map Name.lower @@ Name.split_sname s)
(getss "surname")
in
(* Search type can be AND or OR. *)
let search_type = gets "search_type" in
(* Return empty_field_value if the field is empty. Apply function cmp to the field value. Also check the authorization. *)
let apply_to_field_value_raw p x cmp empty_default_value =
let y = gets x in
if y = "" then empty_default_value
else if authorized_age conf base p then cmp y
else false
in
let apply_to_field_value p x get cmp empty_default_value =
let y = gets x in
if y = "" then empty_default_value
else if authorized_age conf base p then
cmp (abbrev_lower y) (abbrev_lower @@ sou base @@ get p)
else false
in
let do_compare p y get cmp =
let s = abbrev_lower @@ get p in
List.exists (fun s' -> cmp (abbrev_lower s') s) y
in
let apply_to_field_values_raw p x get cmp empty_default_value =
let y = getss x in
if y = [] then empty_default_value
else if authorized_age conf base p then do_compare p y get cmp
else false
in
let apply_to_field_values p x get cmp empty_default_value =
let get p = sou base @@ get p in
apply_to_field_values_raw p x get cmp empty_default_value
in
(* Check if the date matches with the person event. *)
let match_date p x df empty_default_value =
let d1, d2 = getd x in
authorized_age conf base p
&&
match (d1, d2) with
| Some (Dgreg (d1, _)), Some (Dgreg (d2, _)) -> (
match df () with
| Some (Dgreg (d, _)) ->
Date.compare_dmy d d1 >= 0 && Date.compare_dmy d d2 <= 0
| _ -> false)
| Some (Dgreg (d1, _)), _ -> (
match df () with
| Some (Dgreg (d, _)) -> Date.compare_dmy d d1 >= 0
| _ -> false)
| _, Some (Dgreg (d2, _)) -> (
match df () with
| Some (Dgreg (d, _)) -> Date.compare_dmy d d2 <= 0
| _ -> false)
| _ -> empty_default_value
in
let match_sex p empty_default_value =
apply_to_field_value_raw p "sex"
(function
| "M" -> get_sex p = Male | "F" -> get_sex p = Female | _ -> true)
empty_default_value
in
let bapt_date_field_name =
get_event_field_name gets "date" "bapt" search_type
in
let birth_date_field_name =
get_event_field_name gets "date" "birth" search_type
in
let death_date_field_name =
get_event_field_name gets "date" "death" search_type
in
let burial_date_field_name =
get_event_field_name gets "date" "burial" search_type
in
let marriage_date_field_name =
get_event_field_name gets "date" "marriage" search_type
in
let bapt_place_field_name =
get_event_field_name gets "place" "bapt" search_type
in
let birth_place_field_name =
get_event_field_name gets "place" "birth" search_type
in
let death_place_field_name =
get_event_field_name gets "place" "death" search_type
in
let burial_place_field_name =
get_event_field_name gets "place" "burial" search_type
in
let marriage_place_field_name =
get_event_field_name gets "place" "marriage" search_type
in
let match_baptism_date p empty_default_value =
match_date p bapt_date_field_name
(fun () -> Date.od_of_cdate (get_baptism p))
empty_default_value
in
let match_birth_date p empty_default_value =
match_date p birth_date_field_name
(fun () -> Date.od_of_cdate (get_birth p))
empty_default_value
in
let match_death_date p empty_default_value =
match_date p death_date_field_name
(fun () -> Date.date_of_death (get_death p))
empty_default_value
in
let match_burial_date p empty_default_value =
match_date p burial_date_field_name
(fun () ->
(* TODO Date.cdate_of_burial *)
match get_burial p with
| Buried cod | Cremated cod -> Date.od_of_cdate cod
| UnknownBurial -> None)
empty_default_value
in
let cmp_place = if "on" = gets "exact_place" then ( = ) else string_incl in
let match_baptism_place p empty_default_value =
apply_to_field_values p bapt_place_field_name get_baptism_place cmp_place
empty_default_value
in
let match_birth_place p empty_default_value =
apply_to_field_values p birth_place_field_name get_birth_place cmp_place
empty_default_value
in
let match_death_place p empty_default_value =
apply_to_field_values p death_place_field_name get_death_place cmp_place
empty_default_value
in
let match_burial_place p empty_default_value =
apply_to_field_values p burial_place_field_name get_burial_place cmp_place
empty_default_value
in
let match_occupation p empty_default_value =
apply_to_field_value p "occu" get_occupation string_incl empty_default_value
in
let match_name search_list exact : string list -> bool =
let eq : string list -> string list -> bool =
if exact then fun x search ->
List.sort compare search = List.sort compare x
else fun x search -> List.for_all (fun s -> List.mem s x) search
in
fun x -> List.exists (eq x) search_list
in
let match_first_name =
if fn_list = [] then fun _ -> true
else
let eq = match_name fn_list (gets "exact_first_name" = "on") in
fun p ->
eq
(List.map Name.lower @@ Name.split_fname @@ sou base
@@ get_first_name p)
in
let match_surname =
if sn_list = [] then fun _ -> true
else
let eq = match_name sn_list (gets "exact_surname" = "on") in
fun p ->
eq (List.map Name.lower @@ Name.split_sname @@ sou base @@ get_surname p)
in
let match_married p empty_default_value =
apply_to_field_value_raw p "married"
(function
| "Y" -> get_family p <> [||] | "N" -> get_family p = [||] | _ -> true)
empty_default_value
in
let match_marriage p x y empty_default_value =
let d1, d2 = getd x in
let y = getss y in
let test_date_place df =
Array.exists
(fun ifam ->
let fam = foi base ifam in
let sp = poi base @@ Gutil.spouse (get_iper p) fam in
if authorized_age conf base sp then
df fam
&& (y = []
|| do_compare fam y
(fun f -> sou base @@ get_marriage_place f)
cmp_place)
else false)
(get_family p)
in
match (d1, d2) with
| Some d1, Some d2 ->
test_date_place (fun fam ->
match Date.od_of_cdate (get_marriage fam) with
| Some (Dgreg (_, _) as d) ->
if Date.compare_date d d1 < 0 then false
else if Date.compare_date d2 d < 0 then false
else true
| _ -> false)
| Some d1, _ ->
test_date_place (fun fam ->
match Date.od_of_cdate (get_marriage fam) with
| Some (Dgreg (_, _) as d) when authorized_age conf base p ->
if Date.compare_date d d1 < 0 then false else true
| _ -> false)
| _, Some d2 ->
test_date_place (fun fam ->
match Date.od_of_cdate (get_marriage fam) with
| Some (Dgreg (_, _) as d) when authorized_age conf base p ->
if Date.compare_date d d2 > 0 then false else true
| _ -> false)
| _ ->
if y = [] then empty_default_value else test_date_place (fun _ -> true)
in
(* Check the civil status. The test is the same for an AND or a OR search request. *)
let match_civil_status ~skip_fname ~skip_sname p =
match_sex p true
&& (skip_fname || match_first_name p)
&& (skip_sname || match_surname p)
&& match_married p true && match_occupation p true
in
let match_person ?(skip_fname = false) ?(skip_sname = false)
((list, len) as acc) p search_type =
if search_type <> "OR" then
if
match_civil_status ~skip_fname ~skip_sname p
&& match_baptism_date p true && match_baptism_place p true
&& match_birth_date p true && match_birth_place p true
&& match_burial_date p true && match_burial_place p true
&& match_death_date p true && match_death_place p true
&& match_marriage p marriage_date_field_name marriage_place_field_name
true
then (p :: list, len + 1)
else acc
else if
match_civil_status ~skip_fname ~skip_sname p
&& (getss "place" = []
&& gets "date2_yyyy" = ""
&& gets "date1_yyyy" = ""
|| (match_baptism_date p false || match_baptism_place p false)
&& match_baptism_date p true && match_baptism_place p true
|| (match_birth_date p false || match_birth_place p false)
&& match_birth_date p true && match_birth_place p true
|| (match_burial_date p false || match_burial_place p false)
&& match_burial_date p true && match_burial_place p true
|| (match_death_date p false || match_death_place p false)
&& match_death_date p true && match_death_place p true
|| match_marriage p marriage_date_field_name marriage_place_field_name
false)
then (p :: list, len + 1)
else acc
in
let list, len =
if "on" = gets "sosa_filter" then
match Util.find_sosa_ref conf base with
| Some sosa_ref ->
let rec loop p (set, acc) =
if not (IperSet.mem (get_iper p) set) then
let set = IperSet.add (get_iper p) set in
let acc = match_person acc p search_type in
match get_parents p with
| Some ifam ->
let fam = foi base ifam in
let set, acc =
loop (pget conf base @@ get_mother fam) (set, acc)
in
loop (pget conf base @@ get_father fam) (set, acc)
| None -> (set, acc)
else (set, acc)
in
loop (pget conf base @@ get_iper sosa_ref) (IperSet.empty, ([], 0))
|> snd
| None -> ([], 0)
else if fn_list <> [] || sn_list <> [] then
let list_aux strings_of persons_of split n_list exact =
List.map
(List.map (fun x ->
let eq = match_name n_list exact in
let istrs = strings_of base x in
List.fold_left
(fun acc istr ->
let str = Mutil.nominative (sou base istr) in
if eq (List.map Name.lower @@ split str) then istr :: acc
else acc)
[] istrs))
n_list
|> List.flatten |> List.flatten |> List.sort_uniq compare
|> List.map (spi_find @@ persons_of base)
|> List.flatten |> List.sort_uniq compare
in
let skip_fname, skip_sname, list =
if sn_list <> [] then
( false,
true,
list_aux Gwdb.base_strings_of_surname Gwdb.persons_of_surname
Name.split_sname sn_list
(gets "exact_surname" = "on") )
else
( true,
false,
list_aux Gwdb.base_strings_of_first_name Gwdb.persons_of_first_name
Name.split_fname fn_list
(gets "exact_first_name" = "on") )
in
let rec loop ((_, len) as acc) = function
| [] -> acc
| _ when len > max_answers -> acc
| ip :: l ->
loop
(match_person ~skip_fname ~skip_sname acc (pget conf base ip)
search_type)
l
in
loop ([], 0) list
else
Gwdb.Collection.fold_until
(fun (_, len) -> len <= max_answers)
(fun acc i -> match_person acc (pget conf base i) search_type)
([], 0) (Gwdb.ipers base)
in
(List.rev list, len)
let searching_fields conf base =
let test_date x =
reconstitute_date conf (x ^ "1") <> None
|| reconstitute_date conf (x ^ "2") <> None
in
let gets x =
match p_getenv conf.env x with
| Some v when v <> "" -> v
| _ ->
let rec loop acc i =
let k = x ^ "_" ^ string_of_int i in
match p_getenv conf.env k with
| Some v ->
loop
(if acc = "" then v
else if v = "" then acc
else acc ^ " / " ^ v)
(i + 1)
| None -> acc
in
loop "" 1
in
let test_string x = gets x <> "" in
let getd x =
(reconstitute_date conf (x ^ "1"), reconstitute_date conf (x ^ "2"))
in
let sex = match gets "sex" with "M" -> 0 | "F" -> 1 | _ -> 2 in
(* Fonction pour tester un simple champ texte (e.g: first_name). *)
let string_field x (search : Adef.safe_string) =
if test_string x then
search ^^^ " " ^<^ (escape_html (gets x) :> Adef.safe_string)
else search
in
(* Returns the place and date request. (e.g.: ...in Paris between 1800 and 1900) *)
let get_place_date_request place_prefix_field_name date_prefix_field_name
search =
let search =
match getd date_prefix_field_name with
| Some d1, Some d2 ->
search ^^^ " "
^<^ transl conf "between (date)"
^<^ DateDisplay.string_of_date conf d1
^^^ " " ^<^ transl conf "and" ^<^ " "
^<^ DateDisplay.string_of_date conf d2
| Some d1, _ ->
search ^^^ " " ^<^ transl conf "after (date)" ^<^ " "
^<^ DateDisplay.string_of_date conf d1
| _, Some d2 ->
search ^^^ " "
^<^ transl conf "before (date)"
^<^ " "
^<^ DateDisplay.string_of_date conf d2
| _ -> search
in
if test_string place_prefix_field_name then
search ^^^ " " ^<^ transl conf "in (place)" ^<^ " "
^<^ (escape_html (gets place_prefix_field_name) :> Adef.safe_string)
else search
in
(* Returns the event request. (e.g.: born in...) *)
let get_event_field_request place_prefix_field_name date_prefix_field_name
event_name search search_type =
(* Separator character depends on search type operator, a comma for AND search, a slash for OR search. *)
let sep : Adef.safe_string =
if (search : Adef.safe_string :> string) <> "" then
if search_type <> "OR" then Adef.safe ", " else Adef.safe " / "
else Adef.safe ""
in
let search =
if test_string place_prefix_field_name || test_date date_prefix_field_name
then search ^^^ sep ^>^ transl_nth conf event_name sex
else search
in
(* The place and date have to be shown after each event only for the AND request. *)
if search_type <> "OR" then
get_place_date_request place_prefix_field_name date_prefix_field_name
search
else search
in
let sosa_field search =
if gets "sosa_filter" <> "" then
match Util.find_sosa_ref conf base with
| Some p ->
let s =
Adef.safe
@@ Printf.sprintf
(ftransl conf "direct ancestor of %s")
(Util.gen_person_text conf base p : Adef.safe_string :> string)
in
if (search : Adef.safe_string :> string) = "" then s
else if (s :> string) = "" then search
else search ^^^ ", " ^<^ s
| None -> search
else search
in
(* Search type can be AND or OR. *)
let search_type = gets "search_type" in
let bapt_date_field_name =
get_event_field_name gets "date" "bapt" search_type
in
let birth_date_field_name =
get_event_field_name gets "date" "birth" search_type
in
let death_date_field_name =
get_event_field_name gets "date" "death" search_type
in
let burial_date_field_name =
get_event_field_name gets "date" "burial" search_type
in
let marriage_date_field_name =
get_event_field_name gets "date" "marriage" search_type
in
let bapt_place_field_name =
get_event_field_name gets "place" "bapt" search_type
in
let birth_place_field_name =
get_event_field_name gets "place" "birth" search_type
in
let death_place_field_name =
get_event_field_name gets "place" "death" search_type
in
let burial_place_field_name =
get_event_field_name gets "place" "burial" search_type
in
let marriage_place_field_name =
get_event_field_name gets "place" "marriage" search_type
in
let search = Adef.safe "" in
let search = string_field "first_name" search in
let search = string_field "surname" search in
let search = sosa_field search in
let event_search = Adef.safe "" in
let event_search =
get_event_field_request birth_place_field_name birth_date_field_name "born"
event_search search_type
in
let event_search =
get_event_field_request bapt_place_field_name bapt_date_field_name
"baptized" event_search search_type
in
let event_search =
get_event_field_request marriage_place_field_name marriage_date_field_name
"married" event_search search_type
in
let event_search =
get_event_field_request death_place_field_name death_date_field_name "died"
event_search search_type
in
let event_search =
get_event_field_request burial_place_field_name burial_date_field_name
"buried" event_search search_type
in
let search =
if (search :> string) = "" then event_search
else if (event_search :> string) = "" then search
else search ^^^ ", " ^<^ event_search
in
(* Adding the place and date at the end for the OR request. *)
let search =
if
search_type = "OR"
&& (gets "place" != ""
|| gets "date2_yyyy" != ""
|| gets "date1_yyyy" != "")
then get_place_date_request "place" "date" search
else search
in
let search =
if not (test_string marriage_place_field_name || test_date "marriage") then
let sep = if (search :> string) <> "" then ", " else "" in
if gets "married" = "Y" then
search ^>^ sep ^ transl conf "having a family"
else if gets "married" = "N" then
search ^>^ sep ^ transl conf "having no family"
else search
else search
in
let sep = Adef.safe (if (search :> string) <> "" then "," else "") in
string_field "occu" (search ^^^ sep)