Skip to content

Commit a831df4

Browse files
committed
Make strict warn on repeated citekeys
Signed-off-by: Marcello Seri <marcello.seri@gmail.com>
1 parent 689df3f commit a831df4

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
2+
- bibfmt: in strict mode, print a warning if there are multiple
3+
repeated citekeys (case-insensitive).
4+
15
# 0.7.7 (2025-08-07)
26

37
- bibfmt and doi2bib drop the month field in bibtex. It is

bibfmt/lib/bibtex.ml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,31 @@ let format_bibtex_item options = function
677677

678678
let pretty_print_bibtex ?options items =
679679
let options = Option.value options ~default:default_options in
680+
(if options.strict then
681+
(* Report all duplicate citekeys *)
682+
let citekeys =
683+
List.fold_left
684+
(fun acc content ->
685+
match content with
686+
| Entry entry -> entry.citekey :: acc
687+
| Comment _ -> acc)
688+
[] items
689+
in
690+
(* Find duplicate citekeys with case-insensitive comparison *)
691+
let lowercase_citekeys = List.map String.lowercase_ascii citekeys in
692+
let rec find_duplicates seen acc = function
693+
| [] -> acc
694+
| key :: rest ->
695+
if List.mem key seen then find_duplicates seen (key :: acc) rest
696+
else find_duplicates (key :: seen) acc rest
697+
in
698+
let duplicates = find_duplicates [] [] lowercase_citekeys in
699+
if duplicates <> [] then (
700+
let duplicate_msg = String.concat ", " (List.rev duplicates) in
701+
Printf.eprintf
702+
"Warning: Duplicate citekeys found (case-insensitive): %s\n"
703+
duplicate_msg;
704+
flush stderr));
680705
String.concat "\n" (List.map (format_bibtex_item options) items)
681706

682707
(* Helper function to clean and normalize BibTeX entries *)

bibfmt/tests/bibfmt.t/run.t

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,3 +424,109 @@ Test mixed UTF-8 with BibTeX special characters and escapes
424424
NOTE = "Special: \&, \%, $, plus UTF-8: café, résumé, naïve, piñata",
425425
PUBLISHER = "Éditions Académiques & Co."
426426
}
427+
428+
Test duplicate citekey detection with case-insensitive comparison in strict mode
429+
$ cat > duplicates.bib << EOF
430+
> @article{test1,
431+
> title = "First Article",
432+
> author = "John Doe",
433+
> year = 2020
434+
> }
435+
>
436+
> @book{Test1,
437+
> title = "Same Citekey Different Case",
438+
> author = "Jane Smith",
439+
> year = 2021
440+
> }
441+
>
442+
> @inproceedings{test2,
443+
> title = "Different Citekey",
444+
> author = "Bob Wilson",
445+
> year = 2022
446+
> }
447+
> EOF
448+
449+
$ bibfmt --strict -f duplicates.bib
450+
Warning: Duplicate citekeys found (case-insensitive): test1
451+
@article{test1,
452+
TITLE = "First Article",
453+
AUTHOR = "John Doe",
454+
YEAR = 2020
455+
}
456+
457+
@book{Test1,
458+
TITLE = "Same Citekey Different Case",
459+
AUTHOR = "Jane Smith",
460+
YEAR = 2021
461+
}
462+
463+
@inproceedings{test2,
464+
TITLE = "Different Citekey",
465+
AUTHOR = "Bob Wilson",
466+
YEAR = 2022
467+
}
468+
469+
Test that non-strict mode doesn't warn about duplicate citekeys
470+
$ bibfmt -f duplicates.bib
471+
@article{test1,
472+
TITLE = "First Article",
473+
AUTHOR = "John Doe",
474+
YEAR = 2020
475+
}
476+
477+
@book{Test1,
478+
TITLE = "Same Citekey Different Case",
479+
AUTHOR = "Jane Smith",
480+
YEAR = 2021
481+
}
482+
483+
@inproceedings{test2,
484+
TITLE = "Different Citekey",
485+
AUTHOR = "Bob Wilson",
486+
YEAR = 2022
487+
}
488+
489+
Test multiple duplicate citekeys in strict mode
490+
$ cat > multiple_duplicates.bib << EOF
491+
> @article{paper1,
492+
> title = "First Paper",
493+
> author = "Author One"
494+
> }
495+
>
496+
> @book{PAPER1,
497+
> title = "Book Version",
498+
> author = "Author Two"
499+
> }
500+
>
501+
> @inproceedings{conference1,
502+
> title = "Conference Paper",
503+
> author = "Author Three"
504+
> }
505+
>
506+
> @misc{Conference1,
507+
> title = "Same Conference Different Type",
508+
> author = "Author Four"
509+
> }
510+
> EOF
511+
512+
$ bibfmt --strict -f multiple_duplicates.bib
513+
Warning: Duplicate citekeys found (case-insensitive): conference1, paper1
514+
@article{paper1,
515+
TITLE = "First Paper",
516+
AUTHOR = "Author One"
517+
}
518+
519+
@book{PAPER1,
520+
TITLE = "Book Version",
521+
AUTHOR = "Author Two"
522+
}
523+
524+
@inproceedings{conference1,
525+
TITLE = "Conference Paper",
526+
AUTHOR = "Author Three"
527+
}
528+
529+
@misc{Conference1,
530+
TITLE = "Same Conference Different Type",
531+
AUTHOR = "Author Four"
532+
}

0 commit comments

Comments
 (0)