Skip to content

Commit ed1c2e7

Browse files
committed
Tighten ISSN extraction and normalization rules
* prevent ISSN extraction inside longer hyphenated tokens * reject invalid ISSNs during normalization * update tests to use checksum-valid ISSN examples * add regression coverage for ISSN boundaries and wrapped forms
1 parent 25ad818 commit ed1c2e7

4 files changed

Lines changed: 150 additions & 5 deletions

File tree

R/extract_scholid.R

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,29 @@ extract_isbn <- function(text) {
165165
#'
166166
#' @noRd
167167
extract_issn <- function(text) {
168-
pat <- "(\\d{4}-\\d{3}[0-9X])"
169-
.extract_with_pattern(
168+
pat <- "(?<![[:alnum:]_\\-])(\\d{4}-\\d{3}[0-9Xx])(?![[:alnum:]_\\-])"
169+
out <- .extract_with_pattern(
170170
text = text,
171171
pat = pat
172172
)
173+
174+
lapply(out, function(hits) {
175+
if (!length(hits)) {
176+
return(character(0))
177+
}
178+
179+
cleaned <- vapply(
180+
hits,
181+
.clean_extracted_issn,
182+
character(1),
183+
USE.NAMES = FALSE
184+
)
185+
186+
cleaned <- cleaned[nzchar(cleaned)]
187+
cleaned <- cleaned[!is.na(cleaned)]
188+
cleaned <- cleaned[is_issn(cleaned)]
189+
cleaned
190+
})
173191
}
174192

175193

@@ -336,6 +354,28 @@ extract_pmcid <- function(text) {
336354
}
337355

338356

357+
#' Clean an extracted ISSN candidate
358+
#'
359+
#' @description
360+
#' Removes trailing punctuation and surrounding whitespace from an extracted
361+
#' ISSN candidate.
362+
#'
363+
#' @param x A single extracted ISSN candidate.
364+
#'
365+
#' @return A cleaned ISSN candidate string, or `""` if empty.
366+
#'
367+
#' @noRd
368+
.clean_extracted_issn <- function(x) {
369+
if (is.na(x) || !nzchar(x)) {
370+
return("")
371+
}
372+
373+
x <- sub("[[:space:][:punct:]]+$", "", x, perl = TRUE)
374+
x <- trimws(x)
375+
x
376+
}
377+
378+
339379
# Level 3 functions (functions called by level 2 functions) definitions --------
340380

341381

R/normalize_scholid.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ normalize_issn <- function(x) {
214214
paste0(substr(y, 1, 4), "-", substr(y, 5, 8))
215215
)
216216

217+
# Keep only checksum-valid ISSNs
218+
y[!is.na(y) & !is_issn(y)] <- NA_character_
219+
217220
out[ok] <- y
218221
out
219222
}

tests/testthat/test-extract_scholid.R

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ testthat::test_that(
264264
"extract_issn finds ISSN with X and ignores non-matches",
265265
{
266266
txt <- c(
267-
"ISSN 1234-567X",
267+
"ISSN 2434-561X",
268268
"no issn here"
269269
)
270270

@@ -273,7 +273,7 @@ testthat::test_that(
273273
"issn"
274274
)
275275

276-
testthat::expect_true(any(got[[1]] == "1234-567X"))
276+
testthat::expect_true(any(got[[1]] == "2434-561X"))
277277
testthat::expect_identical(
278278
got[[2]],
279279
character(0)
@@ -351,7 +351,7 @@ testthat::test_that(
351351
"arXiv:2101.00001v2",
352352
"PMCID: PMC12345",
353353
"PMID 1234567",
354-
"ISSN 1234-567X",
354+
"ISSN 2434-561X",
355355
"ISBN 0-306-40615-2"
356356
)
357357

@@ -675,3 +675,79 @@ testthat::test_that(
675675
)
676676
}
677677
)
678+
679+
680+
testthat::test_that(
681+
"extract_issn respects token boundaries and wrappers",
682+
{
683+
txt <- c(
684+
"ISSN 2434-561X",
685+
"Quoted '2434-561X'.",
686+
"Wrapped (2434-561X).",
687+
"Noise: abc 2434-561X xyz",
688+
"Phone-like 2434-561X-90",
689+
"Double token 2434-561X-0378-5955",
690+
"Prefix hyphen abc-2434-561X",
691+
"Surrounded by spaces 2434-561X okay"
692+
)
693+
694+
got <- extract_scholid(
695+
txt,
696+
"issn"
697+
)
698+
699+
testthat::expect_identical(
700+
got[[1]],
701+
"2434-561X"
702+
)
703+
testthat::expect_identical(
704+
got[[2]],
705+
"2434-561X"
706+
)
707+
testthat::expect_identical(
708+
got[[3]],
709+
"2434-561X"
710+
)
711+
testthat::expect_identical(
712+
got[[4]],
713+
"2434-561X"
714+
)
715+
testthat::expect_identical(
716+
got[[5]],
717+
character(0)
718+
)
719+
testthat::expect_identical(
720+
got[[6]],
721+
character(0)
722+
)
723+
testthat::expect_identical(
724+
got[[7]],
725+
character(0)
726+
)
727+
testthat::expect_identical(
728+
got[[8]],
729+
"2434-561X"
730+
)
731+
}
732+
)
733+
734+
testthat::test_that(
735+
"detect_scholid_type detects wrapped issn values",
736+
{
737+
x <- c(
738+
"2434-561X",
739+
"0378-5955",
740+
"ISSN 2434-561X",
741+
"issn: 0378-5955",
742+
"2434-561x",
743+
"not an issn"
744+
)
745+
746+
got <- detect_scholid_type(x)
747+
748+
testthat::expect_identical(
749+
got,
750+
c("issn", "issn", "issn", "issn", "issn", NA_character_)
751+
)
752+
}
753+
)

tests/testthat/test-normalize_scholid.R

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,29 @@ testthat::test_that(
235235
)
236236
}
237237
)
238+
239+
240+
testthat::test_that(
241+
"issn normalization rejects invalid values and canonicalizes valid ones",
242+
{
243+
testthat::expect_false(is_issn("9999-9999"))
244+
testthat::expect_false(is_issn("2434-561X-90"))
245+
246+
testthat::expect_identical(
247+
normalize_scholid("9999-9999", "issn"),
248+
NA_character_
249+
)
250+
testthat::expect_identical(
251+
normalize_scholid("2434-561X-90", "issn"),
252+
NA_character_
253+
)
254+
testthat::expect_identical(
255+
normalize_scholid("2434561X", "issn"),
256+
"2434-561X"
257+
)
258+
testthat::expect_identical(
259+
normalize_scholid("2434-561x", "issn"),
260+
"2434-561X"
261+
)
262+
}
263+
)

0 commit comments

Comments
 (0)