;; Given a .aux file, create a .bib file which contains the references ;; it requires, as extracted from the one or more .bib files it ;; references. ;; ;; With options -Ocrossref and -Oyaml, produce the output in ;; (rough) crossref format, or YAML, respectively. ;; ;; Usage: ;; ;; beastie [-O format] [-p] extract-bib.scm foo.aux ;; ;; Just to show how (and to support \cite{*}) we also output the ;; result sorted in order of increasing date+authorname. ;; ;; This file primarily illustrates calling `call-with-aux-file` with a ;; suitable worker procedure, but it also illustrates calling ;; `getopt`. The worker procedure here calls one of three procedures, ;; depending on the -O option, whcih illustrate ;; `with-fields-from-entry` and basic use of the `bibtex` module. ;; ;; The use of ustring->ustring illustrates getting rid of TeXisms in a ;; case where the output is not expected to be fed to LaTeX later. ;; ;; This works with beastie v0.14. ;; The functions it uses may change with later versions. ;; ;; This file is part of Beastie ;; SPDX-FileCopyrightText: 2023 Norman Gray ;; SPDX-License-Identifier: BSD-2-Clause (module 'aux 'bibtex 'authors 'xexpr 'subtex) ;; plain.scm exports assemble-bibliography (load "plain.scm") ;; Pick a format ;(define *name-format* '((von "~") (last) (", " junior) (", " first))) (define *name-format* '((first " ") (von "~") (last) (", " junior))) (define *prettyprint?* #t) ;; Given a list of citation? objects, extract the BibTeX entry ;; from each one, and print it as a .bib entry. (define (print/bibtex! citation-list) (for-each (λ (c) (entry-print! (citation-entry c))) citation-list)) ;; Similarly, printing as a Crossref ... element. ;; For crossref details, see ;; ;; ;; This isn't a particularly careful crossref-writer, so there may be ;; a variety of crossref subtleties being omitted/ignored here. ;; ;; Thanks to Karl Berry for the (implicit) suggestion here. (define (print/crossref! citation-list) (define (first-page page-range/str) ;; Return the first page in the range. ;; However, if page-range/str is #f, return that; ;; or if it's present but not a valid/expected page-range, ;; then return that, also. (cond ((string->page-range page-range/str) => car) (else page-range/str))) (define (citation/default c) (with-fields-from-entry (citation-entry c) () (title year author doi isbn) (list (and author (let ((authors (parse-author-list author))) `(author ,(untexify-string-or-list (format-name *name-format* (car authors)))))) (and title `(volume_title ,(untexify-string-or-list title))) (and year `(cYear ,year)) (and doi `(doi ,doi)) (and isbn `(isbn ,isbn)) `(unstructured_citation ,(xexpr-text (citation-html c)))))) (define (citation/article c) (with-fields-from-entry (citation-entry c) () (journal title booktitle year volume number pages author doi isbn) (list (and journal `(journal_title ,journal)) (and title `(article_title ,(untexify-string-or-list title))) (and booktitle `(volume_title ,(untexify-string-or-list booktitle))) (and author (let ((authors (parse-author-list author))) `(author ,(format-name *name-format* (car authors))))) (and volume `(volume ,volume)) (and number `(issue ,number)) (and pages `(first_page ,(first-page pages))) (and year `(cYear ,year)) (and doi `(doi ,doi)) (and isbn `(isbn ,isbn)) `(unstructured_citation ,(xexpr-text (citation-html c)))))) (define (citation->crossref-citation c) (let ((e (citation-entry c))) ;; Given an entry?, produce a as xexpr?. ;; ;; Each of the citation/foo functions returns a list of (or/c xexpr? #f) `(citation ((key ,(symbol->string (entry-key e)))) . ,(filter values (case (entry-type e) ((article incollection inproceedings) (citation/article c)) (else (citation/default c))))))) (xexpr-write/xml! `(citation_list . ,(map citation->crossref-citation citation-list)) :expand-entities? #t :prettyprint? *prettyprint?*)) ;; ...or printing as YAML, with keys matching BibTeX field names. ;; ;; Here, note the call to ustring->ustring, ;; to remove TeXisms "~" and "{...}" from the output. (define (print/yaml! citation-list) (define (print-field/yaml! field entry) (case field ((author editor translator) (let ((authors (entry-field/authorlist entry field))) (printf " ~a:~%" field) (for-each (λ (a) (printf " - ~a~%" (ustring->ustring (format-name *name-format* a) :nbsp " " :braces? #f))) authors))) (else (printf " ~a: \"~a\"~%" field (entry-field entry field))))) (define (print-entry/yaml! c) (let ((e (citation-entry c))) (printf "- key: ~a~% type: ~a~%" (entry-key e) (entry-type e)) (for-each (λ (f) (print-field/yaml! f e)) (entry-fields e)))) (for-each print-entry/yaml! citation-list)) (define (exit/usage) (eprintf "Usage:~% beastie [-O fmt] [-p] extract-bib.scm foo.aux~%~%Respects the value of $BIBINPUTS.~%fmt can be bibtex|crossref|yaml (default bibtex/.bib output).~%Option -p does (limited) prettyprinting.~%") (exit 1)) ;; Parse the argument list. ;; ;; This defines values *AUX-FILE* and PRINT-ENTRIES! which are respectively ;; the .aux file to be processed, and the function -- one of print/bibtex!, ;; print/crossref! or print/yaml! -- to write them out. (define-values (*aux-file* print-entries!) (receive (cmd opts args) (getopt '((#\O format "Output format: bibtex | crossref | yaml (default bibtex)" (string->symbol format)) (#\p "Prettyprint output (currently only has an effect for 'crossref' output)." (set! *prettyprint?* #t)))) (values (case (length args) ((0) (exit/usage)) ((1) (car args)) (else (eprintf "Too many arguments.~%") (exit/usage))) (cond ((assq #\O opts) => (λ (format-option) (case (cdr format-option) ((bibtex) print/bibtex!) ((crossref) print/crossref!) ((yaml) print/yaml!) (else (eprintf "Unexpected -O option: ~a~%" (cdr format-option)) (exit/usage))))) (else ;default: output .bib print/bibtex!))))) ;; Do the work (print-entries! (call-with-aux-file *aux-file* (λ (citations bibfiles _ignored) ;; call-with-aux-file guarantees citations is a list (unless (list? bibfiles) (eprintf "extract-bib.scm: I didn't find any \\bibdata{foo} in the .aux file, ~a~%" *aux-file*) (exit 1)) (assemble-bibliography citations bibfiles)))) ;; The above uses assemble-bibliography, from plain.scm. ;; ;; An alternative way of doing most of the above is below. ;; This works fine, but doesn't make it easy to include the 'unstructured-citation' ;; elements of the writers above. ;; ;; Below, the extract-aux function returns a sorted list of entry? objects, ;; which can be printed by a slightly different version of print/bibtex! and friends ;; which take an entry? rather than a citation?. ;; Extract the requested citations (may be 'all) from the one or more bibfiles ;; (bibstyle is ignored). ;; (define (extract-aux citations bibfiles bibstyle) ;; (unless (and (or (eqv? citations 'all) ;; (list? citations)) ;; (list? bibfiles)) ;; (define (list/printing l) ;; (cond ((not l) "#f") ;; ((null? l) "()") ;; (else (sprintf "(~s...)" (car l))))) ;; (eprintf "extract-aux: citation list = ~a, and bibfiles list = ~a must both be lists~%" ;; (list/printing citations) (list/printing bibfiles)) ;; (exit 1)) ;; ;; (let ((citations/set ;the list of citations as a set/hash ;; (if (eqv? citations 'all) ;; (λ (key) #t) ;dummy: include all entries in the output ;; (make-set/eqv citations))) ;; (allbib ;parse all of the mentioned .bib files ;; (apply append (map parse-bibtex-file bibfiles)))) ;; (sort! (fold (lambda (e knil) ;; ;; include only those entries whose key appears in citations/set ;; (if (citations/set (entry-key e)) ;; (cons e knil) ;; knil)) ;; '() ;; allbib) ;; entry (lambda (result) ;; (list (car result) (cadr result) #f))) ;; (else #f))) ;; ;; (print-entries! ;; (call-with-aux-file *aux-file* ;; extract-aux ;; :parse-aux-file parse-aux-file/nobst))