(*
* 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: bibtex.ml,v 1.23 2005/12/16 08:39:35 filliatr Exp $ i*)
(*s Datatype for BibTeX bibliographies. *)
type entry_type = string
type key = string
module KeySet = Set.Make(struct type t = key let compare = compare end)
type atom =
| Id of string
| String of string
type command =
| Comment of string
| Preamble of atom list
| Abbrev of string * atom list
| Entry of entry_type * key * (string * atom list) list
(*s biblio is stored as a list. Beware, this in reverse order: the
first entry is at the end of the list. This is intentional! *)
type biblio = command list
let empty_biblio = []
let size b = List.length b
(*s the natural iterator on biblio must start at the first entry, so it
is the [fold_right] function on lists, NOT the [fold_left]! *)
let fold = List.fold_right
let find_entry key biblio =
let rec find key b =
match b with
| [] -> raise Not_found
| (Entry (_,s,_) as e) :: b ->
if String.uppercase s = key then e else find key b
| _ :: b -> find key b
in find (String.uppercase key) biblio
let add_new_entry command biblio = command :: biblio
let rec remove_entry key biblio =
match biblio with
| [] -> raise Not_found
| (Entry(_,s,_) as e) :: b ->
if s = key then b else e :: (remove_entry key b)
| e :: b -> e :: (remove_entry key b)
(*s [add_entry k c b] adds an entry of key [k] and command [c] in
biblio [b] and returns the new biblio. If an entry of key [k]
already exists in [b], it is replaced by the new one. *)
let add_entry command biblio =
match command with
| Entry(_,key,_) ->
begin
try
let new_bib = remove_entry key biblio in
command :: new_bib
with Not_found ->
command :: biblio
end
| _ -> command::biblio
let merge_biblios b1 b2 =
let b2keys =
fold
(fun entry accu -> match entry with
| Entry (_,key,_) -> KeySet.add key accu
| _ -> accu)
b2
KeySet.empty
and b1abbrevs =
fold
(fun entry accu -> match entry with
| Abbrev (key,_) -> KeySet.add key accu
| _ -> accu)
b1
KeySet.empty
in
let new_b1 =
fold
(fun entry accu -> match entry with
| Entry (_,key,_) ->
if KeySet.mem key b2keys then
begin
Format.eprintf "Warning, key '%s' duplicated@." key;
if !Options.warn_error then exit 2;
accu
end
else entry :: accu
| _ -> entry :: accu)
b1
empty_biblio
in
let new_bib =
fold
(fun entry accu -> match entry with
| Abbrev (key,_) ->
if KeySet.mem key b1abbrevs then
begin
Format.eprintf "Warning, key '%s' duplicated@." key;
if !Options.warn_error then exit 2;
accu
end
else entry :: accu
| _ -> entry :: accu)
b2
new_b1
in
new_bib
let month_env =
List.map
(fun s -> (s,[Id s]))
[ "JAN" ; "FEB" ; "MAR" ; "APR" ; "MAY" ; "JUN" ;
"JUL" ; "AUG" ; "SEP" ; "OCT" ; "NOV" ; "DEC" ]
let abbrev_is_implicit key =
try
let _ = int_of_string key in true
with Failure "int_of_string" ->
try
let _ = List.assoc key month_env in true
with Not_found -> false
(*i
let rec abbrev_exists key biblio =
match biblio with
| [] -> false
| (Abbrev (s,_)) :: b -> s = key || abbrev_exists key b
| _ :: b -> abbrev_exists key b
i*)
let rec find_abbrev key biblio =
match biblio with
| [] -> raise Not_found
| (Abbrev (s,_) as e) :: b ->
if s = key then e
else find_abbrev key b
| _ :: b -> find_abbrev key b
let concat_atom_lists a1 a2 =
match (a1,a2) with
| ([String s1], [String s2]) -> [String (s1 ^ s2)]
| _ -> a1 @ a2
let abbrev_table = Hashtbl.create 97
let add_abbrev a l = Hashtbl.add abbrev_table a l
let _ = List.iter (fun (a,l) -> add_abbrev a l) month_env
let find_abbrev_in_table a = Hashtbl.find abbrev_table a
let rec expand_list = function
| [] -> []
| ((Id s) as a) :: rem ->
begin
try
let v = find_abbrev_in_table s in
concat_atom_lists v (expand_list rem)
with Not_found ->
concat_atom_lists [a] (expand_list rem)
end
| ((String _) as a) :: rem ->
concat_atom_lists [a] (expand_list rem)
let rec expand_fields = function
| [] -> []
| (n,l) :: rem -> (n, expand_list l) :: (expand_fields rem)
let rec expand_abbrevs biblio =
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) ->
Entry (t,k,expand_fields f) :: accu
| e ->
e :: accu)
biblio
[]
let rec expand_crossrefs biblio =
let crossref_table = Hashtbl.create 97 in
let add_crossref a l = Hashtbl.add crossref_table (String.uppercase a) l in
let find_crossref a = Hashtbl.find crossref_table (String.uppercase a) in
let replace_crossref a l =
Hashtbl.replace crossref_table (String.uppercase a) l
in
List.iter
(fun command ->
match command with
| Entry (t,k,f) ->
begin
try
match List.assoc "CROSSREF" f with
| [String(s)] ->
add_crossref s []
| _ ->
begin
Format.eprintf
"Warning: invalid cross-reference in entry '%s'.@." k;
if !Options.warn_error then exit 2;
end
with Not_found -> ();
end
| _ -> ())
biblio;
List.iter
(fun command ->
match command with
| Entry (t,k,f) ->
begin
try
let _ = find_crossref k in
if !Options.debug then
Format.eprintf "recording cross-reference '%s'.@." k;
replace_crossref k f
with Not_found -> ()
end
| _ -> ())
biblio;
fold
(fun command accu ->
match command with
| Entry (t,k,f) ->
begin
try
match List.assoc "CROSSREF" f with
| [String(s)] ->
begin
try
let f' = find_crossref s in
if f' = [] then
begin
Format.eprintf
"Warning: cross-reference '%s' not found.@." s;
if !Options.warn_error then exit 2;
end;
Entry (t,k,f@f') :: accu
with Not_found ->
assert false
end
| _ -> command :: accu
with Not_found -> command :: accu
end
| e ->
e :: accu)
biblio
[]
let sort comp bib =
let comments,preambles,abbrevs,entries =
List.fold_left
(fun (c,p,a,e) command ->
match command with
| Comment _ -> (command::c,p,a,e)
| Preamble _ -> (c,command::p,a,e)
| Abbrev _ -> (c,p,command::a,e)
| Entry _ -> (c,p,a,command::e))
([],[],[],[])
bib
in
let sort_abbrevs = List.sort comp abbrevs
and sort_entries = List.sort comp entries
in
List.rev_append sort_entries
(List.rev_append sort_abbrevs
(List.rev_append preambles (List.rev comments)))
let current_key = ref ""