1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! # Parameter node
//!
//! The raw, byte-faithful parameter on the syntax side.
//!
//! [`IcalParamNode`] is the syntactic peer of the decoded
//! [`IcalParam`](crate::param::IcalParam): a name leaf and its raw value
//! leaves, parsed from and serialized back to the wire verbatim. The per-name
//! parameter lenses that give it meaning live alongside in
//! [`crate::tree::param`].
use core::fmt;
use alloc::vec::Vec;
use crate::tree::{codec::mode::Escaper, leaf::IcalLeaf};
/// One raw parameter: a name and its `,`-separated raw values (empty when the
/// parameter has no `=` list). The syntactic peer of the decoded
/// [`IcalParam`](crate::param::IcalParam).
#[derive(Clone, Debug)]
pub struct IcalParamNode<'a> {
/// The parameter name leaf.
pub name: IcalLeaf<'a>,
/// The raw value leaves.
pub values: Vec<IcalLeaf<'a>>,
/// Which version's parameter encoding rules the values are written in.
/// Stamped by the parser once `VERSION` is known, as on a value node.
pub escaper: Escaper,
}
impl<'a> IcalParamNode<'a> {
/// Parse one `name=value,value` parameter (commas outside quotes split).
pub fn parse(param: &'a str) -> Self {
match param.split_once('=') {
Some((name, values)) => Self {
name: IcalLeaf::from(name),
values: split_param_values(values)
.into_iter()
.map(IcalLeaf::from)
.collect(),
escaper: Escaper::default(),
},
None => Self {
name: IcalLeaf::from(param),
values: Vec::new(),
escaper: Escaper::default(),
},
}
}
/// Convert into an owned parameter node (`'static`).
pub(crate) fn into_static(self) -> IcalParamNode<'static> {
IcalParamNode {
name: self.name.into_static(),
values: self.values.into_iter().map(IcalLeaf::into_static).collect(),
escaper: self.escaper,
}
}
/// Serialize the parameter (`name` or `name=value,value`) into `out`,
/// without the intermediate `String` a `Display`-based path would allocate.
pub(crate) fn write_bytes(&self, out: &mut Vec<u8>) {
out.extend_from_slice(self.name.get().as_bytes());
if let Some((first, rest)) = self.values.split_first() {
out.push(b'=');
out.extend_from_slice(first.get().as_bytes());
for value in rest {
out.push(b',');
out.extend_from_slice(value.get().as_bytes());
}
}
}
}
impl fmt::Display for IcalParamNode<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name.get())?;
if let Some((first, rest)) = self.values.split_first() {
write!(f, "={}", first.get())?;
for value in rest {
write!(f, ",{}", value.get())?;
}
}
Ok(())
}
}
/// Split a parameter value list on commas outside double quotes.
fn split_param_values(values: &str) -> Vec<&str> {
let bytes = values.as_bytes();
let mut pieces = Vec::new();
let mut start = 0;
let mut quoted = false;
for (i, &byte) in bytes.iter().enumerate() {
match byte {
b'"' => quoted = !quoted,
b',' if !quoted => {
pieces.push(&values[start..i]);
start = i + 1;
}
_ => {}
}
}
pieces.push(&values[start..]);
pieces
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use crate::tree::param::node::IcalParamNode;
#[test]
fn parses_quoted_values_then_round_trips() {
let node = IcalParamNode::parse(r#"TYPE=work,"a,b""#);
assert_eq!(node.values.len(), 2);
assert_eq!(node.to_string(), r#"TYPE=work,"a,b""#);
}
}