use std::collections::BTreeMap; use nom::bytes::complete::tag; use nom::bytes::complete::take_until; use nom::character::complete::anychar; use nom::combinator::all_consuming; use nom::combinator::eof; use nom::combinator::map; use nom::combinator::map_parser; use nom::combinator::opt; use nom::combinator::peek; use nom::combinator::recognize; use nom::multi::many0; use nom::multi::many_till; use nom::sequence::tuple; use super::object_parser::standard_set_object; use super::util::confine_context; use crate::context::parser_with_context; use crate::context::Context; use crate::context::ContextElement; use crate::context::GlobalSettings; use crate::context::List; use crate::types::AffiliatedKeywordValue; use crate::types::AffiliatedKeywords; use crate::types::Keyword; pub(crate) fn parse_affiliated_keywords<'g, 's>( global_settings: &'g GlobalSettings<'g, 's>, input: Vec>, ) -> AffiliatedKeywords<'s> { let mut ret = BTreeMap::new(); for kw in input.into_iter() { 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 (_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."), } } } AffiliatedKeywords { keywords: ret } } fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String { let name_until_optval = name .split_once("[") .map(|(before, _after)| before) .unwrap_or(name); for (src, dst) in global_settings.element_keyword_translation_alist { if name_until_optval.eq_ignore_ascii_case(src) { return dst.to_lowercase(); } } name_until_optval.to_lowercase() } fn is_single_string_keyword<'g, 's>( _global_settings: &'g GlobalSettings<'g, 's>, name: &'s str, ) -> bool { // TODO: Is this defined by an elisp variable? for single_string_name in ["plot", "name"] { if name.eq_ignore_ascii_case(single_string_name) { return true; } } false } fn is_list_of_single_string_keyword<'g, 's>( _global_settings: &'g GlobalSettings<'g, 's>, name: &'s str, ) -> bool { // TODO: Is this defined by an elisp variable? for single_string_name in ["results"] { if name.eq_ignore_ascii_case(single_string_name) { return true; } } false } fn is_list_of_objects_keyword<'g, 's>( global_settings: &'g GlobalSettings<'g, 's>, name: &'s str, ) -> bool { for parsed_keyword in global_settings.element_parsed_keywords { if name.eq_ignore_ascii_case(parsed_keyword) { return true; } } false }