diff --git a/bibtex-completion.el b/bibtex-completion.el index cac8f3e..0305641 100644 --- a/bibtex-completion.el +++ b/bibtex-completion.el @@ -517,7 +517,10 @@ reparsed whereas the other files in FILES were up-to-date." ;; Insert an empty field so we can discard the crossref info if needed: (append entry (cl-acons "" "" - (gethash (downcase crossref) entry-hash)))) + (cl-remove-if + (lambda (field) + (string= (car field) "=has-note")) + (gethash (downcase crossref) entry-hash))))) entry)))) else ;; The file was not reparsed. @@ -535,7 +538,10 @@ reparsed whereas the other files in FILES were up-to-date." ;; Discard crossref info and resolve crossref again: (append (--take-while (> (length (car it)) 0) entry-alist) (cl-acons "" "" - (gethash (downcase crossref) entry-hash))))) + (cl-remove-if + (lambda (field) + (string= (car field) "=has-note")) + (gethash (downcase crossref) entry-hash)))))) entry))))) (defun bibtex-completion-make-entry-hash (files reparsed-files) @@ -597,17 +603,19 @@ If HT-STRINGS is provided it is assumed to be a hash table." (cons (downcase (car it)) (cdr it))) (bibtex-completion-prepare-entry entry fields))))) -(defun bibtex-completion-get-entry (entry-key) +(defun bibtex-completion-get-entry (entry-key &optional do-not-find-pdf do-not-find-notes) "Given a BibTeX key this function scans all bibliographies -listed in `bibtex-completion-bibliography' and returns an alist of the -record with that key. Fields from crossreferenced entries are -appended to the requested entry." - (let* ((entry (bibtex-completion-get-entry1 entry-key)) +listed in `bibtex-completion-bibliography' and returns an alist +of the record with that key. Fields from crossreferenced entries +are appended to the requested entry. If +DO-NOT-FIND-PDF (resp. DO-NOT-FIND-NOTES) is non-nil, this +function does not attempt to find a PDF (resp. notes) file." + (let* ((entry (bibtex-completion-get-entry1 entry-key do-not-find-pdf do-not-find-notes)) (crossref (bibtex-completion-get-value "crossref" entry)) - (crossref (when crossref (bibtex-completion-get-entry1 crossref)))) + (crossref (when crossref (bibtex-completion-get-entry1 crossref do-not-find-pdf do-not-find-notes)))) (bibtex-completion-remove-duplicated-fields (append entry crossref)))) -(defun bibtex-completion-get-entry1 (entry-key &optional do-not-find-pdf) +(defun bibtex-completion-get-entry1 (entry-key &optional do-not-find-pdf do-not-find-notes) (with-temp-buffer (mapc #'insert-file-contents (bibtex-completion-normalize-bibliography 'bibtex)) @@ -618,69 +626,61 @@ appended to the requested entry." nil t) (let ((entry-type (match-string 1))) (reverse (bibtex-completion-prepare-entry - (parsebib-read-entry entry-type (point) bibtex-completion-string-hash-table) nil do-not-find-pdf))) + (parsebib-read-entry entry-type (point) bibtex-completion-string-hash-table) nil do-not-find-pdf do-not-find-notes))) (progn (display-warning :warning (concat "Bibtex-completion couldn't find entry with key \"" entry-key "\".")) nil)))) -(defun bibtex-completion-find-pdf-in-field (key-or-entry) +(defun bibtex-completion-find-pdf-in-field (entry) "Returns the path of the PDF specified in the field `bibtex-completion-pdf-field' if that file exists. Returns nil if no -file is specified, or if the specified file does not exist, or if -`bibtex-completion-pdf-field' is nil." - (when bibtex-completion-pdf-field - (let* ((entry (if (stringp key-or-entry) - (bibtex-completion-get-entry1 key-or-entry t) - key-or-entry)) - (value (bibtex-completion-get-value bibtex-completion-pdf-field entry))) - (cond - ((not value) nil) ; Field not defined. - ((f-file? value) (list value)) ; A bare full path was found. - ((-any 'f-file? (--map (f-join it (f-filename value)) (-flatten bibtex-completion-library-path))) (-filter 'f-file? (--map (f-join it (f-filename value)) (-flatten bibtex-completion-library-path)))) - (t ; Zotero/Mendeley/JabRef format: - (let ((value (replace-regexp-in-string "\\([^\\]\\);" "\\1\^^" value))) - (cl-loop ; Looping over the files: - for record in (s-split "\^^" value) +file is specified, or if the specified file does not exist." + (let ((value (bibtex-completion-get-value bibtex-completion-pdf-field entry))) + (cond + ((not value) nil) ; Field not defined. + ((f-file? value) (list value)) ; A bare full path was found. + ((-any 'f-file? (--map (f-join it (f-filename value)) (-flatten bibtex-completion-library-path))) (-filter 'f-file? (--map (f-join it (f-filename value)) (-flatten bibtex-completion-library-path)))) + (t ; Zotero/Mendeley/JabRef format: + (let ((value (replace-regexp-in-string "\\([^\\]\\);" "\\1\^^" value))) + (cl-loop ; Looping over the files: + for record in (s-split "\^^" value) ; Replace unescaped colons by field separator: - for record = (replace-regexp-in-string "\\([^\\]\\|^\\):" "\\1\^_" record) + for record = (replace-regexp-in-string "\\([^\\]\\|^\\):" "\\1\^_" record) ; Unescape stuff: - for record = (replace-regexp-in-string "\\\\\\(.\\)" "\\1" record) + for record = (replace-regexp-in-string "\\\\\\(.\\)" "\\1" record) ; Now we can safely split: - for record = (s-split "\^_" record) - for file-name = (nth 0 record) - for path = (or (nth 1 record) "") - for paths = (if (s-match "^[A-Z]:" path) - (list path) ; Absolute Windows path + for record = (s-split "\^_" record) + for file-name = (nth 0 record) + for path = (or (nth 1 record) "") + for paths = (if (s-match "^[A-Z]:" path) + (list path) ; Absolute Windows path ; Something else: - (append - (list - path - (f-join (f-root) path) ; Mendeley #105 - (f-join (f-root) path file-name)) ; Mendeley #105 - (--map (f-join it path) - (-flatten bibtex-completion-library-path)) ; Jabref #100 - (--map (f-join it path file-name) - (-flatten bibtex-completion-library-path)))) ; Jabref #100 - for result = (-first 'f-exists? paths) - if (not (s-blank-str? result)) collect result))))))) - -(defun bibtex-completion-find-pdf-in-library (key-or-entry &optional find-additional) + (append + (list + path + (f-join (f-root) path) ; Mendeley #105 + (f-join (f-root) path file-name)) ; Mendeley #105 + (--map (f-join it path) + (-flatten bibtex-completion-library-path)) ; Jabref #100 + (--map (f-join it path file-name) + (-flatten bibtex-completion-library-path)))) ; Jabref #100 + for result = (-first 'f-exists? paths) + if (not (s-blank-str? result)) collect result)))))) + +(defun bibtex-completion-find-pdf-in-library (key &optional find-additional) "Searches the directories in `bibtex-completion-library-path' -for a PDF whose name is composed of the BibTeX key plus +for a PDF whose name is composed of KEY plus `bibtex-completion-pdf-extension'. The path of the first matching PDF is returned. If FIND-ADDITIONAL is non-nil, the paths of all PDFs whose name -starts with the BibTeX key and ends with +starts with KEY and ends with `bibtex-completion-pdf-extension' are returned instead." - (let* ((key (if (stringp key-or-entry) - key-or-entry - (bibtex-completion-get-value "=key=" key-or-entry))) - (main-pdf (cl-loop - for dir in (-flatten bibtex-completion-library-path) - append (cl-loop - for ext in (-flatten bibtex-completion-pdf-extension) - collect (f-join dir (s-concat key ext)))))) + (let ((main-pdf (cl-loop + for dir in (-flatten bibtex-completion-library-path) + append (cl-loop + for ext in (-flatten bibtex-completion-pdf-extension) + collect (f-join dir (s-concat key ext)))))) (if find-additional (sort ; move main pdf on top of the list if needed (cl-loop @@ -697,7 +697,7 @@ starts with the BibTeX key and ends with (not (member y main-pdf))))) (-flatten (-first 'f-file? main-pdf))))) -(defun bibtex-completion-find-pdf (key-or-entry &optional find-additional) +(defun bibtex-completion-find-pdf (key-or-entry &optional find-additional find-crossref) "Returns the path of the PDF associated with the specified entry. This is either the path(s) specified in the field `bibtex-completion-pdf-field' or, if that does not exist, the @@ -707,19 +707,36 @@ the BibTeX key plus `bibtex-completion-pdf-extension' (or if FIND-ADDITIONAL is non-nil, all PDFs in `bibtex-completion-library-path' whose name starts with the BibTeX key and ends with `bibtex-completion-pdf-extension'). -Returns nil if no PDF is found." - (or (bibtex-completion-find-pdf-in-field key-or-entry) - (bibtex-completion-find-pdf-in-library key-or-entry find-additional))) - -(defun bibtex-completion-prepare-entry (entry &optional fields do-not-find-pdf) +Returns nil if no PDF is found. + +If FIND-CROSSREF is non-nil and the entry has a crossref field, +PDF(s) of the cross-referenced entry are appended." + (let (key entry crossref) + (if (stringp key-or-entry) + (setq key key-or-entry) + (setq entry key-or-entry)) + (append + (or (when bibtex-completion-pdf-field + (bibtex-completion-find-pdf-in-field (or entry + (setq entry (bibtex-completion-get-entry1 key t t))))) + (bibtex-completion-find-pdf-in-library (or key + (bibtex-completion-get-value "=key=" entry)) + find-additional)) + (and find-crossref + (setq crossref (bibtex-completion-get-value "crossref" + (or entry + (bibtex-completion-get-entry1 key t t)))) + (bibtex-completion-find-pdf crossref find-additional))))) + +(defun bibtex-completion-prepare-entry (entry &optional fields do-not-find-pdf do-not-find-notes) "Prepare ENTRY for display. ENTRY is an alist representing an entry as returned by parsebib-read-entry. All the fields not in FIELDS are removed from ENTRY, with the exception of the \"=type=\" and \"=key=\" fields. If FIELDS is empty, all fields are kept. Also add a =has-pdf= and/or =has-note= field, if they exist for ENTRY. If -DO-NOT-FIND-PDF is non-nil, this function does not attempt to -find a PDF file." +DO-NOT-FIND-PDF (resp. DO-NOT-FIND-NOTES) is non-nil, this +function does not attempt to find a PDF (resp. notes) file." (when entry ; entry may be nil, in which case just return nil (let* ((fields (when fields (append fields (list "=type=" "=key=" "=has-pdf=" "=has-note=")))) ; Check for PDF: @@ -728,22 +745,23 @@ find a PDF file." entry)) (entry-key (cdr (assoc "=key=" entry))) ; Check for notes: - (entry (if (or - ;; One note file per entry: - (and bibtex-completion-notes-path - (f-directory? bibtex-completion-notes-path) - (f-file? (f-join bibtex-completion-notes-path - (s-concat entry-key - bibtex-completion-notes-extension)))) - ;; All notes in one file: - (and bibtex-completion-notes-path - (f-file? bibtex-completion-notes-path) - (with-current-buffer (find-file-noselect bibtex-completion-notes-path) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (re-search-forward (format bibtex-completion-notes-key-pattern (regexp-quote entry-key)) nil t)))))) + (entry (if (and (not do-not-find-notes) + (or + ;; One note file per entry: + (and bibtex-completion-notes-path + (f-directory? bibtex-completion-notes-path) + (f-file? (f-join bibtex-completion-notes-path + (s-concat entry-key + bibtex-completion-notes-extension)))) + ;; All notes in one file: + (and bibtex-completion-notes-path + (f-file? bibtex-completion-notes-path) + (with-current-buffer (find-file-noselect bibtex-completion-notes-path) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (re-search-forward (format bibtex-completion-notes-key-pattern (regexp-quote entry-key)) nil t))))))) (cons (cons "=has-note=" bibtex-completion-notes-symbol) entry) entry)) ; Remove unwanted fields: @@ -827,7 +845,7 @@ If multiple PDFs are found for an entry, ask for the one to open using `completion-read'. If FALLBACK-ACTION is non-nil, it is called in case no PDF is found." (dolist (key keys) - (let ((pdf (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs))) + (let ((pdf (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs t))) (cond ((> (length pdf) 1) (let* ((pdf (f-uniquify-alist pdf)) @@ -845,7 +863,7 @@ case no PDF is found." (defun bibtex-completion-open-url-or-doi (keys) "Open the associated URL or DOI in a browser." (dolist (key keys) - (let* ((entry (bibtex-completion-get-entry key)) + (let* ((entry (bibtex-completion-get-entry key t t)) (url (bibtex-completion-get-value "url" entry)) (doi (bibtex-completion-get-value "doi" entry)) (browse-url-browser-function @@ -946,12 +964,12 @@ inside or just after a citation command, only adds KEYS to it." (--map (format "ebib:%s" it) keys))) (defun bibtex-completion-format-citation-org-link-to-PDF (keys) - "Formatter for org-links to PDF. Uses first matching PDF if + "Formatter for org-links to PDF. Uses all matching PDFs if several are available. Entries for which no PDF is available are omitted." (s-join ", " (cl-loop for key in keys - for pdfs = (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs) + for pdfs = (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs t) append (--map (format "[[%s][%s]]" it key) pdfs)))) (defun bibtex-completion-format-citation-org-apa-link-to-PDF (keys) @@ -959,13 +977,13 @@ omitted." format. Uses first matching PDF if several are available." (s-join ", " (cl-loop for key in keys - for entry = (bibtex-completion-get-entry key) + for entry = (bibtex-completion-get-entry key t t) for author = (bibtex-completion-shorten-authors (or (bibtex-completion-get-value "author" entry) (bibtex-completion-get-value "editor" entry))) for year = (or (bibtex-completion-get-value "year" entry) (car (split-string (bibtex-completion-get-value "date" entry "") "-"))) - for pdf = (car (bibtex-completion-find-pdf key)) + for pdf = (car (bibtex-completion-find-pdf key nil t)) if pdf collect (format "[[file:%s][%s (%s)]]" pdf author year) else @@ -992,7 +1010,7 @@ format. Uses first matching PDF if several are available." "Returns a plain text reference in APA format for the publication specified by KEY." (let* - ((entry (bibtex-completion-get-entry key)) + ((entry (bibtex-completion-get-entry key t t)) (ref (pcase (downcase (bibtex-completion-get-value "=type=" entry)) ("article" (s-format @@ -1143,11 +1161,11 @@ defined. Surrounding curly braces are stripped." (funcall 'bibtex-completion-format-citation-default keys))) (defun bibtex-completion-insert-bibtex (keys) - "Insert BibTeX key at point." + "Insert BibTeX entry at point." (insert (s-join "\n" (--map (bibtex-completion-make-bibtex it) keys)))) (defun bibtex-completion-make-bibtex (key) - (let* ((entry (bibtex-completion-get-entry key)) + (let* ((entry (bibtex-completion-get-entry key t t)) (entry-type (bibtex-completion-get-value "=type=" entry))) (format "@%s{%s,\n%s}\n" entry-type key @@ -1158,14 +1176,14 @@ defined. Surrounding curly braces are stripped." unless (member name (append (-map (lambda (it) (if (symbolp it) (symbol-name it) it)) bibtex-completion-no-export-fields) - '("=type=" "=key=" "=has-pdf=" "=has-note=" "crossref"))) + '("=type=" "=key=" "crossref"))) concat (format " %s = {%s},\n" name value))))) (defun bibtex-completion-add-PDF-attachment (keys) "Attach the PDFs of the selected entries where available." (dolist (key keys) - (let ((pdf (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs))) + (let ((pdf (bibtex-completion-find-pdf key bibtex-completion-find-additional-pdfs t))) (if pdf (mapc 'mml-attach-file pdf) (message "No PDF(s) found for this entry: %s" @@ -1212,7 +1230,7 @@ line." (unless (f-exists? path) (insert (s-format bibtex-completion-notes-template-multiple-files 'bibtex-completion-apa-get-value - (bibtex-completion-get-entry key))))) + (bibtex-completion-get-entry key t t))))) ; One file for all notes: (unless (and buffer-file-name (f-same? bibtex-completion-notes-path buffer-file-name)) @@ -1228,7 +1246,7 @@ line." (org-cycle-hide-drawers nil) (bibtex-completion-notes-mode 1)) ; Create a new entry: - (let ((entry (bibtex-completion-get-entry key))) + (let ((entry (bibtex-completion-get-entry key t t))) (goto-char (point-max)) (insert (s-format bibtex-completion-notes-template-one-file 'bibtex-completion-apa-get-value