(* camlp4r ./pa_html.cmo *) (* $Id: dag.ml,v 4.17 2004/12/13 16:05:16 ddr Exp $ *) open Dag2html; open Def; open Config; open Gutil; open Util; open Printf; module Pset = Set.Make (struct type t = iper; value compare = compare; end); (* testing *) value map_dag f d = let a = Array.map (fun d -> {pare = d.pare; valu = f d.valu; chil = d.chil}) d.dag in {dag = a} ; value tag_dag d = let c = ref 'A' in map_dag (fun v -> let v = c.val in do { c.val := if c.val = 'Z' then 'a' else if c.val = 'z' then '1' else Char.chr (Char.code c.val + 1); v }) d ; (* input dag *) value get_dag_elems conf base = loop None Pset.empty 1 where rec loop prev_po set i = let s = string_of_int i in let po = Util.find_person_in_env conf base s in let po = match po with [ None -> prev_po | x -> x ] in let so = Util.p_getenv conf.env ("s" ^ s) in match (po, so) with [ (Some p, Some s) -> let set = match Util.branch_of_sosa conf base p.cle_index (Num.of_string s) with [ Some ipsl -> List.fold_left (fun set (ip, _) -> Pset.add ip set) set ipsl | None -> set ] in loop po set (i + 1) | _ -> set ] ; type sum 'a 'b = [ Left of 'a | Right of 'b ] ; value make_dag conf base list = let module O = struct type t = iper; value compare = compare; end in let module M = Map.Make O in let nodes = Array.of_list list in let map = loop M.empty 0 where rec loop map i = if i = Array.length nodes then map else loop (M.add nodes.(i) (idag_of_int i) map) (i + 1) in let nodes = Array.map (fun ip -> let pare = match parents (aget conf base ip) with [ Some ifam -> let c = coi base ifam in let l = try [M.find (mother c) map] with [ Not_found -> [] ] in try [M.find (father c) map :: l] with [ Not_found -> l ] | None -> [] ] in let chil = let u = uget conf base ip in Array.fold_left (fun chil ifam -> let des = doi base ifam in Array.fold_left (fun chil ip -> try [M.find ip map :: chil] with [ Not_found -> chil ]) chil des.children) [] u.family in let chil = List.rev chil in {pare = pare; valu = Left ip; chil = chil}) nodes in {dag = nodes} ; value image_normal_txt conf base p fname width height = let image_txt = capitale (transl_nth conf "image/images" 0) in let s = Unix.stat fname in let b = acces conf base p in let k = default_image_name base p in let r = sprintf "\ \"%s\"" (commd conf) (int_of_float (mod_float s.Unix.st_mtime (float_of_int max_int))) b k width (if height = 0 then "" else " height=" ^ string_of_int height) image_txt in if conf.cancel_links then r else sprintf "" (commd conf) b k ^ r ^ "" ; value image_url_txt conf base url height = let image_txt = capitale (transl_nth conf "image/images" 0) in sprintf "" url ^ sprintf "\"%s\"" url height image_txt ^ "\n" ; value image_url_txt_with_size conf base url width height = let image_txt = capitale (transl_nth conf "image/images" 0) in sprintf "" url ^ sprintf "\"%s\"" url width height image_txt ^ "\n" ; value image_txt conf base p = match p_getenv conf.env "image" with [ Some "on" -> match image_and_size conf base p (limited_image_size 100 75) with [ Some (True, f, Some (wid, hei)) -> "
\n
\n" ^ image_normal_txt conf base p f wid hei ^ "
\n" | Some (True, f, None) -> "
\n
\n" ^ image_normal_txt conf base p f 100 0 ^ "
\n" | Some (False, url, Some (wid, hei)) -> "
\n
\n" ^ image_url_txt_with_size conf base url wid hei ^ "
\n" | Some (False, url, None) -> "
\n
\n" ^ image_url_txt conf base url 75 ^ "
\n" | _ -> "" ] | _ -> "" ] ; (* Print with HTML table tags:
*) value print_table conf hts = do { Wserver.wprint "
\n"; for i = 0 to Array.length hts - 1 do { Wserver.wprint "\n"; for j = 0 to Array.length hts.(i) - 1 do { let (colspan, align, td) = hts.(i).(j) in Wserver.wprint " Wserver.wprint " align=left" | (LeftA, _) -> () | (CenterA, _) -> Wserver.wprint " align=center" | (RightA, _) -> Wserver.wprint " align=right" ]; Wserver.wprint ">"; match td with [ TDstring s -> Wserver.wprint "%s" s | TDbar s -> if s = "" then Wserver.wprint "|" else Wserver.wprint "|" s | TDhr align -> do { Wserver.wprint "
Wserver.wprint " width=\"50%%\" align=left" | RightA -> Wserver.wprint " width=\"50%%\" align=right" | _ -> Wserver.wprint " width=\"100%%\"" ]; Wserver.wprint ">" } ]; Wserver.wprint "\n" } }; Wserver.wprint "
\n" } ; (* * Print without HTML table tags: using
 *)

