vContent

vCalendar.py

60:577f8dbb9627
2015-02-26 Paul Boddie Handle actual multivalued rule representations.
     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 # Writer classes.   147    148 class vCalendarStreamWriter(vContent.StreamWriter):   149    150     "A stream writer specifically for vCalendar."   151    152     # Overridden methods.   153    154     def write(self, name, parameters, value):   155    156         """   157         Write a content line, serialising the given 'name', 'parameters' and   158         'value' information.   159         """   160    161         if name in SECTION_TYPES:   162             self.write_content_line("BEGIN", {}, name)   163             for n, p, v in value:   164                 self.write(n, p, v)   165             self.write_content_line("END", {}, name)   166         else:   167             vContent.StreamWriter.write(self, name, parameters, value)   168    169     def encode_parameters(self, parameters):   170    171         """   172         Encode the given 'parameters' according to the vCalendar specification.   173         """   174    175         encoded_parameters = {}   176    177         for param_name, param_value in parameters.items():   178             if param_name in QUOTED_PARAMETERS:   179                 param_value = self.encode_quoted_parameter_value(param_value)   180                 separator = '","'   181             else:   182                 separator = ","   183             if param_name in MULTIVALUED_PARAMETERS:   184                 param_value = separator.join(param_value)   185             encoded_parameters[param_name] = param_value   186    187         return encoded_parameters   188    189     def encode_content(self, value):   190    191         """   192         Encode the given 'value' (which may be a list or tuple of separate   193         values), quoting characters and separating collections of values.   194         """   195    196         if isinstance(value, list):   197             sep = ","   198         elif isinstance(value, tuple):   199             sep = ";"   200         else:   201             value = [value]   202             sep = ""   203    204         return sep.join([self.encode_content_value(v) for v in value])   205    206     def encode_content_value(self, value):   207    208         "Encode the given 'value', quoting characters."   209    210         # Replace quoted characters (see 4.3.11 in RFC 2445).   211    212         value = vContent.StreamWriter.encode_content(self, value)   213         return value.replace(";", r"\;").replace(",", r"\,")   214    215 # Public functions.   216    217 def parse(stream_or_string, encoding=None, non_standard_newline=0):   218    219     """   220     Parse the resource data found through the use of the 'stream_or_string',   221     which is either a stream providing Unicode data (the codecs module can be   222     used to open files or to wrap streams in order to provide Unicode data) or a   223     filename identifying a file to be parsed.   224    225     The optional 'encoding' can be used to specify the character encoding used   226     by the file to be parsed.   227    228     The optional 'non_standard_newline' can be set to a true value (unlike the   229     default) in order to attempt to process files with CR as the end of line   230     character.   231    232     As a result of parsing the resource, the root node of the imported resource   233     is returned.   234     """   235    236     return vContent.parse(stream_or_string, encoding, non_standard_newline, vCalendarParser)   237    238 def iterparse(stream_or_string, encoding=None, non_standard_newline=0):   239    240     """   241     Parse the resource data found through the use of the 'stream_or_string',   242     which is either a stream providing Unicode data (the codecs module can be   243     used to open files or to wrap streams in order to provide Unicode data) or a   244     filename identifying a file to be parsed.   245    246     The optional 'encoding' can be used to specify the character encoding used   247     by the file to be parsed.   248    249     The optional 'non_standard_newline' can be set to a true value (unlike the   250     default) in order to attempt to process files with CR as the end of line   251     character.   252    253     An iterator is returned which provides event tuples describing parsing   254     events of the form (name, parameters, value).   255     """   256    257     return vContent.iterparse(stream_or_string, encoding, non_standard_newline, vCalendarStreamParser)   258    259 def iterwrite(stream_or_string=None, write=None, encoding=None, line_length=None):   260    261     """   262     Return a writer which will either send data to the resource found through   263     the use of 'stream_or_string' or using the given 'write' operation.   264    265     The 'stream_or_string' parameter may be either a stream accepting Unicode   266     data (the codecs module can be used to open files or to wrap streams in   267     order to accept Unicode data) or a filename identifying a file to be   268     written.   269    270     The optional 'encoding' can be used to specify the character encoding used   271     by the file to be written.   272    273     The optional 'line_length' can be used to specify how long lines should be   274     in the resulting data.   275     """   276    277     return vContent.iterwrite(stream_or_string, write, encoding, line_length, vCalendarStreamWriter)   278    279 to_dict = vContent.to_dict   280 to_node = vContent.to_node   281    282 # vim: tabstop=4 expandtab shiftwidth=4