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