imip-agent

vCalendar.py

1176:6bc9f39224a9
2016-05-12 Paul Boddie Added initial support for delegating attendance, introducing a quota scheduling function that appoints delegates within a quota group when a recipient cannot itself attend. Changed the journal methods setting user-group mappings and quota limits to operate using entire collections rather than with individual items.
     1 #!/usr/bin/env python     2      3 """     4 Parsing of vCalendar and iCalendar files.     5      6 Copyright (C) 2008, 2009, 2011, 2013, 2014, 2015,     7               2016 Paul Boddie <paul@boddie.org.uk>     8      9 This program is free software; you can redistribute it and/or modify it under    10 the terms of the GNU General Public License as published by the Free Software    11 Foundation; either version 3 of the License, or (at your option) any later    12 version.    13     14 This program is distributed in the hope that it will be useful, but WITHOUT    15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    16 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    17 details.    18     19 You should have received a copy of the GNU General Public License along with    20 this program.  If not, see <http://www.gnu.org/licenses/>.    21     22 --------    23     24 References:    25     26 RFC 5545: Internet Calendaring and Scheduling Core Object Specification    27           (iCalendar)    28           http://tools.ietf.org/html/rfc5545    29     30 RFC 2445: Internet Calendaring and Scheduling Core Object Specification    31           (iCalendar)    32           http://tools.ietf.org/html/rfc2445    33 """    34     35 import vContent    36 import re    37     38 try:    39     set    40 except NameError:    41     from sets import Set as set    42     43 ParseError = vContent.ParseError    44     45 # Format details.    46     47 SECTION_TYPES = set([    48     "VALARM", "VCALENDAR", "VEVENT", "VFREEBUSY", "VJOURNAL", "VTIMEZONE", "VTODO",    49     "DAYLIGHT", "STANDARD"    50     ])    51 QUOTED_PARAMETERS = set([    52     "ALTREP", "DELEGATED-FROM", "DELEGATED-TO", "DIR", "MEMBER", "SENT-BY"    53     ])    54 MULTIVALUED_PARAMETERS = set([    55     "DELEGATED-FROM", "DELEGATED-TO", "MEMBER"    56     ])    57 QUOTED_TYPES = set(["URI"])    58     59 unquoted_separator_regexp = re.compile(r"(?<!\\)([,;])")    60     61 # Parser classes.    62     63 class vCalendarStreamParser(vContent.StreamParser):    64     65     "A stream parser specifically for vCalendar/iCalendar."    66     67     def next(self):    68     69         """    70         Return the next content item in the file as a tuple of the form    71         (name, parameters, value).    72         """    73     74         name, parameters, value = vContent.StreamParser.next(self)    75         return name, self.decode_parameters(parameters), value    76     77     def decode_content(self, value):    78     79         """    80         Decode the given 'value' (which may represent a collection of distinct    81         values), replacing quoted separator characters.    82         """    83     84         sep = None    85         values = []    86     87         for i, s in enumerate(unquoted_separator_regexp.split(value)):    88             if i % 2 != 0:    89                 if not sep:    90                     sep = s    91                 continue    92             values.append(self.decode_content_value(s))    93     94         if sep == ",":    95             return values    96         elif sep == ";":    97             return tuple(values)    98         else:    99             return values[0]   100    101     def decode_content_value(self, value):   102    103         "Decode the given 'value', replacing quoted separator characters."   104    105         # Replace quoted characters (see 4.3.11 in RFC 2445).   106    107         value = vContent.StreamParser.decode_content(self, value)   108         return value.replace(r"\,", ",").replace(r"\;", ";")   109    110     # Internal methods.   111    112     def decode_quoted_value(self, value):   113    114         "Decode the given 'value', returning a list of decoded values."   115    116         if value[0] == '"' and value[-1] == '"':   117             return value[1:-1]   118         else:   119             return value   120    121     def decode_parameters(self, parameters):   122    123         """   124         Decode the given 'parameters' according to the vCalendar specification.   125         """   126    127         decoded_parameters = {}   128    129         for param_name, param_value in parameters.items():   130             if param_name in QUOTED_PARAMETERS:   131                 param_value = self.decode_quoted_value(param_value)   132                 separator = '","'   133             else:   134                 separator = ","   135             if param_name in MULTIVALUED_PARAMETERS:   136                 param_value = param_value.split(separator)   137             decoded_parameters[param_name] = param_value   138    139         return decoded_parameters   140    141 class vCalendarParser(vContent.Parser):   142    143     "A parser specifically for vCalendar/iCalendar."   144    145     def parse(self, f, parser_cls=None):   146         return vContent.Parser.parse(self, f, (parser_cls or vCalendarStreamParser))   147    148     def makeComponent(self, name, parameters, value=None):   149    150         """   151         Make a component object from the given 'name', 'parameters' and optional   152         'value'.   153         """   154    155         if name in SECTION_TYPES:   156             return (name, parameters, value or [])   157         else:   158             return (name, parameters, value or None)   159    160 # Writer classes.   161    162 class vCalendarStreamWriter(vContent.StreamWriter):   163    164     "A stream writer specifically for vCalendar."   165    166     # Overridden methods.   167    168     def write(self, name, parameters, value):   169    170         """   171         Write a content line, serialising the given 'name', 'parameters' and   172         'value' information.   173         """   174    175         if name in SECTION_TYPES:   176             self.write_content_line("BEGIN", {}, name)   177             for n, p, v in value:   178                 self.write(n, p, v)   179             self.write_content_line("END", {}, name)   180         else:   181             vContent.StreamWriter.write(self, name, parameters, value)   182    183     def encode_parameters(self, parameters):   184    185         """   186         Encode the given 'parameters' according to the vCalendar specification.   187         """   188    189         encoded_parameters = {}   190    191         for param_name, param_value in parameters.items():   192             if param_name in QUOTED_PARAMETERS:   193                 separator = '","'   194             else:   195                 separator = ","   196             if param_name in MULTIVALUED_PARAMETERS:   197                 param_value = separator.join(param_value)   198             if param_name in QUOTED_PARAMETERS:   199                 param_value = self.encode_quoted_parameter_value(param_value)   200             encoded_parameters[param_name] = param_value   201    202         return encoded_parameters   203    204     def encode_content(self, value):   205    206         """   207         Encode the given 'value' (which may be a list or tuple of separate   208         values), quoting characters and separating collections of values.   209         """   210    211         if isinstance(value, list):   212             sep = ","   213         elif isinstance(value, tuple):   214             sep = ";"   215         else:   216             value = [value]   217             sep = ""   218    219         return sep.join([self.encode_content_value(v) for v in value])   220    221     def encode_content_value(self, value):   222    223         "Encode the given 'value', quoting characters."   224    225         # Replace quoted characters (see 4.3.11 in RFC 2445).   226    227         value = vContent.StreamWriter.encode_content(self, value)   228         return value.replace(";", r"\;").replace(",", r"\,")   229    230 # Public functions.   231    232 def parse(stream_or_string, encoding=None, non_standard_newline=0):   233    234     """   235     Parse the resource data found through the use of the 'stream_or_string',   236     which is either a stream providing Unicode data (the codecs module can be   237     used to open files or to wrap streams in order to provide Unicode data) or a   238     filename identifying a file to be parsed.   239    240     The optional 'encoding' can be used to specify the character encoding used   241     by the file to be parsed.   242    243     The optional 'non_standard_newline' can be set to a true value (unlike the   244     default) in order to attempt to process files with CR as the end of line   245     character.   246    247     As a result of parsing the resource, the root node of the imported resource   248     is returned.   249     """   250    251     return vContent.parse(stream_or_string, encoding, non_standard_newline, vCalendarParser)   252    253 def iterparse(stream_or_string, encoding=None, non_standard_newline=0):   254    255     """   256     Parse the resource data found through the use of the 'stream_or_string',   257     which is either a stream providing Unicode data (the codecs module can be   258     used to open files or to wrap streams in order to provide Unicode data) or a   259     filename identifying a file to be parsed.   260    261     The optional 'encoding' can be used to specify the character encoding used   262     by the file to be parsed.   263    264     The optional 'non_standard_newline' can be set to a true value (unlike the   265     default) in order to attempt to process files with CR as the end of line   266     character.   267    268     An iterator is returned which provides event tuples describing parsing   269     events of the form (name, parameters, value).   270     """   271    272     return vContent.iterparse(stream_or_string, encoding, non_standard_newline, vCalendarStreamParser)   273    274 def iterwrite(stream_or_string=None, write=None, encoding=None, line_length=None):   275    276     """   277     Return a writer which will either send data to the resource found through   278     the use of 'stream_or_string' or using the given 'write' operation.   279    280     The 'stream_or_string' parameter may be either a stream accepting Unicode   281     data (the codecs module can be used to open files or to wrap streams in   282     order to accept Unicode data) or a filename identifying a file to be   283     written.   284    285     The optional 'encoding' can be used to specify the character encoding used   286     by the file to be written.   287    288     The optional 'line_length' can be used to specify how long lines should be   289     in the resulting data.   290     """   291    292     return vContent.iterwrite(stream_or_string, write, encoding, line_length, vCalendarStreamWriter)   293    294 def to_dict(node):   295    296     "Return the 'node' converted to a dictionary representation."   297    298     return vContent.to_dict(node, SECTION_TYPES)   299    300 to_node = vContent.to_node   301    302 # vim: tabstop=4 expandtab shiftwidth=4