1 # this will go to src/common/xmpp later, for now it is in src/common
3 ## src/common/dataforms.py
5 ## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
6 ## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org>
7 ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
9 ## This file is part of Gajim.
11 ## Gajim is free software; you can redistribute it and/or modify
12 ## it under the terms of the GNU General Public License as published
13 ## by the Free Software Foundation; version 3 only.
15 ## Gajim is distributed in the hope that it will be useful,
16 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ## GNU General Public License for more details.
20 ## You should have received a copy of the GNU General Public License
21 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
25 This module contains wrappers for different parts of data forms (JEP 0004). For
26 information how to use them, read documentation
32 # exceptions used in this module
34 class Error(Exception): pass
35 # when we get xmpp.Node which we do not understand
36 class UnknownDataForm(Error
): pass
37 # when we get xmpp.Node which contains bad fields
38 class WrongFieldValue(Error
): pass
40 # helper class to change class of already existing object
41 class ExtendedNode(xmpp
.Node
, object):
43 def __new__(cls
, *a
, **b
):
44 if 'extend' not in b
.keys() or not b
['extend']:
45 return object.__new
__(cls
)
48 assert issubclass(cls
, extend
.__class
__)
49 extend
.__class
__ = cls
52 # helper decorator to create properties in cleaner way
53 def nested_property(f
):
55 p
= {'doc': f
.__doc
__}
56 for v
in ('fget', 'fset', 'fdel', 'doc'):
57 if v
in ret
.keys(): p
[v
]=ret
[v
]
60 # helper to create fields from scratch
61 def Field(typ
, **attrs
):
62 ''' Helper function to create a field of given type. '''
64 'boolean': BooleanField
,
66 'hidden': StringField
,
67 'text-private': StringField
,
68 'text-single': StringField
,
69 'jid-multi': JidMultiField
,
70 'jid-single': JidSingleField
,
71 'list-multi': ListMultiField
,
72 'list-single': ListSingleField
,
73 'text-multi': TextMultiField
,
74 }[typ
](typ
=typ
, **attrs
)
77 def ExtendField(node
):
79 Helper function to extend a node to field of appropriate type
81 # when validation (XEP-122) will go in, we could have another classes
82 # like DateTimeField - so that dicts in Field() and ExtendField() will
84 typ
=node
.getAttr('type')
86 'boolean': BooleanField
,
88 'hidden': StringField
,
89 'text-private': StringField
,
90 'text-single': StringField
,
91 'jid-multi': JidMultiField
,
92 'jid-single': JidSingleField
,
93 'list-multi': ListMultiField
,
94 'list-single': ListSingleField
,
95 'text-multi': TextMultiField
,
99 return f
[typ
](extend
=node
)
101 def ExtendForm(node
):
103 Helper function to extend a node to form of appropriate type
105 if node
.getTag('reported') is not None:
106 return MultipleDataForm(extend
=node
)
108 return SimpleDataForm(extend
=node
)
110 class DataField(ExtendedNode
):
112 Keeps data about one field - var, field type, labels, instructions... Base
113 class for different kinds of fields. Use Field() function to construct one
117 def __init__(self
, typ
=None, var
=None, value
=None, label
=None, desc
=None,
118 required
=False, options
=None, extend
=None):
121 ExtendedNode
.__init
__(self
, 'field')
125 if value
is not None:
127 if label
is not None:
131 self
.required
= required
132 self
.options
= options
137 Type of field. Recognized values are: 'boolean', 'fixed', 'hidden',
138 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi',
139 'text-private', 'text-single'. If you set this to something different,
140 DataField will store given name, but treat all data as text-single
143 t
= self
.getAttr('type')
148 def fset(self
, value
):
149 assert isinstance(value
, basestring
)
150 self
.setAttr('type', value
)
160 return self
.getAttr('var')
162 def fset(self
, value
):
163 assert isinstance(value
, basestring
)
164 self
.setAttr('var', value
)
174 Human-readable field name
177 l
= self
.getAttr('label')
182 def fset(self
, value
):
183 assert isinstance(value
, basestring
)
184 self
.setAttr('label', value
)
187 if self
.getAttr('label'):
188 self
.delAttr('label')
195 Human-readable description of field meaning
198 return self
.getTagData('desc') or u
''
200 def fset(self
, value
):
201 assert isinstance(value
, basestring
)
205 self
.setTagData('desc', value
)
208 t
= self
.getTag('desc')
217 Controls whether this field required to fill. Boolean
220 return bool(self
.getTag('required'))
222 def fset(self
, value
):
223 t
= self
.getTag('required')
226 elif not t
and value
:
227 self
.addChild('required')
237 media
= self
.getTag('media', namespace
=xmpp
.NS_DATA_MEDIA
)
241 def fset(self
, value
):
243 self
.addChild(node
=value
)
246 t
= self
.getTag('media')
255 class Uri(xmpp
.Node
):
256 def __init__(self
, uri_tag
):
257 xmpp
.Node
.__init
__(self
, node
=uri_tag
)
265 return self
.getAttr('type')
267 def fset(self
, value
):
268 self
.setAttr('type', value
)
281 return self
.getData()
283 def fset(self
, value
):
291 class Media(xmpp
.Node
):
292 def __init__(self
, media_tag
):
293 xmpp
.Node
.__init
__(self
, node
=media_tag
)
298 URIs of the media element.
301 return map(Uri
, self
.getTags('uri'))
303 def fset(self
, value
):
306 self
.addChild(node
=uri
)
308 def fdel(self
, value
):
309 for element
in self
.getTags('uri'):
310 self
.delChild(element
)
314 class BooleanField(DataField
):
318 Value of field. May contain True, False or None
321 v
= self
.getTagData('value')
322 if v
in ('0', 'false'):
324 if v
in ('1', 'true'):
327 return False # default value is False
328 raise WrongFieldValue
330 def fset(self
, value
):
331 self
.setTagData('value', value
and '1' or '0')
333 def fdel(self
, value
):
334 t
= self
.getTag('value')
340 class StringField(DataField
):
342 Covers fields of types: fixed, hidden, text-private, text-single
348 Value of field. May be any unicode string
351 return self
.getTagData('value') or u
''
353 def fset(self
, value
):
354 assert isinstance(value
, basestring
)
355 if value
== '' and not self
.required
:
357 self
.setTagData('value', value
)
361 self
.delChild(self
.getTag('value'))
362 except ValueError: # if there already were no value tag
367 class ListField(DataField
):
369 Covers fields of types: jid-multi, jid-single, list-multi, list-single
379 for element
in self
.getTags('option'):
380 v
= element
.getTagData('value')
382 raise WrongFieldValue
383 l
= element
.getAttr('label')
386 options
.append((l
, v
))
389 def fset(self
, values
):
391 for value
, label
in values
:
392 self
.addChild('option', {'label': label
}).setTagData('value', value
)
395 for element
in self
.getTags('option'):
396 self
.delChild(element
)
400 def iter_options(self
):
401 for element
in self
.iterTags('option'):
402 v
= element
.getTagData('value')
404 raise WrongFieldValue
405 l
= element
.getAttr('label')
410 class ListSingleField(ListField
, StringField
):
412 Covers list-single field
415 if not self
.required
:
421 class JidSingleField(ListSingleField
):
423 Covers jid-single fields
428 helpers
.parse_jid(self
.value
)
436 class ListMultiField(ListField
):
438 Covers list-multi fields
448 for element
in self
.getTags('value'):
449 values
.append(element
.getData())
452 def fset(self
, values
):
455 self
.addChild('value').setData(value
)
458 for element
in self
.getTags('value'):
459 self
.delChild(element
)
463 def iter_values(self
):
464 for element
in self
.getTags('value'):
465 yield element
.getData()
468 if not self
.required
:
474 class JidMultiField(ListMultiField
):
476 Covers jid-multi fields
480 for value
in self
.values
:
482 helpers
.parse_jid(value
)
490 class TextMultiField(DataField
):
498 for element
in self
.iterTags('value'):
499 value
+= '\n' + element
.getData()
502 def fset(self
, value
):
506 for line
in value
.split('\n'):
507 self
.addChild('value').setData(line
)
510 for element
in self
.getTags('value'):
511 self
.delChild(element
)
515 class DataRecord(ExtendedNode
):
517 The container for data fields - an xml element which has DataField elements
520 def __init__(self
, fields
=None, associated
=None, extend
=None):
521 self
.associated
= associated
524 # we have to build this object from scratch
525 xmpp
.Node
.__init
__(self
)
527 if fields
is not None:
530 # we already have xmpp.Node inside - try to convert all
531 # fields into DataField objects
533 for field
in self
.iterTags('field'):
534 if not isinstance(field
, DataField
):
536 self
.vars[field
.var
] = field
538 for field
in self
.getTags('field'):
545 List of fields in this record
548 return self
.getTags('field')
550 def fset(self
, fields
):
553 if not isinstance(field
, DataField
):
554 ExtendField(extend
=field
)
555 self
.addChild(node
=field
)
558 for element
in self
.getTags('field'):
559 self
.delChild(element
)
563 def iter_fields(self
):
565 Iterate over fields in this record. Do not take associated into account
567 for field
in self
.iterTags('field'):
570 def iter_with_associated(self
):
572 Iterate over associated, yielding both our field and associated one
575 for field
in self
.associated
.iter_fields():
576 yield self
[field
.var
], field
578 def __getitem__(self
, item
):
579 return self
.vars[item
]
582 for f
in self
.iter_fields():
587 class DataForm(ExtendedNode
):
588 def __init__(self
, type_
=None, title
=None, instructions
=None, extend
=None):
590 # we have to build form from scratch
591 xmpp
.Node
.__init
__(self
, 'x', attrs
={'xmlns': xmpp
.NS_DATA
})
593 if type_
is not None:
595 if title
is not None:
597 if instructions
is not None:
598 self
.instructions
=instructions
603 Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'.
604 'form' - this form is to be filled in; you will be able soon to do:
605 filledform = DataForm(replyto=thisform)
608 return self
.getAttr('type')
610 def fset(self
, type_
):
611 assert type_
in ('form', 'submit', 'cancel', 'result')
612 self
.setAttr('type', type_
)
621 Human-readable, should not contain any \\r\\n.
624 return self
.getTagData('title')
626 def fset(self
, title
):
627 self
.setTagData('title', title
)
631 self
.delChild('title')
640 Instructions for this form
642 Human-readable, may contain \\r\\n.
644 # TODO: the same code is in TextMultiField. join them
647 for valuenode
in self
.getTags('instructions'):
648 value
+= '\n' + valuenode
.getData()
651 def fset(self
, value
):
653 if value
== '': return
654 for line
in value
.split('\n'):
655 self
.addChild('instructions').setData(line
)
658 for value
in self
.getTags('instructions'):
663 class SimpleDataForm(DataForm
, DataRecord
):
664 def __init__(self
, type_
=None, title
=None, instructions
=None, fields
=None, \
666 DataForm
.__init
__(self
, type_
=type_
, title
=title
,
667 instructions
=instructions
, extend
=extend
)
668 DataRecord
.__init
__(self
, fields
=fields
, extend
=self
, associated
=self
)
670 def get_purged(self
):
671 c
= SimpleDataForm(extend
=self
)
675 for f
in c
.iter_fields():
677 # add <value> if there is not
678 if hasattr(f
, 'value') and not f
.value
:
680 # Keep all required fields
682 if (hasattr(f
, 'value') and not f
.value
and f
.value
!= 0) or (
683 hasattr(f
, 'values') and len(f
.values
) == 0):
684 to_be_removed
.append(f
)
689 for f
in to_be_removed
:
693 class MultipleDataForm(DataForm
):
694 def __init__(self
, type_
=None, title
=None, instructions
=None, items
=None,
696 DataForm
.__init
__(self
, type_
=type_
, title
=title
,
697 instructions
=instructions
, extend
=extend
)
698 # all records, recorded into DataRecords
700 if items
is not None:
703 # we already have xmpp.Node inside - try to convert all
704 # fields into DataField objects
706 self
.items
= list(self
.iterTags('item'))
708 for item
in self
.getTags('item'):
711 reported_tag
= self
.getTag('reported')
712 self
.reported
= DataRecord(extend
=reported_tag
)
717 A list of all records
720 return list(self
.iter_records())
722 def fset(self
, records
):
724 for record
in records
:
725 if not isinstance(record
, DataRecord
):
726 DataRecord(extend
=record
)
727 self
.addChild(node
=record
)
730 for record
in self
.getTags('item'):
731 self
.delChild(record
)
735 def iter_records(self
):
736 for record
in self
.getTags('item'):
742 # DataRecord that contains descriptions of fields in records
745 # return self.getTag('reported')
746 # def fset(self, record):
748 # self.delChild('reported')
752 # record.setName('reported')
753 # self.addChild(node=record)