(* $Id$ * ---------------------------------------------------------------------- * *) open Pxp_types open Pxp_document (**********************************************************************) (* The box class represents formatted text *) (**********************************************************************) class type formatted_text = object method output : int -> int -> out_channel -> unit (* output initial_indent indent ch: * 'initial_indent' is how far the first line should be indented; * 'indent' how far the rest. 'ch' is the channel on which the lines * are to be printed. *) method multiline : bool (* whether the box occupies multiple lines *) method width_of_last_line : int (* returns the width of the last line *) end ;; type text = Text of string | Box of formatted_text ;; let textwidth tl = let rec compute tl r = match tl with [] -> r | t :: tl' -> begin match t with Text s -> compute tl' (r + String.length s) | Box b -> if b # multiline then compute tl' (b # width_of_last_line) else compute tl' (r + b # width_of_last_line) end in compute (List.rev tl) 0 ;; class box the_initial_width the_width = object (self) (* The 'initial_width' is the width that is available on the first * line of output; the 'width' is the width that is available in the * rest. *) val initial_width = the_initial_width val width = the_width (* state: *) val mutable space_added = false val mutable linefeed_added = false val mutable is_first_line = true val mutable lines = [] (* lines in reverse order (first line = last element) *) val mutable current_line = [] (* not member of 'lines'; again reverse order *) val mutable current_indent = 0 method add_space = if not space_added then begin space_added <- true; linefeed_added <- true; current_line <- Text " " :: current_line end method ignore_space = space_added <- true; linefeed_added <- true method add_linefeed = if not linefeed_added then begin linefeed_added <- true; if not space_added then current_line <- Text " " :: current_line end method ignore_linefeed = linefeed_added <- true method add_newline = lines <- current_line :: lines; current_line <- []; space_added <- true; linefeed_added <- true; is_first_line <- false; current_indent <- 0; method add_word s = (* first try to add 's' to 'current_line' *) let current_line' = Text s :: current_line in let current_width = if is_first_line then initial_width else width in if textwidth current_line' + current_indent <= current_width then begin (* ok, the line does not become too long *) current_line <- current_line'; space_added <- false; linefeed_added <- false end else begin (* The line would be too long. *) lines <- current_line :: lines; current_line <- [Text s]; space_added <- false; linefeed_added <- false; is_first_line <- false; current_indent <- 0; end method add_box b = current_line <- Box b :: current_line; space_added <- false; linefeed_added <- false; method width_of_last_line = textwidth current_line + current_indent method available_width = let current_width = if is_first_line then initial_width else width in current_width - textwidth current_line - current_indent method multiline = lines <> [] or (List.exists (function Text _ -> false | Box b -> b # multiline) current_line) method output initial_indent indent ch = let eff_lines = List.rev (current_line :: lines) in let rec out_lines cur_indent ll = match ll with [] -> () | l :: ll' -> output_string ch (String.make cur_indent ' '); List.iter (function Text s -> output_string ch s | Box b -> b # output 0 indent ch ) (List.rev l); if ll' <> [] then output_string ch "\n"; out_lines indent ll' in out_lines initial_indent eff_lines end ;; class listitem_box listmark indent totalwidth = let initial_newline = String.length listmark >= indent in object (self) inherit box totalwidth (totalwidth - indent) as super val extra_indent = indent initializer self # add_word listmark; if initial_newline then self # add_newline else begin current_line <- Text (String.make (indent - String.length listmark) ' ') :: current_line; space_added <- true; linefeed_added <- true; end method output initial_indent indent ch = super # output initial_indent (indent + extra_indent) ch end ;; (**********************************************************************) (* Footnotes etc. *) (**********************************************************************) class type footnote_printer = object method footnote_to_box : store_type -> box -> unit end and store_type = object method alloc_footnote : footnote_printer -> int method print_footnotes : box -> unit end ;; class store = object (self) val mutable footnotes = ( [] : (int * footnote_printer) list ) val mutable next_footnote_number = 1 method alloc_footnote n = let number = next_footnote_number in next_footnote_number <- number+1; footnotes <- footnotes @ [ number, n ]; number method print_footnotes (b : box) = if footnotes <> [] then begin b # add_newline; b # add_newline; let w = b # available_width in b # add_word (String.make (w/3) '-'); b # add_newline; b # add_newline; List.iter (fun (_,n) -> n # footnote_to_box (self : #store_type :> store_type) b) footnotes; b # add_newline; end end ;; (**********************************************************************) (* The extension objects *) (**********************************************************************) class virtual shared = object (self) (* --- default_ext --- *) val mutable node = (None : shared node option) method clone = {< >} method node = match node with None -> assert false | Some n -> n method set_node n = node <- Some n (* --- virtual --- *) method virtual to_box : store -> box -> unit (* to_box store b: * formats the element using box 'b' *) end ;; class only_data = object (self) inherit shared val white_space_re = Str.regexp "[ \t]+\\|\n" method to_box store b = let s = self # node # data in let splitted = Str.full_split white_space_re s in List.iter (function Str.Delim "\n" -> b # add_linefeed | Str.Delim _ -> b # add_space | Str.Text s -> b # add_word s) splitted end ;; class no_markup = object (self) inherit shared method to_box store b = List.iter (fun n -> n # extension # to_box store b) (self # node # sub_nodes) end ;; class readme = object (self) inherit shared method to_box store b = let title = match self # node # attribute "title" with Value s -> s | _ -> assert false in let w = b # available_width in let line = String.make (w-1) '*' in b # add_word line; b # add_newline; b # add_word title; b # add_newline; b # add_word line; b # add_newline; b # add_newline; (* process main content: *) List.iter (fun n -> n # extension # to_box store b) (self # node # sub_nodes); (* now process footnotes *) store # print_footnotes b; (* trailer *) b # add_newline; end ;; class section the_tag = object (self) inherit shared val tag = the_tag method to_box store b = let sub_nodes = self # node # sub_nodes in match sub_nodes with title_node :: rest -> b # add_newline; let w = b # available_width in let line = String.make (w-1) tag in b # add_word line; b # add_newline; b # add_word (title_node # data); b # add_newline; b # add_word line; b # add_newline; List.iter (fun n -> n # extension # to_box store b) rest; | _ -> assert false end ;; class sect1 = section '=';; class sect2 = section '-';; class sect3 = section ':';; class p = object (self) inherit shared method to_box store b = let within_list = match self # node # parent # node_type with T_element "li" -> true | T_element _ -> false | _ -> assert false in if not within_list then b # add_newline; let w = b # available_width in let b' = new box w w in b' # ignore_space; List.iter (fun n -> n # extension # to_box store b') (self # node # sub_nodes); b # add_box (b' :> formatted_text); b # add_newline; end ;; class li = object (self) inherit shared method to_box store b = b # add_newline; let w = b # available_width in let b' = new listitem_box "-" 3 w in b' # ignore_space; List.iter (fun n -> n # extension # to_box store b') (self # node # sub_nodes); b # add_box (b' :> formatted_text); end ;; class code = object (self) inherit shared method to_box store b = b # add_newline; let w = b # available_width in let b' = new box w w in b' # ignore_space; let data = self # node # data in (* convert tabs *) let l = String.length data in let rec add s i column = (* this is very ineffective but comprehensive: *) if i < l then match data.[i] with '\t' -> let n = 8 - (column mod 8) in add (s ^ String.make n ' ') (i+1) (column + n) | '\n' -> b' # add_word s; b' # add_newline; add "" (i+1) 0 | c -> add (s ^ String.make 1 c) (i+1) (column + 1) else if s <> "" then begin b' # add_word s; b' # add_newline; end in add "" 0 0; b # add_box (b' :> formatted_text); b # add_newline; end ;; class br = object (self) inherit shared method to_box store b = b # add_newline; end ;; class footnote = object (self) inherit shared val mutable footnote_number = 0 method to_box store b = let number = store # alloc_footnote (self : #shared :> footnote_printer) in footnote_number <- number; b # add_space; b # add_word ("[" ^ string_of_int number ^ "]"); method footnote_to_box store b = let w = b # available_width in let n = "[" ^ string_of_int footnote_number ^ "]" in let b' = new listitem_box n 6 w in b' # ignore_space; List.iter (fun n -> n # extension # to_box store b') (self # node # sub_nodes); b # add_box (b' :> formatted_text); b # add_newline; b # add_newline; end ;; class a = object (self) inherit shared val mutable footnote_number = 0 val mutable a_href = "" method to_box store b = let href = match self # node # attribute "href" with Value v -> "see " ^ v | Valuelist _ -> assert false | Implied_value -> begin match self # node # attribute "readmeref" with Value v -> "see file " ^ v | Valuelist _ -> assert false | Implied_value -> "" end in a_href <- href; List.iter (fun n -> n # extension # to_box store b) (self # node # sub_nodes); if href <> "" then begin let number = store # alloc_footnote (self : #shared :> footnote_printer) in footnote_number <- number; b # add_space; b # add_word ("[" ^ string_of_int number ^ "]"); end method footnote_to_box store b = if a_href <> "" then begin let w = b # available_width in let n = "[" ^ string_of_int footnote_number ^ "]" in let b' = new listitem_box n 6 w in b' # ignore_space; b' # add_word a_href; b # add_box (b' :> formatted_text); b # add_newline; b # add_newline; end end ;; (**********************************************************************) open Pxp_yacc let tag_map = make_spec_from_alist ~data_exemplar:(new data_impl (new only_data)) ~default_element_exemplar:(new element_impl (new no_markup)) ~element_alist: [ "readme", (new element_impl (new readme)); "sect1", (new element_impl (new sect1)); "sect2", (new element_impl (new sect2)); "sect3", (new element_impl (new sect3)); "title", (new element_impl (new no_markup)); "p", (new element_impl (new p)); "br", (new element_impl (new br)); "code", (new element_impl (new code)); "em", (new element_impl (new no_markup)); "ul", (new element_impl (new no_markup)); "li", (new element_impl (new li)); "footnote", (new element_impl (new footnote : #shared :> shared)); "a", (new element_impl (new a : #shared :> shared)); ] () ;; (* ====================================================================== * History: * * $Log$ * Revision 1.1 2000/11/17 09:57:31 lpadovan * Initial revision * * Revision 1.5 2000/08/22 14:34:25 gerd * Using make_spec_from_alist instead of make_spec_from_mapping. * * Revision 1.4 2000/08/18 21:15:25 gerd * Minor updates because of PXP API changes. * * Revision 1.3 2000/07/08 17:58:17 gerd * Updated because of PXP API changes. * * Revision 1.2 2000/06/04 20:25:38 gerd * Updates because of renamed PXP modules. * * Revision 1.1 1999/08/22 22:29:32 gerd * Initial revision. * * *)