#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PersonName {
pub pn: String,
pub reordered: bool,
}
pub fn from_xpn(xpn: &str) -> Option<PersonName> {
let c: Vec<&str> = xpn.split('^').map(str::trim).collect();
let get = |i: usize| c.get(i).copied().unwrap_or("");
build(get(0), get(1), get(2), get(4), get(3))
}
pub fn from_xcn(xcn: &str) -> Option<PersonName> {
let c: Vec<&str> = xcn.split('^').map(str::trim).collect();
let get = |i: usize| c.get(i).copied().unwrap_or("");
build(get(1), get(2), get(3), get(5), get(4))
}
fn build(
family: &str,
given: &str,
middle: &str,
prefix: &str,
suffix: &str,
) -> Option<PersonName> {
let family = family.split('&').next().unwrap_or("").trim();
let parts = [family, given, middle, prefix, suffix];
if parts.iter().all(|p| p.is_empty()) {
return None;
}
let last = parts.iter().rposition(|p| !p.is_empty()).unwrap_or(0);
let pn = parts[..=last].join("^");
Some(PersonName {
pn,
reordered: !prefix.is_empty() || !suffix.is_empty(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xpn_prefix_and_suffix_swap_positions() {
let n = from_xpn("Doe^Jane^Marie^Jr^Dr").unwrap();
assert_eq!(n.pn, "Doe^Jane^Marie^Dr^Jr");
assert!(n.reordered);
let plain = from_xpn("Doe^Jane").unwrap();
assert_eq!(plain.pn, "Doe^Jane");
assert!(!plain.reordered);
assert_eq!(from_xpn("Doe^^^^Dr").unwrap().pn, "Doe^^^Dr");
assert_eq!(from_xpn("Doe^Jane^^PhD").unwrap().pn, "Doe^Jane^^^PhD");
}
#[test]
fn xcn_skips_the_id_component() {
let n = from_xcn("12345^Curie^Marie^^^Prof").unwrap();
assert_eq!(n.pn, "Curie^Marie^^Prof");
assert!(n.reordered);
assert_eq!(from_xcn("12345^Curie^Marie").unwrap().pn, "Curie^Marie");
assert!(from_xcn("12345").is_none(), "an id alone is not a name");
}
#[test]
fn family_name_takes_the_surname_subcomponent() {
assert_eq!(from_xpn("Neumann&von^John").unwrap().pn, "Neumann^John");
assert!(from_xpn("").is_none());
assert!(from_xpn("^^^").is_none());
}
}