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

# Source Code for Module lxml.html.formfill

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