Web-based Calendar Aggregator https://ticker.c3d2.de/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

200 lines
5.9 KiB

use std::mem::replace;
use std::collections::HashMap;
use super::{Object, Props};
use super::tokenizer::{Tokenizer, Token};
pub struct Parser {
tokenizer: Tokenizer,
current_key: Option<String>,
current_prop: Option<String>,
props: Props,
objects: Vec<Object>,
}
impl Parser {
pub fn new() -> Self {
Parser {
tokenizer: Tokenizer::new(),
current_key: None,
current_prop: None,
props: vec![],
objects: vec![],
}
}
pub fn feed<F>(&mut self, input: &'_ [u8], mut f: F)
where
F: FnMut(Object),
{
let current_key = &mut self.current_key;
let current_prop = &mut self.current_prop;
let props = &mut self.props;
let objects = &mut self.objects;
self.tokenizer.feed(input, |token| {
match token {
Token::Key(key) => *current_key = Some(key),
Token::PropName(name) => *current_prop = Some(name),
Token::PropValue(value) => {
current_prop.take().map(|name| {
props.push((name, value));
});
}
Token::Value(value) => {
fn compare(s1: &Option<String>, s2: &str) -> bool {
s1.as_ref().map(|s1| s1 == s2).unwrap_or(s2.len() == 0)
}
if compare(current_key, "BEGIN") {
objects.push(Object {
name: value,
content: HashMap::new(),
children: vec![],
});
} else if compare(current_key, "END") {
while objects.len() > 1 {
let object = objects.pop().unwrap();
let end = value == object.name;
let objects_len = objects.len();
objects[objects_len - 1].children.push(Box::new(object));
if end {
break;
}
}
if objects.len() == 1 {
f(objects.remove(0));
}
} else if objects.len() > 0 {
let props = replace(props, vec![]);
let key = replace(current_key, None);
let objects_len = objects.len();
let content = &mut objects[objects_len - 1].content;
key.map(|key| content.entry(key)
.or_insert_with(|| vec![])
.push((props, value))
);
}
}
}
});
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_event() {
let mut p = Parser::new();
let mut obj = None;
p.feed(b"BEGIN:VEVENT
SUMMARY:Test event
DTSTART:19700101
END:VEVENT
", |o| {
assert!(obj.is_none());
obj = Some(o);
});
assert_eq!(obj, Some(Object {
name: "VEVENT".to_owned(),
content: [("SUMMARY", "Test event"),
("DTSTART", "19700101")]
.iter()
.cloned()
.map(|(k, v)| (k.to_owned(), vec![(vec![], v.to_owned())]))
.collect(),
children: vec![],
}));
}
#[test]
fn parse_recurring_event() {
let mut p = Parser::new();
let mut obj = None;
p.feed(b"BEGIN:VEVENT
SUMMARY:Test event
DTSTART:19700101
RRULE:FREQ=YEARLY
EXDATE:19710101
EXDATE:19730101
EXDATE:19770101
END:VEVENT
", |o| {
assert!(obj.is_none());
obj = Some(o);
});
assert_eq!(obj, Some(Object {
name: "VEVENT".to_owned(),
content: [("SUMMARY", vec!["Test event"]),
("RRULE", vec!["FREQ=YEARLY"]),
("EXDATE", vec!["19710101", "19730101", "19770101"]),
("DTSTART", vec!["19700101"])]
.iter()
.cloned()
.map(|(k, v)| (k.to_owned(), v.into_iter().map(|v| (vec![], v.to_owned())).collect()))
.collect(),
children: vec![],
}));
}
#[test]
fn parse_props() {
let mut p = Parser::new();
let mut obj = None;
p.feed(b"BEGIN:VEVENT
SUMMARY:Test event
DTSTART;TZID=Europe/Berlin:19700101
END:VEVENT
", |o| {
assert!(obj.is_none());
obj = Some(o);
});
assert_eq!(obj, Some(Object {
name: "VEVENT".to_owned(),
content: [
("SUMMARY".to_owned(), vec![(vec![], "Test event".to_owned())]),
("DTSTART".to_owned(), vec![(vec![("TZID".to_owned(), "Europe/Berlin".to_owned())], "19700101".to_owned())])
].iter().cloned().collect(),
children: vec![],
}));
}
#[test]
fn parse_nested() {
let mut p = Parser::new();
let mut obj = None;
p.feed(b"BEGIN:VEVENT
SUMMARY:Test event
DTSTART:19700101
BEGIN:VALARM
ACTION:NONE
END:VALARM
END:VEVENT
", |o| {
assert!(obj.is_none());
obj = Some(o);
});
assert_eq!(obj, Some(Object {
name: "VEVENT".to_owned(),
content: [("SUMMARY", "Test event"),
("DTSTART", "19700101")]
.iter()
.cloned()
.map(|(k, v)| (k.to_owned(), vec![(vec![], v.to_owned())]))
.collect(),
children: vec![Box::new(Object {
name: "VALARM".to_owned(),
content: [("ACTION", "NONE")]
.iter()
.cloned()
.map(|(k, v)| (k.to_owned(), vec![(vec![], v.to_owned())]))
.collect(),
children: vec![],
})],
}));
}
}