Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

282 lines
9.5 KiB

;;; org-ref-pdf.el --- Drag-n-drop PDF onto bibtex files -*- lexical-binding: t; -*-
;; Copyright (C) 2015 John Kitchin
;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This library provides functions to enable drag-n-drop of pdfs onto a bibtex
;; buffer to add bibtex entries to it.
;; TODO: If no DOI is found, figure out a way to do a crossref/google query to
;; get a doi. This needs a reliable title/citation.
;;; Code:
(require 'f)
;; [2019-10-13 Sun] I am commenting this out for now. I added it for some
;; reason, but I cannot figure out why. It is pretty slow to load, so since I
;; don't know why it is here, I am commenting it out until it is obvious again.
;; (require 'pdf-tools)
(eval-when-compile
(require 'cl-lib))
(declare-function org-ref-bibtex-key-from-doi "org-ref-bibtex.el")
(defgroup org-ref-pdf nil
"Customization group for org-ref-pdf"
:tag "Org Ref PDF"
:group 'org-ref-pdf)
(defcustom pdftotext-executable
"pdftotext"
"Executable for pdftotext. Set if the executable is not on your
path, or you want to use another version."
:type 'file
:group 'org-ref-pdf)
(defcustom org-ref-pdf-doi-regex
"10\\.[0-9]\\{4,9\\}/[-+._;()/:A-Z0-9]+"
"Regular expression to match DOIs in a pdf converted to text."
:type 'regexp
:group 'org-ref-pdf)
(defcustom org-ref-pdf-to-bibtex-function
'copy-file
"Function for getting a pdf to the `org-ref-pdf-directory'.
Defaults to `copy-file', but could also be `rename-file'."
:type 'File :group 'org-ref-pdf)
(defun org-ref-extract-doi-from-pdf (pdf)
"Try to extract a doi from a PDF file.
There may be more than one doi in the file. This function returns
all the ones it finds based on two patterns: doi: up to a quote,
bracket, space or end of line. dx.doi.org/up to a quote, bracket,
space or end of line.
If there is a trailing . we chomp it off. Returns a list of doi
strings, or nil.
"
(with-temp-buffer
(insert (shell-command-to-string (format "%s %s -"
pdftotext-executable
(shell-quote-argument (dnd-unescape-uri pdf)))))
(goto-char (point-min))
(let ((matches '()))
(while (re-search-forward org-ref-pdf-doi-regex nil t)
;; I don't know how to avoid a trailing . on some dois with the
;; expression above, so if it is there, I chomp it off here.
(let ((doi (match-string 0)))
(when (s-ends-with? "." doi)
(setq doi (substring doi 0 (- (length doi) 1))))
(cl-pushnew doi matches :test #'equal)))
matches)))
(defun org-ref-pdf-doi-candidates (dois)
"Generate candidate list for helm source.
Used when multiple dois are found in a pdf file."
(cl-loop for doi in dois
collect
(condition-case nil
(cons
(plist-get (doi-utils-get-json-metadata doi) :title)
doi)
(error (cons (format "%s read error" doi) doi)))))
(defun org-ref-pdf-add-dois (_)
"Add all entries for CANDIDATE in `helm-marked-candidates'."
(cl-loop for doi in (helm-marked-candidates)
do
(doi-utils-add-bibtex-entry-from-doi
doi
(buffer-file-name))))
;;;###autoload
(defun org-ref-pdf-to-bibtex ()
"Add pdf of current buffer to bib file and save pdf to
`org-ref-default-bibliography'. The pdf should be open in Emacs
using the `pdf-tools' package."
(interactive)
(when (not (f-ext? (downcase (buffer-file-name)) "pdf"))
(error "Buffer is not a pdf file"))
;; Get doi from pdf of current buffer
(let* ((dois (org-ref-extract-doi-from-pdf (buffer-file-name)))
(doi-utils-download-pdf nil)
(doi (if (= 1 (length dois))
(car dois)
(completing-read "Select DOI: " dois))))
;; Add bib entry from doi:
(doi-utils-add-bibtex-entry-from-doi doi)
;; Copy pdf to `org-ref-pdf-directory':
(let ((key (org-ref-bibtex-key-from-doi doi)))
(funcall org-ref-pdf-to-bibtex-function
(buffer-file-name)
(expand-file-name (format "%s.pdf" key)
org-ref-pdf-directory)))))
;;;###autoload
;; (defun org-ref-pdf-dnd-func (event)
;; "Drag-n-drop support to add a bibtex entry from a pdf file."
;; (interactive "e")
;; (goto-char (nth 1 (event-start event)))
;; (x-focus-frame nil)
;; (let* ((payload (car (last event)))
;; (pdf (cadr payload))
;; (dois (org-ref-extract-doi-from-pdf pdf)))
;; (cond
;; ((null dois)
;; (message "No doi found in %s" pdf))
;; ((= 1 (length dois))
;; (doi-utils-add-bibtex-entry-from-doi
;; (car dois)
;; (buffer-file-name)))
;; ;; Multiple DOIs found
;; (t
;; (helm :sources `((name . "Select a DOI")
;; (candidates . ,(org-ref-pdf-doi-candidates dois))
;; (action . org-ref-pdf-add-dois)))))))
;; This isn't very flexible, as it hijacks all drag-n-drop events. I switched to
;; using `dnd-protocol-alist'.
;; (define-key bibtex-mode-map (kbd "<drag-n-drop>") 'org-ref-pdf-dnd-func)
;; This is what the original dnd function was.
;; (define-key bibtex-mode-map (kbd "<drag-n-drop>") 'ns-drag-n-drop)
;; I replaced the functionality above with this new approach that leverages
;; ns-drag-n-drop. An alternative approach would be to adapt the function above
;; so that if the item dragged on wasn't a pdf, it would use another function.
;; that is essentially what ns-drag-n-drop enables, multiple handlers for
;; different uris that get dropped on the windwo.
(defun org-ref-pdf-dnd-protocol (uri action)
"Drag-n-drop protocol.
PDF will be a string like file:path.
ACTION is what to do. It is required for `dnd-protocol-alist'.
This function should only apply when in a bibtex file."
(if (and (buffer-file-name)
(f-ext? (buffer-file-name) "bib"))
(let* ((path (substring uri 5))
dois)
(cond
((f-ext? path "pdf")
(setq dois (org-ref-extract-doi-from-pdf
path))
(cond
((null dois)
(message "No doi found in %s" path)
nil)
((= 1 (length dois))
;; we do not need to get the pdf, since we have one.
(let ((doi-utils-download-pdf nil))
(doi-utils-add-bibtex-entry-from-doi
(car dois)
(buffer-file-name))
;; we should copy the pdf to the pdf directory though
(let ((key (cdr (assoc "=key=" (bibtex-parse-entry)))))
(copy-file (dnd-unescape-uri path) (expand-file-name (format "%s.pdf" key) org-ref-pdf-directory))))
action)
;; Multiple DOIs found
(t
(helm :sources `((name . "Select a DOI")
(candidates . ,(org-ref-pdf-doi-candidates dois))
(action . org-ref-pdf-add-dois)))
action)))
;; drag a bib file on and add contents to the end of the file.
((f-ext? path "bib")
(goto-char (point-max))
(insert "\n")
(insert-file-contents path))))
;; ignoring. pass back to dnd. Copied from `org-download-dnd'. Apparently
;; returning nil does not do this.
(let ((dnd-protocol-alist
(rassq-delete-all
'org-ref-pdf-dnd-protocol
(copy-alist dnd-protocol-alist))))
(dnd-handle-one-url nil action uri))))
(add-to-list 'dnd-protocol-alist '("^file:" . org-ref-pdf-dnd-protocol))
;;;###autoload
(defun org-ref-pdf-dir-to-bibtex (bibfile directory)
"Create BIBFILE from pdf files in DIRECTORY."
(interactive (list
(read-file-name "Bibtex file: ")
(read-directory-name "Directory: ")))
(find-file bibfile)
(goto-char (point-max))
(cl-loop for pdf in (f-entries directory (lambda (f) (f-ext? f "pdf")))
do
(goto-char (point-max))
(let ((dois (org-ref-extract-doi-from-pdf pdf)))
(cond
((null dois)
(insert (format "%% No doi found to create entry in %s.\n" pdf)))
((= 1 (length dois))
(doi-utils-add-bibtex-entry-from-doi
(car dois)
(buffer-file-name))
(bibtex-beginning-of-entry)
(insert (format "%% [[file:%s]]\n" pdf)))
;; Multiple DOIs found
(t
(insert (format "%% Multiple dois found in %s\n" pdf))
(helm :sources `((name . "Select a DOI")
(candidates . ,(org-ref-pdf-doi-candidates dois))
(action . org-ref-pdf-add-dois))))))))
;;;###autoload
(defun org-ref-pdf-debug-pdf (pdf-file)
"Try to debug getting a doi from a pdf.
Opens a buffer with the pdf converted to text, and `occur' on the
variable `org-ref-pdf-doi-regex'."
(interactive "fPDF: ")
(switch-to-buffer (get-buffer-create "*org-ref-pdf debug*"))
(erase-buffer)
(insert (shell-command-to-string (format "%s %s -"
pdftotext-executable
(shell-quote-argument pdf-file))))
(goto-char (point-min))
(highlight-regexp org-ref-pdf-doi-regex)
(occur org-ref-pdf-doi-regex)
(switch-to-buffer-other-window "*Occur*"))
;;;###autoload
(defun org-ref-pdf-crossref-lookup ()
"Lookup highlighted text in PDFView in CrossRef."
(interactive)
(require 'pdf-view)
(pdf-view-assert-active-region)
(let* ((txt (pdf-view-active-region-text)))
(pdf-view-deactivate-region)
(crossref-lookup (mapconcat 'identity txt " \n"))))
(provide 'org-ref-pdf)
;;; org-ref-pdf.el ends here