(***********************************************************************) (* *) (* HEVEA *) (* *) (* Luc Maranget, projet PARA, INRIA Rocquencourt *) (* *) (* Copyright 1998 Institut National de Recherche en Informatique et *) (* Automatique. Distributed only by permission. *) (* *) (***********************************************************************) let header = "$Id: html.ml,v 1.109 2006-10-11 16:43:20 maranget Exp $" (* Output function for a strange html model : - Text elements can occur anywhere and are given as in latex - A new grouping construct is given (open_group () ; close_group ()) *) open Misc open Parse_opts open Latexmacros open HtmlCommon exception Error of string type block = HtmlCommon.block let addvsize x = flags.vsize <- flags.vsize + x (* Calls to other modules that are in the interface *) let over, erase_display, begin_item_display, end_item_display, force_item_display, item_display, do_close_display, do_open_display_varg, do_open_display, do_close_maths, do_open_maths, put_in_math, math_put, math_put_char, left, right = if !Parse_opts.mathml then begin MathML.over, MathML.erase_display, MathML.begin_item_display, MathML.end_item_display, MathML.force_item_display, MathML.item_display, MathML.close_display, MathML.open_display_varg, MathML.open_display, MathML.close_maths, MathML.open_maths, MathML.put_in_math, MathML.put, MathML.put_char, MathML.left, MathML.right end else begin HtmlMath.over, HtmlMath.erase_display, HtmlMath.begin_item_display, HtmlMath.end_item_display, HtmlMath.force_item_display, HtmlMath.item_display, (fun () -> HtmlMath.close_display false), (HtmlMath.open_display_varg false), (fun () -> HtmlMath.open_display false), HtmlMath.close_maths, HtmlMath.open_maths, HtmlMath.put_in_math, HtmlMath.put, HtmlMath.put_char, HtmlMath.left, HtmlMath.right end ;; let int_sup_sub, limit_sup_sub, standard_sup_sub = if !Parse_opts.mathml then MathML.int_sup_sub, MathML.limit_sup_sub, MathML.standard_sup_sub else HtmlMath.int_sup_sub, HtmlMath.limit_sup_sub, HtmlMath.standard_sup_sub ;; let set_out out = !cur_out.out <- out and stop () = Stack.push stacks.s_active !cur_out.out ; !cur_out.out <- Out.create_null () and restart () = !cur_out.out <- Stack.pop stacks.s_active ;; (* acces to flags *) let is_empty () = flags.empty ;; let debug m = Printf.fprintf stderr "%s : table_vsize=%d vsize=%d" m flags.table_vsize flags.vsize ; prerr_newline () ;; let debug_empty f = prerr_string (if f.empty then "empty=true" else "empty=false") ;; let put s = if flags.in_math then math_put s else HtmlCommon.put s ;; let put_char c = if flags.in_math then math_put_char c else HtmlCommon.put_char c ;; let put_unicode i = match i with | 0x3C -> put "<" | 0x3E -> put ">" | 0x26 -> put "&" | _ -> try put_char (OutUnicode.translate_out i) with OutUnicode.CannotTranslate -> put (Printf.sprintf "&#X%X;" i) ;; let loc_name _ = () (* freeze everyting and change output file *) let open_chan chan = open_group "" ; free !cur_out ; !cur_out.out <- Out.create_chan chan ; ;; let close_chan () = Out.close !cur_out.out ; !cur_out.out <- Out.create_buff () ; close_group () ;; let to_style f = let old_flags = copy_flags flags in open_block INTERN "" ; (* clearstyle () ; *) f () ; let r = to_pending !cur_out.pending !cur_out.active in erase_block INTERN ; set_flags flags old_flags ; r ;; let get_current_output () = Out.to_string !cur_out.out let finalize check = if check then begin check_stacks () end else begin (* Flush output in case of fatal error *) let rec close_rec () = if not (Stack.empty out_stack) then begin match Stack.pop out_stack with | Freeze _ -> close_rec () | Normal (_,_,pout) -> Out.copy !cur_out.out pout.out ; cur_out := pout ; close_rec () end in close_rec () end ; Out.close !cur_out.out ; !cur_out.out <- Out.create_null () ;; let put_separator () = put "\n" ;; let unskip () = Out.unskip !cur_out.out; if flags.blank then flags.empty <- true; ;; let put_tag tag = put tag ;; let put_nbsp () = if !Lexstate.whitepre || (flags.in_math && !Parse_opts.mathml) then put_char ' ' else put_unicode 0xA0 ;; let put_open_group () = put_char '{' ;; let put_close_group () = put_char '}' ;; let infomenu _ = () and infonode _opt _num _arg = () and infoextranode _num _arg _text = () ;; let image arg n = if flags.in_pre && !Parse_opts.pedantic then begin warning "Image tag inside preformatted block, ignored" end else begin put " "" then begin put arg; put_char ' ' end ; put "SRC=\"" ; put n ; if !Parse_opts.pedantic then begin put "\" ALT=\"" ; put n end ; put "\">" end ;; type saved = HtmlCommon.saved let check = HtmlCommon.check and hot = HtmlCommon.hot let forget_par () = None let rec do_open_par () = match pblock () with | GROUP -> let pending = to_pending !cur_out.pending !cur_out.active in let a,b,_ = top_out out_stack in ignore (close_block_loc check_empty GROUP) ; do_open_par () ; open_block a b ; !cur_out.pending <- pending | P -> Misc.warning "Opening P twice" (* An error in fact ! *) | s -> if !verbose > 2 then Printf.eprintf "Opening par below: '%s'\n" (string_of_block s) ; open_block P "" let open_par () = do_open_par () let rec do_close_par () = match pblock () with | GROUP -> let pending = to_pending !cur_out.pending !cur_out.active in let a,b,_ = top_out out_stack in ignore (close_block_loc check_empty GROUP) ; let r = do_close_par () in open_block a b ; !cur_out.pending <- pending ; r | P -> ignore (close_flow_loc check_blank P) ; true | s -> false let close_par () = do_close_par () (* Find P, maybe above groups *) let rec find_prev_par () = match pblock () with | P -> true | GROUP -> let x = pop_out out_stack in let r = find_prev_par () in push_out out_stack x ; r | _ -> false let rec do_close_prev_par () = match pblock () with | P -> ignore (close_flow_loc check_blank P) | GROUP -> let pending = to_pending !cur_out.pending !cur_out.active in let b,a,_ = top_out out_stack in ignore (close_block_loc check_empty GROUP) ; do_close_prev_par () ; open_block b a ; !cur_out.pending <- pending | _ -> assert false let close_prev_par () = do_close_prev_par () ; flags.saw_par <- true let rec do_par () = match pblock () with | P -> ignore (close_flow_loc check_blank P) ; open_block P "" | GROUP -> let pending = to_pending !cur_out.pending !cur_out.active in let b,a,_ = top_out out_stack in ignore (close_block_loc check_empty GROUP) ; do_par () ; open_block b a ; !cur_out.pending <- pending | s -> if !verbose > 2 then Printf.eprintf "Opening par below: '%s'\n" (string_of_block s) ; open_block P "" let par _ = do_par () (* Interface open block: manage par above *) let open_block_loc = open_block (* save a reference to basic open_block *) let open_block_with_par ss s a = if transmit_par s && find_prev_par () then begin if !verbose > 2 then begin Printf.eprintf "OPEN: %s, closing par\n" ss ; Printf.eprintf "BEFORE: " ; pretty_stack out_stack end ; close_prev_par () ; if !verbose > 2 then begin Printf.eprintf "AFTER: " ; pretty_stack out_stack end end ; open_block_loc s a let open_block ss a = open_block_with_par ss (find_block ss) a let open_display () = if find_prev_par () then begin close_prev_par () end ; do_open_display () and open_display_varg a = if find_prev_par () then begin close_prev_par () end ; do_open_display_varg a and close_display () = do_close_display () ; if flags.saw_par then begin flags.saw_par <- false ; open_par () end let open_maths display = if display && find_prev_par () then begin close_prev_par () end ; do_open_maths display and close_maths display = do_close_maths display ; if flags.saw_par then begin flags.saw_par <- false ; open_par () end let wrap_close close_block s = let s = find_block s in begin match s with GROUP -> () | _ -> ignore (close_par ()) end ; begin match s with | UL|OL -> if flags.nitems > 0 then close_block LI else warning "List with no item" | DL -> if flags.nitems > 0 then close_block DD else warning "List with no item" | _ -> () end ; close_block s ; if flags.saw_par then begin flags.saw_par <- false ; if !verbose > 2 then begin Misc.warning "RE-OPEN PAR:" ; Printf.eprintf "BEFORE: " ; pretty_stack out_stack end ; open_par () ; if !verbose > 2 then begin Printf.eprintf "AFTER: " ; pretty_stack out_stack end end let force_block_with_par s content = ignore (close_par ()) ; force_block s content and close_block_with_par s = ignore (close_par ()) ; close_block s and erase_block_with_par s = ignore (close_par ()) ; erase_block s and force_block s content = wrap_close (fun s -> force_block s content) s and close_block s = wrap_close close_block s and erase_block s = wrap_close erase_block s and close_flow s = prerr_endline ("FLOW: "^s) ; wrap_close close_flow s let skip_line = skip_line and flush_out = flush_out and close_group = close_group and open_aftergroup = open_aftergroup and open_group = open_group and insert_block s attr = if find_prev_par () then warning "Ignoring \\centering or \\ragged..." else insert_block (find_block s) attr and insert_attr s = insert_attr (find_block s) and erase_mods = erase_mods and open_mod = open_mod and clearstyle = clearstyle and nostyle = nostyle and get_fontsize = get_fontsize and to_string = to_string ;; (****************************************) (* Table stuff, must take P into acount *) (****************************************) let open_table border htmlargs = let _,arg_b, arg = if flags.in_math && !Parse_opts.mathml then "mtable","frame = \"solid\"","" else "TABLE","BORDER=1",htmlargs in (* open_block will close P (and record that) if appropriate *) if border then open_block_with_par "TABLE" TABLE (arg_b^" "^arg) else open_block_with_par "TABLE" TABLE arg ;; let new_row () = if flags.in_math && !Parse_opts.mathml then open_block_loc (OTHER "mtr") "" else open_block_loc TR "" ;; let attribut name = function | "" -> "" | s -> " "^name^"="^s and as_colspan = function | 1 -> "" | n -> " COLSPAN="^string_of_int n and as_colspan_mathml = function | 1 -> "" | n -> " columnspan= \""^string_of_int n^"\"" let as_align f span = match f with Tabular.Align {Tabular.vert=v ; Tabular.hor=h ; Tabular.wrap=w ; Tabular.width=_} -> attribut "VALIGN" v^ attribut "ALIGN" h^ (if w then "" else " NOWRAP")^ as_colspan span | _ -> raise (Misc.Fatal ("as_align")) ;; let as_align_mathml f span = match f with Tabular.Align {Tabular.vert=v ; Tabular.hor=h } -> attribut "rowalign" ("\""^v^"\"")^ attribut "columnalign" ("\""^h^"\"")^ as_colspan_mathml span | _ -> raise (Misc.Fatal ("as_align_mathml")) ;; let open_direct_cell attrs span = if flags.in_math && !Parse_opts.mathml then begin open_block_loc (OTHER "mtd") (attrs^as_colspan_mathml span); do_open_display () end else open_block_loc TD (attrs^as_colspan span) let open_cell format span _= if flags.in_math && !Parse_opts.mathml then begin open_block_loc (OTHER "mtd") (as_align_mathml format span); do_open_display () end else open_block_loc TD (as_align format span) ;; (* By contrast closing/erasing TD, may in some occasions implies closing some internal P => use wrapped close functions *) let erase_cell () = if flags.in_math && !Parse_opts.mathml then begin erase_display (); erase_block_with_par (OTHER "mtd") end else erase_block_with_par TD and close_cell content = if flags.in_math && !Parse_opts.mathml then begin do_close_display (); force_block_with_par (OTHER "mtd") "" end else force_block_with_par TD content and do_close_cell () = if flags.in_math && !Parse_opts.mathml then begin do_close_display (); close_block_with_par (OTHER "mtd") end else close_block_with_par TD and open_cell_group () = open_group "" and close_cell_group () = close_group () and erase_cell_group () = erase_group () ;; let erase_row () = if flags.in_math && !Parse_opts.mathml then HtmlCommon.erase_block (OTHER "mtr") else HtmlCommon.erase_block TR and close_row () = if flags.in_math && !Parse_opts.mathml then HtmlCommon.close_block (OTHER "mtr") else HtmlCommon.close_block TR ;; let close_table () = begin if flags.in_math && !Parse_opts.mathml then HtmlCommon.close_block (OTHER "mtable") else HtmlCommon.close_block TABLE end ; if flags.saw_par then begin flags.saw_par <- false ; open_par () end ;; let make_border _ = () ;; let center_format = Tabular.Align {Tabular.hor="center" ; Tabular.vert = "top" ; Tabular.wrap = false ; Tabular.pre = "" ; Tabular.post = "" ; Tabular.width = Length.Default} ;; let make_inside s multi = if not (multi) then begin if pblock ()=TD || pblock() = (OTHER "mtd") then begin close_cell " "; open_cell center_format 1 0; put s; end else begin open_cell center_format 1 0; put s; close_cell " " end; end ;; let make_hline w noborder = if noborder then begin new_row (); if not (flags.in_math && !Parse_opts.mathml) then begin open_direct_cell "CLASS=\"hbar\"" w ; close_cell "" end else begin open_cell center_format w 0; close_mods () ; put ""; force_item_display (); close_cell "" end; close_row (); end ;; (* HR is not correct inside P *) let horizontal_line attr width height = if find_prev_par () then begin close_prev_par () end ; horizontal_line attr width height ; if flags.saw_par then begin flags.saw_par <- false ; open_par () end (* Lists also have to take P into account *) let rec do_li s = match pblock () with | P -> let pend = to_pending !cur_out.pending !cur_out.active in ignore (close_flow_loc check_blank P) ; do_li s ; !cur_out.pending <- pend | LI -> ignore (close_flow_loc no_check LI) ; open_block_loc LI s | GROUP -> let pend = to_pending !cur_out.pending !cur_out.active in let a,b,_ = top_out out_stack in ignore (close_block_loc check_empty GROUP) ; do_li s ; open_block_loc a b ; !cur_out.pending <- pend | _ -> assert false let item s = if !verbose > 2 then begin prerr_string "=> item: stack=" ; pretty_stack out_stack end ; if flags.nitems > 0 then begin do_li s end else begin let saved = let pending = to_pending !cur_out.pending !cur_out.active in do_close_mods () ; ignore (close_par ()) ; (* in case some par opened before first \item *) let r = Out.to_string !cur_out.out in !cur_out.pending <- pending ; r in open_block_loc LI s ; do_put saved end ; if !verbose > 2 then begin prerr_string "<= item: stack=" ; pretty_stack out_stack end ; flags.nitems <- flags.nitems+1 let nitem = item let set_dt s = flags.dt <- s and set_dcount s = flags.dcount <- s ;; (*********************************************) (* s1 and s2 below are attributes to DR/DD *) (*********************************************) let emit_dt_dd scan true_scan arg s1 s2 = open_block_loc DT s1 ; if flags.dcount <> "" then scan ("\\refstepcounter{"^ flags.dcount^"}") ; true_scan ("\\makelabel{"^arg^"}") ; ignore (close_block_loc no_check DT) ; open_block_loc DD s2 let rec do_dt_dd scan true_scan arg s1 s2 = match pblock () with | P -> let pend = to_pending !cur_out.pending !cur_out.active in ignore (close_flow_loc check_blank P) ; do_dt_dd scan true_scan arg s1 s2 ; !cur_out.pending <- pend | DD -> ignore (close_flow_loc no_check DD) ; emit_dt_dd scan true_scan arg s1 s2 | GROUP -> let pend = to_pending !cur_out.pending !cur_out.active in let a,b,_ = top_out out_stack in ignore (close_block_loc check_empty GROUP) ; do_dt_dd scan true_scan arg s1 s2 ; open_block_loc a b ; !cur_out.pending <- pend | _ -> assert false let ditem scan arg s1 s2 = if !verbose > 2 then begin Printf.eprintf "=> DITEM: «%s» «%s» «%s»\n" arg s1 s2 ; prerr_string "ditem: stack=" ; pretty_stack out_stack end ; let true_scan = if flags.nitems = 0 then begin let pending = to_pending !cur_out.pending !cur_out.active in do_close_mods () ; ignore (close_par ()) ; (* in case some par opened before first \item *) let saved = Out.to_string !cur_out.out in !cur_out.pending <- pending ; (fun arg -> do_put saved ; scan arg) end else scan in begin if flags.nitems > 0 then do_dt_dd scan true_scan arg s1 s2 else emit_dt_dd scan true_scan arg s1 s2 end ; flags.nitems <- flags.nitems+1 ; if !verbose > 2 then begin Printf.eprintf "<= DITEM: «%s» «%s» «%s»\n" arg s1 s2 ; prerr_string "ditem: stack=" ; pretty_stack out_stack end ;