Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dataforms.py « common « src - dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: f1390bf0504fc469b01f6461179c04784f61abf0 (plain)
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# this will go to src/common/xmpp later, for now it is in src/common

import xmpp

# Helpers, to handle multi-element values like field values of instructions elements

def get_multiple_tag_value(node, childname):
	""" Join by u'/n' all occurences of childname in node, as described in JEP-0004 for value data for
	*-multi fields."""
	assert isinstance(node, xmpp.Node)
	assert isinstance(childname, basestring)

	nodes = node.getTags(childname)
	values = (node.getData().decode('utf-8') for node in nodes)	# using python2.4 iterators
	return u"\n".join(values)

def set_multiple_tag_value(node, childname, value):
	""" Set children nodes to value, splitting lines between different nodes, as described in JEP-0004
	for value data for *-multi fields. If you try to set the value to None, all tag values will be
	deleted. """
	assert isinstance(node, xmpp.Node)
	assert isinstance(childname, basestring)
	assert isinstance(value, basestring) or value is None

	del_multiple_tag_value(node, childname)

	if value is None: return

	values = value.split(u'\n')
	nodes = (xmpp.Node(childname, payload=[value.encode('utf-8')]) for value in values)# using python2.4 iterators
	for new in nodes:
		node.addChild(node=new)

def del_multiple_tag_value(node, childname):
	""" Delete a value from node, when the value is splitted between many childname elements inside
	node, as described in JEP-0004 for value for *-multi fields. It also removes every instance of
	childname in node.
	"""

	try:
		while node.delChild(childname):
			pass
	except ValueError:
		pass

def iter_elements(node, childname):
	""" Iterate over childname children of node element. """
	for element in node.getChildren():
		if isinstance(element, xmpp.Node) and element.getName()==childname:
			yield element

(DATAFORM_SINGLE, DATAFORM_MULTIPLE) = range(2)

class RecordDoesNotContainRequiredField(Exception): pass
class BadDataFormNode(Exception): pass

