~ubuntu-branches/ubuntu/trusty/pycalendar/trusty

« back to all changes in this revision

Viewing changes to src/pycalendar/componentbase.py

  • Committer: Package Import Robot
  • Author(s): Rahul Amaram
  • Date: 2012-05-29 12:42:34 UTC
  • Revision ID: package-import@ubuntu.com-20120529124234-mzwu4vw4chh0sc1f
Tags: upstream-2.0~svn188
ImportĀ upstreamĀ versionĀ 2.0~svn188

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
##
 
2
#    Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved.
 
3
#    
 
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
 
7
#    
 
8
#        http://www.apache.org/licenses/LICENSE-2.0
 
9
#    
 
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.
 
15
##
 
16
 
 
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
 
24
 
 
25
class PyCalendarComponentBase(object):
 
26
 
 
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
 
33
        
 
34
    propertyValueChecks = None  # Either iCalendar or vCard validation
 
35
 
 
36
    def __init__(self, parent=None):
 
37
        self.mParentComponent = parent
 
38
        self.mComponents = []
 
39
        self.mProperties = {}
 
40
 
 
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,
 
47
        )
 
48
 
 
49
    def duplicate(self, **args):
 
50
        other = self.__class__(**args)
 
51
        
 
52
        for component in self.mComponents:
 
53
            other.addComponent(component.duplicate(parent=other))
 
54
 
 
55
        other.mProperties = {}
 
56
        for propname, props in self.mProperties.iteritems():
 
57
            other.mProperties[propname] = [i.duplicate() for i in props]
 
58
        return other
 
59
    
 
60
    def __str__(self):
 
61
        return self.getText()
 
62
 
 
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)
 
67
 
 
68
    def getType(self):
 
69
        raise NotImplementedError
 
70
 
 
71
    def getBeginDelimiter(self):
 
72
        return "BEGIN:" + self.getType()
 
73
 
 
74
    def getEndDelimiter(self):
 
75
        return "END:" + self.getType()
 
76
 
 
77
    def getSortKey(self):
 
78
        return ""
 
79
 
 
80
    def getParentComponent(self):
 
81
        return self.mParentComponent
 
82
    
 
83
    def setParentComponent(self, parent):
 
84
        self.mParentComponent = parent
 
85
 
 
86
    def compareComponents(self, other):
 
87
        mine = set(self.mComponents)
 
88
        theirs = set(other.mComponents)
 
89
        
 
90
        for item in mine:
 
91
            for another in theirs:
 
92
                if item == another:
 
93
                    theirs.remove(another)
 
94
                    break
 
95
            else:
 
96
                return False
 
97
        return len(theirs) == 0
 
98
 
 
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]
 
102
        
 
103
    def getComponentByKey(self, key):
 
104
        for component in self.mComponents:
 
105
            if component.getMapKey() == key:
 
106
                return component
 
107
        else:
 
108
            return None
 
109
        
 
110
    def removeComponentByKey(self, key):
 
111
 
 
112
        for component in self.mComponents:
 
113
            if component.getMapKey() == key:
 
114
                self.removeComponent(component)
 
115
                return
 
116
 
 
117
    def addComponent(self, component):
 
118
        self.mComponents.append(component)
 
119
 
 
120
    def hasComponent(self, compname):
 
121
        return self.countComponents(compname) != 0
 
122
 
 
123
    def countComponents(self, compname):
 
124
        return len(self.getComponents(compname))
 
125
 
 
126
    def removeComponent(self, component):
 
127
        self.mComponents.remove(component)
 
128
 
 
129
    def removeAllComponent(self, compname=None):
 
130
        if compname:
 
131
            compname = compname.upper()
 
132
            for component in tuple(self.mComponents):
 
133
                if component.getType().upper() == compname:
 
134
                    self.removeComponent(component)
 
135
        else:
 
136
            self.mComponents = []
 
137
 
 
138
    def sortedComponentNames(self):
 
139
        return ()
 
140
 
 
141
    def compareProperties(self, other):
 
142
        mine = set()
 
143
        for props in self.mProperties.values():
 
144
            mine.update(props)
 
145
        theirs = set()
 
146
        for props in other.mProperties.values():
 
147
            theirs.update(props)
 
148
        return mine == theirs
 
149
 
 
150
    def getProperties(self, propname=None):
 
151
        return self.mProperties.get(propname.upper(), []) if propname else self.mProperties
 
152
 
 
153
    def setProperties(self, props):
 
154
        self.mProperties = props
 
155
 
 
156
    def addProperty(self, prop):
 
157
        self.mProperties.setdefault(prop.getName().upper(), []).append(prop)
 
158
 
 
159
    def hasProperty(self, propname):
 
160
        return self.mProperties.has_key(propname.upper())
 
161
 
 
162
    def countProperty(self, propname):
 
163
        return len(self.mProperties.get(propname.upper(), []))
 
164
 
 
165
    def findFirstProperty(self, propname):
 
166
        return self.mProperties.get(propname.upper(), [None])[0]
 
167
 
 
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()]
 
173
 
 
174
    def removeProperties(self, propname):
 
