(*
 * bibtex2html - A BibTeX to HTML translator
 * Copyright (C) 1997-2000 Jean-Christophe Filliâtre and Claude Marché
 * 
 * This software is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * See the GNU General Public License version 2 for more details
 * (enclosed in the file GPL).
 *)

(*i $Id: expand.ml,v 1.15 2006/05/15 09:45:53 filliatr Exp $ i*)

(*s Expansion of abbreviations in BibTeX databases. *)

open Format
open Bibtex

type fields = (string * string) list

type entry = entry_type * key * fields
		

let abbrev_table = Hashtbl.create 97

let add_abbrev a s = Hashtbl.add abbrev_table a s

let find_abbrev s = Hashtbl.find abbrev_table s

(* months are predefined abbreviations *)
let () = 
  List.iter (fun (id,m) -> add_abbrev id m)
  [ "JAN", "January" ;
    "FEB", "February" ;
    "MAR", "March" ;
    "APR", "April" ;
    "MAY", "May" ;
    "JUN", "June" ;
    "JUL", "July" ;
    "AUG", "August" ;
    "SEP", "September" ;
    "OCT", "October" ;
    "NOV", "November" ;
    "DEC", "December" ]

let rec expand_list = function
  | [] -> 
      ""
  | (Id s) :: rem ->
      (try find_abbrev s with Not_found -> s) ^ (expand_list rem)
  | (String s) :: rem ->
      s ^ (expand_list rem)

let rec expand_fields = function
  | [] -> 
      []
  | (n,l) :: rem -> 
      (n,expand_list l) :: (expand_fields rem)

let macros_in_preamble s =
  try
    let lb = Lexing.from_string s in Latexscan.read_macros lb
  with _ -> ()

let rec expand biblio = 
  Bibtex.fold 
    (fun command accu ->
       match command with
	 | Abbrev (a,l) ->
	     let s = expand_list l in
	     add_abbrev a s; 
	     accu
	 | Entry (t,k,f) ->
	     (t,k,expand_fields f) :: accu
	 | Preamble l ->
	     let s = expand_list l in
	     macros_in_preamble s;
	     accu
	 | Comment _ -> accu)	
    biblio
    []

(*s Sort BibTeX entries by decreasing dates. *)

let int_of_month = function
  | "Janvier" | "January" -> 0
  | "Février" | "February" -> 1
  | "Mars" | "March" -> 2
  | "Avril" | "April" -> 3
  | "Mai" | "May" -> 4
  | "Juin" | "June" -> 5
  | "Juillet" | "July" -> 6
  | "Août" | "August" -> 7
  | "Septembre" | "September" -> 8
  | "Octobre" | "October" -> 9
  | "Novembre" | "November" -> 10 
  | "Décembre" | "December" -> 11
  | _ -> invalid_arg "int_of_month"

let month_day_re1 = Str.regexp "\\([a-zA-Z]+\\)\\( \\|~\\)\\([0-9]+\\)"
let month_day_re2 = Str.regexp "\\([0-9]+\\)\\( \\|~\\)\\([a-zA-Z]+\\)"
let month_anything = Str.regexp "\\([a-zA-Z]+\\)"

let parse_month m =
  if Str.string_match month_day_re1 m 0 then
    int_of_month (Str.matched_group 1 m), int_of_string (Str.matched_group 3 m)
  else if Str.string_match month_day_re2 m 0 then
    int_of_month (Str.matched_group 3 m), int_of_string (Str.matched_group 1 m)
  else if Str.string_match month_anything m 0 then
    int_of_month (Str.matched_group 1 m), 1
  else
    int_of_month m, 1

type date = { year : int; month : int; day : int }

let dummy_date = { year = 0; month = 0; day = 0 }

let extract_year k f =
  try
    int_of_string (List.assoc "YEAR" f)
  with Failure "int_of_string" ->
    if not !Options.quiet then
      eprintf "Warning: incorrect year in entry %s@." k;
    if !Options.warn_error then exit 2;
    0

let extract_month k f =
  try
    parse_month (List.assoc "MONTH" f)
  with 
    | Not_found ->
	0,1
    | _ ->
	if not !Options.quiet then
	  eprintf "Warning: incorrect month in entry %s\n" k; 
	if !Options.warn_error then exit 2;
	0,1

let rec find_entry k = function
  | [] -> raise Not_found
  | (_,k',_) as e :: r -> if k = k' then e else find_entry k r

let rec extract_date el (_,k,f) =
  try
    let y = extract_year k f in
    let m,d = extract_month k f in
    (* eprintf "extract_date: year = %d month = %d day = %d@." y m d; *)
    { year = y; month = m; day = d }
  with Not_found ->
    try extract_date el (find_entry (List.assoc "CROSSREF" f) el)
    with Not_found -> dummy_date

let date_order el e1 e2 =
  let d1 = extract_date el e1 in
  let d2 = extract_date el e2 in
  (d1.year < d2.year) ||
  (d1.year == d2.year && d1.month < d2.month) ||
  (d1.year == d2.year && d1.month == d2.month && d1.day < d2.day)

(*s Access to the fields. *)

let get_field (_,_,f) s = List.assoc (String.uppercase s) f
let get_uppercase_field (_,_,f) s = List.assoc s f

let get_title e = get_uppercase_field e "TITLE"

let get_year e = get_uppercase_field e "YEAR"

let get_month e = get_uppercase_field e "MONTH"

let get_author e = get_uppercase_field e "AUTHOR"