(* Machinery opering to 'displayed texts', i.e. strings where not all
   characters correspond to a displayed character (due to html tags or
   encoded characters) *)

(* Return next 'displayed character' location: can be on several 'string
   characters', like " " *)

value displayed_next_char s i =
  loop i where rec loop i =
    if i >= String.length s then None
    else
      match s.[i] with
      [ '<' ->
          let rec loop1 i =
            if i = String.length s then None
            else if s.[i] = '>' then loop (i + 1)
            else loop1 (i + 1)
          in
          loop1 (i + 1)
      | '&' ->
          let rec loop1 j =
            if j = String.length s then Some (i, j)
            else
              match s.[j] with
              [ 'a'..'z' | 'A'..'Z' -> loop1 (j + 1)
              | ';' -> Some (i, j + 1)
              | _ -> Some (i, j) ]
          in
          loop1 (i + 1)
      | _ -> Some (i, i + 1) ]
;

value buff_store_int s blen i j =
  loop blen i where rec loop blen i =
    if i = j then blen else loop (Buff.store blen s.[i]) (i + 1)
;

(* Remove empty tags, i.e.  enclosing empty text, from s *)

value strip_empty_tags s =
  loop 0 None 0 where rec loop blen opened_tag i =
    if i >= String.length s then Buff.get blen
    else
      match s.[i] with
      [ '<' ->
          let j = i + 1 in
          let (tag_close, j) =
            match s.[j] with
            [ '/' -> (True, j + 1)
            | _ -> (False, j) ]
          in
          let (tag_name, j) =
            loop j where rec loop k =
              match s.[k] with
              [ 'a'..'z' | 'A'..'Z' -> loop (k + 1)
              | _ -> (String.sub s j (k - j), k) ]
          in
          let j =
            loop j where rec loop j =
              if s.[j] = '>' then j + 1 else loop (j + 1)
          in
          match opened_tag with
          [ Some (opened_tag_name, k) ->
              if tag_close then
                if tag_name = opened_tag_name then loop blen None j
                else loop (buff_store_int s blen k j) None j
              else loop (buff_store_int s blen k i) (Some (tag_name, i)) j
          | None ->
              if tag_close then loop (buff_store_int s blen i j) None j
              else loop blen (Some (tag_name, i)) j ]
      | c ->
          let blen =
            match opened_tag with
            [ Some (_, k) -> buff_store_int s blen k i
            | None -> blen ]
          in
          loop (Buff.store blen c) None (i + 1) ]
;

value displayed_length s =
  loop 0 0 where rec loop len i =
    match displayed_next_char s i with
    [ Some (i, j) -> loop (len + 1) j
    | None -> len ]
;

value displayed_sub s ibeg ilen =
  loop 0 0 0 0 where rec loop blen di dlen i =
    match displayed_next_char s i with
    [ Some (j, k) ->
        let blen = buff_store_int s blen i j in
        let (blen, dlen) =
          if di >= ibeg && dlen < ilen then
            (buff_store_int s blen j k, dlen + 1)
          else (blen, dlen)
        in
        loop blen (di + 1) dlen k
    | None ->
        let s = Buff.get (buff_store_int s blen i (String.length s)) in
        strip_empty_tags s ]
;

value longuest_word_length s =
  loop 0 0 0 where rec loop maxlen len i =
    match displayed_next_char s i with
    [ Some (j, k) ->
        if s.[j] = ' ' then loop (max maxlen len) 0 k
        else loop maxlen (len + 1) k
    | None -> max maxlen len ]
;

value displayed_end_word s di i =
  loop di i where rec loop di i =
    match displayed_next_char s i with
    [ Some (j, k) -> if s.[j] = ' ' then (di, Some j) else loop (di + 1) k
    | None -> (di, None) ]
;

(* Strip 'displayed text' s by subtexts of limited size sz *)

value displayed_strip s sz =
  loop [] 0 0 0 where rec loop strl dibeg di i =
    let i =
      loop i where rec loop i =
        if i < String.length s && s.[i] = ' ' then loop (i + 1) else i
    in
    let (dj, j) = displayed_end_word s di i in
    match j with
    [ Some j ->
        if dj - dibeg > sz then
          loop [displayed_sub s dibeg (di - dibeg - 1) :: strl] di (dj + 1)
            (j + 1)
        else loop strl dibeg (dj + 1) (j + 1)
    | None ->
        let strl =
          if dj - dibeg > sz then
            let str2 = displayed_sub s dibeg (di - dibeg - 1) in
            let str1 = displayed_sub s di (dj - di) in
            [str1; str2 :: strl]
          else
            let str = displayed_sub s dibeg (dj - dibeg) in
            [str :: strl]
        in
        List.rev strl ]
;

(* Determine columns sizes; scan all table by increasing colspans *)

value gen_compute_columns_sizes size_fun hts ncol =
  let colsz = Array.make ncol 0 in
  let rec loop curr_colspan =
    let next_colspan = ref (ncol + 1) in
    do {
      for i = 0 to Array.length hts - 1 do {
        if i = Array.length hts then ()
        else
          let rec loop col j =
            if j = Array.length hts.(i) then ()
            else do {
              let (colspan, _, td) = hts.(i).(j) in
              match td with
              [ TDstring s ->
                  if colspan = curr_colspan then
                    let len = size_fun s in
                    let currsz =
                      loop 0 col colspan where rec loop currsz col cnt =
                        if cnt = 0 then currsz
                        else
                          let currsz = currsz + colsz.(col) in
                          loop currsz (col + 1) (cnt - 1)
                    in
                    if currsz >= len then ()
                    else
                      let rec loop n col cnt =
                        if cnt = 0 then ()
                        else do {
                          let inc_sz =
                            n * (len - currsz) / colspan -
                              (n - 1) * (len - currsz) / colspan
                          in
                          colsz.(col) := colsz.(col) + inc_sz;
                          loop (n + 1) (col + 1) (cnt - 1)
                        }
                      in
                      loop 1 col colspan
                  else if colspan > curr_colspan then
                    next_colspan.val := min colspan next_colspan.val
                  else ()
              | TDbar _ -> ()
              | TDhr _ -> () ];
              loop (col + colspan) (j + 1)
            }
          in
          loop 0 0
      };
      if next_colspan.val > ncol then () else loop next_colspan.val
    }
  in
  do { loop 1; colsz }
;

value compute_columns_sizes = gen_compute_columns_sizes displayed_length;
value compute_columns_minimum_sizes =
  gen_compute_columns_sizes longuest_word_length
;

(* Gadget to add a | to fill upper/lower part of a table data when
   preceded/followed by a |; not obligatory but nicer *)

value try_add_vbar stra_row stra_row_max hts i col =
  if stra_row < 0 then
    if i = 0 then ""
    else
      let rec loop pcol pj =
        if pj >= Array.length hts.(i - 1) then ""
        else
          let (colspan, _, td) = hts.(i - 1).(pj) in
          if pcol = col then
            match td with
            [ TDbar _ -> "|"
            | _ -> "" ]
          else loop (pcol + colspan) (pj + 1)
      in
      loop 0 0
  else if stra_row >= stra_row_max then
    if i = Array.length hts - 1 then ""
    else
      let rec loop ncol nj =
        if nj >= Array.length hts.(i + 1) then ""
        else
          let (colspan, _, td) = hts.(i + 1).(nj) in
          if ncol = col then
            match td with
            [ TDbar _ -> "|"
            | _ -> "" ]
          else loop (ncol + colspan) (nj + 1)
      in
      loop 0 0
  else ""
;

value strip_troublemakers s =
  loop False 0 0 where rec loop last_space len i =
    if i = String.length s then Buff.get len
    else
      match s.[i] with
      [ '<' ->
          let j = i + 1 in
          let j =
            match s.[j] with
            [ '/' -> j + 1
            | _ -> j ]
          in
          let (tag_name, j) =
            loop j where rec loop k =
              match s.[k] with
              [ 'a'..'z' | 'A'..'Z' -> loop (k + 1)
              | _ -> (String.lowercase (String.sub s j (k - j)), k) ]
          in
          let j =
            loop j where rec loop j =
              if s.[j] = '>' then j + 1 else loop (j + 1)
          in
          let len =
            match tag_name with
            [ "br" | "font" | "img" | "table" | "td" | "tr" | "center" -> len
            | _ -> buff_store_int s len i j ]
          in
          loop last_space len j
      | '\n' | '\r' | ' ' ->
          let len = if last_space then len else Buff.store len ' ' in
          loop True len (i + 1)
      | c -> loop False (Buff.store len c) (i + 1) ]
;

value table_strip_troublemakers hts =
  for i = 0 to Array.length hts - 1 do {
    for j = 0 to Array.length hts.(i) - 1 do {
      match hts.(i).(j) with
      [ (colspan, align, TDstring s) ->
          hts.(i).(j) := (colspan, align, TDstring (strip_troublemakers s))
      | _ -> () ]
    }
  }
;

value print_next_pos conf pos1 pos2 tcol =
  let doit = p_getenv conf.env "notab" = Some "on" in
  if doit then do {
    let dpos =
      match p_getint conf.env "dpos" with
      [ Some dpos -> dpos
      | None -> 78 ]
    in
    let pos1 =
      match pos1 with
      [ Some pos1 -> pos1
      | None -> 0 ]
    in
    let pos2 =
      match pos2 with
      [ Some pos2 -> pos2
      | None -> dpos ]
    in
    let overlap =
      let overlap =
        match p_getint conf.env "overlap" with
        [ Some x -> x
        | None -> 10 ]
      in
      min overlap dpos
    in
    let env =
      List.fold_right
        (fun (k, v) env ->
           match k with
           [ "pos1" | "pos2" -> env
           | _ -> [(k, v) :: env] ])
        conf.env []
    in
    Wserver.wprint "\n"
  }
  else ()
;

(* Main print table algorithm with 
 *)

value table_pre_dim conf hts =
  do {
    table_strip_troublemakers hts;
    let ncol =
      let hts0 = hts.(0) in
      let rec loop ncol j =
        if j = Array.length hts0 then ncol
        else
          let (colspan, _, _) = hts0.(j) in
          loop (ncol + colspan) (j + 1)
      in
      loop 0 0
    in
    let min_widths_tab = compute_columns_minimum_sizes hts ncol in
    let max_widths_tab = compute_columns_sizes hts ncol in
    let min_wid = Array.fold_left  \+ 0 min_widths_tab in
    let max_wid = Array.fold_left  \+ 0 max_widths_tab in
    (min_wid, max_wid, min_widths_tab, max_widths_tab, ncol)
  }
;

value print_table_pre conf hts =
  let (tmincol, tcol, colminsz, colsz, ncol) = table_pre_dim conf hts in
  let dcol =
    let dcol =
      match p_getint conf.env "width" with
      [ Some i -> i
      | None -> 79 ]
    in
    max tmincol (min dcol tcol)
  in
  do {
    if tcol > tmincol then
      for i = 0 to ncol - 1 do {
        colsz.(i) :=
          colminsz.(i) +
            (colsz.(i) - colminsz.(i)) * (dcol - tmincol) / (tcol - tmincol)
      }
    else ();
    let pos1 = p_getint conf.env "pos1" in
    let pos2 =
      match p_getint conf.env "pos2" with
      [ None -> p_getint conf.env "dpos"
      | x -> x ]
    in
    print_next_pos conf pos1 pos2 (Array.fold_left  \+ 0 colsz);
    Wserver.wprint "
\n";
    for i = 0 to Array.length hts - 1 do {
      let (stra, max_row) =
        let (stral, max_row) =
          loop [] 1 0 0 where rec loop stral max_row col j =
            if j = Array.length hts.(i) then (stral, max_row)
            else
              let (colspan, _, td) = hts.(i).(j) in
              let stra =
                match td with
                [ TDstring s ->
                    let sz =
                      loop 0 colspan where rec loop sz k =
                        if k = 0 then sz
                        else loop (sz + colsz.(col + k - 1)) (k - 1)
                    in
                    Array.of_list (displayed_strip s sz)
                | _ -> [| |] ]
              in
              loop [stra :: stral] (max max_row (Array.length stra))
                (col + colspan) (j + 1)
        in
        (Array.of_list (List.rev stral), max_row)
      in
      for row = 0 to max_row - 1 do {
        let rec loop pos col j =
          if j = Array.length hts.(i) then Wserver.wprint "\n"
          else do {
            let (colspan, align, td) = hts.(i).(j) in
            let sz =
              loop 0 colspan where rec loop sz k =
                if k = 0 then sz else loop (sz + colsz.(col + k - 1)) (k - 1)
            in
            let outs =
              match td with
              [ TDstring s ->
                  let s =
                    let k =
                      let dk = (max_row - Array.length stra.(j)) / 2 in
                      row - dk
                    in
                    if k >= 0 && k < Array.length stra.(j) then
                      let s = stra.(j).(k) in
                      if s = " " then " " else s
                    else try_add_vbar k (Array.length stra.(j)) hts i col
                  in
                  let len = displayed_length s in
                  String.make ((sz - len) / 2) ' ' ^ s ^
                    String.make (sz - (sz + len) / 2) ' '
              | TDbar s ->
                  let s = if s = "" then "|" else "|" in
                  let len = displayed_length s in
                  String.make ((sz - len) / 2) ' ' ^ s ^
                    String.make (sz - (sz + len) / 2) ' '
              | TDhr LeftA ->
                  let len = (sz + 1) / 2 in
                  String.make len '-' ^ String.make (sz - len) ' '
              | TDhr RightA ->
                  let len = sz / 2 in
                  String.make (sz - len - 1) ' ' ^ String.make (len + 1) '-'
              | TDhr CenterA -> String.make sz '-' ]
            in
            let clipped_outs =
              if pos1 = None && pos2 = None then outs
              else
                let pos1 =
                  match pos1 with
                  [ Some pos1 -> pos1
                  | None -> pos ]
                in
                let pos2 =
                  match pos2 with
                  [ Some pos2 -> pos2
                  | None -> pos + sz ]
                in
                if pos + sz <= pos1 then ""
                else if pos > pos2 then ""
                else if pos2 >= pos + sz then
                  displayed_sub outs (pos1 - pos) (pos + sz - pos1)
                else if pos1 < pos then displayed_sub outs 0 (pos2 - pos)
                else displayed_sub outs (pos1 - pos) (pos2 - pos1)
            in
            Wserver.wprint "%s" clipped_outs;
            loop (pos + sz) (col + colspan) (j + 1)
          }
        in
        loop 0 0 0
      }
    };
    Wserver.wprint "
\n" } ; (* main *) value print_html_table conf hts = do { if Util.p_getenv conf.env "notab" <> Some "on" then do { Wserver.wprint "\n" } else (); if Util.p_getenv conf.env "notab" = Some "on" || Util.p_getenv conf.env "pos2" <> None || browser_doesnt_have_tables conf then print_table_pre conf hts else print_table conf hts } ; value html_table_of_dag indi_txt vbar_txt phony invert no_group d = let t = Dag2html.table_of_dag phony False invert no_group d in if Array.length t.table = 0 then [| |] else Dag2html.html_table_struct indi_txt vbar_txt phony d t ; value make_tree_hts conf base elem_txt vbar_txt spouse_on invert no_group set spl d = let indi_txt n = match n.valu with [ Left ip -> let p = pget conf base ip in let txt = elem_txt p in let txt = let spouses = if (spouse_on && n.chil <> [] || n.pare = []) && not invert then List.fold_left (fun list id -> match d.dag.(int_of_idag id).valu with [ Left cip -> match parents (aget conf base cip) with [ Some ifam -> let cpl = coi base ifam in if ip == (father cpl) then if List.mem_assoc (mother cpl) list then list else [((mother cpl), Some ifam) :: list] else if ip == (mother cpl) then if List.mem_assoc (father cpl) list then list else [((father cpl), Some ifam) :: list] else list | None -> list ] | Right _ -> list ]) [] n.chil else if n.chil = [] then try [List.assq ip spl] with [ Not_found -> [] ] else [] in List.fold_left (fun txt (ips, ifamo) -> if Pset.mem ips set then txt else let ps = pget conf base ips in let d = match ifamo with [ Some ifam -> Date.short_marriage_date_text conf base (foi base ifam) p ps | None -> "" ] in txt ^ "
\n&" ^ d ^ " " ^ Util.referenced_person_title_text conf base ps ^ Date.short_dates_text conf base ps) txt spouses in txt ^ image_txt conf base p | Right _ -> " " ] in let bd = match Util.p_getint conf.env "bd" with [ Some x -> x | _ -> 0 ] in let td = match Util.p_getenv conf.env "td" with [ Some x -> " " ^ x | _ -> match Util.p_getenv conf.env "color" with [ None | Some "" -> "" | Some x -> " bgcolor=" ^ x ] ] in let indi_txt n = let (bd, td) = match n.valu with [ Left ip -> (bd, td) | _ -> (0, "") ] in if bd > 0 || td <> "" then sprintf "
%s
" bd td (indi_txt n) else indi_txt n in let vbar_txt n = match n.valu with [ Left ip -> vbar_txt ip | _ -> "" ] in let phony n = match n.valu with [ Left _ -> False | Right _ -> True ] in html_table_of_dag indi_txt vbar_txt phony invert no_group d ; value print_slices_menu conf base hts_opt = let txt n = Util.capitale (transl_nth conf "display by slices/slice width/overlap/total width" n) in let title _ = Wserver.wprint "%s" (txt 0) in do { Util.header conf title; Util.print_link_to_welcome conf True; tag "form" "method=get action=\"%s\"" conf.command begin hidden_env conf; List.iter (fun (k, v) -> if k = "slices" then () else Wserver.wprint "\n" (decode_varenv k) (decode_varenv v)) conf.env; tag "table" begin tag "tr" "align=left" begin tag "td" "align=right" begin Wserver.wprint "%s\n" (Util.capitale (transl conf "don't group the common branches together")); Wserver.wprint "\n"; end; end; tag "tr" "align=left" begin tag "td" "align=right" begin Wserver.wprint "%s\n" (txt 1); Wserver.wprint "\n"; end; end; tag "tr" "align=left" begin tag "td" "align=right" begin Wserver.wprint "%s\n" (txt 2); Wserver.wprint "\n"; end; end; tag "tr" "align=left" begin tag "td" "align=right" begin Wserver.wprint "%s\n" (txt 3); let wid = let wid = 78 in match hts_opt with [ Some hts -> let (min_wid, max_wid, _, _, _) = table_pre_dim conf hts in do { Wserver.wprint "(%d-%d)\n" min_wid max_wid; max min_wid (min max_wid wid) } | None -> wid ] in Wserver.wprint "\n" wid; end; end; end; Wserver.wprint "\n"; end; Util.trailer conf } ; value gen_print_dag conf base spouse_on invert set spl d = let dag_elem_txt p = Util.referenced_person_title_text conf base p ^ Date.short_dates_text conf base p in let vbar_txt ip = "" in let no_group = p_getenv conf.env "nogroup" = Some "on" in let hts = make_tree_hts conf base dag_elem_txt vbar_txt spouse_on invert no_group set spl d in if p_getenv conf.env "slices" = Some "on" then print_slices_menu conf base (Some hts) else do { let title _ = Wserver.wprint "%s" (Util.capitale (Util.transl conf "tree")) in Util.header_no_page_title conf title; print_html_table conf hts; Util.trailer conf } ; value print_dag conf base set spl d = let spouse_on = match Util.p_getenv conf.env "spouse" with [ Some "on" -> True | _ -> False ] in let invert = match Util.p_getenv conf.env "invert" with [ Some "on" -> True | _ -> False ] in gen_print_dag conf base spouse_on invert set spl d ; value print conf base = let set = get_dag_elems conf base in let d = make_dag conf base (Pset.elements set) in print_dag conf base set [] d ;