----
title: BIBTEX module – parse .bib files
SPDX-FileCopyrightText: 2024 Norman Gray <https://nxg.me.uk>
SPDX-License-Identifier: BSD-2-Clause


This module contains functions
  * to parse BibTeX files (`.bib`) and examine the results;
  * to write such a parsed database in a couple of different formats; and
  * to format entries for output.

Parsing
-------

The core function is [`parse-bibtex-file`](#fn-parse-bibtex-file),
alongside its companion
[`parse-bibtex-string`](#fn-parse-bibtex-string).  This reads a `.bib`
file, and produces a list of [`entry?`](#fn-entry?) structures.  These
structures can be examined and processed using the various `entry-*`
functions described below.

This produces a list of `entry?` structures, which might be usefully
filtered with [`filter-entries`](#fn-filter-entries).

Entries can have ‘local’ variables attached to them during
processing.  These are set and retrieved using
[`entry-set-local-variable!`](#fn-entry-set-local-variable!)
and
[`entry-get-local-variable`](#fn-entry-get-local-variable)
respectively.


Writing
-------

The three `write-bibtex/FMT!` functions take an entry list and write
it out in one or other suitable formats.

For example, the following program will read an aux file, and write
out a new `.bib` file containing only the entries cited.

    (define auxfile-name "path/to/foo.aux")
    ;; ...includes \bibdata{...}

    (module 'bibtex 'aux)

    (let ((aux-info (parse-aux-file auxfile-name)))
      ;; aux-info is ((key key ...) (bibfile bibfile...) stylefile)
      (let ((citations (car aux-info))
            (bibfile (caadr aux-info)))     ;first bibfile
        ;(printf "citations: ~s~%bibfile=~s~%" citations bibfile)
        (let ((entries
               (filter-entries (parse-bibtex-file bibfile)
                               citations)))
          (write-bibtex/bib! entries))))

See `examples/extract-bib.scm` for a more elaborate version of the
same thing.


Formatting entries
------------------

The overall structure of a beastie BibTeX parser is to parse a `.bib`
file using [`parse-bibtex-file`](#fn-parse-bibtex-file), and then
extract from it the entries required (possibly using `parse-aux-file`
and `filter-entries` as above).  This gives a list of `entry?`
objects.

It is frdequently convenient to then map these to a list of
[`citation?`](#fn-citation?) objects before writing out the results.
The object created by [`make-citation`](#fn-make-citation)
conveniently bundles together information about the citation,
accessible via
[`citation-entry`](#fn-citation-entry),
[`citation-html`](#fn-citation-html),
[`citation-key`](#fn-citation-key),
[`citation-reference`](#fn-citation-reference).
This is the approach taken by the `examples/plain.scm` style file in
the distribution, but if it's not useful to use that style, then this
bit of structure can be omitted.

When formatting an entry into an output, it's useful to use the
[`with-fields-from-entry`](#fn-with-fields-from-entry) function to
extract and validate entry contents, and the
[`maybe-list`](#fn-maybe-list),
[`maybe-list/qq`](#fn-maybe-list/qq),
[`maybe-sprintf`](#fn-maybe-sprintf)
functions to conditionally create output content, and the
[`sentence`](#fn-sentence) macro to add or omit full stops as
necessary.

For example, `plain.scm` contains a function `format-entry-article`,
which looks like:

    (define (format-entry-article e)
      (with-fields-from-entry e
          (author title journal year)
          (volume number pages doi note url)
        (let ((al (parse-author-list author)))
          (make-citation
           e
           (sprintf "~a, ~a" (format-authorlist/ref al) (or year "n.d."))
           `(li ,@(sentence
                   (a ((name ,(symbol->string (entry-key e))))
                      ,(format-authorlist/text al))
                   ,(sprintf " (~a), " year)
                   "‘" ,(titlecase-string/bst title) "’, "
                   (em ,journal)
                   ,(or (maybe-sprintf " (~a)" number) ", ")
                   ,(maybe-list 'strong (stringify/true volume) ", ")
                   ;alternatively ,@(maybe-list `(strong ,(stringify/true volume)))
                   ,@(maybe-list/qq " pp." nbsp ,(en-dashify pages)))
                ,@(format-links doi url)
                ,@(sentence ,note))))))

Within the `with-fields-from-entry` form, the fields for author,
title, journal and year are filled with the corresponding data, and if
they are absent, then beastie prints a warning and defines them to
have some dummy value (ie, they are guaranteed to be non-empty); and
the other fields are filled with entry data, or are set to `#f`.

The function returns a `citation?` object containing the entry, a
citation reference, and an xexpr for a bibliography entry (see the
`xexpr` module).  This `(li...)` list, corresponding to a `<li>`
element, is written using standard Scheme quasiquotation, with the
non-fixed parts of the list filled in as usual.

Here, the [`(maybe-sprintf " (~a)" number)`](#fn-maybe-sprintf) will format the journal
number containing the article.  If `number` is present, then this will
act as the function `sprintf`, but `number` is `#f` then this will
expand to `#f` without error.

The function `stringify/true` (from the `utils` module) will stringify
its argument, unless the argument is `#f`, when it will expand without
error to `#f`.  The form [`(maybe-list 'strong (stringify/true volume)
", ")`](#fn-maybe-list) will act list `(list ...)`, unless any of its
arguments are `#f`, when it will expand to a empty list
([`maybe-list/qq`](#fn-maybe-list/qq) does the same, but quasi-quotes
its arguments).

Finally, the [`(sentence ...)` macro](#fn-sentence) will expand to its contents as a
list, except with a full stop appended.  If this would be an empty
sentence, then it expands to `()`.
