vContent

vCalendar.py

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