[Lxml-checkins] r44258 - in lxml/branch/html/src/lxml/html: . tests

ianb at codespeak.net ianb at codespeak.net
Fri Jun 15 00:53:28 CEST 2007


Author: ianb
Date: Fri Jun 15 00:53:28 2007
New Revision: 44258

Modified:
   lxml/branch/html/src/lxml/html/formfill.py
   lxml/branch/html/src/lxml/html/tests/test_formfill.txt
Log:
Added error-filling function

Modified: lxml/branch/html/src/lxml/html/formfill.py
==============================================================================
--- lxml/branch/html/src/lxml/html/formfill.py	(original)
+++ lxml/branch/html/src/lxml/html/formfill.py	Fri Jun 15 00:53:28 2007
@@ -1,5 +1,6 @@
-from lxml.etree import XPath
-from lxml.html import HTML, tostring
+from lxml.etree import XPath, ElementBase
+from lxml.html import parse, tostring
+from lxml.html import defs
 
 __all__ = ['FormNotFound', 'fill_form']
 
@@ -10,6 +11,8 @@
 
 _form_name_xpath = XPath('descendant-or-self::form[name=$name]')
 _input_xpath = XPath('descendant-or-self::input | descendant-or-self::select | descendant-or-self::textarea')
+_label_for_xpath = XPath('//label[@for=$for_id]')
+_name_xpath = XPath('descendant-or-self::*[@name=$name]')
 
 def fill_form(
     el,
@@ -22,7 +25,7 @@
 
 def fill_form_html(html, values, form_id=None, form_index=None):
     if isinstance(html, basestring):
-        doc = HTML(html)
+        doc = parse(html)
         return_string = True
     else:
         doc = copy.deepcopy(html)
@@ -164,3 +167,130 @@
             yield form.get('name')
         else:
             yield '(unnamed form %s)' % index
+
+############################################################
+## Error filling
+############################################################
+
+class DefaultErrorCreator(object):
+    insert_before = True
+    block_inside = True
+    error_container_tag = 'div'
+    error_message_class = 'error-message'
+    error_block_class = 'error-block'
+    default_message = "Invalid"
+
+    def __init__(self, **kw):
+        for name, value in kw.items():
+            if not hasattr(self, name):
+                raise TypeError(
+                    "Unexpected keyword argument: %s" % name)
+            setattr(self, name, value)
+
+    def __call__(self, el, is_block, message):
+        error_el = el.makeelement(self.error_container_tag)
+        if self.error_message_class:
+            error_el.set('class', self.error_message_class)
+        if is_block and self.error_block_class:
+            error_el.set('class', error_el.get('class', '')+' '+self.error_block_class)
+        if message is None or message == '':
+            message = self.default_message
+        if isinstance(message, ElementBase):
+            error_el.append(message)
+        else:
+            assert isinstance(message, basestring), (
+                "Bad message; should be a string or element: %r" % message)
+            error_el.text = message or self.default_message
+        if is_block and self.block_inside:
+            if self.insert_before:
+                error_el.tail = el.text
+                el.text = None
+                el.insert(0, error_el)
+            else:
+                el.append(error_el)
+        else:
+            parent = el.getparent()
+            pos = parent.index(el)
+            if self.insert_before:
+                parent.insert(pos, error_el)
+            else:
+                error_el.tail = el.tail
+                el.tail = None
+                parent.insert(pos+1, error_el)
+
+default_error_creator = DefaultErrorCreator()
+    
+
+def insert_errors(
+    el,
+    errors,
+    form_id=None,
+    form_index=None,
+    error_class="error",
+    error_creator=default_error_creator,
+    ):
+    el = _find_form(el, form_id=form_id, form_index=form_index)
+    for name, error in errors.iteritems():
+        if error is None:
+            continue
+        for error_el, message in _find_elements_for_name(el, name, error):
+            assert isinstance(message, (basestring, type(None), ElementBase)), (
+                "Bad message: %r" % message)
+            _insert_error(error_el, message, error_class, error_creator)
+
+def insert_errors_html(html, values, **kw):
+    if isinstance(html, basestring):
+        doc = parse(html)
+        return_string = True
+    else:
+        doc = copy.deepcopy(html)
+        return_string = False
+    insert_errors(doc, values, **kw)
+    if return_string:
+        return tostring(doc)
+    else:
+        return doc
+
+def _insert_error(el, error, error_class, error_creator):
+    if el.tag in defs.empty_tags or el.tag == 'textarea':
+        is_block = False
+    else:
+        is_block = True
+    if el.tag != 'form' and error_class:
+        _add_class(el, error_class)
+    if el.get('id'):
+        labels = _label_for_xpath(el, for_id=el.get('id'))
+        if labels:
+            for label in labels:
+                _add_class(label, error_class)
+    error_creator(el, is_block, error)
+
+def _add_class(el, class_name):
+    if el.get('class'):
+        el.set('class', el.get('class')+' '+class_name)
+    else:
+        el.set('class', class_name)
+
+def _find_elements_for_name(form, name, error):
+    if name is None:
+        # An error for the entire form
+        yield form, error
+        return
+    if name.startswith('#'):
+        # By id
+        el = form.get_element_by_id(name[1:])
+        if el is not None:
+            yield el, error
+        return
+    els = _name_xpath(form, name=name)
+    if not els:
+        # FIXME: should this raise an exception?
+        return
+    if not isinstance(error, (list, tuple)):
+        yield els[0], error
+        return
+    # FIXME: if error is longer than els, should it raise an error?
+    for el, err in zip(els, error):
+        if err is None:
+            continue
+        yield el, err

Modified: lxml/branch/html/src/lxml/html/tests/test_formfill.txt
==============================================================================
--- lxml/branch/html/src/lxml/html/tests/test_formfill.txt	(original)
+++ lxml/branch/html/src/lxml/html/tests/test_formfill.txt	Fri Jun 15 00:53:28 2007
@@ -52,3 +52,49 @@
 
 FIXME: I need to test more of this.  But I'm lazy and want to use the
 coverage report for some of this.
+
+
+This module also allows you to add error messages to the form.  The errors
+add an "error" class to the input fields, and any labels if the field
+has a label.  It also inserts an error message into the form, using a
+function you can provide (or the default function).
+
+Example::
+
+    >>> from lxml.html.formfill import insert_errors_html
+    >>> print insert_errors_html('''
+    ... <form>
+    ...   <fieldset id="fieldset">
+    ...     <input name="v1"><br>
+    ...     <label for="v2">label</label>
+    ...     <input name="v2" id="v2"><br>
+    ...   </fieldset>
+    ...   <input name="v3" class="foo">
+    ...   <input name="v3" class="foo">
+    ...   <input name="v4">
+    ...   <input name="v4">
+    ... </form>''', {
+    ...   'v1': "err1",
+    ...   'v2': "err2",
+    ...   'v3': [None, "err3-2"],
+    ...   'v4': "err4",
+    ...   None: 'general error',
+    ...   '#fieldset': 'area error',
+    ... })
+    <form>
+      <div class="error-message error-block">general error</div>
+      <fieldset id="fieldset" class="error">
+        <div class="error-message error-block">area error</div>
+        <div class="error-message">err1</div>
+        <input name="v1" class="error"><br>
+        <label for="v2" class="error">label</label>
+        <div class="error-message">err2</div>
+        <input name="v2" id="v2" class="error"><br>
+      </fieldset>
+      <input name="v3" class="foo">
+      <div class="error-message">err3-2</div>
+      <input name="v3" class="foo error">
+      <div class="error-message">err4</div>
+      <input name="v4" class="error">
+      <input name="v4">
+    </form>


More information about the lxml-checkins mailing list