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

5 years ago
  1. ;;; org-ref-pdf.el --- Drag-n-drop PDF onto bibtex files -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2015 John Kitchin
  3. ;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
  4. ;; Keywords:
  5. ;; This program is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; This library provides functions to enable drag-n-drop of pdfs onto a bibtex
  17. ;; buffer to add bibtex entries to it.
  18. ;; TODO: If no DOI is found, figure out a way to do a crossref/google query to
  19. ;; get a doi. This needs a reliable title/citation.
  20. ;;; Code:
  21. (require 'f)
  22. ;; [2019-10-13 Sun] I am commenting this out for now. I added it for some
  23. ;; reason, but I cannot figure out why. It is pretty slow to load, so since I
  24. ;; don't know why it is here, I am commenting it out until it is obvious again.
  25. ;; (require 'pdf-tools)
  26. (eval-when-compile
  27. (require 'cl-lib))
  28. (declare-function org-ref-bibtex-key-from-doi "org-ref-bibtex.el")
  29. (defgroup org-ref-pdf nil
  30. "Customization group for org-ref-pdf"
  31. :tag "Org Ref PDF"
  32. :group 'org-ref-pdf)
  33. (defcustom pdftotext-executable
  34. "pdftotext"
  35. "Executable for pdftotext. Set if the executable is not on your
  36. path, or you want to use another version."
  37. :type 'file
  38. :group 'org-ref-pdf)
  39. (defcustom org-ref-pdf-doi-regex
  40. "10\\.[0-9]\\{4,9\\}/[-+._;()/:A-Z0-9]+"
  41. "Regular expression to match DOIs in a pdf converted to text."
  42. :type 'regexp
  43. :group 'org-ref-pdf)
  44. (defcustom org-ref-pdf-to-bibtex-function
  45. 'copy-file
  46. "Function for getting a pdf to the `org-ref-pdf-directory'.
  47. Defaults to `copy-file', but could also be `rename-file'."
  48. :type 'File :group 'org-ref-pdf)
  49. (defun org-ref-extract-doi-from-pdf (pdf)
  50. "Try to extract a doi from a PDF file.
  51. There may be more than one doi in the file. This function returns
  52. all the ones it finds based on two patterns: doi: up to a quote,
  53. bracket, space or end of line. dx.doi.org/up to a quote, bracket,
  54. space or end of line.
  55. If there is a trailing . we chomp it off. Returns a list of doi
  56. strings, or nil.
  57. "
  58. (with-temp-buffer
  59. (insert (shell-command-to-string (format "%s %s -"
  60. pdftotext-executable
  61. (shell-quote-argument (dnd-unescape-uri pdf)))))
  62. (goto-char (point-min))
  63. (let ((matches '()))
  64. (while (re-search-forward org-ref-pdf-doi-regex nil t)
  65. ;; I don't know how to avoid a trailing . on some dois with the
  66. ;; expression above, so if it is there, I chomp it off here.
  67. (let ((doi (match-string 0)))
  68. (when (s-ends-with? "." doi)
  69. (setq doi (substring doi 0 (- (length doi) 1))))
  70. (cl-pushnew doi matches :test #'equal)))
  71. matches)))
  72. (defun org-ref-pdf-doi-candidates (dois)
  73. "Generate candidate list for helm source.
  74. Used when multiple dois are found in a pdf file."
  75. (cl-loop for doi in dois
  76. collect
  77. (condition-case nil
  78. (cons
  79. (plist-get (doi-utils-get-json-metadata doi) :title)
  80. doi)
  81. (error (cons (format "%s read error" doi) doi)))))
  82. (defun org-ref-pdf-add-dois (_)
  83. "Add all entries for CANDIDATE in `helm-marked-candidates'."
  84. (cl-loop for doi in (helm-marked-candidates)
  85. do
  86. (doi-utils-add-bibtex-entry-from-doi
  87. doi
  88. (buffer-file-name))))
  89. ;;;###autoload
  90. (defun org-ref-pdf-to-bibtex ()
  91. "Add pdf of current buffer to bib file and save pdf to
  92. `org-ref-default-bibliography'. The pdf should be open in Emacs
  93. using the `pdf-tools' package."
  94. (interactive)
  95. (when (not (f-ext? (downcase (buffer-file-name)) "pdf"))
  96. (error "Buffer is not a pdf file"))
  97. ;; Get doi from pdf of current buffer
  98. (let* ((dois (org-ref-extract-doi-from-pdf (buffer-file-name)))
  99. (doi-utils-download-pdf nil)
  100. (doi (if (= 1 (length dois))
  101. (car dois)
  102. (completing-read "Select DOI: " dois))))
  103. ;; Add bib entry from doi:
  104. (doi-utils-add-bibtex-entry-from-doi doi)
  105. ;; Copy pdf to `org-ref-pdf-directory':
  106. (let ((key (org-ref-bibtex-key-from-doi doi)))
  107. (funcall org-ref-pdf-to-bibtex-function
  108. (buffer-file-name)
  109. (expand-file-name (format "%s.pdf" key)
  110. org-ref-pdf-directory)))))
  111. ;;;###autoload
  112. ;; (defun org-ref-pdf-dnd-func (event)
  113. ;; "Drag-n-drop support to add a bibtex entry from a pdf file."
  114. ;; (interactive "e")
  115. ;; (goto-char (nth 1 (event-start event)))
  116. ;; (x-focus-frame nil)
  117. ;; (let* ((payload (car (last event)))
  118. ;; (pdf (cadr payload))
  119. ;; (dois (org-ref-extract-doi-from-pdf pdf)))
  120. ;; (cond
  121. ;; ((null dois)
  122. ;; (message "No doi found in %s" pdf))
  123. ;; ((= 1 (length dois))
  124. ;; (doi-utils-add-bibtex-entry-from-doi
  125. ;; (car dois)
  126. ;; (buffer-file-name)))
  127. ;; ;; Multiple DOIs found
  128. ;; (t
  129. ;; (helm :sources `((name . "Select a DOI")
  130. ;; (candidates . ,(org-ref-pdf-doi-candidates dois))
  131. ;; (action . org-ref-pdf-add-dois)))))))
  132. ;; This isn't very flexible, as it hijacks all drag-n-drop events. I switched to
  133. ;; using `dnd-protocol-alist'.
  134. ;; (define-key bibtex-mode-map (kbd "<drag-n-drop>") 'org-ref-pdf-dnd-func)
  135. ;; This is what the original dnd function was.
  136. ;; (define-key bibtex-mode-map (kbd "<drag-n-drop>") 'ns-drag-n-drop)
  137. ;; I replaced the functionality above with this new approach that leverages
  138. ;; ns-drag-n-drop. An alternative approach would be to adapt the function above
  139. ;; so that if the item dragged on wasn't a pdf, it would use another function.
  140. ;; that is essentially what ns-drag-n-drop enables, multiple handlers for
  141. ;; different uris that get dropped on the windwo.
  142. (defun org-ref-pdf-dnd-protocol (uri action)
  143. "Drag-n-drop protocol.
  144. PDF will be a string like file:path.
  145. ACTION is what to do. It is required for `dnd-protocol-alist'.
  146. This function should only apply when in a bibtex file."
  147. (if (and (buffer-file-name)
  148. (f-ext? (buffer-file-name) "bib"))
  149. (let* ((path (substring uri 5))
  150. dois)
  151. (cond
  152. ((f-ext? path "pdf")
  153. (setq dois (org-ref-extract-doi-from-pdf
  154. path))
  155. (cond
  156. ((null dois)
  157. (message "No doi found in %s" path)
  158. nil)
  159. ((= 1 (length dois))
  160. ;; we do not need to get the pdf, since we have one.
  161. (let ((doi-utils-download-pdf nil))
  162. (doi-utils-add-bibtex-entry-from-doi
  163. (car dois)
  164. (buffer-file-name))
  165. ;; we should copy the pdf to the pdf directory though
  166. (let ((key (cdr (assoc "=key=" (bibtex-parse-entry)))))
  167. (copy-file (dnd-unescape-uri path) (expand-file-name (format "%s.pdf" key) org-ref-pdf-directory))))
  168. action)
  169. ;; Multiple DOIs found
  170. (t
  171. (helm :sources `((name . "Select a DOI")
  172. (candidates . ,(org-ref-pdf-doi-candidates dois))
  173. (action . org-ref-pdf-add-dois)))
  174. action)))
  175. ;; drag a bib file on and add contents to the end of the file.
  176. ((f-ext? path "bib")
  177. (goto-char (point-max))
  178. (insert "\n")
  179. (insert-file-contents path))))
  180. ;; ignoring. pass back to dnd. Copied from `org-download-dnd'. Apparently
  181. ;; returning nil does not do this.
  182. (let ((dnd-protocol-alist
  183. (rassq-delete-all
  184. 'org-ref-pdf-dnd-protocol
  185. (copy-alist dnd-protocol-alist))))
  186. (dnd-handle-one-url nil action uri))))
  187. (add-to-list 'dnd-protocol-alist '("^file:" . org-ref-pdf-dnd-protocol))
  188. ;;;###autoload
  189. (defun org-ref-pdf-dir-to-bibtex (bibfile directory)
  190. "Create BIBFILE from pdf files in DIRECTORY."
  191. (interactive (list
  192. (read-file-name "Bibtex file: ")
  193. (read-directory-name "Directory: ")))
  194. (find-file bibfile)
  195. (goto-char (point-max))
  196. (cl-loop for pdf in (f-entries directory (lambda (f) (f-ext? f "pdf")))
  197. do
  198. (goto-char (point-max))
  199. (let ((dois (org-ref-extract-doi-from-pdf pdf)))
  200. (cond
  201. ((null dois)
  202. (insert (format "%% No doi found to create entry in %s.\n" pdf)))
  203. ((= 1 (length dois))
  204. (doi-utils-add-bibtex-entry-from-doi
  205. (car dois)
  206. (buffer-file-name))
  207. (bibtex-beginning-of-entry)
  208. (insert (format "%% [[file:%s]]\n" pdf)))
  209. ;; Multiple DOIs found
  210. (t
  211. (insert (format "%% Multiple dois found in %s\n" pdf))
  212. (helm :sources `((name . "Select a DOI")
  213. (candidates . ,(org-ref-pdf-doi-candidates dois))
  214. (action . org-ref-pdf-add-dois))))))))
  215. ;;;###autoload
  216. (defun org-ref-pdf-debug-pdf (pdf-file)
  217. "Try to debug getting a doi from a pdf.
  218. Opens a buffer with the pdf converted to text, and `occur' on the
  219. variable `org-ref-pdf-doi-regex'."
  220. (interactive "fPDF: ")
  221. (switch-to-buffer (get-buffer-create "*org-ref-pdf debug*"))
  222. (erase-buffer)
  223. (insert (shell-command-to-string (format "%s %s -"
  224. pdftotext-executable
  225. (shell-quote-argument pdf-file))))
  226. (goto-char (point-min))
  227. (highlight-regexp org-ref-pdf-doi-regex)
  228. (occur org-ref-pdf-doi-regex)
  229. (switch-to-buffer-other-window "*Occur*"))
  230. ;;;###autoload
  231. (defun org-ref-pdf-crossref-lookup ()
  232. "Lookup highlighted text in PDFView in CrossRef."
  233. (interactive)
  234. (require 'pdf-view)
  235. (pdf-view-assert-active-region)
  236. (let* ((txt (pdf-view-active-region-text)))
  237. (pdf-view-deactivate-region)
  238. (crossref-lookup (mapconcat 'identity txt " \n"))))
  239. (provide 'org-ref-pdf)
  240. ;;; org-ref-pdf.el ends here