;; Insert a bibliography into a Markdown file, to produce XHTML or basic Markdown. ;; ;; This version uses the built-in support for (RMarkdown-style) citations ;; (cf, ). ;; ;; Usage: ;; ;; beastie bibliography-in-markdown.scm [-bms] [-B bib.xml] [-A foo.aux] foo.md bibfile.bib ;; ;; With option `-b`, output the document body only, as XML, rather than a complete ;; XHTML document. ;; ;; With option `-s`, write out the document as an xexpr, rather than X(HT)ML ;; ;; With option `-m`, write out the document as Markdown (this is currently rather unreliable). ;; ;; With option `-B bib.xml`, write out a separate copy of the bibliography. ;; ;; With option `-A foo.aux`, write out a synthetic .aux file ;; corresponding to the citations found in the document. ;; ;; The citations are indicated by [@ref], ;; and the .bib database by ;; ;; ---- ;; bibliography: foo.bib ;; ;; This works with beastie v0.14. ;; The functions it uses, and their interface, may change with later versions. ;; ;; This is intended to be a simple demo. It has limitations: ;; ;; * It currently supports only a subset of types explicitly. ;; * There's no discrimination between colliding author-year pairs ;; (ie, no 1905a/b/c) ;; * No distinction between (the equivalents of) \cite, \citep, \citeyear ;; ;; Respects BIBINPUTS. ;; ;; This file is part of Beastie ;; SPDX-FileCopyrightText: 2023 Norman Gray ;; SPDX-License-Identifier: BSD-2-Clause (module 'markdown 'xexpr 'bibtex) (define (Usage) (eprintf #"""Insert a bibliography into a Markdown file. The citations are identified by [@ref]. Usage: beastie ~a [-bms] foo.md [database.bib ...] If option '-b' is present, then write out only the document body, in
; if it is absent, then write a complete XHTML document. The bibliography can be specified either on the command line, or in a `bibliography` key in the metadata at the top of the input file. Respects the value of $BIBINPUTS.""" (car *command-line*)) (exit 1)) (define document-output? #t) (define output-format 'xml) (define bib-output-file #f) (define copy-aux-file #f) ;; Identify *INPUT-MARKDOWN* and *BIBTEX-DATABASES* from the command-line. ;; Set other options. (define-values (*input-markdown* *bibtex-databases*) (receive (cmd opts args) (getopt '((#\b "Produce only the body of an XHTML document, in
, rather than a complete one" (set! document-output? #f)) (#\m "Write out (basic) Markdown, rather than XML" (set! output-format 'md)) (#\s "Write out a scheme xexpr, rather than X(HT)ML" (set! output-format 'xexpr)) (#\B bibfile "Write a separate copy of the bibliography to the given file" (set! bib-output-file bibfile)) (#\A auxfile "Write a synthetic .aux file." (set! copy-aux-file auxfile)))) (if (>= (length args) 1) (values (car args) (cdr args)) (Usage)))) ;; load plain.scm, which exports assemble-bibliography (module "plain.scm") ;; Let's go... (receive (parse-tree metadata) (parse-markdown-file/metadata *input-markdown*) (unless parse-tree (beastie-error "can't parse markdown file ~a" *input-markdown*)) ;; parse-tree is a sexp representation of the input Markdown: ;; eg (div (p "This is a " (a ((href "http://example.org")) "link")) ...) ;; metadata is information about the content of the file (links and annotations) ;; which can be queried with function METADATA/TYPE ;; METADATA/TYPE with 'citation returns a list of lists -- see docs for that function (let ((citation-elements ;; a (string? -> list?) hash table, where the values are ;; elements in the parse markdown tree (metadata/type metadata 'citation)) (annotations (apply hash-table (apply append (metadata/type metadata 'annotation))))) (when (annotations "bibliography") (set! *bibtex-databases* (cons (annotations "bibliography") *bibtex-databases*))) (when copy-aux-file (with-output-to-file copy-aux-file (λ () (printf "\\relax~%") (for-each (λ (cite) (printf "\\citation{~a}~%" cite)) (map car citation-elements)) (when (annotations "bibliography") (printf "\\bibdata{~a}~%" (annotations "bibliography")))))) (let* ((bib (assemble-bibliography ;;...from plain.scm; produces a list of citation? objects (map car citation-elements) (map (λ (bib) (resolve-file bib ".bib")) *bibtex-databases*))) (citations/key ;; make a key -> reference lookup, as a (symbol? -> string?) hash table (apply hash-table (apply append (map (λ (citation) (list (citation-key citation) (citation-reference citation))) bib))))) ;; Now modify the parse-tree, by running through the CITATION-ELEMENTS list, ;; editing the (cite "foo") elements it refers to. ;; An alternative approach would be to map tree->tree, producing ;; a new document. (for-each (λ (c) ;c is (ref (cite "foo")) (let* ((ref (car c)) (el (cadr c)) (citation (citations/key ref))) (unless citation (eprintf "Undefined citation key: ~a~%" ref)) ;; now edit the tree in place (set-cdr! el `((a ((href ,(sprintf "#~a" ref))) ,(or citation "???")))))) citation-elements) ;; ...and write out the result (let ((output-xexprs `(,@(cdr parse-tree) (h2 "Bibliography") (ul . ,(map citation-html bib))))) (case output-format ((xml) (if document-output? (let ((title (or (annotations "title") "TITLE"))) (xexpr-write/xhtml! `(div (h1 ,title) . ,output-xexprs))) (xexpr-write/xml! `(div . ,output-xexprs)))) ((xexpr) ;; dump the xexpr using built-on WRITE, ;; but telling s7 not to crop lists while doing so (let-temporarily (((*s7* 'print-length) (*s7* 'max-list-length))) (for-each (λ (xe) (write xe) (newline)) output-xexprs))) ((md) (xexpr-write/md! output-xexprs)) (else (eprintf "Unexpected output format: ~s~%" output-format)))) (when bib-output-file (with-output-to-file bib-output-file (λ () (xexpr-write/xml! `(ul . ,(map citation-html bib)))))))))