dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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