175
        if self.mProperties.has_key(propname.upper()):
 
176
            del self.mProperties[propname.upper()]
 
177
 
 
178
    def getPropertyInteger(self, prop, type = None):
 
179
        return self.loadValueInteger(prop, type)
 
180
 
 
181
    def getPropertyString(self, prop):
 
182
        return self.loadValueString(prop)
 
183
 
 
184
    def getProperty(self, prop, value):
 
185
        return self.loadValue(prop, value)
 
186
 
 
187
    def finalise(self):
 
188
        raise NotImplemented
 
189
 
 
190
    def validate(self, doFix=False):
 
191
        """
 
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
 
195
        issues.
 
196
        """
 
197
        
 
198
        fixed = []
 
199
        unfixed = []
 
200
 
 
201
        # Cardinality tests
 
202
        for check in self.cardinalityChecks:
 
203
            check(fixed, unfixed, doFix)
 
204
        
 
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)
 
215
 
 
216
        # Validate all subcomponents
 
217
        for component in self.mComponents:
 
218
            morefixed, moreunfixed = component.validate(doFix)
 
219
            fixed.extend(morefixed)
 
220
            unfixed.extend(moreunfixed)
 
221
 
 
222
        return fixed, unfixed
 
223
 
 
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)
 
229
 
 
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)
 
237
                if doFix:
 
238
                    self.addProperty(PyCalendarProperty(propname, ""))
 
239
                    fixed.append(logProblem)
 
240
                else:
 
241
                    unfixed.append(logProblem)
 
242
 
 
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)
 
248
 
 
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)
 
254
 
 
255
    def getText(self):
 
256
        s = StringIO()
 
257
        self.generate(s)
 
258
        return s.getvalue()
 
259
 
 
260
    def generate(self, os):
 
261
        # Header
 
262
        os.write(self.getBeginDelimiter())
 
263
        os.write("\r\n")
 
264
 
 
265
        # Write each property
 
266
        self.writeProperties(os)
 
267
 
 
268
        # Write each embedded component based on specific order
 
269
        self.writeComponents(os)
 
270
 
 
271
        # Footer
 
272
        os.write(self.getEndDelimiter())
 
273
        os.write("\r\n")
 
274
 
 
275
    def generateFiltered(self, os, filter):
 
276
        # Header
 
277
        os.write(self.getBeginDelimiter())
 
278
        os.write("\r\n")
 
279
 
 
280
        # Write each property
 
281
        self.writePropertiesFiltered(os, filter)
 
282
 
 
283
        # Write each embedded component based on specific order
 
284
        self.writeComponentsFiltered(os, filter)
 
285
 
 
286
        # Footer
 
287
        os.write(self.getEndDelimiter())
 
288
        os.write("\r\n")
 
289
 
 
290
    def writeXML(self, node, namespace):
 
291
        
 
292
        # Component element
 
293
        comp = XML.SubElement(node, xmldefs.makeTag(namespace, self.getType()))
 
294
        
 
295
        # Each property
 
296
        self.writePropertiesXML(comp, namespace)
 
297
    
 
298
        # Each component
 
299
        self.writeComponentsXML(comp, namespace)
 
300
    
 
301
    def writeXMLFiltered(self, node, namespace, filter):
 
302
        # Component element
 
303
        comp = XML.SubElement(node, xmldefs.makeTag(namespace, self.getType()))
 
304
        
 
305
        # Each property
 
306
        self.writePropertiesFilteredXML(comp, namespace, filter)
 
307
    
 
308
        # Each component
 
309
        self.writeComponentsFilteredXML(comp, namespace, filter)
 
310
 
 
311
    def sortedComponents(self):
 
312
        
 
313
        components = self.mComponents[:]
 
314
        sortedcomponents = []
 
315
 
 
316
        # Write each component based on specific order
 
317
        orderedNames = self.sortedComponentNames()
 
318
        for name in orderedNames:
 
319
            
 
320
            # Group by name then sort by map key (UID/R-ID)
 
321
            namedcomponents = []
 
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)
 
328
        
 
329
        # Write out the remainder 
 
330
        for component in components:
 
331
            sortedcomponents.append(component)
 
332
            
 
333
        return sortedcomponents
 
334
        
 
335
    def writeComponents(self, os):
 
336
        
 
337
        # Write out the remainder 
 
338
        for component in self.sortedComponents():
 
339
            component.generate(os)
 
340
        
 
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)
 
350
        
 
351
    def writeComponentsXML(self, node, namespace):
 
352
        
 
353
        if self.mComponents:
 
354
            comps = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.components))
 
355
            
 
356
            # Write out the remainder 
 
357
            for component in self.sortedComponents():
 
358
                component.writeXML(comps, namespace)
 
359
        
 
360
    def writeComponentsFilteredXML(self, node, namespace, filter):
 
361
 
 
362
        if self.mComponents:
 
363
            comps = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.components))
 
364
            
 
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)
 
373
        
 
374
    def loadValue(self, value_name):
 
375
        if self.hasProperty(value_name):
 
376
            return self.findFirstProperty(value_name)
 
