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

# Source Code for Module pyxmpp.jabber.dataforms

      1  #
      2  # (C) Copyright 2005-2006 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  __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
     73 -    def values(self):
     74          """Return list of option values (always single element). Obsolete. For
     75          backwards compatibility only."""
     76          return [self.value]
     77
     78 -    def complete_xml_element(self, xmlnode, doc):
     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
     93 -    def _new_from_xml(cls, xmlnode):
     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
    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          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
    296 -    def _new_from_xml(cls, xmlnode):
    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
    358 -    def __init__(self, fields = None):
    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
    371 -    def __getitem__(self, name_or_index):
    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
    379 -    def __contains__(self, name):
    380          for f in self.fields:
    381              if f.name == name:
    382                  return True
    383          return False
    384
    385 -    def __iter__(self):
    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
    422 -    def complete_xml_element(self, xmlnode, doc):
    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
    435 -    def _new_from_xml(cls, xmlnode):
    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
    457 -class Form(StanzaPayloadObject):
    458      """A JEP-0004 compliant data form.
    459
    460      Additionally to the direct access to the contained fields via the `fields` attribute,
    461      `Form` object provides an iterator and mapping interface for field access. E.g.::
    462
    463          for field in form:
    464              ...
    465
    466      or::
    467
    468          field = form['field_name']
    469
    470      :Ivariables:
    471          - `type`: form type ("form", "submit", "cancel" or "result").
    472          - `title`: form title.
    473          - `instructions`: instructions for a form user.
    474          - `fields`: the fields in the form.
    475          - `reported_fields`: list of fields returned in a multi-item data form.
    476          - `items`: items in a multi-item data form.
    477      :Types:
    478          - `title`: `unicode`
    479          - `instructions`: `unicode`
    480          - `fields`: `list` of `Field`
    481          - `reported_fields`: `list` of `Field`
    482          - `items`: `list` of `Item`
    483      """
    484      allowed_types = ("form", "submit", "cancel", "result")
    485      xml_element_name = "x"
    486      xml_element_namespace = DATAFORM_NS
    487
    488 -    def __init__(self, xmlnode_or_type = "form", title = None, instructions = None,
    489              fields = None, reported_fields = None, items = None):
    490          """Initialize a `Form` object.
    491
    492          :Parameters:
    493              - `xmlnode_or_type`: XML element to parse or a form title.
    494              - `title`: form title.
    495              - `instructions`: instructions for the form.
    496              - `fields`: form fields.
    497              - `reported_fields`: fields reported in multi-item data.
    498              - `items`: items of multi-item data.
    499          :Types:
    500              - `xmlnode_or_type`: `libxml2.xmlNode` or `str`
    501              - `title`: `unicode`
    502              - `instructions`: `unicode`
    503              - `fields`: `list` of `Field`
    504              - `reported_fields`: `list` of `Field`
    505              - `items`: `list` of `Item`
    506          """
    507          if isinstance(xmlnode_or_type, libxml2.xmlNode):
    508              self.__from_xml(xmlnode_or_type)
    509          elif xmlnode_or_type not in self.allowed_types:
    510              raise ValueError, "Form type %r not allowed." % (xmlnode_or_type,)
    511          else:
    512              self.type = xmlnode_or_type
    513              self.title = title
    514              self.instructions = instructions
    515              if fields:
    516                  self.fields = list(fields)
    517              else:
    518                  self.fields = []
    519              if reported_fields:
    520                  self.reported_fields = list(reported_fields)
    521              else:
    522                  self.reported_fields = []
    523              if items:
    524                  self.items = list(items)
    525              else:
    526                  self.items = []
    527
    528 -    def __getitem__(self, name_or_index):
    529          if isinstance(name_or_index, int):
    530              return self.fields[name_or_index]
    531          for f in self.fields:
    532              if f.name == name_or_index:
    533                  return f
    534          raise KeyError, name_or_index
    535
    536 -    def __contains__(self, name):
    537          for f in self.fields:
    538              if f.name == name:
    539                  return True
    540          return False
    541
    542 -    def __iter__(self):
    543          for field in self.fields:
    544              yield field
    545
    546 -    def add_field(self, name = None, values = None, field_type = None,
    547              label = None, options = None, required = False, desc = None, value = None):
    548          """Add a field to the form.
    549
    550          :Parameters:
    551              - `name`: field name.
    552              - `values`: raw field values. Not to be used together with `value`.
    553              - `field_type`: field type.
    554              - `label`: field label.
    555              - `options`: optional values for the field.
    556              - `required`: `True` if the field is required.
    557              - `desc`: natural-language description of the field.
    558              - `value`: field value or values in a field_type-specific type. May be used only
    559                if `values` parameter is not provided.
    560          :Types:
    561              - `name`: `unicode`
    562              - `values`: `list` of `unicode`
    563              - `field_type`: `str`
    564              - `label`: `unicode`
    565              - `options`: `list` of `Option`
    566              - `required`: `bool`
    567              - `desc`: `unicode`
    568              - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID`
    569                for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi"
    570                and `unicode` for other field types.
    571
    572          :return: the field added.
    573          :returntype: `Field`
    574          """
    575          field = Field(name, values, field_type, label, options, required, desc, value)
    576          self.fields.append(field)
    577          return field
    578
    579 -    def add_item(self, fields = None):
    580          """Add and item to the form.
    581
    582          :Parameters:
    583              - `fields`: fields of the item (they may be added later).
    584          :Types:
    585              - `fields`: `list` of `Field`
    586
    587          :return: the item added.
    588          :returntype: `Item`
    589          """
    590          item = Item(fields)
    591          self.items.append(item)
    592          return item
    593
    594 -    def make_submit(self, keep_types = False):
    595          """Make a "submit" form using data in `self`.
    596
    597          Remove uneeded information from the form. The information removed
    598          includes: title, instructions, field labels, fixed fields etc.
    599
    600          :raise ValueError: when any required field has no value.
    601
    602          :Parameters:
    603              - `keep_types`: when `True` field type information will be included
    604                in the result form. That is usually not needed.
    605          :Types:
    606              - `keep_types`: `bool`
    607
    608          :return: the form created.
    609          :returntype: `Form`"""
    610          result = Form("submit")
    611          for field in self.fields:
    612              if field.type == "fixed":
    613                  continue
    614              if not field.values:
    615                  if field.required:
    616                      raise ValueError, "Required field with no value!"
    617                  continue
    618              if keep_types:
    619                  result.add_field(field.name, field.values, field.type)
    620              else:
    621                  result.add_field(field.name, field.values)
    622          return result
    623
    624 -    def copy(self):
    625          """Get a deep copy of `self`.
    626
    627          :return: a deep copy of `self`.
    628          :returntype: `Form`"""
    629          return copy.deepcopy(self)
    630
    631 -    def complete_xml_element(self, xmlnode, doc):
    632          """Complete the XML node with `self` content.
    633
    634          :Parameters:
    635              - `xmlnode`: XML node with the element being built. It has already
    636                right name and namespace, but no attributes or content.
    637              - `doc`: document to which the element belongs.
    638          :Types:
    639              - `xmlnode`: `libxml2.xmlNode`
    640              - `doc`: `libxml2.xmlDoc`"""
    641          if self.type not in self.allowed_types:
    642              raise ValueError, "Form type %r not allowed." % (self.type,)
    643          xmlnode.setProp("type", self.type)
    644          if self.type == "cancel":
    645              return
    646          ns = xmlnode.ns()
    647          if self.title is not None:
    648              xmlnode.newTextChild(ns, "title", self.title)
    649          if self.instructions is not None:
    650              xmlnode.newTextChild(ns, "instructions", self.instructions)
    651          for field in self.fields:
    652              field.as_xml(xmlnode, doc)
    653          if self.type != "result":
    654              return
    655          if self.reported_fields:
    656              reported = xmlnode.newChild(ns, "reported", None)
    657              for field in self.reported_fields:
    658                  field.as_xml(reported, doc)
    659          for item in self.items:
    660              item.as_xml(xmlnode, doc)
    661
    662 -    def __from_xml(self, xmlnode):
    663          """Initialize a `Form` object from an XML element.
    664
    665          :Parameters:
    666              - `xmlnode`: the XML element.
    667          :Types:
    668              - `xmlnode`: `libxml2.xmlNode`
    669          """
    670          self.fields = []
    671          self.reported_fields = []
    672          self.items = []
    673          self.title = None
    674          self.instructions = None
    675          if (xmlnode.type != "element" or xmlnode.name != "x"
    676                  or xmlnode.ns().content != DATAFORM_NS):
    677              raise ValueError, "Not a form: " + xmlnode.serialize()
    678          self.type = xmlnode.prop("type")
    679          if not self.type in self.allowed_types:
    680              raise BadRequestProtocolError, "Bad form type: %r" % (self.type,)
    681          child = xmlnode.children
    682          while child:
    683              if child.type != "element" or child.ns().content != DATAFORM_NS:
    684                  pass
    685              elif child.name == "title":
    686                  self.title = from_utf8(child.getContent())
    687              elif child.name == "instructions":
    688                  self.instructions = from_utf8(child.getContent())
    689              elif child.name == "field":
    690                  self.fields.append(Field._new_from_xml(child))
    691              elif child.name == "item":
    692                  self.items.append(Item._new_from_xml(child))
    693              elif child.name == "reported":
    694                  self.__get_reported(child)
    695              child = child.next
    696
    697 -    def __get_reported(self, xmlnode):
    698          """Parse the <reported/> element of the form.
    699
    700          :Parameters:
    701              - `xmlnode`: the element to parse.
    702          :Types:
    703              - `xmlnode`: `libxml2.xmlNode`"""
    704          child = xmlnode.children
    705          while child:
    706              if child.type != "element" or child.ns().content != DATAFORM_NS:
    707                  pass
    708              elif child.name == "field":
    709                  self.reported_fields.append(Field._new_from_xml(child))
    710              child = child.next
    711  # vi: sts=4 et sw=4
    712