1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """Jabber Data Forms support.
18
19 Normative reference:
20 - `JEP 4 <http://www.jabber.org/jeps/jep-0004.html>`__
21 """
22
23 __revision__="$Id: disco.py 513 2005-01-09 16:34:00Z jajcus $"
24 __docformat__="restructuredtext en"
25
26 import copy
27 import libxml2
28 import warnings
29 from pyxmpp.objects import StanzaPayloadObject
30 from pyxmpp.utils import from_utf8, to_utf8
31 from pyxmpp.xmlextra import xml_element_ns_iter
32 from pyxmpp.jid import JID
33 from pyxmpp.exceptions import BadRequestProtocolError
34
35 DATAFORM_NS = "jabber:x:data"
36
37 -class Option(StanzaPayloadObject):
38 """One of optional data form field values.
39
40 :Ivariables:
41 - `label`: option label.
42 - `value`: option value.
43 :Types:
44 - `label`: `unicode`
45 - `value`: `unicode`
46 """
47 xml_element_name = "option"
48 xml_element_namespace = DATAFORM_NS
49
50 - def __init__(self, value = None, label = None, values = None):
51 """Initialize an `Option` object.
52
53 :Parameters:
54 - `value`: option value (mandatory).
55 - `label`: option label (human-readable description).
56 - `values`: for backward compatibility only.
57 :Types:
58 - `label`: `unicode`
59 - `value`: `unicode`
60 """
61 self.label = label
62
63 if value:
64 self.value = value
65 elif values:
66 warnings.warn("Option constructor accepts only single value now.", DeprecationWarning, stacklevel=1)
67 self.value = values[0]
68 else:
69 raise TypeError, "value argument to pyxmpp.dataforms.Option is required"
70
71
72 @property
74 """Return list of option values (always single element). Obsolete. For
75 backwards compatibility only."""
76 return [self.value]
77
79 """Complete the XML node with `self` content.
80
81 :Parameters:
82 - `xmlnode`: XML node with the element being built. It has already
83 right name and namespace, but no attributes or content.
84 - `doc`: document to which the element belongs.
85 :Types:
86 - `xmlnode`: `libxml2.xmlNode`
87 - `doc`: `libxml2.xmlDoc`"""
88 _unused = doc
89 if self.label is not None:
90 xmlnode.setProp("label", self.label.encode("utf-8"))
91 xmlnode.newTextChild(xmlnode.ns(), "value", self.value.encode("utf-8"))
92 return xmlnode
93
94 @classmethod
96 """Create a new `Option` object from an XML element.
97
98 :Parameters:
99 - `xmlnode`: the XML element.
100 :Types:
101 - `xmlnode`: `libxml2.xmlNode`
102
103 :return: the object created.
104 :returntype: `Option`
105 """
106 label = from_utf8(xmlnode.prop("label"))
107 child = xmlnode.children
108 value = None
109 for child in xml_element_ns_iter(xmlnode.children, DATAFORM_NS):
110 if child.name == "value":
111 value = from_utf8(child.getContent())
112 break
113 if value is None:
114 raise BadRequestProtocolError, "No value in <option/> element"
115 return cls(value, label)
116
117 -class Field(StanzaPayloadObject):
118 """A data form field.
119
120 :Ivariables:
121 - `name`: field name.
122 - `values`: field values.
123 - `value`: field value parsed according to the form type.
124 - `label`: field label (human-readable description).
125 - `type`: field type ("boolean", "fixed", "hidden", "jid-multi",
126 "jid-single", "list-multi", "list-single", "text-multi",
127 "text-private" or "text-single").
128 - `options`: field options (for "list-multi" or "list-single" fields).
129 - `required`: `True` when the field is required.
130 - `desc`: natural-language description of the field.
131 :Types:
132 - `name`: `unicode`
133 - `values`: `list` of `unicode`
134 - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID`
135 for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi"
136 and `unicode` for other field types.
137 - `label`: `unicode`
138 - `type`: `str`
139 - `options`: `Option`
140 - `required`: `boolean`
141 - `desc`: `unicode`
142 """
143 xml_element_name = "field"
144 xml_element_namespace = DATAFORM_NS
145 allowed_types = ("boolean", "fixed", "hidden", "jid-multi",
146 "jid-single", "list-multi", "list-single", "text-multi",
147 "text-private", "text-single")
148 - def __init__(self, name = None, values = None, field_type = None, label = None,
149 options = None, required = False, desc = None, value = None):
150 """Initialize a `Field` object.
151
152 :Parameters:
153 - `name`: field name.
154 - `values`: raw field values. Not to be used together with `value`.
155 - `field_type`: field type.
156 - `label`: field label.
157 - `options`: optional values for the field.
158 - `required`: `True` if the field is required.
159 - `desc`: natural-language description of the field.
160 - `value`: field value or values in a field_type-specific type. May be used only
161 if `values` parameter is not provided.
162 :Types:
163 - `name`: `unicode`
164 - `values`: `list` of `unicode`
165 - `field_type`: `str`
166 - `label`: `unicode`
167 - `options`: `list` of `Option`
168 - `required`: `bool`
169 - `desc`: `unicode`
170 - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID`
171 for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi"
172 and `unicode` for other field types.
173 """
174 self.name = name
175 if field_type is not None and field_type not in self.allowed_types:
176 raise ValueError, "Invalid form field type: %r" % (field_type,)
177 self.type = field_type
178 if value is not None:
179 if values:
180 raise ValueError, "values or value must be given, not both"
181 self.value = value
182 elif not values:
183 self.values = []
184 else:
185 self.values = list(values)
186 if field_type and not field_type.endswith("-multi") and len(self.values) > 1:
187 raise ValueError, "Multiple values for a single-value field"
188 self.label = label
189 if not options:
190 self.options = []
191 elif field_type and not field_type.startswith("list-"):
192 raise ValueError, "Options not allowed for non-list fields"
193 else:
194 self.options = list(options)
195 self.required = required
196 self.desc = desc
197
199 if name != "value":
200 raise AttributeError, "'Field' object has no attribute %r" % (name,)
201 values = self.values
202 t = self.type
203 l = len(values)
204 if t is not None:
205 if t == "boolean":
206 if l == 0:
207 return None
208 elif l == 1:
209 v = values[0]
210 if v in ("0","false"):
211 return False
212 elif v in ("1","true"):
213 return True
214 raise ValueError, "Bad boolean value"
215 elif t.startswith("jid-"):
216 values = [JID(v) for v in values]
217 if t.endswith("-multi"):
218 return values
219 if l == 0:
220 return None
221 elif l == 1:
222 return values[0]
223 else:
224 raise ValueError, "Multiple values of a single-value field"
225
247
249 """Add an option for the field.
250
251 :Parameters:
252 - `value`: option values.
253 - `label`: option label (human-readable description).
254 :Types:
255 - `value`: `list` of `unicode`
256 - `label`: `unicode`
257 """
258 if type(value) is list:
259 warnings.warn(".add_option() accepts single value now.", DeprecationWarning, stacklevel=1)
260 value = value[0]
261 if self.type not in ("list-multi", "list-single"):
262 raise ValueError, "Options are allowed only for list types."
263 option = Option(value, label)
264 self.options.append(option)
265 return option
266
268 """Complete the XML node with `self` content.
269
270 :Parameters:
271 - `xmlnode`: XML node with the element being built. It has already
272 right name and namespace, but no attributes or content.
273 - `doc`: document to which the element belongs.
274 :Types:
275 - `xmlnode`: `libxml2.xmlNode`
276 - `doc`: `libxml2.xmlDoc`"""
277 if self.type is not None and self.type not in self.allowed_types:
278 raise ValueError, "Invalid form field type: %r" % (self.type,)
279 if self.type is not None:
280 xmlnode.setProp("type", self.type)
281 if not self.label is None:
282 xmlnode.setProp("label", to_utf8(self.label))
283 if not self.name is None:
284 xmlnode.setProp("var", to_utf8(self.name))
285 if self.values:
286 if self.type and len(self.values) > 1 and not self.type.endswith(u"-multi"):
287 raise ValueError, "Multiple values not allowed for %r field" % (self.type,)
288 for value in self.values:
289 xmlnode.newTextChild(xmlnode.ns(), "value", to_utf8(value))
290 for option in self.options:
291 option.as_xml(xmlnode, doc)
292 if self.required:
293 xmlnode.newChild(xmlnode.ns(), "required", None)
294 if self.desc:
295 xmlnode.newTextChild(xmlnode.ns(), "desc", to_utf8(self.desc))
296 return xmlnode
297
298 @classmethod
300 """Create a new `Field` object from an XML element.
301
302 :Parameters:
303 - `xmlnode`: the XML element.
304 :Types:
305 - `xmlnode`: `libxml2.xmlNode`
306
307 :return: the object created.
308 :returntype: `Field`
309 """
310 field_type = xmlnode.prop("type")
311 label = from_utf8(xmlnode.prop("label"))
312 name = from_utf8(xmlnode.prop("var"))
313 child = xmlnode.children
314 values = []
315 options = []
316 required = False
317 desc = None
318 while child:
319 if child.type != "element" or child.ns().content != DATAFORM_NS:
320 pass
321 elif child.name == "required":
322 required = True
323 elif child.name == "desc":
324 desc = from_utf8(child.getContent())
325 elif child.name == "value":
326 values.append(from_utf8(child.getContent()))
327 elif child.name == "option":
328 options.append(Option._new_from_xml(child))
329 child = child.next
330 if field_type and not field_type.endswith("-multi") and len(values) > 1:
331 raise BadRequestProtocolError, "Multiple values for a single-value field"
332 return cls(name, values, field_type, label, options, required, desc)
333
334 -class Item(StanzaPayloadObject):
335 """An item of multi-item form data (e.g. a search result).
336
337 Additionally to the direct access to the contained fields via the `fields` attribute,
338 `Item` object provides an iterator and mapping interface for field access. E.g.::
339
340 for field in item:
341 ...
342
343 or::
344
345 field = item['field_name']
346
347 or::
348
349 if 'field_name' in item:
350 ...
351
352 :Ivariables:
353 - `fields`: the fields of the item.
354 :Types:
355 - `fields`: `list` of `Field`.
356 """
357 xml_element_name = "item"
358 xml_element_namespace = DATAFORM_NS
359
361 """Initialize an `Item` object.
362
363 :Parameters:
364 - `fields`: item fields.
365 :Types:
366 - `fields`: `list` of `Field`.
367 """
368 if fields is None:
369 self.fields = []
370 else:
371 self.fields = list(fields)
372
374 if isinstance(name_or_index, int):
375 return self.fields[name_or_index]
376 for f in self.fields:
377 if f.name == name_or_index:
378 return f
379 raise KeyError, name_or_index
380
382 for f in self.fields:
383 if f.name == name:
384 return True
385 return False
386
388 for field in self.fields:
389 yield field
390
391 - def add_field(self, name = None, values = None, field_type = None,
392 label = None, options = None, required = False, desc = None, value = None):
393 """Add a field to the item.
394
395 :Parameters:
396 - `name`: field name.
397 - `values`: raw field values. Not to be used together with `value`.
398 - `field_type`: field type.
399 - `label`: field label.
400 - `options`: optional values for the field.
401 - `required`: `True` if the field is required.
402 - `desc`: natural-language description of the field.
403 - `value`: field value or values in a field_type-specific type. May be used only
404 if `values` parameter is not provided.
405 :Types:
406 - `name`: `unicode`
407 - `values`: `list` of `unicode`
408 - `field_type`: `str`
409 - `label`: `unicode`
410 - `options`: `list` of `Option`
411 - `required`: `bool`
412 - `desc`: `unicode`
413 - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID`
414 for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi"
415 and `unicode` for other field types.
416
417 :return: the field added.
418 :returntype: `Field`
419 """
420 field = Field(name, values, field_type, label, options, required, desc, value)
421 self.fields.append(field)
422 return field
423
425 """Complete the XML node with `self` content.
426
427 :Parameters:
428 - `xmlnode`: XML node with the element being built. It has already
429 right name and namespace, but no attributes or content.
430 - `doc`: document to which the element belongs.
431 :Types:
432 - `xmlnode`: `libxml2.xmlNode`
433 - `doc`: `libxml2.xmlDoc`"""
434 for field in self.fields:
435 field.as_xml(xmlnode, doc)
436
437 @classmethod
439 """Create a new `Item` object from an XML element.
440
441 :Parameters:
442 - `xmlnode`: the XML element.
443 :Types:
444 - `xmlnode`: `libxml2.xmlNode`
445
446 :return: the object created.
447 :returntype: `Item`
448 """
449 child = xmlnode.children
450 fields = []
451 while child:
452 if child.type != "element" or child.ns().content != DATAFORM_NS:
453 pass
454 elif child.name == "field":
455 fields.append(Field._new_from_xml(child))
456 child = child.next
457 return cls(fields)
458
713
714