DOIService.java
package edu.ucsb.cs.citelines.services;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.stereotype.Service;
/**
* Recognizes a DOI (Digital Object Identifier) expressed in any of several common formats and
* normalizes it to its canonical bare form (ANSI/NISO Z39.84), e.g. {@code
* 10.1038/s41586-020-2649-2}.
*
* <p>Recognized formats include:
*
* <ul>
* <li>Actionable web URLs, e.g. {@code https://doi.org/10.1038/s41586-020-2649-2} or the
* deprecated {@code http://dx.doi.org/10.1038/s41586-020-2649-2}
* <li>shortDOI URLs, e.g. {@code https://doi.org/c234} or {@code https://doi.org/10/c234}
* <li>Plain/bare DOI strings, e.g. {@code 10.1038/s41586-020-2649-2}
* <li>The {@code doi:} prefix, e.g. {@code doi:10.1038/s41586-020-2649-2}
* <li>The {@code urn:doi:} namespace, e.g. {@code urn:doi:10.1038/s41586-020-2649-2}
* <li>The (deprecated) {@code info:doi/} URI scheme, e.g. {@code
* info:doi/10.1038/s41586-020-2649-2}
* </ul>
*/
@Service
public class DOIService {
// Matches a bare/canonical DOI, wherever it appears within a larger string (e.g. embedded in a
// URL, an HTML anchor, or a doi:/urn:doi:/info:doi/ prefixed identifier).
private static final Pattern DOI_PATTERN =
Pattern.compile("\\b10\\.\\d{4,}(?:\\.\\d+)*/[^\\s<>\"]+", Pattern.CASE_INSENSITIVE);
// Matches a shortDOI, either as a bare "10/xxxx" identifier or as a doi.org URL, with or
// without the leading "10/" segment (e.g. "10/c234", "https://doi.org/c234", or
// "https://doi.org/10/c234").
private static final Pattern SHORT_DOI_PATTERN =
Pattern.compile(
"^(?:https?://(?:dx\\.)?doi\\.org/(?:10/)?|10/)([a-zA-Z0-9]+)$",
Pattern.CASE_INSENSITIVE);
/**
* Normalizes a DOI given in any of the recognized formats to its canonical bare form.
*
* @param rawDoi the DOI, in any recognized format
* @return the canonical, lower-cased, bare DOI (e.g. {@code 10.1038/s41586-020-2649-2})
* @throws IllegalArgumentException if {@code rawDoi} cannot be recognized as a DOI in any of the
* supported formats
*/
public String normalizeRawDOI(String rawDoi) {
if (rawDoi != null) {
String trimmed = rawDoi.trim();
Matcher shortDoiMatcher = SHORT_DOI_PATTERN.matcher(trimmed);
if (shortDoiMatcher.matches()) {
return "10/" + shortDoiMatcher.group(1).toLowerCase();
}
Matcher doiMatcher = DOI_PATTERN.matcher(trimmed);
if (doiMatcher.find()) {
return doiMatcher.group().toLowerCase();
}
}
throw new IllegalArgumentException("Argument cannot be recognized as a DOI: " + rawDoi);
}
}