diff --git a/src/parser/affiliated_keyword.rs b/src/parser/affiliated_keyword.rs index 27efe6aa..bc02edb1 100644 --- a/src/parser/affiliated_keyword.rs +++ b/src/parser/affiliated_keyword.rs @@ -35,75 +35,92 @@ where let mut ret = BTreeMap::new(); for kw in input { let translated_name = translate_name(global_settings, kw.key); - // if is_single_string_keyword(global_settings, translated_name.as_str()) { - // ret.insert( - // translated_name, - // AffiliatedKeywordValue::SingleString(kw.value), - // ); - // } else if is_list_of_single_string_keyword(global_settings, translated_name.as_str()) { - // let list_of_strings = ret - // .entry(translated_name) - // .or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1))); - // match list_of_strings { - // AffiliatedKeywordValue::ListOfStrings(list_of_strings) - // if list_of_strings.is_empty() => - // { - // list_of_strings.push(kw.value); - // } - // AffiliatedKeywordValue::ListOfStrings(list_of_strings) => { - // list_of_strings.clear(); - // list_of_strings.push(kw.value); - // } - // _ => panic!("Invalid AffiliatedKeywordValue type."), - // } - // } else if is_list_of_objects_keyword(global_settings, translated_name.as_str()) { - // let initial_context = ContextElement::document_context(); - // let initial_context = Context::new(global_settings, List::new(&initial_context)); + let keyword_type = identify_keyword_type(global_settings, translated_name.as_str()); + match keyword_type { + AffiliatedKeywordType::SingleString => { + ret.insert( + translated_name, + AffiliatedKeywordValue::SingleString(kw.value), + ); + } + AffiliatedKeywordType::ListOfStrings => { + let list_of_strings = ret.entry(translated_name).or_insert_with(|| { + AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)) + }); + match list_of_strings { + AffiliatedKeywordValue::ListOfStrings(list_of_strings) + if list_of_strings.is_empty() => + { + list_of_strings.push(kw.value); + } + AffiliatedKeywordValue::ListOfStrings(list_of_strings) => { + list_of_strings.clear(); + list_of_strings.push(kw.value); + } + _ => panic!("Invalid AffiliatedKeywordValue type."), + } + } + AffiliatedKeywordType::OptionalPair => { + let (_remaining, optional_string) = opt(all_consuming(map( + tuple(( + take_until::<_, &str, nom::error::Error<_>>("["), + tag("["), + recognize(many_till(anychar, peek(tuple((tag("]"), eof))))), + tag("]"), + eof, + )), + |(_, _, objects, _, _)| objects, + )))(kw.key.into()) + .expect("Parser should always succeed."); + ret.insert( + translated_name, + AffiliatedKeywordValue::OptionalPair { + optval: optional_string, + val: kw.value, + }, + ); + } + AffiliatedKeywordType::ObjectTree => { + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(global_settings, List::new(&initial_context)); - // let (_remaining, optional_objects) = opt(all_consuming(map( - // tuple(( - // take_until("["), - // tag("["), - // map_parser( - // recognize(many_till(anychar, peek(tuple((tag("]"), eof))))), - // confine_context(|i| { - // all_consuming(many0(parser_with_context!(standard_set_object)( - // &initial_context, - // )))(i) - // }), - // ), - // tag("]"), - // eof, - // )), - // |(_, _, objects, _, _)| objects, - // )))(kw.key.into()) - // .expect("Object parser should always succeed."); + let (_remaining, optional_objects) = opt(all_consuming(map( + tuple(( + take_until("["), + tag("["), + map_parser( + recognize(many_till(anychar, peek(tuple((tag("]"), eof))))), + confine_context(|i| { + all_consuming(many0(parser_with_context!(standard_set_object)( + &initial_context, + )))(i) + }), + ), + tag("]"), + eof, + )), + |(_, _, objects, _, _)| objects, + )))(kw.key.into()) + .expect("Object parser should always succeed."); - // // TODO: This should be omitting footnote references - // let (_remaining, objects) = all_consuming(many0(parser_with_context!( - // standard_set_object - // )(&initial_context)))(kw.value.into()) - // .expect("Object parser should always succeed."); - // let list_of_lists = ret.entry(translated_name).or_insert_with(|| { - // AffiliatedKeywordValue::ListOfListsOfObjects(Vec::with_capacity(1)) - // }); - // match list_of_lists { - // AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => { - // list_of_lists.push((optional_objects, objects)); - // } - // _ => panic!("Invalid AffiliatedKeywordValue type."), - // } - // } else { - // let list_of_strings = ret - // .entry(translated_name) - // .or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1))); - // match list_of_strings { - // AffiliatedKeywordValue::ListOfStrings(list_of_strings) => { - // list_of_strings.push(kw.value); - // } - // _ => panic!("Invalid AffiliatedKeywordValue type."), - // } - // } + // TODO: This should be omitting footnote references + let (_remaining, objects) = + all_consuming(many0(parser_with_context!(standard_set_object)( + &initial_context, + )))(kw.value.into()) + .expect("Object parser should always succeed."); + + let entry_per_keyword_list = ret + .entry(translated_name) + .or_insert_with(|| AffiliatedKeywordValue::ObjectTree(Vec::with_capacity(1))); + match entry_per_keyword_list { + AffiliatedKeywordValue::ObjectTree(entry_per_keyword_list) => { + entry_per_keyword_list.push((optional_objects, objects)); + } + _ => panic!("Invalid AffiliatedKeywordValue type."), + } + } + }; } AffiliatedKeywords { keywords: ret } } @@ -121,6 +138,40 @@ fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s name_until_optval.to_lowercase() } +enum AffiliatedKeywordType { + SingleString, + ListOfStrings, + OptionalPair, + ObjectTree, +} + +fn identify_keyword_type<'g, 's>( + global_settings: &'g GlobalSettings<'g, 's>, + name: &'s str, +) -> AffiliatedKeywordType { + let is_multiple = ["CAPTION", "HEADER"] + .into_iter() + .any(|candidate| name.eq_ignore_ascii_case(candidate)); + let is_parsed = global_settings + .element_parsed_keywords + .iter() + .any(|candidate| name.eq_ignore_ascii_case(candidate)); + let can_have_optval = global_settings + .element_dual_keywords + .iter() + .any(|candidate| name.eq_ignore_ascii_case(candidate)); + match (is_multiple, is_parsed, can_have_optval) { + (true, true, true) => AffiliatedKeywordType::ObjectTree, + (true, true, false) => unreachable!("Nothing like this exists in upstream org-mode."), + (true, false, true) => unreachable!("Nothing like this exists in upstream org-mode."), + (true, false, false) => AffiliatedKeywordType::ListOfStrings, + (false, true, true) => unreachable!("Nothing like this exists in upstream org-mode."), + (false, true, false) => unreachable!("Nothing like this exists in upstream org-mode."), + (false, false, true) => AffiliatedKeywordType::OptionalPair, + (false, false, false) => AffiliatedKeywordType::SingleString, + } +} + fn is_single_string_keyword<'g, 's>( _global_settings: &'g GlobalSettings<'g, 's>, name: &'s str, diff --git a/src/types/affiliated_keyword.rs b/src/types/affiliated_keyword.rs index 5831a4d5..d5f2fd4d 100644 --- a/src/types/affiliated_keyword.rs +++ b/src/types/affiliated_keyword.rs @@ -6,7 +6,11 @@ use super::Object; pub enum AffiliatedKeywordValue<'s> { SingleString(&'s str), ListOfStrings(Vec<&'s str>), - ListOfListsOfObjects(Vec<(Option>>, Vec>)>), + OptionalPair { + optval: Option<&'s str>, + val: &'s str, + }, + ObjectTree(Vec<(Option>>, Vec>)>), } #[derive(Debug)]