imip-agent

vCalendar.py

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