use crate::{
assert_syntax,
encoding::EncodingType,
parsers::{
grammar::{
is_a_key_char, is_a_key_leading_char, is_annotation_close,
is_annotation_key_value_separator, is_annotation_open, is_annotation_value_component,
is_critical_flag, is_hyphen,
},
timezone, Cursor,
},
records::{Annotation, TimeZoneAnnotation},
ParseError, ParserResult,
};
pub(crate) struct AnnotationSet<'a, T: EncodingType> {
pub(crate) tz: Option<TimeZoneAnnotation<'a, T>>,
pub(crate) calendar: Option<&'a [T::CodeUnit]>,
}
pub(crate) fn parse_annotation_set<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
handler: impl FnMut(Annotation<'a, T>) -> Option<Annotation<'a, T>>,
) -> ParserResult<AnnotationSet<'a, T>> {
let tz_annotation = timezone::parse_ambiguous_tz_annotation(cursor)?;
let annotations = cursor.check_or(false, is_annotation_open)?;
if annotations {
let calendar = parse_annotations(cursor, handler)?;
return Ok(AnnotationSet {
tz: tz_annotation,
calendar,
});
}
Ok(AnnotationSet {
tz: tz_annotation,
calendar: None,
})
}
pub(crate) fn parse_annotations<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
mut handler: impl FnMut(Annotation<'a, T>) -> Option<Annotation<'a, T>>,
) -> ParserResult<Option<&'a [T::CodeUnit]>> {
let mut calendar: Option<Annotation<'a, T>> = None;
while cursor.check_or(false, is_annotation_open)? {
let annotation = handler(parse_kv_annotation(cursor)?);
match annotation {
Some(kv) if T::check_calendar_key(kv.key) => {
match calendar {
Some(calendar)
if calendar.value != kv.value && (calendar.critical || kv.critical) =>
{
return Err(ParseError::CriticalDuplicateCalendar)
}
None => {
calendar = Some(kv);
}
_ => {}
}
}
Some(unknown_kv) => {
if unknown_kv.critical {
return Err(ParseError::UnrecognizedCritical);
}
}
None => {}
}
}
Ok(calendar.map(|a| a.value))
}
fn parse_kv_annotation<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
) -> ParserResult<Annotation<'a, T>> {
assert_syntax!(
is_annotation_open(cursor.next_or(ParseError::AnnotationOpen)?),
AnnotationOpen
);
let critical = cursor.check_or(false, is_critical_flag)?;
cursor.advance_if(critical);
let annotation_key = parse_annotation_key(cursor)?;
assert_syntax!(
is_annotation_key_value_separator(cursor.next_or(ParseError::AnnotationKeyValueSeparator)?),
AnnotationKeyValueSeparator,
);
let annotation_value = parse_annotation_value(cursor)?;
assert_syntax!(
is_annotation_close(cursor.next_or(ParseError::AnnotationClose)?),
AnnotationClose
);
Ok(Annotation {
critical,
key: annotation_key,
value: annotation_value,
})
}
fn parse_annotation_key<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
) -> ParserResult<&'a [T::CodeUnit]> {
let key_start = cursor.pos();
assert_syntax!(
is_a_key_leading_char(cursor.next_or(ParseError::AnnotationKeyLeadingChar)?),
AnnotationKeyLeadingChar,
);
while let Some(potential_key_char) = cursor.next()? {
if cursor.check_or(false, is_annotation_key_value_separator)? {
return cursor
.slice(key_start, cursor.pos())
.ok_or(ParseError::ImplAssert);
}
assert_syntax!(is_a_key_char(potential_key_char), AnnotationKeyChar);
}
Err(ParseError::AnnotationChar)
}
fn parse_annotation_value<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
) -> ParserResult<&'a [T::CodeUnit]> {
let value_start = cursor.pos();
cursor.advance();
while let Some(potential_value_char) = cursor.next()? {
if cursor.check_or(false, is_annotation_close)? {
return cursor
.slice(value_start, cursor.pos())
.ok_or(ParseError::ImplAssert);
}
if is_hyphen(potential_value_char) {
assert_syntax!(
cursor.peek()?.is_some_and(is_annotation_value_component),
AnnotationValueCharPostHyphen,
);
cursor.advance();
continue;
}
assert_syntax!(
is_annotation_value_component(potential_value_char),
AnnotationValueChar,
);
}
Err(ParseError::AnnotationValueChar)
}