;;; doi-utils.el --- DOI utilities for making bibtex entries -*- lexical-binding: t; -*- ;; Copyright (C) 2015-2021 John Kitchin ;; Author: John Kitchin ;; Keywords: convenience ;; Package-Requires: ((org-ref)) ;; 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 . ;;; Commentary: ;; This package provides functionality to download PDFs and bibtex entries from ;; a DOI, as well as to update a bibtex entry from a DOI. It depends slightly ;; on org-ref, to determine where to save pdf files too, and where to insert ;; bibtex entries in the default bibliography. ;; The principle commands you will use from here are: ;; - doi-utils-get-bibtex-entry-pdf with the cursor in a bibtex entry. ;; - doi-utils-insert-bibtex-entry-from-doi to insert a bibtex entry at your cursor, clean it and try to get a pdf. ;; - doi-utils-add-bibtex-entry-from-doi to add an entry to your default bibliography (cleaned with pdf if possible). ;; - doi-utils-update-bibtex-entry-from-doi with cursor in an entry to update its fields. ;;; Code: (defvar url-http-end-of-headers) (declare-function org-ref-find-bibliography "org-ref-core") (declare-function org-ref-clean-bibtex-entry "org-ref-core") (declare-function bibtex-completion-edit-notes "bibtex-completion") (declare-function org-bibtex-yank "org-bibtex") (declare-function org-ref-possible-bibfiles "org-ref-core") (declare-function org-ref-normalize-bibtex-completion-bibliography "org-ref-utils") (declare-function org-ref--file-ext-p "org-ref-utils") (declare-function org-ref--directory-files "org-ref-utils") (eval-when-compile (require 'cl-lib)) (require 'bibtex) (require 'json) (require 'org) ; org-add-link-type (or (require 'ol-bibtex nil t) (require 'org-bibtex)) ; org-bibtex-yank (require 'url-http) (require 'url-handlers) (require 'org-ref-utils) (require 'transient) ;;* Customization (defgroup doi-utils nil "Customization group for doi-utils." :tag "DOI utils" :group 'doi-utils) (defcustom doi-utils-download-pdf t "Try to download PDFs when adding bibtex entries when non-nil." :type 'boolean :group 'doi-utils) (defcustom doi-utils-open-pdf-after-download nil "Open PDF after adding bibtex entries." :type 'boolean :group 'doi-utils) (defcustom doi-utils-timestamp-field "DATE_ADDED" "The bibtex field to store the date when an entry has been added." :type 'string :group 'doi-utils) (defcustom doi-utils-timestamp-format-function 'current-time-string "The function to format the timestamp for a bibtex entry. Set to a function that returns nil to avoid setting timestamps in the entries. e.g. (lambda () nil)" :type 'function :group 'doi-utils) (defcustom doi-utils-dx-doi-org-url "https://doi.org/" "Base url to retrieve doi metadata from. A trailing / is required." :type 'string :group 'doi-utils) (defcustom doi-utils-metadata-function 'doi-utils-get-json-metadata "Function for retrieving json metadata from `doi-utils-dx-doi-org-url'. The default is `doi-utils-get-json-metadata', but it sometimes fails with a proxy. An alternative is `doi-utils-get-json-metadata-curl' which requires an external program to use curl." :type 'function :group 'doi-utils) (defcustom doi-utils-async-download t "Use `doi-utils-async-download-pdf' to get pdfs asynchrounously. If nil use `doi-utils-get-bibtex-entry-pdf' synchronously." :type 'boolean :group 'doi-utils) (defun doi-utils-pdf-filename-from-key () "Generate PDF filename from bibtex entry key. This is the default function for `doi-utils-pdf-filename-function'. Returns the bibtex entry key as the filename (without extension or path)." (cdr (assoc "=key=" (bibtex-parse-entry)))) (defcustom doi-utils-pdf-filename-function 'doi-utils-pdf-filename-from-key "Function to generate PDF filename from bibtex entry. The function is called with no arguments while point is in the bibtex entry, and should return a string to use as the PDF filename (without the .pdf extension or directory path). The directory path is determined separately by `bibtex-completion-library-path'. The default function uses the bibtex entry key as the filename. Example: To use the title field as the filename: (setq doi-utils-pdf-filename-function (lambda () (bibtex-autokey-get-field \"title\"))) Note: The function is responsible for ensuring the returned filename is valid for the filesystem. Special characters in fields like title may cause issues on some systems." :type 'function :group 'doi-utils) ;;* Getting pdf files from a DOI ;; The idea here is simple. When you visit http://dx.doi.org/doi or ;; https://doi.org/doi, you get redirected to the journal site. Once you have ;; the url for the article, you can usually compute the url to the pdf, or find ;; it in the page. Then you simply download it. ;; There are some subtleties in doing this that are described here. To get the ;; redirect, we have to use url-retrieve, and a callback function. The callback ;; does not return anything, so we communicate through global variables. ;; url-retrieve is asynchronous, so we have to make sure to wait for it to ;; finish. (defvar *doi-utils-waiting* t "Stores waiting state for url retrieval.") (defvar *doi-utils-redirect* nil "Stores redirect url from a callback function.") (defun doi-utils-redirect-callback (&optional status) "Callback for `url-retrieve' to set the redirect. Optional argument STATUS Unknown why this is optional." (when (plist-get status :error) (signal (car (plist-get status :error)) (cdr(plist-get status :error)))) (when (plist-get status :redirect) ; is nil if there none (setq *doi-utils-redirect* (plist-get status :redirect))) ;; we have done our job, so we are not waiting any more. (setq *doi-utils-waiting* nil)) ;; To actually get the redirect we use url-retrieve like this. (defun doi-utils-get-redirect (doi) "Get redirect url from `doi-utils-dx-doi-org-url'/doi." ;; we are going to wait until the url-retrieve is done (setq *doi-utils-waiting* t) ;; start with no redirect. it will be set in the callback. (setq *doi-utils-redirect* nil) (url-retrieve (format "%s%s" doi-utils-dx-doi-org-url doi) 'doi-utils-redirect-callback) ;; I suspect we need to wait here for the asynchronous process to ;; finish. we loop and sleep until the callback says it is done via ;; `*doi-utils-waiting*'. this works as far as i can tell. Before I ;; had to run this a few times to get it to work, which i suspect ;; just gave the first one enough time to finish. (while *doi-utils-waiting* (sleep-for 0.1))) ;; Once we have a redirect for a particular doi, we need to compute the url to ;; the pdf. We do this with a series of functions. Each function takes a single ;; argument, the redirect url. If it knows how to compute the pdf url it does, ;; and returns it. We store the functions in a variable: (defvar doi-utils-pdf-url-functions nil "Functions that return a url to a pdf from a redirect url. Each function takes one argument, the redirect url. The function must return a pdf-url, or nil.") ;;** APS journals (defun aps-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s*\\)://journals.aps.org" *doi-utils-redirect*) (replace-regexp-in-string "/abstract/" "/pdf/" *doi-utils-redirect*))) ;;** Science (defun science-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://www.sciencemag.org" *doi-utils-redirect*) (concat *doi-utils-redirect* ".full.pdf"))) ;;** Nature (defun nature-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://www.nature.com" *doi-utils-redirect*) (concat *doi-utils-redirect* ".pdf"))) ;;** Elsevier/ScienceDirect ;; You cannot compute these pdf links; they are embedded in the redirected pages. (defvar *doi-utils-pdf-url* nil "Stores url to pdf download from a callback function.") ;;** Wiley ;; Wiley have changed the url structure from ;; http://onlinelibrary.wiley.com/doi/10.1002/anie.201402680/abstract ;; http://onlinelibrary.wiley.com/doi/10.1002/anie.201402680/pdf ;; to ;; http://onlinelibrary.wiley.com/doi/abs/10.1002/anie.201402680 ;; http://onlinelibrary.wiley.com/doi/pdf/10.1002/anie.201402680 ;; Hence fewer steps are now required. ;; https://onlinelibrary.wiley.com/doi/10.1002/adts.202200926 ;; https://onlinelibrary.wiley.com/doi/epdf/10.1002/adts.202200926 ;; (defun wiley-pdf-url (*doi-utils-redirect*) ;; "Get url to the pdf from *DOI-UTILS-REDIRECT*." ;; (when (string-match "^http\\(s?\\)://onlinelibrary.wiley.com" *doi-utils-redirect*) ;; (replace-regexp-in-string "doi/abs" "doi/pdf" *doi-utils-redirect*))) (defun wiley-pdf-url-2 (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*. [2023-04-10 Mon] updated a new rule. https://onlinelibrary.wiley.com/doi/pdfdirect/10.1002/anie.201310461?download=true" (when (string-match "^http\\(s?\\)://onlinelibrary.wiley.com" *doi-utils-redirect*) (concat (replace-regexp-in-string "doi/" "doi/pdfdirect/" *doi-utils-redirect*) "?download=true"))) (defun agu-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "https://agupubs.onlinelibrary.wiley.com" *doi-utils-redirect*) (replace-regexp-in-string "/full/" "/pdfdirect/" *doi-utils-redirect*))) ;;** Springer (defun springer-chapter-pdf-url (*doi-utils-redirect*) (when (string-match "^http\\(s?\\)://link.springer.com/chapter/" *doi-utils-redirect*) (replace-regexp-in-string "/chapter" "/content/pdf" (concat *doi-utils-redirect* ".pdf")))) (defun springer-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://link.springer.com" *doi-utils-redirect*) (replace-regexp-in-string "/article/" "/content/pdf/" (concat *doi-utils-redirect* ".pdf")))) ;;** ACS ;; here is a typical url http://pubs.acs.org/doi/abs/10.1021/nl500037x ;; the pdf is found at http://pubs.acs.org/doi/pdf/10.1021/nl500037x ;; we just change /abs/ to /pdf/. (defun acs-pdf-url-1 (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://pubs.acs.org/doi/abs/" *doi-utils-redirect*) (replace-regexp-in-string "/abs/" "/pdf/" *doi-utils-redirect*))) ;; 1/20/2016 I noticed this new pattern in pdf urls, where there is no abs in ;; the url (defun acs-pdf-url-2 (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://pubs.acs.org/doi/" *doi-utils-redirect*) (replace-regexp-in-string "/doi/" "/doi/pdf/" *doi-utils-redirect*))) ;; 1/18/2019: It looks like they are using https now (defun acs-pdf-url-3 (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^https://pubs.acs.org/doi/" *doi-utils-redirect*) (replace-regexp-in-string "/doi/" "/doi/pdf/" *doi-utils-redirect*))) ;;** IOP (defun iop-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://iopscience.iop.org" *doi-utils-redirect*) (concat *doi-utils-redirect* "/pdf"))) ;;** JSTOR (defun jstor-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://www.jstor.org" *doi-utils-redirect*) (concat (replace-regexp-in-string "/stable/" "/stable/pdfplus/" *doi-utils-redirect*) ".pdf"))) ;;** AIP (defun aip-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://scitation.aip.org" *doi-utils-redirect*) ;; get stuff after content (let (p1 p2 s p3) (setq p2 (replace-regexp-in-string "^http\\(s?\\)://scitation.aip.org/" "" *doi-utils-redirect*)) (setq s (split-string p2 "/")) (setq p1 (mapconcat 'identity (org-ref--remove-at-indices '(0 6) s) "/")) (setq p3 (concat "/" (nth 0 s) (nth 1 s) "/" (nth 2 s) "/" (nth 3 s))) (format "http://scitation.aip.org/deliver/fulltext/%s.pdf?itemId=/%s&mimeType=pdf&containerItemId=%s" p1 p2 p3)))) (defun aip-pdf-url-2 (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." ;; [2021-08-28 Sat] Seems like they changed the link a little? ;; https://aip.scitation.org/doi/10.1063/1.5019667 ;; to ;; https://aip.scitation.org/doi/pdf/10.1063/1.5019667 (when (string-match "^http\\(s?\\)://aip.scitation.org" *doi-utils-redirect*) (concat "https://aip.scitation.org/doi/pdf" (cl-second (split-string *doi-utils-redirect* "doi"))))) ;;** Taylor and Francis (defun tandfonline-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://www.tandfonline.com" *doi-utils-redirect*) (replace-regexp-in-string "/abs/\\|/full/" "/pdf/" *doi-utils-redirect*))) ;;** ECS (defun ecs-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://jes.ecsdl.org" *doi-utils-redirect*) (replace-regexp-in-string "\\.abstract$" ".full.pdf" *doi-utils-redirect*))) ;; http://ecst.ecsdl.org/content/25/2/2769 ;; http://ecst.ecsdl.org/content/25/2/2769.full.pdf (defun ecst-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://ecst.ecsdl.org" *doi-utils-redirect*) (concat *doi-utils-redirect* ".full.pdf"))) ;;** RSC (defun rsc-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://pubs.rsc.org" *doi-utils-redirect*) (let ((url (downcase *doi-utils-redirect*))) (setq url (replace-regexp-in-string "articlelanding" "articlepdf" url)) url))) ;;** Science Direct ;; https://www.sciencedirect.com/science/article/pii/S001085452200577X?via%3Dihub ;; https://www.sciencedirect.com/science/article/pii/S001085452200577X/pdfft?isDTMRedir=true&download=true (defun science-direct-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://www.sciencedirect.com" *doi-utils-redirect*) (replace-string "?via%3Dihub" "/pdfft?isDTMRedir=true&download=true" *doi-utils-redirect*))) ;; (defun doi-utils-get-science-direct-pdf-url (redirect-url) ;; "Science direct hides the pdf url in html. We get it out here. ;; REDIRECT-URL is where the pdf url will be in." ;; (let ((first-url ;; (with-current-buffer (url-retrieve-synchronously redirect-url) ;; (goto-char (point-min)) ;; (when (re-search-forward "pdf_url\" content=\"\\([^\"]*\\)\"" nil t) ;; (match-string-no-properties 1))))) ;; (and first-url ;; (with-current-buffer (url-retrieve-synchronously first-url) ;; (goto-char (point-min)) ;; (when (re-search-forward "or click " nil t) ;; (match-string-no-properties 1)))))) ;; (defun science-direct-pdf-url (*doi-utils-redirect*) ;; "Get url to the pdf from *DOI-UTILS-REDIRECT*." ;; (when (string-match "^http\\(s?\\)://www.sciencedirect.com" *doi-utils-redirect*) ;; (doi-utils-get-science-direct-pdf-url *doi-utils-redirect*))) ;; sometimes I get ;; http://linkinghub.elsevier.com/retrieve/pii/S0927025609004558 ;; which actually redirect to ;; http://www.sciencedirect.com/science/article/pii/S0927025609004558 ;; https://www.sciencedirect.com/science/article/pii/S001085452200577X?via%3Dihub ;; https://www.sciencedirect.com/science/article/pii/S001085452200577X/pdfft?isDTMRedir=true&download=true ;; (defun linkinghub-elsevier-pdf-url (*doi-utils-redirect*) ;; "Get url to the pdf from *DOI-UTILS-REDIRECT*." ;; (when (string-match ;; "^https://linkinghub.elsevier.com/retrieve" *doi-utils-redirect*) ;; (science-direct-pdf-url ;; (replace-regexp-in-string ;; ;; change URL to science direct and use function to get pdf URL ;; "https://linkinghub.elsevier.com/retrieve" ;; "https://www.sciencedirect.com/science/article" ;; *doi-utils-redirect*)))) ;; https://www.sciencedirect.com/science/article/pii/S1385894723014973/pdfft?isDTMRedir=true&download=true (defun linkinghub-elsevier-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^https://linkinghub.elsevier.com/retrieve" *doi-utils-redirect*) (concat (replace-regexp-in-string ;; change URL to science direct and use function to get pdf URL "https://linkinghub.elsevier.com/retrieve" "https://www.sciencedirect.com/science/article" *doi-utils-redirect*) "/pdfft?isDTMRedir=true"))) ;;** PNAS ;; http://www.pnas.org/content/early/2014/05/08/1319030111 ;; http://www.pnas.org/content/early/2014/05/08/1319030111.full.pdf ;; with supporting info ;; http://www.pnas.org/content/early/2014/05/08/1319030111.full.pdf+html?with-ds=yes (defun pnas-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://www.pnas.org" *doi-utils-redirect*) (concat *doi-utils-redirect* ".full.pdf?with-ds=yes"))) ;;** Copernicus Publications (defvar copernicus-journal-urls '( "^https://www.adv-geosci.net/" "^https://www.adv-radio-sci.net/" "^https://www.adv-sci-res.net/" "^https://www.adv-stat-clim-meteorol-oceanogr.net/" "^https://www.ann-geophys.net/" "^https://www.arch-anim-breed.net/" "^https://www.astra-proc.net/" "^https://www.atmos-chem-phys.net/" "^https://www.atmos-chem-phys-discuss.net/" "^https://www.atmos-meas-tech.net/" "^https://www.atmos-meas-tech-discuss.net/" "^https://www.biogeosciences.net/" "^https://www.biogeosciences-discuss.net/" "^https://www.clim-past.net/recent_papers.html" "^https://www.clim-past-discuss.net/" "^https://www.drink-water-eng-sci.net/" "^https://www.drink-water-eng-sci-discuss.net/" "^https://www.eg-quaternary-sci-j.net/" "^https://www.earth-surf-dynam.net/" "^https://www.earth-surf-dynam-discuss.net/" "^https://www.earth-syst-dynam.net/" "^https://www.earth-syst-dynam-discuss.net/" "^https://www.earth-syst-sci-data.net/" "^https://www.earth-syst-sci-data-discuss.net/" "^https://www.foss-rec.net/" "^https://www.geogr-helv.net/" "^https://www.geosci-instrum-method-data-syst.net/" "^https://www.geosci-instrum-method-data-syst-discuss.net/" "^https://www.geosci-model-dev.net/" "^https://www.geosci-model-dev-discuss.net/" "^https://www.hist-geo-space-sci.net/" "^https://www.hydrol-earth-syst-sci.net/" "^https://www.hydrol-earth-syst-sci-discuss.net/" "^https://www.j-sens-sens-syst.net/" "^https://www.mech-sci.net/" "^https://www.nat-hazards-earth-syst-sci.net/" "^https://www.nonlin-processes-geophys-discuss.net/" "^https://www.ocean-sci.net/" "^https://www.ocean-sci-discuss.net/" "^https://www.primate-biol.net/" "^https://www.proc-iahs.net/" "^https://www.sci-dril.net/" "^https://www.soil-journal.net/" "^https://www.soil-discuss.net/" "^https://www.solid-earth.net/" "^https://www.solid-earth-discuss.net/" "^https://www.stephan-mueller-spec-publ-ser.net/" "^https://www.the-cryosphere.net/" "^https://www.the-cryosphere-discuss.net/" "^https://www.web-ecol.net/" "^https://www.wind-energ-sci.net/" "^https://www.wind-energ-sci-discuss.net/" ) "List of Copernicus URLs.") (defun doi-utils-get-copernicus-pdf-url (redirect-url) "Copernicus hides the pdf url in html. We get it out here. REDIRECT-URL is where the pdf url will be in." (setq *doi-utils-waiting* t) (url-retrieve redirect-url (lambda (status) (goto-char (point-min)) (re-search-forward "citation_pdf_url\" content=\"\\([^\"]*\\)\"" nil t) (setq *doi-utils-pdf-url* (match-string 1) *doi-utils-waiting* nil))) (while *doi-utils-waiting* (sleep-for 0.1)) *doi-utils-pdf-url*) (defun copernicus-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (car (cl-loop for copurl in copernicus-journal-urls when (string-match copurl *doi-utils-redirect*) collect (progn (doi-utils-get-copernicus-pdf-url *doi-utils-redirect*) *doi-utils-pdf-url*)))) ;;** Sage (defun sage-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://pss.sagepub.com" *doi-utils-redirect*) (concat *doi-utils-redirect* ".full.pdf"))) ;;** Journal of Neuroscience (defun jneurosci-pdf-url (*doi-utils-redirect*) "Get url to the pdf from *DOI-UTILS-REDIRECT*." (when (string-match "^http\\(s?\\)://www.jneurosci.org" *doi-utils-redirect*) (concat *doi-utils-redirect* ".full.pdf"))) ;;** Generic .full.pdf (defun generic-full-pdf-url (*doi-utils-redirect*) (let ((pdf (concat *doi-utils-redirect* ".full.pdf"))) (when (url-http-file-exists-p pdf) pdf))) ;;** IEEE ;; 10.1109/re.2014.6912247 ;; http(s)://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=6912247 ;; http(s)://ieeexplore.ieee.org/ielx7/6903646/6912234/06912247.pdf ;; http(s)://ieeexplore.ieee.org/iel7/6903646/6912234/06912247.pdf?arnumber=6912247 ;; ;; (defun ieee-pdf-url (*doi-utils-redirect*) "Get a url to the pdf from *DOI-UTILS-REDIRECT* for IEEE urls." (when (string-match "^https?://ieeexplore.ieee.org" *doi-utils-redirect*) (with-current-buffer (url-retrieve-synchronously *doi-utils-redirect*) (goto-char (point-min)) (when (re-search-forward "" nil t) (let ((framed-url (match-string 1))) (with-current-buffer (url-retrieve-synchronously framed-url) (goto-char (point-min)) (when (re-search-forward " (defun ieee3-pdf-url (*doi-utils-redirect*) "Get a url to the pdf from *DOI-UTILS-REDIRECT* for IEEE urls." (when (string-match "^https?://ieeexplore.ieee.org" *doi-utils-redirect*) (with-current-buffer (url-retrieve-synchronously *doi-utils-redirect*) (goto-char (point-min)) (when (re-search-forward "\"pdfUrl\":\"\\([[:ascii:]]*?\\)\"" nil t) (let ((framed-url (match-string 1))) (with-current-buffer (url-retrieve-synchronously (concat "http://ieeexplore.ieee.org" framed-url)) (goto-char (point-min)) (when (re-search-forward "