(* compile with: ocamlopt -o footnotes str.cmxa footnotes.ml *) let sep_line = "@footnote:" (* the seperator line *) let ref_regexp = Str.regexp "\\[\\([0-9]+\\)\\]" let ref_of_int n = "[" ^ string_of_int n ^ "]" let output_endline c s = output_string c s; output_char c '\n' exception End_of_body (* read the body from inc, substitute the references from table refs *) (* if ref is not in refs, evaluate unknown_ref; print to outc *) let process_body inc outc refs unknown_ref = let sub_ref l _ = let old_n = int_of_string (Str.matched_group 1 l) in try ref_of_int (Hashtbl.find refs old_n) with Not_found -> unknown_ref old_n in try while true do let l = input_line inc in if l = sep_line then raise End_of_body; output_endline outc (Str.global_substitute ref_regexp (sub_ref l) l) done with End_of_body -> output_endline outc sep_line (* read the foots from inc, process the lines with process_foot *) let process_foots inc process_foot malformed_foot = try while true do let l = input_line inc in if Str.string_match ref_regexp l 0 then process_foot l (int_of_string (Str.matched_group 1 l)) else malformed_foot l done with End_of_file -> () (* insert a new reference for old_n into refs and return the new n *) let insert_ref refs old_n = let n = Hashtbl.length refs + 1 in Hashtbl.add refs old_n n; n (* renumber and reorder the footnotes by their appearance in the body *) let renumber_by_body inc outc = let refs = Hashtbl.create 1000000 in let new_ref old_n = ref_of_int (insert_ref refs old_n) in process_body inc outc refs new_ref; let foots = Array.make (Hashtbl.length refs) None in let add_foot l old_n = try let n = Hashtbl.find refs old_n in foots.(n-1) <- Some (Str.string_after l (Str.match_end ())) with Not_found -> prerr_endline ("unreferenced footnote: " ^ l) and prerr_malformed l = prerr_endline ("malformed footnote: " ^ l) in process_foots inc add_foot prerr_malformed; for i = 1 to Array.length foots do output_string outc (ref_of_int (i)); match foots.(i-1) with None -> output_endline outc " ### missing footnote ###" | Some l -> output_endline outc l done (* renumber the footnote references by the original footnote order *) let renumber_by_foots inc outc = while input_line inc <> sep_line do () done; let refs = Hashtbl.create 1000000 in let add_foot_ref _ old_n = if not (Hashtbl.mem refs old_n) then ignore (insert_ref refs old_n) in process_foots inc add_foot_ref ignore; seek_in inc 0; process_body inc outc refs (function _ -> "[?]"); let print_foot l old_n = let n = Hashtbl.find refs old_n in output_string outc (ref_of_int n); output_endline outc (Str.string_after l (Str.match_end ())) in process_foots inc print_foot (output_endline outc) (* renumber the footnote references *) let renumber by_foots = if by_foots then renumber_by_foots else renumber_by_body (* process options and files given on command line or stdin *) let main () = if Array.length Sys.argv > 1 then let by_foots = ref false in let do_file f = let c = open_in f in (try renumber !by_foots c stdout with e -> close_in c; raise e); close_in c in Arg.parse [("-f", Arg.Set by_foots, "Renumber footnotes by their original order")] do_file "Usage: footnotes [OPTION...] [file...]" else renumber_by_body stdin stdout;; main ()