1 #!/usr/bin/env python 2 3 """ 4 Parsing of vCalendar and iCalendar files. 5 6 Copyright (C) 2008, 2009 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 # Overridden methods. 117 118 def encode_parameters(self, parameters): 119 120 """ 121 Encode the given 'parameters' according to the vCalendar specification. 122 """ 123 124 encoded_parameters = {} 125 126 for param_name, param_value in parameters.items(): 127 if param_name in QUOTED_PARAMETERS: 128 param_value = self.encode_quoted_parameter_value(param_value) 129 separator = '","' 130 else: 131 separator = "," 132 if param_name in MULTIVALUED_PARAMETERS: 133 param_value = separator.join(param_value) 134 encoded_parameters[param_name] = param_value 135 136 return encoded_parameters 137 138 def encode_content(self, value): 139 140 "Encode the given 'value', quoting characters." 141 142 # Replace quoted characters (see 4.3.11 in RFC 2445). 143 144 value = vContent.StreamWriter.encode_content(self, value) 145 return value.replace(";", "\\;").replace(",", "\\,") 146 147 # Public functions. 148 149 def parse(stream_or_string, encoding=None, non_standard_newline=0): 150 151 """ 152 Parse the resource data found through the use of the 'stream_or_string', 153 which is either a stream providing Unicode data (the codecs module can be 154 used to open files or to wrap streams in order to provide Unicode data) or a 155 filename identifying a file to be parsed. 156 157 The optional 'encoding' can be used to specify the character encoding used 158 by the file to be parsed. 159 160 The optional 'non_standard_newline' can be set to a true value (unlike the 161 default) in order to attempt to process files with CR as the end of line 162 character. 163 164 As a result of parsing the resource, the root node of the imported resource 165 is returned. 166 """ 167 168 return vContent.parse(stream_or_string, encoding, non_standard_newline, vCalendarParser) 169 170 def iterparse(stream_or_string, encoding=None, non_standard_newline=0): 171 172 """ 173 Parse the resource data found through the use of the 'stream_or_string', 174 which is either a stream providing Unicode data (the codecs module can be 175 used to open files or to wrap streams in order to provide Unicode data) or a 176 filename identifying a file to be parsed. 177 178 The optional 'encoding' can be used to specify the character encoding used 179 by the file to be parsed. 180 181 The optional 'non_standard_newline' can be set to a true value (unlike the 182 default) in order to attempt to process files with CR as the end of line 183 character. 184 185 An iterator is returned which provides event tuples describing parsing 186 events of the form (name, parameters, value). 187 """ 188 189 return vContent.iterparse(stream_or_string, encoding, non_standard_newline, vCalendarStreamParser) 190 191 def iterwrite(stream_or_string, encoding=None, line_length=None): 192 193 """ 194 Return a writer which will send data to the resource found through the use 195 of 'stream_or_string', which is either a stream accepting Unicode data (the 196 codecs module can be used to open files or to wrap streams in order to 197 accept Unicode data) or a filename identifying a file to be parsed. 198 199 The optional 'encoding' can be used to specify the character encoding used 200 by the file to be written. 201 202 The optional 'line_length' can be used to specify how long lines should be 203 in the resulting data. 204 """ 205 206 return vContent.iterwrite(stream_or_string, encoding, line_length, vCalendarStreamWriter) 207 208 # vim: tabstop=4 expandtab shiftwidth=4