-
Notifications
You must be signed in to change notification settings - Fork 46
/
query_bib.R
265 lines (228 loc) · 8.65 KB
/
query_bib.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
#' Query bibliography
#'
#' Search entries in a bibliography.
#'
#' @param x Character. Search terms used to filter bibliography (by author, year, title, and journal
#' fields); Regex is supported.
#' @param bib_file Character. Path to Bib(La)TeX-file. See details.
#' @param cache Logical. If \code{cache = TRUE} cached bibliography is used, if available. If
#' \code{cache = FALSE} bibliography is re-imported on every function call.
#' @param use_betterbiblatex Logical. If \code{use_betterbiblatex = TRUE} references are imported from Zotero/Juris-M.
#' @param betterbiblatex_format Character. Bibliography format to export from Zotero/Juris-M. Can be either \code{"bibtex"} or \code{"biblatex"}. Ignored if \code{use_betterbiblatex = FALSE}.
#' Requires that the \href{https://github.com/retorquere/zotero-better-bibtex}{Better BibTeX} is installed and
#' Zotero/Juris-M is running.
#' @param exclude_betterbiblatex_library Character. A vector of Zotero/Juris-M library names not to query.
#' @param encoding Character. Encoding of the Bib(La)TeX-file. See the \code{\link{connection}}.
#'
#' @details The path to the BibTeX-file can be set in the global options and is set to
#' \code{references.bib} when the package is loaded. Once the path is changed in the
#' RStudio addin, the global option is updated. If \code{use_betterbiblatex = TRUE} references
#' are imported from Zotero/Juris-M rather than from the Bib(La)TeX-file. The Bib(La)TeX-file
#' is then updated to include the inserted reference.
#'
#' @return Returns list of class \code{\link[RefManageR]{BibEntry}} including all matching bibliography entries.
#' @seealso \code{\link{md_cite}}, \code{\link{insert_citation}}
#'
#' @import assertthat
query_bib <- function(
x
, bib_file = getOption("citr.bibliography_path")
, cache = TRUE
, use_betterbiblatex = getOption("citr.use_betterbiblatex")
, betterbiblatex_format = getOption("citr.betterbiblatex_format")
, exclude_betterbiblatex_library = getOption("citr.exclude_betterbiblatex_library")
, encoding = getOption("citr.encoding")
) {
assert_that(is.string(x))
assert_that(is.string(bib_file))
assert_that(is.flag(cache))
assert_that(is.flag(use_betterbiblatex))
if(use_betterbiblatex && !betterbiblatex_available()) {
message(
"Could not connect to Zotero's Better-BibTeX extension; importing references from"
, bib_file
, ". Is Zotero up and running?"
)
}
assert_that(is.string(betterbiblatex_format))
if(!betterbiblatex_format %in% c("bibtex", "biblatex")) {
stop("Bibliography format not supported. Use either 'bibtex' or 'biblatex'.")
}
assert_that(is.string(encoding))
# Use cached bibliography, if available
if(is.null(getOption("citr.bibliography_cache")) || !cache) {
if(use_betterbiblatex & betterbiblatex_available()) {
cat("Connecting to Zotero...\n")
bib <- load_betterbiblatex_bib(
encoding
, betterbiblatex_format
, exclude_betterbiblatex_library
)
} else {
bib <- RefManageR::ReadBib(
file = bib_file
, check = FALSE
, .Encoding = encoding
)
}
if(cache) options(citr.bibliography_cache = bib)
} else {
bib <- getOption("citr.bibliography_cache")
}
pasted_bib <- paste_references(bib) # Create searchable text strings for references
entries <- bib[grepl(gsub("\\s", ".+", x, useBytes = TRUE), pasted_bib, ignore.case = TRUE)]
if(length(entries) > 0) entries else NULL
}
paste_references <- function(bib) {
assert_that(methods::is(bib, "bibentry"))
author_names <- sapply(bib, function(x) {
author_names <- x$author$family
if(is.null(author_names)) {
author_names <- unlist(x$author$given)
}
author_names <- if(is.list(author_names)) {
unlist(lapply(author_names, paste, collapse = " "))
} else if(is.null(author_names)) {
""
} else {
paste(author_names, collapse = " ")
}
n_authors <- length(author_names)
if(n_authors == 1) {
author_names
} else if(n_authors == 2) {
paste(author_names, collapse = " & ")
} else if(n_authors > 2 & n_authors < 6) {
paste(
paste(author_names[-n_authors], collapse = ", ")
, author_names[n_authors]
, sep = ", & "
)
} else if(n_authors >= 6) {
paste(author_names[1], "et al.")
}
})
year <- sapply(bib, function(x) {
if(!is.null(x$year)) x$year else {
if(!is.null(x$date)) {
date <- unlist(x$date)
regmatches(date, regexpr("[0-9]{4}", date))
} else {
"n.d."
}
}
})
author_names <- gsub("\\}|\\{", "", author_names, useBytes = TRUE)
titles <- gsub("\\}|\\{|\\\\", "", bib$title, useBytes = TRUE)
titles <- gsub("\\n", " ", titles, useBytes = TRUE)
titles <- gsub("\\.+$", "", titles, useBytes = TRUE)
# if author_names is null replace by title
no_authors <- author_names == ""
author_names[no_authors] <- titles[no_authors]
titles <- paste0(" ", titles, ".")
titles[no_authors] <- ""
journals <- gsub("\\}|\\{|\\\\", "", bib$journal, useBytes = TRUE)
journals <- paste0(" ", journals, ifelse(journals != "", ".", NULL))
journals[journals == " NULL."] <- ""
paste0(author_names, " (", year, ").", titles, journals)
}
betterbiblatex_available <- function() {
tryCatch(
rawToChar(curl::curl_fetch_memory(
url = "http://localhost:23119/better-bibtex/cayw?probe=probe")$content
) == "ready"
, error = function(e) FALSE
)
}
#' Load bibliography from Zotero
#'
#' @param increment_progress logical switch which will use the shiny function
#' \code{incProgress} when \code{TRUE}. \code{FALSE} by default.
#' @inheritParams query_bib
#'
#' @details This function loads into RAM the bibliography stored in Zotero.
#' May take a several seconds if you have many hundreds of references.
#'
#' @export
#'
#' @examples \dontrun{
#' b <- load_betterbiblatex_bib(encoding = "UTF-8")
#' }
load_betterbiblatex_bib <- function(
encoding
, betterbiblatex_format = "bibtex"
, exclude_betterbiblatex_library = NULL
, increment_progress = FALSE
) {
bib <- c()
bbt_libraries <- query_bbt_libraries()
if(is.null(bbt_libraries)) {
betterbibtex_url <- paste0(
"http://localhost:23119/better-bibtex/library?library."
, betterbiblatex_format
)
bib <- import_bbt(bib, betterbibtex_url, encoding)
if(increment_progress) shiny::incProgress(1)
} else {
betterbibtex_baseurl <- "http://localhost:23119/better-bibtex/library?/"
bbt_libraries <- bbt_libraries[
!unlist(bbt_libraries[, "name"] %in% exclude_betterbiblatex_library)
,
, drop = FALSE
]
group_library_id <- unlist(bbt_libraries[, "id"])
for(bib_i in group_library_id) {
betterbibtex_url_i <- paste0(
betterbibtex_baseurl
, bib_i
, "/library."
, betterbiblatex_format
)
group_library_name_i <- unlist(bbt_libraries[unlist(bbt_libraries[, "id"]) == bib_i, "name"])
if(increment_progress) {
setProgress(detail = paste0("\n'", group_library_name_i[[1]], "'"))
} else {
cat("Importing '", group_library_name_i, "'...\n", sep = "")
}
bib <- import_bbt(bib, betterbibtex_url_i, encoding)
if(increment_progress) shiny::incProgress(1 / length(group_library_id))
}
}
class(bib) <- c("BibEntry", "bibentry")
bib
}
import_bbt <- function(x, location, encoding) {
betterbibtex_bib <- rawToChar(curl::curl_fetch_memory(url = location)$content)
# Remove jab-ref comments
betterbibtex_bib <- strsplit(betterbibtex_bib, "@comment\\{jabref-meta", useBytes = TRUE)[[1]][1]
# Download bibliography
tmp_bib_file <- paste0(
paste(
sample(c(letters, LETTERS, 0:9), size = 32, replace = TRUE)
, collapse = ""
)
, ".bib"
)
tmp_con <- file(tmp_bib_file, encoding = encoding)
writeLines(betterbibtex_bib, con = tmp_con)
close(tmp_con)
try(on.exit(suppressWarnings(file.remove(tmp_bib_file))), silent = TRUE)
# If ever bibtex supports reading from connections
# tmp_con <- textConnection(betterbibtex_bib, encoding = encoding)
# try(on.exit(suppressWarnings(close(tmp_con))), silent = TRUE)
c(x, RefManageR::ReadBib(file = tmp_bib_file, check = FALSE, .Encoding = encoding))
}
query_bbt_libraries <- function(x) {
# If installed BBT is >= 5.0.50 use JSON-RPC to qurey library IDs
bbt_json_response <- httr::POST(
"http://localhost:23119/better-bibtex/json-rpc"
, httr::add_headers("Content-Type" = "application/json")
, body = '{"jsonrpc": "2.0", "method": "user.groups" }'
, encode = "json"
)
if(any(grepl("No endpoint found", httr::content(bbt_json_response)))) {
NULL
} else {
do.call(rbind, httr::content(bbt_json_response)$result)
}
}