1use core::fmt;
18
19use alloc::{borrow::Cow, string::ToString, vec, vec::Vec};
20
21use crate::{
22 param::VcardParam,
23 prop::VcardProp,
24 tree::{
25 codec::{VcardCodec, escape::escape_with, mode::VcardEscaper},
26 cst::VcardCst,
27 leaf::{VcardLeaf, VcardValueLeaf},
28 line::VcardLine,
29 param::node::VcardParamNode,
30 value::node::VcardValueNode,
31 },
32 vcard::Vcard,
33};
34
35impl Vcard<'_> {
36 pub fn encode(&self) -> VcardCst<'static> {
38 let escaper = VcardEscaper::for_version(self.version);
39
40 let mut cst = VcardCst::v4();
41 cst.props[0] = VcardLine::text("VERSION", self.version.to_string());
45 cst.props
46 .extend(self.properties.iter().map(|prop| prop.encode(escaper)));
47
48 cst
49 }
50}
51
52impl<'a> From<Vcard<'a>> for VcardCst<'static> {
53 fn from(card: Vcard<'a>) -> Self {
54 card.encode()
55 }
56}
57
58impl VcardProp<'_> {
59 pub fn encode(&self, escaper: VcardEscaper) -> VcardLine<'static> {
62 VcardLine {
63 name: VcardLeaf::from(self.name.to_string()),
64 params: self.params.iter().map(VcardParam::encode).collect(),
65 value: self.value.encode(escaper),
66 eol: VcardLeaf::from("\r\n".to_string()),
67 }
68 }
69}
70
71impl VcardParam<'_> {
72 pub fn encode(&self) -> VcardParamNode<'static> {
74 use crate::param::VcardParamKind::*;
75
76 match self {
77 VcardParam::Language(v) => param_scalar(&Language, v),
78 VcardParam::Charset(v) => param_scalar(&Charset, v),
79 VcardParam::Encoding(v) => param_scalar(&Encoding, v),
80 VcardParam::Value(v) => param_scalar(&Value, v),
81 VcardParam::Pref(v) => param_scalar(&Pref, v),
82 VcardParam::AltId(v) => param_scalar(&AltId, v),
83 VcardParam::Pid(vs) => param_list(&Pid, vs),
84 VcardParam::Type(vs) => param_list(&Type, vs),
85 VcardParam::MediaType(v) => param_scalar(&MediaType, v),
86 VcardParam::CalScale(v) => param_scalar(&CalScale, v),
87 VcardParam::SortAs(vs) => param_list(&SortAs, vs),
88 VcardParam::Geo(v) => param_scalar(&Geo, v),
89 VcardParam::Tz(v) => param_scalar(&Tz, v),
90 VcardParam::Label(v) => param_scalar(&Label, v),
91 VcardParam::Author(v) => param_scalar(&Author, v),
92 VcardParam::AuthorName(v) => param_scalar(&AuthorName, v),
93 VcardParam::Created(v) => param_scalar(&Created, v),
94 VcardParam::Derived(v) => param_scalar(&Derived, v),
95 VcardParam::Jsptr(v) => param_scalar(&Jsptr, v),
96 VcardParam::Phonetic(v) => param_scalar(&Phonetic, v),
97 VcardParam::PropId(v) => param_scalar(&PropId, v),
98 VcardParam::Script(v) => param_scalar(&Script, v),
99 VcardParam::ServiceType(v) => param_scalar(&ServiceType, v),
100 VcardParam::Username(v) => param_scalar(&Username, v),
101
102 VcardParam::Unknown { name, values } => VcardParamNode {
103 name: VcardLeaf::from(name.to_string()),
104 values: values
105 .iter()
106 .map(|v| VcardLeaf::from(v.to_string()))
107 .collect(),
108 },
109 }
110 }
111}
112
113impl fmt::Display for Vcard<'_> {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "{}", self.encode())
117 }
118}
119
120pub(crate) fn scalar_node(value: &str, escaper: VcardEscaper) -> VcardValueNode<'static> {
123 VcardValueNode::from_components(vec![encode_component(&[value], escaper)], escaper)
124}
125
126pub(crate) fn encode_component<S: AsRef<str>>(
128 values: &[S],
129 escaper: VcardEscaper,
130) -> Vec<VcardValueLeaf<'static>> {
131 values.iter().map(|v| encode_leaf(v, escaper)).collect()
132}
133
134pub(crate) fn encode_leaf<S: AsRef<str>>(
138 value: S,
139 escaper: VcardEscaper,
140) -> VcardValueLeaf<'static> {
141 VcardValueLeaf::from(escape_with(value.as_ref().as_bytes(), escaper).into_owned())
142}
143
144fn param_scalar(name: &str, value: &str) -> VcardParamNode<'static> {
147 VcardParamNode {
148 name: VcardLeaf::from(name.to_string()),
149 values: vec![VcardLeaf::from(value.to_string())],
150 }
151}
152
153fn param_list(name: &str, values: &[Cow<'_, str>]) -> VcardParamNode<'static> {
155 VcardParamNode {
156 name: VcardLeaf::from(name.to_string()),
157 values: values
158 .iter()
159 .map(|v| VcardLeaf::from(v.to_string()))
160 .collect(),
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use alloc::{borrow::Cow, string::ToString, vec};
167
168 use crate::{
169 tree::{
170 codec::{VcardCodec, mode::VcardEscaper},
171 cst::VcardCst,
172 },
173 value::{n::VcardN, text::VcardText},
174 };
175
176 #[test]
177 fn encodes_a_text_value_escaping_it() {
178 let node = VcardText(Cow::Borrowed("hi, there")).encode(VcardEscaper::Modern);
179 assert_eq!(node.to_string(), r"hi\, there");
180 }
181
182 #[test]
183 fn encodes_the_structured_n_value_with_all_components() {
184 let n = VcardN {
185 family: vec![Cow::Borrowed("Doe")],
186 ..Default::default()
187 };
188 assert_eq!(n.encode(VcardEscaper::Modern).to_string(), "Doe;;;;");
189 }
190
191 #[test]
192 fn encodes_the_geo_pair_in_the_cards_own_version() {
193 let cst = VcardCst::parse("BEGIN:VCARD\r\nVERSION:2.1\r\nGEO:37.0,-122.0\r\nEND:VCARD\r\n")
196 .unwrap();
197 let card = cst.decode();
198 assert!(
199 card.to_string().contains("GEO:37.0,-122.0\r\n"),
200 "{}",
201 card.to_string(),
202 );
203
204 let cst = VcardCst::parse("BEGIN:VCARD\r\nVERSION:3.0\r\nGEO:37.0;-122.0\r\nEND:VCARD\r\n")
206 .unwrap();
207 let card = cst.decode();
208 assert!(
209 card.to_string().contains("GEO:37.0;-122.0\r\n"),
210 "{}",
211 card.to_string(),
212 );
213 }
214}