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 - `values`: option values.
43 :Types:
44 - `label`: `unicode`
45 - `values`: `list` of `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 xmlnode.setProp("label", self.label.encode("utf-8"))
90 xmlnode.newTextChild(xmlnode.ns(), "value", self.value.encode("utf-8"))
91 return xmlnode
92
94 """Create a new `Option` object from an XML element.
95
96 :Parameters:
97 - `xmlnode`: the XML element.
98 :Types:
99 - `xmlnode`: `libxml2.xmlNode`
100
101 :return: the object created.
102 :returntype: `Option`
103 """
104 label = from_utf8(xmlnode.prop("label"))
105 child = xmlnode.children
106 value = None
107 for child in xml_element_ns_iter(xmlnode.children, DATAFORM_NS):
108 if child.name == "value":
109 value = from_utf8(child.getContent())
110 break
111 if value is None:
112 raise BadRequestProtocolError, "No value in <option/> element"
113 return cls(value, label)
114 _new_from_xml = classmethod(_new_from_xml)
115
116 -class Field(StanzaPayloadObject):
117 """A data form field.
118
119 :Ivariables:
120 - `name`: field name.
121 - `values`: field values.
122 - `value`: field value parsed according to the form type.
123 - `label`: field label (human-readable description).
124 - `type`: field type ("boolean", "fixed", "hidden", "jid-multi",
125 "jid-single", "list-multi", "list-single", "text-multi",
126 "text-private" or "text-single").
127 - `options`: field options (for "list-multi" or "list-single" fields).
128 - `required`: `True` when the field is required.
129 - `desc`: natural-language description of the field.
130 :Types:
131 - `name`: `unicode`
132 - `values`: `list` of `unicode`
133 - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID`
134 for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi"
135 and `unicode` for other field types.
136 - `label`: `unicode`
137 - `type`: `str`
138 - `options`: `Option`
139 - `required`: `boolean`
140 - `desc`: `unicode`
141 """
142 xml_element_name = "field"
143 xml_element_namespace = DATAFORM_NS
144 allowed_types = ("boolean", "fixed", "hidden", "jid-multi",
145 "jid-single", "list-multi", "list-single", "text-multi",
146 "text-private", "text-single")
147 - def __init__(self, name = None, values = None, field_type = None, label = None,
148 options = None, required = False, desc = None, value = None):
149 """Initialize a `Field` object.
150
151 :Parameters:
152 - `name`: field name.
153 - `values`: raw field values. Not to be used together with `value`.
154 - `field_type`: field type.
155 - `label`: field label.
156 - `options`: optional values for the field.
157 - `required`: `True` if the field is required.
158 - `desc`: natural-language description of the field.
159 - `value`: field value or values in a field_type-specific type. May be used only
160 if `values` parameter is not provided.
161 :Types:
162 - `name`: `unicode`
163 - `values`: `list` of `unicode`
164 - `field_type`: `str`
165 - `label`: `unicode`
166 - `options`: `list` of `Option`
167 - `required`: `bool`
168 - `desc`: `unicode`
169 - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID`
170 for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi"
171 and `unicode` for other field types.
172 """
173 self.name = name
174 if field_type is not None and field_type not in self.allowed_types:
175 raise ValueError, "Invalid form field type: %r" % (field_type,)
176 self.type = field_type
177 if value is not None:
178 if values:
179 raise ValueError, "values or value must be given, not both"
180 self.value = value
181 elif not values:
182 self.values = []
183 else:
184 self.values = list(values)
185 if field_type and not field_type.endswith("-multi") and len(self.values) > 1:
186 raise ValueError, "Multiple values for a single-value field"
187 self.label = label
188 if not options:
189 self.options = []
190 elif field_type and not field_type.startswith("list-"):
191 raise ValueError, "Options not allowed for non-list fields"
192 else:
193 self.options = list(options)
194 self.required = required
195 self.desc = desc
196
198 if name != "value":
199 raise AttributeError, "'Field' object has no attribute %r" % (name,)
200 values = self.values
201 t = self.type
202 l = len(values)
203 if t is not None:
204 if t == "boolean":
205 if l == 0:
206 return None
207 elif l == 1:
208 v = values[0]
209 if v in ("0","false"):
210 return False
211 elif v in ("1","true"):
212 return True
213 raise ValueError, "Bad boolean value"
214 elif t.startswith("jid-"):
215 values = [JID(v) for v in values]
216 if t.endswith("-multi"):
217 return values
218 if l == 0:
219 return None
220 elif l == 1:
221 return values[0]
222 else:
223 raise ValueError, "Multiple values of a single-value field"
224
246
248 """Add an option for the field.
249
250 :Parameters:
251 - `value`: option values.
252 - `label`: option label (human-readable description).
253 :Types:
254 - `value`: `list` of `unicode`
255 - `label`: `unicode`
256 """
257 if type(value) is list:
258 warnings.warn(".add_option() accepts single value now.", DeprecationWarning, stacklevel=1)
259 value = value[0]
260 if self.type not in ("list-multi", "list-single"):
261 raise ValueError, "Options are allowed only for list types."
262 option = Option(value, label)
263 self.options.append(option)
264 return option
265
267 """Complete the XML node with `self` content.
268
269 :Parameters:
270 - `xmlnode`: XML node with the element being built. It has already
271 right name and namespace, but no attributes or content.
272 - `doc`: document to which the element belongs.
273 :Types:
274 - `xmlnode`: `libxml2.xmlNode`
275 - `doc`: `libxml2.xmlDoc`"""
276 if self.type is not None and self.type not in self.allowed_types:
277 raise ValueError, "Invalid form field type: %r" % (self.type,)
278 xmlnode.setProp("type", self.type)
279 if not self.label is None:
280 xmlnode.setProp("label", self.label)
281 if not self.name is None:
282 xmlnode.setProp("var", self.name)
283 if self.values:
284 if self.type and len(self.values) > 1 and not self.type.endswith(u"-multi"):
285 raise ValueError, "Multiple values not allowed for %r field" % (self.type,)
286 for value in self.values:
287 xmlnode.newTextChild(xmlnode.ns(), "value", to_utf8(value))
288 for option in self.options:
289 option.as_xml(xmlnode, doc)
290 if self.required:
291 xmlnode.newChild(xmlnode.ns(), "required", None)
292 if self.desc:
293 xmlnode.newTextChild(xmlnode.ns(), "desc", to_utf8(self.desc))
294 return xmlnode
295
297 """Create a new `Field` object from an XML element.
298
299 :Parameters:
300 - `xmlnode`: the XML element.
301 :Types:
302 - `xmlnode`: `libxml2.xmlNode`
303
304 :return: the object created.
305 :returntype: `Field`
306 """
307 field_type = xmlnode.prop("type")
308 label = from_utf8(xmlnode.prop("label"))
309 name = from_utf8(xmlnode.prop("var"))
310 child = xmlnode.children
311 values = []
312 options = []
313 required = False
314 desc = None
315 while child:
316 if child.type != "element" or child.ns().content != DATAFORM_NS:
317 pass
318 elif child.name == "required":
319 required = True
320 elif child.name == "desc":
321 desc = from_utf8(child.getContent())
322 elif child.name == "value":
323 values.append(from_utf8(child.getContent()))
324 elif child.name == "option":
325 options.append(Option._new_from_xml(child))
326 child = child.next
327 if field_type and not field_type.endswith("-multi") and len(values) > 1:
328 raise BadRequestProtocolError, "Multiple values for a single-value field"
329 return cls(name, values, field_type, label, options, required, desc)
330 _new_from_xml = classmethod(_new_from_xml)
331
332 -class Item(StanzaPayloadObject):
333 """An item of multi-item form data (e.g. a search result).
334
335 Additionally to the direct access to the contained fields via the `fields` attribute,
336 `Item` object provides an iterator and mapping interface for field access. E.g.::
337
338 for field in item:
339 ...
340
341 or::
342
343 field = item['field_name']
344
345 or::
346
347 if 'field_name' in item:
348 ...
349
350 :Ivariables:
351 - `fields`: the fields of the item.
352 :Types:
353 - `fields`: `list` of `Field`.
354 """
355 xml_element_name = "item"
356 xml_element_namespace = DATAFORM_NS
357
359 """Initialize an `Item` object.
360
361 :Parameters:
362 - `fields`: item fields.
363 :Types:
364 - `fields`: `list` of `Field`.
365 """
366 if fields is None:
367 self.fields = []
368 else:
369 self.fields = list(fields)
370
372 if isinstance(name_or_index, int):
373 return self.fields[name_or_index]
374 for f in self.fields:
375 if f.name == name_or_index:
376 return f
377 raise KeyError, name_or_index
378
380 for f in self.fields:
381 if f.name == name:
382 return True
383 return False
384
386 for field in self.fields:
387 yield field
388
389 - def add_field(self, name = None, values = None, field_type = None,
390 label = None, options = None, required = False, desc = None, value = None):
391 """Add a field to the item.
392
393 :Parameters:
394 - `name`: field name.
395 - `values`: raw field values. Not to be used together with `value`.
396 - `field_type`: field type.
397 - `label`: field label.
398 - `options`: optional values for the field.
399 - `required`: `True` if the field is required.
400 - `desc`: natural-language description of the field.
401 - `value`: field value or values in a field_type-specific type. May be used only
402 if `values` parameter is not provided.
403 :Types:
404 - `name`: `unicode`
405 - `values`: `list` of `unicode`
406 - `field_type`: `str`
407 - `label`: `unicode`
408 - `options`: `list` of `Option`
409 - `required`: `bool`
410 - `desc`: `unicode`
411 - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID`
412 for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi"
413 and `unicode` for other field types.
414
415 :return: the field added.
416 :returntype: `Field`
417 """
418 field = Field(name, values, field_type, label, options, required, desc, value)
419 self.fields.append(field)
420 return field
421
423 """Complete the XML node with `self` content.
424
425 :Parameters:
426 - `xmlnode`: XML node with the element being built. It has already
427 right name and namespace, but no attributes or content.
428 - `doc`: document to which the element belongs.
429 :Types:
430 - `xmlnode`: `libxml2.xmlNode`
431 - `doc`: `libxml2.xmlDoc`"""
432 for field in self.fields:
433 field.as_xml(xmlnode, doc)
434
436 """Create a new `Item` object from an XML element.
437
438 :Parameters:
439 - `xmlnode`: the XML element.
440 :Types:
441 - `xmlnode`: `libxml2.xmlNode`
442
443 :return: the object created.
444 :returntype: `Item`
445 """
446 child = xmlnode.children
447 fields = []
448 while child:
449 if child.type != "element" or child.ns().content != DATAFORM_NS:
450 pass
451 elif child.name == "field":
452 fields.append(Field._new_from_xml(child))
453 child = child.next
454 return cls(fields)
455 _new_from_xml = classmethod(_new_from_xml)
456
711
712