class DataForm(xmpp.Node, object):
	""" Data form as described in JEP-0004. """

	def __init__(self, typ=None, title=None, instructions=None, fields=None, records=None, node=None, tofill=None):
		""" Create new node, based on the node given by 'node' parameter, or new.
		You can prefill some form properties, look at their description for meaning.

		You can also set 'tofill' to DataForm to get a form of type 'submit' with the
		same fields as in the form given, but without unnecessary data like
		instructions or title. Also the type will be set to 'submit' by default."""
		assert (isinstance(node, xmpp.Node) or node is None)
		assert (isinstance(tofill, DataForm) or tofill is None)
		assert not (node is not None and tofill is not None)

		assert typ in (None, 'form', 'submit', 'cancel', 'result')
		assert isinstance(title, basestring) or title is None
		assert isinstance(instructions, basestring) or instructions is None
		assert (fields is None or fields.__iter__)

		xmpp.Node.__init__(self, 'x', node=node)
		self.setNamespace(xmpp.NS_DATA)

		if tofill is not None:
			self._mode = tofill.mode
			self.fields = (field for field in tofill.fields if field.type!='fixed')
			self.records = tofill.records
			self.type = 'submit'
			for field in self.iter_fields():
				field.required=False
				del field.label
				del field.options
				del field.description
		elif node is not None:
			# if there is <reported/> element, the form contains multiple records
			if self.getTag('reported') is not None:
				# in multiple-record forms there must not be any fields not in <items/>
				if self.getTag('field') is not None: raise BadDataNodeForm
				self._mode = DATAFORM_MULTIPLE

				# change every <field/> to DataField object
				for item in iter_elements(self, 'item'):
					for field in iter_elements(item, 'field'):
						field.delChild(field)
						field.addChild(node=DataField(node=field))
			else:
				self._mode = DATAFORM_SINGLE

				# change every <field/> to DataField object
				toadd=[]
				for field in self.getChildren():
					if not isinstance(field, xmpp.Node): continue
					if not field.getName()=='field': continue
					toadd.append(DataField(node=field))

				del_multiple_tag_value(self, 'field')

				for field in toadd:
					self.addChild(node=field)
		else: # neither tofill nor node has a Node
			if typ is None: typ='result'
			if records is not None and len(records)>1:
				self._mode = DATAFORM_MULTIPLE
			else:
				self._mode = DATAFORM_SINGLE

		if typ is not None: self.type = typ
		if title is not None: self.title = title
		if instructions is not None: self.instructions = instructions
		if fields is not None: self.fields = fields
		if records is not None: self.records = records

	def get_type(self):
		return self.getAttr('type')

	def set_type(self, type):
		assert type in ('form', 'submit', 'cancel', 'result')
		self.setAttr('type', type)

	type = property(get_type, set_type, None,
		"""Form type, one of:
		'form', when it is meant to complete,
		'submit', when it is meant to transport completed form,
		'cancel', when it is meant to cancel the process,
		'result', when it is meant to provide some data. (the default)""")

	def get_title(self):
		return self.getTagData('title')

	def set_title(self, title):
		self.setTagData('title', title)

	def del_title(self):
		try:
			self.delChild('title')
		except ValueError:
			pass

	title = property(get_title, set_title, del_title,
		"Form title, in unicode, from <title/> element.")

	def get_instructions(self):
		return get_multiple_tag_value(self, 'instructions')

	def set_instructions(self, data):
		assert isinstance(data, basestring)
		set_multiple_tag_value(self, 'instructions', data)

	def del_instructions(self):
		del_multiple_tag_value(self, 'instructions')

	instructions = property(get_instructions, set_instructions, None,
		"Instructions how to fill the form, in unicode, from <instructions/> element.")

	def get_mode(self):
		return self._mode

	def set_mode(self, mode):
		assert mode in (DATAFORM_SINGLE, DATAFORM_MULTIPLE)
		assert self.getTag('field') is None
		assert self.getTag('reported') is None
		assert self.getTag('item') is None
		self._mode = mode

	mode = property(get_mode, set_mode, None,
		"""Data form mode: DATAFORM_SINGLE or DATAFORM_MULTIPLE, if the form contains
		more than one record of data. Changing mode is allowed as long as there is no
		values in form.""")

	def iter_records(self):
		if self.mode is DATAFORM_SINGLE:
			yield DataRecord(node=self)
		else:
			for item in self.getChildren():
				if not isinstance(item, xmpp.Node): continue
				if not item.getName()=='item': continue
				yield DataRecord(node=item)

	def get_records(self):
		if self.mode is DATAFORM_SINGLE:
			return [DataRecord(node=self),]
		else:
			items = []
			for node in self.getChildren():
				if not isinstance(node, xmpp.Node): continue
				if not node.getName()=='item': continue
				items.append(DataRecord(node=node))
			return items

	def set_records(self, records):
		if self.mode is DATAFORM_SINGLE:
			assert len(records)==1

			record = records[0]
			assert isinstance(record, DataRecord)
			for field in record.iter_fields():
				self[field.var]=field.value
		else:
			self.del_records(self)
			for record in records:
				assert isinstance(record, dict)
				newitem = self.addChild('item', node=record)

	def del_records(self):
		if self.mode is DATAFORM_SINGLE:
			# removing values from every field
			for field in self.iter_fields():
				del_multiple_tag_value(field, "value")
		else:
			# removing all <items/> elements
			del_multiple_tag_value(self, "items")

	records = property(get_records, set_records, del_records,
		"""Records kept in this form; if in DATAFORM_SINGLE mode, there will be exactly
		one record, otherwise there might be more or less records.""")

	def get_fields(self):
		if self.mode is DATAFORM_SINGLE:
			container = self
		else:
			container = self.getTag("recorded")

		return container.getTags("field")

	def set_fields(self, fields):
		if self.mode is DATAFORM_SINGLE:
			del_multiple_tag_value(self, "field")
			for field in fields:
				assert isinstance(field, DataField)
				self.addChild(node=field)
		else:
			assert len(self.records)==0
			try:
				self.delChild('recorded')
			except ValueError:
				pass
			self.addChild('recorded', None, fields)

	def del_fields(self):
		if self.mode is DATAFORM_SINGLE:
			del_multiple_tag_value(self, "field")
		else:
			try:
				self.delChild('recorded')
			except ValueError:
				pass

	def iter_fields(self):
		if self.mode is DATAFORM_SINGLE:
			container = self
		else:
			container = self.getTag("recorded")

		for child in container.getChildren():
			if isinstance(child, DataField):
				yield child

	fields = property(get_fields, set_fields, del_fields,
		"""Fields in this form; a list; if in DATAFORM_SINGLE mode, you should not
		set their values directly.""")

	def __getitem__(self, var):
		for field in self.iter_fields():
			if field.var==var:
				return field.value
		raise KeyError, "This form does not contain %r field." % var

	def __setitem__(self, var, value):
		for field in self.iter_fields():
			if field.var==var:
				field.value=value
				return
		raise KeyError, "This form does not contain %r field." % var

