Package lxml :: Package html :: Module formfill |
[frames] | no frames]

# Source Code for Module lxml.html.formfill

      1  from lxml.etree import XPath, ElementBase
      2  from lxml.html import fromstring, tostring
      3  from lxml.html import defs
      4
      5  __all__ = ['FormNotFound', 'fill_form', 'fill_form_html',
      6             'insert_errors', 'insert_errors_html',
      7             'DefaultErrorCreator']
      8
      9 -class FormNotFound(LookupError):
     10      """
     11      Raised when no form can be found
     12      """
     13
     14  _form_name_xpath = XPath('descendant-or-self::form[name=$name]')
     15  _input_xpath = XPath('descendant-or-self::input | descendant-or-self::select | descendant-or-self::textarea')
     16  _label_for_xpath = XPath('//label[@for=$for_id]')
     17  _name_xpath = XPath('descendant-or-self::*[@name=$name]')
     18
     19 -def fill_form(
     20      el,
     21      values,
     22      form_id=None,
     23      form_index=None,
     24      ):
     25      el = _find_form(el, form_id=form_id, form_index=form_index)
     26      _fill_form(el, values)
     27
     28 -def fill_form_html(html, values, form_id=None, form_index=None):
     29      if isinstance(html, basestring):
     30          doc = fromstring(html)
     31          return_string = True
     32      else:
     33          doc = copy.deepcopy(html)
     34          return_string = False
     35      fill_form(doc, values, form_id=form_id, form_index=form_index)
     36      if return_string:
     37          return tostring(doc)
     38      else:
     39          return doc
     40
     41 -def _fill_form(el, values):
     42      counts = {}
     43      if hasattr(values, 'mixed'):
     44          # For Paste request parameters
     45          values = values.mixed()
     46      inputs = _input_xpath(el)
     47      for input in inputs:
     48          name = input.get('name')
     49          if not name:
     50              continue
     51          if _takes_multiple(input):
     52              value = values.get(name, [])
     53              if not isinstance(value, (list, tuple)):
     54                  value = [value]
     55              _fill_multiple(input, value)
     56          elif name not in values:
     57              continue
     58          else:
     59              index = counts.get(name, 0)
     60              counts[name] = index + 1
     61              value = values[name]
     62              if isinstance(value, (list, tuple)):
     63                  try:
     64                      value = value[index]
     65                  except IndexError:
     66                      continue
     67              elif index > 0:
     68                  continue
     69              _fill_single(input, value)
     70
     71 -def _takes_multiple(input):
     72      if input.tag == 'select' and input.get('multiple'):
     73          # FIXME: multiple="0"?
     74          return True
     75      type = input.get('type', '').lower()
     76      if type in ('radio', 'checkbox'):
     77          return True
     78      return False
     79
     80 -def _fill_multiple(input, value):
     81      type = input.get('type', '').lower()
     82      if type == 'checkbox':
     83          v = input.get('value')
     84          if v is None:
     85              if not value:
     86                  result = False
     87              else:
     88                  result = value[0]
     89                  if isinstance(value, basestring):
     90                      # The only valid "on" value for an unnamed checkbox is 'on'
     91                      result = result == 'on'
     92              _check(input, result)
     93          else:
     94              _check(input, v in value)
     95      elif type == 'radio':
     96          v = input.get('value')
     97          _check(input, v in value)
     98      else:
     99          assert input.tag == 'select'
    100          for option in input.findall('option'):
    101              v = option.get('value')
    102              if v is None:
    103                  # This seems to be the default, at least on IE
    104                  # FIXME: but I'm not sure
    105                  v = option.text_content()
    106              _select(option, v in value)
    107
    108 -def _check(el, check):
    109      if check:
    110          el.set('checked', '')
    111      else:
    112          if 'checked' in el.attrib:
    113              del el.attrib['checked']
    114
    115 -def _select(el, select):
    116      if select:
    117          el.set('selected', '')
    118      else:
    119          if 'selected' in el.attrib:
    120              del el.attrib['selected']
    121
    122 -def _fill_single(input, value):
    123      if input.tag == 'textarea':
    124          input.clear()
    125          input.text = value
    126      else:
    127          input.set('value', value)
    128
    129 -def _find_form(el, form_id=None, form_index=None):
    130      if form_id is None and form_index is None:
    131          forms = el.getiterator('form')
    132          for form in forms:
    133              return form
    134          raise FormNotFound(
    135              "No forms in page")
    136      if form_id is not None:
    137          form = el.get_element_by_id(form_id)
    138          if form is not None:
    139              return form
    140          forms = _form_name_xpath(el, name=form_id)
    141          if forms:
    142              return forms[0]
    143          else:
    144              raise FormNotFound(
    145                  "No form with the name or id of %r (forms: %s)"
    146                  % (id, ', '.join(_find_form_ids(el))))
    147      if form_index is not None:
    148          forms = el.getiterator('form')
    149          try:
    150              return forms[form_index]
    151          except IndexError:
    152              raise FormNotFound(
    153                  "There is no form with the index %r (%i forms found)"
    154                  % (form_index, len(forms)))
    155
    156 -def _find_form_ids(el):
    157      forms = el.getiterator('form')
    158      if not forms:
    159          yield '(no forms)'
    160          return
    161      for index, form in enumerate(forms):
    162          if form.get('id'):
    163              if form.get('name'):
    164                  yield '%s or %s' % (form.get('id'),
    165                                       form.get('name'))
    166              else:
    167                  yield form.get('id')
    168          elif form.get('name'):
    169              yield form.get('name')
    170          else:
    171              yield '(unnamed form %s)' % index
    172
    173  ############################################################
    174  ## Error filling
    175  ############################################################
    176
    177 -class DefaultErrorCreator(object):
    178      insert_before = True
    179      block_inside = True
    180      error_container_tag = 'div'
    181      error_message_class = 'error-message'
    182      error_block_class = 'error-block'
    183      default_message = "Invalid"
    184
    185 -    def __init__(self, **kw):
    186          for name, value in kw.items():
    187              if not hasattr(self, name):
    188                  raise TypeError(
    189                      "Unexpected keyword argument: %s" % name)
    190              setattr(self, name, value)
    191
    192 -    def __call__(self, el, is_block, message):
    193          error_el = el.makeelement(self.error_container_tag)
    194          if self.error_message_class:
    195              error_el.set('class', self.error_message_class)
    196          if is_block and self.error_block_class:
    197              error_el.set('class', error_el.get('class', '')+' '+self.error_block_class)
    198          if message is None or message == '':
    199              message = self.default_message
    200          if isinstance(message, ElementBase):
    201              error_el.append(message)
    202          else:
    203              assert isinstance(message, basestring), (
    204                  "Bad message; should be a string or element: %r" % message)
    205              error_el.text = message or self.default_message
    206          if is_block and self.block_inside:
    207              if self.insert_before:
    208                  error_el.tail = el.text
    209                  el.text = None
    210                  el.insert(0, error_el)
    211              else:
    212                  el.append(error_el)
    213          else:
    214              parent = el.getparent()
    215              pos = parent.index(el)
    216              if self.insert_before:
    217                  parent.insert(pos, error_el)
    218              else:
    219                  error_el.tail = el.tail
    220                  el.tail = None
    221                  parent.insert(pos+1, error_el)
    222
    223  default_error_creator = DefaultErrorCreator()
    224
    225
    226 -def insert_errors(
    227      el,
    228      errors,
    229      form_id=None,
    230      form_index=None,
    231      error_class="error",
    232      error_creator=default_error_creator,
    233      ):
    234      el = _find_form(el, form_id=form_id, form_index=form_index)
    235      for name, error in errors.iteritems():
    236          if error is None:
    237              continue
    238          for error_el, message in _find_elements_for_name(el, name, error):
    239              assert isinstance(message, (basestring, type(None), ElementBase)), (
    240                  "Bad message: %r" % message)
    241              _insert_error(error_el, message, error_class, error_creator)
    242
    243 -def insert_errors_html(html, values, **kw):
    244      if isinstance(html, basestring):
    245          doc = fromstring(html)
    246          return_string = True
    247      else:
    248          doc = copy.deepcopy(html)
    249          return_string = False
    250      insert_errors(doc, values, **kw)
    251      if return_string:
    252          return tostring(doc)
    253      else:
    254          return doc
    255
    256 -def _insert_error(el, error, error_class, error_creator):
    257      if el.tag in defs.empty_tags or el.tag == 'textarea':
    258          is_block = False
    259      else:
    260          is_block = True
    261      if el.tag != 'form' and error_class:
    262          _add_class(el, error_class)
    263      if el.get('id'):
    264          labels = _label_for_xpath(el, for_id=el.get('id'))
    265          if labels:
    266              for label in labels:
    267                  _add_class(label, error_class)
    268      error_creator(el, is_block, error)
    269
    270 -def _add_class(el, class_name):
    271      if el.get('class'):
    272          el.set('class', el.get('class')+' '+class_name)
    273      else:
    274          el.set('class', class_name)
    275
    276 -def _find_elements_for_name(form, name, error):
    277      if name is None:
    278          # An error for the entire form
    279          yield form, error
    280          return
    281      if name.startswith('#'):
    282          # By id
    283          el = form.get_element_by_id(name[1:])
    284          if el is not None:
    285              yield el, error
    286          return
    287      els = _name_xpath(form, name=name)
    288      if not els:
    289          # FIXME: should this raise an exception?
    290          return
    291      if not isinstance(error, (list, tuple)):
    292          yield els[0], error
    293          return
    294      # FIXME: if error is longer than els, should it raise an error?
    295      for el, err in zip(els, error):
    296          if err is None:
    297              continue
    298          yield el, err
    299