377
 
 
378
        return None
 
379
 
 
380
    def loadValueInteger(self, value_name, type=None):
 
381
        if type:
 
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()
 
391
    
 
392
            return None
 
393
        else:
 
394
            return self.loadValueInteger(value_name, PyCalendarValue.VALUETYPE_INTEGER)
 
395
 
 
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()
 
401
 
 
402
        return None
 
403
 
 
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()
 
409
 
 
410
        return None
 
411
 
 
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()
 
417
 
 
418
        return None
 
419
 
 
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()
 
425
 
 
426
        return None
 
427
 
 
428
    def loadValueRRULE(self, value_name, value, add):
 
429
        # Get RRULEs
 
430
        if self.hasProperty(value_name):
 
431
            items = self.getProperties()[value_name]
 
432
            for iter in items:
 
433
                rvalue = iter.getRecurrenceValue()
 
434
                if (rvalue is not None):
 
435
                    if add:
 
436
                        value.addRule(rvalue.getValue())
 
437
                    else:
 
438
                        value.subtractRule(rvalue.getValue())
 
439
            return True
 
440
        else:
 
441
            return False
 
442
 
 
443
    def loadValueRDATE(self, value_name, value, add):
 
444
        # Get RDATEs
 
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():
 
450
                        # cast to date-time
 
451
                        if isinstance(obj, PyCalendarDateTimeValue):
 
452
                            if add:
 
453
                                value.addDT(obj.getValue())
 
454
                            else:
 
455
                                value.subtractDT(obj.getValue())
 
456
                        elif isinstance(obj, PyCalendarPeriodValue):
 
457
                            if add:
 
458
                                value.addPeriod(obj.getValue().getStart())
 
459
                            else:
 
460
                                value.subtractPeriod(obj.getValue().getStart())
 
461
 
 
462
            return True
 
463
        else:
 
464
            return False
 
465
 
 
466
    def sortedPropertyKeys(self):
 
467
        keys = self.mProperties.keys()
 
468
        keys.sort()
 
469
        
 
470
        results = []
 
471
        for skey in self.sortedPropertyKeyOrder():
 
472
            if skey in keys:
 
473
                results.append(skey)
 
474
                keys.remove(skey)
 
475
        results.extend(keys)
 
476
        return results
 
477
 
 
478
    def sortedPropertyKeyOrder(self):
 
479
        return ()
 
480
 
 
481
    def writeProperties(self, os):
 
482
        # Sort properties by name
 
483
        keys = self.sortedPropertyKeys()
 
484
        for key in keys:
 
485
            props = self.mProperties[key]
 
486
            for prop in props:
 
487
                prop.generate(os)
 
488
 
 
489
    def writePropertiesFiltered(self, os, filter):
 
490
 
 
491
        # Sort properties by name
 
492
        keys = self.sortedPropertyKeys()
 
493
 
 
494
        # Shortcut for all properties
 
495
        if filter.isAllProperties():
 
496
            for key in keys:
 
497
                for prop in self.getProperties(key):
 
498
                    prop.generate(os)
 
499
        elif filter.hasPropertyFilters():
 
500
            for key in keys:
 
501
                for prop in self.getProperties(key):
 
502
                    prop.generateFiltered(os, filter)
 
503
 
 
504
    def writePropertiesXML(self, node, namespace):
 
505
 
 
506
        properties = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.properties))
 
507
        
 
508
        # Sort properties by name
 
509
        keys = self.sortedPropertyKeys()
 
510
        for key in keys:
 
511
            props = self.mProperties[key]
 
512
            for prop in props:
 
513
                prop.writeXML(properties, namespace)
 
514
 
 
515
    def writePropertiesFilteredXML(self, node, namespace, filter):
 
516
 
 
517
        props = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.properties))
 
518
        
 
519
        # Sort properties by name
 
520
        keys = self.sortedPropertyKeys()
 
521
 
 
522
        # Shortcut for all properties
 
523
        if filter.isAllProperties():
 
524
            for key in keys:
 
525
                for prop in self.getProperties(key):
 
526
                    prop.writeXML(props, namespace)
 
527
        elif filter.hasPropertyFilters():
 
528
            for key in keys:
 
529
                for prop in self.getProperties(key):
 
530
                    prop.writeFilteredXML(props, namespace, filter)
 
531
 
 
532
    def loadPrivateValue(self, value_name):
 
533
        # Read it in from properties list and then delete the property from the
 
534
        # main list
 
535
        result = self.loadValueString(value_name)
 
536
        if (result is not None):
 
537
            self.removeProperties(value_name)
 
538
        return result
 
539
 
 
540
    def writePrivateProperty(self, os, key, value):
 
541
        prop = PyCalendarProperty(name=key, value=value)
 
542
        prop.generate(os)
 
543
 
 
544
    def editProperty(self, propname, propvalue):
 
545
 
 
546
        # Remove existing items
 
547
        self.removeProperties(propname)
 
548
 
 
549
        # Now create properties
 
550
        if propvalue:
 
551
            self.addProperty(PyCalendarProperty(name=propname, value=propvalue))