1.1 --- a/vCalendar.py Tue Jun 06 00:02:05 2017 +0200
1.2 +++ b/vCalendar.py Fri Nov 24 18:11:57 2017 +0100
1.3 @@ -3,7 +3,8 @@
1.4 """
1.5 Parsing of vCalendar and iCalendar files.
1.6
1.7 -Copyright (C) 2008, 2009, 2011, 2013, 2014 Paul Boddie <paul@boddie.org.uk>
1.8 +Copyright (C) 2008, 2009, 2011, 2013, 2014, 2015,
1.9 + 2016, 2017 Paul Boddie <paul@boddie.org.uk>
1.10
1.11 This program is free software; you can redistribute it and/or modify it under
1.12 the terms of the GNU General Public License as published by the Free Software
1.13 @@ -53,9 +54,13 @@
1.14 MULTIVALUED_PARAMETERS = set([
1.15 "DELEGATED-FROM", "DELEGATED-TO", "MEMBER"
1.16 ])
1.17 +NON_MULTIVALUED_PROPERTIES = set([
1.18 + "RRULE"
1.19 + ])
1.20 QUOTED_TYPES = set(["URI"])
1.21
1.22 unquoted_separator_regexp = re.compile(r"(?<!\\)([,;])")
1.23 +unquoted_semicolon_regexp = re.compile(r"(?<!\\)([;])")
1.24
1.25 # Parser classes.
1.26
1.27 @@ -73,22 +78,27 @@
1.28 name, parameters, value = vContent.StreamParser.next(self)
1.29 return name, self.decode_parameters(parameters), value
1.30
1.31 - def decode_content(self, value):
1.32 + def decode_content(self, name, value):
1.33
1.34 """
1.35 - Decode the given 'value' (which may represent a collection of distinct
1.36 - values), replacing quoted separator characters.
1.37 + Decode for property 'name' the given 'value' (which may represent a
1.38 + collection of distinct values), replacing quoted separator characters.
1.39 """
1.40
1.41 sep = None
1.42 values = []
1.43
1.44 - for i, s in enumerate(unquoted_separator_regexp.split(value)):
1.45 + if name in NON_MULTIVALUED_PROPERTIES:
1.46 + split = unquoted_semicolon_regexp.split
1.47 + else:
1.48 + split = unquoted_separator_regexp.split
1.49 +
1.50 + for i, s in enumerate(split(value)):
1.51 if i % 2 != 0:
1.52 if not sep:
1.53 sep = s
1.54 continue
1.55 - values.append(self.decode_content_value(s))
1.56 + values.append(self.decode_content_value(name, s))
1.57
1.58 if sep == ",":
1.59 return values
1.60 @@ -97,13 +107,16 @@
1.61 else:
1.62 return values[0]
1.63
1.64 - def decode_content_value(self, value):
1.65 + def decode_content_value(self, name, value):
1.66
1.67 - "Decode the given 'value', replacing quoted separator characters."
1.68 + """
1.69 + Decode for property 'name' the given 'value', replacing quoted separator
1.70 + characters.
1.71 + """
1.72
1.73 # Replace quoted characters (see 4.3.11 in RFC 2445).
1.74
1.75 - value = vContent.StreamParser.decode_content(self, value)
1.76 + value = vContent.StreamParser.decode_content(self, name, value)
1.77 return value.replace(r"\,", ",").replace(r"\;", ";")
1.78
1.79 # Internal methods.
1.80 @@ -189,21 +202,23 @@
1.81
1.82 for param_name, param_value in parameters.items():
1.83 if param_name in QUOTED_PARAMETERS:
1.84 - param_value = self.encode_quoted_parameter_value(param_value)
1.85 separator = '","'
1.86 else:
1.87 separator = ","
1.88 if param_name in MULTIVALUED_PARAMETERS:
1.89 param_value = separator.join(param_value)
1.90 + if param_name in QUOTED_PARAMETERS:
1.91 + param_value = self.encode_quoted_parameter_value(param_value)
1.92 encoded_parameters[param_name] = param_value
1.93
1.94 return encoded_parameters
1.95
1.96 - def encode_content(self, value):
1.97 + def encode_content(self, name, value):
1.98
1.99 """
1.100 - Encode the given 'value' (which may be a list or tuple of separate
1.101 - values), quoting characters and separating collections of values.
1.102 + Encode for property 'name' the given 'value' (which may be a list or
1.103 + tuple of separate values), quoting characters and separating collections
1.104 + of values.
1.105 """
1.106
1.107 if isinstance(value, list):
1.108 @@ -214,17 +229,32 @@
1.109 value = [value]
1.110 sep = ""
1.111
1.112 - return sep.join([self.encode_content_value(v) for v in value])
1.113 + l = []
1.114 + for v in value:
1.115 + l.append(self.encode_content_value(name, v))
1.116 + return sep.join(l)
1.117
1.118 - def encode_content_value(self, value):
1.119 + def encode_content_value(self, name, value):
1.120
1.121 - "Encode the given 'value', quoting characters."
1.122 + "Encode for property 'name' the given 'value', quoting characters."
1.123
1.124 # Replace quoted characters (see 4.3.11 in RFC 2445).
1.125
1.126 - value = vContent.StreamWriter.encode_content(self, value)
1.127 + value = vContent.StreamWriter.encode_content(self, name, value)
1.128 +
1.129 + if name in NON_MULTIVALUED_PROPERTIES:
1.130 + quote = self.quote_semicolons
1.131 + else:
1.132 + quote = self.quote_separators
1.133 +
1.134 + return quote(value)
1.135 +
1.136 + def quote_separators(self, value):
1.137 return value.replace(";", r"\;").replace(",", r"\,")
1.138
1.139 + def quote_semicolons(self, value):
1.140 + return value.replace(";", r"\;")
1.141 +
1.142 # Public functions.
1.143
1.144 def parse(stream_or_string, encoding=None, non_standard_newline=0):
1.145 @@ -289,7 +319,12 @@
1.146
1.147 return vContent.iterwrite(stream_or_string, write, encoding, line_length, vCalendarStreamWriter)
1.148
1.149 -to_dict = vContent.to_dict
1.150 +def to_dict(node):
1.151 +
1.152 + "Return the 'node' converted to a dictionary representation."
1.153 +
1.154 + return vContent.to_dict(node, SECTION_TYPES)
1.155 +
1.156 to_node = vContent.to_node
1.157
1.158 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/vContent.py Tue Jun 06 00:02:05 2017 +0200
2.2 +++ b/vContent.py Fri Nov 24 18:11:57 2017 +0100
2.3 @@ -4,7 +4,7 @@
2.4 Parsing of vCard, vCalendar and iCalendar files.
2.5
2.6 Copyright (C) 2005, 2006, 2007, 2008, 2009, 2011, 2013,
2.7 - 2014, 2015 Paul Boddie <paul@boddie.org.uk>
2.8 + 2014, 2015, 2017 Paul Boddie <paul@boddie.org.uk>
2.9
2.10 This program is free software; you can redistribute it and/or modify it under
2.11 the terms of the GNU General Public License as published by the Free Software
2.12 @@ -280,9 +280,12 @@
2.13
2.14 return self.parse_content_line()
2.15
2.16 - def decode_content(self, value):
2.17 + def decode_content(self, name, value):
2.18
2.19 - "Decode the given 'value', replacing quoted characters."
2.20 + """
2.21 + Decode for property 'name' the given 'value', replacing quoted
2.22 + characters.
2.23 + """
2.24
2.25 return value.replace("\r", "").replace("\\N", "\n").replace("\\n", "\n")
2.26
2.27 @@ -346,7 +349,7 @@
2.28 encoding = parameters.get("ENCODING")
2.29 charset = parameters.get("CHARSET")
2.30
2.31 - value = self.decode_content(value)
2.32 + value = self.decode_content(name, value)
2.33
2.34 if encoding == "QUOTED-PRINTABLE":
2.35 return unicode(quopri.decodestring(value), charset or "iso-8859-1")
2.36 @@ -582,7 +585,7 @@
2.37 elif encoding == "BASE64":
2.38 value = base64.encodestring(value)
2.39
2.40 - return self.encode_content(value)
2.41 + return self.encode_content(name, value)
2.42 except TypeError:
2.43 raise WriteError, "Property %r value with parameters %r cannot be encoded: %r" % (name, parameters, value)
2.44
2.45 @@ -609,9 +612,9 @@
2.46
2.47 return encoded_parameters
2.48
2.49 - def encode_content(self, value):
2.50 + def encode_content(self, name, value):
2.51
2.52 - "Encode the given 'value', quoting characters."
2.53 + "Encode for property 'name' the given 'value', quoting characters."
2.54
2.55 return (value or "").replace("\n", "\\n")
2.56