Package pyxmpp :: Package jabber :: Module dataforms
[hide private]

Source Code for Module pyxmpp.jabber.dataforms

  1  # 
  2  # (C) Copyright 2005-2010 Jacek Konieczny <jajcus@jajcus.net> 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU Lesser General Public License Version 
  6  # 2.1 as published by the Free Software Foundation. 
  7  # 
  8  # This program is distributed in the hope that it will be useful, 
  9  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 11  # GNU Lesser General Public License for more details. 
 12  # 
 13  # You should have received a copy of the GNU Lesser General Public 
 14  # License along with this program; if not, write to the Free Software 
 15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 16  # 
 17  """Jabber Data Forms support. 
 18   
 19  Normative reference: 
 20    - `JEP 4 <http://www.jabber.org/jeps/jep-0004.html>`__ 
 21  """ 
 22   
 23  __docformat__="restructuredtext en" 
 24   
 25  import copy 
 26  import libxml2 
 27  import warnings 
 28  from pyxmpp.objects import StanzaPayloadObject 
 29  from pyxmpp.utils import from_utf8, to_utf8 
 30  from pyxmpp.xmlextra import xml_element_ns_iter 
 31  from pyxmpp.jid import JID 
 32  from pyxmpp.exceptions import BadRequestProtocolError 
 33   
 34  DATAFORM_NS = "jabber:x:data" 
