use core::fmt;
use alloc::{borrow::Cow, string::ToString, vec, vec::Vec};
use crate::{
param::VcardParam,
prop::VcardProp,
tree::{
codec::{
VcardCodec,
escape::{escape_param, escape_with},
mode::VcardEscaper,
},
cst::VcardCst,
leaf::{VcardLeaf, VcardValueLeaf},
line::VcardLine,
param::node::VcardParamNode,
value::node::VcardValueNode,
wire::VcardWire,
},
validator::VcardValid,
vcard::Vcard,
};
impl Vcard<'_> {
pub fn encode(&self) -> VcardCst<'static> {
let escaper = VcardEscaper::for_version(self.version);
let mut cst = VcardCst::v4();
cst.props[0] = VcardLine::text("VERSION", self.version.to_string());
cst.props
.extend(self.properties.iter().map(|prop| prop.encode(escaper)));
cst
}
}
impl<'a> From<Vcard<'a>> for VcardCst<'static> {
fn from(card: Vcard<'a>) -> Self {
card.encode()
}
}
impl From<VcardValid<Vcard<'_>>> for VcardCst<'static> {
fn from(card: VcardValid<Vcard<'_>>) -> Self {
card.into_inner().encode()
}
}
impl VcardProp<'_> {
pub fn encode(&self, escaper: VcardEscaper) -> VcardLine<'static> {
VcardLine {
name: VcardLeaf::from(self.name.to_string()),
params: self
.params
.iter()
.map(|param| param.encode(escaper))
.collect(),
value: self.value.encode(escaper),
eol: VcardLeaf::from("\r\n".to_string()),
wire: VcardWire::default(),
}
}
}
impl VcardParam<'_> {
pub fn encode(&self, escaper: VcardEscaper) -> VcardParamNode<'static> {
use crate::param::VcardParamKind::*;
match self {
VcardParam::Language(v) => param_scalar(&Language, v, escaper),
VcardParam::Charset(v) => param_scalar(&Charset, v, escaper),
VcardParam::Encoding(v) => param_scalar(&Encoding, v, escaper),
VcardParam::Value(v) => param_scalar(&Value, v, escaper),
VcardParam::Pref(v) => param_scalar(&Pref, v, escaper),
VcardParam::AltId(v) => param_scalar(&AltId, v, escaper),
VcardParam::Pid(vs) => param_list(&Pid, vs, escaper),
VcardParam::Type(vs) => param_list(&Type, vs, escaper),
VcardParam::MediaType(v) => param_scalar(&MediaType, v, escaper),
VcardParam::CalScale(v) => param_scalar(&CalScale, v, escaper),
VcardParam::SortAs(vs) => param_list(&SortAs, vs, escaper),
VcardParam::Geo(v) => param_scalar(&Geo, v, escaper),
VcardParam::Tz(v) => param_scalar(&Tz, v, escaper),
VcardParam::Label(v) => param_scalar(&Label, v, escaper),
VcardParam::Author(v) => param_scalar(&Author, v, escaper),
VcardParam::AuthorName(v) => param_scalar(&AuthorName, v, escaper),
VcardParam::Created(v) => param_scalar(&Created, v, escaper),
VcardParam::Derived(v) => param_scalar(&Derived, v, escaper),
VcardParam::Jsptr(v) => param_scalar(&Jsptr, v, escaper),
VcardParam::Phonetic(v) => param_scalar(&Phonetic, v, escaper),
VcardParam::PropId(v) => param_scalar(&PropId, v, escaper),
VcardParam::Script(v) => param_scalar(&Script, v, escaper),
VcardParam::ServiceType(v) => param_scalar(&ServiceType, v, escaper),
VcardParam::Username(v) => param_scalar(&Username, v, escaper),
VcardParam::Unknown { name, values } => param_list(name, values, escaper),
}
}
}
impl fmt::Display for Vcard<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.encode())
}
}
pub(crate) fn scalar_node(value: &str, escaper: VcardEscaper) -> VcardValueNode<'static> {
VcardValueNode::from_components(vec![encode_component(&[value], escaper)], escaper)
}
pub(crate) fn verbatim_node(value: &str, escaper: VcardEscaper) -> VcardValueNode<'static> {
VcardValueNode::from_raw(value.as_bytes().to_vec(), escaper)
}
pub(crate) fn encode_component<S: AsRef<str>>(
values: &[S],
escaper: VcardEscaper,
) -> Vec<VcardValueLeaf<'static>> {
values.iter().map(|v| encode_leaf(v, escaper)).collect()
}
pub(crate) fn encode_bytes_component<B: AsRef<[u8]>>(
values: &[B],
escaper: VcardEscaper,
) -> Vec<VcardValueLeaf<'static>> {
values
.iter()
.map(|v| VcardValueLeaf::from(escape_with(v.as_ref(), escaper).into_owned()))
.collect()
}
pub(crate) fn encode_leaf<S: AsRef<str>>(
value: S,
escaper: VcardEscaper,
) -> VcardValueLeaf<'static> {
VcardValueLeaf::from(escape_with(value.as_ref().as_bytes(), escaper).into_owned())
}
fn param_scalar(name: &str, value: &str, escaper: VcardEscaper) -> VcardParamNode<'static> {
VcardParamNode {
name: VcardLeaf::from(name.to_string()),
values: vec![VcardLeaf::from(escape_param(value, escaper).into_owned())],
escaper,
}
}
fn param_list(
name: &str,
values: &[Cow<'_, str>],
escaper: VcardEscaper,
) -> VcardParamNode<'static> {
VcardParamNode {
name: VcardLeaf::from(name.to_string()),
values: values
.iter()
.map(|v| VcardLeaf::from(escape_param(v, escaper).into_owned()))
.collect(),
escaper,
}
}
#[cfg(test)]
mod tests {
use alloc::{borrow::Cow, string::ToString, vec};
use crate::{
param::VcardParam,
tree::{
codec::{VcardCodec, mode::VcardEscaper},
cst::VcardCst,
},
value::{n::VcardN, text::VcardText},
};
#[test]
fn encodes_a_text_value_escaping_it() {
let node = VcardText(Cow::Borrowed("hi, there")).encode(VcardEscaper::V4_0);
assert_eq!(node.to_string(), r"hi\, there");
}
#[test]
fn encodes_the_structured_n_value_with_all_components() {
let n = VcardN {
family: vec![Cow::Borrowed("Doe")],
..Default::default()
};
assert_eq!(n.encode(VcardEscaper::V4_0).to_string(), "Doe;;;;");
}
#[test]
fn encodes_the_geo_pair_in_the_cards_own_version() {
let cst = VcardCst::parse("BEGIN:VCARD\r\nVERSION:2.1\r\nGEO:37.0,-122.0\r\nEND:VCARD\r\n")
.unwrap();
let card = cst.decode();
assert!(
card.to_string().contains("GEO:37.0,-122.0\r\n"),
"{}",
card.to_string(),
);
let cst = VcardCst::parse("BEGIN:VCARD\r\nVERSION:3.0\r\nGEO:37.0;-122.0\r\nEND:VCARD\r\n")
.unwrap();
let card = cst.decode();
assert!(
card.to_string().contains("GEO:37.0;-122.0\r\n"),
"{}",
card.to_string(),
);
}
#[test]
fn encodes_the_rfc_6868_parameter_sequences() {
let param = VcardParam::Label(Cow::Borrowed("a\nb^c\"d"));
assert_eq!(
param.encode(VcardEscaper::V4_0).to_string(),
"LABEL=a^nb^^c^'d",
);
}
#[test]
fn quotes_a_parameter_value_carrying_a_delimiter() {
let param = VcardParam::Geo(Cow::Borrowed("geo:37.386,-122.083"));
assert_eq!(
param.encode(VcardEscaper::V4_0).to_string(),
"GEO=\"geo:37.386,-122.083\"",
);
}
#[test]
fn writes_a_pre_4_0_parameter_unencoded() {
let param = VcardParam::Label(Cow::Borrowed("a^b"));
assert_eq!(param.encode(VcardEscaper::V3_0).to_string(), "LABEL=a^b");
}
#[test]
fn round_trips_a_parameter_byte_for_byte() {
let input = concat!(
"BEGIN:VCARD\r\n",
"VERSION:4.0\r\n",
"FN;LANGUAGE=en;GEO=\"geo:37.386,-122.083\"",
";X-PATH=\"C:\\temp\";X-NOTE=a^nb^^c^'d:Ada\r\n",
"END:VCARD\r\n",
);
let cst = VcardCst::parse(input).unwrap();
assert_eq!(cst.decode().to_string(), input);
}
}