Skip to content

Commit 432180f

Browse files
committed
Tighten arXiv extraction boundaries
* prevent partial matches inside larger tokens * reject hyphen- and slash-adjacent false positives * add regression tests for wrapped and malformed arXiv cases
1 parent ed1c2e7 commit 432180f

2 files changed

Lines changed: 140 additions & 3 deletions

File tree

R/extract_scholid.R

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ extract_orcid <- function(text) {
128128
#'
129129
#' @noRd
130130
extract_isbn <- function(text) {
131-
pat <- "(?<![[:alnum:]_])([0-9Xx][0-9Xx\\- ]{8,16}[0-9Xx])(?![[:alnum:]_])"
131+
pat <- "(?<![[:alnum:]_])([0-9Xx][0-9Xx\\- ]{8,16}[0-9Xx])(?![[:alnum:]_\\-/])"
132132
out <- .extract_with_pattern(
133133
text = text,
134134
pat = pat
@@ -202,11 +202,38 @@ extract_issn <- function(text) {
202202
#'
203203
#' @noRd
204204
extract_arxiv <- function(text) {
205-
pat <- "(\\d{4}\\.\\d{4,5}(v\\d+)?|[a-z\\-]+/\\d{7}(v\\d+)?)"
206-
.extract_with_pattern(
205+
pat <- paste0(
206+
"(?<![[:alnum:]_\\./-])",
207+
"(",
208+
"\\d{4}\\.\\d{4,5}(v\\d+)?",
209+
"|",
210+
"[a-z\\-]+/\\d{7}(v\\d+)?",
211+
")",
212+
"(?![[:alnum:]_\\-/])"
213+
)
214+
215+
out <- .extract_with_pattern(
207216
text = text,
208217
pat = pat
209218
)
219+
220+
lapply(out, function(hits) {
221+
if (!length(hits)) {
222+
return(character(0))
223+
}
224+
225+
cleaned <- vapply(
226+
hits,
227+
.clean_extracted_arxiv,
228+
character(1),
229+
USE.NAMES = FALSE
230+
)
231+
232+
cleaned <- cleaned[nzchar(cleaned)]
233+
cleaned <- cleaned[!is.na(cleaned)]
234+
cleaned <- cleaned[is_arxiv(cleaned)]
235+
cleaned
236+
})
210237
}
211238

212239

@@ -376,6 +403,28 @@ extract_pmcid <- function(text) {
376403
}
377404

378405

406+
#' Clean an extracted arXiv candidate
407+
#'
408+
#' @description
409+
#' Removes trailing punctuation and surrounding whitespace from an extracted
410+
#' arXiv candidate.
411+
#'
412+
#' @param x A single extracted arXiv candidate.
413+
#'
414+
#' @return A cleaned arXiv candidate string, or `""` if empty.
415+
#'
416+
#' @noRd
417+
.clean_extracted_arxiv <- function(x) {
418+
if (is.na(x) || !nzchar(x)) {
419+
return("")
420+
}
421+
422+
x <- sub("[[:space:][:punct:]]+$", "", x, perl = TRUE)
423+
x <- trimws(x)
424+
x
425+
}
426+
427+
379428
# Level 3 functions (functions called by level 2 functions) definitions --------
380429

381430

tests/testthat/test-extract_scholid.R

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,94 @@ testthat::test_that(
281281
}
282282
)
283283

284+
testthat::test_that(
285+
"extract_arxiv rejects partial matches inside larger tokens",
286+
{
287+
txt <- c(
288+
"bad modern 21011.12345",
289+
"bad version 2101.12345v",
290+
"bad version hep-th/9901001v",
291+
"phone-like 2101.12345-90",
292+
"slash tail 2101.12345/extra",
293+
"arXiv:2101.12345v2",
294+
"legacy hep-th/9901001v3"
295+
)
296+
297+
got <- extract_scholid(
298+
txt,
299+
"arxiv"
300+
)
301+
302+
testthat::expect_identical(
303+
got[[1]],
304+
character(0)
305+
)
306+
testthat::expect_identical(
307+
got[[2]],
308+
character(0)
309+
)
310+
testthat::expect_identical(
311+
got[[3]],
312+
character(0)
313+
)
314+
testthat::expect_identical(
315+
got[[4]],
316+
character(0)
317+
)
318+
testthat::expect_identical(
319+
got[[5]],
320+
character(0)
321+
)
322+
testthat::expect_identical(
323+
got[[6]],
324+
"2101.12345v2"
325+
)
326+
testthat::expect_identical(
327+
got[[7]],
328+
"hep-th/9901001v3"
329+
)
330+
}
331+
)
332+
333+
testthat::test_that(
334+
"extract_arxiv handles wrappers and multiple valid matches",
335+
{
336+
txt <- c(
337+
"Quoted '2101.12345'.",
338+
"Wrapped (2101.12345v2).",
339+
"Two IDs: 2101.12345 and hep-th/9901001v2.",
340+
"[hep-th/9901001]",
341+
"{math/0303001}."
342+
)
343+
344+
got <- extract_scholid(
345+
txt,
346+
"arxiv"
347+
)
348+
349+
testthat::expect_identical(
350+
got[[1]],
351+
"2101.12345"
352+
)
353+
testthat::expect_identical(
354+
got[[2]],
355+
"2101.12345v2"
356+
)
357+
testthat::expect_identical(
358+
got[[3]],
359+
c("2101.12345", "hep-th/9901001v2")
360+
)
361+
testthat::expect_identical(
362+
got[[4]],
363+
"hep-th/9901001"
364+
)
365+
testthat::expect_identical(
366+
got[[5]],
367+
"math/0303001"
368+
)
369+
}
370+
)
371+
284372
testthat::test_that(
285373
"extract_arxiv finds modern and legacy arXiv identifiers",
286374
{

0 commit comments

Comments
 (0)