1 #!/usr/bin/env python 2 3 """ 4 Parsing of vCalendar and iCalendar files. 5 6 Copyright (C) 2008 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU Lesser General Public License as published by the Free 10 Software Foundation; either version 3 of the License, or (at your option) any 11 later version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 16 details. 17 18 You should have received a copy of the GNU Lesser General Public License along 19 with this program. If not, see <http://www.gnu.org/licenses/>. 20 21 -------- 22 23 References: 24 25 RFC 2445: Internet Calendaring and Scheduling Core Object Specification 26 (iCalendar) 27 http://rfc.net/rfc2445.html 28 """ 29 30 import vContent 31 32 try: 33 set 34 except NameError: 35 from sets import Set as set 36 37 # Format details. 38 39 QUOTED_PARAMETERS = set([ 40 "ALTREP", "DELEGATED-FROM", "DELEGATED-TO", "DIR", "MEMBER", "SENT-BY" 41 ]) 42 MULTIVALUED_PARAMETERS = set([ 43 "DELEGATED-FROM", "DELEGATED-TO", "MEMBER" 44 ]) 45 QUOTED_TYPES = set(["URI"]) 46 47 # Parser classes. 48 49 class vCalendarStreamParser(vContent.StreamParser): 50 51 "A stream parser specifically for vCalendar/iCalendar." 52 53 def next(self): 54 55 """ 56 Return the next content item in the file as a tuple of the form 57 (name, parameters, value). 58 """ 59 60 name, parameters, value = vContent.StreamParser.next(self) 61 return name, self.decode_parameters(parameters), self.decode_content(value) 62 63 def decode_content(self, value): 64 65 "Decode the given 'value', replacing quoted separator characters." 66 67 # Replace quoted characters (see 4.3.11 in RFC 2445). 68 69 value = vContent.StreamParser.decode_content(self, value) 70 return value.replace("\\,", ",").replace("\\;", ";") 71 72 # Internal methods. 73 74 def decode_quoted_value(self, value): 75 76 "Decode the given 'value', returning a list of decoded values." 77 78 if value[0] == '"' and value[-1] == '"': 79 return value[1:-1] 80 else: 81 return value 82 83 def decode_parameters(self, parameters): 84 85 """ 86 Decode the given 'parameters' according to the vCalendar specification. 87 """ 88 89 decoded_parameters = {} 90 91 for param_name, param_value in parameters.items(): 92 if param_name in QUOTED_PARAMETERS: 93 param_value = self.decode_quoted_value(param_value) 94 separator = '","' 95 else: 96 separator = "," 97 if param_name in MULTIVALUED_PARAMETERS: 98 param_value = param_value.split(separator) 99 decoded_parameters[param_name] = param_value 100 101 return decoded_parameters 102 103 class vCalendarParser(vContent.Parser): 104 105 "A parser specifically for vCalendar/iCalendar." 106 107 def parse(self, f, parser_cls=None): 108 return vContent.Parser.parse(self, f, vCalendarStreamParser) 109 110 # Writer classes. 111 112 class vCalendarStreamWriter(vContent.StreamWriter): 113 114 "A stream writer specifically for vCard." 115 116 def write(self, name, parameters, value): 117 118 """ 119 Write a content line for the given 'name', 'parameters' and 'value' 120 information. 121 """ 122 123 vContent.StreamWriter.write(self, name, self.encode_parameters(parameters), value) 124 125 # Internal methods. 126 127 def encode_quoted_value(self, value): 128 129 "Encode the given 'value'." 130 131 return '"%s"' % value 132 133 def encode_parameters(self, parameters): 134 135 """ 136 Encode the given 'parameters' according to the vCalendar specification. 137 """ 138 139 encoded_parameters = {} 140 141 for param_name, param_value in parameters.items(): 142 if param_name in QUOTED_PARAMETERS: 143 param_value = self.encode_quoted_value(param_value) 144 separator = '","' 145 else: 146 separator = "," 147 if param_name in MULTIVALUED_PARAMETERS: 148 param_value = separator.join(param_value) 149 encoded_parameters[param_name] = param_value 150 151 return encoded_parameters 152 153 def encode_content(self, value): 154 155 "Encode the given 'value', replacing separator characters." 156 157 # Replace quoted characters (see 4.3.11 in RFC 2445). 158 159 value = vContent.StreamWriter.encode_content(self, value) 160 return value.replace(";", "\\;").replace(",", "\\,") 161 162 # Public functions. 163 164 def parse(f, non_standard_newline=0): 165 166 """ 167 Parse the resource data found through the use of the file object 'f', which 168 should provide Unicode data. (The codecs module can be used to open files or 169 to wrap streams in order to provide Unicode data.) 170 171 The optional 'non_standard_newline' can be set to a true value (unlike the 172 default) in order to attempt to process files with CR as the end of line 173 character. 174 175 As a result of parsing the resource, the root node of the imported resource 176 is returned. 177 """ 178 179 return vContent.parse(f, non_standard_newline, vCalendarParser) 180 181 def iterparse(f, non_standard_newline=0): 182 183 """ 184 Parse the resource data found through the use of the file object 'f', which 185 should provide Unicode data. (The codecs module can be used to open files or 186 to wrap streams in order to provide Unicode data.) 187 188 The optional 'non_standard_newline' can be set to a true value (unlike the 189 default) in order to attempt to process files with CR as the end of line 190 character. 191 192 An iterator is returned which provides event tuples describing parsing 193 events of the form (name, parameters, value). 194 """ 195 196 return vContent.iterparse(f, non_standard_newline, vCalendarStreamParser) 197 198 def iterwrite(f, line_length=None): 199 return vContent.iterwrite(f, line_length, vCalendarStreamWriter) 200 201 # vim: tabstop=4 expandtab shiftwidth=4