///|
pub fn parse_calendar(input : String) -> Result[Calendar, CalError] {
Ok(parse_calendar_or_raise(input)) catch {
err => Err(err)
}
}
///|
fn parse_calendar_or_raise(input : String) -> Calendar raise CalError {
if trim_ascii(input).length() == 0 {
raise EmptyInput
}
let lines = unfold_lines(input)
let calendar : Calendar = { properties: [], components: [] }
let mut in_calendar = false
let mut calendar_started = false
let mut calendar_closed = false
let stack : Array[Component] = []
let mut line_no = 1
for raw in lines {
let prop = parse_content_line(raw, line_no)
if prop.name == "BEGIN" {
let component_name = ascii_upper(trim_ascii(prop.value))
if component_name == "VCALENDAR" {
if in_calendar {
raise NestedCalendar
}
if calendar_closed {
raise MultipleCalendars
}
calendar_started = true
in_calendar = true
} else {
if !in_calendar {
raise ComponentOutsideCalendar(component_name)
}
stack.push({
name: component_name,
properties: [],
components: [],
line: prop.line,
})
}
} else if prop.name == "END" {
let component_name = ascii_upper(trim_ascii(prop.value))
if component_name == "VCALENDAR" {
if !in_calendar {
raise UnexpectedEnd(component_name)
}
if stack.length() > 0 {
raise MismatchedEnd(stack[stack.length() - 1].name, "VCALENDAR")
}
in_calendar = false
calendar_closed = true
} else {
if stack.length() == 0 {
raise UnexpectedEnd(component_name)
}
let active_index = stack.length() - 1
let active = stack[active_index]
if active.name != component_name {
raise MismatchedEnd(active.name, component_name)
}
let completed = stack[active_index]
ignore(stack.pop())
if stack.length() == 0 {
calendar.components.push(completed)
} else {
let parent_index = stack.length() - 1
stack[parent_index].components.push(completed)
}
}
} else {
if !in_calendar {
raise PropertyOutsideCalendar(prop.name)
}
if stack.length() == 0 {
calendar.properties.push(prop)
} else {
let active_index = stack.length() - 1
stack[active_index].properties.push(prop)
}
}
line_no += 1
}
if in_calendar {
if stack.length() == 0 {
raise MismatchedEnd("VCALENDAR", "EOF")
}
raise MismatchedEnd(stack[stack.length() - 1].name, "EOF")
}
if !calendar_started {
raise MissingCalendar
}
calendar
}
///|
pub fn parse_content_line(raw : String, line : Int) -> Property raise CalError {
let colon = index_of_char(raw, 58)
if colon < 0 {
raise MissingColon(line, raw)
}
let head = raw[0:colon].to_owned()
let value = raw[colon + 1:raw.length()].to_owned()
let parts = split_quoted(head, 59)
if parts.length() == 0 {
raise BadPropertyName(line, raw)
}
let name = ascii_upper(trim_ascii(parts[0]))
if !valid_token(name) {
raise BadPropertyName(line, raw)
}
let params : Array[Param] = []
let mut i = 1
while i < parts.length() {
params.push(parse_param(parts[i], line))
i += 1
}
{ name, params, value, line }
}
///|
fn parse_param(raw : String, line : Int) -> Param raise CalError {
let eq = index_of_char(raw, 61)
if eq <= 0 {
raise BadParameter(line, raw)
}
let name = ascii_upper(trim_ascii(raw[0:eq].to_owned()))
if !valid_token(name) {
raise BadParameter(line, raw)
}
let value_raw = raw[eq + 1:raw.length()].to_owned()
let values = split_quoted(value_raw, 44)
let cleaned : Array[String] = []
for value in values {
cleaned.push(strip_quotes(trim_ascii(value)))
}
{ name, values: cleaned }
}
///|
fn strip_quotes(s : String) -> String {
if s.length() >= 2 && s[0] == 34 && s[s.length() - 1] == 34 {
s[1:s.length() - 1].to_owned()
} else {
s
}
}
///|
pub fn Calendar::events(self : Calendar) -> Array[Component] {
let events : Array[Component] = []
for component in self.components {
if component.name == "VEVENT" {
events.push(component)
}
}
events
}
///|
pub fn Component::children(self : Component, name : String) -> Array[Component] {
let wanted = ascii_upper(name)
let children : Array[Component] = []
for component in self.components {
if component.name == wanted {
children.push(component)
}
}
children
}
///|
pub fn Component::alarms(self : Component) -> Array[Component] {
self.children("VALARM")
}
///|
pub fn Component::get(self : Component, name : String) -> String? {
let wanted = ascii_upper(name)
for prop in self.properties {
if prop.name == wanted {
return Some(prop.value)
}
}
None
}
///|
pub fn Component::properties_named(
self : Component,
name : String,
) -> Array[Property] {
let wanted = ascii_upper(name)
let properties : Array[Property] = []
for prop in self.properties {
if prop.name == wanted {
properties.push(prop)
}
}
properties
}
///|
pub fn Calendar::get(self : Calendar, name : String) -> String? {
let wanted = ascii_upper(name)
for prop in self.properties {
if prop.name == wanted {
return Some(prop.value)
}
}
None
}
///|
pub fn Calendar::properties_named(
self : Calendar,
name : String,
) -> Array[Property] {
let wanted = ascii_upper(name)
let properties : Array[Property] = []
for prop in self.properties {
if prop.name == wanted {
properties.push(prop)
}
}
properties
}
///|
pub fn Component::has(self : Component, name : String) -> Bool {
self.get(name) is Some(_)
}
///|
pub fn Component::count(self : Component, name : String) -> Int {
let wanted = ascii_upper(name)
let mut count = 0
for prop in self.properties {
if prop.name == wanted {
count += 1
}
}
count
}
///|
pub fn Component::param_value(
self : Component,
property : String,
param : String,
) -> String? {
let wanted_property = ascii_upper(property)
let wanted_param = ascii_upper(param)
for prop in self.properties {
if prop.name == wanted_property {
match prop.param_value(wanted_param) {
Some(value) => return Some(value)
None => ()
}
}
}
None
}
///|
pub fn Property::param_value(self : Property, name : String) -> String? {
let wanted = ascii_upper(name)
for param in self.params {
if param.name == wanted && param.values.length() > 0 {
return Some(param.values[0])
}
}
None
}