Source

footnotes-ocaml / footnotes.ml

(* 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'

(* read the body from inc, substitute the references from table refs *)
(* if ref is not in refs, evaluate unknown_ref; print to outc *)
let rec process_body inc outc refs unknown_ref =
  let l = input_line inc in
  if l = sep_line then output_endline outc sep_line else
  let sub_ref _ =
    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
  output_endline outc (Str.global_substitute ref_regexp sub_ref l);
  process_body inc outc refs unknown_ref

(* read the foots from inc, process the lines with process_foot *)
let rec process_foots inc process_foot malformed_foot =
  let lo = try Some (input_line inc) with End_of_file -> None in
  match lo with
    None   -> ()
  | Some l ->
      if Str.string_match ref_regexp l 0 then
	process_foot l (int_of_string (Str.matched_group 1 l))
      else malformed_foot l;
      process_foots inc process_foot malformed_foot

(* 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;
  let print_foot i foot =
    output_string outc (ref_of_int (i + 1));
    match foot with
      None   -> output_endline outc " ### missing footnote ###"
    | Some l -> output_endline outc l in
  Array.iteri print_foot foots

(* renumber the footnote references by the original footnote order *)
let renumber_by_foots inc outc =
  while input_line inc <> sep_line do () done; (* skip the body *)
  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;			(* back to beginning *)
  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 ()