ox-bibtex.el (15470B)
1 ;;; ox-bibtex.el --- Export bibtex fragments 2 3 ;; Copyright (C) 2009-2014, 2021 Taru Karttunen 4 5 ;; Author: Taru Karttunen <taruti@taruti.net> 6 ;; Nicolas Goaziou <n dot goaziou at gmail dot com> 7 ;; This file is not currently part of GNU Emacs. 8 9 ;; This program is free software; you can redistribute it and/or 10 ;; modify it under the terms of the GNU General Public License as 11 ;; published by the Free Software Foundation; either version 3, or (at 12 ;; your option) any later version. 13 14 ;; This program is distributed in the hope that it will be useful, but 15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of 16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 ;; General Public License for more details. 18 19 ;; You should have received a copy of the GNU General Public License 20 ;; along with this program ; see the file COPYING. If not, write to 21 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 22 ;; Boston, MA 02111-1307, USA. 23 24 ;;; Commentary: 25 ;; 26 ;; This is an utility to handle BibTeX export to LaTeX, html and ascii 27 ;; exports. For HTML and ascii it uses the bibtex2html software from: 28 ;; 29 ;; https://www.lri.fr/~filliatr/bibtex2html/ 30 ;; 31 ;; For ascii it uses the pandoc software from: 32 ;; 33 ;; https://pandoc.org/ 34 ;; 35 ;; It also introduces "cite" syntax for Org links. 36 ;; 37 ;; The usage is as follows: 38 ;; 39 ;; #+BIBLIOGRAPHY: bibfilename stylename optional-options 40 ;; 41 ;; e.g. given foo.bib and using style plain: 42 ;; 43 ;; #+BIBLIOGRAPHY: foo plain option:-d 44 ;; 45 ;; "stylename" can also be "nil", in which case no style will be used. 46 ;; 47 ;; Full filepaths are also possible: 48 ;; 49 ;; #+BIBLIOGRAPHY: /home/user/Literature/foo.bib plain option:-d 50 ;; 51 ;; Optional options are of the form: 52 ;; 53 ;; option:-foobar pass '-foobar' to bibtex2html 54 ;; 55 ;; e.g., 56 ;; 57 ;; option:-d sort by date 58 ;; option:-a sort as BibTeX (usually by author) *default* 59 ;; option:-u unsorted i.e. same order as in .bib file 60 ;; option:-r reverse the sort 61 ;; 62 ;; See the bibtex2html man page for more. Multiple options can be 63 ;; combined like: 64 ;; 65 ;; option:-d option:-r 66 ;; 67 ;; Limiting to only the entries cited in the document: 68 ;; 69 ;; limit:t 70 ;; 71 ;; For LaTeX export this simply inserts the lines 72 ;; 73 ;; \bibliographystyle{plain} 74 ;; \bibliography{foo} 75 ;; 76 ;; into the TeX file when exporting. 77 ;; 78 ;; For HTML export it: 79 ;; 1) converts all \cite{foo} and [[cite:foo]] to links to the 80 ;; bibliography, 81 ;; 2) creates a foo.html and foo_bib.html, 82 ;; 3) includes the contents of foo.html in the exported HTML file. 83 ;; 84 ;; For ascii export it: 85 ;; 1) converts all \cite{foo} and [[cite:foo]] to links to the 86 ;; bibliography, 87 ;; 2) creates a foo.txt and foo_bib.html, 88 ;; 3) includes the contents of foo.txt in the exported ascii file. 89 ;; 90 ;; For LaTeX export it: 91 ;; 1) converts all [[cite:foo]] to \cite{foo}. 92 93 ;; Initialization 94 95 (require 'cl-lib) 96 97 ;;; Internal Functions 98 99 (defun org-bibtex-get-file (keyword) 100 "Return bibliography file as a string. 101 KEYWORD is a \"BIBLIOGRAPHY\" keyword. If no file is found, 102 return nil instead." 103 (let ((value (org-element-property :value keyword))) 104 (and value 105 (string-match "\\(\\S-+\\)[ \t]+\\(\\S-+\\)\\(.*\\)" value) 106 (match-string 1 value)))) 107 108 (defun org-bibtex-get-style (keyword) 109 "Return bibliography style as a string. 110 KEYWORD is a \"BIBLIOGRAPHY\" keyword. If no style is found, 111 return nil instead." 112 (let ((value (org-element-property :value keyword))) 113 (and value 114 (string-match "\\(\\S-+\\)[ \t]+\\(\\S-+\\)\\(.*\\)" value) 115 (match-string 2 value)))) 116 117 (defun org-bibtex-get-arguments (keyword) 118 "Return \"bibtex2html\" arguments specified by the user. 119 KEYWORD is a \"BIBLIOGRAPHY\" keyword. Return value is a plist 120 containing `:options' and `:limit' properties. The former 121 contains a list of strings to be passed as options to 122 \"bibtex2html\" process. The latter contains a boolean." 123 (let ((value (org-element-property :value keyword))) 124 (and value 125 (string-match "\\(\\S-+\\)[ \t]+\\(\\S-+\\)\\(.*\\)" value) 126 (let (options limit) 127 (dolist (arg (split-string (match-string 3 value)) 128 ;; Return value. 129 (list :options (nreverse options) :limit limit)) 130 (let* ((s (split-string arg ":")) 131 (key (car s)) 132 (value (nth 1 s))) 133 (cond ((equal "limit" key) 134 (setq limit (not (equal "nil" value)))) 135 ((equal "option" key) (push value options))))))))) 136 137 (defun org-bibtex-citation-p (object) 138 "Non-nil when OBJECT is a citation." 139 (cl-case (org-element-type object) 140 (link (equal (org-element-property :type object) "cite")) 141 (latex-fragment 142 (string-match "\\`\\\\cite{" (org-element-property :value object))))) 143 144 (defun org-bibtex-get-citation-key (citation) 145 "Return key for a given citation, as a string. 146 CITATION is a `latex-fragment' or `link' type object satisfying 147 to `org-bibtex-citation-p' predicate." 148 (if (eq (org-element-type citation) 'link) 149 (org-element-property :path citation) 150 (let ((value (org-element-property :value citation))) 151 (and (string-match "\\`\\\\cite{" value) 152 (substring value (match-end 0) -1))))) 153 154 155 ;;; Follow cite: links 156 157 (defvar org-bibtex-file nil 158 "Org file of BibTeX entries.") 159 160 (defun org-bibtex-goto-citation (&optional citation) 161 "Visit a citation given its ID." 162 (interactive) 163 (let ((citation (or citation (completing-read "Citation: " (obe-citations))))) 164 (find-file (or org-bibtex-file 165 (error "`org-bibtex-file' has not been configured"))) 166 (let ((position (org-find-property "CUSTOM_ID" citation))) 167 (and position (progn (goto-char position) t))))) 168 169 (let ((jump-fn (car (cl-remove-if-not #'fboundp '(ebib org-bibtex-goto-citation))))) 170 (org-add-link-type "cite" jump-fn)) 171 172 173 174 ;;; Filters 175 176 (defun org-bibtex-process-bib-files (tree backend info) 177 "Send each bibliography in parse tree to \"bibtex2html\" process. 178 Return new parse tree." 179 (when (org-export-derived-backend-p backend 'ascii 'html) 180 ;; Initialize dynamically scoped variables. The first one 181 ;; contain an alist between keyword objects and their HTML 182 ;; translation. The second one will contain an alist between 183 ;; citation keys and names in the output (according to style). 184 (setq org-bibtex-html-entries-alist nil 185 org-bibtex-html-keywords-alist nil) 186 (org-element-map tree 'keyword 187 (lambda (keyword) 188 (when (equal (org-element-property :key keyword) "BIBLIOGRAPHY") 189 (let ((arguments (org-bibtex-get-arguments keyword)) 190 (file (org-bibtex-get-file keyword)) 191 temp-file 192 out-file) 193 ;; Test if filename is given with .bib-extension and strip 194 ;; it off. Filenames with another extensions will be 195 ;; untouched and will finally rise an error in bibtex2html. 196 (setq file (if (equal (file-name-extension file) "bib") 197 (file-name-sans-extension file) file)) 198 ;; Outpufiles of bibtex2html will be put into current working directory 199 ;; so define a variable for this. 200 (setq out-file (file-name-sans-extension 201 (file-name-nondirectory file))) 202 ;; limit is set: collect citations throughout the document 203 ;; in TEMP-FILE and pass it to "bibtex2html" as "-citefile" 204 ;; argument. 205 (when (plist-get arguments :limit) 206 (let ((citations 207 (org-element-map tree '(latex-fragment link) 208 (lambda (object) 209 (and (org-bibtex-citation-p object) 210 (org-bibtex-get-citation-key object)))))) 211 (with-temp-file (setq temp-file (make-temp-file "ox-bibtex")) 212 (insert (mapconcat 'identity citations "\n"))) 213 (setq arguments 214 (plist-put arguments 215 :options 216 (append (plist-get arguments :options) 217 (list "-citefile" temp-file)))))) 218 ;; Call "bibtex2html" on specified file. 219 (unless (eq 0 (apply 220 'call-process 221 (append '("bibtex2html" nil nil nil) 222 '("-a" "-nodoc" "-noheader" "-nofooter") 223 (let ((style 224 (org-not-nil 225 (org-bibtex-get-style keyword)))) 226 (and style (list "--style" style))) 227 (plist-get arguments :options) 228 (list (concat file ".bib"))))) 229 (error "Executing bibtex2html failed")) 230 (and temp-file (delete-file temp-file)) 231 ;; Open produced HTML file, and collect Bibtex key names 232 (with-temp-buffer 233 (insert-file-contents (concat out-file ".html")) 234 ;; Update `org-bibtex-html-entries-alist'. 235 (goto-char (point-min)) 236 (while (re-search-forward 237 "a name=\"\\([-_a-zA-Z0-9:]+\\)\">\\([^<]+\\)" nil t) 238 (push (cons (match-string 1) (match-string 2)) 239 org-bibtex-html-entries-alist))) 240 ;; Open produced HTML file, wrap references within a block and 241 ;; return it. 242 (with-temp-buffer 243 (cond 244 ((org-export-derived-backend-p backend 'html) 245 (insert (format "<div id=\"bibliography\">\n<h2>%s</h2>\n" 246 (org-export-translate "References" :html info))) 247 (insert-file-contents (concat out-file ".html")) 248 (goto-char (point-max)) 249 (insert "\n</div>")) 250 ((org-export-derived-backend-p backend 'ascii) 251 ;; convert HTML references to text w/pandoc 252 (unless (eq 0 (call-process "pandoc" nil nil nil 253 (concat out-file ".html") 254 "-o" 255 (concat out-file ".txt"))) 256 (error "Executing pandoc failed")) 257 (insert 258 (format 259 "%s\n==========\n\n" 260 (org-export-translate 261 "References" 262 (intern (format ":%s" (plist-get info :ascii-charset))) 263 info))) 264 (insert-file-contents (concat out-file ".txt")) 265 (goto-char (point-min)) 266 (while (re-search-forward 267 "\\[ \\[bib\\][^ ]+ \\(\\]\\||[\n\r]\\)" nil t) 268 (replace-match "")) 269 (goto-char (point-min)) 270 (while (re-search-forward "\\( \\]\\| \\]\\| |\\)" nil t) 271 (replace-match "")) 272 (goto-char (point-min)) 273 (while (re-search-forward "[\n\r]\\([\n\r][\n\r]\\)" nil t) 274 (replace-match "\\1")))) 275 ;; Update `org-bibtex-html-keywords-alist'. 276 (push (cons keyword (buffer-string)) 277 org-bibtex-html-keywords-alist))))))) 278 ;; Return parse tree unchanged. 279 tree) 280 281 (defun org-bibtex-merge-contiguous-citations (tree backend info) 282 "Merge all contiguous citation in parse tree. 283 As a side effect, this filter will also turn all \"cite\" links 284 into \"\\cite{...}\" LaTeX fragments and will extract options. 285 Cite options are placed into square brackets at the beginning of 286 the \"\\cite\" command for the LaTeX backend, and are removed for 287 the HTML and ASCII backends." 288 (when (org-export-derived-backend-p backend 'html 'latex 'ascii) 289 (org-element-map tree '(link latex-fragment) 290 (lambda (object) 291 (when (org-bibtex-citation-p object) 292 (let ((new-citation (list 'latex-fragment 293 (list :value "" 294 :post-blank (org-element-property 295 :post-blank object)))) 296 option) 297 ;; Insert NEW-CITATION right before OBJECT. 298 (org-element-insert-before new-citation object) 299 ;; Remove all subsequent contiguous citations from parse 300 ;; tree, keeping only their citation key. 301 (let ((keys (list (org-bibtex-get-citation-key object))) 302 next) 303 (while (and (setq next (org-export-get-next-element object info)) 304 (or (and (stringp next) 305 (not (string-match-p "\\S-" next))) 306 (org-bibtex-citation-p next))) 307 (unless (stringp next) 308 (push (org-bibtex-get-citation-key next) keys)) 309 (org-element-extract-element object) 310 (setq object next)) 311 ;; Find any options in keys, e.g., "(Chapter 2)key" has 312 ;; the option "Chapter 2". 313 (setq keys 314 (mapcar 315 (lambda (k) 316 (if (string-match "^(\\([^)]\+\\))\\(.*\\)" k) 317 (progn 318 (when (org-export-derived-backend-p backend 'latex) 319 (setq option (format "[%s]" (match-string 1 k)))) 320 (match-string 2 k)) 321 k)) 322 keys)) 323 (org-element-extract-element object) 324 ;; Eventually merge all keys within NEW-CITATION. Also 325 ;; ensure NEW-CITATION has the same :post-blank property 326 ;; as the last citation removed. 327 (org-element-put-property 328 new-citation 329 :post-blank (org-element-property :post-blank object)) 330 (org-element-put-property 331 new-citation 332 :value (format "\\cite%s{%s}" 333 (or option "") 334 (mapconcat 'identity (nreverse keys) ","))))))))) 335 tree) 336 337 (eval-after-load 'ox 338 '(progn (add-to-list 'org-export-filter-parse-tree-functions 339 'org-bibtex-process-bib-files) 340 (add-to-list 'org-export-filter-parse-tree-functions 341 'org-bibtex-merge-contiguous-citations))) 342 343 344 345 ;;; LaTeX Part 346 347 (defadvice org-latex-keyword (around bibtex-keyword) 348 "Translate \"BIBLIOGRAPHY\" keywords into LaTeX syntax. 349 Fallback to `latex' back-end for other keywords." 350 (let ((keyword (ad-get-arg 0))) 351 (if (not (equal (org-element-property :key keyword) "BIBLIOGRAPHY")) 352 ad-do-it 353 (let ((file (org-bibtex-get-file keyword)) 354 (style (org-not-nil (org-bibtex-get-style keyword)))) 355 (setq ad-return-value 356 (when file 357 (concat (and style (format "\\bibliographystyle{%s}\n" style)) 358 (format "\\bibliography{%s}" file)))))))) 359 360 (ad-activate 'org-latex-keyword) 361 362 363 364 ;;; HTML Part 365 366 (defvar org-bibtex-html-entries-alist nil) ; Dynamically scoped. 367 (defvar org-bibtex-html-keywords-alist nil) ; Dynamically scoped. 368 369 370 ;;;; Advices 371 372 (defadvice org-html-keyword (around bibtex-keyword) 373 "Translate \"BIBLIOGRAPHY\" keywords into HTML syntax. 374 Fallback to `html' back-end for other keywords." 375 (let ((keyword (ad-get-arg 0))) 376 (if (not (equal (org-element-property :key keyword) "BIBLIOGRAPHY")) 377 ad-do-it 378 (setq ad-return-value 379 (cdr (assq keyword org-bibtex-html-keywords-alist)))))) 380 381 (defadvice org-html-latex-fragment (around bibtex-citation) 382 "Translate \"\\cite\" LaTeX fragments into HTML syntax. 383 Fallback to `html' back-end for other keywords." 384 (let ((fragment (ad-get-arg 0))) 385 (if (not (org-bibtex-citation-p fragment)) ad-do-it 386 (setq ad-return-value 387 (format "[%s]" 388 (mapconcat 389 (lambda (key) 390 (format "<a href=\"#%s\">%s</a>" 391 key 392 (or (cdr (assoc key org-bibtex-html-entries-alist)) 393 key))) 394 (org-split-string 395 (org-bibtex-get-citation-key fragment) ",") ",")))))) 396 397 (ad-activate 'org-html-keyword) 398 (ad-activate 'org-html-latex-fragment) 399 400 401 ;;; Ascii Part 402 (defadvice org-ascii-keyword (around bibtex-keyword) 403 "Translate \"BIBLIOGRAPHY\" keywords into ascii syntax. 404 Fallback to `ascii' back-end for other keywords." 405 (let ((keyword (ad-get-arg 0))) 406 (if (not (equal (org-element-property :key keyword) "BIBLIOGRAPHY")) 407 ad-do-it 408 (setq ad-return-value 409 (cdr (assq keyword org-bibtex-html-keywords-alist)))))) 410 411 (defadvice org-ascii-latex-fragment (around bibtex-citation) 412 "Translate \"\\cite\" LaTeX fragments into ascii syntax. 413 Fallback to `ascii' back-end for other keywords." 414 (let ((fragment (ad-get-arg 0))) 415 (if (not (org-bibtex-citation-p fragment)) ad-do-it 416 (setq ad-return-value 417 (format "[%s]" 418 (mapconcat 419 (lambda (key) 420 (or (cdr (assoc key org-bibtex-html-entries-alist)) 421 key)) 422 (org-split-string 423 (org-bibtex-get-citation-key fragment) ",") ",")))))) 424 425 (ad-activate 'org-ascii-keyword) 426 (ad-activate 'org-ascii-latex-fragment) 427 428 (provide 'ox-bibtex) 429 430 ;;; ox-bibtex.el ends here