use core::fmt;
use alloc::{borrow::Cow, string::String, vec::Vec};
use crate::tree::{
codec::{
encode::{encode_bytes_component, encode_component},
mode::Escaper,
unescape::{unescape_bytes, unescape_with},
},
leaf::IcalValueLeaf,
};
#[derive(Clone, Debug, Default)]
pub struct IcalValueNode<'a> {
raw: Option<Cow<'a, [u8]>>,
components: Vec<Vec<IcalValueLeaf<'a>>>,
pub escaper: Escaper,
}
impl<'a> IcalValueNode<'a> {
pub fn parse(value: &'a [u8]) -> Self {
Self {
raw: Some(Cow::Borrowed(value)),
components: Vec::new(),
escaper: Escaper::default(),
}
}
pub(crate) fn from_components(
components: Vec<Vec<IcalValueLeaf<'a>>>,
escaper: Escaper,
) -> Self {
Self {
raw: None,
components,
escaper,
}
}
pub fn component_count(&self) -> usize {
match &self.raw {
Some(raw) => {
let mut count = 0;
split_on(raw, b';', |_| count += 1);
count
}
None => self.components.len(),
}
}
pub fn decode(&self) -> Cow<'_, str> {
match &self.raw {
Some(raw) => unescape_with(raw, self.escaper),
None => {
let joined = self.joined_bytes();
Cow::Owned(unescape_with(&joined, self.escaper).into_owned())
}
}
}
pub fn decode_list(&self) -> Vec<Cow<'_, str>> {
let mut values = Vec::new();
match &self.raw {
Some(raw) => split_on(raw, b',', |value| {
values.push(unescape_with(value, self.escaper))
}),
None => {
let joined = self.joined_bytes();
split_on(&joined, b',', |value| {
values.push(Cow::Owned(unescape_with(value, self.escaper).into_owned()))
});
}
}
values
}
pub(crate) fn raw_list(&self) -> Vec<Vec<u8>> {
let mut values = Vec::new();
match &self.raw {
Some(raw) => split_on(raw, b',', |value| values.push(value.to_vec())),
None => {
let joined = self.joined_bytes();
split_on(&joined, b',', |value| values.push(value.to_vec()));
}
}
values
}
pub fn decode_bytes(&self) -> Cow<'_, [u8]> {
match &self.raw {
Some(raw) => unescape_bytes(raw, self.escaper),
None => {
let joined = self.joined_bytes();
Cow::Owned(unescape_bytes(&joined, self.escaper).into_owned())
}
}
}
pub fn decode_component(&self, i: usize) -> Cow<'_, str> {
match self.component_at(i) {
Some(Component::Raw(bytes)) => unescape_with(bytes, self.escaper),
Some(Component::Split(leaves)) => {
if leaves.len() <= 1 {
return leaves
.first()
.map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
.unwrap_or(Cow::Borrowed(""));
}
let mut raw = Vec::new();
for (j, leaf) in leaves.iter().enumerate() {
if j > 0 {
raw.push(b',');
}
raw.extend_from_slice(leaf.as_bytes());
}
Cow::Owned(unescape_with(&raw, self.escaper).into_owned())
}
None => Cow::Borrowed(""),
}
}
pub fn decode_component_list(&self, i: usize) -> Vec<Cow<'_, str>> {
match self.component_at(i) {
Some(Component::Raw(bytes)) => {
let mut values = Vec::new();
split_on(bytes, b',', |value| {
values.push(unescape_with(value, self.escaper))
});
values
}
Some(Component::Split(leaves)) => leaves
.iter()
.map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
.collect(),
None => Vec::new(),
}
}
pub(crate) fn from_raw(bytes: Vec<u8>, escaper: Escaper) -> IcalValueNode<'static> {
IcalValueNode {
raw: Some(Cow::Owned(bytes)),
components: Vec::new(),
escaper,
}
}
pub(crate) fn first_value_bytes(&self) -> &[u8] {
match self.component_at(0) {
Some(Component::Raw(bytes)) => first_value(bytes),
Some(Component::Split(leaves)) => {
leaves.first().map(|leaf| leaf.as_bytes()).unwrap_or(b"")
}
None => b"",
}
}
pub fn set<S: AsRef<str>>(&mut self, values: &[S]) {
self.raw = None;
self.components.clear();
self.components.push(encode_component(values, self.escaper));
}
pub fn set_bytes<B: AsRef<[u8]>>(&mut self, values: &[B]) {
self.raw = None;
self.components.clear();
self.components
.push(encode_bytes_component(values, self.escaper));
}
pub fn set_component<S: AsRef<str>>(&mut self, i: usize, values: &[S]) {
self.materialize();
while self.components.len() <= i {
self.components.push(Vec::new());
}
self.components[i] = encode_component(values, self.escaper);
}
pub fn set_component_bytes<B: AsRef<[u8]>>(&mut self, i: usize, values: &[B]) {
self.materialize();
while self.components.len() <= i {
self.components.push(Vec::new());
}
self.components[i] = encode_bytes_component(values, self.escaper);
}
pub(crate) fn write_bytes(&self, out: &mut Vec<u8>) {
if let Some(raw) = &self.raw {
out.extend_from_slice(raw);
return;
}
for (i, component) in self.components.iter().enumerate() {
if i > 0 {
out.push(b';');
}
for (j, leaf) in component.iter().enumerate() {
if j > 0 {
out.push(b',');
}
out.extend_from_slice(leaf.as_bytes());
}
}
}
pub(crate) fn into_static(self) -> IcalValueNode<'static> {
match self.raw {
Some(raw) => IcalValueNode {
raw: Some(Cow::Owned(raw.into_owned())),
components: Vec::new(),
escaper: self.escaper,
},
None => IcalValueNode {
raw: None,
components: self
.components
.into_iter()
.map(|component| {
component
.into_iter()
.map(IcalValueLeaf::into_static)
.collect()
})
.collect(),
escaper: self.escaper,
},
}
}
fn joined_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
self.write_bytes(&mut out);
out
}
fn component_at(&self, i: usize) -> Option<Component<'_, 'a>> {
match &self.raw {
Some(raw) => {
let mut found = None;
let mut index = 0;
split_on(raw, b';', |component| {
if index == i {
found = Some(component);
}
index += 1;
});
found.map(Component::Raw)
}
None => self
.components
.get(i)
.map(|leaves| Component::Split(leaves)),
}
}
fn materialize(&mut self) {
let Some(raw) = self.raw.take() else {
return;
};
self.components = match raw {
Cow::Borrowed(bytes) => split_all(bytes),
Cow::Owned(bytes) => split_all_owned(&bytes),
};
}
}
impl fmt::Display for IcalValueNode<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(raw) = &self.raw {
return f.write_str(&String::from_utf8_lossy(raw));
}
for (i, component) in self.components.iter().enumerate() {
if i > 0 {
f.write_str(";")?;
}
for (j, leaf) in component.iter().enumerate() {
if j > 0 {
f.write_str(",")?;
}
f.write_str(&String::from_utf8_lossy(leaf.as_bytes()))?;
}
}
Ok(())
}
}
enum Component<'s, 'a> {
Raw(&'s [u8]),
Split(&'s [IcalValueLeaf<'a>]),
}
fn first_value(component: &[u8]) -> &[u8] {
let mut first = None;
split_on(component, b',', |value| {
first.get_or_insert(value);
});
first.unwrap_or(component)
}
fn split_all(bytes: &[u8]) -> Vec<Vec<IcalValueLeaf<'_>>> {
let mut components = Vec::new();
split_on(bytes, b';', |component| {
let mut values = Vec::new();
split_on(component, b',', |value| {
values.push(IcalValueLeaf::from(value));
});
components.push(values);
});
components
}
fn split_all_owned(bytes: &[u8]) -> Vec<Vec<IcalValueLeaf<'static>>> {
let mut components = Vec::new();
split_on(bytes, b';', |component| {
let mut values = Vec::new();
split_on(component, b',', |value| {
values.push(IcalValueLeaf::from(value.to_vec()));
});
components.push(values);
});
components
}
fn split_on<'b>(bytes: &'b [u8], sep: u8, mut piece: impl FnMut(&'b [u8])) {
let mut start = 0;
let mut i = 0;
while let Some(offset) = memchr::memchr2(b'\\', sep, &bytes[i..]) {
let pos = i + offset;
if bytes[pos] == b'\\' {
i = (pos + 2).min(bytes.len());
} else {
piece(&bytes[start..pos]);
start = pos + 1;
i = pos + 1;
}
}
piece(&bytes[start..]);
}
#[cfg(test)]
mod tests {
use alloc::{borrow::Cow, string::ToString, vec, vec::Vec};
use crate::tree::value::node::IcalValueNode;
#[test]
fn splits_components_and_values_then_round_trips() {
let node = IcalValueNode::parse(b"a;b,c;");
assert_eq!(node.component_count(), 3);
assert_eq!(
node.decode_component_list(1),
vec![Cow::Borrowed("b"), Cow::Borrowed("c")]
);
assert_eq!(node.to_string(), "a;b,c;");
}
#[test]
fn keeps_escaped_separators_inside_one_value() {
let node = IcalValueNode::parse(br"a\,b\;c;d");
assert_eq!(node.component_count(), 2);
assert_eq!(node.decode_component_list(0).len(), 1);
assert_eq!(node.to_string(), r"a\,b\;c;d");
}
#[test]
fn an_edit_splits_and_preserves_untouched_components() {
let mut node = IcalValueNode::parse(b"a;b;c");
node.set_component(1, &["X"]);
assert_eq!(node.to_string(), "a;X;c");
}
fn assert_readers_agree(node: &IcalValueNode<'_>, whole: &str) {
assert_eq!(node.component_count(), whole.split(';').count());
assert_eq!(node.decode(), whole);
assert_eq!(node.decode_bytes().as_ref(), whole.as_bytes());
assert_eq!(
node.decode_list(),
whole.split(',').map(Cow::Borrowed).collect::<Vec<_>>(),
);
assert_eq!(node.decode_component(0), "a");
assert_eq!(
node.decode_component_list(1),
vec![Cow::Borrowed("b"), Cow::Borrowed("c")],
);
assert_eq!(node.decode_component(1), "b,c");
assert_eq!(node.decode_component_list(2), vec![Cow::Borrowed("d")]);
}
#[test]
fn readers_agree_before_and_after_an_edit_materializes_the_node() {
let mut node = IcalValueNode::parse(b"a;b,c;d");
assert_readers_agree(&node, "a;b,c;d");
node.set_component(3, &["e"]);
assert_readers_agree(&node, "a;b,c;d;e");
assert_eq!(node.to_string(), "a;b,c;d;e");
}
#[test]
fn readers_agree_after_an_owned_node_is_edited() {
let mut node = IcalValueNode::parse(b"a;b,c;d").into_static();
assert_readers_agree(&node, "a;b,c;d");
node.set_component(3, &["e"]);
assert_readers_agree(&node, "a;b,c;d;e");
assert_eq!(node.to_string(), "a;b,c;d;e");
}
#[test]
fn a_whole_value_write_leaves_no_component_of_the_old_value_behind() {
let mut node = IcalValueNode::parse(b"a;b,c;d");
let whole = node.decode().into_owned();
node.set(&[whole]);
assert_eq!(node.decode(), "a;b,c;d");
assert_eq!(node.to_string(), r"a\;b\,c\;d");
let mut node = IcalValueNode::parse(b"a;b,c;d");
let whole = node.decode_bytes().into_owned();
node.set_bytes(&[whole]);
assert_eq!(node.decode_bytes().as_ref(), b"a;b,c;d");
}
}