use alloc::{
borrow::Cow,
format,
string::{String, ToString},
vec,
vec::Vec,
};
use serde_json::{Map, Value};
use crate::{
jcard::datetime::{extended_to_basic, offset_to_basic},
param::{VcardParam, VcardParamKind},
prop::spec::prop_spec,
prop::{VcardProp, VcardPropName},
value::{
VcardValue, VcardValueKind, VcardValueUnknown,
adr::VcardAdr,
binary::VcardBinary,
client_pid_map::VcardClientPidMap,
datetime::{VcardDateAndOrTime, VcardTimestamp},
gender::VcardGender,
geo::VcardGeo,
language::VcardLanguageTag,
n::VcardN,
org::VcardOrg,
text::{VcardText, VcardTextList},
uri::VcardUri,
utc_offset::VcardUtcOffset,
},
version::VcardVersion,
};
impl<'a> VcardProp<'a> {
pub(crate) fn from_jcard(
name: &'a str,
params: &'a Map<String, Value>,
slot: &'a str,
values: &'a [Value],
version: VcardVersion,
) -> Self {
let group = params.get("group").and_then(Value::as_str);
let name = match group {
Some(group) => format!("{group}.{}", name.to_ascii_uppercase()),
None => name.to_ascii_uppercase(),
};
let name = VcardPropName::from(Cow::Owned(name));
let mut prop_params: Vec<VcardParam<'_>> = params
.iter()
.filter(|(key, _)| !key.eq_ignore_ascii_case("group"))
.map(|(key, value)| VcardParam::from_jcard(key, value))
.collect();
let kind = match &name {
VcardPropName::Kind(prop) => {
let spec = prop_spec(*prop);
let default = (spec.value)(version, None);
let declared = slot.parse::<VcardValueKind>().ok().filter(|declared| {
!(*declared == VcardValueKind::Text && default.is_text_shaped())
});
if declared.is_some() && declared != Some(default) {
prop_params.push(VcardParam::Value(Cow::Borrowed(slot)));
}
Some((spec.value)(version, declared))
}
VcardPropName::Unknown(_) => {
if !slot.eq_ignore_ascii_case("unknown") {
prop_params.push(VcardParam::Value(Cow::Borrowed(slot)));
}
None
}
};
let value = match kind {
Some(kind) => VcardValue::from_jcard(kind, values),
None => VcardValue::Unknown(VcardValueUnknown::from_jcard(values)),
};
Self {
name,
params: prop_params,
value,
}
}
}
impl<'a> VcardParam<'a> {
pub(crate) fn from_jcard(key: &'a str, value: &'a Value) -> Self {
let Ok(kind) = key.parse::<VcardParamKind>() else {
return VcardParam::Unknown {
name: Cow::Owned(key.to_ascii_uppercase()),
values: scalars(value),
};
};
match kind {
VcardParamKind::AltId => VcardParam::AltId(scalar(value)),
VcardParamKind::CalScale => VcardParam::CalScale(scalar(value)),
VcardParamKind::Charset => VcardParam::Charset(scalar(value)),
VcardParamKind::Encoding => VcardParam::Encoding(scalar(value)),
VcardParamKind::Geo => VcardParam::Geo(scalar(value)),
VcardParamKind::Label => VcardParam::Label(scalar(value)),
VcardParamKind::Language => VcardParam::Language(scalar(value)),
VcardParamKind::MediaType => VcardParam::MediaType(scalar(value)),
VcardParamKind::Pid => VcardParam::Pid(scalars(value)),
VcardParamKind::Pref => VcardParam::Pref(scalar(value)),
VcardParamKind::SortAs => VcardParam::SortAs(scalars(value)),
VcardParamKind::Type => VcardParam::Type(scalars(value)),
VcardParamKind::Tz => VcardParam::Tz(scalar(value)),
VcardParamKind::Value => VcardParam::Value(scalar(value)),
VcardParamKind::Author => VcardParam::Author(scalar(value)),
VcardParamKind::AuthorName => VcardParam::AuthorName(scalar(value)),
VcardParamKind::Created => VcardParam::Created(scalar(value)),
VcardParamKind::Derived => VcardParam::Derived(scalar(value)),
VcardParamKind::Jsptr => VcardParam::Jsptr(scalar(value)),
VcardParamKind::Phonetic => VcardParam::Phonetic(scalar(value)),
VcardParamKind::PropId => VcardParam::PropId(scalar(value)),
VcardParamKind::Script => VcardParam::Script(scalar(value)),
VcardParamKind::ServiceType => VcardParam::ServiceType(scalar(value)),
VcardParamKind::Username => VcardParam::Username(scalar(value)),
}
}
}
impl<'a> VcardValue<'a> {
fn from_jcard(kind: VcardValueKind, values: &'a [Value]) -> Self {
match kind {
VcardValueKind::Text => VcardValue::Text(VcardText(first_scalar(values))),
VcardValueKind::TextList => {
VcardValue::TextList(VcardTextList(values.iter().flat_map(scalars).collect()))
}
VcardValueKind::Uri => VcardValue::Uri(VcardUri(first_scalar(values))),
VcardValueKind::DateAndOrTime => VcardValue::DateAndOrTime(VcardDateAndOrTime(
extended_to_basic(first_scalar(values)),
)),
VcardValueKind::Timestamp => {
VcardValue::Timestamp(VcardTimestamp(extended_to_basic(first_scalar(values))))
}
VcardValueKind::LanguageTag => {
VcardValue::LanguageTag(VcardLanguageTag(first_scalar(values)))
}
VcardValueKind::UtcOffset => {
VcardValue::UtcOffset(VcardUtcOffset(offset_to_basic(first_scalar(values))))
}
VcardValueKind::N => {
let mut components = structured(values.first()).into_iter();
VcardValue::N(VcardN {
family: components.next().unwrap_or_default(),
given: components.next().unwrap_or_default(),
additional: components.next().unwrap_or_default(),
prefixes: components.next().unwrap_or_default(),
suffixes: components.next().unwrap_or_default(),
})
}
VcardValueKind::Adr => {
let mut components = structured(values.first()).into_iter();
let mut next = || components.next().unwrap_or_default();
VcardValue::Adr(VcardAdr {
po_box: next(),
extended: next(),
street: next(),
locality: next(),
region: next(),
postal_code: next(),
country: next(),
room: next(),
apartment: next(),
floor: next(),
street_number: next(),
street_name: next(),
building: next(),
block: next(),
subdistrict: next(),
district: next(),
landmark: next(),
direction: next(),
})
}
VcardValueKind::Gender => {
let mut components = structured(values.first()).into_iter();
VcardValue::Gender(VcardGender {
sex: first_of(components.next()),
identity: first_of(components.next()),
})
}
VcardValueKind::Org => VcardValue::Org(VcardOrg(
structured(values.first())
.into_iter()
.map(|unit| first_of(Some(unit)))
.collect(),
)),
VcardValueKind::ClientPidMap => {
let mut components = structured(values.first()).into_iter();
VcardValue::ClientPidMap(VcardClientPidMap {
id: first_of(components.next()),
uri: first_of(components.next()),
})
}
VcardValueKind::Geo => {
let mut components = structured(values.first()).into_iter();
VcardValue::Geo(VcardGeo {
latitude: first_of(components.next()),
longitude: first_of(components.next()),
})
}
VcardValueKind::Binary => VcardValue::Binary(VcardBinary::Base64(first_scalar(values))),
}
}
}
impl<'a> VcardValueUnknown<'a> {
fn from_jcard(values: &'a [Value]) -> Self {
let components = match values {
[] => vec![vec![Cow::Borrowed("")]],
[Value::Array(groups)] if groups.iter().any(Value::is_array) => {
groups.iter().map(scalars).collect()
}
[Value::Array(group)] => vec![group.iter().map(scalar).collect()],
[value] => vec![vec![scalar(value)]],
values => values.iter().map(scalars).collect(),
};
Self { components }
}
}
impl VcardValueKind {
fn is_text_shaped(self) -> bool {
matches!(
self,
VcardValueKind::Text
| VcardValueKind::TextList
| VcardValueKind::N
| VcardValueKind::Adr
| VcardValueKind::Gender
| VcardValueKind::Org
| VcardValueKind::ClientPidMap
| VcardValueKind::Geo,
)
}
}
fn structured(value: Option<&Value>) -> Vec<Vec<Cow<'_, str>>> {
match value {
None => Vec::new(),
Some(Value::Array(components)) => components.iter().map(scalars).collect(),
Some(value) => vec![vec![scalar(value)]],
}
}
fn first_of(group: Option<Vec<Cow<'_, str>>>) -> Cow<'_, str> {
group
.and_then(|group| group.into_iter().next())
.unwrap_or_default()
}
fn first_scalar(values: &[Value]) -> Cow<'_, str> {
values.first().map(scalar).unwrap_or_default()
}
fn scalar(value: &Value) -> Cow<'_, str> {
match value {
Value::String(value) => Cow::Borrowed(value.as_str()),
Value::Number(_) | Value::Bool(_) => Cow::Owned(value.to_string()),
Value::Array(values) => values.first().map(scalar).unwrap_or_default(),
Value::Null | Value::Object(_) => Cow::Borrowed(""),
}
}
fn scalars(value: &Value) -> Vec<Cow<'_, str>> {
match value {
Value::Array(values) => values.iter().map(scalar).collect(),
value => vec![scalar(value)],
}
}