(*--------------------------------------------------------------------------- Copyright (c) 2026 The ocaml-cff programmers. All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (** Reference type for CFF with logical sub-records. *) (** Core identity of a reference. *) module Core = struct type t = { type_ : Cff_enums.Reference_type.t ; title : string ; authors : Cff_author.t list ; abstract : string option ; abbreviation : string option } let make ~type_ ~title ~authors ?abstract ?abbreviation () = { type_; title; authors; abstract; abbreviation } ;; let type_ t = t.type_ let title t = t.title let authors t = t.authors let abstract t = t.abstract let abbreviation t = t.abbreviation let pp ppf t = Format.fprintf ppf "%s (%a)" t.title Cff_enums.Reference_type.pp t.type_ end (** Publication information (journal, volume, pages, etc.). *) module Publication = struct type t = { journal : string option ; volume : string option ; issue : string option ; pages : string option ; start : string option ; end_ : string option ; edition : string option ; section : string option ; status : Cff_enums.Status.t option } let empty = { journal = None ; volume = None ; issue = None ; pages = None ; start = None ; end_ = None ; edition = None ; section = None ; status = None } ;; let make ?journal ?volume ?issue ?pages ?start ?end_ ?edition ?section ?status () = { journal; volume; issue; pages; start; end_; edition; section; status } ;; let journal t = t.journal let volume t = t.volume let issue t = t.issue let pages t = t.pages let start t = t.start let end_ t = t.end_ let edition t = t.edition let section t = t.section let status t = t.status let is_empty t = t.journal = None && t.volume = None && t.issue = None && t.pages = None && t.start = None && t.end_ = None && t.edition = None && t.section = None && t.status = None ;; end (** Collection information (proceedings, book series, etc.). *) module Collection = struct type t = { collection_title : string option ; collection_type : string option ; collection_doi : string option ; volume_title : string option ; number_volumes : string option } let empty = { collection_title = None ; collection_type = None ; collection_doi = None ; volume_title = None ; number_volumes = None } ;; let make ?collection_title ?collection_type ?collection_doi ?volume_title ?number_volumes () = { collection_title; collection_type; collection_doi; volume_title; number_volumes } ;; let collection_title t = t.collection_title let collection_type t = t.collection_type let collection_doi t = t.collection_doi let volume_title t = t.volume_title let number_volumes t = t.number_volumes let is_empty t = t.collection_title = None && t.collection_type = None && t.collection_doi = None && t.volume_title = None && t.number_volumes = None ;; end (** Date information. *) module Dates = struct type t = { date_accessed : Cff_date.t option ; date_downloaded : Cff_date.t option ; date_published : Cff_date.t option ; date_released : Cff_date.t option ; year : int option ; year_original : int option ; month : int option ; issue_date : string option } let empty = { date_accessed = None ; date_downloaded = None ; date_published = None ; date_released = None ; year = None ; year_original = None ; month = None ; issue_date = None } ;; let make ?date_accessed ?date_downloaded ?date_published ?date_released ?year ?year_original ?month ?issue_date () = { date_accessed ; date_downloaded ; date_published ; date_released ; year ; year_original ; month ; issue_date } ;; let date_accessed t = t.date_accessed let date_downloaded t = t.date_downloaded let date_published t = t.date_published let date_released t = t.date_released let year t = t.year let year_original t = t.year_original let month t = t.month let issue_date t = t.issue_date let is_empty t = t.date_accessed = None && t.date_downloaded = None && t.date_published = None && t.date_released = None && t.year = None && t.year_original = None && t.month = None && t.issue_date = None ;; end (** Identifiers and links. *) module Identifiers = struct type t = { doi : string option ; url : string option ; repository : string option ; repository_code : string option ; repository_artifact : string option ; isbn : string option ; issn : string option ; pmcid : string option ; nihmsid : string option ; identifiers : Cff_identifier.t list option } let empty = { doi = None ; url = None ; repository = None ; repository_code = None ; repository_artifact = None ; isbn = None ; issn = None ; pmcid = None ; nihmsid = None ; identifiers = None } ;; let make ?doi ?url ?repository ?repository_code ?repository_artifact ?isbn ?issn ?pmcid ?nihmsid ?identifiers () = { doi ; url ; repository ; repository_code ; repository_artifact ; isbn ; issn ; pmcid ; nihmsid ; identifiers } ;; let doi t = t.doi let url t = t.url let repository t = t.repository let repository_code t = t.repository_code let repository_artifact t = t.repository_artifact let isbn t = t.isbn let issn t = t.issn let pmcid t = t.pmcid let nihmsid t = t.nihmsid let identifiers t = t.identifiers let is_empty t = t.doi = None && t.url = None && t.repository = None && t.repository_code = None && t.repository_artifact = None && t.isbn = None && t.issn = None && t.pmcid = None && t.nihmsid = None && t.identifiers = None ;; end (** Related entities (editors, publisher, etc.). *) module Entities = struct type t = { editors : Cff_author.t list option ; editors_series : Cff_author.t list option ; translators : Cff_author.t list option ; recipients : Cff_author.t list option ; senders : Cff_author.t list option ; contact : Cff_author.t list option ; publisher : Cff_author.Entity.t option ; institution : Cff_author.Entity.t option ; conference : Cff_author.Entity.t option ; database_provider : Cff_author.Entity.t option ; location : Cff_author.Entity.t option } let empty = { editors = None ; editors_series = None ; translators = None ; recipients = None ; senders = None ; contact = None ; publisher = None ; institution = None ; conference = None ; database_provider = None ; location = None } ;; let make ?editors ?editors_series ?translators ?recipients ?senders ?contact ?publisher ?institution ?conference ?database_provider ?location () = { editors ; editors_series ; translators ; recipients ; senders ; contact ; publisher ; institution ; conference ; database_provider ; location } ;; let editors t = t.editors let editors_series t = t.editors_series let translators t = t.translators let recipients t = t.recipients let senders t = t.senders let contact t = t.contact let publisher t = t.publisher let institution t = t.institution let conference t = t.conference let database_provider t = t.database_provider let location t = t.location let is_empty t = t.editors = None && t.editors_series = None && t.translators = None && t.recipients = None && t.senders = None && t.contact = None && t.publisher = None && t.institution = None && t.conference = None && t.database_provider = None && t.location = None ;; end (** Metadata and description. *) module Metadata = struct type t = { keywords : string list option ; languages : string list option ; license : Cff_license.t option ; copyright : string option ; scope : string option ; notes : string option } let empty = { keywords = None ; languages = None ; license = None ; copyright = None ; scope = None ; notes = None } ;; let make ?keywords ?languages ?license ?copyright ?scope ?notes () = { keywords; languages; license; copyright; scope; notes } ;; let keywords t = t.keywords let languages t = t.languages let license t = t.license let copyright t = t.copyright let scope t = t.scope let notes t = t.notes let is_empty t = t.keywords = None && t.languages = None && t.license = None && t.copyright = None && t.scope = None && t.notes = None ;; end (** Technical and domain-specific fields. *) module Technical = struct type t = { commit : string option ; version : string option ; filename : string option ; format : string option ; medium : string option ; data_type : string option ; database : string option ; number : string option ; patent_states : string list option ; thesis_type : string option ; term : string option ; entry : string option ; department : string option ; loc_start : string option ; loc_end : string option } let empty = { commit = None ; version = None ; filename = None ; format = None ; medium = None ; data_type = None ; database = None ; number = None ; patent_states = None ; thesis_type = None ; term = None ; entry = None ; department = None ; loc_start = None ; loc_end = None } ;; let make ?commit ?version ?filename ?format ?medium ?data_type ?database ?number ?patent_states ?thesis_type ?term ?entry ?department ?loc_start ?loc_end () = { commit ; version ; filename ; format ; medium ; data_type ; database ; number ; patent_states ; thesis_type ; term ; entry ; department ; loc_start ; loc_end } ;; let commit t = t.commit let version t = t.version let filename t = t.filename let format t = t.format let medium t = t.medium let data_type t = t.data_type let database t = t.database let number t = t.number let patent_states t = t.patent_states let thesis_type t = t.thesis_type let term t = t.term let entry t = t.entry let department t = t.department let loc_start t = t.loc_start let loc_end t = t.loc_end let is_empty t = t.commit = None && t.version = None && t.filename = None && t.format = None && t.medium = None && t.data_type = None && t.database = None && t.number = None && t.patent_states = None && t.thesis_type = None && t.term = None && t.entry = None && t.department = None && t.loc_start = None && t.loc_end = None ;; end (** Complete reference type. *) type t = { core : Core.t ; publication : Publication.t ; collection : Collection.t ; dates : Dates.t ; identifiers : Identifiers.t ; entities : Entities.t ; metadata : Metadata.t ; technical : Technical.t } let make ~core ?(publication = Publication.empty) ?(collection = Collection.empty) ?(dates = Dates.empty) ?(identifiers = Identifiers.empty) ?(entities = Entities.empty) ?(metadata = Metadata.empty) ?(technical = Technical.empty) () = { core; publication; collection; dates; identifiers; entities; metadata; technical } ;; let make_simple ~type_ ~title ~authors ?doi ?year ?journal () = let core = Core.make ~type_ ~title ~authors () in let publication = Publication.make ?journal () in let dates = Dates.make ?year () in let identifiers = Identifiers.make ?doi () in make ~core ~publication ~dates ~identifiers () ;; (* Accessors for sub-records *) let core t = t.core let publication t = t.publication let collection t = t.collection let dates t = t.dates let identifiers t = t.identifiers let entities t = t.entities let metadata t = t.metadata let technical t = t.technical (* Direct accessors for common fields *) let type_ t = Core.type_ t.core let title t = Core.title t.core let authors t = Core.authors t.core let doi t = Identifiers.doi t.identifiers let year t = Dates.year t.dates let pp ppf t = Core.pp ppf t.core (* Helper for string that can also be int (for pages, etc.) *) let string_or_int_jsont = Jsont.any ~dec_number: (Jsont.number |> Jsont.map ~dec:(fun f -> string_of_int (int_of_float f)) ~enc:float_of_string) ~dec_string:Jsont.string ~enc:(fun s -> match float_of_string_opt s with | Some _ -> Jsont.number |> Jsont.map ~dec:(fun _ -> assert false) ~enc:float_of_string | None -> Jsont.string) () ;; (* Helper to convert array jsont to list jsont *) let list_jsont elt = Jsont.(array elt |> map ~dec:Stdlib.Array.to_list ~enc:Stdlib.Array.of_list) ;; (* Jsont codec for the full reference type *) let jsont = let authors_list_jsont = list_jsont Cff_author.jsont in let identifiers_list_jsont = list_jsont Cff_identifier.jsont in let string_list_jsont = list_jsont Jsont.string in (* We need to decode all 60+ fields and then group into sub-records *) Jsont.Object.map ~kind:"Reference" (fun type_ title authors abstract abbreviation (* Publication *) journal volume issue pages start end_ edition section status (* Collection *) collection_title collection_type collection_doi volume_title number_volumes (* Dates *) date_accessed date_downloaded date_published date_released year year_original month issue_date (* Identifiers *) doi url repository repository_code repository_artifact isbn issn pmcid nihmsid identifiers_list (* Entities *) editors editors_series translators recipients senders contact publisher institution conference database_provider location_entity (* Metadata *) keywords languages license license_url copyright scope notes (* Technical *) commit version filename format medium data_type database number patent_states thesis_type term entry department loc_start loc_end -> let core = { Core.type_; title; authors; abstract; abbreviation } in let publication = { Publication.journal ; volume ; issue ; pages ; start ; end_ ; edition ; section ; status } in let collection = { Collection.collection_title ; collection_type ; collection_doi ; volume_title ; number_volumes } in let dates = { Dates.date_accessed ; date_downloaded ; date_published ; date_released ; year ; year_original ; month ; issue_date } in let identifiers = { Identifiers.doi ; url ; repository ; repository_code ; repository_artifact ; isbn ; issn ; pmcid ; nihmsid ; identifiers = identifiers_list } in let entities = { Entities.editors ; editors_series ; translators ; recipients ; senders ; contact ; publisher ; institution ; conference ; database_provider ; location = location_entity } in let license = Option.map (Cff_license.with_url_opt license_url) license in let metadata = { Metadata.keywords; languages; license; copyright; scope; notes } in let technical = { Technical.commit ; version ; filename ; format ; medium ; data_type ; database ; number ; patent_states ; thesis_type ; term ; entry ; department ; loc_start ; loc_end } in { core ; publication ; collection ; dates ; identifiers ; entities ; metadata ; technical }) (* Core fields *) |> Jsont.Object.mem "type" Cff_enums.Reference_type.jsont ~enc:(fun r -> r.core.type_) |> Jsont.Object.mem "title" Jsont.string ~enc:(fun r -> r.core.title) |> Jsont.Object.mem "authors" authors_list_jsont ~enc:(fun r -> r.core.authors) |> Jsont.Object.opt_mem "abstract" Jsont.string ~enc:(fun r -> r.core.abstract) |> Jsont.Object.opt_mem "abbreviation" Jsont.string ~enc:(fun r -> r.core.abbreviation) (* Publication fields *) |> Jsont.Object.opt_mem "journal" Jsont.string ~enc:(fun r -> r.publication.journal) |> Jsont.Object.opt_mem "volume" string_or_int_jsont ~enc:(fun r -> r.publication.volume) |> Jsont.Object.opt_mem "issue" string_or_int_jsont ~enc:(fun r -> r.publication.issue) |> Jsont.Object.opt_mem "pages" string_or_int_jsont ~enc:(fun r -> r.publication.pages) |> Jsont.Object.opt_mem "start" string_or_int_jsont ~enc:(fun r -> r.publication.start) |> Jsont.Object.opt_mem "end" string_or_int_jsont ~enc:(fun r -> r.publication.end_) |> Jsont.Object.opt_mem "edition" Jsont.string ~enc:(fun r -> r.publication.edition) |> Jsont.Object.opt_mem "section" string_or_int_jsont ~enc:(fun r -> r.publication.section) |> Jsont.Object.opt_mem "status" Cff_enums.Status.jsont ~enc:(fun r -> r.publication.status) (* Collection fields *) |> Jsont.Object.opt_mem "collection-title" Jsont.string ~enc:(fun r -> r.collection.collection_title) |> Jsont.Object.opt_mem "collection-type" Jsont.string ~enc:(fun r -> r.collection.collection_type) |> Jsont.Object.opt_mem "collection-doi" Jsont.string ~enc:(fun r -> r.collection.collection_doi) |> Jsont.Object.opt_mem "volume-title" Jsont.string ~enc:(fun r -> r.collection.volume_title) |> Jsont.Object.opt_mem "number-volumes" string_or_int_jsont ~enc:(fun r -> r.collection.number_volumes) (* Date fields *) |> Jsont.Object.opt_mem "date-accessed" Cff_date.jsont ~enc:(fun r -> r.dates.date_accessed) |> Jsont.Object.opt_mem "date-downloaded" Cff_date.jsont ~enc:(fun r -> r.dates.date_downloaded) |> Jsont.Object.opt_mem "date-published" Cff_date.jsont ~enc:(fun r -> r.dates.date_published) |> Jsont.Object.opt_mem "date-released" Cff_date.jsont ~enc:(fun r -> r.dates.date_released) |> Jsont.Object.opt_mem "year" Jsont.int ~enc:(fun r -> r.dates.year) |> Jsont.Object.opt_mem "year-original" Jsont.int ~enc:(fun r -> r.dates.year_original) |> Jsont.Object.opt_mem "month" Jsont.int ~enc:(fun r -> r.dates.month) |> Jsont.Object.opt_mem "issue-date" Jsont.string ~enc:(fun r -> r.dates.issue_date) (* Identifier fields *) |> Jsont.Object.opt_mem "doi" Jsont.string ~enc:(fun r -> r.identifiers.doi) |> Jsont.Object.opt_mem "url" Jsont.string ~enc:(fun r -> r.identifiers.url) |> Jsont.Object.opt_mem "repository" Jsont.string ~enc:(fun r -> r.identifiers.repository) |> Jsont.Object.opt_mem "repository-code" Jsont.string ~enc:(fun r -> r.identifiers.repository_code) |> Jsont.Object.opt_mem "repository-artifact" Jsont.string ~enc:(fun r -> r.identifiers.repository_artifact) |> Jsont.Object.opt_mem "isbn" Jsont.string ~enc:(fun r -> r.identifiers.isbn) |> Jsont.Object.opt_mem "issn" string_or_int_jsont ~enc:(fun r -> r.identifiers.issn) |> Jsont.Object.opt_mem "pmcid" Jsont.string ~enc:(fun r -> r.identifiers.pmcid) |> Jsont.Object.opt_mem "nihmsid" Jsont.string ~enc:(fun r -> r.identifiers.nihmsid) |> Jsont.Object.opt_mem "identifiers" identifiers_list_jsont ~enc:(fun r -> r.identifiers.identifiers) (* Entity fields *) |> Jsont.Object.opt_mem "editors" authors_list_jsont ~enc:(fun r -> r.entities.editors) |> Jsont.Object.opt_mem "editors-series" authors_list_jsont ~enc:(fun r -> r.entities.editors_series) |> Jsont.Object.opt_mem "translators" authors_list_jsont ~enc:(fun r -> r.entities.translators) |> Jsont.Object.opt_mem "recipients" authors_list_jsont ~enc:(fun r -> r.entities.recipients) |> Jsont.Object.opt_mem "senders" authors_list_jsont ~enc:(fun r -> r.entities.senders) |> Jsont.Object.opt_mem "contact" authors_list_jsont ~enc:(fun r -> r.entities.contact) |> Jsont.Object.opt_mem "publisher" Cff_author.Entity.jsont ~enc:(fun r -> r.entities.publisher) |> Jsont.Object.opt_mem "institution" Cff_author.Entity.jsont ~enc:(fun r -> r.entities.institution) |> Jsont.Object.opt_mem "conference" Cff_author.Entity.jsont ~enc:(fun r -> r.entities.conference) |> Jsont.Object.opt_mem "database-provider" Cff_author.Entity.jsont ~enc:(fun r -> r.entities.database_provider) |> Jsont.Object.opt_mem "location" Cff_author.Entity.jsont ~enc:(fun r -> r.entities.location) (* Metadata fields *) |> Jsont.Object.opt_mem "keywords" string_list_jsont ~enc:(fun r -> r.metadata.keywords) |> Jsont.Object.opt_mem "languages" string_list_jsont ~enc:(fun r -> r.metadata.languages) |> Jsont.Object.opt_mem "license" Cff_license.jsont ~enc:(fun r -> r.metadata.license) |> Jsont.Object.opt_mem "license-url" Jsont.string ~enc:(fun r -> Option.bind r.metadata.license Cff_license.url) |> Jsont.Object.opt_mem "copyright" Jsont.string ~enc:(fun r -> r.metadata.copyright) |> Jsont.Object.opt_mem "scope" Jsont.string ~enc:(fun r -> r.metadata.scope) |> Jsont.Object.opt_mem "notes" Jsont.string ~enc:(fun r -> r.metadata.notes) (* Technical fields *) |> Jsont.Object.opt_mem "commit" Jsont.string ~enc:(fun r -> r.technical.commit) |> Jsont.Object.opt_mem "version" string_or_int_jsont ~enc:(fun r -> r.technical.version) |> Jsont.Object.opt_mem "filename" Jsont.string ~enc:(fun r -> r.technical.filename) |> Jsont.Object.opt_mem "format" Jsont.string ~enc:(fun r -> r.technical.format) |> Jsont.Object.opt_mem "medium" Jsont.string ~enc:(fun r -> r.technical.medium) |> Jsont.Object.opt_mem "data-type" Jsont.string ~enc:(fun r -> r.technical.data_type) |> Jsont.Object.opt_mem "database" Jsont.string ~enc:(fun r -> r.technical.database) |> Jsont.Object.opt_mem "number" string_or_int_jsont ~enc:(fun r -> r.technical.number) |> Jsont.Object.opt_mem "patent-states" string_list_jsont ~enc:(fun r -> r.technical.patent_states) |> Jsont.Object.opt_mem "thesis-type" Jsont.string ~enc:(fun r -> r.technical.thesis_type) |> Jsont.Object.opt_mem "term" Jsont.string ~enc:(fun r -> r.technical.term) |> Jsont.Object.opt_mem "entry" Jsont.string ~enc:(fun r -> r.technical.entry) |> Jsont.Object.opt_mem "department" Jsont.string ~enc:(fun r -> r.technical.department) |> Jsont.Object.opt_mem "loc-start" string_or_int_jsont ~enc:(fun r -> r.technical.loc_start) |> Jsont.Object.opt_mem "loc-end" string_or_int_jsont ~enc:(fun r -> r.technical.loc_end) |> Jsont.Object.skip_unknown |> Jsont.Object.finish ;;