2
# Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved.
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
# you may not use this file except in compliance with the License.
6
# You may obtain a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
17
from cStringIO import StringIO
18
from pycalendar import xmldefs
19
from pycalendar.datetimevalue import PyCalendarDateTimeValue
20
from pycalendar.periodvalue import PyCalendarPeriodValue
21
from pycalendar.property import PyCalendarProperty
22
from pycalendar.value import PyCalendarValue
23
import xml.etree.cElementTree as XML
25
class PyCalendarComponentBase(object):
27
# These are class attributes for sets of properties for testing cardinality constraints. The sets
28
# must contain property names.
29
propertyCardinality_1 = () # Must be present
30
propertyCardinality_1_Fix_Empty = () # Must be present but can be fixed by adding an empty value
31
propertyCardinality_0_1 = () # 0 or 1 only
32
propertyCardinality_1_More = () # 1 or more
34
propertyValueChecks = None # Either iCalendar or vCard validation
36
def __init__(self, parent=None):
37
self.mParentComponent = parent
41
# This is the set of checks we do by default for components
42
self.cardinalityChecks = (
43
self.check_cardinality_1,
44
self.check_cardinality_1_Fix_Empty,
45
self.check_cardinality_0_1,
46
self.check_cardinality_1_More,
49
def duplicate(self, **args):
50
other = self.__class__(**args)
52
for component in self.mComponents:
53
other.addComponent(component.duplicate(parent=other))
55
other.mProperties = {}
56
for propname, props in self.mProperties.iteritems():
57
other.mProperties[propname] = [i.duplicate() for i in props]
63
def __ne__(self, other): return not self.__eq__(other)
64
def __eq__(self, other):
65
if not isinstance(other, PyCalendarComponentBase): return False
66
return self.getType() == other.getType() and self.compareProperties(other) and self.compareComponents(other)
69
raise NotImplementedError
71
def getBeginDelimiter(self):
72
return "BEGIN:" + self.getType()
74
def getEndDelimiter(self):
75
return "END:" + self.getType()
80
def getParentComponent(self):
81
return self.mParentComponent
83
def setParentComponent(self, parent):
84
self.mParentComponent = parent
86
def compareComponents(self, other):
87
mine = set(self.mComponents)
88
theirs = set(other.mComponents)
91
for another in theirs:
93
theirs.remove(another)
97
return len(theirs) == 0
99
def getComponents(self, compname=None):
100
compname = compname.upper() if compname else None
101
return [component for component in self.mComponents if compname is None or component.getType().upper() == compname]
103
def getComponentByKey(self, key):
104
for component in self.mComponents:
105
if component.getMapKey() == key:
110
def removeComponentByKey(self, key):
112
for component in self.mComponents:
113
if component.getMapKey() == key:
114
self.removeComponent(component)
117
def addComponent(self, component):
118
self.mComponents.append(component)
120
def hasComponent(self, compname):
121
return self.countComponents(compname) != 0
123
def countComponents(self, compname):
124
return len(self.getComponents(compname))
126
def removeComponent(self, component):
127
self.mComponents.remove(component)
129
def removeAllComponent(self, compname=None):
131
compname = compname.upper()
132
for component in tuple(self.mComponents):
133
if component.getType().upper() == compname:
134
self.removeComponent(component)
136
self.mComponents = []
138
def sortedComponentNames(self):
141
def compareProperties(self, other):
143
for props in self.mProperties.values():
146
for props in other.mProperties.values():
148
return mine == theirs
150
def getProperties(self, propname=None):
151
return self.mProperties.get(propname.upper(), []) if propname else self.mProperties
153
def setProperties(self, props):
154
self.mProperties = props
156
def addProperty(self, prop):
157
self.mProperties.setdefault(prop.getName().upper(), []).append(prop)
159
def hasProperty(self, propname):
160
return self.mProperties.has_key(propname.upper())
162
def countProperty(self, propname):
163
return len(self.mProperties.get(propname.upper(), []))
165
def findFirstProperty(self, propname):
166
return self.mProperties.get(propname.upper(), [None])[0]
168
def removeProperty(self, prop):
169
if self.mProperties.has_key(prop.getName().upper()):
170
self.mProperties[prop.getName().upper()].remove(prop)
171
if len(self.mProperties[prop.getName().upper()]) == 0:
172
del self.mProperties[prop.getName().upper()]
174
def removeProperties(self, propname):
175
if self.mProperties.has_key(propname.upper()):
176
del self.mProperties[propname.upper()]
178
def getPropertyInteger(self, prop, type = None):
179
return self.loadValueInteger(prop, type)
181
def getPropertyString(self, prop):
182
return self.loadValueString(prop)
184
def getProperty(self, prop, value):
185
return self.loadValue(prop, value)
190
def validate(self, doFix=False):
192
Validate the data in this component and optionally fix any problems. Return
193
a tuple containing two lists: the first describes problems that were fixed, the
194
second problems that were not fixed. Caller can then decide what to do with unfixed
202
for check in self.cardinalityChecks:
203
check(fixed, unfixed, doFix)
205
# Value constraints - these tests come from class specific attributes
206
if self.propertyValueChecks is not None:
207
for properties in self.mProperties.values():
208
for property in properties:
209
propname = property.getName().upper()
210
if propname in self.propertyValueChecks:
211
if not self.propertyValueChecks[propname](property):
212
# Cannot fix a bad property value
213
logProblem = "[%s] Property value incorrect: %s" % (self.getType(), propname,)
214
unfixed.append(logProblem)
216
# Validate all subcomponents
217
for component in self.mComponents:
218
morefixed, moreunfixed = component.validate(doFix)
219
fixed.extend(morefixed)
220
unfixed.extend(moreunfixed)
222
return fixed, unfixed
224
def check_cardinality_1(self, fixed, unfixed, doFix):
225
for propname in self.propertyCardinality_1:
226
if self.countProperty(propname) != 1: # Cannot fix a missing required property
227
logProblem = "[%s] Missing or too many required property: %s" % (self.getType(), propname)
228
unfixed.append(logProblem)
230
def check_cardinality_1_Fix_Empty(self, fixed, unfixed, doFix):
231
for propname in self.propertyCardinality_1_Fix_Empty:
232
if self.countProperty(propname) > 1: # Cannot fix too many required property
233
logProblem = "[%s] Too many required property: %s" % (self.getType(), propname)
234
unfixed.append(logProblem)
235
elif self.countProperty(propname) == 0: # Possibly fix by adding empty property
236
logProblem = "[%s] Missing required property: %s" % (self.getType(), propname)
238
self.addProperty(PyCalendarProperty(propname, ""))
239
fixed.append(logProblem)
241
unfixed.append(logProblem)
243
def check_cardinality_0_1(self, fixed, unfixed, doFix):
244
for propname in self.propertyCardinality_0_1:
245
if self.countProperty(propname) > 1: # Cannot be fixed - no idea which one to delete
246
logProblem = "[%s] Too many properties present: %s" % (self.getType(), propname)
247
unfixed.append(logProblem)
249
def check_cardinality_1_More(self, fixed, unfixed, doFix):
250
for propname in self.propertyCardinality_1_More:
251
if not self.countProperty(propname) > 0: # Cannot fix a missing required property
252
logProblem = "[%s] Missing required property: %s" % (self.getType(), propname)
253
unfixed.append(logProblem)
260
def generate(self, os):
262
os.write(self.getBeginDelimiter())
265
# Write each property
266
self.writeProperties(os)
268
# Write each embedded component based on specific order
269
self.writeComponents(os)
272
os.write(self.getEndDelimiter())
275
def generateFiltered(self, os, filter):
277
os.write(self.getBeginDelimiter())
280
# Write each property
281
self.writePropertiesFiltered(os, filter)
283
# Write each embedded component based on specific order
284
self.writeComponentsFiltered(os, filter)
287
os.write(self.getEndDelimiter())
290
def writeXML(self, node, namespace):
293
comp = XML.SubElement(node, xmldefs.makeTag(namespace, self.getType()))
296
self.writePropertiesXML(comp, namespace)
299
self.writeComponentsXML(comp, namespace)
301
def writeXMLFiltered(self, node, namespace, filter):
303
comp = XML.SubElement(node, xmldefs.makeTag(namespace, self.getType()))
306
self.writePropertiesFilteredXML(comp, namespace, filter)
309
self.writeComponentsFilteredXML(comp, namespace, filter)
311
def sortedComponents(self):
313
components = self.mComponents[:]
314
sortedcomponents = []
316
# Write each component based on specific order
317
orderedNames = self.sortedComponentNames()
318
for name in orderedNames:
320
# Group by name then sort by map key (UID/R-ID)
322
for component in tuple(components):
323
if component.getType().upper() == name:
324
namedcomponents.append(component)
325
components.remove(component)
326
for component in sorted(namedcomponents, key=lambda x:x.getSortKey()):
327
sortedcomponents.append(component)
329
# Write out the remainder
330
for component in components:
331
sortedcomponents.append(component)
333
return sortedcomponents
335
def writeComponents(self, os):
337
# Write out the remainder
338
for component in self.sortedComponents():
339
component.generate(os)
341
def writeComponentsFiltered(self, os, filter):
342
# Shortcut for all sub-components
343
if filter.isAllSubComponents():
344
self.writeComponents(os)
345
elif filter.hasSubComponentFilters():
346
for subcomp in self.sortedcomponents():
347
subfilter = filter.getSubComponentFilter(subcomp.getType())
348
if subfilter is not None:
349
subcomp.generateFiltered(os, subfilter)
351
def writeComponentsXML(self, node, namespace):
354
comps = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.components))
356
# Write out the remainder
357
for component in self.sortedComponents():
358
component.writeXML(comps, namespace)
360
def writeComponentsFilteredXML(self, node, namespace, filter):
363
comps = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.components))
365
# Shortcut for all sub-components
366
if filter.isAllSubComponents():
367
self.writeXML(comps, namespace)
368
elif filter.hasSubComponentFilters():
369
for subcomp in self.sortedcomponents():
370
subfilter = filter.getSubComponentFilter(subcomp.getType())
371
if subfilter is not None:
372
subcomp.writeFilteredXML(comps, namespace, subfilter)
374
def loadValue(self, value_name):
375
if self.hasProperty(value_name):
376
return self.findFirstProperty(value_name)
380
def loadValueInteger(self, value_name, type=None):
382
if self.hasProperty(value_name):
383
if type == PyCalendarValue.VALUETYPE_INTEGER:
384
ivalue = self.findFirstProperty(value_name).getIntegerValue()
385
if ivalue is not None:
386
return ivalue.getValue()
387
elif type == PyCalendarValue.VALUETYPE_UTC_OFFSET:
388
uvalue = self.findFirstProperty(value_name).getUTCOffsetValue()
389
if (uvalue is not None):
390
return uvalue.getValue()
394
return self.loadValueInteger(value_name, PyCalendarValue.VALUETYPE_INTEGER)
396
def loadValueString(self, value_name):
397
if self.hasProperty(value_name):
398
tvalue = self.findFirstProperty(value_name).getTextValue()
399
if (tvalue is not None):
400
return tvalue.getValue()
404
def loadValueDateTime(self, value_name):
405
if self.hasProperty(value_name):
406
dtvalue = self.findFirstProperty(value_name).getDateTimeValue()
407
if dtvalue is not None:
408
return dtvalue.getValue()
412
def loadValueDuration(self, value_name):
413
if self.hasProperty(value_name):
414
dvalue = self.findFirstProperty(value_name).getDurationValue()
415
if (dvalue is not None):
416
return dvalue.getValue()
420
def loadValuePeriod(self, value_name):
421
if self.hasProperty(value_name):
422
pvalue = self.findFirstProperty(value_name).getPeriodValue()
423
if (pvalue is not None):
424
return pvalue.getValue()
428
def loadValueRRULE(self, value_name, value, add):
430
if self.hasProperty(value_name):
431
items = self.getProperties()[value_name]
433
rvalue = iter.getRecurrenceValue()
434
if (rvalue is not None):
436
value.addRule(rvalue.getValue())
438
value.subtractRule(rvalue.getValue())
443
def loadValueRDATE(self, value_name, value, add):
445
if self.hasProperty(value_name):
446
for iter in self.getProperties(value_name):
447
mvalue = iter.getMultiValue()
448
if (mvalue is not None):
449
for obj in mvalue.getValues():
451
if isinstance(obj, PyCalendarDateTimeValue):
453
value.addDT(obj.getValue())
455
value.subtractDT(obj.getValue())
456
elif isinstance(obj, PyCalendarPeriodValue):
458
value.addPeriod(obj.getValue().getStart())
460
value.subtractPeriod(obj.getValue().getStart())
466
def sortedPropertyKeys(self):
467
keys = self.mProperties.keys()
471
for skey in self.sortedPropertyKeyOrder():
478
def sortedPropertyKeyOrder(self):
481
def writeProperties(self, os):
482
# Sort properties by name
483
keys = self.sortedPropertyKeys()
485
props = self.mProperties[key]
489
def writePropertiesFiltered(self, os, filter):
491
# Sort properties by name
492
keys = self.sortedPropertyKeys()
494
# Shortcut for all properties
495
if filter.isAllProperties():
497
for prop in self.getProperties(key):
499
elif filter.hasPropertyFilters():
501
for prop in self.getProperties(key):
502
prop.generateFiltered(os, filter)
504
def writePropertiesXML(self, node, namespace):
506
properties = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.properties))
508
# Sort properties by name
509
keys = self.sortedPropertyKeys()
511
props = self.mProperties[key]
513
prop.writeXML(properties, namespace)
515
def writePropertiesFilteredXML(self, node, namespace, filter):
517
props = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.properties))
519
# Sort properties by name
520
keys = self.sortedPropertyKeys()
522
# Shortcut for all properties
523
if filter.isAllProperties():
525
for prop in self.getProperties(key):
526
prop.writeXML(props, namespace)
527
elif filter.hasPropertyFilters():
529
for prop in self.getProperties(key):
530
prop.writeFilteredXML(props, namespace, filter)
532
def loadPrivateValue(self, value_name):
533
# Read it in from properties list and then delete the property from the
535
result = self.loadValueString(value_name)
536
if (result is not None):
537
self.removeProperties(value_name)
540
def writePrivateProperty(self, os, key, value):
541
prop = PyCalendarProperty(name=key, value=value)
544
def editProperty(self, propname, propvalue):
546
# Remove existing items
547
self.removeProperties(propname)
549
# Now create properties
551
self.addProperty(PyCalendarProperty(name=propname, value=propvalue))