#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum TagKind {
Record,
Event,
Attribute,
Structure,
Unknown,
}
#[must_use]
#[allow(
clippy::too_many_lines,
reason = "one arm per tag reads better than a split table"
)]
pub fn label(tag: &str) -> Option<&'static str> {
Some(match tag {
"HEAD" => "Header",
"INDI" => "Individual",
"FAM" => "Family",
"SOUR" => "Source",
"REPO" => "Repository",
"OBJE" => "Media object",
"NOTE" => "Note",
"SNOTE" => "Shared note",
"SUBM" => "Submitter",
"SUBN" => "Submission",
"TRLR" => "End of file",
"BIRT" => "Birth",
"CHR" => "Christening",
"DEAT" => "Death",
"BURI" => "Burial",
"CREM" => "Cremation",
"ADOP" => "Adoption",
"BAPM" => "Baptism",
"BARM" => "Bar mitzvah",
"BASM" => "Bat mitzvah",
"BLES" => "Blessing",
"CHRA" => "Adult christening",
"CONF" => "Confirmation",
"FCOM" => "First communion",
"ORDN" => "Ordination",
"NATU" => "Naturalization",
"EMIG" => "Emigration",
"IMMI" => "Immigration",
"CENS" => "Census",
"PROB" => "Probate",
"WILL" => "Will",
"GRAD" => "Graduation",
"RETI" => "Retirement",
"EVEN" => "Event",
"CAST" => "Caste",
"DSCR" => "Physical description",
"EDUC" => "Education",
"IDNO" => "Identifying number",
"NATI" => "Nationality",
"NCHI" => "Number of children",
"NMR" => "Number of marriages",
"OCCU" => "Occupation",
"PROP" => "Property",
"RELI" => "Religion",
"RESI" => "Residence",
"SSN" => "Social security number",
"TITL" => "Title",
"FACT" => "Fact",
"MARR" => "Marriage",
"DIV" => "Divorce",
"DIVF" => "Divorce filed",
"ANUL" => "Annulment",
"ENGA" => "Engagement",
"MARB" => "Marriage banns",
"MARC" => "Marriage contract",
"MARL" => "Marriage licence",
"MARS" => "Marriage settlement",
"BAPL" => "Baptism (LDS)",
"CONL" => "Confirmation (LDS)",
"ENDL" => "Endowment (LDS)",
"SLGC" => "Sealing to parents (LDS)",
"SLGS" => "Sealing to spouse (LDS)",
"ABBR" => "Abbreviation",
"ADDR" => "Address",
"ADR1" => "Address line 1",
"ADR2" => "Address line 2",
"ADR3" => "Address line 3",
"AGE" => "Age",
"AGNC" => "Agency",
"ALIA" => "Also recorded as",
"ANCI" => "Ancestor interest",
"ASSO" => "Association",
"AUTH" => "Author",
"CALN" => "Call number",
"CAUS" => "Cause",
"CHAR" => "Character set",
"CHAN" => "Last changed",
"CHIL" => "Child",
"CITY" => "City",
"CONC" => "Continued",
"CONT" => "Continued on a new line",
"COPR" => "Copyright",
"CORP" => "Corporation",
"CREA" => "Created",
"CROP" => "Crop",
"CTRY" => "Country",
"DATA" => "Data",
"DATE" => "Date",
"DESI" => "Descendant interest",
"DEST" => "Destination",
"EMAIL" => "Email",
"EXID" => "External identifier",
"FAMC" => "Child in family",
"FAMS" => "Spouse in family",
"FAX" => "Fax",
"FILE" => "File",
"FONE" => "Phonetic form",
"FORM" => "Format",
"GEDC" => "GEDCOM version",
"GIVN" => "Given name",
"HEIG" | "HEIGHT" => "Height",
"HUSB" => "Husband",
"INIL" => "Initiatory",
"LANG" => "Language",
"LATI" => "Latitude",
"LEFT" => "Crop from the left",
"LONG" => "Longitude",
"MAP" => "Coordinates",
"MEDI" => "Medium",
"MIME" => "Media type",
"NAME" => "Name",
"NO" => "Did not happen",
"NICK" => "Nickname",
"NPFX" => "Name prefix",
"NSFX" => "Name suffix",
"PAGE" => "Where in the source",
"PEDI" => "Pedigree",
"PHON" => "Telephone",
"PHRASE" => "As written",
"PLAC" => "Place",
"POST" => "Postal code",
"PUBL" => "Publication",
"QUAY" => "Certainty",
"REFN" => "Reference number",
"RELA" => "Relationship",
"RESN" => "Restriction",
"RIN" => "Record identifier",
"ROLE" => "Role",
"ROMN" => "Romanized form",
"SCHMA" => "Extension declarations",
"SDATE" => "Sort date",
"SEX" => "Sex",
"SPFX" => "Surname prefix",
"STAE" => "State",
"STAT" => "Status",
"SURN" => "Surname",
"TAG" => "Extension tag",
"TEMP" => "Temple",
"TEXT" => "Text from the source",
"TIME" => "Time",
"TOP" => "Crop from the top",
"TRAN" => "Translation",
"TYPE" => "Type",
"VERS" => "Version",
"WIDTH" => "Width",
"WIFE" => "Wife",
"WWW" => "Web address",
"UID" | "_UID" => "Unique identifier",
_ => return None,
})
}
#[must_use]
pub fn display_label(tag: &str) -> &str {
label(tag).unwrap_or(tag)
}
#[must_use]
pub const fn vendor_label(tag: &str) -> Option<&'static str> {
Some(match tag.as_bytes() {
b"_UID" => "Unique identifier",
b"_FSFTID" => "FamilySearch identifier",
b"_APID" => "Ancestry record identifier",
b"_FREL" => "Relationship to father",
b"_MREL" => "Relationship to mother",
b"_MARNM" => "Married name",
b"_AKA" => "Also known as",
b"_PRIM" | b"_PHOTO" => "Preferred picture",
b"_MILT" => "Military service",
b"_MDCL" => "Medical",
b"_SEPR" => "Separation",
b"_HTITL" => "Husband's title",
b"_WTITL" => "Wife's title",
b"_UPD" => "Last updated",
b"_LINK" | b"_WEBTAG" => "Web link",
b"_URL" | b"_WWW" => "Web address",
b"_FOOT" => "Footnote",
b"_TODO" => "To do",
_ => return None,
})
}
#[must_use]
pub const fn xref_prefix(tag: &str) -> &'static str {
match tag.as_bytes() {
b"INDI" => "I",
b"FAM" => "F",
b"SOUR" => "S",
b"NOTE" | b"SNOTE" => "N",
b"OBJE" => "M",
b"REPO" => "R",
b"SUBM" => "U",
_ => "X",
}
}
#[must_use]
pub fn kind(tag: &str, level: u16) -> TagKind {
if level == 0 {
return if is_record(tag) {
TagKind::Record
} else {
TagKind::Unknown
};
}
if is_event(tag) {
return TagKind::Event;
}
if is_attribute(tag) {
return TagKind::Attribute;
}
if label(tag).is_some() {
TagKind::Structure
} else {
TagKind::Unknown
}
}
#[must_use]
pub const fn is_record(tag: &str) -> bool {
matches!(
tag.as_bytes(),
b"HEAD"
| b"INDI"
| b"FAM"
| b"SOUR"
| b"REPO"
| b"OBJE"
| b"NOTE"
| b"SNOTE"
| b"SUBM"
| b"SUBN"
| b"TRLR"
)
}
#[must_use]
pub const fn is_event(tag: &str) -> bool {
matches!(
tag.as_bytes(),
b"BIRT"
| b"CHR"
| b"DEAT"
| b"BURI"
| b"CREM"
| b"ADOP"
| b"BAPM"
| b"BARM"
| b"BASM"
| b"BLES"
| b"CHRA"
| b"CONF"
| b"FCOM"
| b"ORDN"
| b"NATU"
| b"EMIG"
| b"IMMI"
| b"CENS"
| b"PROB"
| b"WILL"
| b"GRAD"
| b"RETI"
| b"EVEN"
| b"MARR"
| b"DIV"
| b"DIVF"
| b"ANUL"
| b"ENGA"
| b"MARB"
| b"MARC"
| b"MARL"
| b"MARS"
)
}
#[must_use]
pub const fn is_attribute(tag: &str) -> bool {
matches!(
tag.as_bytes(),
b"CAST"
| b"DSCR"
| b"EDUC"
| b"IDNO"
| b"NATI"
| b"NCHI"
| b"NMR"
| b"OCCU"
| b"PROP"
| b"RELI"
| b"RESI"
| b"SSN"
| b"TITL"
| b"FACT"
)
}
#[must_use]
pub fn is_extension(tag: &str) -> bool {
tag.starts_with('_')
}
#[must_use]
pub fn sex_label(value: &str) -> &str {
match value.trim() {
"M" => "Male",
"F" => "Female",
"X" => "Neither male nor female",
"U" => "Unknown",
other => other,
}
}
#[must_use]
pub fn pedigree_label(value: &str) -> &str {
match value.trim() {
"birth" | "Birth" | "BIRTH" => "Birth",
"adopted" | "Adopted" | "ADOPTED" => "Adopted",
"foster" | "Foster" | "FOSTER" => "Foster",
"sealing" | "Sealing" | "SEALING" => "Sealing",
"step" | "Step" | "STEP" => "Step",
"other" | "Other" | "OTHER" => "Other",
other => other,
}
}
#[must_use]
pub fn certainty_label(value: &str) -> &str {
match value.trim() {
"0" => "Unreliable",
"1" => "Questionable",
"2" => "Secondary evidence",
"3" => "Direct primary evidence",
other => other,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_tag_with_no_entry_renders_as_itself() {
assert_eq!(label("_FSFTID"), None);
assert_eq!(display_label("_FSFTID"), "_FSFTID");
assert_eq!(kind("_FSFTID", 1), TagKind::Unknown);
assert!(is_extension("_FSFTID"));
}
#[test]
fn the_same_tag_reads_differently_by_level() {
assert_eq!(kind("SOUR", 0), TagKind::Record);
assert_eq!(kind("SOUR", 1), TagKind::Structure);
}
#[test]
fn events_and_attributes_are_told_apart() {
assert_eq!(kind("BIRT", 1), TagKind::Event);
assert_eq!(kind("RESI", 1), TagKind::Attribute);
assert_eq!(kind("DATE", 2), TagKind::Structure);
}
#[test]
fn every_tag_the_requirement_corpus_uses_has_a_label() {
for tag in [
"HEAD", "SOUR", "NAME", "GEDC", "VERS", "FORM", "CHAR", "NOTE", "INDI", "SEX", "BIRT",
"DATE", "PLAC", "CHR", "DEAT", "BURI", "RESI", "IMMI", "NATU", "OCCU", "EVEN", "TYPE",
"FAMC", "FAMS", "REFN", "OBJE", "FAM", "HUSB", "WIFE", "CHIL", "TITL", "PUBL", "FILE",
"TRLR",
] {
assert!(label(tag).is_some(), "{tag} has no label");
}
}
#[test]
fn enumerations_read_as_words_and_keep_what_they_do_not_know() {
assert_eq!(sex_label("F"), "Female");
assert_eq!(sex_label("Q"), "Q");
assert_eq!(pedigree_label("adopted"), "Adopted");
assert_eq!(certainty_label("3"), "Direct primary evidence");
assert_eq!(certainty_label("9"), "9");
}
}