use crate::{
parse_subset,
tokenizer::Tokenizer,
types::{Corporation, Date, Note},
Parser,
};
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};
use super::UserDefinedTag;
#[derive(Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Header {
pub gedcom: Option<GedcomMeta>,
pub encoding: Option<Encoding>,
pub source: Option<HeadSour>,
pub destination: Option<String>,
pub date: Option<Date>,
pub submitter_tag: Option<String>,
pub submission_tag: Option<String>,
pub copyright: Option<String>,
pub language: Option<String>,
pub filename: Option<String>,
pub note: Option<Note>,
pub place: Option<HeadPlac>,
pub custom_data: Vec<Box<UserDefinedTag>>,
}
impl Header {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> Header {
let mut header = Header::default();
header.parse(tokenizer, level);
header
}
}
impl Parser for Header {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
tokenizer.next_token();
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"GEDC" => self.gedcom = Some(GedcomMeta::new(tokenizer, level + 1)),
"SOUR" => self.source = Some(HeadSour::new(tokenizer, level + 1)),
"DEST" => self.destination = Some(tokenizer.take_line_value()),
"DATE" => self.date = Some(Date::new(tokenizer, level + 1)),
"SUBM" => self.submitter_tag = Some(tokenizer.take_line_value()),
"SUBN" => self.submission_tag = Some(tokenizer.take_line_value()),
"FILE" => self.filename = Some(tokenizer.take_line_value()),
"COPR" => self.copyright = Some(tokenizer.take_continued_text(level + 1)),
"CHAR" => self.encoding = Some(Encoding::new(tokenizer, level + 1)),
"LANG" => self.language = Some(tokenizer.take_line_value()),
"NOTE" => self.note = Some(Note::new(tokenizer, level + 1)),
"PLAC" => self.place = Some(HeadPlac::new(tokenizer, level + 1)),
_ => panic!("{} Unhandled Header Tag: {}", tokenizer.debug(), tag),
};
self.custom_data = parse_subset(tokenizer, level, handle_subset);
}
}
#[derive(Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct GedcomMeta {
pub version: Option<String>,
pub form: Option<String>,
}
impl GedcomMeta {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> GedcomMeta {
let mut gedc = GedcomMeta::default();
gedc.parse(tokenizer, level);
gedc
}
}
impl Parser for GedcomMeta {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
tokenizer.next_token();
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"VERS" => self.version = Some(tokenizer.take_line_value()),
"FORM" => {
let form = tokenizer.take_line_value();
if &form.to_uppercase() != "LINEAGE-LINKED" {
println!(
"WARNING: Unrecognized GEDCOM form. Expected LINEAGE-LINKED, found {}",
form
);
}
self.form = Some(form);
}
_ => panic!("{} Unhandled GEDC Tag: {}", tokenizer.debug(), tag),
};
parse_subset(tokenizer, level, handle_subset);
}
}
#[derive(Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Encoding {
pub value: Option<String>,
pub version: Option<String>,
}
impl Encoding {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> Encoding {
let mut chars = Encoding::default();
chars.parse(tokenizer, level);
chars
}
}
impl Parser for Encoding {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
self.value = Some(tokenizer.take_line_value());
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"VERS" => self.version = Some(tokenizer.take_line_value()),
_ => panic!("{} Unhandled CHAR Tag: {}", tokenizer.debug(), tag),
};
parse_subset(tokenizer, level, handle_subset);
}
}
#[derive(Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct HeadSour {
pub value: Option<String>,
pub version: Option<String>,
pub name: Option<String>,
pub corporation: Option<Corporation>,
pub data: Option<HeadSourData>,
}
impl HeadSour {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> HeadSour {
let mut head_sour = HeadSour::default();
head_sour.parse(tokenizer, level);
head_sour
}
}
impl Parser for HeadSour {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
self.value = Some(tokenizer.take_line_value());
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"VERS" => self.version = Some(tokenizer.take_line_value()),
"NAME" => self.name = Some(tokenizer.take_line_value()),
"CORP" => self.corporation = Some(Corporation::new(tokenizer, level + 1)),
"DATA" => self.data = Some(HeadSourData::new(tokenizer, level + 1)),
_ => panic!("{} Unhandled CHAR Tag: {}", tokenizer.debug(), tag),
};
parse_subset(tokenizer, level, handle_subset);
}
}
#[derive(Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct HeadSourData {
pub value: Option<String>,
pub date: Option<Date>,
pub copyright: Option<String>,
}
impl HeadSourData {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> HeadSourData {
let mut head_sour_data = HeadSourData::default();
head_sour_data.parse(tokenizer, level);
head_sour_data
}
}
impl Parser for HeadSourData {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
self.value = Some(tokenizer.take_line_value());
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"DATE" => self.date = Some(Date::new(tokenizer, level + 1)),
"COPR" => self.copyright = Some(tokenizer.take_continued_text(level + 1)),
_ => panic!(
"{} unhandled DATA tag in header: {}",
tokenizer.debug(),
tag
),
};
parse_subset(tokenizer, level, handle_subset);
}
}
#[derive(Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct HeadPlac {
pub form: Vec<String>,
}
impl HeadPlac {
pub fn push_jurisdictional_title(&mut self, title: String) {
self.form.push(title);
}
pub fn insert_jurisdictional_title(&mut self, index: usize, title: String) {
self.form.insert(index, title);
}
pub fn remove_jurisdictional_title(&mut self, index: usize) {
self.form.remove(index);
}
}
impl HeadPlac {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> HeadPlac {
let mut head_plac = HeadPlac::default();
head_plac.parse(tokenizer, level);
head_plac
}
}
impl Parser for HeadPlac {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
tokenizer.next_token();
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"FORM" => {
let form = tokenizer.take_line_value();
let jurisdictional_titles = form.split(",");
for t in jurisdictional_titles {
let v = t.trim();
self.push_jurisdictional_title(v.to_string());
}
}
_ => panic!(
"{} Unhandled PLAC tag in header: {}",
tokenizer.debug(),
tag
),
};
parse_subset(tokenizer, level, handle_subset);
}
}
#[cfg(test)]
mod tests {
use crate::GedcomDocument;
#[test]
fn test_parse_header_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
1 DEST Destination of transmission\n\
1 SUBM @SUBMITTER@\n\
1 SUBN @SUBMISSION@\n\
1 FILE ALLGED.GED\n\
1 LANG language\n\
0 TRLR";
let mut doc = GedcomDocument::new(sample.chars());
let data = doc.parse_document();
let header = data.header.unwrap();
let dest = header.destination.unwrap();
assert_eq!(dest, "Destination of transmission");
let subn = header.submitter_tag.unwrap();
assert_eq!(subn, "@SUBMITTER@");
let subm = header.submission_tag.unwrap();
assert_eq!(subm, "@SUBMISSION@");
let lang = header.language.unwrap();
assert_eq!(lang.as_str(), "language");
let file = header.filename.unwrap();
assert_eq!(file, "ALLGED.GED");
}
#[test]
fn test_parse_gedcom_meta_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
2 FORM LINEAGE-LINKED\n\
0 TRLR";
let mut ged = GedcomDocument::new(sample.chars());
let data = ged.parse_document();
let head_gedc = data.header.unwrap().gedcom.unwrap();
assert_eq!(head_gedc.version.unwrap(), "5.5");
assert_eq!(head_gedc.form.unwrap(), "LINEAGE-LINKED");
}
#[test]
fn test_parse_encoding_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
1 CHAR ASCII\n\
2 VERS Version number of ASCII (whatever it means)\n\
0 TRLR";
let mut doc = GedcomDocument::new(sample.chars());
let data = doc.parse_document();
let h_char = data.header.unwrap().encoding.unwrap();
assert_eq!(h_char.value.unwrap(), "ASCII");
assert_eq!(
h_char.version.unwrap(),
"Version number of ASCII (whatever it means)"
);
}
#[test]
fn test_parse_header_source_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
1 SOUR SOURCE_NAME\n\
2 VERS Version number of source-program\n\
2 NAME Name of source-program\n\
0 TRLR";
let mut doc = GedcomDocument::new(sample.chars());
let data = doc.parse_document();
let sour = data.header.unwrap().source.unwrap();
assert_eq!(sour.value.unwrap(), "SOURCE_NAME");
let vers = sour.version.unwrap();
assert_eq!(vers, "Version number of source-program");
let name = sour.name.unwrap();
assert_eq!(name, "Name of source-program");
}
#[test]
fn test_parse_header_source_data_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
1 SOUR SOURCE_NAME\n\
2 DATA Name of source data\n\
3 DATE 1 JAN 1998\n\
3 COPR Copyright of source data\n\
0 TRLR";
let mut doc = GedcomDocument::new(sample.chars());
let data = doc.parse_document();
let sour = data.header.unwrap().source.unwrap();
assert_eq!(sour.value.unwrap(), "SOURCE_NAME");
let sour_data = sour.data.unwrap();
assert_eq!(sour_data.value.unwrap(), "Name of source data");
assert_eq!(sour_data.date.unwrap().value.unwrap(), "1 JAN 1998");
assert_eq!(sour_data.copyright.unwrap(), "Copyright of source data");
}
#[test]
fn test_parse_header_place_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
1 PLAC\n\
2 FORM City, County, State, Country\n\
0 TRLR";
let mut doc = GedcomDocument::new(sample.chars());
let data = doc.parse_document();
let h_plac = data.header.unwrap().place.unwrap();
assert_eq!(h_plac.form[0], "City");
assert_eq!(h_plac.form[1], "County");
assert_eq!(h_plac.form[2], "State");
assert_eq!(h_plac.form[3], "Country");
}
}