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