class DataField(xmpp.Node, object):
	def __init__(self, typ=None,var=None, value=None, label=None, desc=None,
		required=None, options=None, node=None):

		assert typ in ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
			'list-single', 'text-multi', 'text-private', 'text-single',None)
		
		xmpp.Node.__init__(self, 'field', node=node)
		if typ is not None: self.type = typ
		if var is not None: self.var = var
		if label is not None: self.label = label
		if desc is not None: self.description = desc
		if required is not None: self.required = required
		if value is not None: self.value = value
		if options is not None: self.options = options

	def get_type(self):
		# JEP says that if we don't understand field name, we should treat it as text-single
		t = self.getAttr('type')
		if t not in ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
			'list-single', 'text-multi', 'text-private'):
			return 'text-single'
		else:
			return t

	def set_type(self, typ):
		assert typ in ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
			'list-single', 'text-multi', 'text-private', 'text-single')

		if typ!='text-single':
			self.setAttr('type', typ)
		else:
			try:
				self.delAttr('type')
			except KeyError:
				pass

	type = property(get_type, set_type, None,
		""" Field type. One of: 'boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single',
		'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single'.""")

	def get_var(self):
		return self.getAttr('var')

	def set_var(self, var):
		self.setAttr('var', var)

	def del_var(self):
		self.delAttr('var')

	var = property(get_var, set_var, del_var,
		""" Field name. """)

	def get_label(self):
		return self.getAttr('label')

	def set_label(self, label):
		self.setAttr('label', label)

	def del_label(self):
		self.delAttr('label')

	label = property(get_label, set_label, del_label,
		""" Human-readable name for field. """)

	def get_description(self):
		return self.getTagData('desc')

	def set_description(self, desc):
		self.setTagData('desc', desc)

	def del_description(self):
		try:
			self.delChild('desc')
		except ValueError:
			pass

	description = property(get_description, set_description, del_description,
		""" A natural-language description of the field. It should not contain
		newlines. """)

	def get_required(self):
		return self.getTag('required') is not None

	def set_required(self, req):
		assert req in (True, False)

		if self.getTag('required') is not None:
			if req is False:
				self.delChild('required')
		else:
			if req is True:
				self.addChild('required')

	required = property(get_required, set_required, None,
		""" If this is set to True, the field is required for form to be valid. """)

	def get_value(self):
		if self.type in ('boolean',):
			if self.getTagData('value') in (1, 'true'):
				return True
			else:
				return False

		elif self.type in ('fixed','text-multi'):
			return get_multiple_tag_value(self, 'value')

		elif self.type in ('jid-multi', 'list-multi'):
			return [value.getData() for value in self.getTags('value')]

		elif self.type in ('hidden', 'jid-single', 'list-single', 'text-single', 'text-private') or True:
			return self.getTagData('value')

	def set_value(self, value):
		if self.type in ('boolean',):
			if value:
				self.setTagData('value', '1')
			else:
				self.setTagData('value', '0')

		elif self.type in ('fixed','text-multi'):
			set_multiple_tag_value(self, 'value', value)

		elif self.type in ('jid-multi', 'list-multi'):
			del_multiple_tag_value(self, 'value')
			for item in value:
				self.addChild('value', {}, (item,))

		elif self.type in ('hidden', 'jid-single', 'list-single', 'text-single', 'text-private'):
			self.setTagData('value', value)

	def del_value(self):
		del_multiple_tag_value(self, 'value')

	value = property(get_value, set_value, del_value,
		""" The value of field. Depending on the type, it is a boolean, a unicode string or a list
		of stings. """)

	def iter_options(self):
		""" Yields a pair: label, value """
		for element in self.getChildren():
			if not isinstance(element, xmpp.Node): continue
			if not element.getName()=='option': continue
			if element.getTag('value') is None: raise BadDataFormNode
			yield element.getAttr('label'), element.getTag('value').getData()

	def get_options(self):
		""" Returns a list of tuples: (label, value). """
		return [(tag.getAttr('label'), tag.getTag('value').getData()) for tag in self.getTags('option')]

	def set_options(self, options):
		""" Options need to be a list of tuples (label, value), both unicode. """
		assert options.__iter__

		del_multiple_tag_value(self, 'option')
		for option in options:
			assert option[0] is None or isinstance(option[0], unicode)
			assert isinstance(option[1], unicode)
			if option[0] is None:
				attr={}
			else:
				attr={'label': option[0].encode('utf-8')}
			self.addChild('option', attr, (xmpp.Node('value', {}, (option[1].encode('utf-8'),)),))

	def del_options(self):
		del_multiple_tag_value(self, 'option')

	options = property(get_options, set_options, del_options,
		""" Options to choose between in list-* fields. """)

class DataRecord(xmpp.Node):
	""" Class to store fields. May be used as temporary storage (for example when reading a list of
	fields from DataForm in DATAFORM_SINGLE mode), may be used as permanent storage place (for example
	for DataForms in DATAFORM_MULTIPLE mode). It expects that every <field/> element is actually
	a DataField instance."""
	def __init__(self, fields=None, node=None):
		assert (fields is None) or (node is None)
		assert (fields is None) or (fields.__iter__)
		assert (node is None) or (isinstance(node, xmpp.Node))

		self.vars = {}

		xmpp.Node.__init__(self, node=node)

		if node is not None:
			for field in node.getTags('field'):
				assert isinstance(field, DataField)
				self.vars[field.var] = field

		if fields is not None:
			for field in fields:
				assert isinstance(field, DataField)
				self.addChild(node=field)
				self.vars[field.var] = field

	# if there will be ever needed access to all fields as a list, write it here, in form of property

	def iter_fields(self):
		for field in self.getChildren():
			if not isinstance(field, xmpp.DataField): continue
			yield field

	def __getitem__(self, item):
		return self.vars[item]