imip-agent

vCalendar.py

740:2962011812c0
2015-09-13 Paul Boddie Added support for recording expiry times on free/busy offers, fixing testing for expired offers.
     1 #!/usr/bin/env python     2      3 """     4 Parsing of vCalendar and iCalendar files.     5      6 Copyright (C) 2008, 2009, 2011, 2013, 2014 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 ParseError = vContent.ParseError    43     44 # Format details.    45     46 SECTION_TYPES = set([    47     "VALARM", "VCALENDAR", "VEVENT", "VFREEBUSY", "VJOURNAL", "VTIMEZONE", "VTODO"    48     ])    49 QUOTED_PARAMETERS = set([    50     "ALTREP", "DELEGATED-FROM", "DELEGATED-TO", "DIR", "MEMBER", "SENT-BY"    51     ])    52 MULTIVALUED_PARAMETERS = set([    53     "DELEGATED-FROM", "DELEGATED-TO", "MEMBER"    54     ])    55 QUOTED_TYPES = set(["URI"])    56     57 unquoted_separator_regexp = re.compile(r"(?<!\\)([,;])")    58     59 # Parser classes.    60     61 class vCalendarStreamParser(vContent.StreamParser):    62     63     "A stream parser specifically for vCalendar/iCalendar."    64     65     def next(self):    66     67         """    68         Return the next content item in the file as a tuple of the form    69         (name, parameters, value).    70         """    71     72         name, parameters, value = vContent.StreamParser.next(self)    73         return name, self.decode_parameters(parameters), value    74     75     def decode_content(self, value):    76     77         """    78         Decode the given 'value' (which may represent a collection of distinct    79         values), replacing quoted separator characters.    80         """    81     82         sep = None    83         values = []    84     85         for i, s in enumerate(unquoted_separator_regexp.split(value)):    86             if i % 2 != 0:    87                 if not sep:    88                     sep = s    89                 continue    90             values.append(self.decode_content_value(s))    91     92         if sep == ",":    93             return values    94         elif sep == ";":    95             return tuple(values)    96         else:    97             return values[0]    98     99     def decode_content_value(self, value):   100    101         "Decode the given 'value', replacing quoted separator characters."   102    103         # Replace quoted characters (see 4.3.11 in RFC 2445).   104    105         value = vContent.StreamParser.decode_content(self, value)   106         return value.replace(r"\,", ",").replace(r"\;", ";")   107    108     # Internal methods.   109    110     def decode_quoted_value(self, value):   111    112         "Decode the given 'value', returning a list of decoded values."   113    114         if value[0] == '"' and value[-1] == '"':   115             return value[1:-1]   116         else:   117             return value   118    119     def decode_parameters(self, parameters):   120    121         """   122         Decode the given 'parameters' according to the vCalendar specification.   123         """   124    125         decoded_parameters = {}   126    127         for param_name, param_value in parameters.items():   128             if param_name in QUOTED_PARAMETERS:   129                 param_value = self.decode_quoted_value(param_value)   130                 separator = '","'   131             else:   132                 separator = ","   133             if param_name in MULTIVALUED_PARAMETERS:   134                 param_value = param_value.split(separator)   135             decoded_parameters[param_name] = param_value   136    137         return decoded_parameters   138    139 class vCalendarParser(vContent.Parser):   140    141     "A parser specifically for vCalendar/iCalendar."   142    143     def parse(self, f, parser_cls=None):   144         return vContent.Parser.parse(self, f, (parser_cls or vCalendarStreamParser))   145    146     def makeComponent(self, name, parameters, value=None):   147    148         """   149         Make a component object from the given 'name', 'parameters' and optional   150         'value'.   151         """   152    153         if name in SECTION_TYPES:   154             return (name, parameters, value or [])   155         else:   156             return (name, parameters, value or None)   157    158 # Writer classes.   159    160 class vCalendarStreamWriter(vContent.StreamWriter):   161    162     "A stream writer specifically for vCalendar."   163    164     # Overridden methods.   165    166     def write(self, name, parameters, value):   167    168         """   169         Write a content line, serialising the given 'name', 'parameters' and   170         'value' information.   171         """   172    173         if name in SECTION_TYPES:   174             self.write_content_line("BEGIN", {}, name)   175             for n, p, v in value:   176                 self.write(n, p, v)   177             self.write_content_line("END", {}, name)   178         else:   179             vContent.StreamWriter.write(self, name, parameters, value)   180    181     def encode_parameters(self, parameters):   182    183         """   184         Encode the given 'parameters' according to the vCalendar specification.   185         """   186    187         encoded_parameters = {}   188    189         for param_name, param_value in parameters.items():   190             if param_name in QUOTED_PARAMETERS:   191                 param_value = self.encode_quoted_parameter_value(param_value)   192                 separator = '","'   193             else:   194                 separator = ","   195             if param_name in MULTIVALUED_PARAMETERS:   196                 param_value = separator.join(param_value)   197             encoded_parameters[param_name] = param_value   198    199         return encoded_parameters   200    201     def encode_content(self, value):   202    203         """   204         Encode the given 'value' (which may be a list or tuple of separate   205         values), quoting characters and separating collections of values.   206         """   207    208         if isinstance(value, list):   209             sep = ","   210         elif isinstance(value, tuple):   211             sep = ";"   212         else:   213             value = [value]   214             sep = ""   215    216         return sep.join([self.encode_content_value(v) for v in value])   217    218     def encode_content_value(self, value):   219    220         "Encode the given 'value', quoting characters."   221    222         # Replace quoted characters (see 4.3.11 in RFC 2445).   223    224         value = vContent.StreamWriter.encode_content(self, value)   225         return value.replace(";", r"\;").replace(",", r"\,")   226    227 # Public functions.   228    229 def parse(stream_or_string, encoding=None, non_standard_newline=0):   230    231     """   232     Parse the resource data found through the use of the 'stream_or_string',   233     which is either a stream providing Unicode data (the codecs module can be   234     used to open files or to wrap streams in order to provide Unicode data) or a   235     filename identifying a file to be parsed.   236    237     The optional 'encoding' can be used to specify the character encoding used   238     by the file to be parsed.   239    240     The optional 'non_standard_newline' can be set to a true value (unlike the   241     default) in order to attempt to process files with CR as the end of line   242     character.   243    244     As a result of parsing the resource, the root node of the imported resource   245     is returned.   246     """   247    248     return vContent.parse(stream_or_string, encoding, non_standard_newline, vCalendarParser)   249    250 def iterparse(stream_or_string, encoding=None, non_standard_newline=0):   251    252     """   253     Parse the resource data found through the use of the 'stream_or_string',   254     which is either a stream providing Unicode data (the codecs module can be   255     used to open files or to wrap streams in order to provide Unicode data) or a   256     filename identifying a file to be parsed.   257    258     The optional 'encoding' can be used to specify the character encoding used   259     by the file to be parsed.   260    261     The optional 'non_standard_newline' can be set to a true value (unlike the   262     default) in order to attempt to process files with CR as the end of line   263     character.   264    265     An iterator is returned which provides event tuples describing parsing   266     events of the form (name, parameters, value).   267     """   268    269     return vContent.iterparse(stream_or_string, encoding, non_standard_newline, vCalendarStreamParser)   270    271 def iterwrite(stream_or_string=None, write=None, encoding=None, line_length=None):   272    273     """   274     Return a writer which will either send data to the resource found through   275     the use of 'stream_or_string' or using the given 'write' operation.   276    277     The 'stream_or_string' parameter may be either a stream accepting Unicode   278     data (the codecs module can be used to open files or to wrap streams in   279     order to accept Unicode data) or a filename identifying a file to be   280     written.   281    282     The optional 'encoding' can be used to specify the character encoding used   283     by the file to be written.   284    285     The optional 'line_length' can be used to specify how long lines should be   286     in the resulting data.   287     """   288    289     return vContent.iterwrite(stream_or_string, write, encoding, line_length, vCalendarStreamWriter)   290    291 def to_dict(node):   292    293     "Return the 'node' converted to a dictionary representation."   294    295     return vContent.to_dict(node, SECTION_TYPES)   296    297 to_node = vContent.to_node   298    299 # vim: tabstop=4 expandtab shiftwidth=4