| 1 | package edu.ucsb.cs.citelines.services; | |
| 2 | ||
| 3 | import java.util.regex.Matcher; | |
| 4 | import java.util.regex.Pattern; | |
| 5 | import org.springframework.stereotype.Service; | |
| 6 | ||
| 7 | /** | |
| 8 | * Recognizes a DOI (Digital Object Identifier) expressed in any of several common formats and | |
| 9 | * normalizes it to its canonical bare form (ANSI/NISO Z39.84), e.g. {@code | |
| 10 | * 10.1038/s41586-020-2649-2}. | |
| 11 | * | |
| 12 | * <p>Recognized formats include: | |
| 13 | * | |
| 14 | * <ul> | |
| 15 | * <li>Actionable web URLs, e.g. {@code https://doi.org/10.1038/s41586-020-2649-2} or the | |
| 16 | * deprecated {@code http://dx.doi.org/10.1038/s41586-020-2649-2} | |
| 17 | * <li>shortDOI URLs, e.g. {@code https://doi.org/c234} or {@code https://doi.org/10/c234} | |
| 18 | * <li>Plain/bare DOI strings, e.g. {@code 10.1038/s41586-020-2649-2} | |
| 19 | * <li>The {@code doi:} prefix, e.g. {@code doi:10.1038/s41586-020-2649-2} | |
| 20 | * <li>The {@code urn:doi:} namespace, e.g. {@code urn:doi:10.1038/s41586-020-2649-2} | |
| 21 | * <li>The (deprecated) {@code info:doi/} URI scheme, e.g. {@code | |
| 22 | * info:doi/10.1038/s41586-020-2649-2} | |
| 23 | * </ul> | |
| 24 | */ | |
| 25 | @Service | |
| 26 | public class DOIService { | |
| 27 | ||
| 28 | // Matches a bare/canonical DOI, wherever it appears within a larger string (e.g. embedded in a | |
| 29 | // URL, an HTML anchor, or a doi:/urn:doi:/info:doi/ prefixed identifier). | |
| 30 | private static final Pattern DOI_PATTERN = | |
| 31 | Pattern.compile("\\b10\\.\\d{4,}(?:\\.\\d+)*/[^\\s<>\"]+", Pattern.CASE_INSENSITIVE); | |
| 32 | ||
| 33 | // Matches a shortDOI, either as a bare "10/xxxx" identifier or as a doi.org URL, with or | |
| 34 | // without the leading "10/" segment (e.g. "10/c234", "https://doi.org/c234", or | |
| 35 | // "https://doi.org/10/c234"). | |
| 36 | private static final Pattern SHORT_DOI_PATTERN = | |
| 37 | Pattern.compile( | |
| 38 | "^(?:https?://(?:dx\\.)?doi\\.org/(?:10/)?|10/)([a-zA-Z0-9]+)$", | |
| 39 | Pattern.CASE_INSENSITIVE); | |
| 40 | ||
| 41 | /** | |
| 42 | * Normalizes a DOI given in any of the recognized formats to its canonical bare form. | |
| 43 | * | |
| 44 | * @param rawDoi the DOI, in any recognized format | |
| 45 | * @return the canonical, lower-cased, bare DOI (e.g. {@code 10.1038/s41586-020-2649-2}) | |
| 46 | * @throws IllegalArgumentException if {@code rawDoi} cannot be recognized as a DOI in any of the | |
| 47 | * supported formats | |
| 48 | */ | |
| 49 | public String normalizeRawDOI(String rawDoi) { | |
| 50 |
1
1. normalizeRawDOI : negated conditional → KILLED |
if (rawDoi != null) { |
| 51 | String trimmed = rawDoi.trim(); | |
| 52 | ||
| 53 | Matcher shortDoiMatcher = SHORT_DOI_PATTERN.matcher(trimmed); | |
| 54 |
1
1. normalizeRawDOI : negated conditional → KILLED |
if (shortDoiMatcher.matches()) { |
| 55 |
1
1. normalizeRawDOI : replaced return value with "" for edu/ucsb/cs/citelines/services/DOIService::normalizeRawDOI → KILLED |
return "10/" + shortDoiMatcher.group(1).toLowerCase(); |
| 56 | } | |
| 57 | ||
| 58 | Matcher doiMatcher = DOI_PATTERN.matcher(trimmed); | |
| 59 |
1
1. normalizeRawDOI : negated conditional → KILLED |
if (doiMatcher.find()) { |
| 60 |
1
1. normalizeRawDOI : replaced return value with "" for edu/ucsb/cs/citelines/services/DOIService::normalizeRawDOI → KILLED |
return doiMatcher.group().toLowerCase(); |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | throw new IllegalArgumentException("Argument cannot be recognized as a DOI: " + rawDoi); | |
| 65 | } | |
| 66 | } | |
Mutations | ||
| 50 |
1.1 |
|
| 54 |
1.1 |
|
| 55 |
1.1 |
|
| 59 |
1.1 |
|
| 60 |
1.1 |