1use core::fmt;
12
13use alloc::vec::Vec;
14
15use crate::tree::{codec::mode::Escaper, leaf::IcalLeaf};
16
17#[derive(Clone, Debug)]
21pub struct IcalParamNode<'a> {
22 pub name: IcalLeaf<'a>,
24 pub values: Vec<IcalLeaf<'a>>,
26 pub escaper: Escaper,
29}
30
31impl<'a> IcalParamNode<'a> {
32 pub fn parse(param: &'a str) -> Self {
34 match param.split_once('=') {
35 Some((name, values)) => Self {
36 name: IcalLeaf::from(name),
37 values: split_param_values(values)
38 .into_iter()
39 .map(IcalLeaf::from)
40 .collect(),
41 escaper: Escaper::default(),
42 },
43 None => Self {
44 name: IcalLeaf::from(param),
45 values: Vec::new(),
46 escaper: Escaper::default(),
47 },
48 }
49 }
50
51 pub(crate) fn into_static(self) -> IcalParamNode<'static> {
53 IcalParamNode {
54 name: self.name.into_static(),
55 values: self.values.into_iter().map(IcalLeaf::into_static).collect(),
56 escaper: self.escaper,
57 }
58 }
59
60 pub(crate) fn write_bytes(&self, out: &mut Vec<u8>) {
63 out.extend_from_slice(self.name.get().as_bytes());
64
65 if let Some((first, rest)) = self.values.split_first() {
66 out.push(b'=');
67 out.extend_from_slice(first.get().as_bytes());
68 for value in rest {
69 out.push(b',');
70 out.extend_from_slice(value.get().as_bytes());
71 }
72 }
73 }
74}
75
76impl fmt::Display for IcalParamNode<'_> {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.write_str(self.name.get())?;
79
80 if let Some((first, rest)) = self.values.split_first() {
81 write!(f, "={}", first.get())?;
82
83 for value in rest {
84 write!(f, ",{}", value.get())?;
85 }
86 }
87
88 Ok(())
89 }
90}
91
92fn split_param_values(values: &str) -> Vec<&str> {
94 let bytes = values.as_bytes();
95 let mut pieces = Vec::new();
96 let mut start = 0;
97 let mut quoted = false;
98
99 for (i, &byte) in bytes.iter().enumerate() {
100 match byte {
101 b'"' => quoted = !quoted,
102 b',' if !quoted => {
103 pieces.push(&values[start..i]);
104 start = i + 1;
105 }
106 _ => {}
107 }
108 }
109
110 pieces.push(&values[start..]);
111 pieces
112}
113
114#[cfg(test)]
115mod tests {
116 use alloc::string::ToString;
117
118 use crate::tree::param::node::IcalParamNode;
119
120 #[test]
121 fn parses_quoted_values_then_round_trips() {
122 let node = IcalParamNode::parse(r#"TYPE=work,"a,b""#);
123 assert_eq!(node.values.len(), 2);
124 assert_eq!(node.to_string(), r#"TYPE=work,"a,b""#);
125 }
126}