pub fn says_public_domain(text: &str) -> bool {
let lower = text.to_lowercase().replace(['-', '_'], " ");
let negations = ["not", "isn't", "isnt", "no", "never", "non", "nor"];
let mut rest = lower.as_str();
let mut before = String::new();
while let Some(at) = rest.find("public domain") {
before.push_str(&rest[..at]);
let words: Vec<&str> = before
.split(|c: char| !(c.is_alphanumeric() || c == '\''))
.filter(|w| !w.is_empty())
.collect();
let near = &words[words.len().saturating_sub(4)..];
if !near.iter().any(|w| negations.contains(w)) {
return true;
}
before.push_str("public domain");
rest = &rest[at + "public domain".len()..];
}
false
}
pub fn license_from_text(text: &str) -> Option<String> {
let lower = text.to_lowercase();
if let Some((_, rest)) = lower.split_once("creativecommons.org/licenses/") {
let mut parts = rest.splitn(2, '/');
let terms = parts.next().unwrap_or_default();
let version = version_at(parts.next().unwrap_or_default());
if let Some(license) = creative_commons(terms, version.as_deref()) {
return Some(license);
}
}
if lower.contains("creativecommons.org/publicdomain/zero") {
return Some(String::from("CC0-1.0"));
}
if lower.contains("creativecommons.org/publicdomain/mark") {
return Some(String::from("LicenseRef-PublicDomain"));
}
if let Some(license) = creative_commons_by_name(&lower) {
return Some(license);
}
says_public_domain(text).then(|| String::from("LicenseRef-PublicDomain"))
}
fn version_at(text: &str) -> Option<String> {
let version: String = (text.chars())
.filter(|c| !matches!(c, '\u{200b}'..='\u{200f}' | '\u{202a}'..='\u{202e}' | '\u{2066}'..='\u{2069}' | '\u{feff}'))
.take_while(|c| c.is_ascii_digit() || *c == '.')
.collect();
let version = version.trim_end_matches('.');
(!version.is_empty()).then(|| version.to_string())
}
fn creative_commons(terms: &str, version: Option<&str>) -> Option<String> {
let known = ["by", "by-sa", "by-nd", "by-nc", "by-nc-sa", "by-nc-nd"];
known
.contains(&terms)
.then(|| format!("CC-{}-{}", terms.to_uppercase(), version.unwrap_or("4.0")))
}
fn creative_commons_by_name(lower: &str) -> Option<String> {
let (_, rest) = lower.split_once("creative commons attribution")?;
let end = rest.find(['.', '\n', '<']).map_or(rest.len(), |at| {
let after = rest[at + 1..].chars().next();
match after.is_some_and(|c| c.is_ascii_digit()) {
true => rest[at + 1..]
.find(|c: char| !c.is_ascii_digit())
.map_or(rest.len(), |n| at + 1 + n),
false => at,
}
});
let name = &rest[..end];
let words = name.replace(['-', '_'], " ");
let has = |word: &str| words.split_whitespace().any(|w| w == word);
let mut terms = String::from("by");
if has("noncommercial") || words.contains("non commercial") {
terms.push_str("-nc");
}
if has("noderivatives") || has("noderivs") || words.contains("no derivative") {
terms.push_str("-nd");
} else if has("sharealike") || words.contains("share alike") {
terms.push_str("-sa");
}
let version = (name.split_whitespace()).find_map(|w| version_at(w).filter(|v| v.contains('.')));
creative_commons(&terms, version.as_deref())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn public_domain_isnt_read_into_a_denial() {
assert!(says_public_domain("public domain"));
assert!(says_public_domain("This text is in the Public Domain."));
assert!(says_public_domain("Public-Domain"));
assert!(!says_public_domain("This is not public domain."));
assert!(!says_public_domain("It is not in the public domain"));
assert!(!says_public_domain("This translation isn't public domain"));
assert!(!says_public_domain("Copyright 2020 Someone"));
assert!(says_public_domain(
"The notes are not public domain; the text is public domain."
));
}
#[test]
fn licenses_are_read_from_statements() {
let link = "under the <a href='http://creativecommons.org/licenses/by-nc-nd/4.0/'>Creative Commons Attribution-Noncommercial-No Derivatives license 4.0.</a>";
assert_eq!(license_from_text(link).as_deref(), Some("CC-BY-NC-ND-4.0"));
let marked = "http://creativecommons.org/licenses/by-sa/4\u{202a}.0 or send a letter";
assert_eq!(license_from_text(marked).as_deref(), Some("CC-BY-SA-4.0"));
let zero = "https://creativecommons.org/publicdomain/zero/1.0/";
assert_eq!(license_from_text(zero).as_deref(), Some("CC0-1.0"));
let named = "made available under the terms of the Creative Commons Attribution Share-Alike license 4.0. You may";
assert_eq!(license_from_text(named).as_deref(), Some("CC-BY-SA-4.0"));
assert_eq!(
license_from_text("public domain").as_deref(),
Some("LicenseRef-PublicDomain")
);
assert_eq!(license_from_text("not public domain"), None);
assert_eq!(license_from_text("Copyright © 2009 Someone"), None);
}
}