[Zope3-checkins] SVN: Zope3/trunk/src/zope/testing/ add a helper module that can parse an HTML form and make it available for

Fred L. Drake, Jr. fdrake at gmail.com
Wed Jan 5 10:42:10 EST 2005


Log message for revision 28729:
  add a helper module that can parse an HTML form and make it available for
  examination
  

Changed:
  A   Zope3/trunk/src/zope/testing/formparser.py
  A   Zope3/trunk/src/zope/testing/formparser.txt
  U   Zope3/trunk/src/zope/testing/tests.py

-=-
Added: Zope3/trunk/src/zope/testing/formparser.py
===================================================================
--- Zope3/trunk/src/zope/testing/formparser.py	2005-01-05 14:00:37 UTC (rev 28728)
+++ Zope3/trunk/src/zope/testing/formparser.py	2005-01-05 15:42:08 UTC (rev 28729)
@@ -0,0 +1,212 @@
+"""HTML parser that extracts form information.
+
+This is intended to support functional tests that need to extract
+information from HTML forms returned by the publisher.
+
+See *formparser.txt* for documentation.
+
+"""
+__docformat__ = "reStructuredText"
+
+import HTMLParser
+import urlparse
+
+
+def parse(data, base=None):
+    """Return a form collection parsed from `data`.
+
+    `base` should be the URL from which `data` was retrieved.
+
+    """
+    parser = FormParser(data, base)
+    return parser.parse()
+
+
+class FormParser(object):
+
+    def __init__(self, data, base=None):
+        self.data = data
+        self.base = base
+        self._parser = HTMLParser.HTMLParser()
+        self._parser.handle_data = self._handle_data
+        self._parser.handle_endtag = self._handle_endtag
+        self._parser.handle_starttag = self._handle_starttag
+        self._parser.handle_startendtag = self._handle_starttag
+        self._buffer = []
+        self.current = None
+        self.forms = FormCollection()
+
+    def parse(self):
+        """Parse the document, returning the collection of forms."""
+        self._parser.feed(self.data)
+        self._parser.close()
+        return self.forms
+
+    # HTMLParser handlers
+
+    def _handle_data(self, data):
+        self._buffer.append(data)
+
+    def _handle_endtag(self, tag):
+        if tag == "textarea":
+            self.textarea.value = "".join(self._buffer)
+            self.textarea = None
+        elif tag == "select":
+            self.select = None
+        elif tag == "option":
+            option = self.select.options[-1]
+            label = "".join(self._buffer)
+            if not option.label:
+                option.label = label
+            if not option.value:
+                option.value = label
+            if option.selected:
+                if self.select.multiple:
+                    self.select.value.append(option.value)
+                else:
+                    self.select.value = option.value
+
+    def _handle_starttag(self, tag, attrs):
+        del self._buffer[:]
+        d = {}
+        for name, value in attrs:
+            d[name] = value
+        name = d.get("name")
+        id = d.get("id") or d.get("xml:id")
+        if tag == "form":
+            method = kwattr(d, "method", "get")
+            action = d.get("action", "").strip() or None
+            if self.base and action:
+                action = urlparse.urljoin(self.base, action)
+            enctype = kwattr(d, "enctype", "application/x-www-form-urlencoded")
+            self.current = Form(name, id, method, action, enctype)
+            self.forms.append(self.current)
+        elif tag == "input":
+            type = kwattr(d, "type", "text")
+            checked = "checked" in d
+            disabled = "disabled" in d
+            readonly = "readonly" in d
+            src = d.get("src", "").strip() or None
+            if self.base and src:
+                src = urlparse.urljoin(self.base, src)
+            value = d.get("value")
+            size = intattr(d, "size")
+            maxlength = intattr(d, "maxlength")
+            self.current[name] = Input(name, id, type, value,
+                                       checked, disabled, readonly,
+                                       src, size, maxlength)
+        elif tag == "button":
+            pass
+        elif tag == "textarea":
+            disabled = "disabled" in d
+            readonly = "readonly" in d
+            self.textarea = Input(name, id, "textarea", None,
+                                  None, disabled, readonly,
+                                  None, None, None)
+            self.textarea.rows = intattr(d, "rows")
+            self.textarea.cols = intattr(d, "cols")
+            self.current[name] = self.textarea
+            # The value will be set when the </textarea> is seen.
+        elif tag == "base":
+            href = d.get("href", "").strip()
+            if href and self.base:
+                href = urlparse.urljoin(self.base, href)
+            self.base = href
+        elif tag == "select":
+            disabled = "disabled" in d
+            multiple = "multiple" in d
+            size = intattr(d, "size")
+            self.select = Select(name, id, disabled, multiple, size)
+            self.current[name] = self.select
+        elif tag == "option":
+            disabled = "disabled" in d
+            selected = "selected" in d
+            value = d.get("value")
+            label = d.get("label")
+            option = Option(id, value, selected, label, disabled)
+            self.select.options.append(option)
+
+
+def kwattr(d, name, default=None):
+    """Return attribute, converted to lowercase."""
+    v = d.get(name, default)
+    if v != default and v is not None:
+        v = v.strip().lower()
+        v = v or default
+    return v
+
+
+def intattr(d, name):
+    """Return attribute as an integer, or None."""
+    if name in d:
+        v = d[name].strip()
+        return int(v)
+    else:
+        return None
+
+
+class FormCollection(list):
+    """Collection of all forms from a page."""
+
+    def __getattr__(self, name):
+        for form in self:
+            if form.name == name:
+                return form
+        raise AttributeError, name
+
+
+class Form(dict):
+    """A specific form within a page."""
+
+    def __init__(self, name, id, method, action, enctype):
+        super(Form, self).__init__()
+        self.name = name
+        self.id = id
+        self.method = method
+        self.action = action
+        self.enctype = enctype
+
+
+class Input(object):
+    """Input element."""
+
+    rows = None
+    cols = None
+
+    def __init__(self, name, id, type, value, checked, disabled, readonly,
+                 src, size, maxlength):
+        super(Input, self).__init__()
+        self.name = name
+        self.id = id
+        self.type = type
+        self.value = value
+        self.checked = checked
+        self.disabled = disabled
+        self.readonly = readonly
+        self.src = src
+        self.size = size
+        self.maxlength = maxlength
+
+
+class Select(Input):
+    """Select element."""
+
+    def __init__(self, name, id, disabled, multiple, size):
+        super(Select, self).__init__(name, id, "select", None, None,
+                                     disabled, None, None, size, None)
+        self.options = []
+        self.multiple = multiple
+        if multiple:
+            self.value = []
+
+
+class Option(object):
+    """Individual value representation for a select element."""
+
+    def __init__(self, id, value, selected, label, disabled):
+        super(Option, self).__init__()
+        self.id = id
+        self.value = value
+        self.selected = selected
+        self.label = label
+        self.disabled = disabled


