1.1 --- a/tests/test.ics Sun Nov 02 00:32:48 2008 +0100
1.2 +++ b/tests/test.ics Sun Nov 02 04:06:23 2008 +0100
1.3 @@ -13,6 +13,6 @@
1.4 SUMMARY:Testing
1.5 DTSTART;TZID=CEST:20081018T150000
1.6 DTEND;TZID=CEST:20081018T153000
1.7 -LOCATION:Oslo, Norway
1.8 +LOCATION:Oslo\, Norway
1.9 END:VEVENT
1.10 END:VCALENDAR
2.1 --- a/tests/test.vcf Sun Nov 02 00:32:48 2008 +0100
2.2 +++ b/tests/test.vcf Sun Nov 02 04:06:23 2008 +0100
2.3 @@ -1,13 +1,12 @@
2.4 -BEGIN:VCARD
2.5 -CATEGORIES:Business
2.6 -CLASS:PUBLIC
2.7 -EMAIL:bjørn@skog.private
2.8 -FN:Bjørn Bear
2.9 -N:Bear;Bjørn;;;
2.10 -ORG:Wildlife
2.11 -REV:2008-10-23T22:38:13Z
2.12 -ROLE:Predator
2.13 -UID:p6uSXHgAu7
2.14 -VERSION:3.0
2.15 -END:VCARD
2.16 -
2.17 +BEGIN:VCARD
2.18 +CATEGORIES:Business
2.19 +CLASS:PUBLIC
2.20 +EMAIL:bjørn@skog.private
2.21 +FN:Bjørn Bear
2.22 +N:Bear;Bjørn;;;
2.23 +ORG:Wildlife
2.24 +REV:2008-10-23T22:38:13Z
2.25 +ROLE:Predator
2.26 +UID:p6uSXHgAu7
2.27 +VERSION:3.0
2.28 +END:VCARD
3.1 --- a/tests/test_calendar_stream.py Sun Nov 02 00:32:48 2008 +0100
3.2 +++ b/tests/test_calendar_stream.py Sun Nov 02 04:06:23 2008 +0100
3.3 @@ -3,6 +3,15 @@
3.4 import codecs, vCalendar
3.5
3.6 f = codecs.open("tests/test.ics", encoding="utf-8")
3.7 -doc = vCalendar.iterparse(f)
3.8 +out = codecs.open("tmp.ics", "w", encoding="utf-8")
3.9 +try:
3.10 + doc = vCalendar.iterparse(f)
3.11 + w = vCalendar.vCalendarStreamWriter(out)
3.12 + for name, parameters, value in doc:
3.13 + print "%r, %r, %r" % (name, parameters, value)
3.14 + w.write(name, parameters, value)
3.15 +finally:
3.16 + out.close()
3.17 + f.close()
3.18
3.19 # vim: tabstop=4 expandtab shiftwidth=4
4.1 --- a/tests/test_card_stream.py Sun Nov 02 00:32:48 2008 +0100
4.2 +++ b/tests/test_card_stream.py Sun Nov 02 04:06:23 2008 +0100
4.3 @@ -3,6 +3,15 @@
4.4 import codecs, vContent
4.5
4.6 f = codecs.open("tests/test.vcf", encoding="utf-8")
4.7 -doc = vContent.iterparse(f)
4.8 +out = codecs.open("tmp.vcf", "w", encoding="utf-8")
4.9 +try:
4.10 + doc = vContent.iterparse(f)
4.11 + w = vContent.StreamWriter(out)
4.12 + for name, parameters, value in doc:
4.13 + print "%r, %r, %r" % (name, parameters, value)
4.14 + w.write(name, parameters, value)
4.15 +finally:
4.16 + out.close()
4.17 + f.close()
4.18
4.19 # vim: tabstop=4 expandtab shiftwidth=4
5.1 --- a/vCalendar.py Sun Nov 02 00:32:48 2008 +0100
5.2 +++ b/vCalendar.py Sun Nov 02 04:06:23 2008 +0100
5.3 @@ -34,27 +34,42 @@
5.4 except NameError:
5.5 from sets import Set as set
5.6
5.7 +# Format details.
5.8 +
5.9 +QUOTED_PARAMETERS = set([
5.10 + "ALTREP", "DELEGATED-FROM", "DELEGATED-TO", "DIR", "MEMBER", "SENT-BY"
5.11 + ])
5.12 +MULTIVALUED_PARAMETERS = set([
5.13 + "DELEGATED-FROM", "DELEGATED-TO", "MEMBER"
5.14 + ])
5.15 +QUOTED_TYPES = set(["URI"])
5.16 +
5.17 +# Parser classes.
5.18 +
5.19 class vCalendarStreamParser(vContent.StreamParser):
5.20
5.21 "A stream parser specifically for vCalendar/iCalendar."
5.22
5.23 - quoted_parameters = set([
5.24 - "ALTREP", "DELEGATED-FROM", "DELEGATED-TO", "DIR", "MEMBER", "SENT-BY"
5.25 - ])
5.26 - multivalued_parameters = set([
5.27 - "DELEGATED-FROM", "DELEGATED-TO", "MEMBER"
5.28 - ])
5.29 - quoted_types = set(["URI"])
5.30 -
5.31 def next(self):
5.32
5.33 """
5.34 Return the next content item in the file as a tuple of the form
5.35 - (name, parameters, values).
5.36 + (name, parameters, value).
5.37 """
5.38
5.39 - name, parameters, values = vContent.StreamParser.next(self)
5.40 - return name, self.decode_parameters(parameters), values
5.41 + name, parameters, value = vContent.StreamParser.next(self)
5.42 + return name, self.decode_parameters(parameters), self.decode_content(value)
5.43 +
5.44 + def decode_content(self, value):
5.45 +
5.46 + "Decode the given 'value', replacing quoted separator characters."
5.47 +
5.48 + # Replace quoted characters (see 4.3.11 in RFC 2445).
5.49 +
5.50 + value = vContent.StreamParser.decode_content(self, value)
5.51 + return value.replace("\\,", ",").replace("\\;", ";")
5.52 +
5.53 + # Internal methods.
5.54
5.55 def decode_quoted_value(self, value):
5.56
5.57 @@ -74,12 +89,12 @@
5.58 decoded_parameters = {}
5.59
5.60 for param_name, param_value in parameters.items():
5.61 - if param_name in self.quoted_parameters:
5.62 + if param_name in QUOTED_PARAMETERS:
5.63 param_value = self.decode_quoted_value(param_value)
5.64 separator = '","'
5.65 else:
5.66 separator = ","
5.67 - if param_name in self.multivalued_parameters:
5.68 + if param_name in MULTIVALUED_PARAMETERS:
5.69 param_value = param_value.split(separator)
5.70 decoded_parameters[param_name] = param_value
5.71
5.72 @@ -92,6 +107,58 @@
5.73 def parse(self, f, parser_cls=None):
5.74 return vContent.Parser.parse(self, f, vCalendarStreamParser)
5.75
5.76 +# Writer classes.
5.77 +
5.78 +class vCalendarStreamWriter(vContent.StreamWriter):
5.79 +
5.80 + "A stream writer specifically for vCard."
5.81 +
5.82 + def write(self, name, parameters, value):
5.83 +
5.84 + """
5.85 + Write a content line for the given 'name', 'parameters' and 'value'
5.86 + information.
5.87 + """
5.88 +
5.89 + vContent.StreamWriter.write(self, name, self.encode_parameters(parameters), value)
5.90 +
5.91 + # Internal methods.
5.92 +
5.93 + def encode_quoted_value(self, value):
5.94 +
5.95 + "Encode the given 'value'."
5.96 +
5.97 + return '"%s"' % value
5.98 +
5.99 + def encode_parameters(self, parameters):
5.100 +
5.101 + """
5.102 + Encode the given 'parameters' according to the vCalendar specification.
5.103 + """
5.104 +
5.105 + encoded_parameters = {}
5.106 +
5.107 + for param_name, param_value in parameters.items():
5.108 + if param_name in QUOTED_PARAMETERS:
5.109 + param_value = self.encode_quoted_value(param_value)
5.110 + separator = '","'
5.111 + else:
5.112 + separator = ","
5.113 + if param_name in MULTIVALUED_PARAMETERS:
5.114 + param_value = separator.join(param_value)
5.115 + encoded_parameters[param_name] = param_value
5.116 +
5.117 + return encoded_parameters
5.118 +
5.119 + def encode_content(self, value):
5.120 +
5.121 + "Encode the given 'value', replacing separator characters."
5.122 +
5.123 + # Replace quoted characters (see 4.3.11 in RFC 2445).
5.124 +
5.125 + value = vContent.StreamWriter.encode_content(self, value)
5.126 + return value.replace(";", "\\;").replace(",", "\\,")
5.127 +
5.128 # Public functions.
5.129
5.130 def parse(f, non_standard_newline=0):
6.1 --- a/vContent.py Sun Nov 02 00:32:48 2008 +0100
6.2 +++ b/vContent.py Sun Nov 02 04:06:23 2008 +0100
6.3 @@ -46,7 +46,7 @@
6.4
6.5 import re
6.6
6.7 -# Simple reader class.
6.8 +# Reader and parser classes.
6.9
6.10 class Reader:
6.11
6.12 @@ -195,13 +195,19 @@
6.13
6.14 return self.parse_content_line()
6.15
6.16 + def decode_content(self, value):
6.17 +
6.18 + "Decode the given 'value', replacing quoted characters."
6.19 +
6.20 + return value.replace("\r", "").replace("\\N", "\n").replace("\\n", "\n")
6.21 +
6.22 # Internal methods.
6.23
6.24 def parse_content_line(self):
6.25
6.26 """
6.27 - Return the name, parameters and a list containing value information for
6.28 - the current content line in the file being parsed.
6.29 + Return the name, parameters and value information for the current
6.30 + content line in the file being parsed.
6.31 """
6.32
6.33 f = self.f
6.34 @@ -254,21 +260,18 @@
6.35
6.36 # Decode the value.
6.37
6.38 - value = self.decode("".join(value_lines), parameters)
6.39 + value = self.decode(name, parameters, "".join(value_lines))
6.40
6.41 return name, parameters, value
6.42
6.43 - def decode(self, value, parameters):
6.44 + def decode(self, name, parameters, value):
6.45
6.46 - "Decode the 'value' using the given 'parameters'."
6.47 + "Decode using 'name' and 'parameters' the given 'value'."
6.48
6.49 encoding = parameters.get("ENCODING")
6.50 charset = parameters.get("CHARSET")
6.51
6.52 - # NOTE: Introducing newline conversions.
6.53 - # Replace quoted characters (see 4.3.11 in RFC 2445).
6.54 -
6.55 - value = value.replace("\r", "").replace("\\N", "\n").replace("\\n", "\n").replace("\\,", ",").replace("\\;", ";")
6.56 + value = self.decode_content(value)
6.57
6.58 if encoding == "QUOTED-PRINTABLE":
6.59 return unicode(quopri.decodestring(value), charset or "iso-8859-1")
6.60 @@ -379,6 +382,86 @@
6.61 ParserBase.parse(self, f, parser_cls)
6.62 return self.components[0]
6.63
6.64 +# Writer classes.
6.65 +
6.66 +class StreamWriter:
6.67 +
6.68 + "A stream writer for content in vCard/vCalendar/iCalendar-like formats."
6.69 +
6.70 + def __init__(self, f, line_length=76):
6.71 +
6.72 + "Initialise the parser for the given file 'f'."
6.73 +
6.74 + self.f = f
6.75 + self.line_length = line_length
6.76 +
6.77 + def write(self, name, parameters, value):
6.78 +
6.79 + """
6.80 + Write a content line for the given 'name', 'parameters' and 'value'
6.81 + information.
6.82 + """
6.83 +
6.84 + f = self.f
6.85 +
6.86 + f.write(name)
6.87 + self.write_parameters(parameters)
6.88 + f.write(":")
6.89 +
6.90 + for line in self.fold(self.encode(name, parameters, value)):
6.91 + f.write(line)
6.92 + f.write("\r\n")
6.93 +
6.94 + def encode_content(self, value):
6.95 +
6.96 + "Encode the given 'value', quoting characters."
6.97 +
6.98 + return value.replace("\n", "\\n")
6.99 +
6.100 + # Internal methods.
6.101 +
6.102 + def write_parameters(self, parameters):
6.103 +
6.104 + "Write the given 'parameters'."
6.105 +
6.106 + f = self.f
6.107 +
6.108 + for parameter_name, parameter_value in parameters.items():
6.109 + f.write(";")
6.110 + f.write(parameter_name)
6.111 + f.write("=")
6.112 + f.write(parameter_value)
6.113 +
6.114 + def encode(self, name, parameters, value):
6.115 +
6.116 + "Encode using 'name' and 'parameters' the given 'value'."
6.117 +
6.118 + encoding = parameters.get("ENCODING")
6.119 + charset = parameters.get("CHARSET")
6.120 +
6.121 + if encoding == "QUOTED-PRINTABLE":
6.122 + value = quopri.encodestring(value.encode(charset or "iso-8859-1"))
6.123 + elif encoding == "BASE64":
6.124 + value = base64.encodestring(value)
6.125 +
6.126 + return self.encode_content(value)
6.127 +
6.128 + def fold(self, text):
6.129 +
6.130 + "Fold the given 'text'."
6.131 +
6.132 + line_length = self.line_length
6.133 + i = 0
6.134 + lines = []
6.135 +
6.136 + line = text[i:i+line_length]
6.137 + while line:
6.138 + lines.append(line)
6.139 + i += line_length
6.140 + line = text[i:i+line_length]
6.141 +
6.142 + return lines
6.143 +
6.144 # Public functions.
6.145
6.146 def parse(f, non_standard_newline=0, parser_cls=None):