[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