summaryrefslogtreecommitdiff
path: root/src/vobject
diff options
context:
space:
mode:
Diffstat (limited to 'src/vobject')
-rw-r--r--src/vobject/base.py56
-rw-r--r--src/vobject/icalendar.py144
-rwxr-xr-xsrc/vobject/midnight.py59
-rw-r--r--src/vobject/to_pst.py55
4 files changed, 170 insertions, 144 deletions
diff --git a/src/vobject/base.py b/src/vobject/base.py
index 0fb3a28..abb6a9e 100644
--- a/src/vobject/base.py
+++ b/src/vobject/base.py
@@ -855,13 +855,35 @@ def dquoteEscape(param):
return param
def foldOneLine(outbuf, input, lineLength = 75):
- if isinstance(input, basestring): input = StringIO.StringIO(input)
- input.seek(0)
- outbuf.write(input.read(lineLength) + CRLF)
- brokenline = input.read(lineLength - 1)
- while brokenline:
- outbuf.write(' ' + brokenline + CRLF)
- brokenline = input.read(lineLength - 1)
+ # Folding line procedure that ensures multi-byte utf-8 sequences are not broken
+ # across lines
+
+ if len(input) < lineLength:
+ # Optimize for unfolded line case
+ outbuf.write(input)
+ else:
+ # Look for valid utf8 range and write that out
+ start = 0
+ written = 0
+ while written < len(input):
+ # Start max length -1 chars on from where we are
+ offset = start + lineLength - 1
+ if offset >= len(input):
+ line = input[start:]
+ outbuf.write(line)
+ written = len(input)
+ else:
+ # Check whether next char is valid utf8 lead byte
+ while (input[offset] > 0x7F) and ((ord(input[offset]) & 0xC0) == 0x80):
+ # Step back until we have a valid char
+ offset -= 1
+
+ line = input[start:offset]
+ outbuf.write(line)
+ outbuf.write("\r\n ")
+ written += offset - start
+ start = offset
+ outbuf.write("\r\n")
def defaultSerialize(obj, buf, lineLength):
"""Encode and fold obj and its children, write to buf or return a string."""
@@ -874,12 +896,12 @@ def defaultSerialize(obj, buf, lineLength):
else:
groupString = obj.group + '.'
if obj.useBegin:
- foldOneLine(outbuf, groupString + u"BEGIN:" + obj.name, lineLength)
+ foldOneLine(outbuf, str(groupString + u"BEGIN:" + obj.name), lineLength)
for child in obj.getSortedChildren():
#validate is recursive, we only need to validate once
child.serialize(outbuf, lineLength, validate=False)
if obj.useBegin:
- foldOneLine(outbuf, groupString + u"END:" + obj.name, lineLength)
+ foldOneLine(outbuf, str(groupString + u"END:" + obj.name), lineLength)
if DEBUG: logger.debug("Finished %s" % obj.name.upper())
elif isinstance(obj, ContentLine):
@@ -887,14 +909,18 @@ def defaultSerialize(obj, buf, lineLength):
if obj.behavior and not startedEncoded: obj.behavior.encode(obj)
s=StringIO.StringIO() #unfolded buffer
if obj.group is not None:
- s.write(obj.group + '.')
+ s.write(str(obj.group + '.'))
if DEBUG: logger.debug("Serializing line" + str(obj))
- s.write(obj.name.upper())
+ s.write(str(obj.name.upper()))
for key, paramvals in obj.params.iteritems():
- s.write(';' + key + '=' + ','.join(map(dquoteEscape, paramvals)))
- s.write(':' + obj.value)
+ s.write(';' + str(key) + '=' + ','.join(map(dquoteEscape, paramvals)).encode("utf-8"))
+ if isinstance(obj.value, unicode):
+ strout = obj.value.encode("utf-8")
+ else:
+ strout = obj.value
+ s.write(':' + strout)
if obj.behavior and not startedEncoded: obj.behavior.decode(obj)
- foldOneLine(outbuf, s, lineLength)
+ foldOneLine(outbuf, s.getvalue(), lineLength)
if DEBUG: logger.debug("Finished %s line" % obj.name.upper())
return buf or outbuf.getvalue()
@@ -1057,7 +1083,7 @@ def newFromBehavior(name, id=None):
else:
obj = ContentLine(name, [], '')
obj.behavior = behavior
- obj.isNative = True
+ obj.isNative = False
return obj
diff --git a/src/vobject/icalendar.py b/src/vobject/icalendar.py
index e61a2af..fd18910 100644
--- a/src/vobject/icalendar.py
+++ b/src/vobject/icalendar.py
@@ -782,20 +782,21 @@ class MultiTextBehavior(behavior.Behavior):
#------------------------ Registered Behavior subclasses -----------------------
class VCalendar2_0(VCalendarComponentBehavior):
- """vCalendar 2.0 behavior."""
+ """vCalendar 2.0 behavior. With added VAVAILABILITY support."""
name = 'VCALENDAR'
description = 'vCalendar 2.0, also known as iCalendar.'
versionString = '2.0'
sortFirst = ('version', 'calscale', 'method', 'prodid', 'vtimezone')
- knownChildren = {'CALSCALE': (0, 1, None),#min, max, behaviorRegistry id
- 'METHOD': (0, 1, None),
- 'VERSION': (0, 1, None),#required, but auto-generated
- 'PRODID': (1, 1, None),
- 'VTIMEZONE': (0, None, None),
- 'VEVENT': (0, None, None),
- 'VTODO': (0, None, None),
- 'VJOURNAL': (0, None, None),
- 'VFREEBUSY': (0, None, None)
+ knownChildren = {'CALSCALE': (0, 1, None),#min, max, behaviorRegistry id
+ 'METHOD': (0, 1, None),
+ 'VERSION': (0, 1, None),#required, but auto-generated
+ 'PRODID': (1, 1, None),
+ 'VTIMEZONE': (0, None, None),
+ 'VEVENT': (0, None, None),
+ 'VTODO': (0, None, None),
+ 'VJOURNAL': (0, None, None),
+ 'VFREEBUSY': (0, None, None),
+ 'VAVAILABILITY': (0, None, None),
}
@classmethod
@@ -941,7 +942,7 @@ class VEvent(RecurringBehavior):
@classmethod
def validate(cls, obj, raiseException, *args):
- if obj.contents.has_key('DTEND') and obj.contents.has_key('DURATION'):
+ if obj.contents.has_key('dtend') and obj.contents.has_key('duration'):
if raiseException:
m = "VEVENT components cannot contain both DTEND and DURATION\
components"
@@ -996,7 +997,7 @@ class VTodo(RecurringBehavior):
@classmethod
def validate(cls, obj, raiseException, *args):
- if obj.contents.has_key('DUE') and obj.contents.has_key('DURATION'):
+ if obj.contents.has_key('due') and obj.contents.has_key('duration'):
if raiseException:
m = "VTODO components cannot contain both DUE and DURATION\
components"
@@ -1047,12 +1048,14 @@ class VFreeBusy(VCalendarComponentBehavior):
>>> vfb.add('dtstart').value = datetime.datetime(2006, 2, 16, 1, tzinfo=utc)
>>> vfb.add('dtend').value = vfb.dtstart.value + twoHours
>>> vfb.add('freebusy').value = [(vfb.dtstart.value, twoHours / 2)]
+ >>> vfb.add('freebusy').value = [(vfb.dtstart.value, vfb.dtend.value)]
>>> print vfb.serialize()
BEGIN:VFREEBUSY
UID:test
DTSTART:20060216T010000Z
DTEND:20060216T030000Z
FREEBUSY:20060216T010000Z/PT1H
+ FREEBUSY:20060216T010000Z/20060216T030000Z
END:VFREEBUSY
"""
@@ -1172,7 +1175,7 @@ class VAlarm(VCalendarComponentBehavior):
; and MUST NOT occur more than once
description /
- if obj.contents.has_key('DTEND') and obj.contents.has_key('DURATION'):
+ if obj.contents.has_key('dtend') and obj.contents.has_key('duration'):
if raiseException:
m = "VEVENT components cannot contain both DTEND and DURATION\
components"
@@ -1185,6 +1188,117 @@ class VAlarm(VCalendarComponentBehavior):
registerBehavior(VAlarm)
+class VAvailability(VCalendarComponentBehavior):
+ """Availability state behavior.
+
+ >>> vav = newFromBehavior('VAVAILABILITY')
+ >>> vav.add('uid').value = 'test'
+ >>> vav.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc)
+ >>> vav.add('dtstart').value = datetime.datetime(2006, 2, 16, 0, tzinfo=utc)
+ >>> vav.add('dtend').value = datetime.datetime(2006, 2, 17, 0, tzinfo=utc)
+ >>> vav.add('busytype').value = "BUSY"
+ >>> av = newFromBehavior('AVAILABLE')
+ >>> av.add('uid').value = 'test1'
+ >>> av.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc)
+ >>> av.add('dtstart').value = datetime.datetime(2006, 2, 16, 9, tzinfo=utc)
+ >>> av.add('dtend').value = datetime.datetime(2006, 2, 16, 12, tzinfo=utc)
+ >>> av.add('summary').value = "Available in the morning"
+ >>> ignore = vav.add(av)
+ >>> print vav.serialize()
+ BEGIN:VAVAILABILITY
+ UID:test
+ DTSTART:20060216T000000Z
+ DTEND:20060217T000000Z
+ BEGIN:AVAILABLE
+ UID:test1
+ DTSTART:20060216T090000Z
+ DTEND:20060216T120000Z
+ DTSTAMP:20060215T000000Z
+ SUMMARY:Available in the morning
+ END:AVAILABLE
+ BUSYTYPE:BUSY
+ DTSTAMP:20060215T000000Z
+ END:VAVAILABILITY
+
+ """
+ name='VAVAILABILITY'
+ description='A component used to represent a user\'s available time slots.'
+ sortFirst = ('uid', 'dtstart', 'duration', 'dtend')
+ knownChildren = {'UID': (1, 1, None),#min, max, behaviorRegistry id
+ 'DTSTAMP': (1, 1, None),
+ 'BUSYTYPE': (0, 1, None),
+ 'CREATED': (0, 1, None),
+ 'DTSTART': (0, 1, None),
+ 'LAST-MODIFIED': (0, 1, None),
+ 'ORGANIZER': (0, 1, None),
+ 'SEQUENCE': (0, 1, None),
+ 'SUMMARY': (0, 1, None),
+ 'URL': (0, 1, None),
+ 'DTEND': (0, 1, None),
+ 'DURATION': (0, 1, None),
+ 'CATEGORIES': (0, None, None),
+ 'COMMENT': (0, None, None),
+ 'CONTACT': (0, None, None),
+ 'AVAILABLE': (0, None, None),
+ }
+
+ @classmethod
+ def validate(cls, obj, raiseException, *args):
+ if obj.contents.has_key('dtend') and obj.contents.has_key('duration'):
+ if raiseException:
+ m = "VAVAILABILITY components cannot contain both DTEND and DURATION\
+ components"
+ raise ValidateError(m)
+ return False
+ else:
+ return super(VAvailability, cls).validate(obj, raiseException, *args)
+
+registerBehavior(VAvailability)
+
+class Available(RecurringBehavior):
+ """Event behavior."""
+ name='AVAILABLE'
+ sortFirst = ('uid', 'recurrence-id', 'dtstart', 'duration', 'dtend')
+
+ description='Defines a period of time in which a user is normally available.'
+ knownChildren = {'DTSTAMP': (1, 1, None),#min, max, behaviorRegistry id
+ 'DTSTART': (1, 1, None),
+ 'UID': (1, 1, None),
+ 'DTEND': (0, 1, None), #NOTE: One of DtEnd or
+ 'DURATION': (0, 1, None), # Duration must appear, but not both
+ 'CREATED': (0, 1, None),
+ 'LAST-MODIFIED':(0, 1, None),
+ 'RECURRENCE-ID':(0, 1, None),
+ 'RRULE': (0, 1, None),
+ 'SUMMARY': (0, 1, None),
+ 'CATEGORIES': (0, None, None),
+ 'COMMENT': (0, None, None),
+ 'CONTACT': (0, None, None),
+ 'EXDATE': (0, None, None),
+ 'RDATE': (0, None, None),
+ }
+
+ @classmethod
+ def validate(cls, obj, raiseException, *args):
+ has_dtend = obj.contents.has_key('dtend')
+ has_duration = obj.contents.has_key('duration')
+ if has_dtend and has_duration:
+ if raiseException:
+ m = "AVAILABLE components cannot contain both DTEND and DURATION\
+ properties"
+ raise ValidateError(m)
+ return False
+ elif not (has_dtend or has_duration):
+ if raiseException:
+ m = "AVAILABLE components must contain one of DTEND or DURATION\
+ properties"
+ raise ValidateError(m)
+ return False
+ else:
+ return super(Available, cls).validate(obj, raiseException, *args)
+
+registerBehavior(Available)
+
class Duration(behavior.Behavior):
"""Behavior for Duration ContentLines. Transform to datetime.timedelta."""
name = 'DURATION'
@@ -1344,7 +1458,7 @@ registerBehavior(MultiDateBehavior, 'EXDATE')
textList = ['CALSCALE', 'METHOD', 'PRODID', 'CLASS', 'COMMENT', 'DESCRIPTION',
'LOCATION', 'STATUS', 'SUMMARY', 'TRANSP', 'CONTACT', 'RELATED-TO',
- 'UID', 'ACTION', 'REQUEST-STATUS', 'TZID']
+ 'UID', 'ACTION', 'REQUEST-STATUS', 'TZID', 'BUSYTYPE']
map(lambda x: registerBehavior(TextBehavior, x), textList)
multiTextList = ['CATEGORIES', 'RESOURCES']
@@ -1678,7 +1792,7 @@ def stringToPeriod(s, tzinfo=None):
delta = stringToDurations(valEnd)[0]
return (start, delta)
else:
- return (start, stringToDateTime(valEnd, tzinfo) - start)
+ return (start, stringToDateTime(valEnd, tzinfo))
def getTransition(transitionTo, year, tzinfo):
diff --git a/src/vobject/midnight.py b/src/vobject/midnight.py
deleted file mode 100755
index c4e7ae8..0000000
--- a/src/vobject/midnight.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python
-"""Rewrite VEVENT's DTSTART and DTEND if they're midnight to midnight"""
-
-from optparse import OptionParser
-import icalendar, base, datetime
-import os
-import codecs
-
-midnight = datetime.time(0)
-one_day = datetime.timedelta(1)
-
-def changeVeventTimes(vevent):
- dtstart = vevent.getChildValue('dtstart')
- dtend = vevent.getChildValue('dtend')
- if isinstance(dtstart, datetime.datetime):
- if dtend is None:
- if dtstart.tzinfo is None and dtstart.time() == midnight:
- vevent.dtstart.value = dtstart.date()
- elif (isinstance(dtend, datetime.datetime) and
- dtend.tzinfo is None and dtend.time() == midnight):
- vevent.dtstart.value = dtstart.date()
- vevent.dtend.value = dtend.date()
-
-
-def main():
- options, args = getOptions()
- if args:
- in_filename, out_filename = args
- cal = base.readOne(file(in_filename))
- for vevent in cal.vevent_list:
- changeVeventTimes(vevent)
- out_file = codecs.open(out_filename, "w", "utf-8")
- cal.serialize(out_file)
- out_file.close()
-
-
-version = "0.1"
-
-def getOptions():
- ##### Configuration options #####
-
- usage = "usage: %prog [options] in_file out_file"
- parser = OptionParser(usage=usage, version=version)
- parser.set_description("convert midnight events to all")
-
- (cmdline_options, args) = parser.parse_args()
- if len(args) < 2:
- print "error: too few arguments given"
- print
- print parser.format_help()
- return False, False
-
- return cmdline_options, args
-
-if __name__ == "__main__":
- try:
- main()
- except KeyboardInterrupt:
- print "Aborted"
diff --git a/src/vobject/to_pst.py b/src/vobject/to_pst.py
deleted file mode 100644
index 318f47f..0000000
--- a/src/vobject/to_pst.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python
-"""Rewrite VEVENT's DTSTART and DTEND if they're midnight to midnight"""
-
-from optparse import OptionParser
-import icalendar, base, datetime
-import os
-import codecs
-import PyICU
-
-pst = PyICU.ICUtzinfo.getInstance('US/Pacific')
-
-def changeVeventTimes(vevent):
- dtstart = vevent.getChildValue('dtstart')
- dtend = vevent.getChildValue('dtend')
- if isinstance(dtstart, datetime.datetime):
- if dtstart.tzinfo is None:
- vevent.dtstart.value = dtstart.replace(tzinfo=pst)
- if dtend is not None and dtend.tzinfo is None:
- vevent.dtend.value = dtend.replace(tzinfo=pst)
-
-def main():
- options, args = getOptions()
- if args:
- in_filename, out_filename = args
- cal = base.readOne(file(in_filename))
- for vevent in cal.vevent_list:
- changeVeventTimes(vevent)
- out_file = codecs.open(out_filename, "w", "utf-8")
- cal.serialize(out_file)
- out_file.close()
-
-
-version = "0.1"
-
-def getOptions():
- ##### Configuration options #####
-
- usage = "usage: %prog [options] in_file out_file"
- parser = OptionParser(usage=usage, version=version)
- parser.set_description("convert midnight events to all")
-
- (cmdline_options, args) = parser.parse_args()
- if len(args) < 2:
- print "error: too few arguments given"
- print
- print parser.format_help()
- return False, False
-
- return cmdline_options, args
-
-if __name__ == "__main__":
- try:
- main()
- except KeyboardInterrupt:
- print "Aborted"