use crate::{
Parser,
common::{CalendarScale, IanaParse, IanaType, PartialDateTime, parser::Integer},
icalendar::*,
jscalendar::{JSCalendarId, JSCalendarProperty, JSCalendarValue, export::ConvertedComponent},
};
use jmap_tools::{JsonPointer, JsonPointerItem, Key, Map, Value};
impl<I: JSCalendarId, B: JSCalendarId> ConvertedComponent<'_, I, B> {
pub(super) fn apply_conversions(
self,
mut component: ICalendarComponent,
ical: &mut ICalendar,
) -> ICalendarComponent {
if !self.properties.is_empty() {
component.import_properties(self.properties);
}
if !self.components.is_empty() {
let mut jcal_components = self.components.into_iter();
let mut stack = Vec::new();
loop {
if let Some(jcal_component) = jcal_components.next() {
let Some(items) = jcal_component.into_array() else {
continue;
};
let mut items = items.into_iter();
if let (
Some(Value::Str(name)),
Some(Value::Array(properties)),
Some(Value::Array(child_components)),
) = (items.next(), items.next(), items.next())
{
let mut sub_component = ICalendarComponent::new(
ICalendarComponentType::parse(name.as_bytes()).unwrap_or_else(|| {
ICalendarComponentType::Other(name.to_ascii_uppercase())
}),
);
sub_component.import_properties(properties);
if !child_components.is_empty() {
stack.push((jcal_components, component));
jcal_components = child_components.into_iter();
component = sub_component;
} else {
component
.component_ids
.push(ical.push_component(sub_component));
}
}
} else if let Some((next_components, mut parent_component)) = stack.pop() {
parent_component
.component_ids
.push(ical.push_component(component));
jcal_components = next_components;
component = parent_component;
} else {
break;
}
}
}
component
}
}
impl ICalendarComponent {
pub(super) fn import_properties<I: JSCalendarId, B: JSCalendarId>(
&mut self,
props: Vec<Value<'_, JSCalendarProperty<I>, JSCalendarValue<I, B>>>,
) {
for prop in props.into_iter().flat_map(|prop| prop.into_array()) {
let mut prop = prop.into_iter();
let Some(name) = prop.next().and_then(|v| v.into_string()).map(|name| {
ICalendarProperty::parse(name.as_bytes())
.unwrap_or(ICalendarProperty::Other(name.to_ascii_uppercase()))
}) else {
continue;
};
let Some(params) = prop.next() else {
continue;
};
let Some(value_type) =
prop.next()
.and_then(|v| v.into_string())
.map(|v| match ICalendarValueType::parse(v.as_bytes()) {
Some(v) => IanaType::Iana(v),
None => IanaType::Other(v.to_ascii_uppercase()),
})
else {
continue;
};
let (default_type, _) = name.default_types();
let convert_type = value_type
.iana()
.filter(|&v| v != &ICalendarValueType::Unknown)
.map(|v| ValueType::Ical(*v))
.unwrap_or(default_type);
let Some(values) = prop.next().and_then(|v| match v {
Value::Array(arr) => Some(
arr.into_iter()
.filter_map(|v| convert_value(v, &convert_type).ok())
.collect::<Vec<_>>(),
),
v => convert_value(v, &convert_type).ok().map(|v| vec![v]),
}) else {
continue;
};
let mut entry = ICalendarEntry::new(name);
entry.import_jcal_params(params);
entry.values = values;
if convert_type != default_type {
entry.params.push(ICalendarParameter::value(value_type));
}
self.entries.push(entry);
}
}
}
pub(super) fn convert_value<'x, I: JSCalendarId, B: JSCalendarId>(
value: Value<'x, JSCalendarProperty<I>, JSCalendarValue<I, B>>,
value_type: &'_ ValueType,
) -> Result<ICalendarValue, Value<'x, JSCalendarProperty<I>, JSCalendarValue<I, B>>> {
match value {
Value::Element(e) => match e {
JSCalendarValue::CalendarScale(v) => Ok(ICalendarValue::CalendarScale(v)),
JSCalendarValue::DateTime(v) => Ok(ICalendarValue::PartialDateTime(Box::new(v.into()))),
JSCalendarValue::Duration(v) => Ok(ICalendarValue::Duration(v)),
JSCalendarValue::Method(v) => Ok(ICalendarValue::Method(v)),
JSCalendarValue::AlertAction(_)
| JSCalendarValue::FreeBusyStatus(_)
| JSCalendarValue::ParticipantKind(_)
| JSCalendarValue::ParticipationStatus(_)
| JSCalendarValue::Privacy(_)
| JSCalendarValue::Progress(_)
| JSCalendarValue::RelativeTo(_)
| JSCalendarValue::ScheduleAgent(_)
| JSCalendarValue::EventStatus(_)
| JSCalendarValue::Frequency(_)
| JSCalendarValue::Skip(_)
| JSCalendarValue::Weekday(_)
| JSCalendarValue::Month(_)
| JSCalendarValue::LinkRelation(_)
| JSCalendarValue::Type(_)
| JSCalendarValue::Id(_)
| JSCalendarValue::BlobId(_)
| JSCalendarValue::IdReference(_) => Err(Value::Element(e)),
},
Value::Str(s) => {
match value_type {
ValueType::CalendarScale => {
if let Some(value) = CalendarScale::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::CalendarScale(value));
}
}
ValueType::Method => {
if let Some(value) = ICalendarMethod::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Method(value));
}
}
ValueType::Classification => {
if let Some(value) = ICalendarClassification::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Classification(value));
}
}
ValueType::Status => {
if let Some(value) = ICalendarStatus::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Status(value));
}
}
ValueType::Transparency => {
if let Some(value) = ICalendarTransparency::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Transparency(value));
}
}
ValueType::Action => {
if let Some(value) = ICalendarAction::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Action(value));
}
}
ValueType::BusyType => {
if let Some(value) = ICalendarFreeBusyType::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::BusyType(value));
}
}
ValueType::ParticipantType => {
if let Some(value) = ICalendarParticipantType::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::ParticipantType(value));
}
}
ValueType::ResourceType => {
if let Some(value) = ICalendarResourceType::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::ResourceType(value));
}
}
ValueType::Proximity => {
if let Some(value) = ICalendarProximityValue::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Proximity(value));
}
}
ValueType::Ical(typ) => match typ {
ICalendarValueType::Uri | ICalendarValueType::CalAddress => {
return Ok(ICalendarValue::Uri(Uri::parse(s)));
}
ICalendarValueType::Date => {
let mut dt = PartialDateTime::default();
if dt.parse_ical_date(&mut s.as_ref().as_bytes().iter().peekable()) {
return Ok(ICalendarValue::PartialDateTime(Box::new(dt)));
}
}
ICalendarValueType::Time => {
let mut dt = PartialDateTime::default();
if dt.parse_ical_time(&mut s.as_ref().as_bytes().iter().peekable()) {
return Ok(ICalendarValue::PartialDateTime(Box::new(dt)));
}
}
ICalendarValueType::DateTime => {
let mut dt = PartialDateTime::default();
if dt.parse_timestamp(&mut s.as_ref().as_bytes().iter().peekable(), false) {
return Ok(ICalendarValue::PartialDateTime(Box::new(dt)));
}
}
ICalendarValueType::UtcOffset => {
let mut dt = PartialDateTime::default();
if dt.parse_zone(&mut s.as_ref().as_bytes().iter().peekable()) {
return Ok(ICalendarValue::PartialDateTime(Box::new(dt)));
}
}
ICalendarValueType::Duration => {
if let Some(duration) = ICalendarDuration::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Duration(duration));
}
}
ICalendarValueType::Float => {
if let Ok(float) = s.as_ref().parse::<f64>() {
return Ok(ICalendarValue::Float(float));
}
}
ICalendarValueType::Integer => {
if let Some(integer) = Integer::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Integer(integer.0));
}
}
ICalendarValueType::Period => {
if let Some(period) = ICalendarPeriod::parse(s.as_ref().as_bytes()) {
return Ok(ICalendarValue::Period(period));
}
}
ICalendarValueType::Recur => {
let mut parser = Parser::new(s.as_ref());
if let Ok(recur) = parser.rrule() {
return Ok(ICalendarValue::RecurrenceRule(Box::new(recur)));
}
}
ICalendarValueType::Boolean => {
if s.eq_ignore_ascii_case("true") {
return Ok(ICalendarValue::Boolean(true));
} else if s.eq_ignore_ascii_case("false") {
return Ok(ICalendarValue::Boolean(false));
}
}
ICalendarValueType::Binary => {
return Ok(match Uri::parse(s) {
Uri::Data(data) => ICalendarValue::Binary(data.data),
Uri::Location(text) => ICalendarValue::Text(text),
});
}
ICalendarValueType::Text
| ICalendarValueType::Unknown
| ICalendarValueType::XmlReference
| ICalendarValueType::Uid => (),
},
}
Ok(ICalendarValue::Text(s.into_owned()))
}
Value::Bool(b) => Ok(ICalendarValue::Boolean(b)),
Value::Number(n) => match n.try_cast_to_i64() {
Ok(i) => Ok(ICalendarValue::Integer(i)),
Err(f) => Ok(ICalendarValue::Float(f)),
},
value => Err(value),
}
}
impl<'x, I: JSCalendarId, B: JSCalendarId> ConvertedComponent<'x, I, B> {
#[allow(clippy::type_complexity)]
pub(super) fn try_from_object(
obj: Vec<(
Key<'x, JSCalendarProperty<I>>,
Value<'x, JSCalendarProperty<I>, JSCalendarValue<I, B>>,
)>,
) -> Option<Self> {
let mut converted = ConvertedComponent {
name: ICalendarComponentType::Other(String::new()),
converted_props: Vec::new(),
converted_props_count: 0,
properties: Vec::new(),
components: Vec::new(),
};
let mut has_name = false;
for (sub_property, value) in obj {
match (sub_property, value) {
(Key::Property(JSCalendarProperty::ConvertedProperties), Value::Object(obj)) => {
for (key, value) in obj.into_vec() {
let ptr = match key {
Key::Property(JSCalendarProperty::Pointer(ptr)) => ptr,
_ => JsonPointer::parse(key.to_string().as_ref()),
};
let mut keys = Vec::with_capacity(2);
for item in ptr.into_iter() {
match item {
JsonPointerItem::Key(key) => {
let key = match &key {
Key::Borrowed(v) if v.contains('/') => v,
Key::Owned(v) if v.contains('/') => v.as_str(),
_ => {
keys.push(key);
continue;
}
};
for item in JsonPointer::parse(key).into_iter() {
keys.push(match item {
JsonPointerItem::Key(k) => k,
JsonPointerItem::Number(n) => Key::Owned(n.to_string()),
JsonPointerItem::Root | JsonPointerItem::Wildcard => {
continue;
}
});
}
}
JsonPointerItem::Number(v) => {
keys.push(Key::Owned(v.to_string()));
}
JsonPointerItem::Root | JsonPointerItem::Wildcard => (),
}
}
converted.converted_props.push((keys, value));
}
}
(Key::Property(JSCalendarProperty::Properties), Value::Array(array)) => {
converted.properties = array;
}
(Key::Property(JSCalendarProperty::Components), Value::Array(array)) => {
converted.components = array;
}
(Key::Property(JSCalendarProperty::Name), Value::Str(text)) => {
converted.name =
ICalendarComponentType::parse(text.as_bytes()).unwrap_or_else(|| {
ICalendarComponentType::Other(text.to_ascii_uppercase())
});
has_name = true;
}
_ => {}
}
}
if !converted.converted_props.is_empty() {
converted
.converted_props
.sort_unstable_by(|a, b| a.0.cmp(&b.0));
}
(!converted.properties.is_empty()
|| !converted.components.is_empty()
|| has_name
|| !converted.converted_props.is_empty())
.then_some(converted)
}
pub(super) fn build(
entries: &mut Map<'x, JSCalendarProperty<I>, JSCalendarValue<I, B>>,
) -> Option<Self> {
for (property, value) in entries.as_mut_vec() {
if let (Key::Property(JSCalendarProperty::ICalendar), Value::Object(obj)) =
(property, value)
{
return Self::try_from_object(std::mem::take(obj.as_mut_vec()));
}
}
None
}
}