forked from skarim/vobject
-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathbehavior.py
More file actions
205 lines (177 loc) · 7.98 KB
/
behavior.py
File metadata and controls
205 lines (177 loc) · 7.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
from . import base
properties_allow_localization = ['NAME','SUMMARY','DESCRIPTION','LOCATION']
#------------------------ Abstract class for behavior --------------------------
class Behavior(object):
"""
Behavior (validation, encoding, and transformations) for vobjects.
Abstract class to describe vobject options, requirements and encodings.
Behaviors are used for root components like VCALENDAR, for subcomponents
like VEVENT, and for individual lines in components.
Behavior subclasses are not meant to be instantiated, all methods should
be classmethods.
@cvar name:
The uppercase name of the object described by the class, or a generic
name if the class defines behavior for many objects.
@cvar description:
A brief excerpt from the RFC explaining the function of the component or
line.
@cvar versionString:
The string associated with the component, for instance, 2.0 if there's a
line like VERSION:2.0, an empty string otherwise.
@cvar knownChildren:
A dictionary with uppercased component/property names as keys and a
tuple (min, max, id) as value, where id is the id used by
L{registerBehavior}, min and max are the limits on how many of this child
must occur. None is used to denote no max or no id.
@cvar quotedPrintable:
A boolean describing whether the object should be encoded and decoded
using quoted printable line folding and character escaping.
@cvar defaultBehavior:
Behavior to apply to ContentLine children when no behavior is found.
@cvar hasNative:
A boolean describing whether the object can be transformed into a more
Pythonic object.
@cvar isComponent:
A boolean, True if the object should be a Component.
@cvar sortFirst:
The lower-case list of children which should come first when sorting.
@cvar allowGroup:
Whether or not vCard style group prefixes are allowed.
"""
name = ''
description = ''
versionString = ''
knownChildren = {}
quotedPrintable = False
defaultBehavior = None
hasNative = False
isComponent = False
allowGroup = False
forceUTC = False
sortFirst = []
def __init__(self):
err = "Behavior subclasses are not meant to be instantiated"
raise base.VObjectError(err)
@classmethod
def validate(cls, obj, raiseException=False, complainUnrecognized=False):
"""Check if the object satisfies this behavior's requirements.
@param obj:
The L{ContentLine<base.ContentLine>} or
L{Component<base.Component>} to be validated.
@param raiseException:
If True, raise a L{base.ValidateError} on validation failure.
Otherwise return a boolean.
@param complainUnrecognized:
If True, fail to validate if an uncrecognized parameter or child is
found. Otherwise log the lack of recognition.
"""
if not cls.allowGroup and obj.group is not None:
err = "{0} has a group, but this object doesn't support groups".format(obj)
raise base.VObjectError(err)
if isinstance(obj, base.ContentLine):
return cls.lineValidate(obj, raiseException, complainUnrecognized)
elif isinstance(obj, base.Component):
count = {}
language_count = {} #example: {'SUMMARY': {'en': 1, 'ja': 1} , 'DESCRIPTION': {None: 1}}
for child in obj.getChildren():
if not child.validate(raiseException, complainUnrecognized):
return False
name = child.name.upper()
count[name] = count.get(name, 0) + 1
if isinstance(child, base.ContentLine) and name in properties_allow_localization:
# for some properties, we count separately with respect to language
lang = child.params.get('LANGUAGE', [])
if len(lang) == 0:
lang = None
elif len(lang) == 1:
lang = lang[0].lower()
elif len(lang) > 1:
if raiseException:
m = "Multiple language parameters specified for property {1}"
raise base.ValidateError(m.format(name))
return False
if name in language_count:
language_count[name][lang] = language_count[name].get(lang, 0) + 1
else:
language_count[name] = {lang: 1}
for key, val in cls.knownChildren.items():
minimum_count = val[0]
if count.get(key, 0) < minimum_count:
if raiseException:
m = "{0} components must contain at least {1} {2}"
raise base.ValidateError(m .format(cls.name, minimum_count, key))
return False
maximum_count = val[1]
if maximum_count and count.get(key, 0) > maximum_count:
if key in properties_allow_localization:
# check if the maximum is exceeded for any language
for lang, count_for_lang in language_count.get(key, {}).items():
if count_for_lang > maximum_count:
if raiseException:
m = "{0} components cannot contain more than {1} {2} with the same language"
raise base.ValidateError(m.format(cls.name, maximum_count, key))
return False
elif raiseException:
m = "{0} components cannot contain more than {1} {2}"
raise base.ValidateError(m.format(cls.name, maximum_count, key))
else:
return False
return True
else:
err = "{0} is not a Component or Contentline".format(obj)
raise base.VObjectError(err)
@classmethod
def lineValidate(cls, line, raiseException, complainUnrecognized):
"""Examine a line's parameters and values, return True if valid."""
return True
@classmethod
def decode(cls, line):
if line.encoded:
line.encoded = 0
@classmethod
def encode(cls, line):
if not line.encoded:
line.encoded = 1
@classmethod
def transformToNative(cls, obj):
"""
Turn a ContentLine or Component into a Python-native representation.
If appropriate, turn dates or datetime strings into Python objects.
Components containing VTIMEZONEs turn into VtimezoneComponents.
"""
return obj
@classmethod
def transformFromNative(cls, obj):
"""
Inverse of transformToNative.
"""
raise base.NativeError("No transformFromNative defined")
@classmethod
def generateImplicitParameters(cls, obj):
"""Generate any required information that don't yet exist."""
pass
@classmethod
def serialize(cls, obj, buf, lineLength, validate=True):
"""
Set implicit parameters, do encoding, return unicode string.
If validate is True, raise VObjectError if the line doesn't validate
after implicit parameters are generated.
Default is to call base.defaultSerialize.
"""
cls.generateImplicitParameters(obj)
if validate:
cls.validate(obj, raiseException=True)
if obj.isNative:
transformed = obj.transformFromNative()
undoTransform = True
else:
transformed = obj
undoTransform = False
out = base.defaultSerialize(transformed, buf, lineLength)
if undoTransform:
obj.transformToNative()
return out
@classmethod
def valueRepr(cls, line):
"""return the representation of the given content line value"""
return line.value