diff options
| -rw-r--r-- | lib/citation/book.ml | 5 | ||||
| -rw-r--r-- | lib/citation/book.mli | 19 | ||||
| -rw-r--r-- | test/test_citation.ml | 48 |
3 files changed, 72 insertions, 0 deletions
diff --git a/lib/citation/book.ml b/lib/citation/book.ml index 545e324..2a2b9c0 100644 --- a/lib/citation/book.ml +++ b/lib/citation/book.ml @@ -78,6 +78,11 @@ let all = List.map fst table @ tradition_targets let tokens = List.concat_map (fun (id, sp) -> List.map (fun s -> (s, id)) sp) table +let default_spelling id = + match List.assoc_opt id table with + | Some (first :: _) -> first + | Some [] | None -> id + let of_token s = let s = String.trim s in List.assoc_opt s tokens diff --git a/lib/citation/book.mli b/lib/citation/book.mli index 8918bc3..0ca229c 100644 --- a/lib/citation/book.mli +++ b/lib/citation/book.mli @@ -36,6 +36,25 @@ val all : id list (** Every accepted spelling paired with its id. *) val tokens : (string * id) list +(** The first spelling registered for an id -- the form the shipped data + itself uses ([luke] -> ["Luke"], [kings_3] -> ["3 Kings"]). + + This is the FALLBACK display name, and it exists because the obvious + alternative is actively worse. A language file's [\[bible\]] lookup is + total and returns THE KEY on a miss, so a book with no entry would + otherwise render as ["luke.abbr 5:12-14"]. Falling back here instead makes + an unnamed book render as ["Luke 5:12-14"] -- exactly what colitur printed + before this feature existed. The degraded case is the OLD behaviour, not a + broken page, the same principle {!Colitur_naming.Lang} states for its own + key-returning misses. + + An id with no registered spelling -- only a {!tradition} target such as + [kings_1] or [sirach], which the Vulgate data never cites -- returns the id + itself. Reachable only from a user language file that selects a tradition + without naming its target books; every SHIPPED language file is asserted + complete over {!all}. *) +val default_spelling : id -> string + (** A numbering tradition: which book an id denotes. Separate from NAMING (what a book is called), which lives in a language file's [\[bible\]] section, because naming varies by language and this does not -- "modern diff --git a/test/test_citation.ml b/test/test_citation.ml index e234942..9405d39 100644 --- a/test/test_citation.ml +++ b/test/test_citation.ml @@ -31,6 +31,44 @@ let test_both_spellings_are_one_book () = same "Ezech" "Ezek"; same "Jas" "James" +(* The ordinal spellings resolve in the token table. NOTE: this does NOT + exercise [Parse.split_book]'s leading-digit scan -- of_token is a plain + table lookup. The parse-layer cases in [parse_suite] cover that; both are + needed, and confusing the two is how the gap survived review once already. *) +let test_ordinal_books_beyond_one () = + let id t = + match B.of_token t with + | Some x -> B.to_string x + | None -> Alcotest.failf "%s did not resolve" t + in + Alcotest.(check s) "3 Kings" "kings_3" (id "3 Kings"); + Alcotest.(check s) "3 Kgs." "kings_3" (id "3 Kgs."); + Alcotest.(check s) "4 Kings" "kings_4" (id "4 Kings") + +(* The fallback display name: what a book renders as when a language file has + no [bible] entry for it. Must be the data's own spelling, never the id -- + and must itself re-parse, or Render/Parse round-tripping breaks. *) +let test_default_spelling () = + let sp t = + match B.of_token t with + | Some x -> B.default_spelling x + | None -> Alcotest.failf "%s did not resolve" t + in + Alcotest.(check s) "luke" "Luke" (sp "Luke"); + Alcotest.(check s) "kings_3 uses first spelling" "3 Kings" (sp "3 Kgs."); + let cited = List.map snd B.tokens in + List.iter + (fun id -> + if List.mem id cited then begin + let d = B.default_spelling id in + match B.of_token d with + | Some back when B.to_string back = B.to_string id -> () + | _ -> + Alcotest.failf "default_spelling %s = %S does not resolve back" + (B.to_string id) d + end) + B.all + let test_unknown_token_is_none () = Alcotest.(check bool) "not a book" true (B.of_token "Nonesuch" = None) @@ -139,6 +177,8 @@ let test_every_data_file_token_resolves () = let suite = ( "book", [ Alcotest.test_case "both spellings one book" `Quick test_both_spellings_are_one_book; + Alcotest.test_case "ordinal books 3 and 4" `Quick test_ordinal_books_beyond_one; + Alcotest.test_case "default spelling round-trips" `Quick test_default_spelling; Alcotest.test_case "unknown token" `Quick test_unknown_token_is_none; Alcotest.test_case "vulgate identity" `Quick test_vulgate_is_identity; Alcotest.test_case "modern renumbers" `Quick test_modern_renumbers; @@ -198,6 +238,14 @@ let parse_suite = ("four parts", `Quick, parses "Eccli 24:5; 14:7; 14:9-11; 24:30-31" "ecclesiasticus|24:5|14:7|14:9-11|24:30-31"); ("modern name, vulgate id", `Quick, parses "Rev 12:1" "apocalypse|12:1"); + (* Ordinals 3 and 4 must survive [split_book]'s leading-digit scan. + Narrowing its '1'..'4' range to '1'..'3' passes every OTHER case in + this suite silently, while seven real citations depend on it. A + Book.of_token test does NOT cover this -- that is a table lookup and + never reaches split_book. *) + ("ordinal 3 parses", `Quick, parses "3 Kings 17:8-16" "kings_3|17:8-16"); + ("ordinal 3 dotted", `Quick, parses "3 Kgs. 19:3-8" "kings_3|19:3-8"); + ("ordinal 4 parses", `Quick, parses "4 Kings 5:1-15" "kings_4|5:1-15"); ("unknown book", `Quick, test_rejects_unknown_book); ("garbage", `Quick, test_rejects_garbage) ] |
