use crate::{
parse_subset,
tokenizer::{Token, Tokenizer},
types::{
ChangeDate, Date, EventDetail, MultimediaRecord, Note, RepoCitation, UserDefinedTag, Xref,
},
Parser,
};
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Source {
pub xref: Option<String>,
pub data: SourceData,
pub abbreviation: Option<String>,
pub title: Option<String>,
pub author: Option<String>,
pub publication_facts: Option<String>,
pub citation_from_source: Option<String>,
pub change_date: Option<Box<ChangeDate>>,
pub multimedia: Vec<MultimediaRecord>,
pub notes: Vec<Note>,
pub repo_citations: Vec<RepoCitation>,
pub submitter_registered_rfn: Option<String>,
pub custom_data: Vec<Box<UserDefinedTag>>,
}
impl Source {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8, xref: Option<String>) -> Source {
let mut sour = Source::default();
sour.xref = xref;
sour.parse(tokenizer, level);
sour
}
pub fn add_multimedia(&mut self, media: MultimediaRecord) {
self.multimedia.push(media);
}
pub fn add_note(&mut self, note: Note) {
self.notes.push(note);
}
pub fn add_repo_citation(&mut self, citation: RepoCitation) {
self.repo_citations.push(citation);
}
}
impl Parser for Source {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
tokenizer.next_token();
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| {
let mut pointer: Option<String> = None;
if let Token::Pointer(xref) = &tokenizer.current_token {
pointer = Some(xref.to_string());
tokenizer.next_token();
}
match tag {
"DATA" => tokenizer.next_token(),
"EVEN" => {
let events_recorded = tokenizer.take_line_value();
let mut event = EventDetail::new(tokenizer, level + 2, "OTHER");
event.with_source_data(events_recorded);
self.data.add_event(event);
}
"AGNC" => self.data.agency = Some(tokenizer.take_line_value()),
"ABBR" => self.abbreviation = Some(tokenizer.take_continued_text(level + 1)),
"CHAN" => self.change_date = Some(Box::new(ChangeDate::new(tokenizer, level + 1))),
"TITL" => self.title = Some(tokenizer.take_continued_text(level + 1)),
"AUTH" => self.author = Some(tokenizer.take_continued_text(level + 1)),
"PUBL" => self.publication_facts = Some(tokenizer.take_continued_text(level + 1)),
"TEXT" => {
self.citation_from_source = Some(tokenizer.take_continued_text(level + 1))
}
"OBJE" => self.add_multimedia(MultimediaRecord::new(tokenizer, level + 1, pointer)),
"NOTE" => self.add_note(Note::new(tokenizer, level + 1)),
"REPO" => self.add_repo_citation(RepoCitation::new(tokenizer, level + 1)),
"RFN" => self.submitter_registered_rfn = Some(tokenizer.take_line_value()),
_ => panic!("{} Unhandled Source Tag: {}", tokenizer.debug(), tag),
}
};
self.custom_data = parse_subset(tokenizer, level, handle_subset);
}
}
#[allow(clippy::module_name_repetitions)]
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct SourceData {
events: Vec<EventDetail>,
pub agency: Option<String>,
}
impl SourceData {
pub fn add_event(&mut self, event: EventDetail) {
self.events.push(event);
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct SourceCitation {
pub xref: Xref,
pub page: Option<String>,
pub data: Option<SourceCitationData>,
pub note: Option<Note>,
pub certainty_assessment: Option<CertaintyAssessment>,
pub submitter_registered_rfn: Option<String>,
pub multimedia: Vec<MultimediaRecord>,
pub custom_data: Vec<Box<UserDefinedTag>>,
}
impl SourceCitation {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> SourceCitation {
let mut citation = SourceCitation {
xref: tokenizer.take_line_value(),
page: None,
data: None,
note: None,
certainty_assessment: None,
multimedia: Vec::new(),
custom_data: Vec::new(),
submitter_registered_rfn: None,
};
citation.parse(tokenizer, level);
citation
}
pub fn add_multimedia(&mut self, m: MultimediaRecord) {
self.multimedia.push(m);
}
}
impl Parser for SourceCitation {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
tokenizer.next_token();
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| {
let mut pointer: Option<String> = None;
if let Token::Pointer(xref) = &tokenizer.current_token {
pointer = Some(xref.to_string());
tokenizer.next_token();
}
match tag {
"PAGE" => self.page = Some(tokenizer.take_continued_text(level + 1)),
"DATA" => self.data = Some(SourceCitationData::new(tokenizer, level + 1)),
"NOTE" => self.note = Some(Note::new(tokenizer, level + 1)),
"QUAY" => {
self.certainty_assessment = Some(CertaintyAssessment::new(tokenizer, level + 1))
}
"RFN" => self.submitter_registered_rfn = Some(tokenizer.take_line_value()),
"OBJE" => self.add_multimedia(MultimediaRecord::new(tokenizer, level + 1, pointer)),
_ => panic!(
"{} Unhandled SourceCitation Tag: {}",
tokenizer.debug(),
tag
),
}
};
self.custom_data = parse_subset(tokenizer, level, handle_subset);
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct SourceCitationData {
pub date: Option<Date>,
pub text: Option<TextFromSource>,
}
impl SourceCitationData {
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> SourceCitationData {
let mut data = SourceCitationData {
date: None,
text: None,
};
data.parse(tokenizer, level);
data
}
}
impl Parser for SourceCitationData {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
tokenizer.next_token();
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"DATE" => self.date = Some(Date::new(tokenizer, level + 1)),
"TEXT" => self.text = Some(TextFromSource::new(tokenizer, level + 1)),
_ => panic!(
"{} unhandled SourceCitationData tag: {}",
tokenizer.debug(),
tag
),
};
parse_subset(tokenizer, level, handle_subset);
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct TextFromSource {
pub value: Option<String>,
}
impl TextFromSource {
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> TextFromSource {
let mut text = TextFromSource { value: None };
text.parse(tokenizer, level);
text
}
}
impl Parser for TextFromSource {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
let mut value = String::new();
value.push_str(&tokenizer.take_line_value());
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"CONC" => value.push_str(&tokenizer.take_line_value()),
"CONT" => {
value.push('\n');
value.push_str(&tokenizer.take_line_value());
}
_ => panic!(
"{} unhandled TextFromSource tag: {}",
tokenizer.debug(),
tag
),
};
parse_subset(tokenizer, level, handle_subset);
if value != "" {
self.value = Some(value);
}
}
}
#[derive(Clone, Debug)]
pub enum CertaintyAssessment {
Unreliable,
Questionable,
Secondary,
Direct,
None,
}
impl CertaintyAssessment {
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8) -> CertaintyAssessment {
let mut quay = CertaintyAssessment::None;
quay.parse(tokenizer, level);
quay
}
pub fn get_int(&self) -> Option<u8> {
match &self {
CertaintyAssessment::Unreliable => Some(0),
CertaintyAssessment::Questionable => Some(1),
CertaintyAssessment::Secondary => Some(2),
CertaintyAssessment::Direct => Some(3),
CertaintyAssessment::None => None,
}
}
}
impl ToString for CertaintyAssessment {
fn to_string(&self) -> String {
format!("{:?}", self)
}
}
impl Parser for CertaintyAssessment {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
tokenizer.next_token();
if let Token::LineValue(val) = &tokenizer.current_token {
*self = match val.as_str() {
"0" => CertaintyAssessment::Unreliable,
"1" => CertaintyAssessment::Questionable,
"2" => CertaintyAssessment::Secondary,
"3" => CertaintyAssessment::Direct,
_ => panic!(
"{} Unknown CertaintyAssessment value {} ({})",
tokenizer.debug(),
val,
level
),
};
} else {
panic!(
"Expected CertaintyAssessment LineValue, found {:?}",
tokenizer.current_token
);
}
tokenizer.next_token();
}
}
#[cfg(test)]
mod tests {
use crate::GedcomDocument;
#[test]
fn test_parse_source_citation_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
2 FORM LINEAGE-LINKED\n\
0 @PERSON1@ INDI\n\
1 SOUR @SOURCE1@\n\
2 PAGE 42\n\
0 TRLR";
let mut ged = GedcomDocument::new(sample.chars());
let data = ged.parse_document();
assert_eq!(data.individuals[0].source[0].xref, "@SOURCE1@");
assert_eq!(data.individuals[0].source[0].page.as_ref().unwrap(), "42");
}
#[test]
fn test_parse_source_citation_data_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
2 FORM LINEAGE-LINKED\n\
0 @PERSON1@ INDI\n\
1 SOUR @SOURCE1@\n\
2 PAGE 42\n\
2 DATA\n\
3 DATE BEF 1 JAN 1900\n\
0 TRLR";
let mut ged = GedcomDocument::new(sample.chars());
let data = ged.parse_document();
let citation_data = data.individuals[0].source[0].data.as_ref().unwrap();
assert_eq!(
citation_data.date.as_ref().unwrap().value.as_ref().unwrap(),
"BEF 1 JAN 1900"
);
}
#[test]
fn test_parse_text_from_source_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
2 FORM LINEAGE-LINKED\n\
0 @PERSON1@ INDI\n\
1 SOUR @SOURCE1@\n\
2 PAGE 42\n\
2 DATA\n\
3 DATE BEF 1 JAN 1900\n\
3 TEXT a sample text\n\
4 CONT Sample text continued here. The word TE\n\
4 CONC ST should not be broken!\n\
0 TRLR";
let mut ged = GedcomDocument::new(sample.chars());
let data = ged.parse_document();
let citation_data = data.individuals[0].source[0].data.as_ref().unwrap();
assert_eq!(
citation_data.text.as_ref().unwrap().value.as_ref().unwrap(),
"a sample text\nSample text continued here. The word TEST should not be broken!"
);
}
#[test]
fn test_parse_certainty_assessment_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
2 FORM LINEAGE-LINKED\n\
0 @PERSON1@ INDI\n\
1 SOUR @SOURCE1@\n\
2 PAGE 42\n\
2 QUAY 1\n\
0 TRLR";
let mut ged = GedcomDocument::new(sample.chars());
let data = ged.parse_document();
let quay = data.individuals[0].source[0]
.certainty_assessment
.as_ref()
.unwrap();
assert_eq!(quay.get_int().unwrap(), 1);
}
}