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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
module RiCal
#- ©2009 Rick DeNatale
#- All rights reserved. Refer to the file README.txt for the license
#
class Parser # :nodoc:
attr_reader :last_line_str #:nodoc:
def next_line #:nodoc:
result = nil
begin
result = buffer_or_line
@buffer = nil
while /^\s/ =~ buffer_or_line
result = "#{result}#{@buffer[1..-1]}"
@buffer = nil
end
rescue EOFError
return nil
ensure
return result
end
end
def self.parse_params(string) #:nodoc:
if string
string.split(";").inject({}) { |result, val|
m = /^(.+)=(.+)$/.match(val)
raise "Invalid parameter value #{val.inspect}" unless m
#TODO - The gsub below is a simplest fix for http://rick_denatale.lighthouseapp.com/projects/30941/tickets/19
# it may need further examination if more pathological cases show up.
param_val = m[2].sub(/^\"(.*)\"$/, '\1')
result[m[1]] = param_val
result
}
else
nil
end
end
def self.params_and_value(string, optional_initial_semi = false) #:nodoc:
string = string.sub(/^:/,'')
return [{}, string] unless optional_initial_semi || string.match(/^;/)
segments = string.sub(';','').split(":", -1)
return [{}, string] if segments.length < 2
quote_count = 0
gathering_params = true
params = []
values = []
segments.each do |segment|
if gathering_params
params << segment
quote_count += segment.count("\"")
gathering_params = (1 == quote_count % 2)
else
values << segment
end
end
[parse_params(params.join(":")), values.join(":")]
end
def separate_line(string) #:nodoc:
match = string.match(/^([^;:]*)(.*)$/)
name = match[1]
@last_line_str = string
params, value = *Parser.params_and_value(match[2])
{
:name => name,
:params => params,
:value => value,
}
end
def next_separated_line #:nodoc:
line = next_line
line ? separate_line(line) : nil
end
def buffer_or_line #:nodoc:
@buffer ||= @io.readline.chomp
end
def initialize(io = StringIO.new("")) #:nodoc:
@io = io
end
def self.parse(io = StringIO.new("")) #:nodoc:
new(io).parse
end
def invalid #:nodoc:
raise Exception.new("Invalid icalendar file")
end
def still_in(component, separated_line) #:nodoc:
invalid unless separated_line
separated_line[:value] != component || separated_line[:name] != "END"
end
def parse #:nodoc:
result = []
while start_line = next_line
@parent_stack = []
component = parse_one(start_line, nil)
result << component if component
end
result
end
# TODO: Need to parse non-standard component types (iana-token or x-name)
def parse_one(start, parent_component) #:nodoc:
@parent_stack << parent_component
if Hash === start
first_line = start
else
first_line = separate_line(start)
end
invalid unless first_line[:name] == "BEGIN"
entity_name = first_line[:value]
result = case entity_name
when "VCALENDAR"
RiCal::Component::Calendar.from_parser(self, parent_component, entity_name)
when "VEVENT"
RiCal::Component::Event.from_parser(self, parent_component, entity_name)
when "VTODO"
RiCal::Component::Todo.from_parser(self, parent_component, entity_name)
when "VJOURNAL"
RiCal::Component::Journal.from_parser(self, parent_component, entity_name)
when "VFREEBUSY"
RiCal::Component::Freebusy.from_parser(self, parent_component, entity_name)
when "VTIMEZONE"
RiCal::Component::Timezone.from_parser(self, parent_component, entity_name)
when "VALARM"
RiCal::Component::Alarm.from_parser(self, parent_component, entity_name)
when "DAYLIGHT"
RiCal::Component::Timezone::DaylightPeriod.from_parser(self, parent_component, entity_name)
when "STANDARD"
RiCal::Component::Timezone::StandardPeriod.from_parser(self, parent_component, entity_name)
else
RiCal::Component::NonStandard.from_parser(self, parent_component, entity_name)
end
@parent_stack.pop
result
end
end
end
|