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