├── .dir-locals.el ├── README.org └── gscholar-bibtex.el /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((emacs-lisp-mode 5 | (indent-tabs-mode))) 6 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * gscholar bibtex 2 | [[http://melpa.org/#/gscholar-bibtex][file:http://melpa.org/packages/gscholar-bibtex-badge.svg]] 3 | [[http://stable.melpa.org/#/gscholar-bibtex][file:http://stable.melpa.org/packages/gscholar-bibtex-badge.svg]] 4 | 5 | Retrieve BibTeX entries from Google Scholar, ACM Digital Library, IEEE Xplore 6 | and DBLP by your query. All in Emacs Lisp! 7 | 8 | *UPDATE*: ACM Digital Library, IEEE Xplore, and DBLP are now supported though 9 | the package name doesn't suggest that. 10 | ** Basic usage 11 | Without =package.el=: 12 | : (add-to-list 'load-path "/path/to/gscholar-bibtex.el") 13 | : (require 'gscholar-bibtex) 14 | 15 | With =package.el=: install via melpa! 16 | 17 | To use, simply call 18 | : M-x gscholar-bibtex 19 | 20 | Choose a source, then enter your query and select the results. 21 | 22 | Available commands in `gscholar-bibtex-mode', /i.e./, in the window of search 23 | results: 24 | - n/p: next/previous 25 | - TAB: show BibTeX entry for current search result 26 | - A/W: append/write to `gscholar-bibtex-database-file' (see later) 27 | - a/w: append/write to a file 28 | - c: copy the current BibTeX entry 29 | - x: close BibTeX entry window 30 | - q: quit 31 | 32 | ** Sources 33 | By default, I enable all sources(Google Scholar, ACM Digital Library, IEEE 34 | Xplore and DBLP). If you don't want to enable some of them, you could call 35 | : M-x gscholar-bibtex-turn-off-sources 36 | 37 | Similarly, if you want to enable some of them, you could call 38 | : M-x gscholar-bibtex-turn-on-sources 39 | 40 | To keep the configuration in your init file, you could use the following 41 | format(*NOT* real code): 42 | : (gscholar-bibtex-source-on-off action source-name) 43 | 44 | Possible values: 45 | - /action/: :on or :off 46 | - /source-name/: "Google Scholar", "ACM Digital Library", "IEEE Xplore" or "DBLP" 47 | 48 | Say if you want to disable "IEEE Xplore", use the following code: 49 | : (gscholar-bibtex-source-on-off :off "IEEE Xplore") 50 | 51 | ** Default source 52 | If you have a preferred source, you can set it as default so you don't have to 53 | type the name to select the source every time you call `gscholar-bibtex'. Say 54 | if you want to set "Google Scholar" as default: 55 | : (setq gscholar-bibtex-default-source "Google Scholar") 56 | 57 | Note that in order to make it work, you have to make sure the source name is 58 | correct and you don't disable the source that you set as default, otherwise 59 | the default source setting has no effect. Besides, if you only have one source 60 | enabled, then the enabled source automatically becomes the default, regardless 61 | of the value of `gscholar-bibtex-default-source'. 62 | 63 | ** Configuring `gscholar-bibtex-database-file' 64 | If you have a master BibTeX file, say =refs.bib=, as database, and want to 65 | append/write the BibTeX entry to =refs.bib= without being asked for a 66 | filename to be written every time, you can set 67 | `gscholar-bibtex-database-file': 68 | : (setq gscholar-bibtex-database-file "/path/to/refs.bib") 69 | 70 | Then use "A" or "W" to append or write to =refs.bib=, respectively. 71 | 72 | ** Adding more sources 73 | Currently these three sources cover nearly all my needs, and it is possible 74 | if you need to add more sources. 75 | 76 | Basically, you need to implement following five functions(if you're willing, 77 | I think looking the source code is better. The implementation is easy!): 78 | #+BEGIN_SRC elisp 79 | (defun gscholar-bibtex-SourceName-search-results (query) 80 | "In the body, call `gscholar-bibtex--url-retrieve-as-string' to return a string 81 | containing query results" 82 | body) 83 | 84 | (defun gscholar-bibtex-SourceName-titles (buffer-content) 85 | "Given the `buffer-content', return the list of titles" 86 | body) 87 | 88 | (defun gscholar-bibtex-SourceName-subtitles (buffer-content) 89 | "Given the `buffer-content', return the list of subtitles" 90 | body) 91 | 92 | (defun gscholar-bibtex-SourceName-bibtex-urls (buffer-content) 93 | "Given the `buffer-content', return the list of urls(or maybe other 94 | feature) of the BibTeX entries, which would be fed to the next function" 95 | body) 96 | 97 | (defun gscholar-bibtex-SourceName-bibtex-content (arg) 98 | "Given the url(or other feature) of a BibTeX entry, return the entry as string. 99 | Also call `gscholar-bibtex--url-retrieve-as-string' for convenience" 100 | body) 101 | #+END_SRC 102 | 103 | Then you need to add a line: 104 | : (gscholar-bibtex-install-source "Source Name" 'SourceName) 105 | 106 | You should put this line somewhere near the end of `gscholar-bibtex.el', 107 | where you could find several `gscholar-bibtex-install-source' lines. 108 | 109 | That's all. Enjoy hacking^_^ 110 | -------------------------------------------------------------------------------- /gscholar-bibtex.el: -------------------------------------------------------------------------------- 1 | ;;; gscholar-bibtex.el --- Retrieve BibTeX from Google Scholar and other online sources(ACM, IEEE, DBLP) 2 | 3 | ;; Copyright (C) 2014 Junpeng Qiu 4 | 5 | ;; Author: Junpeng Qiu 6 | ;; Keywords: extensions 7 | ;; Version: 0.3.1 8 | 9 | ;; This program is free software; you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with this program. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;; * gscholar bibtex 25 | 26 | ;; Retrieve BibTeX entries from Google Scholar, ACM Digital Library, IEEE Xplore 27 | ;; and DBLP by your query. All in Emacs Lisp! 28 | 29 | ;; *UPDATE*: ACM Digital Library, IEEE Xplore, and DBLP are now supported though 30 | ;; the package name doesn't suggest that. 31 | ;; ** Basic usage 32 | ;; Without package.el: 33 | ;; (add-to-list 'load-path "/path/to/gscholar-bibtex.el") 34 | ;; (require 'gscholar-bibtex) 35 | 36 | ;; With package.el: install via melpa! 37 | 38 | ;; To use, simply call 39 | ;; M-x gscholar-bibtex 40 | 41 | ;; Choose a source, then enter your query and select the results. 42 | 43 | ;; Available commands in `gscholar-bibtex-mode', i.e., in the window of search 44 | ;; results: 45 | ;; - n/p: next/previous 46 | ;; - TAB: show BibTeX entry for current search result 47 | ;; - A/W: append/write to `gscholar-bibtex-database-file' (see later) 48 | ;; - a/w: append/write to a file 49 | ;; - c: copy the current BibTeX entry 50 | ;; - x: close BibTeX entry window 51 | ;; - q: quit 52 | 53 | ;; ** Sources 54 | ;; By default, I enable all sources(Google Scholar, ACM Digital Library, IEEE 55 | ;; Xplore and DBLP). If you don't want to enable some of them, you could call 56 | ;; M-x gscholar-bibtex-turn-off-sources 57 | 58 | ;; Similarly, if you want to enable some of them, you could call 59 | ;; M-x gscholar-bibtex-turn-on-sources 60 | 61 | ;; To keep the configuration in your init file, you could use the following 62 | ;; format(*NOT* real code): 63 | ;; (gscholar-bibtex-source-on-off action source-name) 64 | 65 | ;; Possible values: 66 | ;; - action: :on or :off 67 | ;; - source-name: "Google Scholar", "ACM Digital Library", "IEEE Xplore" or "DBLP" 68 | 69 | ;; Say if you want to disable "IEEE Xplore", use the following code: 70 | ;; (gscholar-bibtex-source-on-off :off "IEEE Xplore") 71 | 72 | ;; ** Default source 73 | ;; If you have a preferred source, you can set it as default so you don't have to 74 | ;; type the name to select the source every time you call `gscholar-bibtex'. Say 75 | ;; if you want to set "Google Scholar" as default: 76 | ;; (setq gscholar-bibtex-default-source "Google Scholar") 77 | 78 | ;; Note that in order to make it work, you have to make sure the source name is 79 | ;; correct and you don't disable the source that you set as default, otherwise 80 | ;; the default source setting has no effect. Besides, if you only have one source 81 | ;; enabled, then the enabled source automatically becomes the default, regardless 82 | ;; of the value of `gscholar-bibtex-default-source'. 83 | 84 | ;; ** Configuring `gscholar-bibtex-database-file' 85 | ;; If you have a master BibTeX file, say refs.bib, as database, and want to 86 | ;; append/write the BibTeX entry to refs.bib without being asked for a 87 | ;; filename to be written every time, you can set 88 | ;; `gscholar-bibtex-database-file': 89 | ;; (setq gscholar-bibtex-database-file "/path/to/refs.bib") 90 | 91 | ;; Then use "A" or "W" to append or write to refs.bib, respectively. 92 | 93 | ;; ** Adding more sources 94 | ;; Currently these three sources cover nearly all my needs, and it is possible 95 | ;; if you need to add more sources. 96 | 97 | ;; Basically, you need to implement following five functions(if you're willing, 98 | ;; I think looking the source code is better. The implementation is easy!): 99 | ;; #+BEGIN_SRC elisp 100 | ;; (defun gscholar-bibtex-SourceName-search-results (query) 101 | ;; "In the body, call `gscholar-bibtex--url-retrieve-as-string' to return a string 102 | ;; containing query results" 103 | ;; body) 104 | 105 | ;; (defun gscholar-bibtex-SourceName-titles (buffer-content) 106 | ;; "Given the string `buffer-content', return the list of titles" 107 | ;; body) 108 | 109 | ;; (defun gscholar-bibtex-SourceName-subtitles (buffer-content) 110 | ;; "Given the string `buffer-content', return the list of subtitles" 111 | ;; body) 112 | 113 | ;; (defun gscholar-bibtex-SourceName-bibtex-urls (buffer-content) 114 | ;; "Given the string `buffer-content', return the list of urls(or maybe other 115 | ;; feature) of the BibTeX entries, which would be fed to the next function" 116 | ;; body) 117 | 118 | ;; (defun gscholar-bibtex-SourceName-bibtex-content (arg) 119 | ;; "Given the url(or other feature) of a BibTeX entry, return the entry as string. 120 | ;; Also call `gscholar-bibtex--url-retrieve-as-string' for convenience" 121 | ;; body) 122 | ;; #+END_SRC 123 | 124 | ;; Then you need to add a line: 125 | ;; (gscholar-bibtex-install-source "Source Name" 'SourceName) 126 | 127 | ;; You should put this line somewhere near the end of `gscholar-bibtex.el', 128 | ;; where you could find several `gscholar-bibtex-install-source' lines. 129 | 130 | ;; That's all. Enjoy hacking^_^ 131 | 132 | ;;; Code: 133 | 134 | (require 'bibtex) 135 | (require 'xml) 136 | (require 'url) 137 | (require 'json) 138 | 139 | (defgroup gscholar-bibtex nil 140 | "Retrieve BibTeX from Google Scholar and other online sources(ACM, IEEE, DBLP)." 141 | :group 'bibtex) 142 | 143 | (defconst gscholar-bibtex-version "0.3.1" 144 | "`gscholar-bibtex' version number.") 145 | 146 | (defvar gscholar-bibtex-caller-buffer nil 147 | "Buffer that calls `gscholar-bibtex'.") 148 | 149 | (defvar gscholar-bibtex-urls-cache nil 150 | "Cache for all the urls of BibTeX entries.") 151 | 152 | (defvar gscholar-bibtex-entries-cache nil 153 | "Cache for the retrieved BibTeX entries.") 154 | 155 | (defvar gscholar-bibtex-database-file nil 156 | "Default BibTeX database file.") 157 | 158 | (defconst gscholar-bibtex-item-height 3 159 | "The height for each item.") 160 | 161 | (defvar gscholar-bibtex-available-sources nil 162 | "Avaiable sources for query.") 163 | 164 | (defvar gscholar-bibtex-enabled-sources nil 165 | "List of enabled sources.") 166 | 167 | (defvar gscholar-bibtex-disabled-sources nil 168 | "List of disabled sources.") 169 | 170 | (defvar gscholar-bibtex-selected-source nil 171 | "Currently selected source.") 172 | 173 | (defvar gscholar-bibtex-default-source nil 174 | "Default source name.") 175 | 176 | (defconst gscholar-bibtex-result-buffer-name "*gscholar-bibtex Search Results*" 177 | "Buffer name for Google Scholar search results.") 178 | 179 | (defconst gscholar-bibtex-entry-buffer-name "*BibTeX entry*" 180 | "Buffer name for BibTeX entry.") 181 | 182 | (defconst gscholar-bibtex-user-agent-string 183 | "Mozilla/5.0 (X11; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0" 184 | "User agent for `gscholar-bibtex'.") 185 | 186 | (defconst gscholar-bibtex-function-suffixes-alist 187 | '((:search-results . "search-results") 188 | (:titles . "titles") 189 | (:subtitles . "subtitles") 190 | (:bibtex-urls . "bibtex-urls") 191 | (:bibtex-content . "bibtex-content"))) 192 | 193 | (defconst gscholar-bibtex-help 194 | (let ((help-message "[/

] next/previous; [] show BibTeX entry; [/] append/write to database;\ 195 | [/] append/write to file; [] copy entry; [] close BibTeX entry window; [] quit;")) 196 | (while (string-match "<\\([a-zA-Z]+\\)>" help-message) 197 | (setq help-message 198 | (replace-match 199 | (propertize (match-string 1 help-message) 'face 'font-lock-type-face) 200 | t t help-message))) 201 | help-message) 202 | "Help string for `gscholar-bibtex'.") 203 | 204 | ;; Face related 205 | (defface gscholar-bibtex-title 206 | '((t (:height 1.4 :foreground "light sea green"))) 207 | "Face for title" 208 | :group 'gscholar-bibtex) 209 | 210 | (defface gscholar-bibtex-subtitle 211 | '((t (:height 1.0))) 212 | "Face for subtitle" 213 | :group 'gscholar-bibtex) 214 | 215 | (defconst gcholar-bibtex-highlight-item-overlay 216 | (let ((ov (make-overlay 1 1))) 217 | (overlay-put ov 'face 'highlight) 218 | ov) 219 | "Overlay for item highlight.") 220 | 221 | (defun gscholar-bibtex--move-to-line (N) 222 | (goto-char (point-min)) 223 | (forward-line (1- N))) 224 | 225 | (defun gscholar-bibtex-prettify-title (s) 226 | (propertize (or s "") 'face 'gscholar-bibtex-title)) 227 | 228 | (defun gscholar-bibtex-prettify-subtitle (s) 229 | (propertize (or s "") 'face 'gscholar-bibtex-subtitle)) 230 | 231 | (defun gscholar-bibtex-highlight-current-item-hook () 232 | (save-excursion 233 | (let* ((line (gscholar-bibtex--current-beginning-line)) 234 | (beg (progn (gscholar-bibtex--move-to-line line) (point))) 235 | (end (progn (gscholar-bibtex--move-to-line (+ line 3)) (point)))) 236 | (move-overlay gcholar-bibtex-highlight-item-overlay beg end 237 | (current-buffer))))) 238 | 239 | ;; Major mode 240 | (defvar gscholar-bibtex-mode-map 241 | (let ((map (make-sparse-keymap))) 242 | (define-key map "n" 'gscholar-bibtex-next-item) 243 | (define-key map "p" 'gscholar-bibtex-previous-item) 244 | (define-key map (kbd "") 'gscholar-bibtex-retrieve-and-show-bibtex) 245 | (define-key map "A" 'gscholar-bibtex-append-bibtex-to-database) 246 | (define-key map "W" 'gscholar-bibtex-write-bibtex-to-database) 247 | (define-key map "a" 'gscholar-bibtex-append-bibtex-to-file) 248 | (define-key map "w" 'gscholar-bibtex-write-bibtex-to-file) 249 | (define-key map "c" 'gscholar-bibtex-copy-bibtex-entry) 250 | (define-key map "x" 'gscholar-bibtex-quit-entry-window) 251 | (define-key map "q" 'gscholar-bibtex-quit-gscholar-window) 252 | map)) 253 | 254 | (define-derived-mode gscholar-bibtex-mode fundamental-mode "gscholar-bibtex" 255 | (setq buffer-read-only t) 256 | (add-hook 'pre-command-hook 'gscholar-bibtex-show-help nil t) 257 | (add-hook 'post-command-hook 'gscholar-bibtex-highlight-current-item-hook 258 | nil t)) 259 | 260 | (defun gscholar-bibtex-show-help () 261 | (message "%s" gscholar-bibtex-help)) 262 | 263 | (defun gscholar-bibtex-guard () 264 | (unless (eq major-mode 'gscholar-bibtex-mode) 265 | (error "Error: you are not in `gscholar-bibtex-mode'!"))) 266 | 267 | (defun gscholar-bibtex--string-cleanup (str) 268 | (while (string-match "\\`^\n+\\|^\\s-+\\|\\s-+$\\|\n+\\|\r+\\|^\r\\'" str) 269 | (setq str (replace-match "" t t str))) 270 | (replace-regexp-in-string "[\r\n\t ]+" " " str)) 271 | 272 | (defun gscholar-bibtex--current-beginning-line () 273 | (1+ (* (gscholar-bibtex--current-index) gscholar-bibtex-item-height))) 274 | 275 | (defun gscholar-bibtex--current-index () 276 | (let ((line-number (+ (line-number-at-pos) 277 | (if (= (point) (point-max)) 278 | -1 0)))) 279 | (/ (1- line-number) gscholar-bibtex-item-height))) 280 | 281 | (defun gscholar-bibtex--delete-response-header () 282 | (ignore-errors 283 | (save-match-data 284 | (goto-char (point-min)) 285 | (delete-region (point-min) 286 | (1+ (re-search-forward "^$" nil t))) 287 | (goto-char (point-min))))) 288 | 289 | (defun gscholar-bibtex--replace-html-entities (str) 290 | (let ((retval str) 291 | (pair-list 292 | '(("&" . "&") 293 | ("…" . "...") 294 | (""" . "\"") 295 | ("&#[0-9]*;" . 296 | (lambda (match) 297 | (format "%c" (string-to-number (substring match 2 -1)))))))) 298 | (dolist (elt pair-list retval) 299 | (setq retval (replace-regexp-in-string (car elt) (cdr elt) retval))))) 300 | 301 | (defun gscholar-bibtex--html-value-cleanup (s) 302 | (gscholar-bibtex--string-cleanup 303 | (gscholar-bibtex--replace-html-entities 304 | (replace-regexp-in-string "<.*?>" "" s)))) 305 | 306 | (defun gscholar-bibtex--xml-child (children) 307 | (pcase-let ((`(,child) children)) child)) 308 | 309 | (defun gscholar-bibtex--xml-node-child (node) 310 | (gscholar-bibtex--xml-child 311 | (xml-node-children node))) 312 | 313 | (defun gscholar-bibtex--xml-get-child (node child-name) 314 | (gscholar-bibtex--xml-child 315 | (xml-get-children node child-name))) 316 | 317 | (defun gscholar-bibtex--url-retrieve-as-buffer (url) 318 | (let* ((url-request-extra-headers 319 | (append url-request-extra-headers `(("User-Agent" . ,gscholar-bibtex-user-agent-string)))) 320 | (response-buffer (url-retrieve-synchronously url))) 321 | (with-current-buffer response-buffer 322 | (gscholar-bibtex--delete-response-header) 323 | (set-buffer-multibyte t)) 324 | response-buffer)) 325 | 326 | (defun gscholar-bibtex--url-retrieve-as-string (url) 327 | (let ((response-buffer (gscholar-bibtex--url-retrieve-as-buffer url)) 328 | retval) 329 | (with-current-buffer response-buffer 330 | (setq retval (buffer-string))) 331 | (kill-buffer response-buffer) 332 | retval)) 333 | 334 | (defun gscholar-bibtex-re-search (buffer-content surrounding-regexp subexp-count) 335 | (save-match-data 336 | (with-temp-buffer 337 | (insert buffer-content) 338 | (let (retval) 339 | (goto-char (point-min)) 340 | (while (re-search-forward surrounding-regexp nil t) 341 | (push (gscholar-bibtex--html-value-cleanup 342 | (match-string-no-properties subexp-count)) retval)) 343 | (nreverse retval))))) 344 | 345 | (defun gscholar-bibtex-next-item () 346 | (interactive) 347 | (gscholar-bibtex-guard) 348 | (gscholar-bibtex--move-to-line (+ (gscholar-bibtex--current-beginning-line) 349 | gscholar-bibtex-item-height))) 350 | 351 | (defun gscholar-bibtex-previous-item () 352 | (interactive) 353 | (gscholar-bibtex-guard) 354 | (gscholar-bibtex--move-to-line (- (gscholar-bibtex--current-beginning-line) 355 | gscholar-bibtex-item-height))) 356 | 357 | (defun gscholar-bibtex-retrieve-and-show-bibtex () 358 | (interactive) 359 | (gscholar-bibtex-guard) 360 | (let* ((index (gscholar-bibtex--current-index)) 361 | (bibtex-entry 362 | (progn (when (string= "" (elt gscholar-bibtex-entries-cache index)) 363 | (aset gscholar-bibtex-entries-cache index 364 | (gscholar-bibtex-dispatcher 365 | :bibtex-content 366 | (nth index gscholar-bibtex-urls-cache)))) 367 | (elt gscholar-bibtex-entries-cache index))) 368 | (entry-buffer (get-buffer-create gscholar-bibtex-entry-buffer-name)) 369 | (entry-window (get-buffer-window entry-buffer)) 370 | (gscholar-window (selected-window))) 371 | (with-current-buffer entry-buffer 372 | (erase-buffer) 373 | (insert bibtex-entry) 374 | (bibtex-mode) 375 | ;; Have to manually call this to set `bibtex-entry-head', otherwise 376 | ;; `bibtex-parse-buffers-stealthily' will throw some errors since the our 377 | ;; BibTeX buffer is not associated with an existing file:-( 378 | (bibtex-set-dialect) 379 | (goto-char (point-min))) 380 | (unless entry-window 381 | (let ((entry-window (select-window (split-window-below)))) 382 | (set-window-buffer entry-window entry-buffer) 383 | (display-buffer-record-window 'window entry-window entry-buffer) 384 | (set-window-prev-buffers entry-window nil)) 385 | (select-window gscholar-window))) 386 | (gscholar-bibtex-show-help)) 387 | 388 | (defun gscholar-bibtex--write-bibtex-to-database-impl (&optional append) 389 | (gscholar-bibtex-guard) 390 | (gscholar-bibtex-retrieve-and-show-bibtex) 391 | (unless gscholar-bibtex-database-file 392 | (setq gscholar-bibtex-database-file 393 | (read-file-name "gscholar-bibtex database file:"))) 394 | (if gscholar-bibtex-database-file 395 | (progn 396 | (with-current-buffer (get-buffer gscholar-bibtex-entry-buffer-name) 397 | (write-region nil nil gscholar-bibtex-database-file append)) 398 | (message "%s BibTeX entry to %s" (if append "Append" "Write") 399 | gscholar-bibtex-database-file)) 400 | (error "Please set `gscholar-bibtex-database-file' first"))) 401 | 402 | (defun gscholar-bibtex-append-bibtex-to-database () 403 | (interactive) 404 | (gscholar-bibtex--write-bibtex-to-database-impl t)) 405 | 406 | (defun gscholar-bibtex-write-bibtex-to-database () 407 | (interactive) 408 | (gscholar-bibtex--write-bibtex-to-database-impl)) 409 | 410 | (defun gscholar-bibtex--write-bibtex-to-file-impl (prompt &optional append) 411 | (gscholar-bibtex-guard) 412 | (gscholar-bibtex-retrieve-and-show-bibtex) 413 | (let ((filename (read-file-name prompt))) 414 | (with-current-buffer (get-buffer gscholar-bibtex-entry-buffer-name) 415 | (write-region nil nil filename append)) 416 | (message "%s BibTeX entry to %s" (if append "Append" "Write") filename))) 417 | 418 | (defun gscholar-bibtex-append-bibtex-to-file () 419 | (interactive) 420 | (gscholar-bibtex--write-bibtex-to-file-impl "Append BibTeX entry to file: " t)) 421 | 422 | (defun gscholar-bibtex-write-bibtex-to-file () 423 | (interactive) 424 | (gscholar-bibtex--write-bibtex-to-file-impl "Write BibTeX entry to file: ")) 425 | 426 | (defun gscholar-bibtex-copy-bibtex-entry () 427 | (interactive) 428 | (gscholar-bibtex-retrieve-and-show-bibtex) 429 | (with-current-buffer (get-buffer gscholar-bibtex-entry-buffer-name) 430 | (kill-new (buffer-string)) 431 | (message "The current BiBTeX entry copied.") 432 | (sit-for 2) 433 | (gscholar-bibtex-show-help))) 434 | 435 | (defun gscholar-bibtex-quit-entry-window () 436 | (interactive) 437 | (gscholar-bibtex-guard) 438 | (let ((gscholar-window (selected-window)) 439 | (entry-window (get-buffer-window gscholar-bibtex-entry-buffer-name))) 440 | (when entry-window 441 | (quit-restore-window entry-window 'kill) 442 | (select-window gscholar-window)))) 443 | 444 | (defun gscholar-bibtex-quit-gscholar-window () 445 | (interactive) 446 | (gscholar-bibtex-guard) 447 | (let ((gscholar-window (selected-window)) 448 | (entry-window (get-buffer-window gscholar-bibtex-entry-buffer-name)) 449 | (caller-window (get-buffer-window gscholar-bibtex-caller-buffer))) 450 | (gscholar-bibtex-quit-entry-window) 451 | (quit-restore-window gscholar-window 'kill) 452 | (if caller-window 453 | (select-window caller-window))) 454 | (message "")) 455 | 456 | (defun gscholar-bibtex-install-source (source-name source-symbol) 457 | (let ((retval t)) 458 | (dolist (pair gscholar-bibtex-function-suffixes-alist retval) 459 | (unless 460 | (fboundp 461 | (gscholar-bibtex--get-dispatch-func-name (car pair) source-symbol)) 462 | (setq retval nil))) 463 | (unless retval 464 | (error 465 | "Installation failed! You need to define all necessary functions!")) 466 | (push `(,source-name . ,source-symbol) gscholar-bibtex-available-sources))) 467 | 468 | (defun gscholar-bibtex--get-dispatch-func-name (kind source-symbol) 469 | (intern 470 | (concat 471 | "gscholar-bibtex-" 472 | (symbol-name source-symbol) 473 | "-" 474 | (assoc-default kind gscholar-bibtex-function-suffixes-alist)))) 475 | 476 | ;;; dispatcher 477 | (defun gscholar-bibtex-dispatcher (kind arg) 478 | (funcall 479 | (gscholar-bibtex--get-dispatch-func-name 480 | kind 481 | (assoc-default gscholar-bibtex-selected-source 482 | gscholar-bibtex-enabled-sources)) 483 | arg)) 484 | 485 | (defun gscholar-bibtex--get-list-symbol-pair (action) 486 | (let* ((alist '((:on . ("disabled" . "enabled")) 487 | (:off . ("enabled" . "disabled")))) 488 | (names (assoc-default action alist)) 489 | (build-name (lambda (s) 490 | (intern (concat "gscholar-bibtex-" s "-sources"))))) 491 | `(,(funcall build-name (car names)) . ,(funcall build-name (cdr names))))) 492 | 493 | ;;;###autoload 494 | (defun gscholar-bibtex-source-on-off (action source-name) 495 | (let* ((prompt (if (eq action :on) "available" "enabled")) 496 | (symbol-pair (gscholar-bibtex--get-list-symbol-pair action)) 497 | (source-list (car symbol-pair)) 498 | (dest-list (cdr symbol-pair)) 499 | (source-pair (assoc source-name (symbol-value source-list)))) 500 | (if source-pair 501 | (progn 502 | (set source-list 503 | (remove source-pair (symbol-value source-list))) 504 | (push source-pair (symbol-value dest-list))) 505 | (message 506 | (concat "Please choose from the " prompt " sources!"))))) 507 | 508 | (defun gscholar-bibtex--source-on-off-interactive-impl (action) 509 | (let ((source-list (car (gscholar-bibtex--get-list-symbol-pair action))) 510 | source-name) 511 | (while (and (symbol-value source-list) 512 | (not 513 | (string= "" (setq source-name 514 | (completing-read 515 | "Source[empty to exit]:" 516 | (symbol-value source-list)))))) 517 | (gscholar-bibtex-source-on-off action source-name)))) 518 | 519 | ;;; acm 520 | (defun gscholar-bibtex-acm-search-results (query) 521 | (let* ((url-request-method "POST") 522 | (url-request-extra-headers 523 | '(("Content-Type" . "application/x-www-form-urlencoded"))) 524 | (url-request-data 525 | (mapconcat (lambda (arg) 526 | (concat (url-hexify-string (car arg)) 527 | "=" 528 | (url-hexify-string (cdr arg)))) 529 | `(("query" . ,(replace-regexp-in-string " " "\+" query))) 530 | "&"))) 531 | (gscholar-bibtex--url-retrieve-as-string 532 | "http://dl.acm.org/results.cfm?h=1"))) 533 | (defun gscholar-bibtex-acm-titles (buffer-content) 534 | (gscholar-bibtex-re-search 535 | buffer-content 536 | "]*?>\\(.*?\\)" 1)) 537 | 538 | (defun gscholar-bibtex-acm-subtitles (buffer-content) 539 | (gscholar-bibtex-re-search 540 | buffer-content 541 | "

\\([[:print:][:space:]]*?\\)
" 1)) 542 | 543 | (defun gscholar-bibtex-acm-bibtex-urls (buffer-content) 544 | (mapcar 545 | (lambda (href) 546 | (let ((retval href) 547 | (case-fold-search t) 548 | (pair-list '(("coll=DL" . "expformat=bibtex") 549 | ("id" . "parent_id") 550 | ("\\." . "&id=") 551 | ("cfm" . "downformats.cfm")))) 552 | (dolist (pair pair-list retval) 553 | (setq retval 554 | (replace-regexp-in-string (car pair) (cdr pair) retval))))) 555 | (gscholar-bibtex-re-search 556 | buffer-content 557 | "\\|\r" . "") 610 | ("\n\n+" . "\n") 611 | ("\n *" . "\n "))) 612 | (retval (gscholar-bibtex--url-retrieve-as-string 613 | "http://ieeexplore.ieee.org/xpl/downloadCitations"))) 614 | (dolist (pair pair-list retval) 615 | (setq retval (replace-regexp-in-string (car pair) (cdr pair) retval))))) 616 | 617 | ;;; Google Scholar 618 | (defun gscholar-bibtex-google-scholar-search-results (query) 619 | (let* ((url-request-method "GET") 620 | (system-time-locale "C") 621 | ;; Fabricate a cookie with a random ID that expires in an hour. 622 | (random-id (format "%016x" (random (expt 16 16)))) 623 | (expiration (format-time-string "%a, %d %b %Y %H:%M:%S.00 %Z" 624 | (time-add (current-time) 625 | (seconds-to-time 3600)) t)) 626 | (my-cookie (mapconcat #'identity 627 | (list (format "GSP=ID=%s:CF=4" random-id) 628 | (format "expires=%s" expiration) 629 | "path=/" 630 | "domain=scholar.google.com") 631 | "; ")) 632 | (url-current-object 633 | (url-generic-parse-url "http://scholar.google.com"))) 634 | (url-cookie-handle-set-cookie my-cookie) 635 | (gscholar-bibtex--url-retrieve-as-string 636 | (concat "https://scholar.google.com/scholar?q=" 637 | ;; To prepare the query string, we need to: 638 | ;; 1. Remove some extraneous puncutation. 639 | ;; 2. Hex-encode it. 640 | ;; 3. Convert encoded spaces to + 641 | ;; If we encode spaces as + first, url-hexify-string 642 | ;; hex-encodes the + symbols, and they are not interpreted 643 | ;; properly as spaces on the server. 644 | (replace-regexp-in-string "%20" "\+" 645 | (url-hexify-string 646 | (replace-regexp-in-string "[:,]" "" query))))))) 647 | 648 | (defun gscholar-bibtex-google-scholar-bibtex-urls (buffer-content) 649 | (gscholar-bibtex-re-search buffer-content "\\(/scholar\.bib.*?\\)\"" 1)) 650 | 651 | (defun gscholar-bibtex-google-scholar-titles (buffer-content) 652 | (gscholar-bibtex-re-search buffer-content "
\\(.*?\\)" 1)) 653 | 654 | (defun gscholar-bibtex-google-scholar-subtitles (buffer-content) 655 | (gscholar-bibtex-re-search 656 | buffer-content 657 | "
\\(.*?\\)
" 1)) 658 | 659 | (defun gscholar-bibtex-google-scholar-bibtex-content (bibtex-url) 660 | (gscholar-bibtex--url-retrieve-as-string 661 | (concat "https://scholar.google.com" bibtex-url))) 662 | 663 | ;;; DBLP 664 | (defun gscholar-bibtex-dblp-search-results (query) 665 | (let* ((url-request-method "GET") 666 | (response-buffer (gscholar-bibtex--url-retrieve-as-buffer 667 | (concat "http://dblp.uni-trier.de/search/publ/api?" 668 | (url-build-query-string 669 | `((q ,query) 670 | (format xml))))))) 671 | (with-current-buffer response-buffer 672 | (set-buffer-multibyte t)) 673 | (prog1 674 | (pcase-let ((`(,(and result `(result . ,_))) (xml-parse-region nil nil response-buffer))) 675 | (mapcar (lambda (hit) 676 | (gscholar-bibtex--xml-get-child hit 'info)) 677 | (xml-get-children (gscholar-bibtex--xml-get-child result 'hits) 'hit))) 678 | (kill-buffer response-buffer)))) 679 | 680 | (defun gscholar-bibtex-dblp-titles (search-results) 681 | (mapcar (lambda (info) 682 | (gscholar-bibtex--xml-node-child 683 | (gscholar-bibtex--xml-get-child info 'title))) 684 | search-results)) 685 | 686 | (defun gscholar-bibtex-dblp-subtitles (search-results) 687 | (mapcar (lambda (info) 688 | (mapconcat #'gscholar-bibtex--xml-node-child 689 | (xml-get-children (gscholar-bibtex--xml-get-child info 'authors) 'author) 690 | ", ")) 691 | search-results)) 692 | 693 | (defun gscholar-bibtex-dblp-bibtex-urls (search-results) 694 | (mapcar (lambda (info) 695 | (gscholar-bibtex--xml-node-child 696 | (gscholar-bibtex--xml-get-child info 'url))) 697 | search-results)) 698 | 699 | (defun gscholar-bibtex-dblp-bibtex-content (html-url) 700 | (string-match "/rec/" html-url) 701 | (gscholar-bibtex--url-retrieve-as-string 702 | (replace-match "/rec/bib2/" t t html-url))) 703 | 704 | ;;;###autoload 705 | (defun gscholar-bibtex-turn-on-sources () 706 | (interactive) 707 | (gscholar-bibtex--source-on-off-interactive-impl :on)) 708 | 709 | ;;;###autoload 710 | (defun gscholar-bibtex-turn-off-sources () 711 | (interactive) 712 | (gscholar-bibtex--source-on-off-interactive-impl :off)) 713 | 714 | (defun gscholar-bibtex-select-source () 715 | (if (= 1 (length gscholar-bibtex-enabled-sources)) 716 | (setq gscholar-bibtex-selected-source 717 | (caar gscholar-bibtex-enabled-sources)) 718 | (let* ((default-source (assoc 719 | gscholar-bibtex-default-source 720 | gscholar-bibtex-enabled-sources)) 721 | (source-prompt (if default-source 722 | (concat "Select a source[default " 723 | gscholar-bibtex-default-source 724 | "]: ") 725 | "Select a source: ")) 726 | (selected-source 727 | (completing-read source-prompt 728 | gscholar-bibtex-enabled-sources))) 729 | (setq gscholar-bibtex-selected-source 730 | (if (string= "" selected-source) 731 | gscholar-bibtex-default-source 732 | selected-source)))) 733 | (if (not (assoc gscholar-bibtex-selected-source 734 | gscholar-bibtex-enabled-sources)) 735 | (error "Please select an installed source!") 736 | gscholar-bibtex-selected-source)) 737 | 738 | ;;;###autoload 739 | (defun gscholar-bibtex (&optional source query) 740 | "Look up on a bibliographic SOURCE such as Google Scholar for the given QUERY. 741 | When called interactively, prompts for SOURCE and QUERY string. 742 | Can be called programmatically with SOURCE (to prompt for QUERY 743 | only or SOURCE and QUERY for non-interactive lookup." 744 | (interactive) 745 | (setq gscholar-bibtex-selected-source (or source (gscholar-bibtex-select-source))) 746 | (let* ((query (or query (read-string 747 | (concat "Query[" gscholar-bibtex-selected-source "]: ")))) 748 | (search-results (gscholar-bibtex-dispatcher :search-results query)) 749 | (titles (gscholar-bibtex-dispatcher :titles search-results)) 750 | (subtitles (gscholar-bibtex-dispatcher :subtitles search-results)) 751 | (gscholar-buffer 752 | (get-buffer-create gscholar-bibtex-result-buffer-name))) 753 | (setq gscholar-bibtex-caller-buffer (current-buffer)) 754 | (setq gscholar-bibtex-urls-cache 755 | (gscholar-bibtex-dispatcher :bibtex-urls search-results)) 756 | (setq gscholar-bibtex-entries-cache 757 | (make-vector (length gscholar-bibtex-urls-cache) "")) 758 | (pop-to-buffer gscholar-buffer) 759 | (setq buffer-read-only nil) 760 | (erase-buffer) 761 | (goto-char (point-min)) 762 | (dotimes (i (length titles)) 763 | (insert (gscholar-bibtex-prettify-title (concat "* " (nth i titles)))) 764 | (newline-and-indent) 765 | (insert " " 766 | (gscholar-bibtex-prettify-subtitle (nth i subtitles)) "\n\n")) 767 | (goto-char (point-min)) 768 | (gscholar-bibtex-mode) 769 | (gscholar-bibtex-show-help))) 770 | 771 | ;; install sources 772 | (gscholar-bibtex-install-source "DBLP" 'dblp) 773 | (gscholar-bibtex-install-source "IEEE Xplore" 'ieee) 774 | (gscholar-bibtex-install-source "ACM Digital Library" 'acm) 775 | (gscholar-bibtex-install-source "Google Scholar" 'google-scholar) 776 | ;; initalize 777 | (setq gscholar-bibtex-disabled-sources gscholar-bibtex-available-sources) 778 | ;; enable all 779 | (gscholar-bibtex-source-on-off :on "DBLP") 780 | (gscholar-bibtex-source-on-off :on "IEEE Xplore") 781 | (gscholar-bibtex-source-on-off :on "ACM Digital Library") 782 | (gscholar-bibtex-source-on-off :on "Google Scholar") 783 | 784 | ;; Remove byte compilation warnings 785 | (defvar evil-emacs-state-modes) 786 | (eval-after-load "evil" 787 | '(add-to-list 'evil-emacs-state-modes 'gscholar-bibtex-mode)) 788 | 789 | (provide 'gscholar-bibtex) 790 | ;;; gscholar-bibtex.el ends here 791 | --------------------------------------------------------------------------------