35 36 -class Option(StanzaPayloadObject):
37 """One of optional data form field values. 38 39 :Ivariables: 40 - `label`: option label. 41 - `value`: option value. 42 :Types: 43 - `label`: `unicode` 44 - `value`: `unicode` 45 """ 46 xml_element_name = "option" 47 xml_element_namespace = DATAFORM_NS 48
49 - def __init__(self, value = None, label = None, values = None):
50 """Initialize an `Option` object. 51 52 :Parameters: 53 - `value`: option value (mandatory). 54 - `label`: option label (human-readable description). 55 - `values`: for backward compatibility only. 56 :Types: 57 - `label`: `unicode` 58 - `value`: `unicode` 59 """ 60 self.label = label 61 62 if value: 63 self.value = value 64 elif values: 65 warnings.warn("Option constructor accepts only single value now.", DeprecationWarning, stacklevel=1) 66 self.value = values[0] 67 else: 68 raise TypeError, "value argument to pyxmpp.dataforms.Option is required"
69 70 71 @property
72 - def values(self):
73 """Return list of option values (always single element). Obsolete. For 74 backwards compatibility only.""" 75 return [self.value]
76
77 - def complete_xml_element(self, xmlnode, doc):
78 """Complete the XML node with `self` content. 79 80 :Parameters: 81 - `xmlnode`: XML node with the element being built. It has already 82 right name and namespace, but no attributes or content. 83 - `doc`: document to which the element belongs. 84 :Types: 85 - `xmlnode`: `libxml2.xmlNode` 86 - `doc`: `libxml2.xmlDoc`""" 87 _unused = doc 88 if self.label is not None: 89 xmlnode.setProp("label", self.label.encode("utf-8")) 90 xmlnode.newTextChild(xmlnode.ns(), "value", self.value.encode("utf-8")) 91 return xmlnode
92 93 @classmethod
94 - def _new_from_xml(cls, xmlnode):
95 """Create a new `Option` object from an XML element. 96 97 :Parameters: 98 - `xmlnode`: the XML element. 99 :Types: 100 - `xmlnode`: `libxml2.xmlNode` 101 102 :return: the object created. 103 :returntype: `Option` 104 """ 105 label = from_utf8(xmlnode.prop("label")) 106 child = xmlnode.children 107 value = None 108 for child in xml_element_ns_iter(xmlnode.children, DATAFORM_NS): 109 if child.name == "value": 110 value = from_utf8(child.getContent()) 111 break 112 if value is None: 113 raise BadRequestProtocolError, "No value in <option/> element" 114 return cls(value, label)
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
197 - def __getattr__(self, name):
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
225 - def __setattr__(self, name, value):
226 if name != "value": 227 self.__dict__[name] = value 228 return 229 if value is None: 230 self.values = [] 231 return 232 t = self.type 233 if t == "boolean": 234 if value: 235 self.values = ["1"] 236 else: 237 self.values = ["0"] 238 return 239 if t and t.endswith("-multi"): 240 values = list(value) 241 else: 242 values = [value] 243 if t and t.startswith("jid-"): 244 values = [JID(v).as_unicode() for v in values] 245 self.values = values
246
247 - def add_option(self, value, label):
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
266 - def complete_xml_element(self, xmlnode, doc):
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 if self.type is not None: 279 xmlnode.setProp("type", self.type) 280 if not self.label is None: 281 xmlnode.setProp("label", to_utf8(self.label)) 282 if not self.name is None: 283 xmlnode.setProp("var", to_utf8(self.name)) 284 if self.values: 285 if self.type and len(self.values) > 1 and not self.type.endswith(u"-multi"): 286 raise ValueError, "Multiple values not allowed for %r field" % (self.type,) 287 for value in self.values: 288 xmlnode.newTextChild(xmlnode.ns(), "value", to_utf8(value)) 289 for option in self.options: 290 option.as_xml(xmlnode, doc) 291 if self.required: 292 xmlnode.newChild(xmlnode.ns(), "required", None) 293 if self.desc: 294 xmlnode.newTextChild(xmlnode.ns(), "desc", to_utf8(self.desc)) 295 return xmlnode
296 297 @classmethod
298 - def _new_from_xml(cls, xmlnode):
299 """Create a new `Field` object from an XML element. 300 301 :Parameters: 302 - `xmlnode`: the XML element. 303 :Types: 304 - `xmlnode`: `libxml2.xmlNode` 305 306 :return: the object created. 307 :returntype: `Field` 308 """ 309 field_type = xmlnode.prop("type") 310 label = from_utf8(xmlnode.prop("label")) 311 name = from_utf8(xmlnode.prop("var")) 312 child = xmlnode.children 313 values = [] 314 options = [] 315 required = False 316 desc = None 317 while child: 318 if child.type != "element" or child.ns().content != DATAFORM_NS: 319 pass 320 elif child.name == "required": 321 required = True 322 elif child.name == "desc": 323 desc = from_utf8(child.getContent()) 324 elif child.name == "value": 325 values.append(from_utf8(child.getContent())) 326 elif child.name == "option": 327 options.append(Option._new_from_xml(child)) 328 child = child.next 329 if field_type and not field_type.endswith("-multi") and len(values) > 1: 330 raise BadRequestProtocolError, "Multiple values for a single-value field" 331 return cls(name, values, field_type, label, options, required, desc)
332
333 -class Item(StanzaPayloadObject):
334 """An item of multi-item form data (e.g. a search result). 335 336 Additionally to the direct access to the contained fields via the `fields` attribute, 337 `Item` object provides an iterator and mapping interface for field access. E.g.:: 338 339 for field in item: 340 ... 341 342 or:: 343 344 field = item['field_name'] 345 346 or:: 347 348 if 'field_name' in item: 349 ... 350 351 :Ivariables: 352 - `fields`: the fields of the item. 353 :Types: 354 - `fields`: `list` of `Field`. 355 """ 356 xml_element_name = "item" 357 xml_element_namespace = DATAFORM_NS 358
359 - def __init__(self, fields = None):
360 """Initialize an `Item` object. 361 362 :Parameters: 363 - `fields`: item fields. 364 :Types: 365 - `fields`: `list` of `Field`. 366 """ 367 if fields is None: 368 self.fields = [] 369 else: 370 self.fields = list(fields)
371
372 - def __getitem__(self, name_or_index):
373 if isinstance(name_or_index, int): 374 return self.fields[name_or_index] 375 for f in self.fields: 376 if f.name == name_or_index: 377 return f 378 raise KeyError, name_or_index
379
380 - def __contains__(self, name):
381 for f in self.fields: 382 if f.name == name: 383 return True 384 return False
385
386 - def __iter__(self):
387 for field in self.fields: 388 yield field
389
390 - def add_field(self, name = None, values = None, field_type = None, 391 label = None, options = None, required = False, desc = None, value = None):
392 """Add a field to the item. 393 394 :Parameters: 395 - `name`: field name. 396 - `values`: raw field values. Not to be used together with `value`. 397 - `field_type`: field type. 398 - `label`: field label. 399 - `options`: optional values for the field. 400 - `required`: `True` if the field is required. 401 - `desc`: natural-language description of the field. 402 - `value`: field value or values in a field_type-specific type. May be used only 403 if `values` parameter is not provided. 404 :Types: 405 - `name`: `unicode` 406 - `values`: `list` of `unicode` 407 - `field_type`: `str` 408 - `label`: `unicode` 409 - `options`: `list` of `Option` 410 - `required`: `bool` 411 - `desc`: `unicode` 412 - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID` 413 for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi" 414 and `unicode` for other field types. 415 416 :return: the field added. 417 :returntype: `Field` 418 """ 419 field = Field(name, values, field_type, label, options, required, desc, value) 420 self.fields.append(field) 421 return field
422
423 - def complete_xml_element(self, xmlnode, doc):
424 """Complete the XML node with `self` content. 425 426 :Parameters: 427 - `xmlnode`: XML node with the element being built. It has already 428 right name and namespace, but no attributes or content. 429 - `doc`: document to which the element belongs. 430 :Types: 431 - `xmlnode`: `libxml2.xmlNode` 432 - `doc`: `libxml2.xmlDoc`""" 433 for field in self.fields: 434 field.as_xml(xmlnode, doc)
435 436 @classmethod
437 - def _new_from_xml(cls, xmlnode):
438 """Create a new `Item` object from an XML element. 439 440 :Parameters: 441 - `xmlnode`: the XML element. 442 :Types: 443 - `xmlnode`: `libxml2.xmlNode` 444 445 :return: the object created. 446 :returntype: `Item` 447 """ 448 child = xmlnode.children 449 fields = [] 450 while child: 451 if child.type != "element" or child.ns().content != DATAFORM_NS: 452 pass 453 elif child.name == "field": 454 fields.append(Field._new_from_xml(child)) 455 child = child.next 456 return cls(fields)
457
458 -class Form(StanzaPayloadObject):
459 """A JEP-0004 compliant data form. 460 461 Additionally to the direct access to the contained fields via the `fields` attribute, 462 `Form` object provides an iterator and mapping interface for field access. E.g.:: 463 464 for field in form: 465 ... 466 467 or:: 468 469 field = form['field_name'] 470 471 :Ivariables: 472 - `type`: form type ("form", "submit", "cancel" or "result"). 473 - `title`: form title. 474 - `instructions`: instructions for a form user. 475 - `fields`: the fields in the form. 476 - `reported_fields`: list of fields returned in a multi-item data form. 477 - `items`: items in a multi-item data form. 478 :Types: 479 - `title`: `unicode` 480 - `instructions`: `unicode` 481 - `fields`: `list` of `Field` 482 - `reported_fields`: `list` of `Field` 483 - `items`: `list` of `Item` 484 """ 485 allowed_types = ("form", "submit", "cancel", "result") 486 xml_element_name = "x" 487 xml_element_namespace = DATAFORM_NS 488
489 - def __init__(self, xmlnode_or_type = "form", title = None, instructions = None, 490 fields = None, reported_fields = None, items = None):
491 """Initialize a `Form` object. 492 493 :Parameters: 494 - `xmlnode_or_type`: XML element to parse or a form title. 495 - `title`: form title. 496 - `instructions`: instructions for the form. 497 - `fields`: form fields. 498 - `reported_fields`: fields reported in multi-item data. 499 - `items`: items of multi-item data. 500 :Types: 501 - `xmlnode_or_type`: `libxml2.xmlNode` or `str` 502 - `title`: `unicode` 503 - `instructions`: `unicode` 504 - `fields`: `list` of `Field` 505 - `reported_fields`: `list` of `Field` 506 - `items`: `list` of `Item` 507 """ 508 if isinstance(xmlnode_or_type, libxml2.xmlNode): 509 self.__from_xml(xmlnode_or_type) 510 elif xmlnode_or_type not in self.allowed_types: 511 raise ValueError, "Form type %r not allowed." % (xmlnode_or_type,) 512 else: 513 self.type = xmlnode_or_type 514 self.title = title 515 self.instructions = instructions 516 if fields: 517 self.fields = list(fields) 518 else: 519 self.fields = [] 520 if reported_fields: 521 self.reported_fields = list(reported_fields) 522 else: 523 self.reported_fields = [] 524 if items: 525 self.items = list(items) 526 else: 527 self.items = []
528
529 - def __getitem__(self, name_or_index):
530 if isinstance(name_or_index, int): 531 return self.fields[name_or_index] 532 for f in self.fields: 533 if f.name == name_or_index: 534 return f 535 raise KeyError, name_or_index
536
537 - def __contains__(self, name):
538 for f in self.fields: 539 if f.name == name: 540 return True 541 return False
542
543 - def __iter__(self):
544 for field in self.fields: 545 yield field
546
547 - def add_field(self, name = None, values = None, field_type = None, 548 label = None, options = None, required = False, desc = None, value = None):
549 """Add a field to the form. 550 551 :Parameters: 552 - `name`: field name. 553 - `values`: raw field values. Not to be used together with `value`. 554 - `field_type`: field type. 555 - `label`: field label. 556 - `options`: optional values for the field. 557 - `required`: `True` if the field is required. 558 - `desc`: natural-language description of the field. 559 - `value`: field value or values in a field_type-specific type. May be used only 560 if `values` parameter is not provided. 561 :Types: 562 - `name`: `unicode` 563 - `values`: `list` of `unicode` 564 - `field_type`: `str` 565 - `label`: `unicode` 566 - `options`: `list` of `Option` 567 - `required`: `bool` 568 - `desc`: `unicode` 569 - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID` 570 for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi" 571 and `unicode` for other field types. 572 573 :return: the field added. 574 :returntype: `Field` 575 """ 576 field = Field(name, values, field_type, label, options, required, desc, value) 577 self.fields.append(field) 578 return field
579
580 - def add_item(self, fields = None):
581 """Add and item to the form. 582 583 :Parameters: 584 - `fields`: fields of the item (they may be added later). 585 :Types: 586 - `fields`: `list` of `Field` 587 588 :return: the item added. 589 :returntype: `Item` 590 """ 591 item = Item(fields) 592 self.items.append(item) 593 return item
594
595 - def make_submit(self, keep_types = False):
596 """Make a "submit" form using data in `self`. 597 598 Remove uneeded information from the form. The information removed 599 includes: title, instructions, field labels, fixed fields etc. 600 601 :raise ValueError: when any required field has no value. 602 603 :Parameters: 604 - `keep_types`: when `True` field type information will be included 605 in the result form. That is usually not needed. 606 :Types: 607 - `keep_types`: `bool` 608 609 :return: the form created. 610 :returntype: `Form`""" 611 result = Form("submit") 612 for field in self.fields: 613 if field.type == "fixed": 614 continue 615 if not field.values: 616 if field.required: 617 raise ValueError, "Required field with no value!" 618 continue 619 if keep_types: 620 result.add_field(field.name, field.values, field.type) 621 else: 622 result.add_field(field.name, field.values) 623 return result
624
625 - def copy(self):
626 """Get a deep copy of `self`. 627 628 :return: a deep copy of `self`. 629 :returntype: `Form`""" 630 return copy.deepcopy(self)
631
632 - def complete_xml_element(self, xmlnode, doc):
633 """Complete the XML node with `self` content. 634 635 :Parameters: 636 - `xmlnode`: XML node with the element being built. It has already 637 right name and namespace, but no attributes or content. 638 - `doc`: document to which the element belongs. 639 :Types: 640 - `xmlnode`: `libxml2.xmlNode` 641 - `doc`: `libxml2.xmlDoc`""" 642 if self.type not in self.allowed_types: 643 raise ValueError, "Form type %r not allowed." % (self.type,) 644 xmlnode.setProp("type", self.type) 645 if self.type == "cancel": 646 return 647 ns = xmlnode.ns() 648 if self.title is not None: 649 xmlnode.newTextChild(ns, "title", to_utf8(self.title)) 650 if self.instructions is not None: 651 xmlnode.newTextChild(ns, "instructions", to_utf8(self.instructions)) 652 for field in self.fields: 653 field.as_xml(xmlnode, doc) 654 if self.type != "result": 655 return 656 if self.reported_fields: 657 reported = xmlnode.newChild(ns, "reported", None) 658 for field in self.reported_fields: 659 field.as_xml(reported, doc) 660 for item in self.items: 661 item.as_xml(xmlnode, doc)
662
663 - def __from_xml(self, xmlnode):
664 """Initialize a `Form` object from an XML element. 665 666 :Parameters: 667 - `xmlnode`: the XML element. 668 :Types: 669 - `xmlnode`: `libxml2.xmlNode` 670 """ 671 self.fields = [] 672 self.reported_fields = [] 673 self.items = [] 674 self.title = None 675 self.instructions = None 676 if (xmlnode.type != "element" or xmlnode.name != "x" 677 or xmlnode.ns().content != DATAFORM_NS): 678 raise ValueError, "Not a form: " + xmlnode.serialize() 679 self.type = xmlnode.prop("type") 680 if not self.type in self.allowed_types: 681 raise BadRequestProtocolError, "Bad form type: %r" % (self.type,) 682 child = xmlnode.children 683 while child: 684 if child.type != "element" or child.ns().content != DATAFORM_NS: 685 pass 686 elif child.name == "title": 687 self.title = from_utf8(child.getContent()) 688 elif child.name == "instructions": 689 self.instructions = from_utf8(child.getContent()) 690 elif child.name == "field": 691 self.fields.append(Field._new_from_xml(child)) 692 elif child.name == "item": 693 self.items.append(Item._new_from_xml(child)) 694 elif child.name == "reported": 695 self.__get_reported(child) 696 child = child.next
697
698 - def __get_reported(self, xmlnode):
699 """Parse the <reported/> element of the form. 700 701 :Parameters: 702 - `xmlnode`: the element to parse. 703 :Types: 704 - `xmlnode`: `libxml2.xmlNode`""" 705 child = xmlnode.children 706 while child: 707 if child.type != "element" or child.ns().content != DATAFORM_NS: 708 pass 709 elif child.name == "field": 710 self.reported_fields.append(Field._new_from_xml(child)) 711 child = child.next
712 # vi: sts=4 et sw=4 713