vContent

vCalendar.py

24:65eb8957f696
2013-04-25 Paul Boddie Added support for multiple property values, producing lists for collections of such values when parsing, and accepting lists of such values when writing.
     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 import re    36     37 try:    38     set    39 except NameError:    40     from sets import Set as set    41     42 # Format details.    43     44 QUOTED_PARAMETERS = set([    45     "ALTREP", "DELEGATED-FROM", "DELEGATED-TO", "DIR", "MEMBER", "SENT-BY"    46     ])    47 MULTIVALUED_PARAMETERS = set([    48     "DELEGATED-FROM", "DELEGATED-TO", "MEMBER"    49     ])    50 QUOTED_TYPES = set(["URI"])    51     52 unquoted_comma_regexp = re.compile(r"(?<!\\),")    53     54 # Parser classes.    55     56 class vCalendarStreamParser(vContent.StreamParser):    57     58     "A stream parser specifically for vCalendar/iCalendar."    59     60     def next(self):    61     62         """    63         Return the next content item in the file as a tuple of the form    64         (name, parameters, value).    65         """    66     67         name, parameters, value = vContent.StreamParser.next(self)    68         return name, self.decode_parameters(parameters), value    69     70     def decode_content(self, value):    71     72         """    73         Decode the given 'value' (which may represent a collection of distinct    74         values), replacing quoted separator characters.    75         """    76     77         return [self.decode_content_value(v) for v in unquoted_comma_regexp.split(value)]    78     79     def decode_content_value(self, value):    80     81         "Decode the given 'value', replacing quoted separator characters."    82     83         # Replace quoted characters (see 4.3.11 in RFC 2445).    84     85         value = vContent.StreamParser.decode_content(self, value)    86         return value.replace(r"\,", ",").replace(r"\;", ";")    87     88     # Internal methods.    89     90     def decode_quoted_value(self, value):    91     92         "Decode the given 'value', returning a list of decoded values."    93     94         if value[0] == '"' and value[-1] == '"':    95             return value[1:-1]    96         else:    97             return value    98     99     def decode_parameters(self, parameters):   100    101         """   102         Decode the given 'parameters' according to the vCalendar specification.   103         """   104    105         decoded_parameters = {}   106    107         for param_name, param_value in parameters.items():   108             if param_name in QUOTED_PARAMETERS:   109                 param_value = self.decode_quoted_value(param_value)   110                 separator = '","'   111             else:   112                 separator = ","   113             if param_name in MULTIVALUED_PARAMETERS:   114                 param_value = param_value.split(separator)   115             decoded_parameters[param_name] = param_value   116    117         return decoded_parameters   118    119 class vCalendarParser(vContent.Parser):   120    121     "A parser specifically for vCalendar/iCalendar."   122    123     def parse(self, f, parser_cls=None):   124         return vContent.Parser.parse(self, f, (parser_cls or vCalendarStreamParser))   125    126 # Writer classes.   127    128 class vCalendarStreamWriter(vContent.StreamWriter):   129    130     "A stream writer specifically for vCard."   131    132     # Overridden methods.   133    134     def encode_parameters(self, parameters):   135    136         """   137         Encode the given 'parameters' according to the vCalendar specification.   138         """   139    140         encoded_parameters = {}   141    142         for param_name, param_value in parameters.items():   143             if param_name in QUOTED_PARAMETERS:   144                 param_value = self.encode_quoted_parameter_value(param_value)   145                 separator = '","'   146             else:   147                 separator = ","   148             if param_name in MULTIVALUED_PARAMETERS:   149                 param_value = separator.join(param_value)   150             encoded_parameters[param_name] = param_value   151    152         return encoded_parameters   153    154     def encode_content(self, value):   155    156         """   157         Encode the given 'value' (which may be a list or tuple of separate   158         values), quoting characters and separating collections of values.   159         """   160    161         if not isinstance(value, (list, tuple)):   162             value = [value]   163    164         return ",".join([self.encode_content_value(v) for v in value])   165    166     def encode_content_value(self, value):   167    168         "Encode the given 'value', quoting characters."   169    170         # Replace quoted characters (see 4.3.11 in RFC 2445).   171    172         value = vContent.StreamWriter.encode_content(self, value)   173         return value.replace(";", r"\;").replace(",", r"\,")   174    175 # Public functions.   176    177 def parse(stream_or_string, encoding=None, non_standard_newline=0):   178    179     """   180     Parse the resource data found through the use of the 'stream_or_string',   181     which is either a stream providing Unicode data (the codecs module can be   182     used to open files or to wrap streams in order to provide Unicode data) or a   183     filename identifying a file to be parsed.   184    185     The optional 'encoding' can be used to specify the character encoding used   186     by the file to be parsed.   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     As a result of parsing the resource, the root node of the imported resource   193     is returned.   194     """   195    196     return vContent.parse(stream_or_string, encoding, non_standard_newline, vCalendarParser)   197    198 def iterparse(stream_or_string, encoding=None, non_standard_newline=0):   199    200     """   201     Parse the resource data found through the use of the 'stream_or_string',   202     which is either a stream providing Unicode data (the codecs module can be   203     used to open files or to wrap streams in order to provide Unicode data) or a   204     filename identifying a file to be parsed.   205    206     The optional 'encoding' can be used to specify the character encoding used   207     by the file to be parsed.   208    209     The optional 'non_standard_newline' can be set to a true value (unlike the   210     default) in order to attempt to process files with CR as the end of line   211     character.   212    213     An iterator is returned which provides event tuples describing parsing   214     events of the form (name, parameters, value).   215     """   216    217     return vContent.iterparse(stream_or_string, encoding, non_standard_newline, vCalendarStreamParser)   218    219 def iterwrite(stream_or_string=None, write=None, encoding=None, line_length=None):   220    221     """   222     Return a writer which will either send data to the resource found through   223     the use of 'stream_or_string' or using the given 'write' operation.   224    225     The 'stream_or_string' parameter may be either a stream accepting Unicode   226     data (the codecs module can be used to open files or to wrap streams in   227     order to accept Unicode data) or a filename identifying a file to be   228     written.   229    230     The optional 'encoding' can be used to specify the character encoding used   231     by the file to be written.   232    233     The optional 'line_length' can be used to specify how long lines should be   234     in the resulting data.   235     """   236    237     return vContent.iterwrite(stream_or_string, write, encoding, line_length, vCalendarStreamWriter)   238    239 # vim: tabstop=4 expandtab shiftwidth=4