Klimi's new dotfiles with stow.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

1522 wiersze
60 KiB

5 lat temu
  1. ;;; doi-utils.el --- DOI utilities for making bibtex entries
  2. ;; Copyright (C) 2015 John Kitchin
  3. ;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
  4. ;; Keywords: convenience
  5. ;; Version: 0.1
  6. ;; Package-Requires: ((org-ref))
  7. ;; This program is free software; you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; This program is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;; This package provides functionality to download PDFs and bibtex entries from
  19. ;; a DOI, as well as to update a bibtex entry from a DOI. It depends slightly
  20. ;; on org-ref, to determine where to save pdf files too, and where to insert
  21. ;; bibtex entries in the default bibliography.
  22. ;; The principle commands you will use from here are:
  23. ;; - doi-utils-get-bibtex-entry-pdf with the cursor in a bibtex entry.
  24. ;; - doi-utils-insert-bibtex-entry-from-doi to insert a bibtex entry at your cursor, clean it and try to get a pdf.
  25. ;; - doi-utils-add-bibtex-entry-from-doi to add an entry to your default bibliography (cleaned with pdf if possible).
  26. ;; - doi-utils-update-bibtex-entry-from-doi with cursor in an entry to update its fields.
  27. ;;; Code:
  28. (defvar org-ref-pdf-directory)
  29. (defvar org-ref-bibliography-notes)
  30. (defvar org-ref-default-bibliography)
  31. (defvar reftex-default-bibliography)
  32. (defvar url-http-end-of-headers)
  33. (declare-function org-ref-bib-citation "org-ref-core")
  34. (declare-function org-ref-find-bibliography "org-ref-core")
  35. (declare-function org-ref-clean-bibtex-entry "org-ref-core")
  36. (declare-function reftex-get-bib-field "reftex-cite")
  37. (declare-function bibtex-completion-edit-notes "bibtex-completion")
  38. (declare-function helm "helm")
  39. (declare-function org-bibtex-yank "org-bibtex")
  40. (declare-function org-ref-possible-bibfiles "org-ref-core")
  41. (eval-when-compile
  42. (require 'cl-lib))
  43. (require 'bibtex)
  44. (require 'dash)
  45. (require 'json)
  46. (require 'org) ; org-add-link-type
  47. (or (require 'ol-bibtex nil t)
  48. (require 'org-bibtex)) ; org-bibtex-yank
  49. (require 'url-http)
  50. (require 'org-ref-utils)
  51. ;;* Customization
  52. (defgroup doi-utils nil
  53. "Customization group for doi-utils."
  54. :tag "DOI utils"
  55. :group 'doi-utils)
  56. (defcustom doi-utils-download-pdf
  57. t
  58. "Try to download PDFs when adding bibtex entries when non-nil."
  59. :type 'boolean
  60. :group 'doi-utils)
  61. (defcustom doi-utils-open-pdf-after-download
  62. nil
  63. "Open PDF after adding bibtex entries."
  64. :type 'boolean
  65. :group 'doi-utils)
  66. (defcustom doi-utils-make-notes
  67. t
  68. "Whether to create notes when adding bibtex entries."
  69. :type 'boolean
  70. :group 'doi-utils)
  71. (defcustom doi-utils-timestamp-field
  72. "DATE_ADDED"
  73. "The bibtex field to store the date when an entry has been added."
  74. :type 'string
  75. :group 'doi-utils)
  76. (defcustom doi-utils-timestamp-format-function
  77. 'current-time-string
  78. "The function to format the timestamp for a bibtex entry.
  79. Set to a function that returns nil to avoid setting timestamps in the entries.
  80. e.g. (lambda () nil)"
  81. :type 'function
  82. :group 'doi-utils)
  83. (defcustom doi-utils-make-notes-function
  84. (lambda ()
  85. (bibtex-beginning-of-entry)
  86. (bibtex-completion-edit-notes (list (cdr (assoc "=key=" (bibtex-parse-entry))))))
  87. "Function to create notes for a bibtex entry.
  88. Set `doi-utils-make-notes' to nil if you want no notes."
  89. :type 'function
  90. :group 'doi-utils)
  91. (defcustom doi-utils-dx-doi-org-url
  92. "https://doi.org/"
  93. "Base url to retrieve doi metadata from. A trailing / is required."
  94. :type 'string
  95. :group 'doi-utils)
  96. ;;* Getting pdf files from a DOI
  97. ;; The idea here is simple. When you visit http://dx.doi.org/doi or
  98. ;; https://doi.org/doi, you get redirected to the journal site. Once you have
  99. ;; the url for the article, you can usually compute the url to the pdf, or find
  100. ;; it in the page. Then you simply download it.
  101. ;; There are some subtleties in doing this that are described here. To get the
  102. ;; redirect, we have to use url-retrieve, and a callback function. The callback
  103. ;; does not return anything, so we communicate through global variables.
  104. ;; url-retrieve is asynchronous, so we have to make sure to wait for it to
  105. ;; finish.
  106. (defvar *doi-utils-waiting* t
  107. "Stores waiting state for url retrieval.")
  108. (defvar *doi-utils-redirect* nil
  109. "Stores redirect url from a callback function.")
  110. (defun doi-utils-redirect-callback (&optional status)
  111. "Callback for `url-retrieve' to set the redirect.
  112. Optional argument STATUS Unknown why this is optional."
  113. (when (plist-get status :error)
  114. (signal (car (plist-get status :error)) (cdr(plist-get status :error))))
  115. (when (plist-get status :redirect) ; is nil if there none
  116. (setq *doi-utils-redirect* (plist-get status :redirect)))
  117. ;; we have done our job, so we are not waiting any more.
  118. (setq *doi-utils-waiting* nil))
  119. ;; To actually get the redirect we use url-retrieve like this.
  120. (defun doi-utils-get-redirect (doi)
  121. "Get redirect url from `doi-utils-dx-doi-org-url'/doi."
  122. ;; we are going to wait until the url-retrieve is done
  123. (setq *doi-utils-waiting* t)
  124. ;; start with no redirect. it will be set in the callback.
  125. (setq *doi-utils-redirect* nil)
  126. (url-retrieve
  127. (format "%s%s" doi-utils-dx-doi-org-url doi)
  128. 'doi-utils-redirect-callback)
  129. ;; I suspect we need to wait here for the asynchronous process to
  130. ;; finish. we loop and sleep until the callback says it is done via
  131. ;; `*doi-utils-waiting*'. this works as far as i can tell. Before I
  132. ;; had to run this a few times to get it to work, which i suspect
  133. ;; just gave the first one enough time to finish.
  134. (while *doi-utils-waiting* (sleep-for 0.1)))
  135. ;; Once we have a redirect for a particular doi, we need to compute the url to
  136. ;; the pdf. We do this with a series of functions. Each function takes a single
  137. ;; argument, the redirect url. If it knows how to compute the pdf url it does,
  138. ;; and returns it. We store the functions in a variable:
  139. (defvar doi-utils-pdf-url-functions nil
  140. "Functions that return a url to a pdf from a redirect url.
  141. Each function takes one argument, the redirect url. The function
  142. must return a pdf-url, or nil.")
  143. ;;** APS journals
  144. (defun aps-pdf-url (*doi-utils-redirect*)
  145. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  146. (when (string-match "^http://journals.aps.org" *doi-utils-redirect*)
  147. (replace-regexp-in-string "/abstract/" "/pdf/" *doi-utils-redirect*)))
  148. ;;** Science
  149. (defun science-pdf-url (*doi-utils-redirect*)
  150. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  151. (when (string-match "^http://www.sciencemag.org" *doi-utils-redirect*)
  152. (concat *doi-utils-redirect* ".full.pdf")))
  153. ;;** Nature
  154. (defun nature-pdf-url (*doi-utils-redirect*)
  155. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  156. (when (string-match "^http://www.nature.com" *doi-utils-redirect*)
  157. (let ((result *doi-utils-redirect*))
  158. (setq result (replace-regexp-in-string "/full/" "/pdf/" result))
  159. (replace-regexp-in-string "\.html$" "\.pdf" result))))
  160. ;;** Elsevier/ScienceDirect
  161. ;; You cannot compute these pdf links; they are embedded in the redirected pages.
  162. (defvar *doi-utils-pdf-url* nil
  163. "Stores url to pdf download from a callback function.")
  164. ;;** Wiley
  165. ;; http://onlinelibrary.wiley.com/doi/10.1002/anie.201402680/abstract
  166. ;; http://onlinelibrary.wiley.com/doi/10.1002/anie.201402680/pdf
  167. ;; It appears that it is not enough to use the pdf url above. That takes you to
  168. ;; an html page. The actual link to teh pdf is embedded in that page. This is
  169. ;; how ScienceDirect does things too.
  170. ;; This is where the link is hidden:
  171. ;; <iframe id="pdfDocument" src="http://onlinelibrary.wiley.com/store/10.1002/anie.201402680/asset/6397_ftp.pdf?v=1&amp;t=hwut2142&amp;s=d4bb3cd4ad20eb733836717f42346ffb34017831" width="100%" height="675px"></iframe>
  172. (defun doi-utils-get-wiley-pdf-url (redirect-url)
  173. "Wileyscience direct hides the pdf url in html.
  174. We get it out here by parsing the html.
  175. Argument REDIRECT-URL URL you are redirected to."
  176. (setq *doi-utils-waiting* t)
  177. (url-retrieve
  178. redirect-url
  179. (lambda (status)
  180. (goto-char (point-min))
  181. (re-search-forward "<iframe id=\"pdfDocument\" src=\"\\([^\"]*\\)\"" nil t)
  182. (setq *doi-utils-pdf-url* (match-string 1)
  183. *doi-utils-waiting* nil)))
  184. (while *doi-utils-waiting* (sleep-for 0.1))
  185. *doi-utils-pdf-url*)
  186. (defun wiley-pdf-url (*doi-utils-redirect*)
  187. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  188. (when (string-match "^http://onlinelibrary.wiley.com" *doi-utils-redirect*)
  189. (doi-utils-get-wiley-pdf-url
  190. (replace-regexp-in-string "/abstract" "/pdf" *doi-utils-redirect*))
  191. *doi-utils-pdf-url*))
  192. ;;** Springer
  193. (defun springer-chapter-pdf-url (*doi-utils-redirect*)
  194. (when (string-match "^http://link.springer.com/chapter/" *doi-utils-redirect*)
  195. (replace-regexp-in-string "/chapter" "/content/pdf"
  196. (concat *doi-utils-redirect* ".pdf"))))
  197. (defun springer-pdf-url (*doi-utils-redirect*)
  198. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  199. (when (string-match "^http://link.springer.com" *doi-utils-redirect*)
  200. (replace-regexp-in-string "/article/" "/content/pdf/"
  201. (concat *doi-utils-redirect* ".pdf"))))
  202. ;;** ACS
  203. ;; here is a typical url http://pubs.acs.org/doi/abs/10.1021/nl500037x
  204. ;; the pdf is found at http://pubs.acs.org/doi/pdf/10.1021/nl500037x
  205. ;; we just change /abs/ to /pdf/.
  206. (defun acs-pdf-url-1 (*doi-utils-redirect*)
  207. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  208. (when (string-match "^http://pubs.acs.org/doi/abs/" *doi-utils-redirect*)
  209. (replace-regexp-in-string "/abs/" "/pdf/" *doi-utils-redirect*)))
  210. ;; 1/20/2016 I noticed this new pattern in pdf urls, where there is no abs in
  211. ;; the url
  212. (defun acs-pdf-url-2 (*doi-utils-redirect*)
  213. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  214. (when (string-match "^http://pubs.acs.org/doi/" *doi-utils-redirect*)
  215. (replace-regexp-in-string "/doi/" "/doi/pdf/" *doi-utils-redirect*)))
  216. ;; 1/18/2019: It looks like they are using https now
  217. (defun acs-pdf-url-3 (*doi-utils-redirect*)
  218. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  219. (when (string-match "^https://pubs.acs.org/doi/" *doi-utils-redirect*)
  220. (replace-regexp-in-string "/doi/" "/doi/pdf/" *doi-utils-redirect*)))
  221. ;;** IOP
  222. (defun iop-pdf-url (*doi-utils-redirect*)
  223. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  224. (when (string-match "^http://iopscience.iop.org" *doi-utils-redirect*)
  225. (replace-regexp-in-string "/meta" "/pdf" *doi-utils-redirect*)))
  226. ;;** JSTOR
  227. (defun jstor-pdf-url (*doi-utils-redirect*)
  228. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  229. (when (string-match "^http://www.jstor.org" *doi-utils-redirect*)
  230. (concat (replace-regexp-in-string "/stable/" "/stable/pdfplus/" *doi-utils-redirect*) ".pdf")))
  231. ;;** AIP
  232. (defun aip-pdf-url (*doi-utils-redirect*)
  233. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  234. (when (string-match "^http://scitation.aip.org" *doi-utils-redirect*)
  235. ;; get stuff after content
  236. (let (p1 p2 s p3)
  237. (setq p2 (replace-regexp-in-string
  238. "^http://scitation.aip.org/" "" *doi-utils-redirect*))
  239. (setq s (split-string p2 "/"))
  240. (setq p1 (mapconcat 'identity (-remove-at-indices '(0 6) s) "/"))
  241. (setq p3 (concat "/" (nth 0 s) (nth 1 s) "/" (nth 2 s) "/" (nth 3 s)))
  242. (format "http://scitation.aip.org/deliver/fulltext/%s.pdf?itemId=/%s&mimeType=pdf&containerItemId=%s"
  243. p1 p2 p3))))
  244. ;;** Taylor and Francis
  245. (defun tandfonline-pdf-url (*doi-utils-redirect*)
  246. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  247. (when (string-match "^http://www.tandfonline.com" *doi-utils-redirect*)
  248. (replace-regexp-in-string "/abs/\\|/full/" "/pdf/" *doi-utils-redirect*)))
  249. ;;** ECS
  250. (defun ecs-pdf-url (*doi-utils-redirect*)
  251. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  252. (when (string-match "^http://jes.ecsdl.org" *doi-utils-redirect*)
  253. (replace-regexp-in-string "\.abstract$" ".full.pdf" *doi-utils-redirect*)))
  254. ;; http://ecst.ecsdl.org/content/25/2/2769
  255. ;; http://ecst.ecsdl.org/content/25/2/2769.full.pdf
  256. (defun ecst-pdf-url (*doi-utils-redirect*)
  257. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  258. (when (string-match "^http://ecst.ecsdl.org" *doi-utils-redirect*)
  259. (concat *doi-utils-redirect* ".full.pdf")))
  260. ;;** RSC
  261. (defun rsc-pdf-url (*doi-utils-redirect*)
  262. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  263. (when (string-match "^http://pubs.rsc.org" *doi-utils-redirect*)
  264. (let ((url (downcase *doi-utils-redirect*)))
  265. (setq url (replace-regexp-in-string "articlelanding" "articlepdf" url))
  266. url)))
  267. ;;** Science Direct
  268. (defun doi-utils-get-science-direct-pdf-url (redirect-url)
  269. "Science direct hides the pdf url in html. We get it out here.
  270. REDIRECT-URL is where the pdf url will be in."
  271. (setq *doi-utils-waiting* t)
  272. (url-retrieve
  273. redirect-url
  274. (lambda (status)
  275. (goto-char (point-min))
  276. (re-search-forward "pdf_url\" content=\"\\([^\"]*\\)\"" nil t) ; modified the search string to reflect updated science direct
  277. (setq *doi-utils-pdf-url* (match-string 1)
  278. *doi-utils-waiting* nil)))
  279. (while *doi-utils-waiting* (sleep-for 0.1))
  280. *doi-utils-pdf-url*)
  281. (defun science-direct-pdf-url (*doi-utils-redirect*)
  282. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  283. (when (string-match "^http://www.sciencedirect.com" *doi-utils-redirect*)
  284. (doi-utils-get-science-direct-pdf-url *doi-utils-redirect*)
  285. *doi-utils-pdf-url*))
  286. ;; sometimes I get
  287. ;; http://linkinghub.elsevier.com/retrieve/pii/S0927025609004558
  288. ;; which actually redirect to
  289. ;; http://www.sciencedirect.com/science/article/pii/S0927025609004558
  290. (defun linkinghub-elsevier-pdf-url (*doi-utils-redirect*)
  291. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  292. (when (string-match
  293. "^https://linkinghub.elsevier.com/retrieve" *doi-utils-redirect*)
  294. (doi-utils-get-science-direct-pdf-url
  295. (replace-regexp-in-string
  296. ;; change URL to science direct and use function to get pdf URL
  297. "https://linkinghub.elsevier.com/retrieve"
  298. "https://www.sciencedirect.com/science/article"
  299. *doi-utils-redirect*))
  300. *doi-utils-pdf-url*))
  301. ;;** PNAS
  302. ;; http://www.pnas.org/content/early/2014/05/08/1319030111
  303. ;; http://www.pnas.org/content/early/2014/05/08/1319030111.full.pdf
  304. ;; with supporting info
  305. ;; http://www.pnas.org/content/early/2014/05/08/1319030111.full.pdf+html?with-ds=yes
  306. (defun pnas-pdf-url (*doi-utils-redirect*)
  307. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  308. (when (string-match "^http://www.pnas.org" *doi-utils-redirect*)
  309. (concat *doi-utils-redirect* ".full.pdf?with-ds=yes")))
  310. ;;** Copernicus Publications
  311. (defvar copernicus-journal-urls '(
  312. "^https://www.adv-geosci.net/"
  313. "^https://www.adv-radio-sci.net/"
  314. "^https://www.adv-sci-res.net/"
  315. "^https://www.adv-stat-clim-meteorol-oceanogr.net/"
  316. "^https://www.ann-geophys.net/"
  317. "^https://www.arch-anim-breed.net/"
  318. "^https://www.astra-proc.net/"
  319. "^https://www.atmos-chem-phys.net/"
  320. "^https://www.atmos-chem-phys-discuss.net/"
  321. "^https://www.atmos-meas-tech.net/"
  322. "^https://www.atmos-meas-tech-discuss.net/"
  323. "^https://www.biogeosciences.net/"
  324. "^https://www.biogeosciences-discuss.net/"
  325. "^https://www.clim-past.net/recent_papers.html"
  326. "^https://www.clim-past-discuss.net/"
  327. "^https://www.drink-water-eng-sci.net/"
  328. "^https://www.drink-water-eng-sci-discuss.net/"
  329. "^https://www.eg-quaternary-sci-j.net/"
  330. "^https://www.earth-surf-dynam.net/"
  331. "^https://www.earth-surf-dynam-discuss.net/"
  332. "^https://www.earth-syst-dynam.net/"
  333. "^https://www.earth-syst-dynam-discuss.net/"
  334. "^https://www.earth-syst-sci-data.net/"
  335. "^https://www.earth-syst-sci-data-discuss.net/"
  336. "^https://www.foss-rec.net/"
  337. "^https://www.geogr-helv.net/"
  338. "^https://www.geosci-instrum-method-data-syst.net/"
  339. "^https://www.geosci-instrum-method-data-syst-discuss.net/"
  340. "^https://www.geosci-model-dev.net/"
  341. "^https://www.geosci-model-dev-discuss.net/"
  342. "^https://www.hist-geo-space-sci.net/"
  343. "^https://www.hydrol-earth-syst-sci.net/"
  344. "^https://www.hydrol-earth-syst-sci-discuss.net/"
  345. "^https://www.j-sens-sens-syst.net/"
  346. "^https://www.mech-sci.net/"
  347. "^https://www.nat-hazards-earth-syst-sci.net/"
  348. "^https://www.nonlin-processes-geophys-discuss.net/"
  349. "^https://www.ocean-sci.net/"
  350. "^https://www.ocean-sci-discuss.net/"
  351. "^https://www.primate-biol.net/"
  352. "^https://www.proc-iahs.net/"
  353. "^https://www.sci-dril.net/"
  354. "^https://www.soil-journal.net/"
  355. "^https://www.soil-discuss.net/"
  356. "^https://www.solid-earth.net/"
  357. "^https://www.solid-earth-discuss.net/"
  358. "^https://www.stephan-mueller-spec-publ-ser.net/"
  359. "^https://www.the-cryosphere.net/"
  360. "^https://www.the-cryosphere-discuss.net/"
  361. "^https://www.web-ecol.net/"
  362. "^https://www.wind-energ-sci.net/"
  363. "^https://www.wind-energ-sci-discuss.net/"
  364. )
  365. "List of Copernicus URLs.")
  366. (defun doi-utils-get-copernicus-pdf-url (redirect-url)
  367. "Copernicus hides the pdf url in html. We get it out here.
  368. REDIRECT-URL is where the pdf url will be in."
  369. (setq *doi-utils-waiting* t)
  370. (url-retrieve
  371. redirect-url
  372. (lambda (status)
  373. (goto-char (point-min))
  374. (re-search-forward "citation_pdf_url\" content=\"\\([^\"]*\\)\"" nil t)
  375. (setq *doi-utils-pdf-url* (match-string 1)
  376. *doi-utils-waiting* nil)))
  377. (while *doi-utils-waiting* (sleep-for 0.1))
  378. *doi-utils-pdf-url*)
  379. (defun copernicus-pdf-url (*doi-utils-redirect*)
  380. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  381. (car (cl-loop for copurl in copernicus-journal-urls
  382. when (string-match copurl *doi-utils-redirect*)
  383. collect
  384. (progn (doi-utils-get-copernicus-pdf-url *doi-utils-redirect*)
  385. *doi-utils-pdf-url*))))
  386. ;;** Sage
  387. (defun sage-pdf-url (*doi-utils-redirect*)
  388. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  389. (when (string-match "^http://pss.sagepub.com" *doi-utils-redirect*)
  390. (concat *doi-utils-redirect* ".full.pdf")))
  391. ;;** Journal of Neuroscience
  392. (defun jneurosci-pdf-url (*doi-utils-redirect*)
  393. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  394. (when (string-match "^http://www.jneurosci.org" *doi-utils-redirect*)
  395. (concat *doi-utils-redirect* ".full.pdf")))
  396. ;;** Generic .full.pdf
  397. (defun generic-full-pdf-url (*doi-utils-redirect*)
  398. (let ((pdf (concat *doi-utils-redirect* ".full.pdf")))
  399. (when (url-http-file-exists-p pdf)
  400. pdf)))
  401. ;;** IEEE
  402. ;; 10.1109/re.2014.6912247
  403. ;; http(s)://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=6912247
  404. ;; http(s)://ieeexplore.ieee.org/ielx7/6903646/6912234/06912247.pdf
  405. ;; http(s)://ieeexplore.ieee.org/iel7/6903646/6912234/06912247.pdf?arnumber=6912247
  406. ;; <meta name="citation_pdf_url" content="http(s)://ieeexplore.ieee.org/iel7/6903646/6912234/06912247.pdf?arnumber=6912247">
  407. ;; <frame src="http(s)://ieeexplore.ieee.org/ielx7/6903646/6912234/06912247.pdf?tp=&arnumber=6912247&isnumber=6912234" frameborder=0 />
  408. (defun ieee-pdf-url (*doi-utils-redirect*)
  409. "Get a url to the pdf from *DOI-UTILS-REDIRECT* for IEEE urls."
  410. (when (string-match "^https?://ieeexplore.ieee.org" *doi-utils-redirect*)
  411. (with-current-buffer (url-retrieve-synchronously *doi-utils-redirect*)
  412. (goto-char (point-min))
  413. (when (re-search-forward "<meta name=\"citation_pdf_url\" content=\"\\([[:ascii:]]*?\\)\">" nil t)
  414. (let ((framed-url (match-string 1)))
  415. (with-current-buffer (url-retrieve-synchronously framed-url)
  416. (goto-char (point-min))
  417. (when (re-search-forward "<frame src=\"\\(http[[:ascii:]]*?\\)\"" nil t)
  418. (match-string 1))))))))
  419. ;; At least some IEEE papers need the following new pdf-link parsing
  420. ;; Example: 10.1109/35.667413
  421. (defun ieee2-pdf-url (*doi-utils-redirect*)
  422. "Get a url to the pdf from *DOI-UTILS-REDIRECT* for IEEE urls."
  423. (when (string-match "^https?://ieeexplore.ieee.org" *doi-utils-redirect*)
  424. (with-current-buffer (url-retrieve-synchronously *doi-utils-redirect*)
  425. (goto-char (point-min))
  426. (when (re-search-forward "\"pdfUrl\":\"\\([[:ascii:]]*?\\)\"" nil t)
  427. (let ((framed-url (match-string 1)))
  428. (with-current-buffer (url-retrieve-synchronously (concat "http://ieeexplore.ieee.org" framed-url))
  429. (goto-char (point-min))
  430. (when (re-search-forward "<frame src=\"\\(http[[:ascii:]]*?\\)\"" nil t)
  431. (match-string 1))))))))
  432. ;; Another try to get the ieee pdf
  433. ;; <iframe src="http(s)://ieeexplore.ieee.org/ielx5/8/4538127/04538164.pdf?tp=&arnumber=4538164&isnumber=4538127" frameborder=0>
  434. (defun ieee3-pdf-url (*doi-utils-redirect*)
  435. "Get a url to the pdf from *DOI-UTILS-REDIRECT* for IEEE urls."
  436. (when (string-match "^https?://ieeexplore.ieee.org" *doi-utils-redirect*)
  437. (with-current-buffer (url-retrieve-synchronously *doi-utils-redirect*)
  438. (goto-char (point-min))
  439. (when (re-search-forward "\"pdfUrl\":\"\\([[:ascii:]]*?\\)\"" nil t)
  440. (let ((framed-url (match-string 1)))
  441. (with-current-buffer (url-retrieve-synchronously (concat "http://ieeexplore.ieee.org" framed-url))
  442. (goto-char (point-min))
  443. (when (re-search-forward "<iframe src=\"\\(http[[:ascii:]]*?\\)\"" nil t)
  444. (match-string 1))))))))
  445. ;; ACM Digital Library
  446. ;; http(s)://dl.acm.org/citation.cfm?doid=1368088.1368132
  447. ;; <a name="FullTextPDF" title="FullText PDF" href="ft_gateway.cfm?id=1368132&ftid=518423&dwn=1&CFID=766519780&CFTOKEN=49739320" target="_blank">
  448. (defun acm-pdf-url (*doi-utils-redirect*)
  449. "Get a url to the pdf from *DOI-UTILS-REDIRECT* for ACM urls."
  450. (when (string-match "^https?://dl.acm.org" *doi-utils-redirect*)
  451. (with-current-buffer (url-retrieve-synchronously *doi-utils-redirect*)
  452. (goto-char (point-min))
  453. (when (re-search-forward "<a name=\"FullTextPDF\".*href=\"\\([[:ascii:]]*?\\)\"" nil t)
  454. (concat "http://dl.acm.org/" (match-string 1))))))
  455. ;;** Optical Society of America (OSA)
  456. (defun osa-pdf-url (*doi-utils-redirect*)
  457. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  458. (when (string-match "^https://www.osapublishing.org" *doi-utils-redirect*)
  459. (replace-regexp-in-string "abstract.cfm" "viewmedia.cfm" *doi-utils-redirect* )))
  460. ;;** ASME Biomechanical Journal
  461. (defun asme-biomechanical-pdf-url (*doi-utils-redirect*)
  462. "Typical URL: http://biomechanical.asmedigitalcollection.asme.org/article.aspx?articleid=1427237
  463. On this page the pdf might be here: <meta name=\"citation_author\" content=\"Dalong Li\" /><meta name=\"citation_author_email\" content=\"dal40@pitt.edu\" /><meta name=\"citation_author\" content=\"Anne M. Robertson\" /><meta name=\"citation_author_email\" content=\"rbertson@pitt.edu\" /><meta name=\"citation_title\" content=\"A Structural Multi-Mechanism Damage Model for Cerebral Arterial Tissue\" /><meta name=\"citation_firstpage\" content=\"101013\" /><meta name=\"citation_doi\" content=\"10.1115/1.3202559\" /><meta name=\"citation_keyword\" content=\"Mechanisms\" /><meta name=\"citation_keyword\" content=\"Biological tissues\" /><meta name=\"citation_keyword\" content=\"Stress\" /><meta name=\"citation_keyword\" content=\"Fibers\" /><meta name=\"citation_journal_title\" content=\"Journal of Biomechanical Engineering\" /><meta name=\"citation_journal_abbrev\" content=\"J Biomech Eng\" /><meta name=\"citation_volume\" content=\"131\" /><meta name=\"citation_issue\" content=\"10\" /><meta name=\"citation_publication_date\" content=\"2009/10/01\" /><meta name=\"citation_issn\" content=\"0148-0731\" /><meta name=\"citation_publisher\" content=\"American Society of Mechanical Engineers\" /><meta name=\"citation_pdf_url\" content=\"http://biomechanical.asmedigitalcollection.asme.org/data/journals/jbendy/27048/101013_1.pdf\" />
  464. It is in the citation_pdf_url.
  465. It would be better to parse this, but here I just use a regexp.
  466. "
  467. (when (string-match "^http://biomechanical.asmedigitalcollection.asme.org" *doi-utils-redirect*)
  468. (setq *doi-utils-waiting* 0)
  469. (url-retrieve
  470. *doi-utils-redirect*
  471. (lambda (status)
  472. (goto-char (point-min))
  473. (re-search-forward "citation_pdf_url\" content=\"\\(.*\\)\"" nil t)
  474. (message-box (match-string 1))
  475. (setq *doi-utils-pdf-url* (match-string 1)
  476. *doi-utils-waiting* nil)))
  477. (while (and *doi-utils-waiting* (< *doi-utils-waiting* 5))
  478. (setq *doi-utils-waiting* (+ *doi-utils-waiting* 0.1))
  479. (sleep-for 0.1))
  480. *doi-utils-pdf-url*))
  481. ;; Society for Industrial and Applied Mathematics (SIAM)
  482. (defun siam-pdf-url (*doi-utils-redirect*)
  483. "Get url to the pdf from *DOI-UTILS-REDIRECT*."
  484. (when (string-match "^http://epubs.siam.org" *doi-utils-redirect*)
  485. (replace-regexp-in-string "/doi/" "/doi/pdf/" *doi-utils-redirect* )))
  486. ;;** Add all functions
  487. (setq doi-utils-pdf-url-functions
  488. (list
  489. 'aps-pdf-url
  490. 'science-pdf-url
  491. 'nature-pdf-url
  492. 'wiley-pdf-url
  493. 'springer-chapter-pdf-url
  494. 'springer-pdf-url
  495. 'acs-pdf-url-1
  496. 'acs-pdf-url-2
  497. 'acs-pdf-url-3
  498. 'iop-pdf-url
  499. 'jstor-pdf-url
  500. 'aip-pdf-url
  501. 'science-direct-pdf-url
  502. 'linkinghub-elsevier-pdf-url
  503. 'tandfonline-pdf-url
  504. 'ecs-pdf-url
  505. 'ecst-pdf-url
  506. 'rsc-pdf-url
  507. 'pnas-pdf-url
  508. 'copernicus-pdf-url
  509. 'sage-pdf-url
  510. 'jneurosci-pdf-url
  511. 'ieee-pdf-url
  512. 'ieee2-pdf-url
  513. 'ieee3-pdf-url
  514. 'acm-pdf-url
  515. 'osa-pdf-url
  516. 'asme-biomechanical-pdf-url
  517. 'siam-pdf-url
  518. 'generic-full-pdf-url))
  519. ;;** Get the pdf url for a doi
  520. (defun doi-utils-get-pdf-url (doi)
  521. "Return a url to a pdf for the DOI if one can be calculated.
  522. Loops through the functions in `doi-utils-pdf-url-functions'
  523. until one is found."
  524. (doi-utils-get-redirect doi)
  525. (unless *doi-utils-redirect*
  526. (error "No redirect found for %s" doi))
  527. (catch 'pdf-url
  528. (dolist (func doi-utils-pdf-url-functions)
  529. (let ((this-pdf-url (funcall func *doi-utils-redirect*)))
  530. (when this-pdf-url
  531. (throw 'pdf-url this-pdf-url))))))
  532. ;;** Finally, download the pdf
  533. ;;;###autoload
  534. (defun doi-utils-get-bibtex-entry-pdf (&optional arg)
  535. "Download pdf for entry at point if the pdf does not already exist locally.
  536. The entry must have a doi. The pdf will be saved
  537. to `org-ref-pdf-directory', by the name %s.pdf where %s is the
  538. bibtex label. Files will not be overwritten. The pdf will be
  539. checked to make sure it is a pdf, and not some html failure
  540. page. You must have permission to access the pdf. We open the pdf
  541. at the end if `doi-utils-open-pdf-after-download' is non-nil.
  542. With one prefix ARG, directly get the pdf from a file (through
  543. `read-file-name') instead of looking up a DOI. With a double
  544. prefix ARG, directly get the pdf from an open buffer (through
  545. `read-buffer-to-switch') instead. These two alternative methods
  546. work even if the entry has no DOI, and the pdf file is not
  547. checked."
  548. (interactive "P")
  549. (save-excursion
  550. (bibtex-beginning-of-entry)
  551. (let ( ;; get doi, removing http://dx.doi.org/ if it is there.
  552. (doi (replace-regexp-in-string
  553. "https?://\\(dx.\\)?.doi.org/" ""
  554. (bibtex-autokey-get-field "doi")))
  555. (key (cdr (assoc "=key=" (bibtex-parse-entry))))
  556. (pdf-url)
  557. (pdf-file))
  558. (setq pdf-file (concat
  559. (if org-ref-pdf-directory
  560. (file-name-as-directory org-ref-pdf-directory)
  561. (read-directory-name "PDF directory: " "."))
  562. key ".pdf"))
  563. ;; now get file if needed.
  564. (unless (file-exists-p pdf-file)
  565. (cond
  566. ((and (not arg)
  567. doi
  568. (setq pdf-url (doi-utils-get-pdf-url doi)))
  569. (url-copy-file pdf-url pdf-file)
  570. ;; now check if we got a pdf
  571. (if (org-ref-pdf-p pdf-file)
  572. (message "%s saved" pdf-file)
  573. (delete-file pdf-file)
  574. (message "No pdf was downloaded.")
  575. (browse-url pdf-url)))
  576. ((equal arg '(4))
  577. (copy-file (expand-file-name (read-file-name "Pdf file: " nil nil t))
  578. pdf-file))
  579. ((equal arg '(16))
  580. (with-current-buffer (read-buffer-to-switch "Pdf buffer: ")
  581. (write-file pdf-file)))
  582. (t
  583. (message "We don't have a recipe for this journal.")))
  584. (when (and doi-utils-open-pdf-after-download (file-exists-p pdf-file))
  585. (org-open-file pdf-file))))))
  586. ;;* Getting bibtex entries from a DOI
  587. ;; I
  588. ;; [[http://homepages.see.leeds.ac.uk/~eeaol/notes/2013/02/doi-metadata/][found]]
  589. ;; you can download metadata about a DOI from http://dx.doi.org. You just have
  590. ;; to construct the right http request to get it. Here is a function that gets
  591. ;; the metadata as a plist in emacs.
  592. (defun doi-utils-get-json-metadata (doi)
  593. "Try to get json metadata for DOI. Open the DOI in a browser if we do not get it."
  594. (let ((url-request-method "GET")
  595. (url-mime-accept-string "application/citeproc+json")
  596. (json-object-type 'plist)
  597. (json-data))
  598. (with-current-buffer
  599. (url-retrieve-synchronously
  600. (concat "http://dx.doi.org/" doi))
  601. (setq json-data (buffer-substring url-http-end-of-headers (point-max)))
  602. (if (or (string-match "<title>Error: DOI Not Found</title>" json-data)
  603. (string-match "Resource not found" json-data)
  604. (string-match "Status *406" json-data))
  605. (progn
  606. (browse-url (concat doi-utils-dx-doi-org-url doi))
  607. (error "Resource not found. Opening website"))
  608. (json-read-from-string json-data)))))
  609. ;; We can use that data to construct a bibtex entry. We do that by defining a
  610. ;; template, and filling it in. I wrote this template expansion code which makes
  611. ;; it easy to substitute values like %{} in emacs lisp.
  612. (defun doi-utils-expand-template (s)
  613. "Expand a string template S containing %{} with the eval of its contents."
  614. (replace-regexp-in-string "%{\\([^}]+\\)}"
  615. (lambda (arg)
  616. (let ((sexp (substring arg 2 -1)))
  617. (format "%s" (eval (read sexp)))))
  618. s))
  619. ;; Now we define a function that fills in that template from the metadata.
  620. ;; As different bibtex types share common keys, it is advantageous to separate
  621. ;; data extraction from json, and the formatting of the bibtex entry.
  622. ;; We use eval-and-compile because we use the three following forms in the
  623. ;; `doi-utils-def-bibtex-type' macro. Since the macro is expanded at compile
  624. ;; time, we need to ensure these defuns and defvars are evaluated at
  625. ;; compile-time.
  626. (eval-and-compile
  627. (defvar doi-utils-json-metadata-extract
  628. '((type (plist-get results :type))
  629. (author (mapconcat (lambda (x) (concat (plist-get x :given) " " (plist-get x :family)))
  630. (plist-get results :author) " and "))
  631. (title (plist-get results :title))
  632. (subtitle (plist-get results :subtitle))
  633. (journal (plist-get results :container-title))
  634. (series (plist-get results :container-title))
  635. (publisher (plist-get results :publisher))
  636. (volume (plist-get results :volume))
  637. (issue (plist-get results :issue))
  638. (number (plist-get results :issue))
  639. (year (elt (elt (plist-get (plist-get results :issued) :date-parts) 0) 0))
  640. ;; Some dates don't have a month in them.
  641. (month (let ((date (elt
  642. (plist-get (plist-get results :issued) :date-parts) 0)))
  643. (if (>= (length date) 2)
  644. (elt date 1)
  645. "-")))
  646. (pages (or (plist-get results :page)
  647. (plist-get results :article-number)))
  648. (doi (plist-get results :DOI))
  649. (url (plist-get results :URL))
  650. (booktitle (plist-get results :container-title))))
  651. ;; Next, we need to define the different bibtex types. Each type has a bibtex
  652. ;; type (for output) and the type as provided in the doi record. Finally, we
  653. ;; have to declare the fields we want to output.
  654. (defvar doi-utils-bibtex-type-generators nil)
  655. (defun doi-utils-concat-prepare (lst &optional acc)
  656. "Minimize the number of args passed to `concat' from LST.
  657. Given a list LST of strings and other expressions, which are
  658. intended to be passed to `concat', concat any subsequent strings,
  659. minimising the number of arguments being passed to `concat'
  660. without changing the results. ACC is the list of additional
  661. expressions."
  662. (cond ((null lst) (nreverse acc))
  663. ((and (stringp (car lst))
  664. (stringp (car acc)))
  665. (doi-utils-concat-prepare (cdr lst) (cons (concat (car acc) (car lst))
  666. (cdr acc))))
  667. (t (doi-utils-concat-prepare (cdr lst) (cons (car lst) acc))))))
  668. (defmacro doi-utils-def-bibtex-type (name matching-types &rest fields)
  669. "Define a BibTeX type identified by (symbol) NAME.
  670. MATCHING-TYPES is a list of strings. FIELDS are symbols that
  671. match to retrieval expressions in
  672. `doi-utils-json-metadata-extract'. This type will only be used
  673. when the `:type' parameter in the JSON metadata is contained in
  674. MATCHING-TYPES."
  675. `(push (lambda (type results)
  676. (when
  677. (or ,@(mapcar
  678. (lambda (match-type)
  679. `(string= type ,match-type)) matching-types))
  680. (let ,(mapcar (lambda (field)
  681. (let ((field-expr
  682. (assoc field doi-utils-json-metadata-extract)))
  683. (if field-expr
  684. ;; need to convert to string first
  685. `(,(car field-expr) (format "%s" ,(cadr field-expr)))
  686. (error "Unknown bibtex field type %s" field))))
  687. fields)
  688. (concat
  689. ,@(doi-utils-concat-prepare
  690. (-flatten
  691. (list (concat "@" (symbol-name name) "{,\n")
  692. ;; there seems to be some bug with mapcan,
  693. ;; so we fall back to flatten
  694. (mapcar (lambda (field)
  695. `(" " ,(symbol-name field) " = {" ,field "},\n"))
  696. fields)
  697. "}\n")))))))
  698. doi-utils-bibtex-type-generators))
  699. (doi-utils-def-bibtex-type article ("journal-article" "article-journal")
  700. author title journal year volume number pages doi url)
  701. (doi-utils-def-bibtex-type inproceedings ("proceedings-article" "paper-conference")
  702. author title booktitle year month pages doi url)
  703. (doi-utils-def-bibtex-type book ("book")
  704. author title series publisher year pages doi url)
  705. (doi-utils-def-bibtex-type inbook ("chapter" "book-chapter" "reference-entry")
  706. author title booktitle series publisher year pages doi url)
  707. ;; this is what preprints in chemrxiv look like for now
  708. (doi-utils-def-bibtex-type misc ("posted-content")
  709. author title year doi url)
  710. ;; With the code generating the bibtex entry in place, we can glue it to the json retrieval code.
  711. (defun doi-utils-doi-to-bibtex-string (doi)
  712. "Return a bibtex entry as a string for the DOI. Not all types are supported yet."
  713. (let* ((results (doi-utils-get-json-metadata doi))
  714. (type (plist-get results :type)))
  715. ;; (format "%s" results) ; json-data
  716. (or (-some (lambda (g) (funcall g type results)) doi-utils-bibtex-type-generators)
  717. (message "%s not supported yet\n%S." type results))))
  718. ;; That is just the string for the entry. To be useful, we need a function that
  719. ;; inserts the string into a buffer. This function will insert the string at the
  720. ;; cursor, clean the entry, try to get the pdf, and create a notes entry for
  721. ;; you.
  722. (defun doi-utils-insert-bibtex-entry-from-doi (doi)
  723. "Insert bibtex entry from a DOI.
  724. Also cleans entry using org-ref, and tries to download the corresponding pdf."
  725. (insert (doi-utils-doi-to-bibtex-string doi))
  726. (backward-char)
  727. ;; set date added for the record
  728. (let ((ts (funcall doi-utils-timestamp-format-function)))
  729. (when ts
  730. (bibtex-set-field doi-utils-timestamp-field
  731. ts)))
  732. (org-ref-clean-bibtex-entry)
  733. (save-buffer)
  734. ;; try to get pdf
  735. (when doi-utils-download-pdf
  736. (doi-utils-get-bibtex-entry-pdf))
  737. (when (and doi-utils-make-notes org-ref-bibliography-notes)
  738. (save-excursion
  739. (when (f-file? org-ref-bibliography-notes)
  740. (find-file-noselect org-ref-bibliography-notes)
  741. (save-buffer))
  742. (let ((bibtex-completion-bibliography (list (buffer-file-name))))
  743. (funcall doi-utils-make-notes-function)))))
  744. ;; It may be you are in some other place when you want to add a bibtex entry.
  745. ;; This next function will open the first entry in org-ref-default-bibliography
  746. ;; go to the end, and add the entry. You can sort it later.
  747. ;;;###autoload
  748. (defun doi-utils-add-bibtex-entry-from-doi (doi &optional bibfile)
  749. "Add DOI entry to end of a file in the current directory.
  750. Pick the file ending with .bib or in
  751. `org-ref-default-bibliography'. If you have an active region that
  752. starts like a DOI, that will be the initial prompt. If no region
  753. is selected and the first entry of the kill-ring starts like a
  754. DOI, then that is the intial prompt. Otherwise, you have to type
  755. or paste in a DOI.
  756. Argument BIBFILE the bibliography to use."
  757. (interactive
  758. (list (read-string
  759. "DOI: "
  760. ;; now set initial input
  761. (cond
  762. ;; If region is active and it starts like a doi we want it.
  763. ((and (region-active-p)
  764. (s-match "^10" (buffer-substring
  765. (region-beginning)
  766. (region-end))))
  767. (buffer-substring (region-beginning) (region-end)))
  768. ((and (region-active-p)
  769. (s-match "^http://dx\\.doi\\.org/" (buffer-substring
  770. (region-beginning)
  771. (region-end))))
  772. (replace-regexp-in-string "^http://dx\\.doi\\.org/" ""
  773. (buffer-substring (region-beginning) (region-end))))
  774. ((and (region-active-p)
  775. (s-match "^https://dx\\.doi\\.org/" (buffer-substring
  776. (region-beginning)
  777. (region-end))))
  778. (replace-regexp-in-string "^https://dx\\.doi\\.org/" ""
  779. (buffer-substring (region-beginning) (region-end))))
  780. ((and (region-active-p)
  781. (s-match (regexp-quote doi-utils-dx-doi-org-url) (buffer-substring
  782. (region-beginning)
  783. (region-end))))
  784. (replace-regexp-in-string (regexp-quote doi-utils-dx-doi-org-url) ""
  785. (buffer-substring (region-beginning) (region-end)))
  786. (buffer-substring (region-beginning) (region-end)))
  787. ;; if the first entry in the kill-ring looks
  788. ;; like a DOI, let's use it.
  789. ((and
  790. ;; make sure the kill-ring has something in it
  791. (stringp (car kill-ring))
  792. (s-match "^10" (car kill-ring)))
  793. (car kill-ring))
  794. ;; maybe kill-ring matches http://dx.doi or somthing
  795. ((and
  796. ;; make sure the kill-ring has something in it
  797. (stringp (car kill-ring))
  798. (s-match "^http://dx\\.doi\\.org/" (car kill-ring)))
  799. (replace-regexp-in-string "^http://dx\\.doi\\.org/" "" (car kill-ring)))
  800. ((and
  801. ;; make sure the kill-ring has something in it
  802. (stringp (car kill-ring))
  803. (s-match "^https://dx\\.doi\\.org/" (car kill-ring)))
  804. (replace-regexp-in-string "^https://dx\\.doi\\.org/" "" (car kill-ring)))
  805. ((and
  806. ;; make sure the kill-ring has something in it
  807. (stringp (car kill-ring))
  808. (s-match (regexp-quote doi-utils-dx-doi-org-url) (car kill-ring)))
  809. (replace-regexp-in-string (regexp-quote doi-utils-dx-doi-org-url) "" (car kill-ring)))
  810. ;; otherwise, we have no initial input. You
  811. ;; will have to type it in.
  812. (t
  813. nil)))))
  814. (unless bibfile
  815. (setq bibfile (completing-read "Bibfile: " (org-ref-possible-bibfiles))))
  816. ;; Wrap in save-window-excursion to restore your window arrangement after this
  817. ;; is done.
  818. (save-window-excursion
  819. (with-current-buffer
  820. (find-file-noselect bibfile)
  821. ;; Check if the doi already exists
  822. (goto-char (point-min))
  823. (if (word-search-forward (concat doi) nil t)
  824. (message "%s is already in this file" doi)
  825. (goto-char (point-max))
  826. (when (not (looking-back "\n\n" (min 3 (point))))
  827. (insert "\n\n"))
  828. (doi-utils-insert-bibtex-entry-from-doi doi)
  829. (save-buffer)))))
  830. (defalias 'doi-add-bibtex-entry 'doi-utils-add-bibtex-entry-from-doi
  831. "Alias function for convenience.")
  832. ;;;###autoload
  833. (defun doi-utils-doi-to-org-bibtex (doi)
  834. "Convert a DOI to an ‘org-bibtex’ form and insert it at point."
  835. (interactive "sDOI: ")
  836. (with-temp-buffer
  837. (insert (doi-utils-doi-to-bibtex-string doi))
  838. (bibtex-clean-entry)
  839. (kill-region (point-min) (point-max)))
  840. (org-bibtex-yank)
  841. (org-metaright)
  842. (org-metaright))
  843. ;;* Updating bibtex entries
  844. ;; I wrote this code because it is pretty common for me to copy bibtex entries
  845. ;; from ASAP articles that are incomplete, e.g. no page numbers because it is
  846. ;; not in print yet. I wanted a convenient way to update an entry from its DOI.
  847. ;; Basically, we get the metadata, and update the fields in the entry.
  848. ;; There is not bibtex set field function, so I wrote this one.
  849. ;;;###autoload
  850. (defun bibtex-set-field (field value &optional nodelim)
  851. "Set FIELD to VALUE in bibtex file. create field if it does not exist.
  852. Optional argument NODELIM see `bibtex-make-field'."
  853. (interactive "sfield: \nsvalue: ")
  854. (bibtex-beginning-of-entry)
  855. (let ((found))
  856. (if (setq found (bibtex-search-forward-field field t))
  857. ;; we found a field
  858. (progn
  859. (goto-char (car (cdr found)))
  860. (when value
  861. (bibtex-kill-field)
  862. (bibtex-make-field field nil nil nodelim)
  863. (backward-char)
  864. (insert value)))
  865. ;; make a new field
  866. (bibtex-beginning-of-entry)
  867. (forward-line) (beginning-of-line)
  868. (bibtex-next-field nil)
  869. (forward-char)
  870. (bibtex-make-field field nil nil nodelim)
  871. (backward-char)
  872. (insert value))))
  873. ;; The updating function for a whole entry looks like this. We get all the keys
  874. ;; from the json plist metadata, and update the fields if they exist.
  875. (defun plist-get-keys (plist)
  876. "Return keys in a PLIST."
  877. (-slice plist 0 nil 2))
  878. ;;;###autoload
  879. (defun doi-utils-update-bibtex-entry-from-doi (doi)
  880. "Update fields in a bibtex entry from the DOI.
  881. Every field will be updated, so previous change will be lost."
  882. (interactive (list
  883. (or (replace-regexp-in-string
  884. "https?://\\(dx.\\)?doi.org/" ""
  885. (bibtex-autokey-get-field "doi"))
  886. (read-string "DOI: "))))
  887. (let* ((results (doi-utils-get-json-metadata doi))
  888. (type (plist-get results :type))
  889. (author (mapconcat
  890. (lambda (x) (concat (plist-get x :given)
  891. " " (plist-get x :family)))
  892. (plist-get results :author) " and "))
  893. (title (plist-get results :title))
  894. (journal (plist-get results :container-title))
  895. (year (format "%s"
  896. (elt
  897. (elt
  898. (plist-get
  899. (plist-get results :issued) :date-parts) 0) 0)))
  900. (volume (plist-get results :volume))
  901. (number (or (plist-get results :issue) ""))
  902. (pages (or (plist-get results :page) ""))
  903. (url (or (plist-get results :URL) ""))
  904. (doi (plist-get results :DOI))
  905. mapping)
  906. ;; map the json fields to bibtex fields. The code each field is mapped to is
  907. ;; evaluated.
  908. (setq mapping '((:author . (bibtex-set-field "author" author))
  909. (:title . (bibtex-set-field "title" title))
  910. (:container-title . (bibtex-set-field "journal" journal))
  911. (:issued . (bibtex-set-field "year" year))
  912. (:volume . (bibtex-set-field "volume" volume))
  913. (:issue . (bibtex-set-field "number" number))
  914. (:page . (bibtex-set-field "pages" pages))
  915. (:DOI . (bibtex-set-field "doi" doi))
  916. (:URL . (bibtex-set-field "url" url))))
  917. ;; now we have code to run for each entry. we map over them and evaluate the code
  918. (mapc
  919. (lambda (key)
  920. (eval (cdr (assoc key mapping))))
  921. (plist-get-keys results)))
  922. (org-ref-clean-bibtex-entry))
  923. ;; A downside to updating an entry is it overwrites what you have already fixed.
  924. ;; So, we next develop a function to update the field at point.
  925. ;;;###autoload
  926. (defun doi-utils-update-field ()
  927. "Update the field at point in the bibtex entry.
  928. Data is retrieved from the doi in the entry."
  929. (interactive)
  930. (let* ((doi (bibtex-autokey-get-field "doi"))
  931. (results (doi-utils-get-json-metadata doi))
  932. (field (car (bibtex-find-text-internal nil nil ","))))
  933. (cond
  934. ((string= field "volume")
  935. (bibtex-set-field field (plist-get results :volume)))
  936. ((string= field "number")
  937. (bibtex-set-field field (plist-get results :issue)))
  938. ((string= field "pages")
  939. (bibtex-set-field field (or (plist-get results :page)
  940. (plist-get results :article-number))))
  941. ((string= field "year")
  942. (bibtex-set-field field (plist-get results :year)))
  943. (t
  944. (message "%s not supported yet." field)))))
  945. ;;* DOI functions for WOS
  946. ;; I came across this API http://wokinfo.com/media/pdf/OpenURL-guide.pdf to make
  947. ;; links to the things I am interested in here. Based on that document, here are
  948. ;; three links based on a doi:10.1021/jp047349j that take you to different Web
  949. ;; Of Science (WOS) pages.
  950. ;; 1. go to article in WOS: http://ws.isiknowledge.com/cps/openurl/service?url_ver=Z39.88-2004&rft_id=info:doi/10.1021/jp047349j
  951. ;; 2. citing articles: http://ws.isiknowledge.com/cps/openurl/service?url_ver=Z39.88-2004&rft_id=info%3Adoi%2F10.1021/jp047349j&svc_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Asch_svc&svc.citing=yes
  952. ;; 3. related articles: http://ws.isiknowledge.com/cps/openurl/service?url_ver=Z39.88-2004&rft_id=info%3Adoi%2F10.1021/jp047349j&svc_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Asch_svc&svc.related=yes
  953. ;; These are pretty easy to construct, so we can write functions that will
  954. ;; create them and open the url in our browser. There are some other options
  955. ;; that could be considered, but since we usually have a doi, it seems like the
  956. ;; best way to go for creating the links. Here are the functions.
  957. ;;;###autoload
  958. (defun doi-utils-wos (doi)
  959. "Open Web of Science entry for DOI."
  960. (interactive "sDOI: ")
  961. (browse-url
  962. (format
  963. "http://ws.isiknowledge.com/cps/openurl/service?url_ver=Z39.88-2004&rft_id=info:doi/%s" doi)))
  964. ;;;###autoload
  965. (defun doi-utils-wos-citing (doi)
  966. "Open Web of Science citing articles entry for DOI.
  967. May be empty if none are found."
  968. (interactive "sDOI: ")
  969. (browse-url
  970. (concat
  971. "http://ws.isiknowledge.com/cps/openurl/service?url_ver=Z39.88-2004&rft_id=info%3Adoi%2F"
  972. doi
  973. "&svc_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Asch_svc&svc.citing=yes")))
  974. ;;;###autoload
  975. (defun doi-utils-wos-related (doi)
  976. "Open Web of Science related articles page for DOI."
  977. (interactive "sDOI: ")
  978. (browse-url
  979. (concat "http://ws.isiknowledge.com/cps/openurl/service?url_ver=Z39.88-2004&rft_id=info%3Adoi%2F"
  980. doi
  981. "&svc_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Asch_svc&svc.related=yes")))
  982. ;;* A new doi link for org-mode
  983. ;; The idea is to add a menu to the doi link, so rather than just clicking to open the article, you can do other things.
  984. ;; 1. open doi
  985. ;; 2. open in wos
  986. ;; 3. open citing articles
  987. ;; 4. open related articles
  988. ;; 5. open bibtex entry
  989. ;; 6. get bibtex entry
  990. ;;;###autoload
  991. (defun doi-utils-open (doi)
  992. "Open DOI in browser."
  993. (interactive "sDOI: ")
  994. (browse-url (concat doi-utils-dx-doi-org-url doi)))
  995. ;;;###autoload
  996. (defun doi-utils-open-bibtex (doi)
  997. "Search through variable `reftex-default-bibliography' for DOI."
  998. (interactive "sDOI: ")
  999. (catch 'file
  1000. (dolist (f reftex-default-bibliography)
  1001. (find-file f)
  1002. (when (search-forward doi (point-max) t)
  1003. (bibtex-beginning-of-entry)
  1004. (throw 'file t)))))
  1005. ;;;###autoload
  1006. (defun doi-utils-crossref (doi)
  1007. "Search DOI in CrossRef."
  1008. (interactive "sDOI: ")
  1009. (browse-url
  1010. (format
  1011. "http://search.crossref.org/?q=%s" doi)))
  1012. ;;;###autoload
  1013. (defun doi-utils-google-scholar (doi)
  1014. "Google scholar the DOI."
  1015. (interactive "sDOI: ")
  1016. (browse-url
  1017. (format
  1018. "http://scholar.google.com/scholar?q=%s" doi)))
  1019. ;;;###autoload
  1020. (defun doi-utils-pubmed (doi)
  1021. "Search Pubmed for the DOI."
  1022. (interactive "sDOI: ")
  1023. (browse-url
  1024. (format
  1025. "http://www.ncbi.nlm.nih.gov/pubmed/?term=%s"
  1026. (url-hexify-string doi))))
  1027. (defvar doi-link-menu-funcs '()
  1028. "Functions to run in doi menu.
  1029. Each entry is a list of (key menu-name function). The function
  1030. must take one argument, the doi.")
  1031. (setq doi-link-menu-funcs
  1032. '(("o" "pen" doi-utils-open)
  1033. ("w" "os" doi-utils-wos)
  1034. ("c" "iting articles" doi-utils-wos-citing)
  1035. ("r" "elated articles" doi-utils-wos-related)
  1036. ("s" "Google Scholar" doi-utils-google-scholar)
  1037. ("f" "CrossRef" doi-utils-crossref)
  1038. ("p" "ubmed" doi-utils-pubmed)
  1039. ("b" "open in bibtex" doi-utils-open-bibtex)
  1040. ("g" "et bibtex entry" doi-utils-add-bibtex-entry-from-doi)))
  1041. ;;;###autoload
  1042. (defun doi-link-menu (link-string)
  1043. "Generate the link menu message, get choice and execute it.
  1044. Options are stored in `doi-link-menu-funcs'.
  1045. Argument LINK-STRING Passed in on link click."
  1046. (interactive)
  1047. (message
  1048. (concat
  1049. (mapconcat
  1050. (lambda (tup)
  1051. (concat "[" (elt tup 0) "]"
  1052. (elt tup 1) " "))
  1053. doi-link-menu-funcs "") ": "))
  1054. (let* ((input (read-char-exclusive))
  1055. (choice (assoc
  1056. (char-to-string input) doi-link-menu-funcs)))
  1057. (when choice
  1058. (funcall
  1059. (elt
  1060. choice
  1061. 2)
  1062. link-string))))
  1063. (org-ref-link-set-parameters "doi"
  1064. :follow #'doi-link-menu
  1065. :export (lambda (doi desc format)
  1066. (cond
  1067. ((eq format 'html)
  1068. (format "<a href=\"%s%s\">%s</a>"
  1069. doi-utils-dx-doi-org-url
  1070. doi
  1071. (or desc (concat "doi:" doi))))
  1072. ((eq format 'latex)
  1073. (format "\\href{%s%s}{%s}"
  1074. doi-utils-dx-doi-org-url
  1075. doi
  1076. (or desc (concat "doi:" doi)))))))
  1077. ;;* Getting a doi for a bibtex entry missing one
  1078. ;; Some bibtex entries do not have a DOI, maybe because they were entered by
  1079. ;; hand, or copied from a source that did not have it available. Here we develop
  1080. ;; some functions to help you find the DOI using Crossref.
  1081. ;; Here is our example bibtex entry.
  1082. ;; #+BEGIN_SRC bibtex
  1083. ;; @article{deml-2014-oxide,
  1084. ;; author = {Ann M. Deml and Vladan Stevanovi{\'c} and
  1085. ;; Christopher L. Muhich and Charles B. Musgrave and
  1086. ;; Ryan O'Hayre},
  1087. ;; title = {Oxide Enthalpy of Formation and Band Gap Energy As
  1088. ;; Accurate Descriptors of Oxygen Vacancy Formation
  1089. ;; Energetics},
  1090. ;; journal = {Energy Environ. Sci.},
  1091. ;; volume = 7,
  1092. ;; number = 6,
  1093. ;; pages = 1996,
  1094. ;; year = 2014,
  1095. ;; doi = {10.1039/c3ee43874k,
  1096. ;; url = {http://dx.doi.org/10.1039/c3ee43874k}},
  1097. ;; }
  1098. ;; The idea is to query Crossref in a way that is likely to give us a hit
  1099. ;; relevant to the entry.
  1100. ;; According to http://search.crossref.org/help/api we can send a query with a
  1101. ;; free form citation that may give us something back. We do this to get a list
  1102. ;; of candidates, and run a helm command to get the doi.
  1103. ;;;###autoload
  1104. (defun doi-utils-crossref-citation-query ()
  1105. "Query Crossref with the title of the bibtex entry at point.
  1106. Get a list of possible matches. This opens a helm buffer to
  1107. select an entry. The default action inserts a doi and url field
  1108. in the bibtex entry at point. The second action opens the doi
  1109. url. If there is already a doi field, the function raises an
  1110. error."
  1111. (interactive)
  1112. (bibtex-beginning-of-entry)
  1113. (let* ((entry (bibtex-parse-entry))
  1114. (raw-json-string)
  1115. (json-string)
  1116. (json-data)
  1117. (doi))
  1118. (unless (string= ""(reftex-get-bib-field "doi" entry))
  1119. (error "Entry already has a doi field"))
  1120. (with-current-buffer
  1121. (url-retrieve-synchronously
  1122. (concat
  1123. "http://search.crossref.org/dois?q="
  1124. (url-hexify-string (org-ref-bib-citation))))
  1125. (save-excursion
  1126. (goto-char (point-min))
  1127. (while (re-search-forward "<i>\\|</i>" nil t)
  1128. (replace-match ""))
  1129. (goto-char (point-min))
  1130. (while (re-search-forward "&amp;" nil t)
  1131. (replace-match "&"))
  1132. (goto-char (point-min))
  1133. (while (re-search-forward "&quot;" nil t)
  1134. (replace-match "\\\"" nil t)))
  1135. (setq raw-json-string (buffer-substring url-http-end-of-headers (point-max)))
  1136. ;; decode json string
  1137. (setq json-string (decode-coding-string (string-make-unibyte raw-json-string) 'utf-8))
  1138. (setq json-data (json-read-from-string json-string)))
  1139. (let* ((name (format "Crossref hits for %s" (org-ref-bib-citation)))
  1140. (helm-candidates (mapcar (lambda (x)
  1141. (cons
  1142. (concat
  1143. (cdr (assoc 'fullCitation x)))
  1144. (cdr (assoc 'doi x))))
  1145. json-data))
  1146. (source `((name . ,name)
  1147. (candidates . ,helm-candidates)
  1148. ;; just return the candidate
  1149. (action . (("Insert doi and url field" . (lambda (doi)
  1150. (bibtex-make-field "doi" t)
  1151. (backward-char)
  1152. ;; crossref returns doi url, but I prefer only a doi for the doi field
  1153. (insert (replace-regexp-in-string "^https?://\\(dx.\\)?doi.org/" "" doi))
  1154. (when (string= ""(reftex-get-bib-field "url" entry))
  1155. (bibtex-make-field "url" t)
  1156. (backward-char)
  1157. (insert doi))))
  1158. ("Open url" . (lambda (doi)
  1159. (browse-url doi))))))))
  1160. (helm :sources source
  1161. :buffer "*doi utils*"))))
  1162. ;;* Debugging a DOI
  1163. ;; I wrote this function to help debug a DOI. This function generates an
  1164. ;; org-buffer with the doi, gets the json metadata, shows the bibtex entry, and
  1165. ;; the pdf link for it.
  1166. (defun doi-utils-get-json (doi)
  1167. "Return json data as a string for DOI."
  1168. (let ((url-request-method "GET")
  1169. (url-mime-accept-string "application/citeproc+json")
  1170. (json-data))
  1171. (with-current-buffer
  1172. (url-retrieve-synchronously
  1173. (concat doi-utils-dx-doi-org-url doi))
  1174. (setq json-data (buffer-substring url-http-end-of-headers (point-max)))
  1175. (if (string-match "Resource not found" json-data)
  1176. (progn
  1177. (browse-url (concat doi-utils-dx-doi-org-url doi))
  1178. (error "Resource not found. Opening website"))
  1179. json-data))))
  1180. ;;;###autoload
  1181. (defun doi-utils-debug (doi)
  1182. "Generate an org-buffer showing data about DOI."
  1183. (interactive "sDOI: ")
  1184. (switch-to-buffer "*debug-doi*")
  1185. (erase-buffer)
  1186. (org-mode)
  1187. (insert (concat "doi:" doi) "\n\n")
  1188. (insert "* JSON
  1189. "
  1190. (let ((url-request-method "GET")
  1191. (url-mime-accept-string "application/citeproc+json"))
  1192. (pp
  1193. (json-read-from-string (with-current-buffer
  1194. (url-retrieve-synchronously
  1195. (concat doi-utils-dx-doi-org-url doi))
  1196. (buffer-substring url-http-end-of-headers (point-max))))))
  1197. "\n\n")
  1198. (goto-char (point-min)))
  1199. ;;* Adding a bibtex entry from a crossref query
  1200. ;; The idea here is to perform a query on Crossref, get a helm buffer of
  1201. ;; candidates, and select the entry(ies) you want to add to your bibtex file.
  1202. ;; You can select a region, e.g. a free form citation, or set of words, or you
  1203. ;; can type the query in by hand.
  1204. ;;;###autoload
  1205. (defun doi-utils-add-entry-from-crossref-query (query bibtex-file)
  1206. "Search Crossref with QUERY and use helm to select an entry to add to BIBTEX-FILE."
  1207. (interactive (list
  1208. (read-string
  1209. "Query: "
  1210. ;; now set initial input
  1211. (cond
  1212. ;; If region is active assume we want it
  1213. ((region-active-p)
  1214. (replace-regexp-in-string
  1215. "\n" " "
  1216. (buffer-substring (region-beginning) (region-end))))
  1217. ;; type or paste it in
  1218. (t
  1219. nil)))
  1220. (completing-read
  1221. "Bibfile: "
  1222. (append (f-entries "." (lambda (f) (f-ext? f "bib")))
  1223. org-ref-default-bibliography))))
  1224. (let* ((raw-json-string)
  1225. (json-string)
  1226. (json-data)
  1227. (doi))
  1228. (with-current-buffer
  1229. (url-retrieve-synchronously
  1230. (concat
  1231. "http://search.crossref.org/dois?q="
  1232. (url-hexify-string query)))
  1233. ;; replace html entities
  1234. (save-excursion
  1235. (goto-char (point-min))
  1236. (while (re-search-forward "<i>\\|</i>" nil t)
  1237. (replace-match ""))
  1238. (goto-char (point-min))
  1239. (while (re-search-forward "&amp;" nil t)
  1240. (replace-match "&"))
  1241. (goto-char (point-min))
  1242. (while (re-search-forward "&quot;" nil t)
  1243. (replace-match "\\\"" nil t)))
  1244. (setq raw-json-string (buffer-substring url-http-end-of-headers (point-max)))
  1245. ;; decode json string
  1246. (setq json-string (decode-coding-string (string-make-unibyte raw-json-string) 'utf-8))
  1247. (setq json-data (json-read-from-string json-string)))
  1248. (let* ((name (format "Crossref hits for %s"
  1249. ;; remove carriage returns. they cause problems in helm.
  1250. (replace-regexp-in-string "\n" " " query)))
  1251. (helm-candidates (mapcar (lambda (x)
  1252. (cons
  1253. (concat
  1254. (cdr (assoc 'fullCitation x)))
  1255. (cdr (assoc 'doi x))))
  1256. json-data))
  1257. (source `((name . ,name)
  1258. (candidates . ,helm-candidates)
  1259. ;; just return the candidate
  1260. (action . (("Insert bibtex entry" . (lambda (doi)
  1261. (with-current-buffer (find-file-noselect bibtex-file)
  1262. (cl-loop for doi in (helm-marked-candidates)
  1263. do
  1264. (doi-utils-add-bibtex-entry-from-doi
  1265. (replace-regexp-in-string
  1266. "^https?://\\(dx.\\)?doi.org/" "" doi)
  1267. ,bibtex-file)))))
  1268. ("Open url" . (lambda (doi)
  1269. (browse-url doi))))))))
  1270. (helm :sources source
  1271. :buffer "*doi utils*"))))
  1272. (defalias 'crossref-add-bibtex-entry 'doi-utils-add-entry-from-crossref-query
  1273. "Alias function for convenience.")
  1274. ;; * Convenience
  1275. (defun doi-utils-toggle-pdf-download ()
  1276. "Toggle the setting of `doi-utils-download-pdf'.
  1277. I find this useful when downloading the pdfs slows down adding a
  1278. lot of references; then you just toggle it off."
  1279. (interactive)
  1280. (message "Setting doi-utils-download-pdf to %s"
  1281. (setq doi-utils-download-pdf (not doi-utils-download-pdf))))
  1282. ;;* The end
  1283. (provide 'doi-utils)
  1284. ;;; doi-utils.el ends here