Source

footnotes-ocaml / footnotes.ml

Full commit
(* 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;;

(* footnotes: read from channel inc and print with renumbered references
 *            and reordered footnotes to channel outc *)
let footnotes inc outc =
  let refs = Hashtbl.create 1000000 in
  let sub_ref l ref =
    let old_n = int_of_string (Str.matched_group 1 l) in
    ref_of_int
      (try Hashtbl.find refs old_n
      with Not_found ->
	let n = Hashtbl.length refs + 1 in
	Hashtbl.add refs old_n n;
	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_file ->
      failwith "Missing footnotes part"
  | End_of_body ->
      output_endline outc sep_line;
      let foots = Array.make (Hashtbl.length refs) None in
      let add_foot l =
	if Str.string_match ref_regexp l 0 then
	  let old_n = int_of_string (Str.matched_group 1 l) in
	  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)
	else prerr_endline ("malformed footnote: " ^ l)
      in
      try
	while true do
	  let l = input_line inc in
	  add_foot l
	done
      with End_of_file ->
	let print_foot idx elt =
	  output_string outc (ref_of_int (idx + 1));
	  match elt with
	    None   -> output_endline outc " ### missing footnote ###"
	  | Some l -> output_endline outc l in
	Array.iteri print_foot foots;;

(* main: call footnotes either with given files or with stdin *)
let main () =
  if Array.length Sys.argv > 1 then
    let do_file f =
      let c = open_in f in
      (try footnotes c stdout with e -> close_in c; raise e);
      close_in c
    in
    Arg.parse [] do_file "Usage: footnotes [file...]"
  else footnotes stdin stdout;;

main ();;