Property changes on: Zope3/trunk/src/zope/testing/formparser.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Added: Zope3/trunk/src/zope/testing/formparser.txt
===================================================================
--- Zope3/trunk/src/zope/testing/formparser.txt	2005-01-05 14:00:37 UTC (rev 28728)
+++ Zope3/trunk/src/zope/testing/formparser.txt	2005-01-05 15:42:08 UTC (rev 28729)
@@ -0,0 +1,130 @@
+==================
+Parsing HTML Forms
+==================
+
+Sometimes in functional tests, information from a generated form must
+be extracted in order to re-submit it as part of a subsequent request.
+The `zope.testing.formparser` module can be used for this purpose.
+
+The scanner is implemented using the `FormParser` class.  The
+constructor arguments are the page data containing the form and
+(optionally) the URL from which the page was retrieved::
+
+  >>> import zope.testing.formparser
+
+  >>> page_text = '''\
+  ... <html><body>
+  ...   <form name="form1" action="/cgi-bin/foobar.py" method="POST">
+  ...     <input type="hidden" name="f1" value="today" />
+  ...     <input type="submit" name="do-it-now" value="Go for it!" />
+  ...     <input type="IMAGE" name="not-really" value="Don't."
+  ...            src="dont.png" />
+  ...     <select name="pick-two" size="3" multiple>
+  ...       <option value="one" selected>First</option>
+  ...       <option value="two" label="Second">Another</option>
+  ...       <optgroup>
+  ...         <option value="three">Third</option>
+  ...         <option selected="selected">Fourth</option>
+  ...       </optgroup>
+  ...     </select>
+  ...   </form>
+  ...
+  ...   Just for fun, a second form, after specifying a base:
+  ...   <base href="http://www.example.com/base/" />
+  ...   <form action = 'sproing/sprung.html' enctype="multipart/form">
+  ...     <textarea name="sometext" rows="5">Some text.</textarea>
+  ...     <input type="Image" name="action" value="Do something."
+  ...            src="else.png" />
+  ...   </form>
+  ... </body></html>
+  ... '''
+
+  >>> parser = zope.testing.formparser.FormParser(page_text)
+  >>> forms = parser.parse()
+
+  >>> len(forms)
+  2
+  >>> forms.form1 is forms[0]
+  True
+  >>> forms.form1 is forms[1]
+  False
+
+More often, the `parse()` convenience function is all that's needed::
+
+  >>> forms = zope.testing.formparser.parse(
+  ...     page_text, "http://cgi.example.com/somewhere/form.html")
+
+  >>> len(forms)
+  2
+  >>> forms.form1 is forms[0]
+  True
+  >>> forms.form1 is forms[1]
+  False
+
+Once we have the form we're interested in, we can check form
+attributes and individual field values::
+
+  >>> form = forms.form1
+  >>> form.enctype
+  'application/x-www-form-urlencoded'
+  >>> form.method
+  'post'
+
+  >>> keys = form.keys()
+  >>> keys.sort()
+  >>> keys
+  ['do-it-now', 'f1', 'not-really', 'pick-two']
+
+  >>> not_really = form["not-really"]
+  >>> not_really.type
+  'image'
+  >>> not_really.value
+  "Don't."
+  >>> not_really.readonly
+  False
+  >>> not_really.disabled
+  False
+
+Note that relative URLs are converted to absolute URLs based on the
+``<base>`` element (if present) or using the base passed in to the
+constructor.
+
+  >>> form.action
+  'http://cgi.example.com/cgi-bin/foobar.py'
+  >>> not_really.src
+  'http://cgi.example.com/somewhere/dont.png'
+
+  >>> forms[1].action
+  'http://www.example.com/base/sproing/sprung.html'
+  >>> forms[1]["action"].src
+  'http://www.example.com/base/else.png'
+
+The ``<textarea>`` element provides some additional attributes::
+
+  >>> ta = forms[1]["sometext"]
+  >>> print ta.rows
+  5
+  >>> print ta.cols
+  None
+  >>> ta.value
+  'Some text.'
+
+The ``<select>`` element provides access to the options as well::
+
+  >>> select = form["pick-two"]
+  >>> select.multiple
+  True
+  >>> select.size
+  3
+  >>> select.type
+  'select'
+  >>> select.value
+  ['one', 'Fourth']
+
+  >>> options = select.options
+  >>> len(options)
+  4
+  >>> [opt.label for opt in options]
+  ['First', 'Second', 'Third', 'Fourth']
+  >>> [opt.value for opt in options]
+  ['one', 'two', 'three', 'Fourth']


Property changes on: Zope3/trunk/src/zope/testing/formparser.txt
___________________________________________________________________
Name: svn:mime-type
   + text/plain
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/testing/tests.py
===================================================================
--- Zope3/trunk/src/zope/testing/tests.py	2005-01-05 14:00:37 UTC (rev 28728)
+++ Zope3/trunk/src/zope/testing/tests.py	2005-01-05 15:42:08 UTC (rev 28729)
@@ -16,11 +16,12 @@
 $Id$
 """
 import unittest
-from zope.testing.doctestunit import DocTestSuite
+from zope.testing.doctestunit import DocTestSuite, DocFileSuite
 
 
 def test_suite():
     return unittest.TestSuite((
+        DocFileSuite('formparser.txt'),
         DocTestSuite('zope.testing.loggingsupport'),
         ))
 



More information about the Zope3-Checkins mailing list