[Checkins] SVN: ClientForm/tags/0.2.9/ Vendor import ClientForm 0.2.9.

Tres Seaver tseaver at palladion.com
Mon Dec 1 00:16:10 EST 2008


Log message for revision 93460:
  Vendor import ClientForm 0.2.9.

Changed:
  A   ClientForm/tags/0.2.9/
  A   ClientForm/tags/0.2.9/COPYING.txt
  A   ClientForm/tags/0.2.9/COPYRIGHT.txt
  A   ClientForm/tags/0.2.9/ChangeLog.txt
  A   ClientForm/tags/0.2.9/ClientForm.egg-info/
  A   ClientForm/tags/0.2.9/ClientForm.egg-info/PKG-INFO
  A   ClientForm/tags/0.2.9/ClientForm.egg-info/SOURCES.txt
  A   ClientForm/tags/0.2.9/ClientForm.egg-info/dependency_links.txt
  A   ClientForm/tags/0.2.9/ClientForm.egg-info/top_level.txt
  A   ClientForm/tags/0.2.9/ClientForm.egg-info/zip-safe
  A   ClientForm/tags/0.2.9/ClientForm.py
  A   ClientForm/tags/0.2.9/GeneralFAQ.html
  A   ClientForm/tags/0.2.9/INSTALL.txt
  A   ClientForm/tags/0.2.9/MANIFEST.in
  A   ClientForm/tags/0.2.9/PKG-INFO
  A   ClientForm/tags/0.2.9/README.html
  A   ClientForm/tags/0.2.9/README.html.in
  A   ClientForm/tags/0.2.9/README.txt
  A   ClientForm/tags/0.2.9/examples/
  A   ClientForm/tags/0.2.9/examples/data.dat
  A   ClientForm/tags/0.2.9/examples/data.txt
  A   ClientForm/tags/0.2.9/examples/echo.cgi
  A   ClientForm/tags/0.2.9/examples/example.html
  A   ClientForm/tags/0.2.9/examples/example.py
  A   ClientForm/tags/0.2.9/examples/simple.py
  A   ClientForm/tags/0.2.9/ez_setup.py
  A   ClientForm/tags/0.2.9/setup.cfg
  A   ClientForm/tags/0.2.9/setup.py
  A   ClientForm/tags/0.2.9/test/
  A   ClientForm/tags/0.2.9/test/test_clientform.py
  A   ClientForm/tags/0.2.9/test.py
  A   ClientForm/tags/0.2.9/testdata/
  A   ClientForm/tags/0.2.9/testdata/Auth.html
  A   ClientForm/tags/0.2.9/testdata/FullSearch.html
  A   ClientForm/tags/0.2.9/testdata/GeneralSearch.html
  A   ClientForm/tags/0.2.9/testdata/MarkedRecords.html
  A   ClientForm/tags/0.2.9/testdata/MarkedResults.html
  A   ClientForm/tags/0.2.9/testdata/Results.html
  A   ClientForm/tags/0.2.9/testdata/SearchType.html

-=-
Added: ClientForm/tags/0.2.9/COPYING.txt
===================================================================
--- ClientForm/tags/0.2.9/COPYING.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/COPYING.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,61 @@
+See the file COPYRIGHT.txt for copyright information.
+
+This software is dual-licensed: you may pick either of the licenses
+below.
+
+
+BSD-style license
+==================
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+Neither the name of the contributors nor the names of their employers
+may be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+
+ZPL 2.1
+==================
+
+Zope Public License (ZPL) Version 2.1
+
+A copyright notice accompanies this license document that identifies the copyright holders.
+
+This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer.
+   2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.
+   3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders.
+   4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders.
+   5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
+
+Disclaimer
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Added: ClientForm/tags/0.2.9/COPYRIGHT.txt
===================================================================
--- ClientForm/tags/0.2.9/COPYRIGHT.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/COPYRIGHT.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,4 @@
+Copyright 2002-2005 John J. Lee <jjl at pobox.com>
+Copyright 2005 Gary Poster
+Copyright 2005 Zope Corporation
+Copyright 1998-2000 Gisle Aas.

Added: ClientForm/tags/0.2.9/ChangeLog.txt
===================================================================
--- ClientForm/tags/0.2.9/ChangeLog.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/ChangeLog.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,736 @@
+This isn't really in proper GNU ChangeLog format, it just happens to
+look that way.
+
+2008-07-19 John J Lee <jjl at pobox.com>
+	* 0.2.9 release:
+	* Fix "import *" in case where BeautifulSoup 2 is not
+	  available (thanks Marius Gedminas for patch)
+
+2008-06-29 John J Lee <jjl at pobox.com>
+	* 0.2.8 release:
+	* Don't eagerly close label when the element for that label is
+	  seen, since there may be more label text to follow (thanks
+	  Marius Gedminas for patch)
+
+2007-05-24 John J Lee <jjl at pobox.com>
+	* 0.2.7 release:
+	* Fix ParseError affecting global SELECT and TEXTAREA controls
+	* Fix entity ref double-decoding bug (thanks David Moews and
+	  Bayle Shanks).
+	* Don't merge multiple SELECT controls with the same name.
+	* Fix bad use of module warnings.
+	* Add an __all__ attribute.
+	* Fix source file line endings in SVN (svn:eol-style native).
+
+2007-01-07 John J Lee <jjl at pobox.com>
+	* 0.2.6 release:
+	* Don't allow underlying parser errors (e.g. SGMLParseError)
+	  through -- always raise ClientForm.ParseError .  To preserve.
+	  However, any code that distinguishes between
+	  ClientForm.ParseError and these other exceptions will break.
+	* Allow controls to appear outside of forms (the HTML spec allows
+	  this).  This involved adding new functions ParseFileEx and
+	  ParseResponseEx, which return a list that's always one longer
+	  than the return value of their counterparts ParseFile and
+	  ParseResponse.  The new first element in the list of forms is an
+	  HTMLForm representing the collection of all forms that lie
+	  outside of any FORM element.
+
+2006-10-24 John J Lee <jjl at pobox.com>
+	* 0.2.5 release:
+	* Fix fragment bug introduced in 0.2.4 (thanks Dave Marble).  This
+	  only caused a bug when used with mechanize: it does not affect
+	  users using ClientForm without mechanize.
+
+2006-10-14 John J Lee <jjl at pobox.com>
+	* 0.2.4 release:
+	* Support for mechanize 0.1.4b.
+
+2006-10-12 John J Lee <jjl at pobox.com>
+	* 0.2.3 release:
+	* Fix entity reference / character reference handling for
+	  Python 2.5 .
+	* Nameless list controls are now never successful.
+	* List controls used to get inappropriately .merge_control()ed
+	  with other controls, or parsing would raise AmbiguityError.
+	  That's fixed now.
+	* Handle line endings in element content the same way browsers do
+	  (strip exactly one leading linebreaks, if any leading linebreaks
+	  are present) (patch from Benji York).
+	* Convert TEXTAREA content to DOS line ending convention, again
+	  following the major browsers.
+	* Allow mechanize to supply URL join / parse / unparse functions,
+	  to allow mechanize to follow RFC 3986, thus fixing some URL
+	  processing bugs.  ClientForm should do the same; probably I
+	  should merge the two projects after final mechanize release.
+	* Doc fixes.
+
+2006-03-22 John J Lee <jjl at pobox.com>
+	* 0.2.2 release:
+	* Stop trying to record precise dates in changelog, since that's
+	  silly ;-)
+	* Fixes to setup.py &c.
+	* Follow IE and Firefox on algorithm for choosing MIME boundary --
+	  servers are buggy on this.
+	* Fix POST multipart/form-data parameter ordering (patch from
+	  Balazs Ree) and ImageControl ordering.
+	* Fix .fixup() of disabled select with no selected options (John
+	  Wayne).
+	* Encoding fixes.
+	* Add BeautifulSoup support (not yet well tested).
+	* Switch from htmllib to sgmllib.
+	* Add form name to str(HTMLForm).
+	* Make parser debugging a bit easier.
+
+2005-11-19 John J Lee <jjl at pobox.com>
+	* Fixes to setuptools support.
+	* Released 0.2.1b.
+
+2005-10-30 John J Lee <jjl at pobox.com>
+	* Fix redirection of content-type header (Titus Brown).
+	* Fix ordering of interspersed list controls (Balazs Ree).
+
+2005-10-28 John J Lee <jjl at pobox.com>
+	* Implement HTMLForm.__contains__, since it's odd that
+	  'form[blah]' works but 'blah in form' did not.
+
+2005-10-11 John J Lee <jjl at pobox.com>
+	* Released 0.2.0a.
+
+2005-10-09 John J Lee <jjl at pobox.com>
+	* Many improvements have been made as part of 0.2 release,
+	  thanks largely to Gary Poster, Benji York and their employer
+	  Zope Corporation.  These include:
+	* 0.1 backwards compatibility mode (backwards_compat switch).
+	* The example script on the web page / README.html is now an
+	  executable script in the examples directory, that runs against a
+	  test page on the wwwsearch.sourceforge.net site.
+	* Greatly improved support for labels, including control labels.
+	* Label matching is now by substring, not by exact string
+	  equality.
+	* Support for list item ids.
+	* Finding controls or items now raises AmbiguityError if no nr
+	  argument is supplied and the other arguments do not uniquely
+	  identify the control or item.  The old behaviour is restored by
+	  passing nr=0.
+	* Fix multiple identical list item behaviour.
+	* Fixed a bug where disabled list items were successful (got sent
+	  back to the server).
+	* More intuitive disabled list item behaviour.
+	* Added first-class support for list items and labels.
+	* Large sections of the module have been reimplemented using
+	  classes Item and Label, making for better code.
+	* Added ListControl.get(), ListControl.get_items(),
+	  HTMLForm.set_value_by_label(), and HTMLForm.get_value_by_label()
+	  methods.
+	* The following ListControl methods have been deprecated:
+	  possible_items
+	  get_item_attrs
+	  set_item_disabled
+	  get_item_disabled
+	  set_single
+	  toggle_single
+	  set
+	  toggle
+	* The following HTMLForm methods have been deprecated:
+	  possible_items
+	  set_single
+	  toggle_single
+	  set
+	  toggle
+	* The by_label argument of the following methods has been
+	  deprecated.
+	  get_value
+	  set_value
+	* Added support for setuptools / EasyInstall / Python Eggs.
+
+2005-05-20 John J Lee <jjl at pobox.com>
+	* Fix failure to honour request_class.
+
+2005-05-08 John J Lee <jjl at pobox.com>
+	* Fix bug where form action not HTML-unescaped (thanks to Tomasz
+	  Malesinski for bug report).  Make entitydefs more sane.  Expose
+	  entitydefs in ParseFile / ParseResponse functions.
+
+2005-04-17 John J Lee <jjl at pobox.com>
+	* By default, treat unrecognized controls like input type="text".
+
+2005-02-23 John J Lee <jjl at pobox.com>
+	* Applied patch from Titus Brown to add .clear() method to all
+	  Controls.
+
+2005-01-30 John J Lee <jjl at pobox.com>
+	* Fix failure to raise ParseError (!)
+	* Workaround for failure of sgmllib to unescape attributes (bug
+	  report from Titus Brown).
+	* Released 0.1.17.
+
+2005-01-17 John J Lee <jjl at pobox.com>
+	* Fixed case where FORM action contains a '?' or '#' (again).
+	* Allow user to supply own Request class (Tobias).
+	* Fix ISINDEX action URL (bug only showed up in Python 2.4).
+	* Fix image control in case where value is present.
+	* Hack choose_boundary not to fail on socket.gaierror.
+
+2004-05-15 John J Lee <jjl at pobox.com>
+	* Released 0.1.16 and 0.0.16.
+
+	* 0.1.x and 0.0.x:
+	* Fixed case where FORM action contains a '?' (bug report from
+	  Moof).
+
+	* 0.1.x only:
+	* Look for BASE element attribute 'href', not 'uri'! (patch from
+	  Jochen Knuth)
+	* Applied workaround for file upload for AOLServer (patch from
+	  Andrei Mitran).
+	* Added optional form_parser_class arguments to allow choice
+	  between htmllib and HTMLParser modules.
+	* Added a SelectControl._delete_items() method, useful for quick-
+	  hack JS simulation.  Not yet a stable interface, hence the
+	  initial underscore.
+
+	* 0.0.x only:
+	* SubmitControls with no initial value in HTML are now successful
+	  (default value is '', not None)
+
+2004-01-22 John J Lee <jjl at pobox.com>
+	* 0.1.x only:
+	* Cleaned up docs a bit, and removed references to toggle methods.
+
+2004-01-05 John J Lee <jjl at pobox.com>
+	* 0.1.x only:
+	* Take note of base element.  Thanks to Phillip J. Eby for bug
+	  report.
+	* All form attributes are now available in HTMLForm.attrs
+	  (previously, name, action, method and enctype were not present).
+	* Released 0.1.15.
+
+2004-01-01 John J Lee <jjl at pobox.com>
+	* 0.1.x only: Disovered ignore_errors was ignored by
+	  ParseResponse!  It seems nobody uses it from ParseResponse, and
+	  it's probably worthless anyway.  Also, I just now realise that
+	  FormParser.error() is actually overriding a base class method
+	  without my noticing it!  The arguments are still there, but
+	  they're now ignored.  Thanks to Per Cederqvist.
+
+2003-12-24 John J Lee <jjl at pobox.com>
+	* Modified setup.py so can easily register with PyPI.
+
+2003-12-06 John J Lee <jjl at pobox.com>
+	* Fixed bug where ClientForm.urlencode choked on Unicode.
+	* Released 0.1.14 and 0.0.15.
+
+2003-11-14 John J Lee <jjl at pobox.com>
+	* A few doc fixes in HTMLForm.__doc__.
+	* Minor code clean-up.
+
+2003-11-12 John J Lee <jjl at pobox.com>
+	* Fixed bug where empty OPTION caused KeyError.  Thanks to Doug
+	  Henderson.
+	* Released 0.1.13 and 0.0.14.
+
+2003-11-11 John J Lee <jjl at pobox.com>
+	* Fixed bugs where TEXTAREA or OPTION containing entity reference
+	  would result in truncated element contents.  Thanks to Michael
+	  Howitz again!
+	* Applied fixes to 0.0.x for ImageControl integer coordinates,
+	  TEXTAREA content .strip()ping and entity references in TEXTAREA
+	  and OPTION.
+	* Released 0.1.12 and 0.0.13.
+
+2003-11-07 John J Lee <jjl at pobox.com>
+	* TEXTAREA contents are no longer .strip()ped on form parsing.
+	* Released 0.1.11.
+
+2003-11-03 John J Lee <jjl at pobox.com>
+	* Fixed ImageControl.pairs(): return value contained integer
+	  coordinates instead of strings.  Thanks to Michael Howitz.
+
+2003-10-31 John J Lee <jjl at pobox.com>
+	* XHTML support for Pythons >= 2.2.  Thanks to Michael Howitz.
+	* Released 0.1.10.
+
+2003-10-02 John J Lee <jjl at pobox.com>
+	* Bugfix: selection of default control to click on is supposed to
+	  only happen if no control is explictly requested, but id wasn't
+	  included in that.  Now, it is.
+
+2003-09-28 John J Lee <jjl at pobox.com>
+	* Fixed HTMLForm.attrs.  Thanks to Scott Chapman.
+	* Released ClientForm 0.0.12 and 0.1.9 (first stable release of
+	  0.1.x).
+
+2003-09-21 John J Lee <jjl at pobox.com>
+	* Interface change (sorry): id is now supported.  This means
+	  Controls have an id attribute, and appropriate HTMLForm methods
+	  have an id argument.  This will only affect people using
+	  positional arguments after the 'kind' argument.
+	* Interface change: BUTTON/BUTTON now has type "buttonbutton" (was
+	  "button") to prevent clash with type of INPUT/BUTTON (was and is
+	  "button").  Both types of control are ignored anyway (ie.
+	  represented by IgnoreControl), so it's unlikely any code is
+	  affected.
+	* SubmitControl value now defaults to "", so it is successful even
+	  when no value is given in the HTML.
+	* Extraneous "\r\n\r\n" at start of multipart/form-data POST data
+	  removed.
+	* Multiple file upload now emits multipart/mixed, rather than
+	  multipart/multipart/mixed, as content-type!
+	* Content-disposition header now comes before content-type, in
+	  case that matters...
+	* Slight tweak to SelectControl.fixup, to fix case where multiple
+	  SELECT is empty.
+	* Released 0.1.8b.
+
+2003-07-12 John J Lee <jjl at pobox.com>
+	* Added indication to ListControl.__str__ of disabled items --
+	  they have parentheses around them: item 1, (item 2), item 3
+	  means "item 2" is disabled.
+	* Released 0.1.7b.
+
+2003-07-10 John J Lee <jjl at pobox.com>
+	* Removed assertion that self.value is None in
+	  IgnoreControl.__init__.  Now sets value to None instead.  Thanks
+	  to Martijn Faasen for bug report.  Same for FileControl.
+
+2003-07-23 John J Lee <jjl at pobox.com>
+	* 0.1.6a changes:
+	* After some thought about Law of Demeter, realised that there was
+	  no justification for deprecating most use of find_control, nor
+	  for all of the new methods on HTMLForm.  Use of find_control
+	  is now officially OK again.  set_/get_readonly,
+	  set_/get_disabled, set_/get_item_disabled and
+	  set_all_items disabled, have been removed from HTMLForm.
+	* Added HTMLForm.set_all_readonly method.  This one is actually
+	  useful!
+	* All methods on Controls that used to be separate _by_label
+	  methods are now by_label arguments, now I see that labels can be
+	  defined for all controls' items.  The exceptions are
+	  set_value_by_label and get_value_by_label, since there is no
+	  method to add an argument to in those cases.  The lack of
+	  implementation of by_label for CHECKBOX and RADIO is considered
+	  a bug, so NotImplementedError is raised.  LabelNotSupportedError
+	  has gone.
+	* Released 0.1.6a.
+
+2003-07-22 John J Lee <jjl at pobox.com>
+	* 0.1.6a changes:
+	* Added some tests for new HTMLForm methods.
+	* ListControl.readonly now exists and works.
+	* Corrected error message for predicate arg of
+	  HTMLForm.find_control.
+	* Fixed HTMLForm.get_readonly.
+	* Fixed ListControl.get_item_disabled.
+	* Fixed exception raised by ListControl: was TypeError, now
+	  LabelNotSupportedError.
+	* Fixed ListControl.get_value, .set_value and .possible_items
+	  exception messages.
+	* Enforced restriction on new HTMLForm methods that at least one
+	  find_control argument must be supplied.
+
+2003-07-14 John J Lee <jjl at pobox.com>
+	* 0.1.5a changes:
+	* Removed listcontrol arg from HTMLForm methods, added kind and
+	  predicate arguments to .find_control.  kind argument is
+	  available in most HTMLForm methods.
+	* set methods take selected argument set(selected, "itemname"),
+	  and the clear methods are gone.
+
+2003-07-13 John J Lee <jjl at pobox.com>
+	* 0.1.5a changes:
+	* FileControl now unsuccessful when disabled attribute is true.
+	* Moved most method definitions in Control into ScalarControl.
+	* ListControls now always take sequence values, never string-like
+	  values.
+	* listcontrol argument on appropriate HTMLForm methods, in
+	  addition to name, type and nr.  This allows you to ask for a
+	  ListControl without specifying the exact type.
+	* All controls now have the readonly attribute.
+	* Renamed get_value_as_label --> get_value_by_label.
+	* Renamed possible_values --> possible_items.
+	* Renamed possible_labels --> possible_item_labels.
+	* SelectControl.set_by_label, .clear_by_label, and
+	  .toggle_by_label have now gone, to be replaced by by_label
+	  arguments to .get, .set and .toggle.
+	* Added files now have their MIME content type guessed unless the
+	  content type is explicitly specified in content_type argument to
+	  add_file.  At the moment, it's always guessed to be
+	  application/octet-stream.
+
+2003-07-12 John J Lee <jjl at pobox.com>
+	* 0.1.5a changes:
+	* get_item_attrs now raises IndexError instead of returning None
+	  when the item is not found.
+	* Realised that exceptions raised are a mess (IndexError should
+	  never have been raised at all, for a start).  Rethought it all
+	  and thoroughly overhauled it.
+
+2003-07-08 John J Lee <jjl at pobox.com>
+	* 0.1.5a changes:
+	* Added toggle_single, set_single, clear_single methods to
+	  HTMLForm and ListControl.  This is useful when you have a
+	  single-item list control (usually a single checkbox that you
+	  want to check), and you want to select that item without having
+	  to know what the item's name is (it's usually something
+	  meaningless like "1" or "on").
+	* FileControl no longer derives from TextControl.
+	* Moved most documentation from Control objects into HTMLForm.
+	  The class docstring for HTMLForm now contains most of what you
+	  need to know.
+
+2003-07-07 John J Lee <jjl at pobox.com>
+	* 0.1.5a: Empty SelectControl can now be constructed.
+
+2003-07-06 John J Lee <jjl at pobox.com>
+	* 0.1.5a changes:
+	* Interface change: the HTMLForm.set, .clear and .toggle
+	  methods now take value as *first* argument, with the other
+	  arguments reflecting those of find_control (ie. name, type, nr).
+	* find_control and find_item now behave as documented with regard
+	  to need to supply all arguments (nr now defaults to None, not
+	  0).
+	* Renamed find_item --> get_item_attrs.
+	* Added ListControl.get_item_disabled and .set_item_disabled
+	  methods, and support for OPTGROUP (disabled OPTGROUPs make their
+	  OPTIONs disabled).  No longer need to mess with attrs dictionary
+	  to set disabled state of items.
+	* Renamed items --> pairs.
+	* Renamed click_items --> click_pairs.
+	* HTML attribute dictionaries now contain *all* original HTML
+	  attributes, including those that are exposed elsewhere in the
+	  ClientForm API (such as name, type, multiple, selected).
+	* HTMLForm.find_control now raises IndexError instead of returning
+	  None when no control is found.  set_disabled, set_readonly,
+	  click, click_request_data, click_pairs, set, clear, toggle,
+	  possible_values all now raise IndexError instead of ValueError
+	  when no control is found.
+	* HTMLForm.set_disabled, .set_readonly now take boolean arg as
+	  first argument, and take type and nr args.
+	* HTMLForm.set_readonly now raises AttributeError, not ValueError,
+	  when invoked for control with no readonly attribute.
+	* Fixed minor, latent 1.5.2-compatibility bug in MapBase.
+	* HTMLForm.set, .get, .toggle now raise AttributeError, not
+	  TypeError, on being invoked for non-list controls.
+	* Removed nr argument from all methods related to find_item_attrs.
+	  Not needed AFAICS!
+	* Lots of new delegating methods on HTMLForm.
+	* ListControl.multiple is now enforced to be readonly.
+	* Controls now take extra name argument (to enable creating empty
+	  ListControls).
+	* Some code cleanup.
+
+2003-07-04 John J Lee <jjl at pobox.com>
+	* 0.1.5a: Added HTMLForm.set_disabled and .set_readonly methods.
+
+2003-06-29 John J Lee <jjl at pobox.com>
+	* Noticed that I was wrong about browser behaviour with default
+	  selection for RADIO with no explictly selected items in HTML.
+	  In fact, browsers don't select any items in that case, in
+	  contradiction to HTML 4.01 (and RFC 1866, FWIW).  Default is now
+	  for RadioControl to follow this behaviour, and the various
+	  select_default arguments now make RADIO follow the HTML 4.01
+	  standard.
+	* RadioControls now no longer have to have exactly one item
+	  selected.
+	* 0.1.4a: set / clear / toggle methods on HTMLForm and ListControl
+	  now work with single-selection controls.
+	* Released 0.0.11 and 0.1.4a.
+
+2003-06-28 John J Lee <jjl at pobox.com>
+	* 0.1.4a:
+	* Removed all asserts from tests.  Now uses TestCase.assert_
+	  method.
+	* All raise statements now raise Exception objects, not classes,
+	  and use the raise FooError('msg') syntax.
+	* Simplified implementation of HTMLForm set / clear / toggle
+	  methods.
+	* Corrected exception message for ListControl set / clear / toggle
+	  methods: was giving item name instead of control name for
+	  single-selection lists.
+	* Moved ListControl._single_set_value method from ListControl into
+	  RadioControl.
+	* disabled attribute is now handled differently: if any item has
+	  the disabled HTML-attribute, the control's value can't be set,
+	  but ListControl.set, .clear and .toggle (or the methods on
+	  HTMLForm with the same names) can still be used.  Using those
+	  methods, individual items can't be set if they're disabled.
+	  ATM, to un-disable an item, you have to del the dictionary key:
+
+	  del form.find_control("cheeses").find_item("cheddar")["disabled"]
+
+	  which will have to change, I think.
+
+2003-06-25 John J Lee <jjl at pobox.com>
+	* Changed license to BSD, to make it easier to use other code.
+	  The only difference is the addition of a non-endorsement clause.
+	* Default value for single-selection SELECT controls was wrong,
+	  and at odds with my own comments!  Before, nothing was selected
+	  if select_default was False (the default).  Now, the first item
+	  is selected.  Thanks to Chris Curvey.
+	* CHECKBOX and multiple SELECT controls now allow
+	  control.value = None.
+	* Better isstringlike function, after Alex Martelli.
+	* RadioControl now has default value "on" -- same as for
+	  CheckboxControl.  Both IE5 and Mozilla Firebird 0.6 do this.
+	* Fixed toggle_by_label & co. exceptions: before, raised
+	  KeyError, now raises ValueError.
+	* Released 0.0.10 and 0.1.3a.
+
+2003-06-13 John J Lee <jjl at pobox.com>
+	* Parse errors may now be ignored, thanks to ignore_errors
+	  argument to ParseFile and ParseResponse.
+	* 0.1.3a: Added HTMLForm.set and HTMLForm.clear methods (and
+	  corresponding control methods).
+
+2003-06-12 John J Lee <jjl at pobox.com>
+	* HTMLForm.__getitem__ and .__setitem__ now raise IndexError when
+	  they should.
+	* 0.1.3a: Change all HTTP headers to use initial caps in first
+	  word only (Content-type, not Content-Type), for 2.3
+	  compatibility when checking private Request.headers dict in
+	  tests.
+
+2003-06-09 John J Lee <jjl at pobox.com>
+	* Released 0.0.9 and 0.1.2a.
+
+2003-06-07 John J Lee <jjl at pobox.com>
+	* Improved output of __str__ methods.  Every control type now has
+	  its own class.
+	* Added nr argument to click* methods.
+	* 0.1.2a: Fixed bug in _request_data: POST with
+	  "application/x-www-form-urlencoded" failed due to incorrect
+	  return value.
+
+2003-06-03 John J Lee <jjl at pobox.com>
+	* Released 0.0.8 and 0.1.1a.
+
+2003-05-28 John J Lee <jjl at pobox.com>
+	* Fixed HTMLForm.__str__, which was calling repr on its Controls
+	  rather than str, which was rather unhelpful.
+	* Added a bit in README.html explaining single-checkbox-with-
+	  missing-value-attribute case.
+
+2003-04-30 John J Lee <jjl at pobox.com>
+	* Released 0.1.0a.
+
+2003-04-05 John J Lee <jjl at pobox.com>
+	* In 0.1.0a: Added file upload capability for INPUT TYPE=FILE
+	  controls (for single files only).
+	* In 0.1.0a: Removed items argument to HTMLForm.click method, and
+	  added click_items and click_request_data methods.  Removed items
+	  and make_request methods from HTMLForm.  Made SubmitControl.click
+	  method private -- is now named _click, and is only called by
+	  HTMLForm.
+	* In 0.1.0a: IsindexControl is now clickable, and isindex_url has
+	  been removed, since it was essentially pointless.
+	* In 0.1.0a: Changed SelectControl so it has an attrs dict of HTML
+	  attributes.  SELECT and OPTION HTML attributes are now separate.
+
+2003-03-23 John J Lee <jjl at pobox.com>
+	* Released 0.0.7.
+
+2003-03-08 John J Lee <jjl at pobox.com>
+	* In 0.1.0a: FormParser no longer deletes type HTML attribute from
+	  the dictionary of HTML attributes it provides -- is now the
+	  control's responsibility.
+
+2003-03-05 John J Lee <jjl at pobox.com>
+	* Allow INPUT TYPE=FILE in form (file upload still not
+	  implemented -- this is just to allow parsing forms containing
+	  file upload controls).
+
+2003-02-14 John J Lee <jjl at pobox.com>
+	* Fixed empty TEXTAREA case.  Thanks to Khalid Zuberi for the bug
+	  report and fix.
+	* Released 0.0.6.
+
+2003-02-05 John J Lee <jjl at pobox.com>
+	* Released 0.0.5 (first stable release).
+
+2003-01-05 John J Lee <jjl at pobox.com>
+	* Parser now no longer reads entire file before starting to work
+	  on data.
+
+2002-12-13 John J Lee <jjl at pobox.com>
+	* Implemented ISINDEX submission, and updated documentation (see
+	  IsindexControl.__doc__).
+	* Changed type attributes of BUTTON TYPE=SUBMIT and
+	  BUTTON TYPE=RESET to "submitbutton" and "resetbutton"
+	  respectively.  Previously, they were "submit" and "reset"
+	  respectively, which made it impossible to tell whether they came
+	  from a BUTTON or an INPUT control.
+	* Improved README.html.
+
+2002-11-19 John J Lee <jjl at pobox.com>
+	* Released 0.0.4b.
+
+2002-11-17 John J Lee <jjl at pobox.com>
+	* Changed license to MIT (from Perl Artistic).  Thanks, Gisle.
+	* Removed README, created README.html and INSTALL.  README mostly
+	  just restated what was in the web page, so README.html is now
+	  just a copy of the web page.
+
+2002-11-16 John J Lee <jjl at pobox.com>
+	* Tested label methods of SelectControl.
+	* Removed undocumented munging of SELECT's value HTML attribute to
+	  the key "select_value" in the HTML attributes dict returned by
+	  SelectControl.items().  The purpose of this, in the original
+	  Perl, was presumably to avoid clobbering SELECT's value HTML
+	  attribute (since OPTION and SELECT HTML attributes are merged to
+	  generate this dictionary).  The only trouble is, SELECT *has* no
+	  value HTML attribute!  Either some buggy HTML contains SELECT
+	  controls with value attributes, or Gisle was not paying
+	  attention when he wrote this, or both!
+
+2002-11-14 John J Lee <jjl at pobox.com>
+	* Fixed select_default for single-selection SELECT controls.
+
+2002-11-13 John J Lee <jjl at pobox.com>
+	* Replaced __repr__ methods with __str__ methods.  Very unlikely
+	  to break anyone's code.  repr(obj) now gives something more
+	  useful, str(obj) still gives the same result.
+	* Fixed ParseResponse, which was ignoring the select_default
+	  argument.
+	* Cleaned up constructors of ScalarControl and ListControl.
+	  Control is now more clearly an abstract base class (not meant to
+	  be instantiated).
+	* ListControl is now an abstract base class, with subclasses
+	  RadioControl, CheckboxControl and SelectControl.
+	* Rather than using the values of the OPTION elements to set
+	  SelectControl values, SelectControl items can also be specified
+	  by the labels of the OPTION elements.  For example, if you have
+	  a SELECT control like so:
+
+	  <SELECT>
+	    <OPTION value="br">Brie</OPTION>
+	    <OPTION value="ched">Cheddar</OPTION>
+	    <OPTION value="grgnz" label="Gorgonzola">Special offer on
+	      Gorgonzola!</OPTION>
+	  </SELECT>
+
+	  instead of setting its value like this:
+
+	  control.value = ["br", "ched", "grgnz"]
+
+	  you can now optionally use the more readable (and, possibly,
+	  more maintainable):
+
+	  control.set_value_by_label(["Brie", "Cheddar", "Gorgonzola"])
+
+	  Note that the label HTML attribute defaults to the content of
+	  the OPTION element (as does the value HTML attribute).
+	* Improved documentation and comments.
+
+2002-11-04 John J Lee <jjl at pobox.com>
+	* Fixed TextControl default value to be empty string rather than
+	  None.  This has the effect that text controls are successful
+	  even when empty.
+	* Stopped Content-Type from being emitted twice.
+
+2002-10-25 John J Lee <jjl at pobox.com>
+	* Released 0.0.3b
+
+2002-10-24 John J Lee <jjl at pobox.com>
+	* Changed handling of SELECT/multiple ListControls: select_default
+	  argument to various functions and methods now indicates whether
+	  or not should follow RFC 1866 or Netscape / IE behaviour in
+	  setting default selection if no 'selected' HTML attribute was
+	  given.
+	* Changed type of SELECT/OPTION controls to "select" from
+	  "option".  This is more appropriate, since SELECT is the element
+	  that represents the control, whereas the OPTION element
+	  represents the list items inside the control.
+	* Removed readonly attribute from ListControl -- reading W3C
+	  HTML 4 specification carefully and testing with Netscape / IE
+	  reveals that this isn't intended to work with INPUT elements
+	  other than those of type TEXT and PASSWORD.
+	* Fixed Control.__setattr__ to make value of disabled controls
+	  read-only.
+	* Improved tests and documentation.
+
+2002-10-20 John J Lee <jjl at pobox.com>
+	* Some testing on a site having a fairly complicated sequence of
+	  forms.  No problems came to light.
+	* Made name and type attributes of Control readonly.
+	* Improved documentation.
+
+2002-10-15 John J Lee <jjl at pobox.com>
+	* Fixed make_request to pass urlencode(data) instead of data for
+	  POST.
+	* Thanks to Conrad Schneiker for help with HTTPS on Windows and a
+	  bug report.
+
+2002-10-11 John J Lee <jjl at pobox.com>
+	* Fixed silly Python 2.3 forwards-compatibility bug (True / False
+	  constants were defined, overwriting the new builtin versions in
+	  2.3).
+	* Fixed treatment of form method -- was incorrectly treated as
+	  case-sentitive.
+	* Fixed enctype default in FormParser.
+
+2002-10-07 John J Lee <jjl at pobox.com>
+	* Added TEXTAREA.
+	* Added HTMLForm.attrs attribute, which is a dictionary mapping
+	  HTML attributes to their values.
+	* Added more tests.
+	* Back-ported to Python 1.5.2.
+
+2002-10-06 John J Lee <jjl at pobox.com>
+	* Renamed 'input' to 'control' everywhere (HTML 4.0 terminology,
+	  and more accurate, because one Control may represent more than
+	  one INPUT or OPTION, in the case of ListControl).
+	* Changed interface of HTMLForm.find_control and
+	  HTMLForm.possible_values, so that nr argument begins indexing at
+	  0 rather than 1.
+	* Added name attribute to HTMLForm.
+	* Fixed case where HTMLForm.find_control is passed only nr
+	  argument.
+	* Fixed find_control to return None rather than raise an
+	  exception.
+	* Renamed HTMLForm.push_control to new_control.
+	* Replaced HTMLForm.controls method with attribute.
+	* Fixed ListControl.set_value method in single-selection case.
+	* Replaced all type, name, value and set_value methods with
+	  attributes and __getattr__ / __setattr__.
+	* Added multiple attribute, indicating whether or not ListControl
+	  can have more than one value selected at a time.
+	* Added ScalarControl base class, which has attrs attribute which
+	  is a dictionary mapping HTML attributes to their values.
+	* Added find_item method to ListControl, which allows access to
+	  HTML attributes of items in the sequence.
+	* Removed controls argument of HTMLForm.__init__.
+	* Altered handling of disabled and readonly -- now are attributes
+	  on Control instances, and may be set or cleared to change
+	  Control's behaviour.
+	* Added toggle methods to ListControl and Form.
+	* Fixed ParseFile (hence ParseResponse) to set default form action
+	  correctly when there is none given in HTML.
+	* Fixed many tests.
+	* Improved documentation.
+
+2002-09-29 John J Lee <jjl at pobox.com>
+	* Edited down large test file to save space.
+
+2002-09-22 John J Lee <jjl at pobox.com>
+	* Added HTMLForm.possible_values method.
+	* First use on internet -- seems to work.
+	* Announced on comp.lang.python.announce.
+	* Released 0.0.2a
+
+2002-09-20 John J Lee <jjl at pobox.com>
+	* Uploaded 0.0.1a
+
+2002-09-14 John J Lee <jjl at pobox.com>
+	* Ported form tests from my old classes.
+	* Added input.merge_input() so that ListInputs can be created easily
+	  without an HTMLForm.
+
+2002-08-23 John J Lee <jjl at pobox.com>
+	* General clean-up.
+	* Added tests for input classes and debugged: tests now pass.
+	* Things should more-or-less work now.
+
+2002-08-19 John J Lee <jjl at pobox.com>
+	* Finished port.
+	* Tests from LWP pass.

Added: ClientForm/tags/0.2.9/ClientForm.egg-info/PKG-INFO
===================================================================
--- ClientForm/tags/0.2.9/ClientForm.egg-info/PKG-INFO	                        (rev 0)
+++ ClientForm/tags/0.2.9/ClientForm.egg-info/PKG-INFO	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,38 @@
+Metadata-Version: 1.0
+Name: ClientForm
+Version: 0.2.9
+Summary: Client-side HTML form handling.
+Home-page: http://wwwsearch.sourceforge.net/ClientForm/
+Author: John J. Lee
+Author-email: jjl at pobox.com
+License: BSD
+Download-URL: http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0.2.9.tar.gz
+Description: ClientForm is a Python module for handling HTML forms on the client
+        side, useful for parsing HTML forms, filling them in and returning the
+        completed forms to the server.  It developed from a port of Gisle Aas'
+        Perl module HTML::Form, from the libwww-perl library, but the
+        interface is not the same.
+        
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: BSD License
+Classifier: License :: OSI Approved :: Zope Public License
+Classifier: Natural Language :: English
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Internet
+Classifier: Topic :: Internet :: WWW/HTTP
+Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
+Classifier: Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Testing
+Classifier: Topic :: Software Development :: Testing :: Traffic Generation
+Classifier: Topic :: System :: Networking :: Monitoring
+Classifier: Topic :: System :: Systems Administration
+Classifier: Topic :: Text Processing
+Classifier: Topic :: Text Processing :: Markup
+Classifier: Topic :: Text Processing :: Markup :: HTML
+Classifier: Topic :: Text Processing :: Markup :: XML

Added: ClientForm/tags/0.2.9/ClientForm.egg-info/SOURCES.txt
===================================================================
--- ClientForm/tags/0.2.9/ClientForm.egg-info/SOURCES.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/ClientForm.egg-info/SOURCES.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,33 @@
+COPYING.txt
+COPYRIGHT.txt
+ChangeLog.txt
+ClientForm.py
+GeneralFAQ.html
+INSTALL.txt
+MANIFEST.in
+README.html
+README.html.in
+README.txt
+ez_setup.py
+setup.cfg
+setup.py
+test.py
+ClientForm.egg-info/PKG-INFO
+ClientForm.egg-info/SOURCES.txt
+ClientForm.egg-info/dependency_links.txt
+ClientForm.egg-info/top_level.txt
+ClientForm.egg-info/zip-safe
+examples/data.dat
+examples/data.txt
+examples/echo.cgi
+examples/example.html
+examples/example.py
+examples/simple.py
+test/test_clientform.py
+testdata/Auth.html
+testdata/FullSearch.html
+testdata/GeneralSearch.html
+testdata/MarkedRecords.html
+testdata/MarkedResults.html
+testdata/Results.html
+testdata/SearchType.html

Added: ClientForm/tags/0.2.9/ClientForm.egg-info/dependency_links.txt
===================================================================
--- ClientForm/tags/0.2.9/ClientForm.egg-info/dependency_links.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/ClientForm.egg-info/dependency_links.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1 @@
+

Added: ClientForm/tags/0.2.9/ClientForm.egg-info/top_level.txt
===================================================================
--- ClientForm/tags/0.2.9/ClientForm.egg-info/top_level.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/ClientForm.egg-info/top_level.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1 @@
+ClientForm

Added: ClientForm/tags/0.2.9/ClientForm.egg-info/zip-safe
===================================================================
--- ClientForm/tags/0.2.9/ClientForm.egg-info/zip-safe	                        (rev 0)
+++ ClientForm/tags/0.2.9/ClientForm.egg-info/zip-safe	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1 @@
+

Added: ClientForm/tags/0.2.9/ClientForm.py
===================================================================
--- ClientForm/tags/0.2.9/ClientForm.py	                        (rev 0)
+++ ClientForm/tags/0.2.9/ClientForm.py	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,3382 @@
+"""HTML form handling for web clients.
+
+ClientForm is a Python module for handling HTML forms on the client
+side, useful for parsing HTML forms, filling them in and returning the
+completed forms to the server.  It has developed from a port of Gisle
+Aas' Perl module HTML::Form, from the libwww-perl library, but the
+interface is not the same.
+
+The most useful docstring is the one for HTMLForm.
+
+RFC 1866: HTML 2.0
+RFC 1867: Form-based File Upload in HTML
+RFC 2388: Returning Values from Forms: multipart/form-data
+HTML 3.2 Specification, W3C Recommendation 14 January 1997 (for ISINDEX)
+HTML 4.01 Specification, W3C Recommendation 24 December 1999
+
+
+Copyright 2002-2007 John J. Lee <jjl at pobox.com>
+Copyright 2005 Gary Poster
+Copyright 2005 Zope Corporation
+Copyright 1998-2000 Gisle Aas.
+
+This code is free software; you can redistribute it and/or modify it
+under the terms of the BSD or ZPL 2.1 licenses (see the file
+COPYING.txt included with the distribution).
+
+"""
+
+# XXX
+# Remove parser testing hack
+# safeUrl()-ize action
+# Switch to unicode throughout (would be 0.3.x)
+#  See Wichert Akkerman's 2004-01-22 message to c.l.py.
+# Add charset parameter to Content-type headers?  How to find value??
+# Add some more functional tests
+#  Especially single and multiple file upload on the internet.
+#  Does file upload work when name is missing?  Sourceforge tracker form
+#   doesn't like it.  Check standards, and test with Apache.  Test
+#   binary upload with Apache.
+# mailto submission & enctype text/plain
+# I'm not going to fix this unless somebody tells me what real servers
+#  that want this encoding actually expect: If enctype is
+#  application/x-www-form-urlencoded and there's a FILE control present.
+#  Strictly, it should be 'name=data' (see HTML 4.01 spec., section
+#  17.13.2), but I send "name=" ATM.  What about multiple file upload??
+
+# Would be nice, but I'm not going to do it myself:
+# -------------------------------------------------
+# Maybe a 0.4.x?
+#   Replace by_label etc. with moniker / selector concept. Allows, eg.,
+#    a choice between selection by value / id / label / element
+#    contents.  Or choice between matching labels exactly or by
+#    substring.  Etc.
+#   Remove deprecated methods.
+#   ...what else?
+# Work on DOMForm.
+# XForms?  Don't know if there's a need here.
+
+__all__ = ['AmbiguityError', 'CheckboxControl', 'Control',
+           'ControlNotFoundError', 'FileControl', 'FormParser', 'HTMLForm',
+           'HiddenControl', 'IgnoreControl', 'ImageControl', 'IsindexControl',
+           'Item', 'ItemCountError', 'ItemNotFoundError', 'Label',
+           'ListControl', 'LocateError', 'Missing', 'ParseError', 'ParseFile',
+           'ParseFileEx', 'ParseResponse', 'ParseResponseEx','PasswordControl',
+           'RadioControl', 'ScalarControl', 'SelectControl',
+           'SubmitButtonControl', 'SubmitControl', 'TextControl',
+           'TextareaControl', 'XHTMLCompatibleFormParser']
+
+try: True
+except NameError:
+    True = 1
+    False = 0
+
+try: bool
+except NameError:
+    def bool(expr):
+        if expr: return True
+        else: return False
+
+try:
+    import logging
+    import inspect
+except ImportError:
+    def debug(msg, *args, **kwds):
+        pass
+else:
+    _logger = logging.getLogger("ClientForm")
+    OPTIMIZATION_HACK = True
+
+    def debug(msg, *args, **kwds):
+        if OPTIMIZATION_HACK:
+            return
+
+        caller_name = inspect.stack()[1][3]
+        extended_msg = '%%s %s' % msg
+        extended_args = (caller_name,)+args
+        debug = _logger.debug(extended_msg, *extended_args, **kwds)
+
+    def _show_debug_messages():
+        global OPTIMIZATION_HACK
+        OPTIMIZATION_HACK = False
+        _logger.setLevel(logging.DEBUG)
+        handler = logging.StreamHandler(sys.stdout)
+        handler.setLevel(logging.DEBUG)
+        _logger.addHandler(handler)
+
+import sys, urllib, urllib2, types, mimetools, copy, urlparse, \
+       htmlentitydefs, re, random
+from cStringIO import StringIO
+
+import sgmllib
+# monkeypatch to fix http://www.python.org/sf/803422 :-(
+sgmllib.charref = re.compile("&#(x?[0-9a-fA-F]+)[^0-9a-fA-F]")
+
+# HTMLParser.HTMLParser is recent, so live without it if it's not available
+# (also, sgmllib.SGMLParser is much more tolerant of bad HTML)
+try:
+    import HTMLParser
+except ImportError:
+    HAVE_MODULE_HTMLPARSER = False
+else:
+    HAVE_MODULE_HTMLPARSER = True
+
+try:
+    import warnings
+except ImportError:
+    def deprecation(message, stack_offset=0):
+        pass
+else:
+    def deprecation(message, stack_offset=0):
+        warnings.warn(message, DeprecationWarning, stacklevel=3+stack_offset)
+
+VERSION = "0.2.9"
+
+CHUNK = 1024  # size of chunks fed to parser, in bytes
+
+DEFAULT_ENCODING = "latin-1"
+
+class Missing: pass
+
+_compress_re = re.compile(r"\s+")
+def compress_text(text): return _compress_re.sub(" ", text.strip())
+
+def normalize_line_endings(text):
+    return re.sub(r"(?:(?<!\r)\n)|(?:\r(?!\n))", "\r\n", text)
+
+
+# This version of urlencode is from my Python 1.5.2 back-port of the
+# Python 2.1 CVS maintenance branch of urllib.  It will accept a sequence
+# of pairs instead of a mapping -- the 2.0 version only accepts a mapping.
+def urlencode(query,doseq=False,):
+    """Encode a sequence of two-element tuples or dictionary into a URL query \
+string.
+
+    If any values in the query arg are sequences and doseq is true, each
+    sequence element is converted to a separate parameter.
+
+    If the query arg is a sequence of two-element tuples, the order of the
+    parameters in the output will match the order of parameters in the
+    input.
+    """
+
+    if hasattr(query,"items"):
+        # mapping objects
+        query = query.items()
+    else:
+        # it's a bother at times that strings and string-like objects are
+        # sequences...
+        try:
+            # non-sequence items should not work with len()
+            x = len(query)
+            # non-empty strings will fail this
+            if len(query) and type(query[0]) != types.TupleType:
+                raise TypeError()
+            # zero-length sequences of all types will get here and succeed,
+            # but that's a minor nit - since the original implementation
+            # allowed empty dicts that type of behavior probably should be
+            # preserved for consistency
+        except TypeError:
+            ty,va,tb = sys.exc_info()
+            raise TypeError("not a valid non-string sequence or mapping "
+                            "object", tb)
+
+    l = []
+    if not doseq:
+        # preserve old behavior
+        for k, v in query:
+            k = urllib.quote_plus(str(k))
+            v = urllib.quote_plus(str(v))
+            l.append(k + '=' + v)
+    else:
+        for k, v in query:
+            k = urllib.quote_plus(str(k))
+            if type(v) == types.StringType:
+                v = urllib.quote_plus(v)
+                l.append(k + '=' + v)
+            elif type(v) == types.UnicodeType:
+                # is there a reasonable way to convert to ASCII?
+                # encode generates a string, but "replace" or "ignore"
+                # lose information and "strict" can raise UnicodeError
+                v = urllib.quote_plus(v.encode("ASCII","replace"))
+                l.append(k + '=' + v)
+            else:
+                try:
+                    # is this a sufficient test for sequence-ness?
+                    x = len(v)
+                except TypeError:
+                    # not a sequence
+                    v = urllib.quote_plus(str(v))
+                    l.append(k + '=' + v)
+                else:
+                    # loop over the sequence
+                    for elt in v:
+                        l.append(k + '=' + urllib.quote_plus(str(elt)))
+    return '&'.join(l)
+
+def unescape(data, entities, encoding=DEFAULT_ENCODING):
+    if data is None or "&" not in data:
+        return data
+
+    def replace_entities(match, entities=entities, encoding=encoding):
+        ent = match.group()
+        if ent[1] == "#":
+            return unescape_charref(ent[2:-1], encoding)
+
+        repl = entities.get(ent)
+        if repl is not None:
+            if type(repl) != type(""):
+                try:
+                    repl = repl.encode(encoding)
+                except UnicodeError:
+                    repl = ent
+        else:
+            repl = ent
+
+        return repl
+
+    return re.sub(r"&#?[A-Za-z0-9]+?;", replace_entities, data)
+
+def unescape_charref(data, encoding):
+    name, base = data, 10
+    if name.startswith("x"):
+        name, base= name[1:], 16
+    uc = unichr(int(name, base))
+    if encoding is None:
+        return uc
+    else:
+        try:
+            repl = uc.encode(encoding)
+        except UnicodeError:
+            repl = "&#%s;" % data
+        return repl
+
+def get_entitydefs():
+    import htmlentitydefs
+    from codecs import latin_1_decode
+    entitydefs = {}
+    try:
+        htmlentitydefs.name2codepoint
+    except AttributeError:
+        entitydefs = {}
+        for name, char in htmlentitydefs.entitydefs.items():
+            uc = latin_1_decode(char)[0]
+            if uc.startswith("&#") and uc.endswith(";"):
+                uc = unescape_charref(uc[2:-1], None)
+            entitydefs["&%s;" % name] = uc
+    else:
+        for name, codepoint in htmlentitydefs.name2codepoint.items():
+            entitydefs["&%s;" % name] = unichr(codepoint)
+    return entitydefs
+
+
+def issequence(x):
+    try:
+        x[0]
+    except (TypeError, KeyError):
+        return False
+    except IndexError:
+        pass
+    return True
+
+def isstringlike(x):
+    try: x+""
+    except: return False
+    else: return True
+
+
+def choose_boundary():
+    """Return a string usable as a multipart boundary."""
+    # follow IE and firefox
+    nonce = "".join([str(random.randint(0, sys.maxint-1)) for i in 0,1,2])
+    return "-"*27 + nonce
+
+# This cut-n-pasted MimeWriter from standard library is here so can add
+# to HTTP headers rather than message body when appropriate.  It also uses
+# \r\n in place of \n.  This is a bit nasty.
+class MimeWriter:
+
+    """Generic MIME writer.
+
+    Methods:
+
+    __init__()
+    addheader()
+    flushheaders()
+    startbody()
+    startmultipartbody()
+    nextpart()
+    lastpart()
+
+    A MIME writer is much more primitive than a MIME parser.  It
+    doesn't seek around on the output file, and it doesn't use large
+    amounts of buffer space, so you have to write the parts in the
+    order they should occur on the output file.  It does buffer the
+    headers you add, allowing you to rearrange their order.
+
+    General usage is:
+
+    f = <open the output file>
+    w = MimeWriter(f)
+    ...call w.addheader(key, value) 0 or more times...
+
+    followed by either:
+
+    f = w.startbody(content_type)
+    ...call f.write(data) for body data...
+
+    or:
+
+    w.startmultipartbody(subtype)
+    for each part:
+        subwriter = w.nextpart()
+        ...use the subwriter's methods to create the subpart...
+    w.lastpart()
+
+    The subwriter is another MimeWriter instance, and should be
+    treated in the same way as the toplevel MimeWriter.  This way,
+    writing recursive body parts is easy.
+
+    Warning: don't forget to call lastpart()!
+
+    XXX There should be more state so calls made in the wrong order
+    are detected.
+
+    Some special cases:
+
+    - startbody() just returns the file passed to the constructor;
+      but don't use this knowledge, as it may be changed.
+
+    - startmultipartbody() actually returns a file as well;
+      this can be used to write the initial 'if you can read this your
+      mailer is not MIME-aware' message.
+
+    - If you call flushheaders(), the headers accumulated so far are
+      written out (and forgotten); this is useful if you don't need a
+      body part at all, e.g. for a subpart of type message/rfc822
+      that's (mis)used to store some header-like information.
+
+    - Passing a keyword argument 'prefix=<flag>' to addheader(),
+      start*body() affects where the header is inserted; 0 means
+      append at the end, 1 means insert at the start; default is
+      append for addheader(), but insert for start*body(), which use
+      it to determine where the Content-type header goes.
+
+    """
+
+    def __init__(self, fp, http_hdrs=None):
+        self._http_hdrs = http_hdrs
+        self._fp = fp
+        self._headers = []
+        self._boundary = []
+        self._first_part = True
+
+    def addheader(self, key, value, prefix=0,
+                  add_to_http_hdrs=0):
+        """
+        prefix is ignored if add_to_http_hdrs is true.
+        """
+        lines = value.split("\r\n")
+        while lines and not lines[-1]: del lines[-1]
+        while lines and not lines[0]: del lines[0]
+        if add_to_http_hdrs:
+            value = "".join(lines)
+            self._http_hdrs.append((key, value))
+        else:
+            for i in range(1, len(lines)):
+                lines[i] = "    " + lines[i].strip()
+            value = "\r\n".join(lines) + "\r\n"
+            line = key + ": " + value
+            if prefix:
+                self._headers.insert(0, line)
+            else:
+                self._headers.append(line)
+
+    def flushheaders(self):
+        self._fp.writelines(self._headers)
+        self._headers = []
+
+    def startbody(self, ctype=None, plist=[], prefix=1,
+                  add_to_http_hdrs=0, content_type=1):
+        """
+        prefix is ignored if add_to_http_hdrs is true.
+        """
+        if content_type and ctype:
+            for name, value in plist:
+                ctype = ctype + ';\r\n %s=%s' % (name, value)
+            self.addheader("Content-type", ctype, prefix=prefix,
+                           add_to_http_hdrs=add_to_http_hdrs)
+        self.flushheaders()
+        if not add_to_http_hdrs: self._fp.write("\r\n")
+        self._first_part = True
+        return self._fp
+
+    def startmultipartbody(self, subtype, boundary=None, plist=[], prefix=1,
+                           add_to_http_hdrs=0, content_type=1):
+        boundary = boundary or choose_boundary()
+        self._boundary.append(boundary)
+        return self.startbody("multipart/" + subtype,
+                              [("boundary", boundary)] + plist,
+                              prefix=prefix,
+                              add_to_http_hdrs=add_to_http_hdrs,
+                              content_type=content_type)
+
+    def nextpart(self):
+        boundary = self._boundary[-1]
+        if self._first_part:
+            self._first_part = False
+        else:
+            self._fp.write("\r\n")
+        self._fp.write("--" + boundary + "\r\n")
+        return self.__class__(self._fp)
+
+    def lastpart(self):
+        if self._first_part:
+            self.nextpart()
+        boundary = self._boundary.pop()
+        self._fp.write("\r\n--" + boundary + "--\r\n")
+
+
+class LocateError(ValueError): pass
+class AmbiguityError(LocateError): pass
+class ControlNotFoundError(LocateError): pass
+class ItemNotFoundError(LocateError): pass
+
+class ItemCountError(ValueError): pass
+
+# for backwards compatibility, ParseError derives from exceptions that were
+# raised by versions of ClientForm <= 0.2.5
+if HAVE_MODULE_HTMLPARSER:
+    SGMLLIB_PARSEERROR = sgmllib.SGMLParseError
+    class ParseError(sgmllib.SGMLParseError,
+                     HTMLParser.HTMLParseError,
+                     ):
+        pass
+else:
+    if hasattr(sgmllib, "SGMLParseError"):
+        SGMLLIB_PARSEERROR = sgmllib.SGMLParseError
+        class ParseError(sgmllib.SGMLParseError):
+            pass
+    else:
+        SGMLLIB_PARSEERROR = RuntimeError
+        class ParseError(RuntimeError):
+            pass
+
+
+class _AbstractFormParser:
+    """forms attribute contains HTMLForm instances on completion."""
+    # thanks to Moshe Zadka for an example of sgmllib/htmllib usage
+    def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
+        if entitydefs is None:
+            entitydefs = get_entitydefs()
+        self._entitydefs = entitydefs
+        self._encoding = encoding
+
+        self.base = None
+        self.forms = []
+        self.labels = []
+        self._current_label = None
+        self._current_form = None
+        self._select = None
+        self._optgroup = None
+        self._option = None
+        self._textarea = None
+
+        # forms[0] will contain all controls that are outside of any form
+        # self._global_form is an alias for self.forms[0]
+        self._global_form = None
+        self.start_form([])
+        self.end_form()
+        self._current_form = self._global_form = self.forms[0]
+
+    def do_base(self, attrs):
+        debug("%s", attrs)
+        for key, value in attrs:
+            if key == "href":
+                self.base = self.unescape_attr_if_required(value)
+
+    def end_body(self):
+        debug("")
+        if self._current_label is not None:
+            self.end_label()
+        if self._current_form is not self._global_form:
+            self.end_form()
+
+    def start_form(self, attrs):
+        debug("%s", attrs)
+        if self._current_form is not self._global_form:
+            raise ParseError("nested FORMs")
+        name = None
+        action = None
+        enctype = "application/x-www-form-urlencoded"
+        method = "GET"
+        d = {}
+        for key, value in attrs:
+            if key == "name":
+                name = self.unescape_attr_if_required(value)
+            elif key == "action":
+                action = self.unescape_attr_if_required(value)
+            elif key == "method":
+                method = self.unescape_attr_if_required(value.upper())
+            elif key == "enctype":
+                enctype = self.unescape_attr_if_required(value.lower())
+            d[key] = self.unescape_attr_if_required(value)
+        controls = []
+        self._current_form = (name, action, method, enctype), d, controls
+
+    def end_form(self):
+        debug("")
+        if self._current_label is not None:
+            self.end_label()
+        if self._current_form is self._global_form:
+            raise ParseError("end of FORM before start")
+        self.forms.append(self._current_form)
+        self._current_form = self._global_form
+
+    def start_select(self, attrs):
+        debug("%s", attrs)
+        if self._select is not None:
+            raise ParseError("nested SELECTs")
+        if self._textarea is not None:
+            raise ParseError("SELECT inside TEXTAREA")
+        d = {}
+        for key, val in attrs:
+            d[key] = self.unescape_attr_if_required(val)
+
+        self._select = d
+        self._add_label(d)
+
+        self._append_select_control({"__select": d})
+
+    def end_select(self):
+        debug("")
+        if self._select is None:
+            raise ParseError("end of SELECT before start")
+
+        if self._option is not None:
+            self._end_option()
+
+        self._select = None
+
+    def start_optgroup(self, attrs):
+        debug("%s", attrs)
+        if self._select is None:
+            raise ParseError("OPTGROUP outside of SELECT")
+        d = {}
+        for key, val in attrs:
+            d[key] = self.unescape_attr_if_required(val)
+
+        self._optgroup = d
+
+    def end_optgroup(self):
+        debug("")
+        if self._optgroup is None:
+            raise ParseError("end of OPTGROUP before start")
+        self._optgroup = None
+
+    def _start_option(self, attrs):
+        debug("%s", attrs)
+        if self._select is None:
+            raise ParseError("OPTION outside of SELECT")
+        if self._option is not None:
+            self._end_option()
+
+        d = {}
+        for key, val in attrs:
+            d[key] = self.unescape_attr_if_required(val)
+
+        self._option = {}
+        self._option.update(d)
+        if (self._optgroup and self._optgroup.has_key("disabled") and
+            not self._option.has_key("disabled")):
+            self._option["disabled"] = None
+
+    def _end_option(self):
+        debug("")
+        if self._option is None:
+            raise ParseError("end of OPTION before start")
+
+        contents = self._option.get("contents", "").strip()
+        self._option["contents"] = contents
+        if not self._option.has_key("value"):
+            self._option["value"] = contents
+        if not self._option.has_key("label"):
+            self._option["label"] = contents
+        # stuff dict of SELECT HTML attrs into a special private key
+        #  (gets deleted again later)
+        self._option["__select"] = self._select
+        self._append_select_control(self._option)
+        self._option = None
+
+    def _append_select_control(self, attrs):
+        debug("%s", attrs)
+        controls = self._current_form[2]
+        name = self._select.get("name")
+        controls.append(("select", name, attrs))
+
+    def start_textarea(self, attrs):
+        debug("%s", attrs)
+        if self._textarea is not None:
+            raise ParseError("nested TEXTAREAs")
+        if self._select is not None:
+            raise ParseError("TEXTAREA inside SELECT")
+        d = {}
+        for key, val in attrs:
+            d[key] = self.unescape_attr_if_required(val)
+        self._add_label(d)
+
+        self._textarea = d
+
+    def end_textarea(self):
+        debug("")
+        if self._textarea is None:
+            raise ParseError("end of TEXTAREA before start")
+        controls = self._current_form[2]
+        name = self._textarea.get("name")
+        controls.append(("textarea", name, self._textarea))
+        self._textarea = None
+
+    def start_label(self, attrs):
+        debug("%s", attrs)
+        if self._current_label:
+            self.end_label()
+        d = {}
+        for key, val in attrs:
+            d[key] = self.unescape_attr_if_required(val)
+        taken = bool(d.get("for"))  # empty id is invalid
+        d["__text"] = ""
+        d["__taken"] = taken
+        if taken:
+            self.labels.append(d)
+        self._current_label = d
+
+    def end_label(self):
+        debug("")
+        label = self._current_label
+        if label is None:
+            # something is ugly in the HTML, but we're ignoring it
+            return
+        self._current_label = None
+        # if it is staying around, it is True in all cases
+        del label["__taken"]
+
+    def _add_label(self, d):
+        #debug("%s", d)
+        if self._current_label is not None:
+            if not self._current_label["__taken"]:
+                self._current_label["__taken"] = True
+                d["__label"] = self._current_label
+
+    def handle_data(self, data):
+        debug("%s", data)
+
+        # according to http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1
+        # line break immediately after start tags or immediately before end
+        # tags must be ignored, but real browsers only ignore a line break
+        # after a start tag, so we'll do that.
+        if data[0:2] == "\r\n":
+            data = data[2:]
+        if data[0:1] in ["\n", "\r"]:
+            data = data[1:]
+
+        if self._option is not None:
+            # self._option is a dictionary of the OPTION element's HTML
+            # attributes, but it has two special keys, one of which is the
+            # special "contents" key contains text between OPTION tags (the
+            # other is the "__select" key: see the end_option method)
+            map = self._option
+            key = "contents"
+        elif self._textarea is not None:
+            map = self._textarea
+            key = "value"
+            data = normalize_line_endings(data)
+        # not if within option or textarea
+        elif self._current_label is not None:
+            map = self._current_label
+            key = "__text"
+        else:
+            return
+
+        if not map.has_key(key):
+            map[key] = data
+        else:
+            map[key] = map[key] + data
+
+    def do_button(self, attrs):
+        debug("%s", attrs)
+        d = {}
+        d["type"] = "submit"  # default
+        for key, val in attrs:
+            d[key] = self.unescape_attr_if_required(val)
+        controls = self._current_form[2]
+
+        type = d["type"]
+        name = d.get("name")
+        # we don't want to lose information, so use a type string that
+        # doesn't clash with INPUT TYPE={SUBMIT,RESET,BUTTON}
+        # e.g. type for BUTTON/RESET is "resetbutton"
+        #     (type for INPUT/RESET is "reset")
+        type = type+"button"
+        self._add_label(d)
+        controls.append((type, name, d))
+
+    def do_input(self, attrs):
+        debug("%s", attrs)
+        d = {}
+        d["type"] = "text"  # default
+        for key, val in attrs:
+            d[key] = self.unescape_attr_if_required(val)
+        controls = self._current_form[2]
+
+        type = d["type"]
+        name = d.get("name")
+        self._add_label(d)
+        controls.append((type, name, d))
+
+    def do_isindex(self, attrs):
+        debug("%s", attrs)
+        d = {}
+        for key, val in attrs:
+            d[key] = self.unescape_attr_if_required(val)
+        controls = self._current_form[2]
+
+        self._add_label(d)
+        # isindex doesn't have type or name HTML attributes
+        controls.append(("isindex", None, d))
+
+    def handle_entityref(self, name):
+        #debug("%s", name)
+        self.handle_data(unescape(
+            '&%s;' % name, self._entitydefs, self._encoding))
+
+    def handle_charref(self, name):
+        #debug("%s", name)
+        self.handle_data(unescape_charref(name, self._encoding))
+
+    def unescape_attr(self, name):
+        #debug("%s", name)
+        return unescape(name, self._entitydefs, self._encoding)
+
+    def unescape_attrs(self, attrs):
+        #debug("%s", attrs)
+        escaped_attrs = {}
+        for key, val in attrs.items():
+            try:
+                val.items
+            except AttributeError:
+                escaped_attrs[key] = self.unescape_attr(val)
+            else:
+                # e.g. "__select" -- yuck!
+                escaped_attrs[key] = self.unescape_attrs(val)
+        return escaped_attrs
+
+    def unknown_entityref(self, ref): self.handle_data("&%s;" % ref)
+    def unknown_charref(self, ref): self.handle_data("&#%s;" % ref)
+
+
+if not HAVE_MODULE_HTMLPARSER:
+    class XHTMLCompatibleFormParser:
+        def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
+            raise ValueError("HTMLParser could not be imported")
+else:
+    class XHTMLCompatibleFormParser(_AbstractFormParser, HTMLParser.HTMLParser):
+        """Good for XHTML, bad for tolerance of incorrect HTML."""
+        # thanks to Michael Howitz for this!
+        def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
+            HTMLParser.HTMLParser.__init__(self)
+            _AbstractFormParser.__init__(self, entitydefs, encoding)
+
+        def feed(self, data):
+            try:
+                HTMLParser.HTMLParser.feed(self, data)
+            except HTMLParser.HTMLParseError, exc:
+                raise ParseError(exc)
+
+        def start_option(self, attrs):
+            _AbstractFormParser._start_option(self, attrs)
+
+        def end_option(self):
+            _AbstractFormParser._end_option(self)
+
+        def handle_starttag(self, tag, attrs):
+            try:
+                method = getattr(self, "start_" + tag)
+            except AttributeError:
+                try:
+                    method = getattr(self, "do_" + tag)
+                except AttributeError:
+                    pass  # unknown tag
+                else:
+                    method(attrs)
+            else:
+                method(attrs)
+
+        def handle_endtag(self, tag):
+            try:
+                method = getattr(self, "end_" + tag)
+            except AttributeError:
+                pass  # unknown tag
+            else:
+                method()
+
+        def unescape(self, name):
+            # Use the entitydefs passed into constructor, not
+            # HTMLParser.HTMLParser's entitydefs.
+            return self.unescape_attr(name)
+
+        def unescape_attr_if_required(self, name):
+            return name  # HTMLParser.HTMLParser already did it
+        def unescape_attrs_if_required(self, attrs):
+            return attrs  # ditto
+
+
+class _AbstractSgmllibParser(_AbstractFormParser):
+
+    def do_option(self, attrs):
+        _AbstractFormParser._start_option(self, attrs)
+
+    if sys.version_info[:2] >= (2,5):
+        # we override this attr to decode hex charrefs
+        entity_or_charref = re.compile(
+            '&(?:([a-zA-Z][-.a-zA-Z0-9]*)|#(x?[0-9a-fA-F]+))(;?)')
+        def convert_entityref(self, name):
+            return unescape("&%s;" % name, self._entitydefs, self._encoding)
+        def convert_charref(self, name):
+            return unescape_charref("%s" % name, self._encoding)
+        def unescape_attr_if_required(self, name):
+            return name  # sgmllib already did it
+        def unescape_attrs_if_required(self, attrs):
+            return attrs  # ditto
+    else:
+        def unescape_attr_if_required(self, name):
+            return self.unescape_attr(name)
+        def unescape_attrs_if_required(self, attrs):
+            return self.unescape_attrs(attrs)
+
+
+class FormParser(_AbstractSgmllibParser, sgmllib.SGMLParser):
+    """Good for tolerance of incorrect HTML, bad for XHTML."""
+    def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
+        sgmllib.SGMLParser.__init__(self)
+        _AbstractFormParser.__init__(self, entitydefs, encoding)
+
+    def feed(self, data):
+        try:
+            sgmllib.SGMLParser.feed(self, data)
+        except SGMLLIB_PARSEERROR, exc:
+            raise ParseError(exc)
+
+
+
+# sigh, must support mechanize by allowing dynamic creation of classes based on
+# its bundled copy of BeautifulSoup (which was necessary because of dependency
+# problems)
+
+def _create_bs_classes(bs,
+                       icbinbs,
+                       ):
+    class _AbstractBSFormParser(_AbstractSgmllibParser):
+        bs_base_class = None
+        def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
+            _AbstractFormParser.__init__(self, entitydefs, encoding)
+            self.bs_base_class.__init__(self)
+        def handle_data(self, data):
+            _AbstractFormParser.handle_data(self, data)
+            self.bs_base_class.handle_data(self, data)
+        def feed(self, data):
+            try:
+                self.bs_base_class.feed(self, data)
+            except SGMLLIB_PARSEERROR, exc:
+                raise ParseError(exc)
+
+
+    class RobustFormParser(_AbstractBSFormParser, bs):
+        """Tries to be highly tolerant of incorrect HTML."""
+        pass
+    RobustFormParser.bs_base_class = bs
+    class NestingRobustFormParser(_AbstractBSFormParser, icbinbs):
+        """Tries to be highly tolerant of incorrect HTML.
+
+        Different from RobustFormParser in that it more often guesses nesting
+        above missing end tags (see BeautifulSoup docs).
+
+        """
+        pass
+    NestingRobustFormParser.bs_base_class = icbinbs
+
+    return RobustFormParser, NestingRobustFormParser
+
+try:
+    if sys.version_info[:2] < (2, 2):
+        raise ImportError  # BeautifulSoup uses generators
+    import BeautifulSoup
+except ImportError:
+    pass
+else:
+    RobustFormParser, NestingRobustFormParser = _create_bs_classes(
+        BeautifulSoup.BeautifulSoup, BeautifulSoup.ICantBelieveItsBeautifulSoup
+        )
+    __all__ += ['RobustFormParser', 'NestingRobustFormParser']
+
+
+#FormParser = XHTMLCompatibleFormParser  # testing hack
+#FormParser = RobustFormParser  # testing hack
+
+
+def ParseResponseEx(response,
+                    select_default=False,
+                    form_parser_class=FormParser,
+                    request_class=urllib2.Request,
+                    entitydefs=None,
+                    encoding=DEFAULT_ENCODING,
+
+                    # private
+                    _urljoin=urlparse.urljoin,
+                    _urlparse=urlparse.urlparse,
+                    _urlunparse=urlparse.urlunparse,
+                    ):
+    """Identical to ParseResponse, except that:
+
+    1. The returned list contains an extra item.  The first form in the list
+    contains all controls not contained in any FORM element.
+
+    2. The arguments ignore_errors and backwards_compat have been removed.
+
+    3. Backwards-compatibility mode (backwards_compat=True) is not available.
+    """
+    return _ParseFileEx(response, response.geturl(),
+                        select_default,
+                        False,
+                        form_parser_class,
+                        request_class,
+                        entitydefs,
+                        False,
+                        encoding,
+                        _urljoin=_urljoin,
+                        _urlparse=_urlparse,
+                        _urlunparse=_urlunparse,
+                        )
+
+def ParseFileEx(file, base_uri,
+                select_default=False,
+                form_parser_class=FormParser,
+                request_class=urllib2.Request,
+                entitydefs=None,
+                encoding=DEFAULT_ENCODING,
+
+                # private
+                _urljoin=urlparse.urljoin,
+                _urlparse=urlparse.urlparse,
+                _urlunparse=urlparse.urlunparse,
+                ):
+    """Identical to ParseFile, except that:
+
+    1. The returned list contains an extra item.  The first form in the list
+    contains all controls not contained in any FORM element.
+
+    2. The arguments ignore_errors and backwards_compat have been removed.
+
+    3. Backwards-compatibility mode (backwards_compat=True) is not available.
+    """
+    return _ParseFileEx(file, base_uri,
+                        select_default,
+                        False,
+                        form_parser_class,
+                        request_class,
+                        entitydefs,
+                        False,
+                        encoding,
+                        _urljoin=_urljoin,
+                        _urlparse=_urlparse,
+                        _urlunparse=_urlunparse,
+                        )
+
+def ParseResponse(response, *args, **kwds):
+    """Parse HTTP response and return a list of HTMLForm instances.
+
+    The return value of urllib2.urlopen can be conveniently passed to this
+    function as the response parameter.
+
+    ClientForm.ParseError is raised on parse errors.
+
+    response: file-like object (supporting read() method) with a method
+     geturl(), returning the URI of the HTTP response
+    select_default: for multiple-selection SELECT controls and RADIO controls,
+     pick the first item as the default if none are selected in the HTML
+    form_parser_class: class to instantiate and use to pass
+    request_class: class to return from .click() method (default is
+     urllib2.Request)
+    entitydefs: mapping like {"&amp;": "&", ...} containing HTML entity
+     definitions (a sensible default is used)
+    encoding: character encoding used for encoding numeric character references
+     when matching link text.  ClientForm does not attempt to find the encoding
+     in a META HTTP-EQUIV attribute in the document itself (mechanize, for
+     example, does do that and will pass the correct value to ClientForm using
+     this parameter).
+
+    backwards_compat: boolean that determines whether the returned HTMLForm
+     objects are backwards-compatible with old code.  If backwards_compat is
+     true:
+
+     - ClientForm 0.1 code will continue to work as before.
+
+     - Label searches that do not specify a nr (number or count) will always
+       get the first match, even if other controls match.  If
+       backwards_compat is False, label searches that have ambiguous results
+       will raise an AmbiguityError.
+
+     - Item label matching is done by strict string comparison rather than
+       substring matching.
+
+     - De-selecting individual list items is allowed even if the Item is
+       disabled.
+
+    The backwards_compat argument will be deprecated in a future release.
+
+    Pass a true value for select_default if you want the behaviour specified by
+    RFC 1866 (the HTML 2.0 standard), which is to select the first item in a
+    RADIO or multiple-selection SELECT control if none were selected in the
+    HTML.  Most browsers (including Microsoft Internet Explorer (IE) and
+    Netscape Navigator) instead leave all items unselected in these cases.  The
+    W3C HTML 4.0 standard leaves this behaviour undefined in the case of
+    multiple-selection SELECT controls, but insists that at least one RADIO
+    button should be checked at all times, in contradiction to browser
+    behaviour.
+
+    There is a choice of parsers.  ClientForm.XHTMLCompatibleFormParser (uses
+    HTMLParser.HTMLParser) works best for XHTML, ClientForm.FormParser (uses
+    sgmllib.SGMLParser) (the default) works better for ordinary grubby HTML.
+    Note that HTMLParser is only available in Python 2.2 and later.  You can
+    pass your own class in here as a hack to work around bad HTML, but at your
+    own risk: there is no well-defined interface.
+
+    """
+    return _ParseFileEx(response, response.geturl(), *args, **kwds)[1:]
+
+def ParseFile(file, base_uri, *args, **kwds):
+    """Parse HTML and return a list of HTMLForm instances.
+
+    ClientForm.ParseError is raised on parse errors.
+
+    file: file-like object (supporting read() method) containing HTML with zero
+     or more forms to be parsed
+    base_uri: the URI of the document (note that the base URI used to submit
+     the form will be that given in the BASE element if present, not that of
+     the document)
+
+    For the other arguments and further details, see ParseResponse.__doc__.
+
+    """
+    return _ParseFileEx(file, base_uri, *args, **kwds)[1:]
+
+def _ParseFileEx(file, base_uri,
+                 select_default=False,
+                 ignore_errors=False,
+                 form_parser_class=FormParser,
+                 request_class=urllib2.Request,
+                 entitydefs=None,
+                 backwards_compat=True,
+                 encoding=DEFAULT_ENCODING,
+                 _urljoin=urlparse.urljoin,
+                 _urlparse=urlparse.urlparse,
+                 _urlunparse=urlparse.urlunparse,
+                 ):
+    if backwards_compat:
+        deprecation("operating in backwards-compatibility mode", 1)
+    fp = form_parser_class(entitydefs, encoding)
+    while 1:
+        data = file.read(CHUNK)
+        try:
+            fp.feed(data)
+        except ParseError, e:
+            e.base_uri = base_uri
+            raise
+        if len(data) != CHUNK: break
+    if fp.base is not None:
+        # HTML BASE element takes precedence over document URI
+        base_uri = fp.base
+    labels = []  # Label(label) for label in fp.labels]
+    id_to_labels = {}
+    for l in fp.labels:
+        label = Label(l)
+        labels.append(label)
+        for_id = l["for"]
+        coll = id_to_labels.get(for_id)
+        if coll is None:
+            id_to_labels[for_id] = [label]
+        else:
+            coll.append(label)
+    forms = []
+    for (name, action, method, enctype), attrs, controls in fp.forms:
+        if action is None:
+            action = base_uri
+        else:
+            action = _urljoin(base_uri, action)
+        # would be nice to make HTMLForm class (form builder) pluggable
+        form = HTMLForm(
+            action, method, enctype, name, attrs, request_class,
+            forms, labels, id_to_labels, backwards_compat)
+        form._urlparse = _urlparse
+        form._urlunparse = _urlunparse
+        for ii in range(len(controls)):
+            type, name, attrs = controls[ii]
+            # index=ii*10 allows ImageControl to return multiple ordered pairs
+            form.new_control(
+                type, name, attrs, select_default=select_default, index=ii*10)
+        forms.append(form)
+    for form in forms:
+        form.fixup()
+    return forms
+
+
+class Label:
+    def __init__(self, attrs):
+        self.id = attrs.get("for")
+        self._text = attrs.get("__text").strip()
+        self._ctext = compress_text(self._text)
+        self.attrs = attrs
+        self._backwards_compat = False  # maintained by HTMLForm
+
+    def __getattr__(self, name):
+        if name == "text":
+            if self._backwards_compat:
+                return self._text
+            else:
+                return self._ctext
+        return getattr(Label, name)
+
+    def __setattr__(self, name, value):
+        if name == "text":
+            # don't see any need for this, so make it read-only
+            raise AttributeError("text attribute is read-only")
+        self.__dict__[name] = value
+
+    def __str__(self):
+        return "<Label(id=%r, text=%r)>" % (self.id, self.text)
+
+
+def _get_label(attrs):
+    text = attrs.get("__label")
+    if text is not None:
+        return Label(text)
+    else:
+        return None
+
+class Control:
+    """An HTML form control.
+
+    An HTMLForm contains a sequence of Controls.  The Controls in an HTMLForm
+    are accessed using the HTMLForm.find_control method or the
+    HTMLForm.controls attribute.
+
+    Control instances are usually constructed using the ParseFile /
+    ParseResponse functions.  If you use those functions, you can ignore the
+    rest of this paragraph.  A Control is only properly initialised after the
+    fixup method has been called.  In fact, this is only strictly necessary for
+    ListControl instances.  This is necessary because ListControls are built up
+    from ListControls each containing only a single item, and their initial
+    value(s) can only be known after the sequence is complete.
+
+    The types and values that are acceptable for assignment to the value
+    attribute are defined by subclasses.
+
+    If the disabled attribute is true, this represents the state typically
+    represented by browsers by 'greying out' a control.  If the disabled
+    attribute is true, the Control will raise AttributeError if an attempt is
+    made to change its value.  In addition, the control will not be considered
+    'successful' as defined by the W3C HTML 4 standard -- ie. it will
+    contribute no data to the return value of the HTMLForm.click* methods.  To
+    enable a control, set the disabled attribute to a false value.
+
+    If the readonly attribute is true, the Control will raise AttributeError if
+    an attempt is made to change its value.  To make a control writable, set
+    the readonly attribute to a false value.
+
+    All controls have the disabled and readonly attributes, not only those that
+    may have the HTML attributes of the same names.
+
+    On assignment to the value attribute, the following exceptions are raised:
+    TypeError, AttributeError (if the value attribute should not be assigned
+    to, because the control is disabled, for example) and ValueError.
+
+    If the name or value attributes are None, or the value is an empty list, or
+    if the control is disabled, the control is not successful.
+
+    Public attributes:
+
+    type: string describing type of control (see the keys of the
+     HTMLForm.type2class dictionary for the allowable values) (readonly)
+    name: name of control (readonly)
+    value: current value of control (subclasses may allow a single value, a
+     sequence of values, or either)
+    disabled: disabled state
+    readonly: readonly state
+    id: value of id HTML attribute
+
+    """
+    def __init__(self, type, name, attrs, index=None):
+        """
+        type: string describing type of control (see the keys of the
+         HTMLForm.type2class dictionary for the allowable values)
+        name: control name
+        attrs: HTML attributes of control's HTML element
+
+        """
+        raise NotImplementedError()
+
+    def add_to_form(self, form):
+        self._form = form
+        form.controls.append(self)
+
+    def fixup(self):
+        pass
+
+    def is_of_kind(self, kind):
+        raise NotImplementedError()
+
+    def clear(self):
+        raise NotImplementedError()
+
+    def __getattr__(self, name): raise NotImplementedError()
+    def __setattr__(self, name, value): raise NotImplementedError()
+
+    def pairs(self):
+        """Return list of (key, value) pairs suitable for passing to urlencode.
+        """
+        return [(k, v) for (i, k, v) in self._totally_ordered_pairs()]
+
+    def _totally_ordered_pairs(self):
+        """Return list of (key, value, index) tuples.
+
+        Like pairs, but allows preserving correct ordering even where several
+        controls are involved.
+
+        """
+        raise NotImplementedError()
+
+    def _write_mime_data(self, mw, name, value):
+        """Write data for a subitem of this control to a MimeWriter."""
+        # called by HTMLForm
+        mw2 = mw.nextpart()
+        mw2.addheader("Content-disposition",
+                      'form-data; name="%s"' % name, 1)
+        f = mw2.startbody(prefix=0)
+        f.write(value)
+
+    def __str__(self):
+        raise NotImplementedError()
+
+    def get_labels(self):
+        """Return all labels (Label instances) for this control.
+        
+        If the control was surrounded by a <label> tag, that will be the first
+        label; all other labels, connected by 'for' and 'id', are in the order
+        that appear in the HTML.
+
+        """
+        res = []
+        if self._label:
+            res.append(self._label)
+        if self.id:
+            res.extend(self._form._id_to_labels.get(self.id, ()))
+        return res
+
+
+#---------------------------------------------------
+class ScalarControl(Control):
+    """Control whose value is not restricted to one of a prescribed set.
+
+    Some ScalarControls don't accept any value attribute.  Otherwise, takes a
+    single value, which must be string-like.
+
+    Additional read-only public attribute:
+
+    attrs: dictionary mapping the names of original HTML attributes of the
+     control to their values
+
+    """
+    def __init__(self, type, name, attrs, index=None):
+        self._index = index
+        self._label = _get_label(attrs)
+        self.__dict__["type"] = type.lower()
+        self.__dict__["name"] = name
+        self._value = attrs.get("value")
+        self.disabled = attrs.has_key("disabled")
+        self.readonly = attrs.has_key("readonly")
+        self.id = attrs.get("id")
+
+        self.attrs = attrs.copy()
+
+        self._clicked = False
+
+        self._urlparse = urlparse.urlparse
+        self._urlunparse = urlparse.urlunparse
+
+    def __getattr__(self, name):
+        if name == "value":
+            return self.__dict__["_value"]
+        else:
+            raise AttributeError("%s instance has no attribute '%s'" %
+                                 (self.__class__.__name__, name))
+
+    def __setattr__(self, name, value):
+        if name == "value":
+            if not isstringlike(value):
+                raise TypeError("must assign a string")
+            elif self.readonly:
+                raise AttributeError("control '%s' is readonly" % self.name)
+            elif self.disabled:
+                raise AttributeError("control '%s' is disabled" % self.name)
+            self.__dict__["_value"] = value
+        elif name in ("name", "type"):
+            raise AttributeError("%s attribute is readonly" % name)
+        else:
+            self.__dict__[name] = value
+
+    def _totally_ordered_pairs(self):
+        name = self.name
+        value = self.value
+        if name is None or value is None or self.disabled:
+            return []
+        return [(self._index, name, value)]
+
+    def clear(self):
+        if self.readonly:
+            raise AttributeError("control '%s' is readonly" % self.name)
+        self.__dict__["_value"] = None
+
+    def __str__(self):
+        name = self.name
+        value = self.value
+        if name is None: name = "<None>"
+        if value is None: value = "<None>"
+
+        infos = []
+        if self.disabled: infos.append("disabled")
+        if self.readonly: infos.append("readonly")
+        info = ", ".join(infos)
+        if info: info = " (%s)" % info
+
+        return "<%s(%s=%s)%s>" % (self.__class__.__name__, name, value, info)
+
+
+#---------------------------------------------------
+class TextControl(ScalarControl):
+    """Textual input control.
+
+    Covers:
+
+    INPUT/TEXT
+    INPUT/PASSWORD
+    INPUT/HIDDEN
+    TEXTAREA
+
+    """
+    def __init__(self, type, name, attrs, index=None):
+        ScalarControl.__init__(self, type, name, attrs, index)
+        if self.type == "hidden": self.readonly = True
+        if self._value is None:
+            self._value = ""
+
+    def is_of_kind(self, kind): return kind == "text"
+
+#---------------------------------------------------
+class FileControl(ScalarControl):
+    """File upload with INPUT TYPE=FILE.
+
+    The value attribute of a FileControl is always None.  Use add_file instead.
+
+    Additional public method: add_file
+
+    """
+
+    def __init__(self, type, name, attrs, index=None):
+        ScalarControl.__init__(self, type, name, attrs, index)
+        self._value = None
+        self._upload_data = []
+
+    def is_of_kind(self, kind): return kind == "file"
+
+    def clear(self):
+        if self.readonly:
+            raise AttributeError("control '%s' is readonly" % self.name)
+        self._upload_data = []
+
+    def __setattr__(self, name, value):
+        if name in ("value", "name", "type"):
+            raise AttributeError("%s attribute is readonly" % name)
+        else:
+            self.__dict__[name] = value
+
+    def add_file(self, file_object, content_type=None, filename=None):
+        if not hasattr(file_object, "read"):
+            raise TypeError("file-like object must have read method")
+        if content_type is not None and not isstringlike(content_type):
+            raise TypeError("content type must be None or string-like")
+        if filename is not None and not isstringlike(filename):
+            raise TypeError("filename must be None or string-like")
+        if content_type is None:
+            content_type = "application/octet-stream"
+        self._upload_data.append((file_object, content_type, filename))
+
+    def _totally_ordered_pairs(self):
+        # XXX should it be successful even if unnamed?
+        if self.name is None or self.disabled:
+            return []
+        return [(self._index, self.name, "")]
+
+    def _write_mime_data(self, mw, _name, _value):
+        # called by HTMLForm
+        # assert _name == self.name and _value == ''
+        if len(self._upload_data) == 1:
+            # single file
+            file_object, content_type, filename = self._upload_data[0]
+            mw2 = mw.nextpart()
+            fn_part = filename and ('; filename="%s"' % filename) or ""
+            disp = 'form-data; name="%s"%s' % (self.name, fn_part)
+            mw2.addheader("Content-disposition", disp, prefix=1)
+            fh = mw2.startbody(content_type, prefix=0)
+            fh.write(file_object.read())
+        elif len(self._upload_data) != 0:
+            # multiple files
+            mw2 = mw.nextpart()
+            disp = 'form-data; name="%s"' % self.name
+            mw2.addheader("Content-disposition", disp, prefix=1)
+            fh = mw2.startmultipartbody("mixed", prefix=0)
+            for file_object, content_type, filename in self._upload_data:
+                mw3 = mw2.nextpart()
+                fn_part = filename and ('; filename="%s"' % filename) or ""
+                disp = "file%s" % fn_part
+                mw3.addheader("Content-disposition", disp, prefix=1)
+                fh2 = mw3.startbody(content_type, prefix=0)
+                fh2.write(file_object.read())
+            mw2.lastpart()
+
+    def __str__(self):
+        name = self.name
+        if name is None: name = "<None>"
+
+        if not self._upload_data:
+            value = "<No files added>"
+        else:
+            value = []
+            for file, ctype, filename in self._upload_data:
+                if filename is None:
+                    value.append("<Unnamed file>")
+                else:
+                    value.append(filename)
+            value = ", ".join(value)
+
+        info = []
+        if self.disabled: info.append("disabled")
+        if self.readonly: info.append("readonly")
+        info = ", ".join(info)
+        if info: info = " (%s)" % info
+
+        return "<%s(%s=%s)%s>" % (self.__class__.__name__, name, value, info)
+
+
+#---------------------------------------------------
+class IsindexControl(ScalarControl):
+    """ISINDEX control.
+
+    ISINDEX is the odd-one-out of HTML form controls.  In fact, it isn't really
+    part of regular HTML forms at all, and predates it.  You're only allowed
+    one ISINDEX per HTML document.  ISINDEX and regular form submission are
+    mutually exclusive -- either submit a form, or the ISINDEX.
+
+    Having said this, since ISINDEX controls may appear in forms (which is
+    probably bad HTML), ParseFile / ParseResponse will include them in the
+    HTMLForm instances it returns.  You can set the ISINDEX's value, as with
+    any other control (but note that ISINDEX controls have no name, so you'll
+    need to use the type argument of set_value!).  When you submit the form,
+    the ISINDEX will not be successful (ie., no data will get returned to the
+    server as a result of its presence), unless you click on the ISINDEX
+    control, in which case the ISINDEX gets submitted instead of the form:
+
+    form.set_value("my isindex value", type="isindex")
+    urllib2.urlopen(form.click(type="isindex"))
+
+    ISINDEX elements outside of FORMs are ignored.  If you want to submit one
+    by hand, do it like so:
+
+    url = urlparse.urljoin(page_uri, "?"+urllib.quote_plus("my isindex value"))
+    result = urllib2.urlopen(url)
+
+    """
+    def __init__(self, type, name, attrs, index=None):
+        ScalarControl.__init__(self, type, name, attrs, index)
+        if self._value is None:
+            self._value = ""
+
+    def is_of_kind(self, kind): return kind in ["text", "clickable"]
+
+    def _totally_ordered_pairs(self):
+        return []
+
+    def _click(self, form, coord, return_type, request_class=urllib2.Request):
+        # Relative URL for ISINDEX submission: instead of "foo=bar+baz",
+        # want "bar+baz".
+        # This doesn't seem to be specified in HTML 4.01 spec. (ISINDEX is
+        # deprecated in 4.01, but it should still say how to submit it).
+        # Submission of ISINDEX is explained in the HTML 3.2 spec, though.
+        parts = self._urlparse(form.action)
+        rest, (query, frag) = parts[:-2], parts[-2:]
+        parts = rest + (urllib.quote_plus(self.value), None)
+        url = self._urlunparse(parts)
+        req_data = url, None, []
+
+        if return_type == "pairs":
+            return []
+        elif return_type == "request_data":
+            return req_data
+        else:
+            return request_class(url)
+
+    def __str__(self):
+        value = self.value
+        if value is None: value = "<None>"
+
+        infos = []
+        if self.disabled: infos.append("disabled")
+        if self.readonly: infos.append("readonly")
+        info = ", ".join(infos)
+        if info: info = " (%s)" % info
+
+        return "<%s(%s)%s>" % (self.__class__.__name__, value, info)
+
+
+#---------------------------------------------------
+class IgnoreControl(ScalarControl):
+    """Control that we're not interested in.
+
+    Covers:
+
+    INPUT/RESET
+    BUTTON/RESET
+    INPUT/BUTTON
+    BUTTON/BUTTON
+
+    These controls are always unsuccessful, in the terminology of HTML 4 (ie.
+    they never require any information to be returned to the server).
+
+    BUTTON/BUTTON is used to generate events for script embedded in HTML.
+
+    The value attribute of IgnoreControl is always None.
+
+    """
+    def __init__(self, type, name, attrs, index=None):
+        ScalarControl.__init__(self, type, name, attrs, index)
+        self._value = None
+
+    def is_of_kind(self, kind): return False
+
+    def __setattr__(self, name, value):
+        if name == "value":
+            raise AttributeError(
+                "control '%s' is ignored, hence read-only" % self.name)
+        elif name in ("name", "type"):
+            raise AttributeError("%s attribute is readonly" % name)
+        else:
+            self.__dict__[name] = value
+
+
+#---------------------------------------------------
+# ListControls
+
+# helpers and subsidiary classes
+
+class Item:
+    def __init__(self, control, attrs, index=None):
+        label = _get_label(attrs)
+        self.__dict__.update({
+            "name": attrs["value"],
+            "_labels": label and [label] or [],
+            "attrs": attrs,
+            "_control": control,
+            "disabled": attrs.has_key("disabled"),
+            "_selected": False,
+            "id": attrs.get("id"),
+            "_index": index,
+            })
+        control.items.append(self)
+
+    def get_labels(self):
+        """Return all labels (Label instances) for this item.
+        
+        For items that represent radio buttons or checkboxes, if the item was
+        surrounded by a <label> tag, that will be the first label; all other
+        labels, connected by 'for' and 'id', are in the order that appear in
+        the HTML.
+        
+        For items that represent select options, if the option had a label
+        attribute, that will be the first label.  If the option has contents
+        (text within the option tags) and it is not the same as the label
+        attribute (if any), that will be a label.  There is nothing in the
+        spec to my knowledge that makes an option with an id unable to be the
+        target of a label's for attribute, so those are included, if any, for
+        the sake of consistency and completeness.
+
+        """
+        res = []
+        res.extend(self._labels)
+        if self.id:
+            res.extend(self._control._form._id_to_labels.get(self.id, ()))
+        return res
+
+    def __getattr__(self, name):
+        if name=="selected":
+            return self._selected
+        raise AttributeError(name)
+
+    def __setattr__(self, name, value):
+        if name == "selected":
+            self._control._set_selected_state(self, value)
+        elif name == "disabled":
+            self.__dict__["disabled"] = bool(value)
+        else:
+            raise AttributeError(name)
+
+    def __str__(self):
+        res = self.name
+        if self.selected:
+            res = "*" + res
+        if self.disabled:
+            res = "(%s)" % res
+        return res
+
+    def __repr__(self):
+        # XXX appending the attrs without distinguishing them from name and id
+        # is silly
+        attrs = [("name", self.name), ("id", self.id)]+self.attrs.items()
+        return "<%s %s>" % (
+            self.__class__.__name__,
+            " ".join(["%s=%r" % (k, v) for k, v in attrs])
+            )
+
+def disambiguate(items, nr, **kwds):
+    msgs = []
+    for key, value in kwds.items():
+        msgs.append("%s=%r" % (key, value))
+    msg = " ".join(msgs)
+    if not items:
+        raise ItemNotFoundError(msg)
+    if nr is None:
+        if len(items) > 1:
+            raise AmbiguityError(msg)
+        nr = 0
+    if len(items) <= nr:
+        raise ItemNotFoundError(msg)
+    return items[nr]
+
+class ListControl(Control):
+    """Control representing a sequence of items.
+
+    The value attribute of a ListControl represents the successful list items
+    in the control.  The successful list items are those that are selected and
+    not disabled.
+
+    ListControl implements both list controls that take a length-1 value
+    (single-selection) and those that take length >1 values
+    (multiple-selection).
+
+    ListControls accept sequence values only.  Some controls only accept
+    sequences of length 0 or 1 (RADIO, and single-selection SELECT).
+    In those cases, ItemCountError is raised if len(sequence) > 1.  CHECKBOXes
+    and multiple-selection SELECTs (those having the "multiple" HTML attribute)
+    accept sequences of any length.
+
+    Note the following mistake:
+
+    control.value = some_value
+    assert control.value == some_value    # not necessarily true
+
+    The reason for this is that the value attribute always gives the list items
+    in the order they were listed in the HTML.
+
+    ListControl items can also be referred to by their labels instead of names.
+    Use the label argument to .get(), and the .set_value_by_label(),
+    .get_value_by_label() methods.
+
+    Note that, rather confusingly, though SELECT controls are represented in
+    HTML by SELECT elements (which contain OPTION elements, representing
+    individual list items), CHECKBOXes and RADIOs are not represented by *any*
+    element.  Instead, those controls are represented by a collection of INPUT
+    elements.  For example, this is a SELECT control, named "control1":
+
+    <select name="control1">
+     <option>foo</option>
+     <option value="1">bar</option>
+    </select>
+
+    and this is a CHECKBOX control, named "control2":
+
+    <input type="checkbox" name="control2" value="foo" id="cbe1">
+    <input type="checkbox" name="control2" value="bar" id="cbe2">
+
+    The id attribute of a CHECKBOX or RADIO ListControl is always that of its
+    first element (for example, "cbe1" above).
+
+
+    Additional read-only public attribute: multiple.
+
+    """
+
+    # ListControls are built up by the parser from their component items by
+    # creating one ListControl per item, consolidating them into a single
+    # master ListControl held by the HTMLForm:
+
+    # -User calls form.new_control(...)
+    # -Form creates Control, and calls control.add_to_form(self).
+    # -Control looks for a Control with the same name and type in the form,
+    #  and if it finds one, merges itself with that control by calling
+    #  control.merge_control(self).  The first Control added to the form, of
+    #  a particular name and type, is the only one that survives in the
+    #  form.
+    # -Form calls control.fixup for all its controls.  ListControls in the
+    #  form know they can now safely pick their default values.
+
+    # To create a ListControl without an HTMLForm, use:
+
+    # control.merge_control(new_control)
+
+    # (actually, it's much easier just to use ParseFile)
+
+    _label = None
+
+    def __init__(self, type, name, attrs={}, select_default=False,
+                 called_as_base_class=False, index=None):
+        """
+        select_default: for RADIO and multiple-selection SELECT controls, pick
+         the first item as the default if no 'selected' HTML attribute is
+         present
+
+        """
+        if not called_as_base_class:
+            raise NotImplementedError()
+
+        self.__dict__["type"] = type.lower()
+        self.__dict__["name"] = name
+        self._value = attrs.get("value")
+        self.disabled = False
+        self.readonly = False
+        self.id = attrs.get("id")
+        self._closed = False
+
+        # As Controls are merged in with .merge_control(), self.attrs will
+        # refer to each Control in turn -- always the most recently merged
+        # control.  Each merged-in Control instance corresponds to a single
+        # list item: see ListControl.__doc__.
+        self.items = []
+        self._form = None
+
+        self._select_default = select_default
+        self._clicked = False
+
+    def clear(self):
+        self.value = []
+
+    def is_of_kind(self, kind):
+        if kind  == "list":
+            return True
+        elif kind == "multilist":
+            return bool(self.multiple)
+        elif kind == "singlelist":
+            return not self.multiple
+        else:
+            return False
+
+    def get_items(self, name=None, label=None, id=None,
+                  exclude_disabled=False):
+        """Return matching items by name or label.
+
+        For argument docs, see the docstring for .get()
+
+        """
+        if name is not None and not isstringlike(name):
+            raise TypeError("item name must be string-like")
+        if label is not None and not isstringlike(label):
+            raise TypeError("item label must be string-like")
+        if id is not None and not isstringlike(id):
+            raise TypeError("item id must be string-like")
+        items = []  # order is important
+        compat = self._form.backwards_compat
+        for o in self.items:
+            if exclude_disabled and o.disabled:
+                continue
+            if name is not None and o.name != name:
+                continue
+            if label is not None:
+                for l in o.get_labels():
+                    if ((compat and l.text == label) or
+                        (not compat and l.text.find(label) > -1)):
+                        break
+                else:
+                    continue
+            if id is not None and o.id != id:
+                continue
+            items.append(o)
+        return items
+
+    def get(self, name=None, label=None, id=None, nr=None,
+            exclude_disabled=False):
+        """Return item by name or label, disambiguating if necessary with nr.
+
+        All arguments must be passed by name, with the exception of 'name',
+        which may be used as a positional argument.
+
+        If name is specified, then the item must have the indicated name.
+
+        If label is specified, then the item must have a label whose
+        whitespace-compressed, stripped, text substring-matches the indicated
+        label string (eg. label="please choose" will match
+        "  Do  please  choose an item ").
+
+        If id is specified, then the item must have the indicated id.
+
+        nr is an optional 0-based index of the items matching the query.
+
+        If nr is the default None value and more than item is found, raises
+        AmbiguityError (unless the HTMLForm instance's backwards_compat
+        attribute is true).
+
+        If no item is found, or if items are found but nr is specified and not
+        found, raises ItemNotFoundError.
+
+        Optionally excludes disabled items.
+
+        """
+        if nr is None and self._form.backwards_compat:
+            nr = 0  # :-/
+        items = self.get_items(name, label, id, exclude_disabled)
+        return disambiguate(items, nr, name=name, label=label, id=id)
+
+    def _get(self, name, by_label=False, nr=None, exclude_disabled=False):
+        # strictly for use by deprecated methods
+        if by_label:
+            name, label = None, name
+        else:
+            name, label = name, None
+        return self.get(name, label, nr, exclude_disabled)
+
+    def toggle(self, name, by_label=False, nr=None):
+        """Deprecated: given a name or label and optional disambiguating index
+        nr, toggle the matching item's selection.
+
+        Selecting items follows the behavior described in the docstring of the
+        'get' method.
+
+        if the item is disabled, or this control is disabled or readonly,
+        raise AttributeError.
+
+        """
+        deprecation(
+            "item = control.get(...); item.selected = not item.selected")
+        o = self._get(name, by_label, nr)
+        self._set_selected_state(o, not o.selected)
+
+    def set(self, selected, name, by_label=False, nr=None):
+        """Deprecated: given a name or label and optional disambiguating index
+        nr, set the matching item's selection to the bool value of selected.
+
+        Selecting items follows the behavior described in the docstring of the
+        'get' method.
+
+        if the item is disabled, or this control is disabled or readonly,
+        raise AttributeError.
+
+        """
+        deprecation(
+            "control.get(...).selected = <boolean>")
+        self._set_selected_state(self._get(name, by_label, nr), selected)
+
+    def _set_selected_state(self, item, action):
+        # action:
+        # bool False: off
+        # bool True: on
+        if self.disabled:
+            raise AttributeError("control '%s' is disabled" % self.name)
+        if self.readonly:
+            raise AttributeError("control '%s' is readonly" % self.name)
+        action == bool(action)
+        compat = self._form.backwards_compat
+        if not compat and item.disabled:
+            raise AttributeError("item is disabled")
+        else:
+            if compat and item.disabled and action:
+                raise AttributeError("item is disabled")
+            if self.multiple:
+                item.__dict__["_selected"] = action
+            else:
+                if not action:
+                    item.__dict__["_selected"] = False
+                else:
+                    for o in self.items:
+                        o.__dict__["_selected"] = False
+                    item.__dict__["_selected"] = True
+
+    def toggle_single(self, by_label=None):
+        """Deprecated: toggle the selection of the single item in this control.
+        
+        Raises ItemCountError if the control does not contain only one item.
+        
+        by_label argument is ignored, and included only for backwards
+        compatibility.
+
+        """
+        deprecation(
+            "control.items[0].selected = not control.items[0].selected")
+        if len(self.items) != 1:
+            raise ItemCountError(
+                "'%s' is not a single-item control" % self.name)
+        item = self.items[0]
+        self._set_selected_state(item, not item.selected)
+
+    def set_single(self, selected, by_label=None):
+        """Deprecated: set the selection of the single item in this control.
+        
+        Raises ItemCountError if the control does not contain only one item.
+        
+        by_label argument is ignored, and included only for backwards
+        compatibility.
+
+        """
+        deprecation(
+            "control.items[0].selected = <boolean>")
+        if len(self.items) != 1:
+            raise ItemCountError(
+                "'%s' is not a single-item control" % self.name)
+        self._set_selected_state(self.items[0], selected)
+
+    def get_item_disabled(self, name, by_label=False, nr=None):
+        """Get disabled state of named list item in a ListControl."""
+        deprecation(
+            "control.get(...).disabled")
+        return self._get(name, by_label, nr).disabled
+
+    def set_item_disabled(self, disabled, name, by_label=False, nr=None):
+        """Set disabled state of named list item in a ListControl.
+
+        disabled: boolean disabled state
+
+        """
+        deprecation(
+            "control.get(...).disabled = <boolean>")
+        self._get(name, by_label, nr).disabled = disabled
+
+    def set_all_items_disabled(self, disabled):
+        """Set disabled state of all list items in a ListControl.
+
+        disabled: boolean disabled state
+
+        """
+        for o in self.items:
+            o.disabled = disabled
+
+    def get_item_attrs(self, name, by_label=False, nr=None):
+        """Return dictionary of HTML attributes for a single ListControl item.
+
+        The HTML element types that describe list items are: OPTION for SELECT
+        controls, INPUT for the rest.  These elements have HTML attributes that
+        you may occasionally want to know about -- for example, the "alt" HTML
+        attribute gives a text string describing the item (graphical browsers
+        usually display this as a tooltip).
+
+        The returned dictionary maps HTML attribute names to values.  The names
+        and values are taken from the original HTML.
+
+        """
+        deprecation(
+            "control.get(...).attrs")
+        return self._get(name, by_label, nr).attrs
+
+    def close_control(self):
+        self._closed = True
+
+    def add_to_form(self, form):
+        assert self._form is None or form == self._form, (
+            "can't add control to more than one form")
+        self._form = form
+        if self.name is None:
+            # always count nameless elements as separate controls
+            Control.add_to_form(self, form)
+        else:
+            for ii in range(len(form.controls)-1, -1, -1):
+                control = form.controls[ii]
+                if control.name == self.name and control.type == self.type:
+                    if control._closed:
+                        Control.add_to_form(self, form)
+                    else:
+                        control.merge_control(self)
+                    break
+            else:
+                Control.add_to_form(self, form)
+
+    def merge_control(self, control):
+        assert bool(control.multiple) == bool(self.multiple)
+        # usually, isinstance(control, self.__class__)
+        self.items.extend(control.items)
+
+    def fixup(self):
+        """
+        ListControls are built up from component list items (which are also
+        ListControls) during parsing.  This method should be called after all
+        items have been added.  See ListControl.__doc__ for the reason this is
+        required.
+
+        """
+        # Need to set default selection where no item was indicated as being
+        # selected by the HTML:
+
+        # CHECKBOX:
+        #  Nothing should be selected.
+        # SELECT/single, SELECT/multiple and RADIO:
+        #  RFC 1866 (HTML 2.0): says first item should be selected.
+        #  W3C HTML 4.01 Specification: says that client behaviour is
+        #   undefined in this case.  For RADIO, exactly one must be selected,
+        #   though which one is undefined.
+        #  Both Netscape and Microsoft Internet Explorer (IE) choose first
+        #   item for SELECT/single.  However, both IE5 and Mozilla (both 1.0
+        #   and Firebird 0.6) leave all items unselected for RADIO and
+        #   SELECT/multiple.
+
+        # Since both Netscape and IE all choose the first item for
+        # SELECT/single, we do the same.  OTOH, both Netscape and IE
+        # leave SELECT/multiple with nothing selected, in violation of RFC 1866
+        # (but not in violation of the W3C HTML 4 standard); the same is true
+        # of RADIO (which *is* in violation of the HTML 4 standard).  We follow
+        # RFC 1866 if the _select_default attribute is set, and Netscape and IE
+        # otherwise.  RFC 1866 and HTML 4 are always violated insofar as you
+        # can deselect all items in a RadioControl.
+        
+        for o in self.items: 
+            # set items' controls to self, now that we've merged
+            o.__dict__["_control"] = self
+
+    def __getattr__(self, name):
+        if name == "value":
+            compat = self._form.backwards_compat
+            if self.name is None:
+                return []
+            return [o.name for o in self.items if o.selected and
+                    (not o.disabled or compat)]
+        else:
+            raise AttributeError("%s instance has no attribute '%s'" %
+                                 (self.__class__.__name__, name))
+
+    def __setattr__(self, name, value):
+        if name == "value":
+            if self.disabled:
+                raise AttributeError("control '%s' is disabled" % self.name)
+            if self.readonly:
+                raise AttributeError("control '%s' is readonly" % self.name)
+            self._set_value(value)
+        elif name in ("name", "type", "multiple"):
+            raise AttributeError("%s attribute is readonly" % name)
+        else:
+            self.__dict__[name] = value
+
+    def _set_value(self, value):
+        if value is None or isstringlike(value):
+            raise TypeError("ListControl, must set a sequence")
+        if not value:
+            compat = self._form.backwards_compat
+            for o in self.items:
+                if not o.disabled or compat:
+                    o.selected = False
+        elif self.multiple:
+            self._multiple_set_value(value)
+        elif len(value) > 1:
+            raise ItemCountError(
+                "single selection list, must set sequence of "
+                "length 0 or 1")
+        else:
+            self._single_set_value(value)
+
+    def _get_items(self, name, target=1):
+        all_items = self.get_items(name)
+        items = [o for o in all_items if not o.disabled]
+        if len(items) < target:
+            if len(all_items) < target:
+                raise ItemNotFoundError(
+                    "insufficient items with name %r" % name)
+            else:
+                raise AttributeError(
+                    "insufficient non-disabled items with name %s" % name)
+        on = []
+        off = []
+        for o in items:
+            if o.selected:
+                on.append(o)
+            else:
+                off.append(o)
+        return on, off
+
+    def _single_set_value(self, value):
+        assert len(value) == 1
+        on, off = self._get_items(value[0])
+        assert len(on) <= 1
+        if not on:
+            off[0].selected = True
+
+    def _multiple_set_value(self, value):
+        compat = self._form.backwards_compat
+        turn_on = []  # transactional-ish
+        turn_off = [item for item in self.items if
+                    item.selected and (not item.disabled or compat)]
+        names = {}
+        for nn in value:
+            if nn in names.keys():
+                names[nn] += 1
+            else:
+                names[nn] = 1
+        for name, count in names.items():
+            on, off = self._get_items(name, count)
+            for i in range(count):
+                if on:
+                    item = on[0]
+                    del on[0]
+                    del turn_off[turn_off.index(item)]
+                else:
+                    item = off[0]
+                    del off[0]
+                    turn_on.append(item)
+        for item in turn_off:
+            item.selected = False
+        for item in turn_on:
+            item.selected = True
+
+    def set_value_by_label(self, value):
+        """Set the value of control by item labels.
+
+        value is expected to be an iterable of strings that are substrings of
+        the item labels that should be selected.  Before substring matching is
+        performed, the original label text is whitespace-compressed
+        (consecutive whitespace characters are converted to a single space
+        character) and leading and trailing whitespace is stripped.  Ambiguous
+        labels are accepted without complaint if the form's backwards_compat is
+        True; otherwise, it will not complain as long as all ambiguous labels
+        share the same item name (e.g. OPTION value).
+
+        """
+        if isstringlike(value):
+            raise TypeError(value)
+        if not self.multiple and len(value) > 1:
+            raise ItemCountError(
+                "single selection list, must set sequence of "
+                "length 0 or 1")
+        items = []
+        for nn in value:
+            found = self.get_items(label=nn)
+            if len(found) > 1:
+                if not self._form.backwards_compat:
+                    # ambiguous labels are fine as long as item names (e.g.
+                    # OPTION values) are same
+                    opt_name = found[0].name
+                    if [o for o in found[1:] if o.name != opt_name]:
+                        raise AmbiguityError(nn)
+                else:
+                    # OK, we'll guess :-(  Assume first available item.
+                    found = found[:1]
+            for o in found:
+                # For the multiple-item case, we could try to be smarter,
+                # saving them up and trying to resolve, but that's too much.
+                if self._form.backwards_compat or o not in items:
+                    items.append(o)
+                    break
+            else:  # all of them are used
+                raise ItemNotFoundError(nn)
+        # now we have all the items that should be on
+        # let's just turn everything off and then back on.
+        self.value = []
+        for o in items:
+            o.selected = True
+
+    def get_value_by_label(self):
+        """Return the value of the control as given by normalized labels."""
+        res = []
+        compat = self._form.backwards_compat
+        for o in self.items:
+            if (not o.disabled or compat) and o.selected:
+                for l in o.get_labels():
+                    if l.text:
+                        res.append(l.text)
+                        break
+                else:
+                    res.append(None)
+        return res
+
+    def possible_items(self, by_label=False):
+        """Deprecated: return the names or labels of all possible items.
+
+        Includes disabled items, which may be misleading for some use cases.
+
+        """
+        deprecation(
+            "[item.name for item in self.items]")
+        if by_label:
+            res = []
+            for o in self.items:
+                for l in o.get_labels():
+                    if l.text:
+                        res.append(l.text)
+                        break
+                else:
+                    res.append(None)
+            return res
+        return [o.name for o in self.items]
+
+    def _totally_ordered_pairs(self):
+        if self.disabled or self.name is None:
+            return []
+        else:
+            return [(o._index, self.name, o.name) for o in self.items
+                    if o.selected and not o.disabled]
+
+    def __str__(self):
+        name = self.name
+        if name is None: name = "<None>"
+
+        display = [str(o) for o in self.items]
+
+        infos = []
+        if self.disabled: infos.append("disabled")
+        if self.readonly: infos.append("readonly")
+        info = ", ".join(infos)
+        if info: info = " (%s)" % info
+
+        return "<%s(%s=[%s])%s>" % (self.__class__.__name__,
+                                    name, ", ".join(display), info)
+
+
+class RadioControl(ListControl):
+    """
+    Covers:
+
+    INPUT/RADIO
+
+    """
+    def __init__(self, type, name, attrs, select_default=False, index=None):
+        attrs.setdefault("value", "on")
+        ListControl.__init__(self, type, name, attrs, select_default,
+                             called_as_base_class=True, index=index)
+        self.__dict__["multiple"] = False
+        o = Item(self, attrs, index)
+        o.__dict__["_selected"] = attrs.has_key("checked")
+
+    def fixup(self):
+        ListControl.fixup(self)
+        found = [o for o in self.items if o.selected and not o.disabled]
+        if not found:
+            if self._select_default:
+                for o in self.items:
+                    if not o.disabled:
+                        o.selected = True
+                        break
+        else:
+            # Ensure only one item selected.  Choose the last one,
+            # following IE and Firefox.
+            for o in found[:-1]:
+                o.selected = False
+
+    def get_labels(self):
+        return []
+
+class CheckboxControl(ListControl):
+    """
+    Covers:
+
+    INPUT/CHECKBOX
+
+    """
+    def __init__(self, type, name, attrs, select_default=False, index=None):
+        attrs.setdefault("value", "on")
+        ListControl.__init__(self, type, name, attrs, select_default,
+                             called_as_base_class=True, index=index)
+        self.__dict__["multiple"] = True
+        o = Item(self, attrs, index)
+        o.__dict__["_selected"] = attrs.has_key("checked")
+
+    def get_labels(self):
+        return []
+
+
+class SelectControl(ListControl):
+    """
+    Covers:
+
+    SELECT (and OPTION)
+
+
+    OPTION 'values', in HTML parlance, are Item 'names' in ClientForm parlance.
+
+    SELECT control values and labels are subject to some messy defaulting
+    rules.  For example, if the HTML representation of the control is:
+
+    <SELECT name=year>
+      <OPTION value=0 label="2002">current year</OPTION>
+      <OPTION value=1>2001</OPTION>
+      <OPTION>2000</OPTION>
+    </SELECT>
+
+    The items, in order, have labels "2002", "2001" and "2000", whereas their
+    names (the OPTION values) are "0", "1" and "2000" respectively.  Note that
+    the value of the last OPTION in this example defaults to its contents, as
+    specified by RFC 1866, as do the labels of the second and third OPTIONs.
+
+    The OPTION labels are sometimes more meaningful than the OPTION values,
+    which can make for more maintainable code.
+
+    Additional read-only public attribute: attrs
+
+    The attrs attribute is a dictionary of the original HTML attributes of the
+    SELECT element.  Other ListControls do not have this attribute, because in
+    other cases the control as a whole does not correspond to any single HTML
+    element.  control.get(...).attrs may be used as usual to get at the HTML
+    attributes of the HTML elements corresponding to individual list items (for
+    SELECT controls, these are OPTION elements).
+
+    Another special case is that the Item.attrs dictionaries have a special key
+    "contents" which does not correspond to any real HTML attribute, but rather
+    contains the contents of the OPTION element:
+
+    <OPTION>this bit</OPTION>
+
+    """
+    # HTML attributes here are treated slightly differently from other list
+    # controls:
+    # -The SELECT HTML attributes dictionary is stuffed into the OPTION
+    #  HTML attributes dictionary under the "__select" key.
+    # -The content of each OPTION element is stored under the special
+    #  "contents" key of the dictionary.
+    # After all this, the dictionary is passed to the SelectControl constructor
+    # as the attrs argument, as usual.  However:
+    # -The first SelectControl constructed when building up a SELECT control
+    #  has a constructor attrs argument containing only the __select key -- so
+    #  this SelectControl represents an empty SELECT control.
+    # -Subsequent SelectControls have both OPTION HTML-attribute in attrs and
+    #  the __select dictionary containing the SELECT HTML-attributes.
+
+    def __init__(self, type, name, attrs, select_default=False, index=None):
+        # fish out the SELECT HTML attributes from the OPTION HTML attributes
+        # dictionary
+        self.attrs = attrs["__select"].copy()
+        self.__dict__["_label"] = _get_label(self.attrs)
+        self.__dict__["id"] = self.attrs.get("id")
+        self.__dict__["multiple"] = self.attrs.has_key("multiple")
+        # the majority of the contents, label, and value dance already happened
+        contents = attrs.get("contents")
+        attrs = attrs.copy()
+        del attrs["__select"]
+
+        ListControl.__init__(self, type, name, self.attrs, select_default,
+                             called_as_base_class=True, index=index)
+        self.disabled = self.attrs.has_key("disabled")
+        self.readonly = self.attrs.has_key("readonly")
+        if attrs.has_key("value"):
+            # otherwise it is a marker 'select started' token
+            o = Item(self, attrs, index)
+            o.__dict__["_selected"] = attrs.has_key("selected")
+            # add 'label' label and contents label, if different.  If both are
+            # provided, the 'label' label is used for display in HTML 
+            # 4.0-compliant browsers (and any lower spec? not sure) while the
+            # contents are used for display in older or less-compliant
+            # browsers.  We make label objects for both, if the values are
+            # different.
+            label = attrs.get("label")
+            if label:
+                o._labels.append(Label({"__text": label}))
+                if contents and contents != label:
+                    o._labels.append(Label({"__text": contents}))
+            elif contents:
+                o._labels.append(Label({"__text": contents}))
+
+    def fixup(self):
+        ListControl.fixup(self)
+        # Firefox doesn't exclude disabled items from those considered here
+        # (i.e. from 'found', for both branches of the if below).  Note that
+        # IE6 doesn't support the disabled attribute on OPTIONs at all.
+        found = [o for o in self.items if o.selected]
+        if not found:
+            if not self.multiple or self._select_default:
+                for o in self.items:
+                    if not o.disabled:
+                        was_disabled = self.disabled
+                        self.disabled = False
+                        try:
+                            o.selected = True
+                        finally:
+                            o.disabled = was_disabled
+                        break
+        elif not self.multiple:
+            # Ensure only one item selected.  Choose the last one,
+            # following IE and Firefox.
+            for o in found[:-1]:
+                o.selected = False
+
+
+#---------------------------------------------------
+class SubmitControl(ScalarControl):
+    """
+    Covers:
+
+    INPUT/SUBMIT
+    BUTTON/SUBMIT
+
+    """
+    def __init__(self, type, name, attrs, index=None):
+        ScalarControl.__init__(self, type, name, attrs, index)
+        # IE5 defaults SUBMIT value to "Submit Query"; Firebird 0.6 leaves it
+        # blank, Konqueror 3.1 defaults to "Submit".  HTML spec. doesn't seem
+        # to define this.
+        if self.value is None: self.value = ""
+        self.readonly = True
+
+    def get_labels(self):
+        res = []
+        if self.value:
+            res.append(Label({"__text": self.value}))
+        res.extend(ScalarControl.get_labels(self))
+        return res
+
+    def is_of_kind(self, kind): return kind == "clickable"
+
+    def _click(self, form, coord, return_type, request_class=urllib2.Request):
+        self._clicked = coord
+        r = form._switch_click(return_type, request_class)
+        self._clicked = False
+        return r
+
+    def _totally_ordered_pairs(self):
+        if not self._clicked:
+            return []
+        return ScalarControl._totally_ordered_pairs(self)
+
+
+#---------------------------------------------------
+class ImageControl(SubmitControl):
+    """
+    Covers:
+
+    INPUT/IMAGE
+
+    Coordinates are specified using one of the HTMLForm.click* methods.
+
+    """
+    def __init__(self, type, name, attrs, index=None):
+        SubmitControl.__init__(self, type, name, attrs, index)
+        self.readonly = False
+
+    def _totally_ordered_pairs(self):
+        clicked = self._clicked
+        if self.disabled or not clicked:
+            return []
+        name = self.name
+        if name is None: return []
+        pairs = [
+            (self._index, "%s.x" % name, str(clicked[0])),
+            (self._index+1, "%s.y" % name, str(clicked[1])),
+            ]
+        value = self._value
+        if value:
+            pairs.append((self._index+2, name, value))
+        return pairs
+
+    get_labels = ScalarControl.get_labels
+
+# aliases, just to make str(control) and str(form) clearer
+class PasswordControl(TextControl): pass
+class HiddenControl(TextControl): pass
+class TextareaControl(TextControl): pass
+class SubmitButtonControl(SubmitControl): pass
+
+
+def is_listcontrol(control): return control.is_of_kind("list")
+
+
+class HTMLForm:
+    """Represents a single HTML <form> ... </form> element.
+
+    A form consists of a sequence of controls that usually have names, and
+    which can take on various values.  The values of the various types of
+    controls represent variously: text, zero-or-one-of-many or many-of-many
+    choices, and files to be uploaded.  Some controls can be clicked on to
+    submit the form, and clickable controls' values sometimes include the
+    coordinates of the click.
+
+    Forms can be filled in with data to be returned to the server, and then
+    submitted, using the click method to generate a request object suitable for
+    passing to urllib2.urlopen (or the click_request_data or click_pairs
+    methods if you're not using urllib2).
+
+    import ClientForm
+    forms = ClientForm.ParseFile(html, base_uri)
+    form = forms[0]
+
+    form["query"] = "Python"
+    form.find_control("nr_results").get("lots").selected = True
+
+    response = urllib2.urlopen(form.click())
+
+    Usually, HTMLForm instances are not created directly.  Instead, the
+    ParseFile or ParseResponse factory functions are used.  If you do construct
+    HTMLForm objects yourself, however, note that an HTMLForm instance is only
+    properly initialised after the fixup method has been called (ParseFile and
+    ParseResponse do this for you).  See ListControl.__doc__ for the reason
+    this is required.
+
+    Indexing a form (form["control_name"]) returns the named Control's value
+    attribute.  Assignment to a form index (form["control_name"] = something)
+    is equivalent to assignment to the named Control's value attribute.  If you
+    need to be more specific than just supplying the control's name, use the
+    set_value and get_value methods.
+
+    ListControl values are lists of item names (specifically, the names of the
+    items that are selected and not disabled, and hence are "successful" -- ie.
+    cause data to be returned to the server).  The list item's name is the
+    value of the corresponding HTML element's"value" attribute.
+
+    Example:
+
+      <INPUT type="CHECKBOX" name="cheeses" value="leicester"></INPUT>
+      <INPUT type="CHECKBOX" name="cheeses" value="cheddar"></INPUT>
+
+    defines a CHECKBOX control with name "cheeses" which has two items, named
+    "leicester" and "cheddar".
+
+    Another example:
+
+      <SELECT name="more_cheeses">
+        <OPTION>1</OPTION>
+        <OPTION value="2" label="CHEDDAR">cheddar</OPTION>
+      </SELECT>
+
+    defines a SELECT control with name "more_cheeses" which has two items,
+    named "1" and "2" (because the OPTION element's value HTML attribute
+    defaults to the element contents -- see SelectControl.__doc__ for more on
+    these defaulting rules).
+
+    To select, deselect or otherwise manipulate individual list items, use the
+    HTMLForm.find_control() and ListControl.get() methods.  To set the whole
+    value, do as for any other control: use indexing or the set_/get_value
+    methods.
+
+    Example:
+
+    # select *only* the item named "cheddar"
+    form["cheeses"] = ["cheddar"]
+    # select "cheddar", leave other items unaffected
+    form.find_control("cheeses").get("cheddar").selected = True
+
+    Some controls (RADIO and SELECT without the multiple attribute) can only
+    have zero or one items selected at a time.  Some controls (CHECKBOX and
+    SELECT with the multiple attribute) can have multiple items selected at a
+    time.  To set the whole value of a ListControl, assign a sequence to a form
+    index:
+
+    form["cheeses"] = ["cheddar", "leicester"]
+
+    If the ListControl is not multiple-selection, the assigned list must be of
+    length one.
+
+    To check if a control has an item, if an item is selected, or if an item is
+    successful (selected and not disabled), respectively:
+
+    "cheddar" in [item.name for item in form.find_control("cheeses").items]
+    "cheddar" in [item.name for item in form.find_control("cheeses").items and
+                  item.selected]
+    "cheddar" in form["cheeses"]  # (or "cheddar" in form.get_value("cheeses"))
+
+    Note that some list items may be disabled (see below).
+
+    Note the following mistake:
+
+    form[control_name] = control_value
+    assert form[control_name] == control_value  # not necessarily true
+
+    The reason for this is that form[control_name] always gives the list items
+    in the order they were listed in the HTML.
+
+    List items (hence list values, too) can be referred to in terms of list
+    item labels rather than list item names using the appropriate label
+    arguments.  Note that each item may have several labels.
+
+    The question of default values of OPTION contents, labels and values is
+    somewhat complicated: see SelectControl.__doc__ and
+    ListControl.get_item_attrs.__doc__ if you think you need to know.
+
+    Controls can be disabled or readonly.  In either case, the control's value
+    cannot be changed until you clear those flags (see example below).
+    Disabled is the state typically represented by browsers by 'greying out' a
+    control.  Disabled controls are not 'successful' -- they don't cause data
+    to get returned to the server.  Readonly controls usually appear in
+    browsers as read-only text boxes.  Readonly controls are successful.  List
+    items can also be disabled.  Attempts to select or deselect disabled items
+    fail with AttributeError.
+
+    If a lot of controls are readonly, it can be useful to do this:
+
+    form.set_all_readonly(False)
+
+    To clear a control's value attribute, so that it is not successful (until a
+    value is subsequently set):
+
+    form.clear("cheeses")
+
+    More examples:
+
+    control = form.find_control("cheeses")
+    control.disabled = False
+    control.readonly = False
+    control.get("gruyere").disabled = True
+    control.items[0].selected = True
+
+    See the various Control classes for further documentation.  Many methods
+    take name, type, kind, id, label and nr arguments to specify the control to
+    be operated on: see HTMLForm.find_control.__doc__.
+
+    ControlNotFoundError (subclass of ValueError) is raised if the specified
+    control can't be found.  This includes occasions where a non-ListControl
+    is found, but the method (set, for example) requires a ListControl.
+    ItemNotFoundError (subclass of ValueError) is raised if a list item can't
+    be found.  ItemCountError (subclass of ValueError) is raised if an attempt
+    is made to select more than one item and the control doesn't allow that, or
+    set/get_single are called and the control contains more than one item.
+    AttributeError is raised if a control or item is readonly or disabled and
+    an attempt is made to alter its value.
+
+    Security note: Remember that any passwords you store in HTMLForm instances
+    will be saved to disk in the clear if you pickle them (directly or
+    indirectly).  The simplest solution to this is to avoid pickling HTMLForm
+    objects.  You could also pickle before filling in any password, or just set
+    the password to "" before pickling.
+
+
+    Public attributes:
+
+    action: full (absolute URI) form action
+    method: "GET" or "POST"
+    enctype: form transfer encoding MIME type
+    name: name of form (None if no name was specified)
+    attrs: dictionary mapping original HTML form attributes to their values
+
+    controls: list of Control instances; do not alter this list
+     (instead, call form.new_control to make a Control and add it to the
+     form, or control.add_to_form if you already have a Control instance)
+
+
+
+    Methods for form filling:
+    -------------------------
+
+    Most of the these methods have very similar arguments.  See
+    HTMLForm.find_control.__doc__ for details of the name, type, kind, label
+    and nr arguments.
+
+    def find_control(self,
+                     name=None, type=None, kind=None, id=None, predicate=None,
+                     nr=None, label=None)
+
+    get_value(name=None, type=None, kind=None, id=None, nr=None,
+              by_label=False,  # by_label is deprecated
+              label=None)
+    set_value(value,
+              name=None, type=None, kind=None, id=None, nr=None,
+              by_label=False,  # by_label is deprecated
+              label=None)
+
+    clear_all()
+    clear(name=None, type=None, kind=None, id=None, nr=None, label=None)
+
+    set_all_readonly(readonly)
+
+
+    Method applying only to FileControls:
+
+    add_file(file_object,
+             content_type="application/octet-stream", filename=None,
+             name=None, id=None, nr=None, label=None)
+
+
+    Methods applying only to clickable controls:
+
+    click(name=None, type=None, id=None, nr=0, coord=(1,1), label=None)
+    click_request_data(name=None, type=None, id=None, nr=0, coord=(1,1),
+                       label=None)
+    click_pairs(name=None, type=None, id=None, nr=0, coord=(1,1), label=None)
+
+    """
+
+    type2class = {
+        "text": TextControl,
+        "password": PasswordControl,
+        "hidden": HiddenControl,
+        "textarea": TextareaControl,
+
+        "isindex": IsindexControl,
+
+        "file": FileControl,
+
+        "button": IgnoreControl,
+        "buttonbutton": IgnoreControl,
+        "reset": IgnoreControl,
+        "resetbutton": IgnoreControl,
+
+        "submit": SubmitControl,
+        "submitbutton": SubmitButtonControl,
+        "image": ImageControl,
+
+        "radio": RadioControl,
+        "checkbox": CheckboxControl,
+        "select": SelectControl,
+        }
+
+#---------------------------------------------------
+# Initialisation.  Use ParseResponse / ParseFile instead.
+
+    def __init__(self, action, method="GET",
+                 enctype="application/x-www-form-urlencoded",
+                 name=None, attrs=None,
+                 request_class=urllib2.Request,
+                 forms=None, labels=None, id_to_labels=None,
+                 backwards_compat=True):
+        """
+        In the usual case, use ParseResponse (or ParseFile) to create new
+        HTMLForm objects.
+
+        action: full (absolute URI) form action
+        method: "GET" or "POST"
+        enctype: form transfer encoding MIME type
+        name: name of form
+        attrs: dictionary mapping original HTML form attributes to their values
+
+        """
+        self.action = action
+        self.method = method
+        self.enctype = enctype
+        self.name = name
+        if attrs is not None:
+            self.attrs = attrs.copy()
+        else:
+            self.attrs = {}
+        self.controls = []
+        self._request_class = request_class
+
+        # these attributes are used by zope.testbrowser
+        self._forms = forms  # this is a semi-public API!
+        self._labels = labels  # this is a semi-public API!
+        self._id_to_labels = id_to_labels  # this is a semi-public API!
+
+        self.backwards_compat = backwards_compat  # note __setattr__
+
+        self._urlunparse = urlparse.urlunparse
+        self._urlparse = urlparse.urlparse
+
+    def __getattr__(self, name):
+        if name == "backwards_compat":
+            return self._backwards_compat
+        return getattr(HTMLForm, name)
+
+    def __setattr__(self, name, value):
+        # yuck
+        if name == "backwards_compat":
+            name = "_backwards_compat"
+            value = bool(value)
+            for cc in self.controls:
+                try:
+                    items = cc.items 
+                except AttributeError:
+                    continue
+                else:
+                    for ii in items:
+                        for ll in ii.get_labels():
+                            ll._backwards_compat = value
+        self.__dict__[name] = value
+
+    def new_control(self, type, name, attrs,
+                    ignore_unknown=False, select_default=False, index=None):
+        """Adds a new control to the form.
+
+        This is usually called by ParseFile and ParseResponse.  Don't call it
+        youself unless you're building your own Control instances.
+
+        Note that controls representing lists of items are built up from
+        controls holding only a single list item.  See ListControl.__doc__ for
+        further information.
+
+        type: type of control (see Control.__doc__ for a list)
+        attrs: HTML attributes of control
+        ignore_unknown: if true, use a dummy Control instance for controls of
+         unknown type; otherwise, use a TextControl
+        select_default: for RADIO and multiple-selection SELECT controls, pick
+         the first item as the default if no 'selected' HTML attribute is
+         present (this defaulting happens when the HTMLForm.fixup method is
+         called)
+        index: index of corresponding element in HTML (see
+         MoreFormTests.test_interspersed_controls for motivation)
+
+        """
+        type = type.lower()
+        klass = self.type2class.get(type)
+        if klass is None:
+            if ignore_unknown:
+                klass = IgnoreControl
+            else:
+                klass = TextControl
+
+        a = attrs.copy()
+        if issubclass(klass, ListControl):
+            control = klass(type, name, a, select_default, index)
+        else:
+            control = klass(type, name, a, index)
+
+        if type == "select" and len(attrs) == 1:
+            for ii in range(len(self.controls)-1, -1, -1):
+                ctl = self.controls[ii]
+                if ctl.type == "select":
+                    ctl.close_control()
+                    break
+
+        control.add_to_form(self)
+        control._urlparse = self._urlparse
+        control._urlunparse = self._urlunparse
+
+    def fixup(self):
+        """Normalise form after all controls have been added.
+
+        This is usually called by ParseFile and ParseResponse.  Don't call it
+        youself unless you're building your own Control instances.
+
+        This method should only be called once, after all controls have been
+        added to the form.
+
+        """
+        for control in self.controls:
+            control.fixup()
+        self.backwards_compat = self._backwards_compat
+
+#---------------------------------------------------
+    def __str__(self):
+        header = "%s%s %s %s" % (
+            (self.name and self.name+" " or ""),
+            self.method, self.action, self.enctype)
+        rep = [header]
+        for control in self.controls:
+            rep.append("  %s" % str(control))
+        return "<%s>" % "\n".join(rep)
+
+#---------------------------------------------------
+# Form-filling methods.
+
+    def __getitem__(self, name):
+        return self.find_control(name).value
+    def __contains__(self, name):
+        return bool(self.find_control(name))
+    def __setitem__(self, name, value):
+        control = self.find_control(name)
+        try:
+            control.value = value
+        except AttributeError, e:
+            raise ValueError(str(e))
+
+    def get_value(self,
+                  name=None, type=None, kind=None, id=None, nr=None,
+                  by_label=False,  # by_label is deprecated
+                  label=None):
+        """Return value of control.
+
+        If only name and value arguments are supplied, equivalent to
+
+        form[name]
+
+        """
+        if by_label:
+            deprecation("form.get_value_by_label(...)")
+        c = self.find_control(name, type, kind, id, label=label, nr=nr)
+        if by_label:
+            try:
+                meth = c.get_value_by_label
+            except AttributeError:
+                raise NotImplementedError(
+                    "control '%s' does not yet support by_label" % c.name)
+            else:
+                return meth()
+        else:
+            return c.value
+    def set_value(self, value,
+                  name=None, type=None, kind=None, id=None, nr=None,
+                  by_label=False,  # by_label is deprecated
+                  label=None):
+        """Set value of control.
+
+        If only name and value arguments are supplied, equivalent to
+
+        form[name] = value
+
+        """
+        if by_label:
+            deprecation("form.get_value_by_label(...)")
+        c = self.find_control(name, type, kind, id, label=label, nr=nr)
+        if by_label:
+            try:
+                meth = c.set_value_by_label
+            except AttributeError:
+                raise NotImplementedError(
+                    "control '%s' does not yet support by_label" % c.name)
+            else:
+                meth(value)
+        else:
+            c.value = value
+    def get_value_by_label(
+        self, name=None, type=None, kind=None, id=None, label=None, nr=None):
+        """
+
+        All arguments should be passed by name.
+
+        """
+        c = self.find_control(name, type, kind, id, label=label, nr=nr)
+        return c.get_value_by_label()
+
+    def set_value_by_label(
+        self, value,
+        name=None, type=None, kind=None, id=None, label=None, nr=None):
+        """
+
+        All arguments should be passed by name.
+
+        """
+        c = self.find_control(name, type, kind, id, label=label, nr=nr)
+        c.set_value_by_label(value)
+
+    def set_all_readonly(self, readonly):
+        for control in self.controls:
+            control.readonly = bool(readonly)
+
+    def clear_all(self):
+        """Clear the value attributes of all controls in the form.
+
+        See HTMLForm.clear.__doc__.
+
+        """
+        for control in self.controls:
+            control.clear()
+
+    def clear(self,
+              name=None, type=None, kind=None, id=None, nr=None, label=None):
+        """Clear the value attribute of a control.
+
+        As a result, the affected control will not be successful until a value
+        is subsequently set.  AttributeError is raised on readonly controls.
+
+        """
+        c = self.find_control(name, type, kind, id, label=label, nr=nr)
+        c.clear()
+
+
+#---------------------------------------------------
+# Form-filling methods applying only to ListControls.
+
+    def possible_items(self,  # deprecated
+                       name=None, type=None, kind=None, id=None,
+                       nr=None, by_label=False, label=None):
+        """Return a list of all values that the specified control can take."""
+        c = self._find_list_control(name, type, kind, id, label, nr)
+        return c.possible_items(by_label)
+
+    def set(self, selected, item_name,  # deprecated
+            name=None, type=None, kind=None, id=None, nr=None,
+            by_label=False, label=None):
+        """Select / deselect named list item.
+
+        selected: boolean selected state
+
+        """
+        self._find_list_control(name, type, kind, id, label, nr).set(
+            selected, item_name, by_label)
+    def toggle(self, item_name,  # deprecated
+               name=None, type=None, kind=None, id=None, nr=None,
+               by_label=False, label=None):
+        """Toggle selected state of named list item."""
+        self._find_list_control(name, type, kind, id, label, nr).toggle(
+            item_name, by_label)
+
+    def set_single(self, selected,  # deprecated
+                   name=None, type=None, kind=None, id=None,
+                   nr=None, by_label=None, label=None):
+        """Select / deselect list item in a control having only one item.
+
+        If the control has multiple list items, ItemCountError is raised.
+
+        This is just a convenience method, so you don't need to know the item's
+        name -- the item name in these single-item controls is usually
+        something meaningless like "1" or "on".
+
+        For example, if a checkbox has a single item named "on", the following
+        two calls are equivalent:
+
+        control.toggle("on")
+        control.toggle_single()
+
+        """  # by_label ignored and deprecated
+        self._find_list_control(
+            name, type, kind, id, label, nr).set_single(selected)
+    def toggle_single(self, name=None, type=None, kind=None, id=None,
+                      nr=None, by_label=None, label=None):  # deprecated
+        """Toggle selected state of list item in control having only one item.
+
+        The rest is as for HTMLForm.set_single.__doc__.
+
+        """  # by_label ignored and deprecated
+        self._find_list_control(name, type, kind, id, label, nr).toggle_single()
+
+#---------------------------------------------------
+# Form-filling method applying only to FileControls.
+
+    def add_file(self, file_object, content_type=None, filename=None,
+                 name=None, id=None, nr=None, label=None):
+        """Add a file to be uploaded.
+
+        file_object: file-like object (with read method) from which to read
+         data to upload
+        content_type: MIME content type of data to upload
+        filename: filename to pass to server
+
+        If filename is None, no filename is sent to the server.
+
+        If content_type is None, the content type is guessed based on the
+        filename and the data from read from the file object.
+
+        XXX
+        At the moment, guessed content type is always application/octet-stream.
+        Use sndhdr, imghdr modules.  Should also try to guess HTML, XML, and
+        plain text.
+
+        Note the following useful HTML attributes of file upload controls (see
+        HTML 4.01 spec, section 17):
+
+        accept: comma-separated list of content types that the server will
+         handle correctly; you can use this to filter out non-conforming files
+        size: XXX IIRC, this is indicative of whether form wants multiple or
+         single files
+        maxlength: XXX hint of max content length in bytes?
+
+        """
+        self.find_control(name, "file", id=id, label=label, nr=nr).add_file(
+            file_object, content_type, filename)
+
+#---------------------------------------------------
+# Form submission methods, applying only to clickable controls.
+
+    def click(self, name=None, type=None, id=None, nr=0, coord=(1,1),
+              request_class=urllib2.Request,
+              label=None):
+        """Return request that would result from clicking on a control.
+
+        The request object is a urllib2.Request instance, which you can pass to
+        urllib2.urlopen (or ClientCookie.urlopen).
+
+        Only some control types (INPUT/SUBMIT & BUTTON/SUBMIT buttons and
+        IMAGEs) can be clicked.
+
+        Will click on the first clickable control, subject to the name, type
+        and nr arguments (as for find_control).  If no name, type, id or number
+        is specified and there are no clickable controls, a request will be
+        returned for the form in its current, un-clicked, state.
+
+        IndexError is raised if any of name, type, id or nr is specified but no
+        matching control is found.  ValueError is raised if the HTMLForm has an
+        enctype attribute that is not recognised.
+
+        You can optionally specify a coordinate to click at, which only makes a
+        difference if you clicked on an image.
+
+        """
+        return self._click(name, type, id, label, nr, coord, "request",
+                           self._request_class)
+
+    def click_request_data(self,
+                           name=None, type=None, id=None,
+                           nr=0, coord=(1,1),
+                           request_class=urllib2.Request,
+                           label=None):
+        """As for click method, but return a tuple (url, data, headers).
+
+        You can use this data to send a request to the server.  This is useful
+        if you're using httplib or urllib rather than urllib2.  Otherwise, use
+        the click method.
+
+        # Untested.  Have to subclass to add headers, I think -- so use urllib2
+        # instead!
+        import urllib
+        url, data, hdrs = form.click_request_data()
+        r = urllib.urlopen(url, data)
+
+        # Untested.  I don't know of any reason to use httplib -- you can get
+        # just as much control with urllib2.
+        import httplib, urlparse
+        url, data, hdrs = form.click_request_data()
+        tup = urlparse(url)
+        host, path = tup[1], urlparse.urlunparse((None, None)+tup[2:])
+        conn = httplib.HTTPConnection(host)
+        if data:
+            httplib.request("POST", path, data, hdrs)
+        else:
+            httplib.request("GET", path, headers=hdrs)
+        r = conn.getresponse()
+
+        """
+        return self._click(name, type, id, label, nr, coord, "request_data",
+                           self._request_class)
+
+    def click_pairs(self, name=None, type=None, id=None,
+                    nr=0, coord=(1,1),
+                    label=None):
+        """As for click_request_data, but returns a list of (key, value) pairs.
+
+        You can use this list as an argument to ClientForm.urlencode.  This is
+        usually only useful if you're using httplib or urllib rather than
+        urllib2 or ClientCookie.  It may also be useful if you want to manually
+        tweak the keys and/or values, but this should not be necessary.
+        Otherwise, use the click method.
+
+        Note that this method is only useful for forms of MIME type
+        x-www-form-urlencoded.  In particular, it does not return the
+        information required for file upload.  If you need file upload and are
+        not using urllib2, use click_request_data.
+
+        Also note that Python 2.0's urllib.urlencode is slightly broken: it
+        only accepts a mapping, not a sequence of pairs, as an argument.  This
+        messes up any ordering in the argument.  Use ClientForm.urlencode
+        instead.
+
+        """
+        return self._click(name, type, id, label, nr, coord, "pairs",
+                           self._request_class)
+
+#---------------------------------------------------
+
+    def find_control(self,
+                     name=None, type=None, kind=None, id=None,
+                     predicate=None, nr=None,
+                     label=None):
+        """Locate and return some specific control within the form.
+
+        At least one of the name, type, kind, predicate and nr arguments must
+        be supplied.  If no matching control is found, ControlNotFoundError is
+        raised.
+
+        If name is specified, then the control must have the indicated name.
+
+        If type is specified then the control must have the specified type (in
+        addition to the types possible for <input> HTML tags: "text",
+        "password", "hidden", "submit", "image", "button", "radio", "checkbox",
+        "file" we also have "reset", "buttonbutton", "submitbutton",
+        "resetbutton", "textarea", "select" and "isindex").
+
+        If kind is specified, then the control must fall into the specified
+        group, each of which satisfies a particular interface.  The types are
+        "text", "list", "multilist", "singlelist", "clickable" and "file".
+
+        If id is specified, then the control must have the indicated id.
+
+        If predicate is specified, then the control must match that function.
+        The predicate function is passed the control as its single argument,
+        and should return a boolean value indicating whether the control
+        matched.
+
+        nr, if supplied, is the sequence number of the control (where 0 is the
+        first).  Note that control 0 is the first control matching all the
+        other arguments (if supplied); it is not necessarily the first control
+        in the form.  If no nr is supplied, AmbiguityError is raised if
+        multiple controls match the other arguments (unless the
+        .backwards-compat attribute is true).
+
+        If label is specified, then the control must have this label.  Note
+        that radio controls and checkboxes never have labels: their items do.
+
+        """
+        if ((name is None) and (type is None) and (kind is None) and
+            (id is None) and (label is None) and (predicate is None) and
+            (nr is None)):
+            raise ValueError(
+                "at least one argument must be supplied to specify control")
+        return self._find_control(name, type, kind, id, label, predicate, nr)
+
+#---------------------------------------------------
+# Private methods.
+
+    def _find_list_control(self,
+                           name=None, type=None, kind=None, id=None, 
+                           label=None, nr=None):
+        if ((name is None) and (type is None) and (kind is None) and
+            (id is None) and (label is None) and (nr is None)):
+            raise ValueError(
+                "at least one argument must be supplied to specify control")
+
+        return self._find_control(name, type, kind, id, label, 
+                                  is_listcontrol, nr)
+
+    def _find_control(self, name, type, kind, id, label, predicate, nr):
+        if ((name is not None) and (name is not Missing) and
+            not isstringlike(name)):
+            raise TypeError("control name must be string-like")
+        if (type is not None) and not isstringlike(type):
+            raise TypeError("control type must be string-like")
+        if (kind is not None) and not isstringlike(kind):
+            raise TypeError("control kind must be string-like")
+        if (id is not None) and not isstringlike(id):
+            raise TypeError("control id must be string-like")
+        if (label is not None) and not isstringlike(label):
+            raise TypeError("control label must be string-like")
+        if (predicate is not None) and not callable(predicate):
+            raise TypeError("control predicate must be callable")
+        if (nr is not None) and nr < 0:
+            raise ValueError("control number must be a positive integer")
+
+        orig_nr = nr
+        found = None
+        ambiguous = False
+        if nr is None and self.backwards_compat:
+            nr = 0
+
+        for control in self.controls:
+            if ((name is not None and name != control.name) and
+                (name is not Missing or control.name is not None)):
+                continue
+            if type is not None and type != control.type:
+                continue
+            if kind is not None and not control.is_of_kind(kind):
+                continue
+            if id is not None and id != control.id:
+                continue
+            if predicate and not predicate(control):
+                continue
+            if label:
+                for l in control.get_labels():
+                    if l.text.find(label) > -1:
+                        break
+                else:
+                    continue
+            if nr is not None:
+                if nr == 0:
+                    return control  # early exit: unambiguous due to nr
+                nr -= 1
+                continue
+            if found:
+                ambiguous = True
+                break
+            found = control
+
+        if found and not ambiguous:
+            return found
+
+        description = []
+        if name is not None: description.append("name %s" % repr(name))
+        if type is not None: description.append("type '%s'" % type)
+        if kind is not None: description.append("kind '%s'" % kind)
+        if id is not None: description.append("id '%s'" % id)
+        if label is not None: description.append("label '%s'" % label)
+        if predicate is not None:
+            description.append("predicate %s" % predicate)
+        if orig_nr: description.append("nr %d" % orig_nr)
+        description = ", ".join(description)
+
+        if ambiguous:
+            raise AmbiguityError("more than one control matching "+description)
+        elif not found:
+            raise ControlNotFoundError("no control matching "+description)
+        assert False
+
+    def _click(self, name, type, id, label, nr, coord, return_type,
+               request_class=urllib2.Request):
+        try:
+            control = self._find_control(
+                name, type, "clickable", id, label, None, nr)
+        except ControlNotFoundError:
+            if ((name is not None) or (type is not None) or (id is not None) or
+                (nr != 0)):
+                raise
+            # no clickable controls, but no control was explicitly requested,
+            # so return state without clicking any control
+            return self._switch_click(return_type, request_class)
+        else:
+            return control._click(self, coord, return_type, request_class)
+
+    def _pairs(self):
+        """Return sequence of (key, value) pairs suitable for urlencoding."""
+        return [(k, v) for (i, k, v, c_i) in self._pairs_and_controls()]
+
+
+    def _pairs_and_controls(self):
+        """Return sequence of (index, key, value, control_index)
+        of totally ordered pairs suitable for urlencoding.
+
+        control_index is the index of the control in self.controls
+        """
+        pairs = []
+        for control_index in range(len(self.controls)):
+            control = self.controls[control_index]
+            for ii, key, val in control._totally_ordered_pairs():
+                pairs.append((ii, key, val, control_index))
+
+        # stable sort by ONLY first item in tuple
+        pairs.sort()
+
+        return pairs
+
+    def _request_data(self):
+        """Return a tuple (url, data, headers)."""
+        method = self.method.upper()
+        #scheme, netloc, path, parameters, query, frag = urlparse.urlparse(self.action)
+        parts = self._urlparse(self.action)
+        rest, (query, frag) = parts[:-2], parts[-2:]
+
+        if method == "GET":
+            if self.enctype != "application/x-www-form-urlencoded":
+                raise ValueError(
+                    "unknown GET form encoding type '%s'" % self.enctype)
+            parts = rest + (urlencode(self._pairs()), None)
+            uri = self._urlunparse(parts)
+            return uri, None, []
+        elif method == "POST":
+            parts = rest + (query, None)
+            uri = self._urlunparse(parts)
+            if self.enctype == "application/x-www-form-urlencoded":
+                return (uri, urlencode(self._pairs()),
+                        [("Content-type", self.enctype)])
+            elif self.enctype == "multipart/form-data":
+                data = StringIO()
+                http_hdrs = []
+                mw = MimeWriter(data, http_hdrs)
+                f = mw.startmultipartbody("form-data", add_to_http_hdrs=True,
+                                          prefix=0)
+                for ii, k, v, control_index in self._pairs_and_controls():
+                    self.controls[control_index]._write_mime_data(mw, k, v)
+                mw.lastpart()
+                return uri, data.getvalue(), http_hdrs
+            else:
+                raise ValueError(
+                    "unknown POST form encoding type '%s'" % self.enctype)
+        else:
+            raise ValueError("Unknown method '%s'" % method)
+
+    def _switch_click(self, return_type, request_class=urllib2.Request):
+        # This is called by HTMLForm and clickable Controls to hide switching
+        # on return_type.
+        if return_type == "pairs":
+            return self._pairs()
+        elif return_type == "request_data":
+            return self._request_data()
+        else:
+            req_data = self._request_data()
+            req = request_class(req_data[0], req_data[1])
+            for key, val in req_data[2]:
+                add_hdr = req.add_header
+                if key.lower() == "content-type":
+                    try:
+                        add_hdr = req.add_unredirected_header
+                    except AttributeError:
+                        # pre-2.4 and not using ClientCookie
+                        pass
+                add_hdr(key, val)
+            return req

Added: ClientForm/tags/0.2.9/GeneralFAQ.html
===================================================================
--- ClientForm/tags/0.2.9/GeneralFAQ.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/GeneralFAQ.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,170 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+        "http://www.w3.org/TR/html4/strict.dtd">
+
+
+
+
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+  <meta name="author" content="John J. Lee &lt;jjl at pobox.com&gt;">
+  <meta name="date" content="2006-05-06">
+  <meta name="keywords" content="FAQ,cookie,HTTP,HTML,form,table,Python,web,client,client-side,testing,sniffer,https,script,embedded">
+  <title>Python web-client programming general FAQs</title>
+  <style type="text/css" media="screen">@import "../styles/style.css";</style>
+  <base href="http://wwwsearch.sourceforge.net/bits/clientx.html">
+</head>
+<body>
+
+<div id="sf"><a href="http://sourceforge.net">
+<img src="http://sourceforge.net/sflogo.php?group_id=48205&amp;type=2"
+ width="125" height="37" alt="SourceForge.net Logo"></a></div>
+<!--<img src="../images/sflogo.png"-->
+
+<h1>Python web-client programming general FAQs</h1>
+
+<div id="Content">
+<ul>
+  <li>Is there any example code?
+     <p>Look in the examples directory of <a href="../mechanize">mechanize</a>.
+     Note that the examples on the <a href="../ClientForm">ClientForm page</a>
+     are executable as-is.  Contributions of example code would be very
+     welcome!
+  <li>HTTPS on Windows?
+     <p>Use this <a href="http://pypgsql.sourceforge.net/misc/python22-win32-ssl.zip">
+     _socket.pyd</a>, or use Python 2.3.
+  <li>I want to see what my web browser is doing, but standard network sniffers
+     like <a href="http://www.ethereal.com/">ethereal</a> or netcat (nc) don't
+     work for HTTPS.  How do I sniff HTTPS traffic?
+  <p>Three good options:
+  <ul>
+    <li>Mozilla plugin: <a href="http://livehttpheaders.mozdev.org/">
+     livehttpheaders</a>.
+    <li><a href="http://www.blunck.info/iehttpheaders.html">ieHTTPHeaders</a>
+     does the same for MSIE.
+    <li>Use <a href="http://lynx.browser.org/">lynx</a> <code>-trace</code>,
+     and filter out the junk with a script.
+  </ul>
+  <p>I'm told you can also use a proxy like <a
+  href="http://www.proxomitron.info/">proxomitron</a> (never tried it
+  myself).  There's also a commercial <a href="http://www.simtec.ltd.uk/">MSIE
+  plugin</a>.
+  <li>Embedded script is messing up my web-scraping.  What do I do?
+     <p>It is possible to embed script in HTML pages (sandwiched between
+     <code>&lt;SCRIPT&gt;here&lt;/SCRIPT&gt;</code> tags, and in
+     <code>javascript:</code> URLs) - JavaScript / ECMAScript, VBScript, or
+     even Python.  These scripts can do all sorts of things, including causing
+     cookies to be set in a browser, submitting or filling in parts of forms in
+     response to user actions, changing link colours as the mouse moves over a
+     link, etc.
+
+     <p>If you come across this in a page you want to automate, you
+     have four options.  Here they are, roughly in order of simplicity.
+
+     <ul>
+       <li>Simply figure out what the embedded script is doing and emulate it
+       in your Python code: for example, by manually adding cookies to your
+       <code>CookieJar</code> instance, calling methods on
+       <code>HTMLForm</code>s, calling <code>urlopen</code>, etc.
+
+       <li>Dump mechanize and ClientForm and automate a browser instead.
+       For example use MS Internet Explorer via its COM automation interfaces, using
+       the <a href="http://starship.python.net/crew/mhammond/">Python for
+       Windows extensions</a>, aka pywin32, aka win32all (eg.
+       <a href="http://vsbabu.org/mt/archives/2003/06/13/ie_automation.html">simple
+       function</a>, <a href="http://pamie.sourceforge.net/">pamie</a>;
+       <a href="http://www.oreilly.com/catalog/pythonwin32/chapter/ch12.html">
+       pywin32 chapter from the O'Reilly book</a>) or
+       <a href="http://starship.python.net/crew/theller/ctypes/">ctypes</a>
+       (<a href="http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305273">
+       example</a>: may be out of date, since <code>ctypes</code>' COM support is
+       still evolving).
+       <a href="http://www.brunningonline.net/simon/blog/archives/winGuiAuto.py.html">This</a>
+       kind of thing may also come in useful on Windows for cases where the
+       automation API is lacking.
+       <a href="http://ftp.acc.umu.se/pub/GNOME/sources/pyphany/">pyphany</a>
+       is a binding to the <a href="http://www.gnome.org/projects/epiphany/">
+       epiphany web browser</a>, allowing both plugins and automation code to be
+       written in Python.
+       XXX Mozilla automation &amp; XPCOM / PyXPCOM, Konqueror &amp; DCOP / KParts / PyKDE).
+
+       <li>Use Java's <a href="httpunit.sourceforge.net">httpunit</a> from
+       Jython, since it knows some JavaScript.
+       <li>Get ambitious and automatically delegate the work to an appropriate
+       interpreter (Mozilla's JavaScript interpreter, for instance).  This
+       approach is the one taken by <a href="../DOMForm">DOMForm</a> (the
+       JavaScript support is &quot;very alpha&quot;, though!).
+     </ul>
+  <li>Misc links
+     <ul>
+       <li><a href="http://www.crummy.com/software/BeautifulSoup/">Beautiful
+       Soup</a> is a widely recommended HTML-parsing module.
+       <li><a href="http://linux.duke.edu/projects/urlgrabber/">urlgrabber</a>
+       contains useful stuff like persistent connections, mirroring and
+       throttling, and it looks like most or all of it is well-integrated with
+       <code>urllib2</code> (originally part of the yum package manager, but
+       now becoming a separate project).
+       <li>Another Java thing: <a href="http://maxq.tigris.org/">maxq</a>,
+       which provides a proxy to aid automatic generation of functional tests
+       written in Jython using the standard library unittest module (PyUnit)
+       and the &quot;Jakarta Commons&quot; HttpClient library.
+       <li>A useful set Zope-oriented links on <a
+       href="http://viii.dclxvi.org/bookmarks/tech/zope/test">tools for testing
+       web applications</a>.
+       <li>O'Reilly book: <a href="">Spidering Hacks</a>.  Very Perl-oriented.
+       <li>Useful
+       <a href="http://chrispederick.com/work/webdeveloper/"> Firefox extension
+       </a> which, amongst other things, can display HTML form information and
+       HTML table structure(thanks to Erno Kuusela for this link).
+       <li>
+       <a href="http://www.openqa.org/selenium/">Selenium</a>: In-browser web
+       functional testing.
+       <li><a href="http://www.opensourcetesting.org/functional.php">Open
+       source functional testing tools</a>.  A nice list.
+       <li><a href="http://www.rexx.com/~dkuhlman/quixote_htmlscraping.html">
+       A HOWTO on web scraping</a> from Dave Kuhlman.
+     </ul>
+  <li>Will any of this code make its way into the Python standard library?
+
+  <p>The request / response processing extensions to <code>urllib2</code> from
+     mechanize have been merged into <code>urllib2</code> for Python 2.4.
+     The cookie processing has been added, as module <code>cookielib</code>.
+     Eventually, I'll submit patches to get the http-equiv, refresh, and
+     robots.txt code in there too, and maybe <code>mechanize.UserAgent</code>
+     too (but <em>not</em> <code>mechanize.Browser</code>).  The rest, probably
+     not.
+
+</ul>
+
+<p>I prefer questions and comments to be sent to the <a
+href="http://lists.sourceforge.net/lists/listinfo/wwwsearch-general">
+mailing list</a> rather than direct to me.
+
+<p><a href="mailto:jjl at pobox.com">John J. Lee</a>,
+May 2006.
+
+</div> <!--id="Content"-->
+
+<div id="Menu">
+
+<a href="..">Home</a><br>
+<br>
+<span class="thispage">General FAQs</span><br>
+<br>
+<a href="../mechanize/">mechanize</a><br>
+<a href="../mechanize/doc.html"><span class="subpage">mechanize docs</span></a><br>
+<a href="../ClientForm/">ClientForm</a><br>
+<br>
+<a href="../ClientCookie/">ClientCookie</a><br>
+<a href="../ClientCookie/doc.html"><span class="subpage">ClientCookie docs</span></a><br>
+<a href="../pullparser/">pullparser</a><br>
+<a href="../DOMForm/">DOMForm</a><br>
+<a href="../python-spidermonkey/">python-spidermonkey</a><br>
+<a href="../ClientTable/">ClientTable</a><br>
+<a href="../bits/urllib2_152.py">1.5.2 urllib2.py</a><br>
+<a href="../bits/urllib_152.py">1.5.2 urllib.py</a><br>
+
+<br>
+
+</body>
+</html>

Added: ClientForm/tags/0.2.9/INSTALL.txt
===================================================================
--- ClientForm/tags/0.2.9/INSTALL.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/INSTALL.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,61 @@
+ClientForm installation instructions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+Dependencies
+~~~~~~~~~~~~
+
+Python 2.0 or above is required.
+
+Use of standard library module urllib2 is recommended.
+
+
+Installation
+~~~~~~~~~~~~
+
+To install the package, run the following command:
+
+ python setup.py build
+
+then (with appropriate permissions)
+
+ python setup.py install
+
+
+Alternatively, just copy the whole ClientForm.py into a directory that's
+on your Python path (eg. unix: /usr/local/lib/python2.2/site-packages,
+Windows: C:\Python24\Lib\site-packages).
+
+
+To run the tests (none of which access the network), run the following
+command:
+
+ python test.py
+
+This runs the tests against the source files extracted from the package.
+For help on command line options:
+
+ python test.py --help
+
+
+If you're using a pre-2.1 version of Python, you'll need to get
+unittest.py (from http://pyunit.sourceforge.net) to run the Pyunit
+tests.
+
+Bugs and comments to jjl at pobox.com.
+
+
+NO WARRANTY
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+See the file COPYRIGHT.txt for copyright information.
+
+This code in this package is free software; you can redistribute it
+and/or modify it under the terms of the BSD or ZPL 2.1 licenses (see
+the file COPYING.txt).
+
+John J. Lee <jjl at pobox.com>
+March 2006

Added: ClientForm/tags/0.2.9/MANIFEST.in
===================================================================
--- ClientForm/tags/0.2.9/MANIFEST.in	                        (rev 0)
+++ ClientForm/tags/0.2.9/MANIFEST.in	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,12 @@
+include MANIFEST.in
+include COPYING.txt
+include COPYRIGHT.txt
+include INSTALL.txt
+include GeneralFAQ.html
+include README.html.in
+include README.html
+include README.txt
+include ChangeLog.txt
+include *.py
+recursive-include testdata *.html
+recursive-include examples *.dat *.txt *.html *.cgi *.py

Added: ClientForm/tags/0.2.9/PKG-INFO
===================================================================
--- ClientForm/tags/0.2.9/PKG-INFO	                        (rev 0)
+++ ClientForm/tags/0.2.9/PKG-INFO	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,38 @@
+Metadata-Version: 1.0
+Name: ClientForm
+Version: 0.2.9
+Summary: Client-side HTML form handling.
+Home-page: http://wwwsearch.sourceforge.net/ClientForm/
+Author: John J. Lee
+Author-email: jjl at pobox.com
+License: BSD
+Download-URL: http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0.2.9.tar.gz
+Description: ClientForm is a Python module for handling HTML forms on the client
+        side, useful for parsing HTML forms, filling them in and returning the
+        completed forms to the server.  It developed from a port of Gisle Aas'
+        Perl module HTML::Form, from the libwww-perl library, but the
+        interface is not the same.
+        
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: BSD License
+Classifier: License :: OSI Approved :: Zope Public License
+Classifier: Natural Language :: English
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Internet
+Classifier: Topic :: Internet :: WWW/HTTP
+Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
+Classifier: Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Testing
+Classifier: Topic :: Software Development :: Testing :: Traffic Generation
+Classifier: Topic :: System :: Networking :: Monitoring
+Classifier: Topic :: System :: Systems Administration
+Classifier: Topic :: Text Processing
+Classifier: Topic :: Text Processing :: Markup
+Classifier: Topic :: Text Processing :: Markup :: HTML
+Classifier: Topic :: Text Processing :: Markup :: XML

Added: ClientForm/tags/0.2.9/README.html
===================================================================
--- ClientForm/tags/0.2.9/README.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/README.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,583 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+        "http://www.w3.org/TR/html4/strict.dtd">
+<!--This file was generated by EmPy from README.html.in : do not edit-->
+
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+  <meta name="author" content="John J. Lee &lt;jjl at pobox.com&gt;">
+  <meta name="date" content="2008-07-19">
+  <meta name="keywords" content="form,HTML,Python,web,client,client-side">
+  <title>ClientForm</title>
+  <style type="text/css" media="screen">@import "../styles/style.css";</style>
+  
+</head>
+<body>
+
+<div id="sf"><a href="http://sourceforge.net">
+<img src="http://sourceforge.net/sflogo.php?group_id=48205&amp;type=2"
+ width="125" height="37" alt="SourceForge.net Logo"></a></div>
+
+<h1>ClientForm</h1>
+
+<div id="Content">
+
+<p>ClientForm is a Python module for handling HTML forms on the client
+side, useful for parsing HTML forms, filling them in and returning the
+completed forms to the server.  It developed from a port of Gisle Aas'
+Perl module <code>HTML::Form</code>, from the <a
+href="http://www.linpro.no/lwp/">libwww-perl</a> library, but the
+interface is not the same.
+
+<p>Simple working example:
+
+<pre><span class="pykw">from</span> urllib2 <span class="pykw">import</span> urlopen
+<span class="pykw">from</span> ClientForm <span class="pykw">import</span> ParseResponse
+
+response = urlopen(<span class="pystr">"http://wwwsearch.sourceforge.net/ClientForm/example.html"</span>)
+forms = ParseResponse(response, backwards_compat=False)
+form = forms[0]
+<span class="pykw">print</span> form
+form[<span class="pystr">"comments"</span>] = <span class="pystr">"Thanks, Gisle"</span>
+
+<span class="pycmt"># form.click() returns a urllib2.Request object
+</span><span class="pycmt"># (see HTMLForm.click.__doc__ if you don't have urllib2)
+</span><span class="pykw">print</span> urlopen(form.click()).read()</pre>
+
+
+<p>A more complicated working example (<em><strong>Note</strong>: this
+example makes use of the ClientForm 0.2 API; refer to the README.html
+file in the latest 0.1 release for the corresponding code for that
+version.</em>):
+
+<a name="example"></a>
+<pre><span class="pykw">import</span> ClientForm
+<span class="pykw">import</span> urllib2
+request = urllib2.Request(
+    <span class="pystr">"http://wwwsearch.sourceforge.net/ClientForm/example.html"</span>)
+response = urllib2.urlopen(request)
+forms = ClientForm.ParseResponse(response, backwards_compat=False)
+response.close()
+<span class="pycmt">## f = open("example.html")
+</span><span class="pycmt">## forms = ClientForm.ParseFile(f, "http://example.com/example.html",
+</span><span class="pycmt">##                              backwards_compat=False)
+</span><span class="pycmt">## f.close()
+</span>form = forms[0]
+<span class="pykw">print</span> form  <span class="pycmt"># very useful!</span>
+
+<span class="pycmt"># A 'control' is a graphical HTML form widget: a text entry box, a
+</span><span class="pycmt"># dropdown 'select' list, a checkbox, etc.
+</span>
+<span class="pycmt"># Indexing allows setting and retrieval of control values
+</span>original_text = form[<span class="pystr">"comments"</span>]  <span class="pycmt"># a string, NOT a Control instance</span>
+form[<span class="pystr">"comments"</span>] = <span class="pystr">"Blah."</span>
+
+<span class="pycmt"># Controls that represent lists (checkbox, select and radio lists) are
+</span><span class="pycmt"># ListControl instances.  Their values are sequences of list item names.
+</span><span class="pycmt"># They come in two flavours: single- and multiple-selection:
+</span>form[<span class="pystr">"favorite_cheese"</span>] = [<span class="pystr">"brie"</span>]  <span class="pycmt"># single</span>
+form[<span class="pystr">"cheeses"</span>] = [<span class="pystr">"parmesan"</span>, <span class="pystr">"leicester"</span>, <span class="pystr">"cheddar"</span>]  <span class="pycmt"># multi</span>
+<span class="pycmt">#  equivalent, but more flexible:
+</span>form.set_value([<span class="pystr">"parmesan"</span>, <span class="pystr">"leicester"</span>, <span class="pystr">"cheddar"</span>], name=<span class="pystr">"cheeses"</span>)
+
+<span class="pycmt"># Add files to FILE controls with .add_file().  Only call this multiple
+</span><span class="pycmt"># times if the server is expecting multiple files.
+</span><span class="pycmt">#  add a file, default value for MIME type, no filename sent to server
+</span>form.add_file(open(<span class="pystr">"data.dat"</span>))
+<span class="pycmt">#  add a second file, explicitly giving MIME type, and telling the server
+</span><span class="pycmt">#   what the filename is
+</span>form.add_file(open(<span class="pystr">"data.txt"</span>), <span class="pystr">"text/plain"</span>, <span class="pystr">"data.txt"</span>)
+
+<span class="pycmt"># All Controls may be disabled (equivalent of greyed-out in browser)...
+</span>control = form.find_control(<span class="pystr">"comments"</span>)
+<span class="pykw">print</span> control.disabled
+<span class="pycmt">#  ...or readonly
+</span><span class="pykw">print</span> control.readonly
+<span class="pycmt">#  readonly and disabled attributes can be assigned to
+</span>control.disabled = False
+<span class="pycmt">#  convenience method, used here to make all controls writable (unless
+</span><span class="pycmt">#   they're disabled):
+</span>form.set_all_readonly(False)
+
+<span class="pycmt"># A couple of notes about list controls and HTML:
+</span>
+<span class="pycmt"># 1. List controls correspond to either a single SELECT element, or
+</span><span class="pycmt"># multiple INPUT elements.  Items correspond to either OPTION or INPUT
+</span><span class="pycmt"># elements.  For example, this is a SELECT control, named "control1":
+</span>
+<span class="pycmt">#    &lt;select name="control1"&gt;
+</span><span class="pycmt">#     &lt;option&gt;foo&lt;/option&gt;
+</span><span class="pycmt">#     &lt;option value="1"&gt;bar&lt;/option&gt;
+</span><span class="pycmt">#    &lt;/select&gt;
+</span>
+<span class="pycmt"># and this is a CHECKBOX control, named "control2":
+</span>
+<span class="pycmt">#    &lt;input type="checkbox" name="control2" value="foo" id="cbe1"&gt;
+</span><span class="pycmt">#    &lt;input type="checkbox" name="control2" value="bar" id="cbe2"&gt;
+</span>
+<span class="pycmt"># You know the latter is a single control because all the name attributes
+</span><span class="pycmt"># are the same.
+</span>
+<span class="pycmt"># 2. Item names are the strings that go to make up the value that should
+</span><span class="pycmt"># be returned to the server.  These strings come from various different
+</span><span class="pycmt"># pieces of text in the HTML.  The HTML standard and the ClientForm
+</span><span class="pycmt"># docstrings explain in detail, but playing around with an HTML file,
+</span><span class="pycmt"># ParseFile() and 'print form' is very useful to understand this!
+</span>
+<span class="pycmt"># You can get the Control instances from inside the form...
+</span>control = form.find_control(<span class="pystr">"cheeses"</span>, type=<span class="pystr">"select"</span>)
+<span class="pykw">print</span> control.name, control.value, control.type
+control.value = [<span class="pystr">"mascarpone"</span>, <span class="pystr">"curd"</span>]
+<span class="pycmt"># ...and the Item instances from inside the Control
+</span>item = control.get(<span class="pystr">"curd"</span>)
+<span class="pykw">print</span> item.name, item.selected, item.id, item.attrs
+item.selected = False
+
+<span class="pycmt"># Controls may be referred to by label:
+</span><span class="pycmt">#  find control with label that has a *substring* "Cheeses"
+</span><span class="pycmt">#  (eg., a label "Please select a cheese" would match).
+</span>control = form.find_control(label=<span class="pystr">"select a cheese"</span>)
+
+<span class="pycmt"># You can explicitly say that you're referring to a ListControl:
+</span><span class="pycmt">#  set value of "cheeses" ListControl
+</span>form.set_value([<span class="pystr">"gouda"</span>], name=<span class="pystr">"cheeses"</span>, kind=<span class="pystr">"list"</span>)
+<span class="pycmt">#  equivalent:
+</span>form.find_control(name=<span class="pystr">"cheeses"</span>, kind=<span class="pystr">"list"</span>).value = [<span class="pystr">"gouda"</span>]
+<span class="pycmt">#  the first example is also almost equivalent to the following (but
+</span><span class="pycmt">#  insists that the control be a ListControl -- so it will skip any
+</span><span class="pycmt">#  non-list controls that come before the control we want)
+</span>form[<span class="pystr">"cheeses"</span>] = [<span class="pystr">"gouda"</span>]
+<span class="pycmt"># The kind argument can also take values "multilist", "singlelist", "text",
+</span><span class="pycmt"># "clickable" and "file":
+</span><span class="pycmt">#  find first control that will accept text, and scribble in it
+</span>form.set_value(<span class="pystr">"rhubarb rhubarb"</span>, kind=<span class="pystr">"text"</span>, nr=0)
+<span class="pycmt">#  find, and set the value of, the first single-selection list control
+</span>form.set_value([<span class="pystr">"spam"</span>], kind=<span class="pystr">"singlelist"</span>, nr=0)
+
+<span class="pycmt"># You can find controls with a general predicate function:
+</span><span class="pykw">def</span> control_has_caerphilly(control):
+    <span class="pykw">for</span> item <span class="pykw">in</span> control.items:
+        <span class="pykw">if</span> item.name == <span class="pystr">"caerphilly"</span>: <span class="pykw">return</span> True
+form.find_control(kind=<span class="pystr">"list"</span>, predicate=control_has_caerphilly)
+
+<span class="pycmt"># HTMLForm.controls is a list of all controls in the form
+</span><span class="pykw">for</span> control <span class="pykw">in</span> form.controls:
+    <span class="pykw">if</span> control.value == <span class="pystr">"inquisition"</span>: sys.exit()
+
+<span class="pycmt"># Control.items is a list of all Item instances in the control
+</span><span class="pykw">for</span> item <span class="pykw">in</span> form.find_control(<span class="pystr">"cheeses"</span>).items:
+    <span class="pykw">print</span> item.name
+
+<span class="pycmt"># To remove items from a list control, remove it from .items:
+</span>cheeses = form.find_control(<span class="pystr">"cheeses"</span>)
+curd = cheeses.get(<span class="pystr">"curd"</span>)
+<span class="pykw">del</span> cheeses.items[cheeses.items.index(curd)]
+<span class="pycmt"># To add items to a list container, instantiate an Item with its control
+</span><span class="pycmt"># and attributes:
+</span><span class="pycmt"># Note that you are responsible for getting the attributes correct here,
+</span><span class="pycmt"># and these are not quite identical to the original HTML, due to
+</span><span class="pycmt"># defaulting rules and a few special attributes (e.g. Items that represent
+</span><span class="pycmt"># OPTIONs have a special "contents" key in their .attrs dict).  In future
+</span><span class="pycmt"># there will be an explicitly supported way of using the parsing logic to
+</span><span class="pycmt"># add items and controls from HTML strings without knowing these details.
+</span>ClientForm.Item(cheeses, {<span class="pystr">"contents"</span>: <span class="pystr">"mascarpone"</span>,
+                          <span class="pystr">"value"</span>: <span class="pystr">"mascarpone"</span>})
+
+<span class="pycmt"># You can specify list items by label using set/get_value_by_label() and
+</span><span class="pycmt"># the label argument of the .get() method.  Sometimes labels are easier to
+</span><span class="pycmt"># maintain than names, sometimes the other way around.
+</span>form.set_value_by_label([<span class="pystr">"Mozzarella"</span>, <span class="pystr">"Caerphilly"</span>], <span class="pystr">"cheeses"</span>)
+
+<span class="pycmt"># Which items are present, selected, and successful?
+</span><span class="pycmt">#  is the "parmesan" item of the "cheeses" control successful (selected
+</span><span class="pycmt">#   and not disabled)?
+</span><span class="pykw">print</span> <span class="pystr">"parmesan"</span> <span class="pykw">in</span> form[<span class="pystr">"cheeses"</span>]
+<span class="pycmt">#  is the "parmesan" item of the "cheeses" control selected?
+</span><span class="pykw">print</span> <span class="pystr">"parmesan"</span> <span class="pykw">in</span> [
+    item.name <span class="pykw">for</span> item <span class="pykw">in</span> form.find_control(<span class="pystr">"cheeses"</span>).items <span class="pykw">if</span> item.selected]
+<span class="pycmt">#  does cheeses control have a "caerphilly" item?
+</span><span class="pykw">print</span> <span class="pystr">"caerphilly"</span> <span class="pykw">in</span> [item.name <span class="pykw">for</span> item <span class="pykw">in</span> form.find_control(<span class="pystr">"cheeses"</span>).items]
+
+<span class="pycmt"># Sometimes one wants to set or clear individual items in a list, rather
+</span><span class="pycmt"># than setting the whole .value:
+</span><span class="pycmt">#  select the item named "gorgonzola" in the first control named "cheeses"
+</span>form.find_control(<span class="pystr">"cheeses"</span>).get(<span class="pystr">"gorgonzola"</span>).selected = True
+<span class="pycmt"># You can be more specific:
+</span><span class="pycmt">#  deselect "edam" in third CHECKBOX control
+</span>form.find_control(type=<span class="pystr">"checkbox"</span>, nr=2).get(<span class="pystr">"edam"</span>).selected = False
+<span class="pycmt">#  deselect item labelled "Mozzarella" in control with id "chz"
+</span>form.find_control(id=<span class="pystr">"chz"</span>).get(label=<span class="pystr">"Mozzarella"</span>).selected = False
+
+<span class="pycmt"># Often, a single checkbox (a CHECKBOX control with a single item) is
+</span><span class="pycmt"># present.  In that case, the name of the single item isn't of much
+</span><span class="pycmt"># interest, so it's a good idea to check and uncheck the box without
+</span><span class="pycmt"># using the item name:
+</span>form.find_control(<span class="pystr">"smelly"</span>).items[0].selected = True  <span class="pycmt"># check</span>
+form.find_control(<span class="pystr">"smelly"</span>).items[0].selected = False  <span class="pycmt"># uncheck</span>
+
+<span class="pycmt"># Items may be disabled (selecting or de-selecting a disabled item is
+</span><span class="pycmt"># not allowed):
+</span>control = form.find_control(<span class="pystr">"cheeses"</span>)
+<span class="pykw">print</span> control.get(<span class="pystr">"emmenthal"</span>).disabled
+control.get(<span class="pystr">"emmenthal"</span>).disabled = True
+<span class="pycmt">#  enable all items in control
+</span>control.set_all_items_disabled(False)
+
+request2 = form.click()  <span class="pycmt"># urllib2.Request object</span>
+<span class="pykw">try</span>:
+    response2 = urllib2.urlopen(request2)
+<span class="pykw">except</span> urllib2.HTTPError, response2:
+    <span class="pykw">pass</span>
+
+<span class="pykw">print</span> response2.geturl()
+<span class="pykw">print</span> response2.info()  <span class="pycmt"># headers</span>
+<span class="pykw">print</span> response2.read()  <span class="pycmt"># body</span>
+response2.close()</pre>
+
+
+<a name="notes"></a>
+
+<p>All of the standard control types are supported: <code>TEXT</code>,
+<code>PASSWORD</code>, <code>HIDDEN</code>, <code>TEXTAREA</code>,
+<code>ISINDEX</code>, <code>RESET</code>, <code>BUTTON</code> (<code>INPUT
+TYPE=BUTTON</code> and the various <code>BUTTON</code> types),
+<code>SUBMIT</code>, <code>IMAGE</code>, <code>RADIO</code>,
+<code>CHECKBOX</code>, <code>SELECT</code>/<code>OPTION</code> and
+<code>FILE</code> (for file upload).  Both standard form encodings
+(<code>application/x-www-form-urlencoded</code> and
+<code>multipart/form-data</code>) are supported.
+
+<p>The module is designed for testing and automation of web
+interfaces, not for implementing interactive user agents.
+
+<p><strong><em>Security note</em>: Remember that any passwords you store in
+<code>HTMLForm</code> instances will be saved to disk in the clear if you
+pickle them (directly or indirectly).  The simplest solution to this is to
+avoid pickling <code>HTMLForm</code> objects.  You could also pickle before
+filling in any password, or just set the password to <code>""</code> before
+pickling.</strong>
+
+<p>Python 2.0 or above is required.  To run the tests, you need the
+<code>unittest</code> module (from <a
+href="http://pyunit.sourceforge.net/">PyUnit</a>).  <code>unittest</code> is a
+standard library module with Python 2.1 and above.
+
+<p>For full documentation, see the docstrings in ClientForm.py.
+
+<p><em><strong>Note: this page describes the 0.2 (stable release)
+interface.  See <a href="./src/README-0_1_17.html">here</a> for the
+old 0.1 interface.</strong> </em>
+
+
+<a name="parsers"></a>
+<h2>Parsers</h2>
+
+<p>ClientForm contains two parsers.  See <a href="./#faq">the FAQ entry on
+XHTML</a> for details.
+
+<p><a href="http://www.egenix.com/files/python/mxTidy.html">mxTidy</a> or <a
+href="http://utidylib.berlios.de/">µTidylib</a> can be useful for dealing with
+bad HTML.
+
+<p>I think it would be nice to have an implementation of ClientForm based on <a
+href="http://www.crummy.com/software/BeautifulSoup/">BeautifulSoup</a>
+(i.e. all methods and attributes implemented using the BeautifulSoup API),
+since that module does tolerant HTML parsing with a nice API for doing
+non-forms stuff.  (I'm not about to do this, though.  For anybody interested in
+doing this, note that the ClientForm tests would need making
+constructor-independent first.)
+
+
+<a name="compat"></a>
+<h2>Backwards-compatibility mode</h2>
+
+<p>ClientForm 0.2 includes three minor backwards-incompatible interface
+changes from version 0.1.
+
+<p>To make upgrading from 0.1 easier, and to allow me to stop supporting
+version 0.1 sooner, version 0.2 contains support for operating in a
+backwards-compatible mode, under which code written for 0.1 should work without
+modification.  This is done on a per-<code>HTMLForm</code> basis via the
+<code>.backwards_compat</code> attribute, but for convenience the
+ParseResponse() and ParseFile() factory functions accept
+<code>backwards_compat</code> arguments.  These backwards-compatibility
+features will be removed in version 0.3.  The default is to operate in
+backwards-compatible mode.  To run with backwards compatible mode turned
+<em><strong>OFF</strong></em> (<strong>strongly recommended</strong>):
+
+<pre>
+<span class="pykw">from</span> urllib2 <span class="pykw">import</span> urlopen
+<span class="pykw">from</span> ClientForm <span class="pykw">import</span> ParseResponse
+forms = ParseResponse(urlopen(<span class="pystr">"http://example.com/"</span>), backwards_compat=False)
+<span class="pycmt"># ...</span></pre>
+
+
+<p>The backwards-incompatible changes are:
+
+<ul>
+<li><p>Ambiguous specification of controls or items now results in
+AmbiguityError.  If you want the old behaviour, explicitly pass
+<code>nr=0</code> to indicate you want the first matching control or item.
+
+<li><p>Item label matching is now done by substring, not by strict
+string-equality (but note leading and trailing space is always stripped).
+(Control label matching is always done by substring.)
+
+<li><p>Handling of disabled list items has changed.  First, note that handling
+of disabled list items in 0.1 (and in 0.2's backwards-compatibility mode!) is
+buggy: disabled items are successful (ie. disabled item names are sent back to
+the server).  As a result, there was no distinction to be made between
+successful items and selected items.  In 0.2, the bug is fixed, so this is no
+longer the case, and it is important to note that list controls'
+<code>.value</code> attribute contains only the <em>successful</em> item names;
+items that are <em>selected </em> but not successful (because disabled) are not
+included in <code>.value</code>.  Second, disabled list items may no longer be
+deselected: AttributeError is raised in 0.2, whereas deselection was allowed in
+0.1.  The bug in 0.1 and in 0.2's backwards-compatibility mode will not be
+fixed, to preserve compatibility and to encourage people to upgrade to the new
+0.2 <code>backwards_compat=False</code> behaviour.  </ul>
+
+<a name="credits"></a>
+<h2>Credits</h2>
+
+<p>Apart from Gisle Aas for allowing the original port from
+libwww-perl, particular credit is due to Gary Poster and Benji York,
+and their employer, Zope Corporation, for their contributions which
+led to ClientForm 0.2 being released.  Thanks also to the many people
+who have contributed bug reports.
+
+
+
+<a name="download"></a>
+
+<h2>Download</h2>
+
+<p>For installation instructions, see the INSTALL.txt file included in the
+distribution. 
+
+<p><span class="spanhdr">Stable release</span> There have been three fairly
+minor backwards-incompatible interface changes since version 0.1 (see <a
+href="./#compat">above</a>), but by default the code operates in a
+backwards-compatible mode so that code written for 0.1 should work without
+changes.
+
+<p>0.2 includes better support for labels, and a simpler interface (all the old
+methods are still there, but some have been deprecated and a few added).
+
+<ul>
+
+<li><a href="./src/ClientForm-0.2.9.tar.gz">ClientForm-0.2.9.tar.gz</a>
+<li><a href="./src/ClientForm-0.2.9.zip">ClientForm-0.2.9.zip</a>
+<li><a href="./src/ChangeLog.txt">Change Log</a> (included in distribution)
+<li><a href="./src/">Older releases.</a>
+</ul>
+
+<br>
+
+<p><span class="spanhdr">Old release</span> No longer maintained.  I recommend
+upgrading from 0.1 to 0.2.
+
+<p>There were many interface changes between 0.0 and 0.1, so you should take
+care if upgrading old code from 0.0.
+
+<p>0.1 includes <code>FILE</code> control support for file upload, handling
+of disabled list items, and a redesigned interface.
+<ul>
+
+
+<li><a href="./src/ClientForm-0.1.17.tar.gz">ClientForm-0.1.17.tar.gz</a>
+<li><a href="./src/ClientForm-0_1_17.zip">ClientForm-0_1_17.zip</a>
+<li><a href="./src/ChangeLog.txt">Change Log</a> (included in distribution)
+<li><a href="./src/">Older releases.</a>
+</ul>
+
+<br>
+
+<p><span class="spanhdr">Ancient release</span> No longer maintained.  You
+don't want this.
+
+<ul>
+
+
+<li><a href="./src/ClientForm-0.0.16.tar.gz">ClientForm-0.0.16.tar.gz</a>
+<li><a href="./src/ClientForm-0_0_16.zip">ClientForm-0_0_16.zip</a>
+<li><a href="./src/ChangeLog.txt">Change Log</a> (included in distribution)
+<li><a href="./src/">Older releases.</a>
+</ul>
+
+
+<a name="svn"></a>
+<h2>Subversion</h2>
+
+<p>The <a href="http://subversion.tigris.org/">Subversion (SVN)</a> trunk is <a href="http://codespeak.net/svn/wwwsearch/ClientForm/trunk#egg=ClientForm-dev">http://codespeak.net/svn/wwwsearch/ClientForm/trunk</a>, so to check out the source:
+
+<pre>
+svn co http://codespeak.net/svn/wwwsearch/ClientForm/trunk ClientForm
+</pre>
+
+
+<a name="faq"></a>
+<h2>FAQs</h2>
+<ul>
+  <li>Doesn't the standard Python library module, <code>cgi</code>, do this?
+  <p>No: the <code>cgi</code> module does the server end of the job.  It
+     doesn't know how to parse or fill in a form or how to send it back to the
+     server.
+  <li>Which version of Python do I need?
+  <p>2.0 or above (ClientForm 0.2; version 0.1 requires Python 1.5.2 or above).
+  <li>Is <code>urllib2</code> required?
+  <p>No.
+  <li>How do I use it without <code>urllib2</code>?
+  <p>Use <code>.click_request_data()</code> instead of <code>.click()</code>.
+  <li>Which <code>urllib2</code> do I need?
+  <p>You don't.  It's convenient, though.  If you have Python 2.0, you need to
+     upgrade to the version from Python 2.1 (available from <a
+     href="http://www.python.org/">www.python.org</a>).  Otherwise, you're OK.
+  <li>Which license?
+  <p>ClientForm is dual-licensed: you may pick either the
+     <a href="http://www.opensource.org/licenses/bsd-license.php">BSD license</a>,
+     or the <a href="http://www.zope.org/Resources/ZPL">ZPL 2.1</a> (both are
+     included in the distribution).
+  <a name="xhtml"></a>
+  <li>Is XHTML supported?
+  <p>Yes.  You must pass
+     <code>form_parser_class=ClientForm.XHTMLCompatibleFormParser</code> to
+     <code>ParseResponse()</code> / <code>ParseFile()</code>.  Note this parser
+     is less tolerant of bad HTML than the default,
+     <code>ClientForm.FormParser</code>
+  <li>How do I figure out what control names and values to use?
+  <p><code>print form</code> is usually all you need.
+     In your code, things like the <code>HTMLForm.items</code> attribute of
+     <code>HTMLForm</code> instances can be useful to inspect forms at
+     runtime.  Note that it's possible to use item labels instead of item
+     names, which can be useful &mdash; use the <code>by_label</code>
+     arguments to the various methods, and the <code>.get_value_by_label()</code> /
+     <code>.set_value_by_label()</code> methods on <code>ListControl</code>.
+  <li>What do those <code>'*'</code> characters mean in the string
+     representations of list controls?
+  <p>A <code>*</code> next to an item means that item is selected.
+  <li>What do those parentheses (round brackets) mean in the string
+     representations of list controls?
+  <p>Parentheses <code>(foo)</code> around an item mean that item is disabled.
+  <li>Why doesn't &lt;some control&gt; turn up in the data returned by
+     <code>.click*()</code> when that control has non-<code>None</code> value?
+  <p>Either the control is disabled, or it is not successful for some other
+     reason.  'Successful' (see HTML 4 specification) means that the control
+     will cause data to get sent to the server.
+  <li>Why does ClientForm not follow the HTML 4.0 / RFC 1866 standards for
+     <code>RADIO</code> and multiple-selection <code>SELECT</code> controls?
+  <p>Because by default, it follows browser behaviour when setting the
+     initially-selected items in list controls that have no items explicitly
+     selected in the HTML. Use the <code>select_default</code> argument to
+     <code>ParseResponse</code> if you want to follow the RFC 1866 rules
+     instead.  Note that browser behaviour violates the HTML 4.01 specification
+     in the case of <code>RADIO</code> controls.
+  <li>Why does <code>.click()</code>ing on a button not work for me?
+    <ul>
+    <li>Clicking on a <code>RESET</code> button doesn't do anything, by design
+        - this is a library for web automation, not an interactive browser.
+        Even in an interactive browser, clicking on <code>RESET</code> sends
+        nothing to the server, so there is little point in having
+        <code>.click()</code> do anything special here.
+    <li>Clicking on a <code>BUTTON TYPE=BUTTON</code> doesn't do anything
+        either, also by design.  This time, the reason is that that
+        <code>BUTTON</code> is only in the HTML standard so that one can attach
+        callbacks to its events.  The callbacks are functions in
+        <code>SCRIPT</code> elements (such as Javascript) embedded in the HTML,
+        and their execution may result in information getting sent back to the
+        server.  ClientForm, however, knows nothing about these callbacks, so
+        it can't do anything useful with a click on a <code>BUTTON</code> whose
+        type is <code>BUTTON</code>.
+    <li>Generally, embedded script may be messing things up in all kinds of
+        ways.  See the answer to the next question.
+    </ul>
+  <li>Embedded script is messing up my form filling.  What do I do?
+  <p>See the <a href="../bits/GeneralFAQ.html">General FAQs</a> page and the
+     next FAQ entry for what to do about this.
+<!-- XXX example here -->
+  <li>How do I change <code>INPUT TYPE=HIDDEN</code> field values (for example,
+      to emulate the effect of JavaScript code)?
+  <p>As with any control, set the control's <code>readonly</code> attribute
+     false.
+<p><pre>
+form.find_control(<span class="pystr">"foo"</span>).readonly = False  <span class="pycmt"># allow changing .value of control foo</span>
+form.set_all_readonly(False)  <span class="pycmt"># allow changing the .value of all controls</span></pre>
+
+  </li>
+  <li>I'm having trouble debugging my code.
+  <p>The <a href="../ClientCookie/">ClientCookie</a> package makes it
+     easy to get <code>.seek()</code>able response objects, which is
+     convenient for debugging.  See also <a
+     href="../ClientCookie/doc.html#debugging">here</a> for few
+     relevant tips.  Also see <a href="../bits/GeneralFAQ.html"> General
+     FAQs</a>.
+  <li>I have a control containing a list of integers.  How do I select the one
+     whose value is nearest to the one I want?
+<p><pre>
+<span class="pykw">import</span> bisect
+<span class="pykw">def</span> closest_int_value(form, ctrl_name, value):
+    values = map(int, [item.name <span class="pykw">for</span> item <span class="pykw">in</span> form.find_control(ctrl_name).items])
+    <span class="pykw">return</span> str(values[bisect.bisect(values, value) - 1])
+
+form[<span class="pystr">"distance"</span>] = [closest_int_value(form, <span class="pystr">"distance"</span>, 23)]</pre>
+
+  </li>
+  <li>Where can I find out more about the HTML and HTTP standards?
+  <ul>
+     <li>W3C <a href="http://www.w3.org/TR/html401/">HTML 4.01
+        Specification</a>.
+     <li><a href="http://www.ietf.org/rfc/rfc1866.txt">RFC 1866</a> -
+        the HTML 2.0 standard.
+     <li><a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> -
+        Form-based file upload.
+     <li><a href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a> -
+        HTTP 1.1 Specification.
+  </ul>
+</ul>
+
+<p>I prefer questions and comments to be sent to the <a
+href="http://lists.sourceforge.net/lists/listinfo/wwwsearch-general">
+mailing list</a> rather than direct to me.
+
+<p><a href="mailto:jjl at pobox.com">John J. Lee</a>,
+July 2008.
+
+</div>
+
+<div id="Menu">
+
+<a href="..">Home</a><br>
+<br>
+<a href="../bits/GeneralFAQ.html">General FAQs</a><br>
+<br>
+<a href="../mechanize/">mechanize</a><br>
+<a href="../mechanize/doc.html"><span class="subpage">mechanize docs</span></a><br>
+<span class="thispage">ClientForm</span><br>
+<br>
+<a href="../ClientCookie/">ClientCookie</a><br>
+<a href="../ClientCookie/doc.html"><span class="subpage">ClientCookie docs</span></a><br>
+<a href="../pullparser/">pullparser</a><br>
+<a href="../DOMForm/">DOMForm</a><br>
+<a href="../python-spidermonkey/">python-spidermonkey</a><br>
+<a href="../ClientTable/">ClientTable</a><br>
+<a href="../bits/urllib2_152.py">1.5.2 urllib2.py</a><br>
+<a href="../bits/urllib_152.py">1.5.2 urllib.py</a><br>
+
+<br>
+
+<a href="../#other">Other stuff</a><br>
+
+<br>
+
+<a href="./#example">Example</a><br>
+<a href="./#notes">Notes</a><br>
+<a href="./#parsers">Parsers</a><br>
+<a href="./#compat">Compatibility</a><br>
+<a href="./#credits">Credits</a><br>
+<a href="./#download">Download</a><br>
+<a href="./#faq">FAQs</a><br>
+
+</div>
+
+</body>
+</html>

Added: ClientForm/tags/0.2.9/README.html.in
===================================================================
--- ClientForm/tags/0.2.9/README.html.in	                        (rev 0)
+++ ClientForm/tags/0.2.9/README.html.in	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,385 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+        "http://www.w3.org/TR/html4/strict.dtd">
+@# This file is processed by EmPy
+<!--This file was generated by EmPy from README.html.in : do not edit-->
+@# http://wwwsearch.sf.net/bits/colorize.py
+@{
+from colorize import colorize
+import time
+import release
+last_modified = release.svn_id_to_time("$Id: README.html.in 56671 2008-07-19 13:17:32Z jjlee $")
+try:
+    base
+except NameError:
+    base = False
+}
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+  <meta name="author" content="John J. Lee &lt;jjl@@pobox.com&gt;">
+  <meta name="date" content="@(time.strftime("%Y-%m-%d", last_modified))">
+  <meta name="keywords" content="form,HTML,Python,web,client,client-side">
+  <title>ClientForm</title>
+  <style type="text/css" media="screen">@@import "../styles/style.css";</style>
+  @[if base]<base href="http://wwwsearch.sourceforge.net/ClientForm/">@[end if]
+</head>
+<body>
+
+<div id="sf"><a href="http://sourceforge.net">
+<img src="http://sourceforge.net/sflogo.php?group_id=48205&amp;type=2"
+ width="125" height="37" alt="SourceForge.net Logo"></a></div>
+
+<h1>ClientForm</h1>
+
+<div id="Content">
+
+<p>ClientForm is a Python module for handling HTML forms on the client
+side, useful for parsing HTML forms, filling them in and returning the
+completed forms to the server.  It developed from a port of Gisle Aas'
+Perl module <code>HTML::Form</code>, from the <a
+href="http://www.linpro.no/lwp/">libwww-perl</a> library, but the
+interface is not the same.
+
+<p>Simple working example:
+
+@{colorize("".join(open("examples/simple.py").readlines()[2:]))}
+
+<p>A more complicated working example (<em><strong>Note</strong>: this
+example makes use of the ClientForm 0.2 API; refer to the README.html
+file in the latest 0.1 release for the corresponding code for that
+version.</em>):
+
+<a name="example"></a>
+@{colorize("".join(open("examples/example.py").readlines()[2:]))}
+
+<a name="notes"></a>
+
+<p>All of the standard control types are supported: <code>TEXT</code>,
+<code>PASSWORD</code>, <code>HIDDEN</code>, <code>TEXTAREA</code>,
+<code>ISINDEX</code>, <code>RESET</code>, <code>BUTTON</code> (<code>INPUT
+TYPE=BUTTON</code> and the various <code>BUTTON</code> types),
+<code>SUBMIT</code>, <code>IMAGE</code>, <code>RADIO</code>,
+<code>CHECKBOX</code>, <code>SELECT</code>/<code>OPTION</code> and
+<code>FILE</code> (for file upload).  Both standard form encodings
+(<code>application/x-www-form-urlencoded</code> and
+<code>multipart/form-data</code>) are supported.
+
+<p>The module is designed for testing and automation of web
+interfaces, not for implementing interactive user agents.
+
+<p><strong><em>Security note</em>: Remember that any passwords you store in
+<code>HTMLForm</code> instances will be saved to disk in the clear if you
+pickle them (directly or indirectly).  The simplest solution to this is to
+avoid pickling <code>HTMLForm</code> objects.  You could also pickle before
+filling in any password, or just set the password to <code>""</code> before
+pickling.</strong>
+
+<p>Python 2.0 or above is required.  To run the tests, you need the
+<code>unittest</code> module (from <a
+href="http://pyunit.sourceforge.net/">PyUnit</a>).  <code>unittest</code> is a
+standard library module with Python 2.1 and above.
+
+<p>For full documentation, see the docstrings in ClientForm.py.
+
+<p><em><strong>Note: this page describes the 0.2 (stable release)
+interface.  See <a href="./src/README-0_1_17.html">here</a> for the
+old 0.1 interface.</strong> </em>
+
+
+<a name="parsers"></a>
+<h2>Parsers</h2>
+
+<p>ClientForm contains two parsers.  See <a href="./#faq">the FAQ entry on
+XHTML</a> for details.
+
+<p><a href="http://www.egenix.com/files/python/mxTidy.html">mxTidy</a> or <a
+href="http://utidylib.berlios.de/">µTidylib</a> can be useful for dealing with
+bad HTML.
+
+<p>I think it would be nice to have an implementation of ClientForm based on <a
+href="http://www.crummy.com/software/BeautifulSoup/">BeautifulSoup</a>
+(i.e. all methods and attributes implemented using the BeautifulSoup API),
+since that module does tolerant HTML parsing with a nice API for doing
+non-forms stuff.  (I'm not about to do this, though.  For anybody interested in
+doing this, note that the ClientForm tests would need making
+constructor-independent first.)
+
+
+<a name="compat"></a>
+<h2>Backwards-compatibility mode</h2>
+
+<p>ClientForm 0.2 includes three minor backwards-incompatible interface
+changes from version 0.1.
+
+<p>To make upgrading from 0.1 easier, and to allow me to stop supporting
+version 0.1 sooner, version 0.2 contains support for operating in a
+backwards-compatible mode, under which code written for 0.1 should work without
+modification.  This is done on a per-<code>HTMLForm</code> basis via the
+<code>.backwards_compat</code> attribute, but for convenience the
+ParseResponse() and ParseFile() factory functions accept
+<code>backwards_compat</code> arguments.  These backwards-compatibility
+features will be removed in version 0.3.  The default is to operate in
+backwards-compatible mode.  To run with backwards compatible mode turned
+<em><strong>OFF</strong></em> (<strong>strongly recommended</strong>):
+
+@{colorize(r"""
+from urllib2 import urlopen
+from ClientForm import ParseResponse
+forms = ParseResponse(urlopen("http://example.com/"), backwards_compat=False)
+# ...
+""")}
+
+<p>The backwards-incompatible changes are:
+
+<ul>
+<li><p>Ambiguous specification of controls or items now results in
+AmbiguityError.  If you want the old behaviour, explicitly pass
+<code>nr=0</code> to indicate you want the first matching control or item.
+
+<li><p>Item label matching is now done by substring, not by strict
+string-equality (but note leading and trailing space is always stripped).
+(Control label matching is always done by substring.)
+
+<li><p>Handling of disabled list items has changed.  First, note that handling
+of disabled list items in 0.1 (and in 0.2's backwards-compatibility mode!) is
+buggy: disabled items are successful (ie. disabled item names are sent back to
+the server).  As a result, there was no distinction to be made between
+successful items and selected items.  In 0.2, the bug is fixed, so this is no
+longer the case, and it is important to note that list controls'
+<code>.value</code> attribute contains only the <em>successful</em> item names;
+items that are <em>selected </em> but not successful (because disabled) are not
+included in <code>.value</code>.  Second, disabled list items may no longer be
+deselected: AttributeError is raised in 0.2, whereas deselection was allowed in
+0.1.  The bug in 0.1 and in 0.2's backwards-compatibility mode will not be
+fixed, to preserve compatibility and to encourage people to upgrade to the new
+0.2 <code>backwards_compat=False</code> behaviour.  </ul>
+
+<a name="credits"></a>
+<h2>Credits</h2>
+
+<p>Apart from Gisle Aas for allowing the original port from
+libwww-perl, particular credit is due to Gary Poster and Benji York,
+and their employer, Zope Corporation, for their contributions which
+led to ClientForm 0.2 being released.  Thanks also to the many people
+who have contributed bug reports.
+
+
+
+<a name="download"></a>
+
+<h2>Download</h2>
+
+<p>For installation instructions, see the INSTALL.txt file included in the
+distribution. 
+
+<p><span class="spanhdr">Stable release</span> There have been three fairly
+minor backwards-incompatible interface changes since version 0.1 (see <a
+href="./#compat">above</a>), but by default the code operates in a
+backwards-compatible mode so that code written for 0.1 should work without
+changes.
+
+<p>0.2 includes better support for labels, and a simpler interface (all the old
+methods are still there, but some have been deprecated and a few added).
+
+<ul>
+@{version = "0.2.9"}
+<li><a href="./src/ClientForm-@(version).tar.gz">ClientForm-@(version).tar.gz</a>
+<li><a href="./src/ClientForm-@(version).zip">ClientForm-@(version).zip</a>
+<li><a href="./src/ChangeLog.txt">Change Log</a> (included in distribution)
+<li><a href="./src/">Older releases.</a>
+</ul>
+
+<br>
+
+<p><span class="spanhdr">Old release</span> No longer maintained.  I recommend
+upgrading from 0.1 to 0.2.
+
+<p>There were many interface changes between 0.0 and 0.1, so you should take
+care if upgrading old code from 0.0.
+
+<p>0.1 includes <code>FILE</code> control support for file upload, handling
+of disabled list items, and a redesigned interface.
+<ul>
+@{version = "0.1.17"}
+@{win_version = release.win_version(version)}
+<li><a href="./src/ClientForm-@(version).tar.gz">ClientForm-@(version).tar.gz</a>
+<li><a href="./src/ClientForm-@(win_version).zip">ClientForm-@(win_version).zip</a>
+<li><a href="./src/ChangeLog.txt">Change Log</a> (included in distribution)
+<li><a href="./src/">Older releases.</a>
+</ul>
+
+<br>
+
+<p><span class="spanhdr">Ancient release</span> No longer maintained.  You
+don't want this.
+
+<ul>
+@{version = "0.0.16"}
+@{win_version = release.win_version(version)}
+<li><a href="./src/ClientForm-@(version).tar.gz">ClientForm-@(version).tar.gz</a>
+<li><a href="./src/ClientForm-@(win_version).zip">ClientForm-@(win_version).zip</a>
+<li><a href="./src/ChangeLog.txt">Change Log</a> (included in distribution)
+<li><a href="./src/">Older releases.</a>
+</ul>
+
+
+<a name="svn"></a>
+<h2>Subversion</h2>
+
+<p>The <a href="http://subversion.tigris.org/">Subversion (SVN)</a> trunk is <a href="http://codespeak.net/svn/wwwsearch/ClientForm/trunk#egg=ClientForm-dev">http://codespeak.net/svn/wwwsearch/ClientForm/trunk</a>, so to check out the source:
+
+<pre>
+svn co http://codespeak.net/svn/wwwsearch/ClientForm/trunk ClientForm
+</pre>
+
+
+<a name="faq"></a>
+<h2>FAQs</h2>
+<ul>
+  <li>Doesn't the standard Python library module, <code>cgi</code>, do this?
+  <p>No: the <code>cgi</code> module does the server end of the job.  It
+     doesn't know how to parse or fill in a form or how to send it back to the
+     server.
+  <li>Which version of Python do I need?
+  <p>2.0 or above (ClientForm 0.2; version 0.1 requires Python 1.5.2 or above).
+  <li>Is <code>urllib2</code> required?
+  <p>No.
+  <li>How do I use it without <code>urllib2</code>?
+  <p>Use <code>.click_request_data()</code> instead of <code>.click()</code>.
+  <li>Which <code>urllib2</code> do I need?
+  <p>You don't.  It's convenient, though.  If you have Python 2.0, you need to
+     upgrade to the version from Python 2.1 (available from <a
+     href="http://www.python.org/">www.python.org</a>).  Otherwise, you're OK.
+  <li>Which license?
+  <p>ClientForm is dual-licensed: you may pick either the
+     <a href="http://www.opensource.org/licenses/bsd-license.php">BSD license</a>,
+     or the <a href="http://www.zope.org/Resources/ZPL">ZPL 2.1</a> (both are
+     included in the distribution).
+  <a name="xhtml"></a>
+  <li>Is XHTML supported?
+  <p>Yes.  You must pass
+     <code>form_parser_class=ClientForm.XHTMLCompatibleFormParser</code> to
+     <code>ParseResponse()</code> / <code>ParseFile()</code>.  Note this parser
+     is less tolerant of bad HTML than the default,
+     <code>ClientForm.FormParser</code>
+  <li>How do I figure out what control names and values to use?
+  <p><code>print form</code> is usually all you need.
+     In your code, things like the <code>HTMLForm.items</code> attribute of
+     <code>HTMLForm</code> instances can be useful to inspect forms at
+     runtime.  Note that it's possible to use item labels instead of item
+     names, which can be useful &mdash; use the <code>by_label</code>
+     arguments to the various methods, and the <code>.get_value_by_label()</code> /
+     <code>.set_value_by_label()</code> methods on <code>ListControl</code>.
+  <li>What do those <code>'*'</code> characters mean in the string
+     representations of list controls?
+  <p>A <code>*</code> next to an item means that item is selected.
+  <li>What do those parentheses (round brackets) mean in the string
+     representations of list controls?
+  <p>Parentheses <code>(foo)</code> around an item mean that item is disabled.
+  <li>Why doesn't &lt;some control&gt; turn up in the data returned by
+     <code>.click*()</code> when that control has non-<code>None</code> value?
+  <p>Either the control is disabled, or it is not successful for some other
+     reason.  'Successful' (see HTML 4 specification) means that the control
+     will cause data to get sent to the server.
+  <li>Why does ClientForm not follow the HTML 4.0 / RFC 1866 standards for
+     <code>RADIO</code> and multiple-selection <code>SELECT</code> controls?
+  <p>Because by default, it follows browser behaviour when setting the
+     initially-selected items in list controls that have no items explicitly
+     selected in the HTML. Use the <code>select_default</code> argument to
+     <code>ParseResponse</code> if you want to follow the RFC 1866 rules
+     instead.  Note that browser behaviour violates the HTML 4.01 specification
+     in the case of <code>RADIO</code> controls.
+  <li>Why does <code>.click()</code>ing on a button not work for me?
+    <ul>
+    <li>Clicking on a <code>RESET</code> button doesn't do anything, by design
+        - this is a library for web automation, not an interactive browser.
+        Even in an interactive browser, clicking on <code>RESET</code> sends
+        nothing to the server, so there is little point in having
+        <code>.click()</code> do anything special here.
+    <li>Clicking on a <code>BUTTON TYPE=BUTTON</code> doesn't do anything
+        either, also by design.  This time, the reason is that that
+        <code>BUTTON</code> is only in the HTML standard so that one can attach
+        callbacks to its events.  The callbacks are functions in
+        <code>SCRIPT</code> elements (such as Javascript) embedded in the HTML,
+        and their execution may result in information getting sent back to the
+        server.  ClientForm, however, knows nothing about these callbacks, so
+        it can't do anything useful with a click on a <code>BUTTON</code> whose
+        type is <code>BUTTON</code>.
+    <li>Generally, embedded script may be messing things up in all kinds of
+        ways.  See the answer to the next question.
+    </ul>
+  <li>Embedded script is messing up my form filling.  What do I do?
+  <p>See the <a href="../bits/GeneralFAQ.html">General FAQs</a> page and the
+     next FAQ entry for what to do about this.
+<!-- XXX example here -->
+  <li>How do I change <code>INPUT TYPE=HIDDEN</code> field values (for example,
+      to emulate the effect of JavaScript code)?
+  <p>As with any control, set the control's <code>readonly</code> attribute
+     false.
+<p>@{colorize(r"""
+form.find_control("foo").readonly = False  # allow changing .value of control foo
+form.set_all_readonly(False)  # allow changing the .value of all controls
+""")}
+  </li>
+  <li>I'm having trouble debugging my code.
+  <p>The <a href="../ClientCookie/">ClientCookie</a> package makes it
+     easy to get <code>.seek()</code>able response objects, which is
+     convenient for debugging.  See also <a
+     href="../ClientCookie/doc.html#debugging">here</a> for few
+     relevant tips.  Also see <a href="../bits/GeneralFAQ.html"> General
+     FAQs</a>.
+  <li>I have a control containing a list of integers.  How do I select the one
+     whose value is nearest to the one I want?
+<p>@{colorize(r"""
+import bisect
+def closest_int_value(form, ctrl_name, value):
+    values = map(int, [item.name for item in form.find_control(ctrl_name).items])
+    return str(values[bisect.bisect(values, value) - 1])
+
+form["distance"] = [closest_int_value(form, "distance", 23)]
+""")}
+  </li>
+  <li>Where can I find out more about the HTML and HTTP standards?
+  <ul>
+     <li>W3C <a href="http://www.w3.org/TR/html401/">HTML 4.01
+        Specification</a>.
+     <li><a href="http://www.ietf.org/rfc/rfc1866.txt">RFC 1866</a> -
+        the HTML 2.0 standard.
+     <li><a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> -
+        Form-based file upload.
+     <li><a href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a> -
+        HTTP 1.1 Specification.
+  </ul>
+</ul>
+
+<p>I prefer questions and comments to be sent to the <a
+href="http://lists.sourceforge.net/lists/listinfo/wwwsearch-general">
+mailing list</a> rather than direct to me.
+
+<p><a href="mailto:jjl@@pobox.com">John J. Lee</a>,
+@(time.strftime("%B %Y", last_modified)).
+
+</div>
+
+<div id="Menu">
+
+@(release.navbar('ClientForm'))
+
+<br>
+
+<a href="../#other">Other stuff</a><br>
+
+<br>
+
+<a href="./#example">Example</a><br>
+<a href="./#notes">Notes</a><br>
+<a href="./#parsers">Parsers</a><br>
+<a href="./#compat">Compatibility</a><br>
+<a href="./#credits">Credits</a><br>
+<a href="./#download">Download</a><br>
+<a href="./#faq">FAQs</a><br>
+
+</div>
+
+</body>
+</html>

Added: ClientForm/tags/0.2.9/README.txt
===================================================================
--- ClientForm/tags/0.2.9/README.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/README.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,526 @@
+   [1]SourceForge.net Logo
+
+                                   ClientForm
+
+   ClientForm is a Python module for handling HTML forms on the client
+   side, useful for parsing HTML forms, filling them in and returning the
+   completed forms to the server. It developed from a port of Gisle Aas'
+   Perl module HTML::Form, from the [2]libwww-perl library, but the
+   interface is not the same.
+
+   Simple working example:
+from urllib2 import urlopen
+from ClientForm import ParseResponse
+
+response = urlopen("http://wwwsearch.sourceforge.net/ClientForm/example.html")
+forms = ParseResponse(response, backwards_compat=False)
+form = forms[0]
+print form
+form["comments"] = "Thanks, Gisle"
+
+# form.click() returns a urllib2.Request object
+# (see HTMLForm.click.__doc__ if you don't have urllib2)
+print urlopen(form.click()).read()
+
+   A more complicated working example (Note: this example makes use of the
+   ClientForm 0.2 API; refer to the README.html file in the latest 0.1
+   release for the corresponding code for that version.):
+import ClientForm
+import urllib2
+request = urllib2.Request(
+    "http://wwwsearch.sourceforge.net/ClientForm/example.html")
+response = urllib2.urlopen(request)
+forms = ClientForm.ParseResponse(response, backwards_compat=False)
+response.close()
+## f = open("example.html")
+## forms = ClientForm.ParseFile(f, "http://example.com/example.html",
+##                              backwards_compat=False)
+## f.close()
+form = forms[0]
+print form  # very useful!
+
+# A 'control' is a graphical HTML form widget: a text entry box, a
+# dropdown 'select' list, a checkbox, etc.
+
+# Indexing allows setting and retrieval of control values
+original_text = form["comments"]  # a string, NOT a Control instance
+form["comments"] = "Blah."
+
+# Controls that represent lists (checkbox, select and radio lists) are
+# ListControl instances.  Their values are sequences of list item names.
+# They come in two flavours: single- and multiple-selection:
+form["favorite_cheese"] = ["brie"]  # single
+form["cheeses"] = ["parmesan", "leicester", "cheddar"]  # multi
+#  equivalent, but more flexible:
+form.set_value(["parmesan", "leicester", "cheddar"], name="cheeses")
+
+# Add files to FILE controls with .add_file().  Only call this multiple
+# times if the server is expecting multiple files.
+#  add a file, default value for MIME type, no filename sent to server
+form.add_file(open("data.dat"))
+#  add a second file, explicitly giving MIME type, and telling the server
+#   what the filename is
+form.add_file(open("data.txt"), "text/plain", "data.txt")
+
+# All Controls may be disabled (equivalent of greyed-out in browser)...
+control = form.find_control("comments")
+print control.disabled
+#  ...or readonly
+print control.readonly
+#  readonly and disabled attributes can be assigned to
+control.disabled = False
+#  convenience method, used here to make all controls writable (unless
+#   they're disabled):
+form.set_all_readonly(False)
+
+# A couple of notes about list controls and HTML:
+
+# 1. List controls correspond to either a single SELECT element, or
+# multiple INPUT elements.  Items correspond to either OPTION or INPUT
+# elements.  For example, this is a SELECT control, named "control1":
+
+#    <select name="control1">
+#     <option>foo</option>
+#     <option value="1">bar</option>
+#    </select>
+
+# and this is a CHECKBOX control, named "control2":
+
+#    <input type="checkbox" name="control2" value="foo" id="cbe1">
+#    <input type="checkbox" name="control2" value="bar" id="cbe2">
+
+# You know the latter is a single control because all the name attributes
+# are the same.
+
+# 2. Item names are the strings that go to make up the value that should
+# be returned to the server.  These strings come from various different
+# pieces of text in the HTML.  The HTML standard and the ClientForm
+# docstrings explain in detail, but playing around with an HTML file,
+# ParseFile() and 'print form' is very useful to understand this!
+
+# You can get the Control instances from inside the form...
+control = form.find_control("cheeses", type="select")
+print control.name, control.value, control.type
+control.value = ["mascarpone", "curd"]
+# ...and the Item instances from inside the Control
+item = control.get("curd")
+print item.name, item.selected, item.id, item.attrs
+item.selected = False
+
+# Controls may be referred to by label:
+#  find control with label that has a *substring* "Cheeses"
+#  (eg., a label "Please select a cheese" would match).
+control = form.find_control(label="select a cheese")
+
+# You can explicitly say that you're referring to a ListControl:
+#  set value of "cheeses" ListControl
+form.set_value(["gouda"], name="cheeses", kind="list")
+#  equivalent:
+form.find_control(name="cheeses", kind="list").value = ["gouda"]
+#  the first example is also almost equivalent to the following (but
+#  insists that the control be a ListControl -- so it will skip any
+#  non-list controls that come before the control we want)
+form["cheeses"] = ["gouda"]
+# The kind argument can also take values "multilist", "singlelist", "text",
+# "clickable" and "file":
+#  find first control that will accept text, and scribble in it
+form.set_value("rhubarb rhubarb", kind="text", nr=0)
+#  find, and set the value of, the first single-selection list control
+form.set_value(["spam"], kind="singlelist", nr=0)
+
+# You can find controls with a general predicate function:
+def control_has_caerphilly(control):
+    for item in control.items:
+        if item.name == "caerphilly": return True
+form.find_control(kind="list", predicate=control_has_caerphilly)
+
+# HTMLForm.controls is a list of all controls in the form
+for control in form.controls:
+    if control.value == "inquisition": sys.exit()
+
+# Control.items is a list of all Item instances in the control
+for item in form.find_control("cheeses").items:
+    print item.name
+
+# To remove items from a list control, remove it from .items:
+cheeses = form.find_control("cheeses")
+curd = cheeses.get("curd")
+del cheeses.items[cheeses.items.index(curd)]
+# To add items to a list container, instantiate an Item with its control
+# and attributes:
+# Note that you are responsible for getting the attributes correct here,
+# and these are not quite identical to the original HTML, due to
+# defaulting rules and a few special attributes (e.g. Items that represent
+# OPTIONs have a special "contents" key in their .attrs dict).  In future
+# there will be an explicitly supported way of using the parsing logic to
+# add items and controls from HTML strings without knowing these details.
+ClientForm.Item(cheeses, {"contents": "mascarpone",
+                          "value": "mascarpone"})
+
+# You can specify list items by label using set/get_value_by_label() and
+# the label argument of the .get() method.  Sometimes labels are easier to
+# maintain than names, sometimes the other way around.
+form.set_value_by_label(["Mozzarella", "Caerphilly"], "cheeses")
+
+# Which items are present, selected, and successful?
+#  is the "parmesan" item of the "cheeses" control successful (selected
+#   and not disabled)?
+print "parmesan" in form["cheeses"]
+#  is the "parmesan" item of the "cheeses" control selected?
+print "parmesan" in [
+    item.name for item in form.find_control("cheeses").items if item.selected]
+#  does cheeses control have a "caerphilly" item?
+print "caerphilly" in [item.name for item in form.find_control("cheeses").items]
+
+# Sometimes one wants to set or clear individual items in a list, rather
+# than setting the whole .value:
+#  select the item named "gorgonzola" in the first control named "cheeses"
+form.find_control("cheeses").get("gorgonzola").selected = True
+# You can be more specific:
+#  deselect "edam" in third CHECKBOX control
+form.find_control(type="checkbox", nr=2).get("edam").selected = False
+#  deselect item labelled "Mozzarella" in control with id "chz"
+form.find_control(id="chz").get(label="Mozzarella").selected = False
+
+# Often, a single checkbox (a CHECKBOX control with a single item) is
+# present.  In that case, the name of the single item isn't of much
+# interest, so it's a good idea to check and uncheck the box without
+# using the item name:
+form.find_control("smelly").items[0].selected = True  # check
+form.find_control("smelly").items[0].selected = False  # uncheck
+
+# Items may be disabled (selecting or de-selecting a disabled item is
+# not allowed):
+control = form.find_control("cheeses")
+print control.get("emmenthal").disabled
+control.get("emmenthal").disabled = True
+#  enable all items in control
+control.set_all_items_disabled(False)
+
+request2 = form.click()  # urllib2.Request object
+try:
+    response2 = urllib2.urlopen(request2)
+except urllib2.HTTPError, response2:
+    pass
+
+print response2.geturl()
+print response2.info()  # headers
+print response2.read()  # body
+response2.close()
+
+   All of the standard control types are supported: TEXT, PASSWORD,
+   HIDDEN, TEXTAREA, ISINDEX, RESET, BUTTON (INPUT TYPE=BUTTON and the
+   various BUTTON types), SUBMIT, IMAGE, RADIO, CHECKBOX, SELECT/OPTION
+   and FILE (for file upload). Both standard form encodings
+   (application/x-www-form-urlencoded and multipart/form-data) are
+   supported.
+
+   The module is designed for testing and automation of web interfaces,
+   not for implementing interactive user agents.
+
+   Security note: Remember that any passwords you store in HTMLForm
+   instances will be saved to disk in the clear if you pickle them
+   (directly or indirectly). The simplest solution to this is to avoid
+   pickling HTMLForm objects. You could also pickle before filling in any
+   password, or just set the password to "" before pickling.
+
+   Python 2.0 or above is required. To run the tests, you need the
+   unittest module (from [3]PyUnit). unittest is a standard library module
+   with Python 2.1 and above.
+
+   For full documentation, see the docstrings in ClientForm.py.
+
+   Note: this page describes the 0.2 (stable release) interface. See
+   [4]here for the old 0.1 interface.
+
+Parsers
+
+   ClientForm contains two parsers. See [5]the FAQ entry on XHTML for
+   details.
+
+   [6]mxTidy or [7]µTidylib can be useful for dealing with bad HTML.
+
+   I think it would be nice to have an implementation of ClientForm based
+   on [8]BeautifulSoup (i.e. all methods and attributes implemented using
+   the BeautifulSoup API), since that module does tolerant HTML parsing
+   with a nice API for doing non-forms stuff. (I'm not about to do this,
+   though. For anybody interested in doing this, note that the ClientForm
+   tests would need making constructor-independent first.)
+
+Backwards-compatibility mode
+
+   ClientForm 0.2 includes three minor backwards-incompatible interface
+   changes from version 0.1.
+
+   To make upgrading from 0.1 easier, and to allow me to stop supporting
+   version 0.1 sooner, version 0.2 contains support for operating in a
+   backwards-compatible mode, under which code written for 0.1 should work
+   without modification. This is done on a per-HTMLForm basis via the
+   .backwards_compat attribute, but for convenience the ParseResponse()
+   and ParseFile() factory functions accept backwards_compat arguments.
+   These backwards-compatibility features will be removed in version 0.3.
+   The default is to operate in backwards-compatible mode. To run with
+   backwards compatible mode turned OFF (strongly recommended):
+from urllib2 import urlopen
+from ClientForm import ParseResponse
+forms = ParseResponse(urlopen("http://example.com/"), backwards_compat=False)
+# ...
+
+   The backwards-incompatible changes are:
+     * Ambiguous specification of controls or items now results in
+       AmbiguityError. If you want the old behaviour, explicitly pass nr=0
+       to indicate you want the first matching control or item.
+     * Item label matching is now done by substring, not by strict
+       string-equality (but note leading and trailing space is always
+       stripped). (Control label matching is always done by substring.)
+     * Handling of disabled list items has changed. First, note that
+       handling of disabled list items in 0.1 (and in 0.2's
+       backwards-compatibility mode!) is buggy: disabled items are
+       successful (ie. disabled item names are sent back to the server).
+       As a result, there was no distinction to be made between successful
+       items and selected items. In 0.2, the bug is fixed, so this is no
+       longer the case, and it is important to note that list controls'
+       .value attribute contains only the successful item names; items
+       that are selected but not successful (because disabled) are not
+       included in .value. Second, disabled list items may no longer be
+       deselected: AttributeError is raised in 0.2, whereas deselection
+       was allowed in 0.1. The bug in 0.1 and in 0.2's
+       backwards-compatibility mode will not be fixed, to preserve
+       compatibility and to encourage people to upgrade to the new 0.2
+       backwards_compat=False behaviour.
+
+Credits
+
+   Apart from Gisle Aas for allowing the original port from libwww-perl,
+   particular credit is due to Gary Poster and Benji York, and their
+   employer, Zope Corporation, for their contributions which led to
+   ClientForm 0.2 being released. Thanks also to the many people who have
+   contributed bug reports.
+
+Download
+
+   For installation instructions, see the INSTALL.txt file included in the
+   distribution.
+
+   Stable release There have been three fairly minor
+   backwards-incompatible interface changes since version 0.1 (see
+   [9]above), but by default the code operates in a backwards-compatible
+   mode so that code written for 0.1 should work without changes.
+
+   0.2 includes better support for labels, and a simpler interface (all
+   the old methods are still there, but some have been deprecated and a
+   few added).
+     * [10]ClientForm-0.2.9.tar.gz
+     * [11]ClientForm-0.2.9.zip
+     * [12]Change Log (included in distribution)
+     * [13]Older releases.
+
+   Old release No longer maintained. I recommend upgrading from 0.1 to
+   0.2.
+
+   There were many interface changes between 0.0 and 0.1, so you should
+   take care if upgrading old code from 0.0.
+
+   0.1 includes FILE control support for file upload, handling of disabled
+   list items, and a redesigned interface.
+     * [14]ClientForm-0.1.17.tar.gz
+     * [15]ClientForm-0_1_17.zip
+     * [16]Change Log (included in distribution)
+     * [17]Older releases.
+
+   Ancient release No longer maintained. You don't want this.
+     * [18]ClientForm-0.0.16.tar.gz
+     * [19]ClientForm-0_0_16.zip
+     * [20]Change Log (included in distribution)
+     * [21]Older releases.
+
+Subversion
+
+   The [22]Subversion (SVN) trunk is
+   [23]http://codespeak.net/svn/wwwsearch/ClientForm/trunk, so to check
+   out the source:
+svn co http://codespeak.net/svn/wwwsearch/ClientForm/trunk ClientForm
+
+FAQs
+
+     * Doesn't the standard Python library module, cgi, do this?
+       No: the cgi module does the server end of the job. It doesn't know
+       how to parse or fill in a form or how to send it back to the
+       server.
+     * Which version of Python do I need?
+       2.0 or above (ClientForm 0.2; version 0.1 requires Python 1.5.2 or
+       above).
+     * Is urllib2 required?
+       No.
+     * How do I use it without urllib2?
+       Use .click_request_data() instead of .click().
+     * Which urllib2 do I need?
+       You don't. It's convenient, though. If you have Python 2.0, you
+       need to upgrade to the version from Python 2.1 (available from
+       [24]www.python.org). Otherwise, you're OK.
+     * Which license?
+       ClientForm is dual-licensed: you may pick either the [25]BSD
+       license, or the [26]ZPL 2.1 (both are included in the
+       distribution).
+     * Is XHTML supported?
+       Yes. You must pass
+       form_parser_class=ClientForm.XHTMLCompatibleFormParser to
+       ParseResponse() / ParseFile(). Note this parser is less tolerant of
+       bad HTML than the default, ClientForm.FormParser
+     * How do I figure out what control names and values to use?
+       print form is usually all you need. In your code, things like the
+       HTMLForm.items attribute of HTMLForm instances can be useful to
+       inspect forms at runtime. Note that it's possible to use item
+       labels instead of item names, which can be useful -- use the
+       by_label arguments to the various methods, and the
+       .get_value_by_label() / .set_value_by_label() methods on
+       ListControl.
+     * What do those '*' characters mean in the string representations of
+       list controls?
+       A * next to an item means that item is selected.
+     * What do those parentheses (round brackets) mean in the string
+       representations of list controls?
+       Parentheses (foo) around an item mean that item is disabled.
+     * Why doesn't <some control> turn up in the data returned by
+       .click*() when that control has non-None value?
+       Either the control is disabled, or it is not successful for some
+       other reason. 'Successful' (see HTML 4 specification) means that
+       the control will cause data to get sent to the server.
+     * Why does ClientForm not follow the HTML 4.0 / RFC 1866 standards
+       for RADIO and multiple-selection SELECT controls?
+       Because by default, it follows browser behaviour when setting the
+       initially-selected items in list controls that have no items
+       explicitly selected in the HTML. Use the select_default argument to
+       ParseResponse if you want to follow the RFC 1866 rules instead.
+       Note that browser behaviour violates the HTML 4.01 specification in
+       the case of RADIO controls.
+     * Why does .click()ing on a button not work for me?
+          + Clicking on a RESET button doesn't do anything, by design -
+            this is a library for web automation, not an interactive
+            browser. Even in an interactive browser, clicking on RESET
+            sends nothing to the server, so there is little point in
+            having .click() do anything special here.
+          + Clicking on a BUTTON TYPE=BUTTON doesn't do anything either,
+            also by design. This time, the reason is that that BUTTON is
+            only in the HTML standard so that one can attach callbacks to
+            its events. The callbacks are functions in SCRIPT elements
+            (such as Javascript) embedded in the HTML, and their execution
+            may result in information getting sent back to the server.
+            ClientForm, however, knows nothing about these callbacks, so
+            it can't do anything useful with a click on a BUTTON whose
+            type is BUTTON.
+          + Generally, embedded script may be messing things up in all
+            kinds of ways. See the answer to the next question.
+     * Embedded script is messing up my form filling. What do I do?
+       See the [27]General FAQs page and the next FAQ entry for what to do
+       about this.
+     * How do I change INPUT TYPE=HIDDEN field values (for example, to
+       emulate the effect of JavaScript code)?
+       As with any control, set the control's readonly attribute false.
+form.find_control("foo").readonly = False  # allow changing .value of control fo
+o
+form.set_all_readonly(False)  # allow changing the .value of all controls
+     * I'm having trouble debugging my code.
+       The [28]ClientCookie package makes it easy to get .seek()able
+       response objects, which is convenient for debugging. See also
+       [29]here for few relevant tips. Also see [30]General FAQs.
+     * I have a control containing a list of integers. How do I select the
+       one whose value is nearest to the one I want?
+import bisect
+def closest_int_value(form, ctrl_name, value):
+    values = map(int, [item.name for item in form.find_control(ctrl_name).items]
+)
+    return str(values[bisect.bisect(values, value) - 1])
+
+form["distance"] = [closest_int_value(form, "distance", 23)]
+     * Where can I find out more about the HTML and HTTP standards?
+          + W3C [31]HTML 4.01 Specification.
+          + [32]RFC 1866 - the HTML 2.0 standard.
+          + [33]RFC 1867 - Form-based file upload.
+          + [34]RFC 2616 - HTTP 1.1 Specification.
+
+   I prefer questions and comments to be sent to the [35]mailing list
+   rather than direct to me.
+
+   [36]John J. Lee, July 2008.
+
+   [37]Home
+   [38]General FAQs
+   [39]mechanize
+   [40]mechanize docs
+   ClientForm
+   [41]ClientCookie
+   [42]ClientCookie docs
+   [43]pullparser
+   [44]DOMForm
+   [45]python-spidermonkey
+   [46]ClientTable
+   [47]1.5.2 urllib2.py
+   [48]1.5.2 urllib.py
+   [49]Other stuff
+   [50]Example
+   [51]Notes
+   [52]Parsers
+   [53]Compatibility
+   [54]Credits
+   [55]Download
+   [56]FAQs
+
+References
+
+   1. http://sourceforge.net/
+   2. http://www.linpro.no/lwp/
+   3. http://pyunit.sourceforge.net/
+   4. file://localhost/tmp/tmpro6pOT/src/README-0_1_17.html
+   5. file://localhost/tmp/tmpro6pOT/#faq
+   6. http://www.egenix.com/files/python/mxTidy.html
+   7. http://utidylib.berlios.de/
+   8. http://www.crummy.com/software/BeautifulSoup/
+   9. file://localhost/tmp/tmpro6pOT/#compat
+  10. file://localhost/tmp/tmpro6pOT/src/ClientForm-0.2.9.tar.gz
+  11. file://localhost/tmp/tmpro6pOT/src/ClientForm-0.2.9.zip
+  12. file://localhost/tmp/tmpro6pOT/src/ChangeLog.txt
+  13. file://localhost/tmp/tmpro6pOT/src/
+  14. file://localhost/tmp/tmpro6pOT/src/ClientForm-0.1.17.tar.gz
+  15. file://localhost/tmp/tmpro6pOT/src/ClientForm-0_1_17.zip
+  16. file://localhost/tmp/tmpro6pOT/src/ChangeLog.txt
+  17. file://localhost/tmp/tmpro6pOT/src/
+  18. file://localhost/tmp/tmpro6pOT/src/ClientForm-0.0.16.tar.gz
+  19. file://localhost/tmp/tmpro6pOT/src/ClientForm-0_0_16.zip
+  20. file://localhost/tmp/tmpro6pOT/src/ChangeLog.txt
+  21. file://localhost/tmp/tmpro6pOT/src/
+  22. http://subversion.tigris.org/
+  23. http://codespeak.net/svn/wwwsearch/ClientForm/trunk#egg=ClientForm-dev
+  24. http://www.python.org/
+  25. http://www.opensource.org/licenses/bsd-license.php
+  26. http://www.zope.org/Resources/ZPL
+  27. file://localhost/tmp/bits/GeneralFAQ.html
+  28. file://localhost/tmp/ClientCookie/
+  29. file://localhost/tmp/ClientCookie/doc.html#debugging
+  30. file://localhost/tmp/bits/GeneralFAQ.html
+  31. http://www.w3.org/TR/html401/
+  32. http://www.ietf.org/rfc/rfc1866.txt
+  33. http://www.ietf.org/rfc/rfc1867.txt
+  34. http://www.ietf.org/rfc/rfc2616.txt
+  35. http://lists.sourceforge.net/lists/listinfo/wwwsearch-general
+  36. mailto:jjl at pobox.com
+  37. file://localhost/tmp
+  38. file://localhost/tmp/bits/GeneralFAQ.html
+  39. file://localhost/tmp/mechanize/
+  40. file://localhost/tmp/mechanize/doc.html
+  41. file://localhost/tmp/ClientCookie/
+  42. file://localhost/tmp/ClientCookie/doc.html
+  43. file://localhost/tmp/pullparser/
+  44. file://localhost/tmp/DOMForm/
+  45. file://localhost/tmp/python-spidermonkey/
+  46. file://localhost/tmp/ClientTable/
+  47. file://localhost/tmp/bits/urllib2_152.py
+  48. file://localhost/tmp/bits/urllib_152.py
+  49. file://localhost/tmp/#other
+  50. file://localhost/tmp/tmpro6pOT/#example
+  51. file://localhost/tmp/tmpro6pOT/#notes
+  52. file://localhost/tmp/tmpro6pOT/#parsers
+  53. file://localhost/tmp/tmpro6pOT/#compat
+  54. file://localhost/tmp/tmpro6pOT/#credits
+  55. file://localhost/tmp/tmpro6pOT/#download
+  56. file://localhost/tmp/tmpro6pOT/#faq

Added: ClientForm/tags/0.2.9/examples/data.dat
===================================================================
--- ClientForm/tags/0.2.9/examples/data.dat	                        (rev 0)
+++ ClientForm/tags/0.2.9/examples/data.dat	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1 @@
+Let's pretend this is a binary file.

Added: ClientForm/tags/0.2.9/examples/data.txt
===================================================================
--- ClientForm/tags/0.2.9/examples/data.txt	                        (rev 0)
+++ ClientForm/tags/0.2.9/examples/data.txt	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,3 @@
+Text, text, text.
+
+Blah.

Added: ClientForm/tags/0.2.9/examples/echo.cgi
===================================================================
--- ClientForm/tags/0.2.9/examples/echo.cgi	                        (rev 0)
+++ ClientForm/tags/0.2.9/examples/echo.cgi	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,23 @@
+#!/usr/bin/python1.5
+# -*-python-*-
+
+print "Content-Type: text/html\n"
+import sys, os, string, cgi
+
+from types import ListType
+
+print "<html><head><title>Form submission parameters</title></head>"
+form = cgi.FieldStorage()
+print "<p>Received parameters:</p>"
+print "<pre>"
+for k in form.keys():
+    v = form[k]
+    if isinstance(v, ListType):
+        vs = []
+        for item in v:
+            vs.append(item.value)
+        text = string.join(vs, ", ")
+    else:
+        text = v.value
+    print "%s: %s" % (cgi.escape(k), cgi.escape(text))
+print "</pre></html>"

Added: ClientForm/tags/0.2.9/examples/example.html
===================================================================
--- ClientForm/tags/0.2.9/examples/example.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/examples/example.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,54 @@
+<html>
+<head>
+<title>Example</title>
+</head>
+<body>
+
+<!--Note that echo.cgi doesn't show file upload data-->
+<form action="http://wwwsearch.sourceforge.net/cgi-bin/echo.cgi"
+ method="POST" enctype="multipart/form-data">
+ <input type="textarea" name="blah"></input>
+
+ <input type="text" name="comments"></input>
+
+ <input type="checkbox" name="eggs" value="spam"></input>
+
+<label for="chz">Please select a cheese</label>
+ <select multiple name="cheeses" id="chz">
+  <option value="mozz">Mozzarella</option>
+  <option value="caerphilly">Caerphilly</option>
+  <option>gouda</option>
+  <option>gorgonzola</option>
+  <option>parmesan</option>
+  <option>leicester</option>
+  <option>cheddar</option>
+  <option>mascarpone</option>
+  <option>curd</option>
+  <option>limburger</option>
+  <option>emmenthal</option>
+ </select>
+
+ <input type="checkbox" name="apples" value="pears"></input>
+
+ <input type="checkbox" name="whocares" value="edam"></input>
+ <input type="checkbox" name="whocares" value="gouda"></input>
+
+ <input type="radio" name="spam" value="spam"></input>
+ <input type="radio" name="spam" value="rhubarb"></input>
+
+ <input type="radio" name="smelly"></input>
+
+<label for="fchz" value="What's your favourite cheese?" />
+ <select single name="favorite_cheese" id="fchz">
+  <option>cheddar</option>
+  <option>brie</option>
+  <option>leicester</option>
+  <option>jahlsberg</option>
+ </select>
+
+ <input type="file"></input>
+</form>
+
+
+</body>
+</html>

Added: ClientForm/tags/0.2.9/examples/example.py
===================================================================
--- ClientForm/tags/0.2.9/examples/example.py	                        (rev 0)
+++ ClientForm/tags/0.2.9/examples/example.py	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+
+import ClientForm
+import urllib2
+request = urllib2.Request(
+    "http://wwwsearch.sourceforge.net/ClientForm/example.html")
+response = urllib2.urlopen(request)
+forms = ClientForm.ParseResponse(response, backwards_compat=False)
+response.close()
+## f = open("example.html")
+## forms = ClientForm.ParseFile(f, "http://example.com/example.html",
+##                              backwards_compat=False)
+## f.close()
+form = forms[0]
+print form  # very useful!
+
+# A 'control' is a graphical HTML form widget: a text entry box, a
+# dropdown 'select' list, a checkbox, etc.
+
+# Indexing allows setting and retrieval of control values
+original_text = form["comments"]  # a string, NOT a Control instance
+form["comments"] = "Blah."
+
+# Controls that represent lists (checkbox, select and radio lists) are
+# ListControl instances.  Their values are sequences of list item names.
+# They come in two flavours: single- and multiple-selection:
+form["favorite_cheese"] = ["brie"]  # single
+form["cheeses"] = ["parmesan", "leicester", "cheddar"]  # multi
+#  equivalent, but more flexible:
+form.set_value(["parmesan", "leicester", "cheddar"], name="cheeses")
+
+# Add files to FILE controls with .add_file().  Only call this multiple
+# times if the server is expecting multiple files.
+#  add a file, default value for MIME type, no filename sent to server
+form.add_file(open("data.dat"))
+#  add a second file, explicitly giving MIME type, and telling the server
+#   what the filename is
+form.add_file(open("data.txt"), "text/plain", "data.txt")
+
+# All Controls may be disabled (equivalent of greyed-out in browser)...
+control = form.find_control("comments")
+print control.disabled
+#  ...or readonly
+print control.readonly
+#  readonly and disabled attributes can be assigned to
+control.disabled = False
+#  convenience method, used here to make all controls writable (unless
+#   they're disabled):
+form.set_all_readonly(False)
+
+# A couple of notes about list controls and HTML:
+
+# 1. List controls correspond to either a single SELECT element, or
+# multiple INPUT elements.  Items correspond to either OPTION or INPUT
+# elements.  For example, this is a SELECT control, named "control1":
+
+#    <select name="control1">
+#     <option>foo</option>
+#     <option value="1">bar</option>
+#    </select>
+
+# and this is a CHECKBOX control, named "control2":
+
+#    <input type="checkbox" name="control2" value="foo" id="cbe1">
+#    <input type="checkbox" name="control2" value="bar" id="cbe2">
+
+# You know the latter is a single control because all the name attributes
+# are the same.
+
+# 2. Item names are the strings that go to make up the value that should
+# be returned to the server.  These strings come from various different
+# pieces of text in the HTML.  The HTML standard and the ClientForm
+# docstrings explain in detail, but playing around with an HTML file,
+# ParseFile() and 'print form' is very useful to understand this!
+
+# You can get the Control instances from inside the form...
+control = form.find_control("cheeses", type="select")
+print control.name, control.value, control.type
+control.value = ["mascarpone", "curd"]
+# ...and the Item instances from inside the Control
+item = control.get("curd")
+print item.name, item.selected, item.id, item.attrs
+item.selected = False
+
+# Controls may be referred to by label:
+#  find control with label that has a *substring* "Cheeses"
+#  (eg., a label "Please select a cheese" would match).
+control = form.find_control(label="select a cheese")
+
+# You can explicitly say that you're referring to a ListControl:
+#  set value of "cheeses" ListControl
+form.set_value(["gouda"], name="cheeses", kind="list")
+#  equivalent:
+form.find_control(name="cheeses", kind="list").value = ["gouda"]
+#  the first example is also almost equivalent to the following (but
+#  insists that the control be a ListControl -- so it will skip any
+#  non-list controls that come before the control we want)
+form["cheeses"] = ["gouda"]
+# The kind argument can also take values "multilist", "singlelist", "text",
+# "clickable" and "file":
+#  find first control that will accept text, and scribble in it
+form.set_value("rhubarb rhubarb", kind="text", nr=0)
+#  find, and set the value of, the first single-selection list control
+form.set_value(["spam"], kind="singlelist", nr=0)
+
+# You can find controls with a general predicate function:
+def control_has_caerphilly(control):
+    for item in control.items:
+        if item.name == "caerphilly": return True
+form.find_control(kind="list", predicate=control_has_caerphilly)
+
+# HTMLForm.controls is a list of all controls in the form
+for control in form.controls:
+    if control.value == "inquisition": sys.exit()
+
+# Control.items is a list of all Item instances in the control
+for item in form.find_control("cheeses").items:
+    print item.name
+
+# To remove items from a list control, remove it from .items:
+cheeses = form.find_control("cheeses")
+curd = cheeses.get("curd")
+del cheeses.items[cheeses.items.index(curd)]
+# To add items to a list container, instantiate an Item with its control
+# and attributes:
+# Note that you are responsible for getting the attributes correct here,
+# and these are not quite identical to the original HTML, due to
+# defaulting rules and a few special attributes (e.g. Items that represent
+# OPTIONs have a special "contents" key in their .attrs dict).  In future
+# there will be an explicitly supported way of using the parsing logic to
+# add items and controls from HTML strings without knowing these details.
+ClientForm.Item(cheeses, {"contents": "mascarpone",
+                          "value": "mascarpone"})
+
+# You can specify list items by label using set/get_value_by_label() and
+# the label argument of the .get() method.  Sometimes labels are easier to
+# maintain than names, sometimes the other way around.
+form.set_value_by_label(["Mozzarella", "Caerphilly"], "cheeses")
+
+# Which items are present, selected, and successful?
+#  is the "parmesan" item of the "cheeses" control successful (selected
+#   and not disabled)?
+print "parmesan" in form["cheeses"]
+#  is the "parmesan" item of the "cheeses" control selected?
+print "parmesan" in [
+    item.name for item in form.find_control("cheeses").items if item.selected]
+#  does cheeses control have a "caerphilly" item?
+print "caerphilly" in [item.name for item in form.find_control("cheeses").items]
+
+# Sometimes one wants to set or clear individual items in a list, rather
+# than setting the whole .value:
+#  select the item named "gorgonzola" in the first control named "cheeses"
+form.find_control("cheeses").get("gorgonzola").selected = True
+# You can be more specific:
+#  deselect "edam" in third CHECKBOX control
+form.find_control(type="checkbox", nr=2).get("edam").selected = False
+#  deselect item labelled "Mozzarella" in control with id "chz"
+form.find_control(id="chz").get(label="Mozzarella").selected = False
+
+# Often, a single checkbox (a CHECKBOX control with a single item) is
+# present.  In that case, the name of the single item isn't of much
+# interest, so it's a good idea to check and uncheck the box without
+# using the item name:
+form.find_control("smelly").items[0].selected = True  # check
+form.find_control("smelly").items[0].selected = False  # uncheck
+
+# Items may be disabled (selecting or de-selecting a disabled item is
+# not allowed):
+control = form.find_control("cheeses")
+print control.get("emmenthal").disabled
+control.get("emmenthal").disabled = True
+#  enable all items in control
+control.set_all_items_disabled(False)
+
+request2 = form.click()  # urllib2.Request object
+try:
+    response2 = urllib2.urlopen(request2)
+except urllib2.HTTPError, response2:
+    pass
+
+print response2.geturl()
+print response2.info()  # headers
+print response2.read()  # body
+response2.close()


Property changes on: ClientForm/tags/0.2.9/examples/example.py
___________________________________________________________________
Added: svn:executable
   + 

Added: ClientForm/tags/0.2.9/examples/simple.py
===================================================================
--- ClientForm/tags/0.2.9/examples/simple.py	                        (rev 0)
+++ ClientForm/tags/0.2.9/examples/simple.py	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+from urllib2 import urlopen
+from ClientForm import ParseResponse
+
+response = urlopen("http://wwwsearch.sourceforge.net/ClientForm/example.html")
+forms = ParseResponse(response, backwards_compat=False)
+form = forms[0]
+print form
+form["comments"] = "Thanks, Gisle"
+
+# form.click() returns a urllib2.Request object
+# (see HTMLForm.click.__doc__ if you don't have urllib2)
+print urlopen(form.click()).read()


Property changes on: ClientForm/tags/0.2.9/examples/simple.py
___________________________________________________________________
Added: svn:executable
   + 

Added: ClientForm/tags/0.2.9/ez_setup.py
===================================================================
--- ClientForm/tags/0.2.9/ez_setup.py	                        (rev 0)
+++ ClientForm/tags/0.2.9/ez_setup.py	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,222 @@
+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+    from ez_setup import use_setuptools
+    use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c3"
+DEFAULT_URL     = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+}
+
+import sys, os
+
+def _validate_md5(egg_name, data):
+    if egg_name in md5_data:
+        from md5 import md5
+        digest = md5(data).hexdigest()
+        if digest != md5_data[egg_name]:
+            print >>sys.stderr, (
+                "md5 validation of %s failed!  (Possible download problem?)"
+                % egg_name
+            )
+            sys.exit(2)
+    return data
+
+
+def use_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    download_delay=15
+):
+    """Automatically find/download setuptools and make it available on sys.path
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end with
+    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
+    it is not already available.  If `download_delay` is specified, it should
+    be the number of seconds that will be paused before initiating a download,
+    should one be required.  If an older version of setuptools is installed,
+    this routine will print a message to ``sys.stderr`` and raise SystemExit in
+    an attempt to abort the calling script.
+    """
+    try:
+        import setuptools
+        if setuptools.__version__ == '0.0.1':
+            print >>sys.stderr, (
+            "You have an obsolete version of setuptools installed.  Please\n"
+            "remove it from your system entirely before rerunning this script."
+            )
+            sys.exit(2)
+    except ImportError:
+        egg = download_setuptools(version, download_base, to_dir, download_delay)
+        sys.path.insert(0, egg)
+        import setuptools; setuptools.bootstrap_install_from = egg
+
+    import pkg_resources
+    try:
+        pkg_resources.require("setuptools>="+version)
+
+    except pkg_resources.VersionConflict, e:
+        # XXX could we install in a subprocess here?
+        print >>sys.stderr, (
+            "The required version of setuptools (>=%s) is not available, and\n"
+            "can't be installed while this script is running. Please install\n"
+            " a more recent version first.\n\n(Currently using %r)"
+        ) % (version, e.args[0])
+        sys.exit(2)
+
+def download_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    delay = 15
+):
+    """Download setuptools from a specified location and return its filename
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end
+    with a '/'). `to_dir` is the directory where the egg will be downloaded.
+    `delay` is the number of seconds to pause before an actual download attempt.
+    """
+    import urllib2, shutil
+    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+    url = download_base + egg_name
+    saveto = os.path.join(to_dir, egg_name)
+    src = dst = None
+    if not os.path.exists(saveto):  # Avoid repeated downloads
+        try:
+            from distutils import log
+            if delay:
+                log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help).  I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+   %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+                    version, download_base, delay, url
+                ); from time import sleep; sleep(delay)
+            log.warn("Downloading %s", url)
+            src = urllib2.urlopen(url)
+            # Read/write all in one block, so we don't create a corrupt file
+            # if the download is interrupted.
+            data = _validate_md5(egg_name, src.read())
+            dst = open(saveto,"wb"); dst.write(data)
+        finally:
+            if src: src.close()
+            if dst: dst.close()
+    return os.path.realpath(saveto)
+
+def main(argv, version=DEFAULT_VERSION):
+    """Install or upgrade setuptools and EasyInstall"""
+
+    try:
+        import setuptools
+    except ImportError:
+        egg = None
+        try:
+            egg = download_setuptools(version, delay=0)
+            sys.path.insert(0,egg)
+            from setuptools.command.easy_install import main
+            return main(list(argv)+[egg])   # we're done here
+        finally:
+            if egg and os.path.exists(egg):
+                os.unlink(egg)
+    else:
+        if setuptools.__version__ == '0.0.1':
+            # tell the user to uninstall obsolete version
+            use_setuptools(version)
+
+    req = "setuptools>="+version
+    import pkg_resources
+    try:
+        pkg_resources.require(req)
+    except pkg_resources.VersionConflict:
+        try:
+            from setuptools.command.easy_install import main
+        except ImportError:
+            from easy_install import main
+        main(list(argv)+[download_setuptools(delay=0)])
+        sys.exit(0) # try to force an exit
+    else:
+        if argv:
+            from setuptools.command.easy_install import main
+            main(argv)
+        else:
+            print "Setuptools version",version,"or greater has been installed."
+            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+
+
+def update_md5(filenames):
+    """Update our built-in md5 registry"""
+
+    import re
+    from md5 import md5
+
+    for name in filenames:
+        base = os.path.basename(name)
+        f = open(name,'rb')
+        md5_data[base] = md5(f.read()).hexdigest()
+        f.close()
+
+    data = ["    %r: %r,\n" % it for it in md5_data.items()]
+    data.sort()
+    repl = "".join(data)
+
+    import inspect
+    srcfile = inspect.getsourcefile(sys.modules[__name__])
+    f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+    match = re.search("\nmd5_data = {\n([^}]+)}", src)
+    if not match:
+        print >>sys.stderr, "Internal error!"
+        sys.exit(2)
+
+    src = src[:match.start(1)] + repl + src[match.end(1):]
+    f = open(srcfile,'w')
+    f.write(src)
+    f.close()
+
+
+if __name__=='__main__':
+    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+        update_md5(sys.argv[2:])
+    else:
+        main(sys.argv[1:])
+
+
+
+
+

Added: ClientForm/tags/0.2.9/setup.cfg
===================================================================
--- ClientForm/tags/0.2.9/setup.cfg	                        (rev 0)
+++ ClientForm/tags/0.2.9/setup.cfg	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+

Added: ClientForm/tags/0.2.9/setup.py
===================================================================
--- ClientForm/tags/0.2.9/setup.py	                        (rev 0)
+++ ClientForm/tags/0.2.9/setup.py	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+"""Client-side HTML form handling.
+
+ClientForm is a Python module for handling HTML forms on the client
+side, useful for parsing HTML forms, filling them in and returning the
+completed forms to the server.  It developed from a port of Gisle Aas'
+Perl module HTML::Form, from the libwww-perl library, but the
+interface is not the same.
+"""
+
+try: True
+except NameError:
+    False, True = 0, 1
+
+import re
+#VERSION_MATCH = re.search(r'VERSION = "(.*)"', open("ClientForm.py").read())
+#VERSION = VERSION_MATCH.group(1)
+VERSION = '0.2.9'
+INSTALL_REQUIRES = []
+NAME = "ClientForm"
+PACKAGE = False
+LICENSE = "BSD"  # or ZPL 2.1
+PLATFORMS = ["any"]
+ZIP_SAFE = True
+CLASSIFIERS = """\
+Development Status :: 5 - Production/Stable
+Intended Audience :: Developers
+Intended Audience :: System Administrators
+License :: OSI Approved :: BSD License
+License :: OSI Approved :: Zope Public License
+Natural Language :: English
+Operating System :: OS Independent
+Programming Language :: Python
+Topic :: Internet
+Topic :: Internet :: WWW/HTTP
+Topic :: Internet :: WWW/HTTP :: Site Management
+Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking
+Topic :: Software Development :: Libraries
+Topic :: Software Development :: Libraries :: Python Modules
+Topic :: Software Development :: Testing
+Topic :: Software Development :: Testing :: Traffic Generation
+Topic :: System :: Networking :: Monitoring
+Topic :: System :: Systems Administration
+Topic :: Text Processing
+Topic :: Text Processing :: Markup
+Topic :: Text Processing :: Markup :: HTML
+Topic :: Text Processing :: Markup :: XML
+"""
+
+#-------------------------------------------------------
+# the rest is constant for most of my released packages:
+
+import sys
+
+if PACKAGE:
+    packages, py_modules = [NAME], None
+else:
+    packages, py_modules = None, [NAME]
+
+doclines = __doc__.split("\n")
+
+if not hasattr(sys, "version_info") or sys.version_info < (2, 3):
+    from distutils.core import setup
+    _setup = setup
+    def setup(**kwargs):
+        ignore_keys = [
+            # distutils >= Python 2.3 args
+            # XXX probably download_url came in earlier than 2.3
+            "classifiers", "download_url",
+            # setuptools args
+            "install_requires", "zip_safe", "test_suite",
+            ]
+        if sys.version_info < (2, 1):
+            ignore_keys.append("platforms")
+        for key in ignore_keys:
+            if kwargs.has_key(key):
+                del kwargs[key]
+        # Only want packages keyword if this is a package,
+        # only want py_modules keyword if this is a single-file module,
+        # so get rid of packages or py_modules keyword as appropriate.
+        if kwargs["packages"] is None:
+            del kwargs["packages"]
+        else:
+            del kwargs["py_modules"]
+        apply(_setup, (), kwargs)
+else:
+    import ez_setup
+    ez_setup.use_setuptools()
+    from setuptools import setup
+
+setup(
+    name = NAME,
+    version = VERSION,
+    license = LICENSE,
+    platforms = PLATFORMS,
+    classifiers = [c for c in CLASSIFIERS.split("\n") if c],
+    install_requires = INSTALL_REQUIRES,
+    zip_safe = ZIP_SAFE,
+    test_suite = "test",
+    author = "John J. Lee",
+    author_email = "jjl at pobox.com",
+    description = doclines[0],
+    long_description = "\n".join(doclines[2:]),
+    url = "http://wwwsearch.sourceforge.net/%s/" % NAME,
+    download_url = ("http://wwwsearch.sourceforge.net/%s/src/"
+                    "%s-%s.tar.gz" % (NAME, NAME, VERSION)),
+    py_modules = py_modules,
+    packages = packages,
+    )

Added: ClientForm/tags/0.2.9/test/test_clientform.py
===================================================================
--- ClientForm/tags/0.2.9/test/test_clientform.py	                        (rev 0)
+++ ClientForm/tags/0.2.9/test/test_clientform.py	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,3447 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+
+# Copyright 2002-2005 John J. Lee <jjl at pobox.com>
+# Copyright 2005 Gary Poster
+# Copyright 2005 Zope Corporation
+# Copyright 1998-2000 Gisle Aas.
+
+import unittest, string
+from unittest import TestCase
+from cStringIO import StringIO
+
+import ClientForm
+from ClientForm import ControlNotFoundError,  ItemNotFoundError, \
+     ItemCountError, AmbiguityError, ParseError
+
+# XXX
+# HTMLForm.set/get_value_by_label()
+# Base control tests on ParseFile, so can use same tests for DOMForm and
+#  ClientForm.  That wouldn't be unit testing exactly, but saner than the
+#  current situation with massive duplication of tests between the two
+#  modules.
+# HTMLForm.enctype
+# XHTML
+
+try: True
+except NameError:
+    True = 1
+    False = 0
+
+try: bool
+except NameError:
+    def bool(expr):
+        if expr: return True
+        else: return False
+
+try:
+    import warnings
+except ImportError:
+    warnings_imported = False
+    def hide_deprecations():
+        pass
+    def reset_deprecations():
+        pass
+    def raise_deprecations():
+        pass
+else:
+    warnings_imported = True
+    def hide_deprecations():
+        warnings.filterwarnings('ignore', category=DeprecationWarning)
+    def reset_deprecations():
+        warnings.filterwarnings('default', category=DeprecationWarning)
+        #warnings.resetwarnings()  # XXX probably safer
+    def raise_deprecations():
+        try:
+            registry = ClientForm.__warningregistry__
+        except AttributeError:
+            pass
+        else:
+            registry.clear()
+        warnings.filterwarnings('error', category=DeprecationWarning)
+
+class DummyForm:
+    def __init__(self):
+        self._forms = []
+        self._labels = []
+        self._id_to_labels = {}
+        self.backwards_compat = False
+        self.controls = []
+
+    def find_control(self, name, type):
+        raise ClientForm.ControlNotFoundError
+
+class UnescapeTests(TestCase):
+
+    def test_unescape_charref(self):
+        from ClientForm import unescape_charref, get_entitydefs
+        mdash_utf8 = u"\u2014".encode("utf-8")
+        for ref, codepoint, utf8, latin1 in [
+            ("38", 38, u"&".encode("utf-8"), "&"),
+            ("x2014", 0x2014, mdash_utf8, "&#x2014;"),
+            ("8212", 8212, mdash_utf8, "&#8212;"),
+            ]:
+            self.assertEqual(unescape_charref(ref, None), unichr(codepoint))
+            self.assertEqual(unescape_charref(ref, 'latin-1'), latin1)
+            self.assertEqual(unescape_charref(ref, 'utf-8'), utf8)
+
+    def test_get_entitydefs(self):
+        from ClientForm import get_entitydefs
+        ed = get_entitydefs()
+        for name, char in [
+            ("&amp;", u"&"),
+            ("&lt;", u"<"),
+            ("&gt;", u">"),
+            ("&mdash;", u"\u2014"),
+            ("&spades;", u"\u2660"),
+            ]:
+            self.assertEqual(ed[name], char)
+
+    def test_unescape1(self):
+        import htmlentitydefs
+        from ClientForm import unescape, get_entitydefs
+        data = "&amp; &lt; &mdash; &#8212; &#x2014;"
+        mdash_utf8 = u"\u2014".encode("utf-8")
+        ue = unescape(data, get_entitydefs(), "utf-8")
+        self.assertEqual("& < %s %s %s" % ((mdash_utf8,)*3), ue)
+
+        for text, expect in [
+            ("&a&amp;", "&a&"),
+            ("a&amp;", "a&"),
+            ]:
+            got = unescape(text, get_entitydefs(), "latin-1")
+            self.assertEqual(got, expect)
+
+    def test_unescape2(self):
+        from ClientForm import unescape, get_entitydefs
+        self.assertEqual(unescape("Donald Duck &amp; Co",
+                                  {"&amp;": "&"}), "Donald Duck & Co")
+        self.assertEqual(
+            unescape("&lt;Donald Duck &amp; Co&gt;",
+                     {"&amp;": "&", "&lt;": "<", "&gt;": ">"}),
+            "<Donald Duck & Co>")
+        self.assertEqual(unescape("Hei p&aring; deg", {"&aring;" : "å"}),
+                         "Hei på deg")
+        self.assertEqual(
+            unescape("&amp;foo;",
+                     {"&amp;": "&", "&foo;": "splat"}), "&foo;")
+        self.assertEqual(unescape("&amp;", {}), "&amp;")
+
+        for encoding, expected in [
+            ("utf-8", u"&\u06aa\u2014\u2014".encode("utf-8")),
+            ("latin-1", "&&#x06aa;&#x2014;&mdash;")]:
+            self.assertEqual(
+                expected,
+                unescape("&amp;&#x06aa;&#x2014;&mdash;", get_entitydefs(), encoding))
+
+    def test_unescape_parsing(self):
+        file = StringIO(
+"""<form action="&amp;amp;&mdash;&#x2014;&#8212;">
+<textarea name="name&amp;amp;&mdash;&#x2014;&#8212;">val&amp;amp;&mdash;&#x2014;&#8212;</textarea>
+</form>
+""")  #"
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False, encoding="utf-8")
+        form = forms[0]
+        test_string = "&amp;"+(u"\u2014".encode('utf8')*3)
+        self.assertEqual(form.action, "http://localhost/"+test_string)
+        control = form.find_control(type="textarea", nr=0)
+        self.assertEqual(control.value, "val"+test_string)
+        self.assertEqual(control.name, "name"+test_string)
+
+    def test_unescape_parsing_select(self):
+        f = StringIO("""\
+<form>
+<select name="a">
+    <option>1&amp;amp;&mdash;&#x2014;&#8212;</option>
+    <option value="2&amp;amp;&mdash;&#x2014;&#8212;">2&amp;amp;&mdash;&#x2014;&#8212;</option>
+</select>
+</form>
+""")  #"
+        forms = ClientForm.ParseFileEx(
+            f, "http://localhost/", encoding="utf-8")
+        form = forms[1]
+        test_string = "&amp;"+(u"\u2014".encode('utf8')*3)
+        control = form.find_control(nr=0)
+        for ii in range(len(control.items)):
+            item = control.items[ii]
+            self.assertEqual(item.name, str(ii+1)+test_string)
+            # XXX label
+
+    def test_unescape_parsing_data(self):
+        file = StringIO(
+"""\
+<form>
+    <label for="foo">Blah &#x201d; &rdquo; blah</label>
+    <input type="text" id="foo" name="foo">
+</form>
+""")  #"
+        # don't crash if we can't encode -- rather, leave entity ref intact
+        forms = ClientForm.ParseFile(
+            file, "http://localhost/", backwards_compat=False,
+            encoding="latin-1")
+        label = forms[0].find_control(nr=0).get_labels()[0]
+        self.assertEqual(label.text, "Blah &#x201d; &rdquo; blah")
+
+
+class LWPFormTests(TestCase):
+    """The original tests from libwww-perl 5.64."""
+    def testEmptyParse(self):
+        forms = ClientForm.ParseFile(StringIO(""), "http://localhost",
+                                     backwards_compat=False)
+        self.assert_(len(forms) == 0)
+
+    def _forms(self):
+        file = StringIO("""<form action="abc">
+
+        <input name="firstname" value="Gisle">
+
+        </form>
+
+        """)
+        return ClientForm.ParseFile(file, "http://localhost/",
+                                    backwards_compat=False)
+
+    def testParse(self):
+        forms = self._forms()
+        self.assert_(len(forms) == 1)
+        self.assert_(forms[0]["firstname"] == "Gisle")
+
+    def testFillForm(self):
+        forms = self._forms()
+        form = forms[0]
+        form["firstname"] = "Gisle Aas"
+        req = form.click()
+        def request_method(req):
+            if req.has_data():
+                return "POST"
+            else:
+                return "GET"
+        self.assert_(request_method(req) == "GET")
+        self.assert_(req.get_full_url() == "http://localhost/abc?firstname=Gisle+Aas")
+
+def get_header(req, name):
+    try:
+        return req.get_header(name)
+    except AttributeError:
+        return req.headers[name]
+
+def header_items(req):
+    try:
+        return req.header_items()
+    except AttributeError:
+        return req.headers.items()
+
+class MockResponse:
+    def __init__(self, f, url):
+        self._file = f
+        self._url = url
+    def geturl(self):
+        return self._url
+    def __getattr__(self, name):
+        return getattr(self._file, name)
+
+class ParseTests(TestCase):
+
+    def test_failing_parse(self):
+        # XXX couldn't provoke an error from BeautifulSoup (!), so this has not
+        # been tested with RobustFormParser
+        import sgmllib
+        # Python 2.0 sgmllib raises RuntimeError rather than SGMLParseError,
+        # but seems never to even raise that except as an assertion, from
+        # reading the code...
+        if hasattr(sgmllib, "SGMLParseError"):
+            f = StringIO("<!!!!>")
+            base_uri = "http://localhost/"
+            self.assertRaises(
+                ClientForm.ParseError,
+                ClientForm.ParseFile, f, base_uri, backwards_compat=False,
+                )
+            self.assert_(issubclass(ClientForm.ParseError, sgmllib.SGMLParseError))
+
+    def test_unknown_control(self):
+        f = StringIO(
+"""<form action="abc">
+<input type="bogus">
+<input>
+</form>
+""")
+        base_uri = "http://localhost/"
+        forms = ClientForm.ParseFile(f, base_uri, backwards_compat=False)
+        form = forms[0]
+        for ctl in form.controls:
+            self.assert_(isinstance(ctl, ClientForm.TextControl))
+
+    def test_ParseFileEx(self):
+        # empty "outer form" (where the "outer form" is the form consisting of
+        # all controls outside of any form)
+        f = StringIO(
+"""<form action="abc">
+<input type="text"></input>
+</form>
+""")
+        base_uri = "http://localhost/"
+        forms = ClientForm.ParseFileEx(f, base_uri)
+        outer = forms[0]
+        self.assertEqual(len(forms), 2)
+        self.assertEqual(outer.controls, [])
+        self.assertEqual(outer.name, None)
+        self.assertEqual(outer.action, base_uri)
+        self.assertEqual(outer.method, "GET")
+        self.assertEqual(outer.enctype, "application/x-www-form-urlencoded")
+        self.assertEqual(outer.attrs, {})
+
+        # non-empty outer form
+        f = StringIO(
+"""
+<input type="text" name="a"></input>
+<form action="abc">
+  <input type="text" name="b"></input>
+</form>
+<input type="text" name="c"></input>
+<form action="abc">
+  <input type="text" name="d"></input>
+</form>
+<input type="text" name="e"></input>
+""")
+        base_uri = "http://localhost/"
+        forms = ClientForm.ParseFileEx(f, base_uri)
+        outer = forms[0]
+        self.assertEqual(len(forms), 3)
+        self.assertEqual([c.name for c in outer.controls], ["a", "c", "e"])
+        self.assertEqual(outer.name, None)
+        self.assertEqual(outer.action, base_uri)
+        self.assertEqual(outer.method, "GET")
+        self.assertEqual(outer.enctype, "application/x-www-form-urlencoded")
+        self.assertEqual(outer.attrs, {})
+
+    def test_ParseResponse(self):
+        url = "http://example.com/"
+        r = MockResponse(
+            StringIO("""\
+<input type="text" name="outer"></input>
+<form action="abc"><input type="text" name="inner"></input></form>
+"""),
+            url,
+            )
+
+        hide_deprecations()
+        forms = ClientForm.ParseResponse(r)
+        reset_deprecations()
+        self.assertEqual(len(forms), 1)
+        form = forms[0]
+        self.assertEqual(form.action, url+"abc")
+        self.assertEqual(form.controls[0].name, "inner")
+
+    def test_ParseResponseEx(self):
+        url = "http://example.com/"
+        r = MockResponse(
+            StringIO("""\
+<input type="text" name="outer"></input>
+<form action="abc"><input type="text" name="inner"></input></form>
+"""),
+            url,
+            )
+
+        forms = ClientForm.ParseResponseEx(r)
+        self.assertEqual(len(forms), 2)
+        outer = forms[0]
+        inner = forms[1]
+        self.assertEqual(inner.action, url+"abc")
+        self.assertEqual(outer.action, url)
+        self.assertEqual(outer.controls[0].name, "outer")
+        self.assertEqual(inner.controls[0].name, "inner")
+
+    def test_parse_error(self):
+        f = StringIO(
+"""<form action="abc">
+<option>
+</form>
+""")
+        base_uri = "http://localhost/"
+        try:
+            ClientForm.ParseFile(f, base_uri, backwards_compat=False)
+        except ClientForm.ParseError, e:
+            self.assert_(e.base_uri == base_uri)
+        else:
+            self.assert_(0)
+
+    def test_base_uri(self):
+        # BASE element takes priority over document URI
+        file = StringIO(
+"""<base HREF="http://example.com">
+<form action="abc">
+<input type="submit"></input>
+</form>
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        self.assert_(form.action == "http://example.com/abc")
+
+        file = StringIO(
+"""<form action="abc">
+<input type="submit"></input>
+</form>
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        self.assert_(form.action == "http://localhost/abc")
+
+    def testTextarea(self):
+        file = StringIO(
+"""<form action="abc&amp;amp;&mdash;d">
+
+<input name="firstname" value="Gisle">
+<textarea>blah, blah,
+Rhubarb.
+
+</textarea>
+
+<textarea></textarea>
+
+<textarea name="&quot;ta&quot;" id="foo&amp;amp;bar">Hello testers &amp;amp; users!</textarea>
+
+</form>
+
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False, encoding="utf-8")
+        self.assert_(len(forms) == 1)
+        form = forms[0]
+        self.assert_(form.name is None)
+        self.assertEqual(
+            form.action,
+            "http://localhost/abc&amp;"+u"\u2014".encode('utf8')+"d")
+        control = form.find_control(type="textarea", nr=0)
+        self.assert_(control.name is None)
+        self.assert_(control.value == "blah, blah,\r\nRhubarb.\r\n\r\n")
+
+        empty_control = form.find_control(type="textarea", nr=1)
+        self.assert_(str(empty_control) == "<TextareaControl(<None>=)>")
+        self.assert_(empty_control.value == "")
+
+        entity_ctl = form.find_control(type="textarea", nr=2)
+        self.assertEqual(entity_ctl.name, '"ta"')
+        self.assertEqual(entity_ctl.attrs["id"], "foo&amp;bar")
+        self.assertEqual(entity_ctl.value, "Hello testers &amp; users!")
+
+    def testSelect(self):
+        file = StringIO(
+"""<form action="abc">
+
+<select name="foo">
+ <option>Hello testers &amp; &blah; users!</option>
+ <option></option><option></option>
+</select>
+
+</form>
+
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        self.assert_(len(forms) == 1)
+        form = forms[0]
+
+        entity_ctl = form.find_control(type="select")
+        self.assert_(entity_ctl.name == "foo")
+        self.assertEqual(entity_ctl.value[0], "Hello testers & &blah; users!")
+        
+        hide_deprecations()
+        opt = entity_ctl.get_item_attrs("Hello testers & &blah; users!")
+        reset_deprecations()
+        self.assertEqual(opt["value"], "Hello testers & &blah; users!")
+        self.assertEqual(opt["label"], "Hello testers & &blah; users!")
+        self.assertEqual(opt["contents"], "Hello testers & &blah; users!")
+
+    def testButton(self):
+        file = StringIO(
+"""<form action="abc" name="myform">
+
+<input type="text" value="cow" name="moo">
+
+<button name="b">blah, blah,
+Rhubarb.</button>
+
+<button type="reset" name="b2"></button>
+<button type="button" name="b3"></button>
+
+</form>
+
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        self.assert_(form.name == "myform")
+        control = form.find_control(name="b")
+        self.assert_(control.type == "submitbutton")
+        self.assert_(control.value == "")
+        self.assert_(form.find_control("b2").type == "resetbutton")
+        self.assert_(form.find_control("b3").type == "buttonbutton")
+        pairs = form.click_pairs()
+        self.assert_(pairs == [("moo", "cow"), ("b", "")])
+
+    def testIsindex(self):
+        file = StringIO(
+"""<form action="abc">
+
+<isindex prompt=">>>">
+
+</form>
+
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        control = form.find_control(type="isindex")
+        self.assert_(control.type == "isindex")
+        self.assert_(control.name is None)
+        self.assert_(control.value == "")
+        control.value = "some stuff"
+        self.assert_(form.click_pairs() == [])
+        self.assert_(form.click_request_data() ==
+                     ("http://localhost/abc?some+stuff", None, []))
+        self.assert_(form.click().get_full_url() ==
+                     "http://localhost/abc?some+stuff")
+
+    def testEmptySelect(self):
+        file = StringIO(
+"""<form action="abc">
+<select name="foo"></select>
+
+<select name="bar" multiple></select>
+
+</form>
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        control0 = form.find_control(type="select", nr=0)
+        control1 = form.find_control(type="select", nr=1)
+        self.assert_(str(control0) == "<SelectControl(foo=[])>")
+        self.assert_(str(control1) == "<SelectControl(bar=[])>")
+        form.set_value([], "foo")
+        self.assertRaises(ItemNotFoundError, form.set_value, ["oops"], "foo")
+        self.assert_(form.click_pairs() == [])
+
+# XXX figure out what to do in these sorts of cases
+##     def badSelect(self):
+##         # what objects should these generate, if any?
+##         # what should happen on submission of these?
+##         # what about similar checkboxes and radios?
+## """<form action="abc" name="myform">
+
+## <select multiple>
+##  <option>1</option>
+##  <option>2</option>
+##  <option>3</option>
+## </select>
+
+## <select multiple>
+##  <option>1</option>
+##  <option>2</option>
+##  <option>3</option>
+## </select>
+
+## </form>
+## """
+
+## """<form action="abc" name="myform">
+
+## <select multiple>
+##  <option>1</option>
+##  <option>2</option>
+##  <option>3</option>
+##  <option>1</option>
+##  <option>2</option>
+##  <option>3</option>
+## </select>
+
+## </form>
+## """
+## <select name="foo">
+##  <option>1</option>
+##  <option>2</option>
+##  <option>3</option>
+## </select>
+
+## <select name="foo" multiple>
+##  <option>4</option>
+##  <option>5</option>
+##  <option>6</option>
+## </select>
+## """
+
+## """<form action="abc" name="myform">
+
+## <select>
+##  <option>1</option>
+##  <option>2</option>
+##  <option>3</option>
+## </select>
+
+## <select>
+##  <option>1</option>
+##  <option>2</option>
+##  <option>3</option>
+## </select>
+
+## </form>
+## """
+
+##     def testBadCheckbox(self):
+##         # see comments above
+##         # split checkbox -- is it one control, or two?
+
+## """
+## <html>
+
+## <input type=checkbox name=foo value=bar>
+## <input type=checkbox name=foo value=bar>
+
+## <select>
+##  <option>1</option>
+##  <option>2</option>
+## </select>
+
+## <input type=checkbox name=foo value=baz>
+## <input type=checkbox name=foo value=bar>
+
+## </html>
+## """
+
+    def testUnnamedControl(self):
+        file = StringIO("""
+<form action="./weird.html">
+
+<input type="checkbox" value="foo"></input>
+
+</form>
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        self.assert_(form.controls[0].name is None)
+
+    def testNamelessListItems(self):
+        # XXX SELECT
+        # these controls have no item names
+        file = StringIO("""<form action="./weird.html">
+
+<input type="checkbox" name="foo"></input>
+
+<input type="radio" name="bar"></input>
+
+<!--
+<select name="baz">
+  <option></option>
+</select>
+
+<select name="baz" multiple>
+  <option></option>
+</select>
+-->
+
+<input type="submit" name="submit">
+</form>
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        hide_deprecations()
+        self.assert_(form.possible_items("foo") == ["on"])
+        self.assert_(form.possible_items("bar") == ["on"])
+        reset_deprecations()
+        #self.assert_(form.possible_items("baz") == [])
+        self.assert_(form["foo"] == [])
+        self.assert_(form["bar"] == [])
+        #self.assert_(form["baz"] == [])
+        form["foo"] = ["on"]
+        form["bar"] = ["on"]
+        pairs = form.click_pairs()
+        self.assert_(pairs == [("foo", "on"), ("bar", "on"), ("submit", "")])
+
+    def testSingleSelectFixup(self):
+        # HTML 4.01 section 17.6.1: single selection SELECT controls shouldn't
+        # have > 1 item selected, but if they do, not more than one should end
+        # up selected.
+        # In fact, testing really obscure stuff here, which follows Firefox
+        # 1.0.7 -- IE doesn't even support disabled OPTIONs.
+        file = StringIO("""<form action="./bad.html">
+
+<select name="spam">
+  <option selected>1</option>
+  <option selected>2</option>
+</select>
+
+<select name="cow">
+  <option selected>1</option>
+  <option disabled selected>2</option>
+</select>
+
+<select name="moo">
+  <option selected disabled>1</option>
+  <option>2</option>
+</select>
+
+<select name="nnn">
+  <option disabled>1</option>
+  <option>2</option>
+  <option>3</option>
+</select>
+
+</form>
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        # deselect all but last item if more than one were selected...
+        spam = form.find_control("spam")
+        self.assertEqual([ii.name for ii in spam.items if ii.selected], ["2"])
+        # ...even if it's disabled
+        cow = form.find_control("cow")
+        self.assertEqual([ii.name for ii in cow.items if ii.selected], ["2"])
+        # exactly one selected item is OK even if it's disabled
+        moo = form.find_control("moo")
+        self.assertEqual([ii.name for ii in moo.items if ii.selected], ["1"])
+        # if nothing was selected choose the first non-disabled item
+        moo = form.find_control("nnn")
+        self.assertEqual([ii.name for ii in moo.items if ii.selected], ["2"])
+
+    def testSelectDefault(self):
+        file = StringIO(
+"""<form action="abc" name="myform">
+
+<select name="a" multiple>
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+</select>
+
+<select name="b">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+</select>
+
+</form>
+
+""")
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=False)
+        form = forms[0]
+        control = form.find_control("a")
+        self.assert_(control.value == [])
+        single_control = form.find_control("b")
+        self.assert_(single_control.value == ["1"])
+
+        file.seek(0)
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     select_default=1, backwards_compat=False)
+        form = forms[0]
+        # select_default only affects *multiple* selection select controls
+        control = form.find_control(type="select", nr=0)
+        self.assert_(control.value == ["1"])
+        single_control = form.find_control(type="select", nr=1)
+        self.assert_(single_control.value == ["1"])
+
+    def test_close_base_tag(self):
+        # Benji York: a single newline immediately after a start tag is
+        # stripped by browsers, but not one immediately before an end tag.
+        # TEXTAREA content is converted to the DOS newline convention.
+        forms = ClientForm.ParseFile(
+            StringIO("<form><textarea>\n\nblah\n</textarea></form>"),
+            "http://example.com/",
+            backwards_compat=False,
+            )
+        ctl = forms[0].find_control(type="textarea")
+        self.assertEqual(ctl.value, "\r\nblah\r\n")
+
+    def test_double_select(self):
+        # More than one SELECT control of the same name in a form never
+        # represent a single control (unlike RADIO and CHECKBOX elements), so
+        # don't merge them.
+        forms = ClientForm.ParseFile(
+            StringIO("""\
+<form>
+    <select name="a">
+        <option>b</option>
+        <option>c</option>
+    </select>
+    <select name="a">
+        <option>d</option>
+        <option>e</option>
+    </select>
+</form>
+"""),
+            "http://example.com/",
+            backwards_compat=False,
+            )
+        form = forms[0]
+        self.assertEquals(len(form.controls), 2)
+        ctl = form.find_control(name="a", nr=0)
+        self.assertEqual([item.name for item in ctl.items], ["b", "c"])
+        ctl = form.find_control(name="a", nr=1)
+        self.assertEqual([item.name for item in ctl.items], ["d", "e"])
+
+    def test_global_select(self):
+        # regression test: closing select and textarea tags should not be
+        # ignored, causing a ParseError due to incorrect tag nesting
+
+        forms = ClientForm.ParseFileEx(
+            StringIO("""\
+<select name="a">
+    <option>b</option>
+    <option>c</option>
+</select>
+<select name="a">
+    <option>d</option>
+    <option>e</option>
+</select>
+"""),
+            "http://example.com/",
+            )
+
+        forms = ClientForm.ParseFile(
+            StringIO("""\
+<textarea></textarea>
+<textarea></textarea>
+"""),
+            "http://example.com/",
+            backwards_compat=False,
+            )
+
+
+class DisabledTests(TestCase):
+    def testOptgroup(self):
+        for compat in [False, True]:
+            self._testOptgroup(compat)
+
+    def _testOptgroup(self, compat):
+        file = StringIO(
+"""<form action="abc" name="myform">
+
+<select name="foo" multiple>
+ <option>1</option>
+ <optgroup>
+ <option>2</option>
+ </optgroup>
+ <option>3</option>
+ <optgroup>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ </optgroup>
+ <optgroup disabled>
+ <option selected>7</option>
+ <option>8</option>
+ </optgroup>
+ <option>9</option>
+ <optgroup disabled>
+ <option>10</option>
+ </optgroup>
+</select>
+
+<select name="bar">
+ <option>1</option>
+ <optgroup>
+ <option>2</option>
+ </optgroup>
+ <option>3</option>
+ <optgroup>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ </optgroup>
+ <optgroup disabled>
+ <option selected>7</option>
+ <option>8</option>
+ </optgroup>
+ <option>9</option>
+ <optgroup disabled>
+ <option>10</option>
+ </optgroup>
+</select>
+
+</form>""")
+
+        def get_control(name, file=file, compat=compat):
+            file.seek(0)
+            forms = ClientForm.ParseFile(file, "http://localhost/",
+                                         backwards_compat=False)
+            form = forms[0]
+            form.backwards_compat = compat
+            return form.find_control(name)
+
+        # can't call item_disabled with no args
+        control = get_control("foo")
+        self.assertRaises(TypeError, control.get_item_disabled)
+
+        hide_deprecations()
+        control.set_item_disabled(True, "2")
+        reset_deprecations()
+        self.assertEqual(
+            str(control),
+            "<SelectControl(foo=[1, (2), 3, 4, 5, 6, (*7), (8), 9, (10)])>")
+
+        # list controls only allow assignment to .value if no attempt is
+        # made to set any disabled item...
+
+        # ...multi selection
+        control = get_control("foo")
+        if compat:
+            extra = ["7"]
+        else:
+            extra = []
+        # disabled items are not part of the submitted value, so "7" not
+        # included (they are not "successful":
+        # http://www.w3.org/TR/REC-html40/interact/forms.html#successful-controls
+        # ).  This behavior was confirmed in Firefox 1.0.4 at least.
+        self.assertEqual(control.value, []+extra)
+        control.value = ["1"]
+        self.assertEqual(control.value, ["1"])
+        control = get_control("foo")
+        self.assertRaises(AttributeError, setattr, control, 'value', ['8'])
+        self.assertEqual(control.value, []+extra)
+        # even though 7 is set already, attempt to set it fails
+        self.assertRaises(AttributeError, setattr, control, 'value', ['7'])
+        control.value = ["1", "3"]
+        self.assertEqual(control.value, ["1", "3"])
+        control = get_control("foo")
+        self.assertRaises(AttributeError, setattr, control, 'value', ['1', '7'])
+        self.assertEqual(control.value, []+extra)
+        # enable all items
+        control.set_all_items_disabled(False)
+        control.value = ['1', '7']
+        self.assertEqual(control.value, ["1", "7"])
+
+        control = get_control("foo")
+        hide_deprecations()
+        for name in 7, 8, 10:
+            self.assert_(control.get_item_disabled(str(name)))
+            if not compat:
+                # a disabled option is never "successful" (see above) so never
+                # in value
+                self.assert_(str(name) not in control.value)
+                # a disabled option always is always upset if you try to set it
+                self.assertRaises(AttributeError, control.set, True, str(name))
+                self.assert_(str(name) not in control.value)
+                self.assertRaises(AttributeError, control.set, False, str(name))
+                self.assert_(str(name) not in control.value)
+                self.assertRaises(AttributeError, control.toggle, str(name))
+                self.assert_(str(name) not in control.value)
+            else:
+                self.assertRaises(AttributeError, control.set, True, str(name))
+                control.set(False, str(name))
+                self.assert_(str(name) not in control.value)
+                control.set(False, str(name))
+                self.assert_(str(name) not in control.value)
+                self.assertRaises(AttributeError, control.toggle, str(name))
+                self.assert_(str(name) not in control.value)
+                self.assertRaises(AttributeError, control.set, True, str(name))
+                self.assert_(str(name) not in control.value)
+
+        control = get_control("foo")
+        for name in 1, 2, 3, 4, 5, 6, 9:
+            self.assert_(not control.get_item_disabled(str(name)))
+            control.set(False, str(name))
+            self.assert_(str(name) not in control.value)
+            control.toggle(str(name))
+            self.assert_(str(name) in control.value)
+            control.set(True, str(name))
+            self.assert_(str(name) in control.value)
+            control.toggle(str(name))
+            self.assert_(str(name) not in control.value)
+
+        control = get_control("foo")
+        self.assert_(control.get_item_disabled("7"))
+        control.set_item_disabled(True, "7")
+        self.assert_(control.get_item_disabled("7"))
+        self.assertRaises(AttributeError, control.set, True, "7")
+        control.set_item_disabled(False, "7")
+        self.assert_(not control.get_item_disabled("7"))
+        control.set(True, "7")
+        control.set(False, "7")
+        control.toggle("7")
+        control.toggle("7")
+        reset_deprecations()
+
+        # ...single-selection
+        control = get_control("bar")
+        # 7 is selected but disabled
+        if compat:
+            value = ["7"]
+        else:
+            value = []
+        self.assertEqual(control.value, value)
+        self.assertEqual(
+            [ii.name for ii in control.items if ii.selected], ["7"])
+        control.value = ["2"]
+
+        control = get_control("bar")
+        def assign_8(control=control): control.value = ["8"]
+        self.assertRaises(AttributeError, assign_8)
+        self.assertEqual(control.value, value)
+        def assign_7(control=control): control.value = ["7"]
+        self.assertRaises(AttributeError, assign_7)
+        # enable all items
+        control.set_all_items_disabled(False)
+        assign_7()
+        self.assertEqual(control.value, ['7'])
+
+        control = get_control("bar")
+        hide_deprecations()
+        for name in 7, 8, 10:
+            self.assert_(control.get_item_disabled(str(name)))
+            if not compat:
+                # a disabled option is never "successful" (see above) so never in
+                # value
+                self.assert_(str(name) not in control.value)
+                # a disabled option always is always upset if you try to set it
+                self.assertRaises(AttributeError, control.set, True, str(name))
+                self.assert_(str(name) not in control.value)
+                self.assertRaises(AttributeError, control.set, False, str(name))
+                self.assert_(str(name) not in control.value)
+                self.assertRaises(AttributeError, control.toggle, str(name))
+                self.assert_(str(name) not in control.value)
+            else:
+                self.assertRaises(AttributeError, control.set, True, str(name))
+                control.set(False, str(name))
+                self.assert_(str(name) != control.value)
+                control.set(False, str(name))
+                self.assert_(str(name) != control.value)
+                self.assertRaises(AttributeError, control.toggle, str(name))
+                self.assert_(str(name) != control.value)
+                self.assertRaises(AttributeError, control.set, True, str(name))
+                self.assert_(str(name) != control.value)
+
+        control = get_control("bar")
+        for name in 1, 2, 3, 4, 5, 6, 9:
+            self.assert_(not control.get_item_disabled(str(name)))
+            control.set(False, str(name))
+            self.assert_(str(name) not in control.value)
+            control.toggle(str(name))
+            self.assert_(str(name) == control.value[0])
+            control.set(True, str(name))
+            self.assert_(str(name) == control.value[0])
+            control.toggle(str(name))
+            self.assert_(str(name) not in control.value)
+
+        control = get_control("bar")
+        self.assert_(control.get_item_disabled("7"))
+        control.set_item_disabled(True, "7")
+        self.assert_(control.get_item_disabled("7"))
+        self.assertRaises(AttributeError, control.set, True, "7")
+        self.assertEqual(control.value, value)
+        control.set_item_disabled(False, "7")
+        self.assertEqual(control.value, ["7"])
+        self.assert_(not control.get_item_disabled("7"))
+        control.set(True, "7")
+        control.set(False, "7")
+        control.toggle("7")
+        control.toggle("7")
+
+        # set_all_items_disabled
+        for name in "foo", "bar":
+            control = get_control(name)
+            control.set_all_items_disabled(False)
+            control.set(True, "7")
+            control.set(True, "1")
+            control.set_all_items_disabled(True)
+            self.assertRaises(AttributeError, control.set, True, "7")
+            self.assertRaises(AttributeError, control.set, True, "1")
+        reset_deprecations()
+
+# XXX single select
+    def testDisabledSelect(self):
+        for compat in [False, True]:
+           self._testDisabledSelect(compat)
+    def _testDisabledSelect(self, compat):
+        file = StringIO(
+"""<form action="abc" name="myform">
+
+<select name="foo" multiple>
+ <option label="a">1</option>
+ <option>2</option>
+ <option>3</option>
+</select>
+
+<select name="bar" multiple>
+ <option>1</option>
+ <option disabled>2</option>
+ <option>3</option>
+</select>
+
+<select name="baz" disabled multiple>
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+</select>
+
+<select name="spam" disabled multiple>
+ <option>1</option>
+ <option disabled>2</option>
+ <option>3</option>
+</select>
+
+<!--This is disabled, but fixup still needs to select an option,
+ rather than  raising AttributeError-->
+<select name="blah" disabled>
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+</select>
+
+</form>
+""")    
+        hide_deprecations()
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=compat)
+        reset_deprecations()
+        form = forms[0]
+        for name, control_disabled, item_disabled in [
+            ("foo", False, False),
+            ("bar", False, True),
+            ("baz", True, False),
+            ("spam", True, True)]:
+            control = form.find_control(name)
+            self.assertEqual(bool(control.disabled), control_disabled)
+            hide_deprecations()
+            item = control.get_item_attrs("2")
+            reset_deprecations()
+            self.assertEqual(bool(item.has_key("disabled")), item_disabled)
+
+            def bad_assign(value, control=control): control.value = value
+            hide_deprecations()
+            if control_disabled:
+                for name in "1", "2", "3":
+                    self.assertRaises(AttributeError, control.set, True, name)
+                    self.assertRaises(AttributeError, bad_assign, [name])
+            elif item_disabled:
+                self.assertRaises(AttributeError, control.set, True, "2")
+                self.assertRaises(AttributeError, bad_assign, ["2"])
+                for name in "1", "3":
+                    control.set(True, name)
+            else:
+                control.value = ["1", "2", "3"]
+            reset_deprecations()
+
+        control = form.find_control("foo")
+        # missing disabled arg
+        hide_deprecations()
+        self.assertRaises(TypeError, control.set_item_disabled, "1")
+        # by_label
+        self.assert_(not control.get_item_disabled("a", by_label=True))
+        control.set_item_disabled(True, "a", by_label=True)
+        self.assert_(control.get_item_disabled("a", by_label=True))
+        reset_deprecations()
+
+    def testDisabledRadio(self):
+        for compat in False, True:
+            self._testDisabledRadio(compat)
+    def _testDisabledRadio(self, compat):
+        file = StringIO(
+"""<form>
+<input type="checkbox" name="foo" value="1" disabled></input>
+<input type="checkbox" name="foo" value="2" disabled></input>
+<input type="checkbox" name="foo" value="3" disabled></input>
+</form>""")
+        hide_deprecations()
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=compat)
+        form = forms[0]
+        control = form.find_control('foo')
+
+        # since all items are disabled, .fixup() should not select
+        # anything
+        self.assertEquals(
+            [item.name for item in control.items if item.selected],
+            [],
+            )
+        reset_deprecations()
+
+    def testDisabledCheckbox(self):
+        for compat in False, True:
+            self._testDisabledCheckbox(compat)
+    def _testDisabledCheckbox(self, compat):
+        file = StringIO(
+"""<form action="abc" name="myform">
+
+<label><input type="checkbox" name="foo" value="1"></input> a</label>
+<input type="checkbox" name="foo" value="2"></input>
+<input type="checkbox" name="foo" value="3"></input>
+
+<input type="checkbox" name="bar" value="1"></input>
+<input type="checkbox" name="bar" value="2" disabled></input>
+<input type="checkbox" name="bar" value="3"></input>
+
+<input type="checkbox" name="baz" value="1" disabled></input>
+<input type="checkbox" name="baz" value="2" disabled></input>
+<input type="checkbox" name="baz" value="3" disabled></input>
+
+</form>""")
+        hide_deprecations()
+        forms = ClientForm.ParseFile(file, "http://localhost/",
+                                     backwards_compat=compat)
+        reset_deprecations()
+        form = forms[0]
+        for name, control_disabled, item_disabled in [
+            ("foo", False, False),
+            ("bar", False, True),
+            ("baz", False, True)]:
+            control = form.find_control(name)
+            self.assert_(bool(control.disabled) == control_disabled)
+            hide_deprecations()
+            item = control.get_item_attrs("2")
+            self.assert_(bool(item.has_key("disabled")) == item_disabled)
+            self.assert_(control.get_item_disabled("2") == item_disabled)
+
+            def bad_assign(value, control=control): control.value = value
+            if item_disabled:
+                self.assertRaises(AttributeError, control.set, True, "2")
+                self.assertRaises(AttributeError, bad_assign, ["2"])
+                if not control.get_item_disabled("1"):
+                    control.set(True, "1")
+            else:
+                control.value = ["1", "2", "3"]
+            reset_deprecations()
+
+        control = form.find_control("foo")
+        hide_deprecations()
+        control.set_item_disabled(False, "1")
+        # missing disabled arg
+        self.assertRaises(TypeError, control.set_item_disabled, "1")
+        # by_label
+        self.failIf(control.get_item_disabled('a', by_label=True))
+        self.assert_(not control.get_item_disabled("1"))
+        control.set_item_disabled(True, 'a', by_label=True)
+        self.assert_(control.get_item_disabled("1"))
+        reset_deprecations()
+
+
+class ControlTests(TestCase):
+    def testTextControl(self):
+        attrs = {"type": "this is ignored",
+                 "name": "ath_Uname",
+                 "value": "",
+                 "maxlength": "20",
+                 "id": "foo"}
+        c = ClientForm.TextControl("texT", "ath_Uname", attrs)
+        c.fixup()
+        self.assert_(c.type == "text")
+        self.assert_(c.name == "ath_Uname")
+        self.assert_(c.id == "foo")
+        self.assert_(c.value == "")
+        self.assert_(str(c) == "<TextControl(ath_Uname=)>")
+        self.assert_(c.pairs() == [("ath_Uname", "")])
+        def bad_assign(c=c): c.type = "sometype"
+        self.assertRaises(AttributeError, bad_assign)
+        self.assert_(c.type == "text")
+        def bad_assign(c=c): c.name = "somename"
+        self.assertRaises(AttributeError, bad_assign)
+        self.assert_(c.name == "ath_Uname")
+        c.value = "2"
+        self.assert_(c.value == "2")
+        
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value is None)
+        
+        self.assert_(c.pairs() == [])
+        c.value = "2"                   # reset value...
+        self.assert_(str(c) == "<TextControl(ath_Uname=2)>")
+        def bad_assign(c=c): c.value = ["foo"]
+        self.assertRaises(TypeError, bad_assign)
+        self.assert_(c.value == "2")
+        self.assert_(not c.readonly)
+        c.readonly = True
+        def bad_assign(c=c): c.value = "foo"
+        self.assertRaises(AttributeError, bad_assign)
+        self.assert_(c.value == "2")
+        c.disabled = True
+        self.assert_(str(c) ==
+                     "<TextControl(ath_Uname=2) (disabled, readonly)>")
+        c.readonly = False
+        self.assert_(str(c) == "<TextControl(ath_Uname=2) (disabled)>")
+        self.assertRaises(AttributeError, bad_assign)
+        self.assert_(c.value == "2")
+        self.assert_(c.pairs() == [])
+        c.disabled = False
+        self.assert_(str(c) == "<TextControl(ath_Uname=2)>")
+
+        self.assert_(c.attrs.has_key("maxlength"))
+        for key in "name", "type", "value":
+            self.assert_(c.attrs.has_key(key))
+
+        # initialisation of readonly and disabled attributes
+        attrs["readonly"] = True
+        c = ClientForm.TextControl("text", "ath_Uname", attrs)
+        def bad_assign(c=c): c.value = "foo"
+        self.assertRaises(AttributeError, bad_assign)
+        del attrs["readonly"]
+        attrs["disabled"] = True
+        c = ClientForm.TextControl("text", "ath_Uname", attrs)
+        def bad_assign(c=c): c.value = "foo"
+        self.assertRaises(AttributeError, bad_assign)
+        del attrs["disabled"]
+        c = ClientForm.TextControl("hidden", "ath_Uname", attrs)
+        self.assert_(c.readonly)
+        def bad_assign(c=c): c.value = "foo"
+        self.assertRaises(AttributeError, bad_assign)
+
+    def testFileControl(self):
+        c = ClientForm.FileControl("file", "test_file", {})
+        fp = StringIO()
+        c.add_file(fp)
+        fp2 = StringIO()
+        c.add_file(fp2, None, "fp2 file test")
+        self.assert_(str(c) == '<FileControl(test_file=<Unnamed file>, fp2 file test)>')
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(str(c) == '<FileControl(test_file=<No files added>)>')
+
+    def testIsindexControl(self):
+        attrs = {"type": "this is ignored",
+                 "prompt": ">>>"}
+        c = ClientForm.IsindexControl("isIndex", None, attrs)
+        c.fixup()
+        self.assert_(c.type == "isindex")
+        self.assert_(c.name is None)
+        self.assert_(c.value == "")
+        self.assert_(str(c) == "<IsindexControl()>")
+        self.assert_(c.pairs() == [])
+        def set_type(c=c): c.type = "sometype"
+        self.assertRaises(AttributeError, set_type)
+        self.assert_(c.type == "isindex")
+        def set_name(c=c): c.name = "somename"
+        self.assertRaises(AttributeError, set_name)
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(TypeError, set_value, [None])
+        self.assert_(c.name is None)
+        c.value = "2"
+        self.assert_(c.value == "2")
+        self.assert_(str(c) == "<IsindexControl(2)>")
+        c.disabled = True
+        self.assert_(str(c) == "<IsindexControl(2) (disabled)>")
+        self.assertRaises(AttributeError, set_value, "foo")
+        self.assert_(c.value == "2")
+        self.assert_(c.pairs() == [])
+        c.readonly = True
+        self.assert_(str(c) == "<IsindexControl(2) (disabled, readonly)>")
+        self.assertRaises(AttributeError, set_value, "foo")
+        c.disabled = False
+        self.assert_(str(c) == "<IsindexControl(2) (readonly)>")
+        self.assertRaises(AttributeError, set_value, "foo")
+        c.readonly = False
+        self.assert_(str(c) == "<IsindexControl(2)>")
+
+        self.assert_(c.attrs.has_key("type"))
+        self.assert_(c.attrs.has_key("prompt"))
+        self.assert_(c.attrs["prompt"] == ">>>")
+        for key in "name", "value":
+            self.assert_(not c.attrs.has_key(key))
+
+        c.value = "foo 1 bar 2"
+        class FakeForm: action = "http://localhost/"
+        form = FakeForm()
+        self.assert_(c._click(form, (1,1), "request_data") == 
+                     ("http://localhost/?foo+1+bar+2", None, []))
+
+        c.value = "foo 1 bar 2"
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value is None)
+
+    def testIgnoreControl(self):
+        attrs = {"type": "this is ignored"}
+        c = ClientForm.IgnoreControl("reset", None, attrs)
+        self.assert_(c.type == "reset")
+        self.assert_(c.value is None)
+        self.assert_(str(c) == "<IgnoreControl(<None>=<None>)>")
+
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(AttributeError, set_value, "foo")
+        self.assert_(c.value is None)
+
+        # this is correct, but silly; basically nothing should happen
+        c.clear()
+        self.assert_(c.value is None)
+
+    def testSubmitControl(self):
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "value": "value_value",
+                 "img": "foo.gif"}
+        c = ClientForm.SubmitControl("submit", "name_value", attrs)
+        self.assert_(c.type == "submit")
+        self.assert_(c.name == "name_value")
+        self.assert_(c.value == "value_value")
+        self.assert_(str(c) == "<SubmitControl(name_value=value_value) (readonly)>")
+
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value is None)
+        c.value = "value_value"
+        c.readonly = True
+        
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(TypeError, set_value, ["foo"])
+        c.disabled = True
+        self.assertRaises(AttributeError, set_value, "value_value")
+        self.assert_(str(c) == "<SubmitControl(name_value=value_value) "
+                     "(disabled, readonly)>")
+        c.disabled = False
+        c.readonly = False
+        set_value("value_value")
+        self.assert_(str(c) == "<SubmitControl(name_value=value_value)>")
+        c.readonly = True
+
+        # click on button
+        form = ClientForm.HTMLForm("http://foo.bar.com/")
+        c.add_to_form(form)
+        self.assert_(c.pairs() == [])
+        pairs = c._click(form, (1,1), "pairs")
+        request = c._click(form, (1,1), "request")
+        data = c._click(form, (1,1), "request_data")
+        self.assert_(c.pairs() == [])
+        self.assert_(pairs == [("name_value", "value_value")])
+        self.assert_(request.get_full_url() ==
+                     "http://foo.bar.com/?name_value=value_value")
+        self.assert_(data ==
+                     ("http://foo.bar.com/?name_value=value_value", None, []))
+        c.disabled = True
+        pairs = c._click(form, (1,1), "pairs")
+        request = c._click(form, (1,1), "request")
+        data = c._click(form, (1,1), "request_data")
+        self.assert_(pairs == [])
+        # XXX not sure if should have '?' on end of this URL, or if it really matters...
+        self.assert_(request.get_full_url() == "http://foo.bar.com/")
+        self.assert_(data == ("http://foo.bar.com/", None, []))
+
+    def testImageControl(self):
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "img": "foo.gif"}
+        c = ClientForm.ImageControl("image", "name_value", attrs, index=0)
+        self.assert_(c.type == "image")
+        self.assert_(c.name == "name_value")
+        self.assert_(c.value == "")
+        self.assert_(str(c) == "<ImageControl(name_value=)>")
+
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value is None)
+        c.value = ""
+
+        # click, at coordinate (0, 55), on image
+        form = ClientForm.HTMLForm("http://foo.bar.com/")
+        c.add_to_form(form)
+        self.assert_(c.pairs() == [])
+        request = c._click(form, (0, 55), "request")
+        self.assert_(c.pairs() == [])
+        self.assert_(request.get_full_url() ==
+                     "http://foo.bar.com/?name_value.x=0&name_value.y=55")
+        self.assert_(c._click(form, (0,55), return_type="request_data") ==
+                     ("http://foo.bar.com/?name_value.x=0&name_value.y=55",
+                      None, []))
+        c.value = "blah"
+        request = c._click(form, (0, 55), "request")
+        self.assertEqual(request.get_full_url(), "http://foo.bar.com/?"
+                         "name_value.x=0&name_value.y=55&name_value=blah")
+
+        c.disabled = True
+        self.assertEqual(c.value, "blah")
+        self.assert_(str(c) == "<ImageControl(name_value=blah) (disabled)>")
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(AttributeError, set_value, "blah")
+        self.assert_(c._click(form, (1,1), return_type="pairs") == [])
+        c.readonly = True
+        self.assert_(str(c) == "<ImageControl(name_value=blah) "
+                     "(disabled, readonly)>")
+        self.assertRaises(AttributeError, set_value, "blah")
+        self.assert_(c._click(form, (1,1), return_type="pairs") == [])
+        c.disabled = c.readonly = False
+        self.assert_(c._click(form, (1,1), return_type="pairs") ==
+                     [("name_value.x", "1"),
+                      ("name_value.y", "1"),
+                      ('name_value', 'blah')])
+
+    def testCheckboxControl(self):
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "value": "value_value",
+                 "alt": "some string"}
+        form = DummyForm()
+        c = ClientForm.CheckboxControl("checkbox", "name_value", attrs)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(c.type == "checkbox")
+        self.assert_(c.name == "name_value")
+        self.assert_(c.value == [])
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["value_value"])
+        reset_deprecations()
+        def set_type(c=c): c.type = "sometype"
+        self.assertRaises(AttributeError, set_type)
+        self.assert_(c.type == "checkbox")
+        def set_name(c=c): c.name = "somename"
+        self.assertRaises(AttributeError, set_name)
+        self.assert_(c.name == "name_value")
+
+        # construct larger list from length-1 lists
+        c = ClientForm.CheckboxControl("checkbox", "name_value", attrs)
+        attrs2 = attrs.copy()
+        attrs2["value"] = "value_value2"
+        c2 = ClientForm.CheckboxControl("checkbox", "name_value", attrs2)
+        c2.add_to_form(form)
+        c.merge_control(c2)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(str(c) == "<CheckboxControl("
+                     "name_value=[value_value, value_value2])>")
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["value_value", "value_value2"])
+
+        attrs = c.get_item_attrs("value_value")
+        for key in "alt", "name", "value", "type":
+            self.assert_(attrs.has_key(key))
+        self.assertRaises(ItemNotFoundError, c.get_item_attrs, "oops")
+        reset_deprecations()
+
+        def set_value(value, c=c): c.value = value
+
+        c.value = ["value_value", "value_value2"]
+        self.assert_(c.value == ["value_value", "value_value2"])
+        c.value = ["value_value"]
+        self.assertEqual(c.value, ["value_value"])
+        self.assertRaises(ItemNotFoundError, set_value, ["oops"])
+        self.assertRaises(TypeError, set_value, "value_value")
+        c.value = ["value_value2"]
+        self.assert_(c.value == ["value_value2"])
+        hide_deprecations()
+        c.toggle("value_value")
+        self.assert_(c.value == ["value_value", "value_value2"])
+        c.toggle("value_value2")
+        reset_deprecations()
+        self.assert_(c.value == ["value_value"])
+        hide_deprecations()
+        self.assertRaises(ItemNotFoundError, c.toggle, "oops")
+        reset_deprecations()
+
+        self.assert_(c.value == ["value_value"])
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value == [])
+        
+        # set
+        hide_deprecations()
+        c.set(True, "value_value")
+        self.assert_(c.value == ["value_value"])
+        c.set(True, "value_value2")
+        self.assert_(c.value == ["value_value", "value_value2"])
+        c.set(True, "value_value2")
+        self.assert_(c.value == ["value_value", "value_value2"])
+        c.set(False, "value_value2")
+        self.assert_(c.value == ["value_value"])
+        c.set(False, "value_value2")
+        self.assert_(c.value == ["value_value"])
+        self.assertRaises(ItemNotFoundError, c.set, True, "oops")
+        self.assertRaises(TypeError, c.set, True, ["value_value"])
+        self.assertRaises(ItemNotFoundError, c.set, False, "oops")
+        self.assertRaises(TypeError, c.set, False, ["value_value"])
+        reset_deprecations()
+
+        self.assert_(str(c) == "<CheckboxControl("
+                     "name_value=[*value_value, value_value2])>")
+        c.disabled = True
+        self.assertRaises(AttributeError, set_value, ["value_value"])
+        self.assert_(str(c) == "<CheckboxControl("
+                     "name_value=[*value_value, value_value2]) "
+                     "(disabled)>")
+        self.assert_(c.value == ["value_value"])
+        self.assert_(c.pairs() == [])
+        c.readonly = True
+        self.assertRaises(AttributeError, set_value, ["value_value"])
+        self.assert_(str(c) == "<CheckboxControl("
+                     "name_value=[*value_value, value_value2]) "
+                     "(disabled, readonly)>")
+        self.assert_(c.value == ["value_value"])
+        self.assert_(c.pairs() == [])
+        c.disabled = False
+        self.assert_(str(c) == "<CheckboxControl("
+                     "name_value=[*value_value, value_value2]) "
+                     "(readonly)>")
+        self.assertRaises(AttributeError, set_value, ["value_value"])
+        self.assert_(c.value == ["value_value"])
+        self.assert_(c.pairs() == [("name_value", "value_value")])
+        c.readonly = False
+        c.value = []
+        self.assert_(c.value == [])
+
+    def testSelectControlMultiple(self):
+        import copy
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "value": "value_value",
+                 "alt": "some string",
+                 "label": "contents_value",
+                 "contents": "contents_value",
+                 "__select": {"type": "this is ignored",
+                              "name": "select_name",
+                              "multiple": "",
+                              "alt": "alt_text"}}
+        form = DummyForm()
+        # with Netscape / IE default selection...
+        c = ClientForm.SelectControl("select", "select_name", attrs)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(c.type == "select")
+        self.assert_(c.name == "select_name")
+        self.assert_(c.value == [])
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["value_value"])
+        reset_deprecations()
+        self.assert_(c.attrs.has_key("name"))
+        self.assert_(c.attrs.has_key("type"))
+        self.assert_(c.attrs["alt"] == "alt_text")
+        # ... and with RFC 1866 default selection
+        c = ClientForm.SelectControl("select", "select_name", attrs, select_default=True)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(c.value == ["value_value"])
+
+        # construct larger list from length-1 lists
+        c = ClientForm.SelectControl("select", "select_name", attrs)
+        attrs2 = attrs.copy()
+        attrs2["value"] = "value_value2"
+        c2 = ClientForm.SelectControl("select", "select_name", attrs2)
+        c2.add_to_form(form)
+        c.merge_control(c2)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(str(c) == "<SelectControl("
+                     "select_name=[value_value, value_value2])>")
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["value_value", "value_value2"])
+
+        # get_item_attrs
+        attrs3 = c.get_item_attrs("value_value")
+        reset_deprecations()
+        self.assert_(attrs3.has_key("alt"))
+        self.assert_(not attrs3.has_key("multiple"))
+        # HTML attributes dictionary should have been copied by ListControl
+        # constructor.
+        attrs["new_attr"] = "new"
+        attrs2["new_attr2"] = "new2"
+        for key in ("new_attr", "new_attr2"):
+            self.assert_(not attrs3.has_key(key))
+        hide_deprecations()
+        self.assertRaises(ItemNotFoundError, c.get_item_attrs, "oops")
+        reset_deprecations()
+
+        c.value = ["value_value", "value_value2"]
+        self.assert_(c.value == ["value_value", "value_value2"])
+        c.value = ["value_value"]
+        self.assertEqual(c.value, ["value_value"])
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(ItemNotFoundError, set_value, ["oops"])
+        self.assertRaises(TypeError, set_value, "value_value")
+        self.assertRaises(TypeError, set_value, None)
+        c.value = ["value_value2"]
+        self.assert_(c.value == ["value_value2"])
+        hide_deprecations()
+        c.toggle("value_value")
+        self.assert_(c.value == ["value_value", "value_value2"])
+        c.toggle("value_value2")
+        self.assert_(c.value == ["value_value"])
+        self.assertRaises(ItemNotFoundError, c.toggle, "oops")
+        self.assert_(c.value == ["value_value"])
+        reset_deprecations()
+
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value == [])
+        
+        # test ordering of items
+        c.value = ["value_value2", "value_value"]
+        self.assert_(c.value == ["value_value", "value_value2"])
+        # set
+        hide_deprecations()
+        c.set(True, "value_value")
+        self.assert_(c.value == ["value_value", "value_value2"])
+        c.set(True, "value_value2")
+        self.assert_(c.value == ["value_value", "value_value2"])
+        c.set(False, "value_value")
+        self.assert_(c.value == ["value_value2"])
+        c.set(False, "value_value")
+        self.assert_(c.value == ["value_value2"])
+        self.assertRaises(ItemNotFoundError, c.set, True, "oops")
+        self.assertRaises(TypeError, c.set, True, ["value_value"])
+        self.assertRaises(ItemNotFoundError, c.set, False, "oops")
+        self.assertRaises(TypeError, c.set, False, ["value_value"])
+        reset_deprecations()
+        c.value = []
+        self.assert_(c.value == [])
+
+    def testSelectControlMultiple_label(self):
+        import ClientForm
+##         <SELECT name=year>
+##          <OPTION value=0 label="2002">current year</OPTION>
+##          <OPTION value=1>2001</OPTION>
+##          <OPTION>2000</OPTION>
+##         </SELECT>
+        attrs = {"type": "ignored",
+                 "name": "year",
+                 "value": "0",
+                 "label": "2002",
+                 "contents": "current year",
+                 "__select": {"type": "this is ignored",
+                              "name": "select_name",
+                              "multiple": ""}}
+        attrs2 = {"type": "ignored",
+                  "name": "year",
+                  "value": "1",
+                  "label": "2001",  # label defaults to contents
+                  "contents": "2001",
+                 "__select": {"type": "this is ignored",
+                              "name": "select_name",
+                              "multiple": ""}}
+        attrs3 = {"type": "ignored",
+                  "name": "year",
+                  "value": "2000",  # value defaults to contents
+                  "label": "2000",  # label defaults to contents
+                  "contents": "2000",
+                 "__select": {"type": "this is ignored",
+                              "name": "select_name",
+                              "multiple": ""}}
+        c = ClientForm.SelectControl("select", "select_name", attrs)
+        c2 = ClientForm.SelectControl("select", "select_name", attrs2)
+        c3 = ClientForm.SelectControl("select", "select_name", attrs3)
+        form = DummyForm()
+        c.merge_control(c2)
+        c.merge_control(c3)
+        c.add_to_form(form)
+        c.fixup()
+
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["0", "1", "2000"])
+        self.assert_(c.possible_items(by_label=True) ==
+                     ["2002", "2001", "2000"])
+
+        self.assert_(c.value == [])
+        c.toggle("2002", by_label=True)
+        self.assert_(c.value == ["0"])
+        c.toggle("0")
+        self.assert_(c.value == [])
+        c.toggle("0")
+        self.assert_(c.value == ["0"])
+        self.assert_(c.get_value_by_label() == ["2002"])
+        c.toggle("2002", by_label=True)
+        self.assertRaises(ItemNotFoundError, c.toggle, "blah", by_label=True)
+        self.assert_(c.value == [])
+        c.toggle("2000")
+        reset_deprecations()
+        self.assert_(c.value == ["2000"])
+        self.assert_(c.get_value_by_label() == ["2000"])
+
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(ItemNotFoundError, set_value, ["2002"])
+        self.assertRaises(TypeError, set_value, "1")
+        self.assertRaises(TypeError, set_value, None)
+        self.assert_(c.value == ["2000"])
+        c.value = ["0"]
+        self.assertEqual(c.value, ["0"])
+        c.value = []
+        self.assertRaises(TypeError, c.set_value_by_label, "2002")
+        c.set_value_by_label(["2002"])
+        self.assert_(c.value == ["0"])
+        self.assert_(c.get_value_by_label() == ["2002"])
+        c.set_value_by_label(["2000"])
+        self.assert_(c.value == ["2000"])
+        self.assert_(c.get_value_by_label() == ["2000"])
+        c.set_value_by_label(["2000", "2002"])
+        self.assert_(c.value == ["0", "2000"])
+        self.assert_(c.get_value_by_label() == ["2002", "2000"])
+
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value == [])
+
+        c.set_value_by_label(["2000", "2002"])
+        hide_deprecations()
+        c.set(False, "2002", by_label=True)
+        self.assert_(c.get_value_by_label() == c.value == ["2000"])
+        c.set(False, "2002", by_label=True)
+        self.assert_(c.get_value_by_label() == c.value == ["2000"])
+        c.set(True, "2002", by_label=True)
+        self.assert_(c.get_value_by_label() == ["2002", "2000"])
+        self.assert_(c.value == ["0", "2000"])
+        c.set(False, "2000", by_label=True)
+        self.assert_(c.get_value_by_label() == ["2002"])
+        self.assert_(c.value == ["0"])
+        c.set(True, "2001", by_label=True)
+        self.assert_(c.get_value_by_label() == ["2002", "2001"])
+        self.assert_(c.value == ["0", "1"])
+        self.assertRaises(ItemNotFoundError, c.set, True, "blah",
+                          by_label=True)
+        self.assertRaises(ItemNotFoundError, c.set,
+                          False, "blah", by_label=True)
+        reset_deprecations()
+
+    def testSelectControlSingle_label(self):
+        import ClientForm
+##         <SELECT name=year>
+##          <OPTION value=0 label="2002">current year</OPTION>
+##          <OPTION value=1>2001</OPTION>
+##          <OPTION>2000</OPTION>
+##         </SELECT>
+        attrs = {"type": "ignored",
+                 "name": "year",
+                 "value": "0",
+                 "label": "2002",
+                 "contents": "current year",
+                 "__select": {"type": "this is ignored",
+                              "name": "select_name"}}
+        attrs2 = {"type": "ignored",
+                  "name": "year",
+                  "value": "1",
+                  "label": "2001",  # label defaults to contents
+                  "contents": "2001",
+                 "__select": {"type": "this is ignored",
+                              "name": "select_name"}}
+        attrs3 = {"type": "ignored",
+                  "name": "year",
+                  "value": "2000",  # value defaults to contents
+                  "label": "2000",  # label defaults to contents
+                  "contents": "2000",
+                 "__select": {"type": "this is ignored",
+                              "name": "select_name"}}
+        c = ClientForm.SelectControl("select", "select_name", attrs)
+        c2 = ClientForm.SelectControl("select", "select_name", attrs2)
+        c3 = ClientForm.SelectControl("select", "select_name", attrs3)
+        form = DummyForm()
+        c.merge_control(c2)
+        c.merge_control(c3)
+        c.add_to_form(form)
+        c.fixup()
+
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["0", "1", "2000"])
+        self.assert_(c.possible_items(by_label=True) ==
+                     ["2002", "2001", "2000"])
+        reset_deprecations()
+
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(ItemNotFoundError, set_value, ["2002"])
+        self.assertRaises(TypeError, set_value, "1")
+        self.assertRaises(TypeError, set_value, None)
+        self.assert_(c.value == ["0"])
+        c.value = []
+        self.assert_(c.value == [])
+        c.value = ["0"]
+        self.assert_(c.value == ["0"])
+
+        c.value = []
+        self.assertRaises(TypeError, c.set_value_by_label, "2002")
+        self.assertRaises(ItemCountError, c.set_value_by_label,
+                          ["2000", "2001"])
+        self.assertRaises(ItemNotFoundError, c.set_value_by_label, ["foo"])
+        c.set_value_by_label(["2002"])
+        self.assert_(c.value == ["0"])
+        self.assert_(c.get_value_by_label() == ["2002"])
+        c.set_value_by_label(["2000"])
+        self.assert_(c.value == ["2000"])
+        self.assert_(c.get_value_by_label() == ["2000"])
+
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value == [])
+
+    def testSelectControlSingle(self):
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "value": "value_value",
+                 "label": "contents_value",
+                 "contents": "contents_value",
+                 "__select": {"type": "this is ignored",
+                              "name": "select_name",
+                              "alt": "alt_text"}}
+        # Netscape and IE behaviour...
+        c = ClientForm.SelectControl("select", "select_name", attrs)
+        form = DummyForm()
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(c.type == "select")
+        self.assert_(c.name == "select_name")
+        self.assert_(c.value == ["value_value"])
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["value_value"])
+        reset_deprecations()
+        self.assert_(c.attrs.has_key("name"))
+        self.assert_(c.attrs.has_key("type"))
+        self.assert_(c.attrs["alt"] == "alt_text")
+        # ...and RFC 1866 behaviour are identical (unlike multiple SELECT).
+        c = ClientForm.SelectControl("select", "select_name", attrs,
+                                     select_default=1)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(c.value == ["value_value"])
+
+        # construct larger list from length-1 lists
+        c = ClientForm.SelectControl("select", "select_name", attrs)
+        attrs2 = attrs.copy()
+        attrs2["value"] = "value_value2"
+        c2 = ClientForm.SelectControl("select", "select_name", attrs2)
+        c.merge_control(c2)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(str(c) == "<SelectControl("
+                     "select_name=[*value_value, value_value2])>")
+        c.value = []
+        self.assert_(c.value == [])
+        self.assert_(str(c) == "<SelectControl("
+                     "select_name=[value_value, value_value2])>")
+        c.value = ["value_value"]
+        self.assert_(c.value == ["value_value"])
+        self.assert_(str(c) == "<SelectControl("
+                     "select_name=[*value_value, value_value2])>")
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["value_value", "value_value2"])
+        reset_deprecations()
+
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(ItemCountError, set_value,
+                          ["value_value", "value_value2"])
+        self.assertRaises(TypeError, set_value, "value_value")
+        self.assertRaises(TypeError, set_value, None)
+        c.value = ["value_value2"]
+        self.assert_(c.value == ["value_value2"])
+        c.value = ["value_value"]
+        self.assert_(c.value == ["value_value"])
+        self.assertRaises(ItemNotFoundError, set_value, ["oops"])
+        self.assert_(c.value == ["value_value"])
+        hide_deprecations()
+        c.toggle("value_value")
+        self.assertRaises(ItemNotFoundError, c.toggle, "oops")
+        self.assertRaises(TypeError, c.toggle, ["oops"])
+        reset_deprecations()
+        self.assert_(c.value == [])
+        c.value = ["value_value"]
+        self.assert_(c.value == ["value_value"])
+        # nothing selected is allowed
+        c.value = []
+        self.assert_(c.value == [])
+
+        hide_deprecations()
+        c.set(True, "value_value")
+        self.assert_(c.value == ["value_value"])
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assert_(c.value == [])
+        
+        # set
+        c.set(True, "value_value")
+        self.assert_(c.value == ["value_value"])
+        c.set(True, "value_value")
+        self.assert_(c.value == ["value_value"])
+        c.set(True, "value_value2")
+        self.assert_(c.value == ["value_value2"])
+        c.set(False, "value_value")
+        self.assert_("value_value2")
+        c.set(False, "value_value2")
+        self.assert_(c.value == [])
+        c.set(False, "value_value2")
+        self.assert_(c.value == [])
+        self.assertRaises(ItemNotFoundError, c.set, True, "oops")
+        self.assertRaises(TypeError, c.set, True, ["value_value"])
+        self.assertRaises(ItemNotFoundError, c.set, False, "oops")
+        self.assertRaises(TypeError, c.set, False, ["value_value"])
+        reset_deprecations()
+
+    def testRadioControl(self):
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "value": "value_value",
+                 "id": "blah"}
+        # Netscape and IE behaviour...
+        c = ClientForm.RadioControl("radio", "name_value", attrs)
+        form = DummyForm()
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(c.type == "radio")
+        self.assert_(c.name == "name_value")
+        self.assert_(c.id == "blah")
+        self.assert_(c.value == [])
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["value_value"])
+        reset_deprecations()
+        # ...and RFC 1866 behaviour
+        c = ClientForm.RadioControl("radio", "name_value", attrs,
+                                    select_default=True)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(c.value == ["value_value"])
+
+        # construct larger list from length-1 lists
+        c = ClientForm.RadioControl("radio", "name_value", attrs,
+                                    select_default=True)
+        attrs2 = attrs.copy()
+        attrs2["value"] = "value_value2"
+        c2 = ClientForm.RadioControl("radio", "name_value", attrs2,
+                                     select_default=True)
+        c.merge_control(c2)
+        c.add_to_form(form)
+        c.fixup()
+        self.assert_(str(c) == "<RadioControl("
+                     "name_value=[*value_value, value_value2])>")
+        hide_deprecations()
+        self.assert_(c.possible_items() == ["value_value", "value_value2"])
+        reset_deprecations()
+
+        def set_value(value, c=c): c.value = value
+        self.assertRaises(ItemCountError, set_value,
+                          ["value_value", "value_value2"])
+        self.assertRaises(TypeError, set_value, "value_value")
+        self.assertEqual(c.value, ["value_value"])
+        c.value = ["value_value2"]
+        self.assertEqual(c.value, ["value_value2"])
+        c.value = ["value_value"]
+        self.assertEqual(c.value, ["value_value"])
+        self.assertRaises(ItemNotFoundError, set_value, ["oops"])
+        self.assertEqual(c.value, ["value_value"])
+        hide_deprecations()
+        c.toggle("value_value")
+        self.assertEqual(c.value, [])
+        c.toggle("value_value")
+        self.assertEqual(c.value, ["value_value"])
+        self.assertRaises(TypeError, c.toggle, ["value_value"])
+        self.assertEqual(c.value, ["value_value"])
+        # nothing selected is allowed
+        c.value = []
+        self.assertEqual(c.value, [])
+
+        c.set(True, "value_value")
+        reset_deprecations()
+        self.assertEqual(c.value, ["value_value"])
+        c.readonly = True
+        self.assertRaises(AttributeError, c.clear)
+        c.readonly = False
+        c.clear()
+        self.assertEqual(c.value, [])
+        
+        # set
+        hide_deprecations()
+        c.set(True, "value_value")
+        self.assertEqual(c.value, ["value_value"])
+        c.set(True, "value_value")
+        self.assertEqual(c.value, ["value_value"])
+        c.set(True, "value_value2")
+        self.assertEqual(c.value, ["value_value2"])
+        c.set(False, "value_value")
+        self.assert_("value_value2")
+        c.set(False, "value_value2")
+        self.assertEqual(c.value, [])
+        c.set(False, "value_value2")
+        self.assertEqual(c.value, [])
+        self.assertRaises(ItemNotFoundError, c.set, True, "oops")
+        self.assertRaises(TypeError, c.set, True, ["value_value"])
+        self.assertRaises(ItemNotFoundError, c.set, False, "oops")
+        self.assertRaises(TypeError, c.set, False, ["value_value"])
+        reset_deprecations()
+
+        # tests for multiple identical values
+
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "value": "value_value",
+                 "id": "name_value_1"}
+        c1 = ClientForm.RadioControl("radio", "name_value", attrs)
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "value": "value_value",
+                 "id": "name_value_2",
+                 "checked": "checked"}
+        c2 = ClientForm.RadioControl("radio", "name_value", attrs)
+        attrs = {"type": "this is ignored",
+                 "name": "name_value",
+                 "value": "another_value",
+                 "id": "name_value_3",
+                 "__label": {"__text": "Third Option"}}
+        c3 = ClientForm.RadioControl("radio", "name_value", attrs)
+        form = DummyForm()
+        c1.merge_control(c2)
+        c1.merge_control(c3)
+        c1.add_to_form(form)
+        c1.fixup()
+        self.assertEqual(c1.value, ['value_value'])
+        hide_deprecations()
+        self.assertEqual(
+            c1.possible_items(),
+            ['value_value', 'value_value', 'another_value'])
+        reset_deprecations()
+        self.assertEqual(c1.value, ['value_value'])
+        self.failIf(c1.items[0].selected)
+        self.failUnless(c1.items[1].selected)
+        self.failIf(c1.items[2].selected)
+        c1.value = ['value_value']  # should be no change
+        self.failUnless(c1.items[1].selected)
+        self.assertEqual(c1.value, ['value_value'])
+        c1.value = ['another_value']
+        self.failUnless(c1.items[2].selected)
+        self.assertEqual(c1.value, ['another_value'])
+        c1.value = ['value_value']
+        self.failUnless(c1.items[0].selected)
+        self.assertEqual(c1.value, ['value_value'])
+
+        # id labels
+        form._id_to_labels['name_value_1'] = [
+            ClientForm.Label({'for': 'name_value_1', '__text':'First Option'})]
+        form._id_to_labels['name_value_2'] = [
+            ClientForm.Label({'for': 'name_value_2',
+                              '__text':'Second Option'})]
+        form._id_to_labels['name_value_3'] = [
+            ClientForm.Label({'for': 'name_value_3',
+                              '__text':'Last Option'})]  # notice __label above
+        self.assertEqual([l.text for l in c1.items[0].get_labels()],
+                         ['First Option'])
+        self.assertEqual([l.text for l in c1.items[1].get_labels()],
+                         ['Second Option'])
+        self.assertEqual([l.text for l in c1.items[2].get_labels()],
+                         ['Third Option', 'Last Option'])
+        self.assertEqual(c1.get_value_by_label(), ['First Option'])
+        c1.set_value_by_label(['Second Option'])
+        self.assertEqual(c1.get_value_by_label(), ['Second Option'])
+        self.assertEqual(c1.value, ['value_value'])
+        c1.set_value_by_label(['Third Option'])
+        self.assertEqual(c1.get_value_by_label(), ['Third Option'])
+        self.assertEqual(c1.value, ['another_value'])
+        c1.items[1].selected = True
+        self.assertEqual(c1.get_value_by_label(), ['Second Option'])
+        self.assertEqual(c1.value, ['value_value'])
+        c1.set_value_by_label(['Last Option'])  # by second label
+        self.assertEqual(c1.get_value_by_label(), ['Third Option'])
+        self.assertEqual(c1.value, ['another_value'])
+        c1.set_value_by_label(['irst'])  # by substring
+        self.assertEqual(c1.get_value_by_label(), ['First Option'])
+
+class FormTests(TestCase):
+    base_uri = "http://auth.athensams.net/"
+
+    def test_find_control(self):
+        f = StringIO("""\
+<form>
+    <label for="form.title"> Book Title </label></td>
+    <input type="text" id="form.title" name="form.title"
+        value="The Grapes of Wrath" />
+
+    <label for="form.quality">Book Quality</label></td>
+    <select id="form.quality" name="form.country">
+        <option>Good</option>
+        <option>Bad</option>
+    </select>
+
+    <label><input type="checkbox" id="form.genre.western" name="form.genre"
+        value="western" /> Western</label>
+    <label><input type="checkbox" id="form.genre.horror" name="form.genre"
+        value="horror" /> Horror</label>
+
+    <label for="form.password">Password</label>
+    <input type="password" id="pswd1" name="password" value="123" />
+    <input type="password" id="pswd2" name="password" value="123" />
+</form>
+""")
+        form = ClientForm.ParseFile(f, "http://example.com/",
+                                    backwards_compat=False)[0]
+        for compat in True, False:
+            form.backwards_compat = compat
+            fc = form.find_control
+
+            self.assertEqual(fc("form.title").id, "form.title")
+            self.assertEqual(fc("form.title", nr=0).id, "form.title")
+            if compat:
+                self.assertEqual(fc("password").id, "pswd1")
+            else:
+                self.assertRaises(AmbiguityError, fc, "password")
+            self.assertEqual(fc("password", id="pswd2").id, "pswd2")
+            self.assertEqual(fc("password", nr=0).id, "pswd1")
+            self.assertRaises(ControlNotFoundError, fc, "form.title", nr=1)
+            self.assertRaises(ControlNotFoundError, fc, nr=50)
+            self.assertRaises(ValueError, fc, nr=-1)
+            self.assertRaises(ControlNotFoundError, fc, label="Bananas")
+
+            # label
+            self.assertEqual(fc(label="Title").id, "form.title")
+            self.assertEqual(fc(label="Book Title").id, "form.title")
+            self.assertRaises(ControlNotFoundError, fc, label=" Book Title ")
+            self.assertRaises(ControlNotFoundError, fc, label="Bananas")
+            self.assertRaises(ControlNotFoundError, fc, label="title")
+
+            self.assertEqual(fc(label="Book", nr=0).id, "form.title")
+            self.assertEqual(fc(label="Book", nr=1).id, "form.quality")
+            if compat:
+                self.assertEqual(fc(label="Book").id, "form.title")
+            else:
+                self.assertRaises(AmbiguityError, fc, label="Book")
+
+    def test_find_nameless_control(self):
+        data = """\
+<form>
+  <input type="checkbox"/>
+  <input type="checkbox" id="a" onclick="blah()"/>
+</form>
+"""
+        f = StringIO(data)
+        form = ClientForm.ParseFile(f, "http://example.com/",
+                                    backwards_compat=False)[0]
+        self.assertRaises(
+            AmbiguityError,
+            form.find_control, type="checkbox", name=ClientForm.Missing)
+        ctl = form.find_control(type="checkbox", name=ClientForm.Missing, nr=1)
+        self.assertEqual(ctl.id, "a")
+
+    def test_deselect_disabled(self):
+        def get_new_form(f, compat):
+            f.seek(0)
+            form = ClientForm.ParseFile(f, "http://example.com/",
+                                        backwards_compat=False)[0]
+            form.backwards_compat = compat
+            return form
+
+        f = StringIO("""\
+<form>
+    <input type="checkbox" name="p" value="a" disabled checked></input>
+    <input type="checkbox" name="p" value="b"></input>
+    <input type="checkbox" name="p" value="c"></input>
+</form>
+""")
+        for compat in [False]:#True, False:
+            def new_form(compat=compat, f=f, get_new_form=get_new_form):
+                form = get_new_form(f, compat)
+                ctl = form.find_control("p")
+                a = ctl.get("a")
+                return ctl, a
+            ctl, a = new_form()
+            ctl.value = ["b"]
+
+            # :-((
+            if compat:
+                # rationale: allowed to deselect, but not select, disabled
+                # items
+                ctl, a = new_form()
+                self.assertRaises(AttributeError, setattr, a, "selected", True)
+                self.assertRaises(AttributeError, setattr, ctl, "value", ["a"])
+                a.selected = False
+                ctl, a = new_form()
+                ctl.value = ["b"]
+                self.assertEqual(a.selected, False)
+                self.assertEqual(ctl.value, ["b"])
+                ctl, a = new_form()
+                self.assertRaises(AttributeError,
+                                  setattr, ctl, "value", ["a", "b"])
+            else:
+
+                # rationale: Setting an individual item's selected state to its
+                # present value is a no-op, as is setting the whole control
+                # value where an item name doesn't appear in the new value, but
+                # that item is disabled anyway (but an item name that does
+                # appear in the new value is treated an explicit request that
+                # that item name get sent to the server).  However, if the
+                # item's state does change, both selecting and deselecting are
+                # disallowed for disabled items.
+
+                ctl, a = new_form()
+                self.assertRaises(AttributeError, setattr, a, "selected", True)
+                ctl, a = new_form()
+                self.assertRaises(AttributeError, setattr, ctl, "value", ["a"])
+                ctl, a = new_form()
+                self.assertRaises(AttributeError,
+                                  setattr, a, "selected", False)
+                ctl.value = ["b"]
+                self.assertEqual(a.selected, True)
+                self.assertEqual(ctl.value, ["b"])
+                ctl, a = new_form()
+                self.assertRaises(AttributeError,
+                                  setattr, ctl, "value", ["a", "b"])
+
+        f = StringIO("""\
+<form>
+    <input type="radio" name="p" value="a" disabled checked></input>
+    <input type="radio" name="p" value="b"></input>
+    <input type="radio" name="p" value="c"></input>
+</form>
+""")
+
+        for compat in [False]:#True, False:
+            def new_form(compat=compat, f=f, get_new_form=get_new_form):
+                form = get_new_form(f, compat)
+                ctl = form.find_control("p")
+                a = ctl.get("a")
+                return ctl, a
+            ctl, a = new_form()
+            ctl.value = ["b"]
+
+            if compat:
+                ctl, a = new_form()
+                self.assertRaises(AttributeError, setattr, a, "selected", True)
+                self.assertRaises(AttributeError, setattr, ctl, "value", ["a"])
+                a.selected = False
+                ctl, a = new_form()
+                ctl.value = ["b"]
+                self.assertEqual(a.selected, False)
+                self.assertEqual(ctl.value, ["b"])
+                ctl, a = new_form()
+                self.assertRaises(ItemCountError,
+                                  setattr, ctl, "value", ["a", "b"])
+            else:
+                ctl, a = new_form()
+                self.assertRaises(AttributeError, setattr, a, "selected", True)
+                ctl, a = new_form()
+                self.assertRaises(AttributeError, setattr, ctl, "value", ["a"])
+                ctl, a = new_form()
+                self.assertRaises(AttributeError, setattr, a, "selected", False)
+                ctl.value = ["b"]
+                self.assertEqual(a.selected, False)
+                self.assertEqual(ctl.value, ["b"])
+                ctl, a = new_form()
+                self.assertRaises(ItemCountError,
+                                  setattr, ctl, "value", ["a", "b"])
+
+    def test_click(self):
+        file = StringIO(
+"""<form action="abc" name="myform">
+
+<input type="submit" name="foo"></input>
+<input type="submit" name="bar"></input>
+</form>
+""")
+        form = ClientForm.ParseFile(file, "http://blah/",
+                                    backwards_compat=False)[0]
+        self.assertRaises(ControlNotFoundError, form.click, nr=2)
+        self.assert_(form.click().get_full_url() == "http://blah/abc?foo=")
+        self.assert_(form.click(name="bar").get_full_url() == "http://blah/abc?bar=")
+
+        for method in ["GET", "POST"]:
+            file = StringIO(
+"""<form method="%s" action="abc?bang=whizz#doh" name="myform">
+
+<input type="submit" name="foo"></input>
+</form>
+""" % method)
+            # " (this line is here for emacs)
+            form = ClientForm.ParseFile(file, "http://blah/",
+                                        backwards_compat=False)[0]
+            if method == "GET":
+                url = "http://blah/abc?foo="
+            else:
+                url = "http://blah/abc?bang=whizz"
+            self.assert_(form.click().get_full_url() == url)
+
+    def testAuth(self):
+        file = open("./testdata/Auth.html", "r")
+        forms = ClientForm.ParseFile(file, self.base_uri,
+                                     backwards_compat=False)
+        self.assert_(len(forms) == 1)
+        form = forms[0]
+        self.assert_(form.action ==
+                     "http://auth.athensams.net/"
+                     "?ath_returl=%22http%3A%2F%2Ftame.mimas.ac.uk%2Fisicgi"
+                     "%2FWOS-login.cgi%22&ath_dspid=MIMAS.WOS")
+
+        self.assertRaises(ControlNotFoundError,
+                          lambda form=form: form.toggle("d'oh", "oops"))
+        self.assertRaises(ControlNotFoundError, lambda form=form: form["oops"])
+        def bad_assign(form=form): form["oops"] = ["d'oh"]
+        self.assertRaises(ControlNotFoundError, bad_assign)
+
+        self.assertRaises(ValueError, form.find_control)
+
+        keys = ["ath_uname", "ath_passwd"]
+        values = ["", ""]
+        types = ["text", "password"]
+        for i in range(len(keys)):
+            key = keys[i]
+            c = form.find_control(key)
+            self.assert_(c.value == values[i])
+            self.assert_(c.type == types[i])
+        c = form.find_control(type="image")
+        self.assert_(c.name is None)
+        self.assert_(c.value == "")
+        self.assert_(c.type == "image")
+
+        form["ath_uname"] = "jbloggs"
+        form["ath_passwd"] = "foobar"
+
+        self.assert_(form.click_pairs() ==
+                     [("ath_uname", "jbloggs"),
+                      ("ath_passwd", "foobar")])
+
+    def testSearchType(self):
+        file = open("./testdata/SearchType.html", "r")
+        forms = ClientForm.ParseFile(file, self.base_uri,
+                                     backwards_compat=False)
+        self.assert_(len(forms) == 1)
+        form = forms[0]
+
+        keys = ["SID", "SESSION_DIR", "Full Search", "Easy Search",
+                "New Session", "Log off", "Form", "JavaScript"]
+        values = ["PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0",
+                  "", "", "", "", "", "Welcome", "No"]
+        types = ["hidden", "hidden", "image", "image", "image", "image",
+                 "hidden", "hidden"]
+        for i in range(len(keys)):
+            key = keys[i]
+            self.assert_(form.find_control(key).value == values[i])
+            self.assert_(form.find_control(key).type == types[i])
+
+        pairs = form.click_pairs("Full Search")
+        self.assert_(pairs == [
+            ("SID", "PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0"),
+            ("SESSION_DIR", ""), ("Full Search.x", "1"), ("Full Search.y", "1"),
+            ("Form", "Welcome"), ("JavaScript", "No")])
+
+    def testFullSearch(self):
+        pass  # XXX
+
+    def testGeneralSearch(self):
+        file = open("./testdata/GeneralSearch.html", "r")
+        forms = ClientForm.ParseFile(file, self.base_uri,
+                                     backwards_compat=False)
+        self.assert_(len(forms) == 1)
+        form = forms[0]
+
+        keys = ["SID", "SESSION_DIR",
+                "Home", "Date & Database Limits", "Cited Ref Search",
+                "Log off", "Search",
+                "topic", "titleonly", "author", "journal", "address",
+                "Search", "Save query", "Clear",
+                "languagetype", "doctype", "Sort",
+                "Form", "Func"]
+        values = ["PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0", "",
+                  "", "", "", "", "",
+                  "", [], "", "", "",
+                  "", "", "",
+                  ["All languages"], ["All document types"], ["Latest date"],
+                  "General", "Search"]
+        types = ["hidden", "hidden",
+                 "image", "image", "image", "image", "image",
+                 "text", "checkbox", "text", "text", "text",
+                 "image", "image", "image",
+                 "select", "select", "select",
+                 "hidden", "hidden"]
+        fc = form.find_control
+        for i in range(len(keys)):
+            name = keys[i]
+            type = types[i]
+            self.assertEqual(fc(name, nr=0).value, form.get_value(name, nr=0))
+            self.assertEqual(fc(name, nr=0).value, values[i])
+            self.assertEqual(fc(name, nr=0).type, type)
+            self.assertEqual(fc(name, type, nr=0).name, name)
+        self.assert_(fc(type="hidden", nr=0).name == "SID")
+        self.assert_(fc(type="image", nr=0).name == "Home")
+        self.assert_(fc(nr=6).name == "Search")
+        self.assertRaises(ControlNotFoundError, fc, nr=50)
+        self.assertRaises(ValueError, fc, nr=-1)
+        self.assert_(fc("Search", "image", nr=0).name == "Search")
+        self.assertRaises(ControlNotFoundError, fc, "Search", "hidden")
+        s0 = fc("Search", "image", nr=0)
+        s0b = fc("Search", "image", nr=0)
+        s1 = fc("Search", "image", nr=1)
+        self.assert_(s0.name == s1.name == "Search")
+        self.assert_(s0 is s0b)
+        self.assert_(s0 is not s1)
+        self.assertRaises(ControlNotFoundError, fc, "Search", "image", nr=2)
+        self.assert_(fc(type="text", nr=2).name == "journal")
+        self.assert_(fc("Search", nr=0) is not fc("Search", nr=1))
+
+        form["topic"] = "foo"
+        self.assert_(form["topic"] == "foo")
+        form["author"] = "bar"
+        form["journal"] = ""
+        form["address"] = "baz"
+        form["languagetype"] = ["English", "Catalan"]
+        self.assert_(form["languagetype"] == ["English", "Catalan"])
+        form["titleonly"] = ["on"]
+        self.assert_(form["titleonly"] == ["on"])
+        pairs = form.click_pairs("Search")
+        self.assert_(pairs == [
+            ("SID", "PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0"),
+            ("SESSION_DIR", ""),
+            ("Search.x", "1"), ("Search.y", "1"),
+            ("topic", "foo"),
+            ("titleonly", "on"),
+            ("author", "bar"),
+            ("journal", ""), ("address", "baz"),
+            ("languagetype", "English"), ("languagetype", "Catalan"),
+            ("doctype", "All document types"), ("Sort", "Latest date"),
+            ("Form", "General"), ("Func", "Search")])
+
+        hide_deprecations()
+        pvs = form.possible_items("languagetype")
+        self.assert_(pvs[0] == "All languages")
+        self.assert_(len(pvs) == 47)
+
+        self.assertRaises(
+            ItemNotFoundError,
+            lambda form=form: form.toggle("d'oh", "languagetype"))
+        form.toggle("English", "languagetype")
+        self.assert_(form["languagetype"] == ["Catalan"])
+        self.assertRaises(TypeError, form.toggle, ["Catalan"], "languagetype")
+        self.assertRaises(TypeError, form.toggle, "Catalan", ["languagetype"])
+
+        # XXX type, nr, by_label args
+
+        self.assertRaises(ControlNotFoundError, form.set, True, "blah", "SID")
+
+        # multiple select
+        form["languagetype"] = []
+        self.assert_(form["languagetype"] == [])
+        form.set(True, "Catalan", "languagetype")
+        self.assert_(form["languagetype"] == ["Catalan"])
+        form.set(True, "English", "languagetype")
+        self.assert_(form["languagetype"] == ["English", "Catalan"])
+        form.set(False, "English", "languagetype")
+        self.assert_(form["languagetype"] == ["Catalan"])
+        form.set(False, "Catalan", "languagetype")
+        self.assert_(form["languagetype"] == [])
+        self.assertRaises(ItemNotFoundError, form.set, True, "doh", "languagetype")
+        self.assertRaises(ItemNotFoundError, form.set, False, "doh", "languagetype")
+        self.assertRaises(ControlNotFoundError, form.set, True, "blah", "oops")
+        self.assertRaises(TypeError, form.set, True, ["Catalan"], "languagetype")
+        self.assertRaises(TypeError, form.set, False, ["Catalan"], "languagetype")
+        self.assertRaises(TypeError, form.set, True, "Catalan", ["languagetype"])
+        self.assertRaises(TypeError, form.set, False, "Catalan", ["languagetype"])
+
+        def setitem(name, value, form=form): form[name] = value
+        form["languagetype"] = ["Catalan"]
+        self.assert_(form["languagetype"] == ["Catalan"])
+        self.assertRaises(ItemNotFoundError,
+                          setitem, "languagetype", ["doh"])
+        self.assertRaises(ControlNotFoundError, setitem, "oops", ["blah"])
+        self.assertRaises(TypeError, setitem, ["languagetype"], "Catalan")
+
+        # single select
+        form["Sort"] = []
+        self.assert_(form["Sort"] == [])
+        form.set(True, "Relevance", "Sort")
+        self.assert_(form["Sort"] == ["Relevance"])
+        form.set(True, "Times Cited", "Sort")
+        self.assert_(form["Sort"] == ["Times Cited"])
+        form.set(False, "Times Cited", "Sort")
+        self.assert_(form["Sort"] == [])
+        self.assertRaises(ItemNotFoundError, form.set, True, "doh", "Sort")
+        self.assertRaises(ItemNotFoundError, form.set, False, "doh", "Sort")
+        self.assertRaises(ControlNotFoundError, form.set, True, "blah", "oops")
+        self.assertRaises(TypeError, form.set, True, ["Relevance"], "Sort")
+        self.assertRaises(TypeError, form.set, False, ["Relevance"], "Sort")
+        self.assertRaises(TypeError, form.set, True, "Relevance", ["Sort"])
+        self.assertRaises(TypeError, form.set, False, "Relevance", ["Sort"])
+        reset_deprecations()
+
+        form["Sort"] = ["Relevance"]
+        self.assert_(form["Sort"] == ["Relevance"])
+        self.assertRaises(ItemNotFoundError,
+                          setitem, "Sort", ["doh"])
+        self.assertRaises(ControlNotFoundError, setitem, "oops", ["blah"])
+        self.assertRaises(TypeError, setitem, ["Sort"], ["Relevance"])
+
+    def testSetValueByLabelIgnoringAmbiguity(self):
+        # regression test: follow ClientForm 0.1 behaviour
+        # also test that backwards_compat argument to ParseFile works
+        f = StringIO("""\
+<form>
+    <select multiple name="form.grocery">
+        <option value="bread" id="1">Loaf of Bread</option>
+        <option value="bread" id="2">Loaf of Bread</option>
+        <option value="challah">Loaf of Challah</option>
+    </select>
+    <input type="submit" value="Submit" />
+</form>
+""")
+        for kwds, backwards_compat in [({}, True),
+                     ({"backwards_compat": True}, True),
+                     ({"backwards_compat": False}, False),
+                     ]:
+            hide_deprecations()
+            form = ClientForm.ParseFile(f, "http://localhost/", **kwds)[0]
+            reset_deprecations()
+            f.seek(0)
+            c = form.find_control("form.grocery")
+            #for item in c.items:
+            #    print [label.text for label in item.get_labels()] 
+            c.set_value_by_label(
+                ["Loaf of Bread", "Loaf of Bread", "Loaf of Challah"])
+            if backwards_compat:
+                # select first item of ambiguous set
+                self.assertEqual(
+                    c.get_value_by_label(),
+                    ["Loaf of Bread", "Loaf of Challah"])
+                self.assertEqual(
+                    [item.id for item in c.items if item.selected],
+                    ["1", None])
+                # disabled items still part of 'value by label'
+                c.get(label="Loaf of Challah").disabled = True
+                self.assertEqual(
+                    c.get_value_by_label(),
+                    ["Loaf of Bread", "Loaf of Challah"])
+            else:
+                self.assertEqual(
+                    c.get_value_by_label(),
+                    ["Loaf of Bread", "Loaf of Bread", "Loaf of Challah"])
+                self.assertEqual(
+                    [item.id for item in c.items if item.selected],
+                    ["1", "2", None])
+                # disabled items NOT part of 'value by label'
+                c.get(label="Challah").disabled = True
+                self.assertEqual(
+                    c.get_value_by_label(),
+                    ["Loaf of Bread", "Loaf of Bread"])
+
+    def testClearValue(self):
+        # regression test: follow ClientForm 0.1 behaviour
+        # assigning [] to value is implemented as a special case
+        f = StringIO("""\
+<form>
+    <select multiple name="s">
+        <option disabled selected>a</option>
+        <option selected>b</option>
+    </select>
+</form>
+""")
+        for kwds, backwards_compat in [
+                ({}, True),
+                ({"backwards_compat": True}, True),
+                ({"backwards_compat": False}, False),
+                ]:
+            hide_deprecations()
+            form = ClientForm.ParseFile(f, "http://localhost/", **kwds)[0]
+            reset_deprecations()
+            f.seek(0)
+            cc = form.find_control("s")
+            if backwards_compat:
+                self.assertEqual(cc.value, ["a", "b"])
+                cc.value = []
+                self.assertEqual(
+                    [ii.name for ii in cc.items if ii.selected], [])
+            else:
+                self.assertEqual(cc.value, ["b"])
+                cc.value = []
+                # first is disabled, so no need to deselect
+                self.assertEqual(
+                    [ii.name for ii in cc.items if ii.selected], ["a"])
+
+    def testSearchByLabel(self):
+        f = StringIO("""\
+<form>
+<table>
+  <tr>
+    <td><label for="form.title">Book Title</label></td>
+    <td><input type="text" id="form.title" name="form.title"
+               value="The Grapes of Wrath" /></tr>
+  </tr>
+  <tr>
+    <td>Quality</td>
+    <td>
+      <div>
+        <label><input type="radio" id="form.quality.good" name="form.quality"
+                      value="good" /> Good</label>
+      </div><div>
+        <label><input type="radio" id="form.quality.indifferent"
+                      name="form.quality" value="indifferent" />
+          Indifferent</label>
+      </div><div>
+        <label><input type="radio" id="form.quality.bad" name="form.quality"
+                      value="bad" /> Bad</label>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td><label for="form.country" blah="foo">Country of Origin</label></td>
+    <td>
+      <select id="form.country" name="form.country">
+        <option value="albania">Albania</option>
+        <optgroup label="European Union">
+          <option label="GB" value="EU: Great Britain">Great Britain</option>
+        </optgroup>
+        <option value="USA">United States of America</option>
+        <option value="zimbabwe">Zimbabwe</option>
+      </select>
+    </td>
+  </tr>
+  <tr>
+    <td>Genre</label></td>
+    <td>
+      <div>
+        <label><input type="checkbox" id="form.genre.western" name="form.genre"
+                      value="western" /> Western</label>
+      </div><div>
+        <label><input type="checkbox" id="form.genre.sciencefiction"
+                      name="form.genre" value="scifi" />
+          Science Fiction</label>
+      </div><div>
+        <label><input type="checkbox" id="form.genre.horror" name="form.genre"
+                      value="horror" /> Horror</label>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td><label for="form.password">Password</label></td>
+    <td><input type="text" id="form.password" name="form.password"
+               value="123" /></tr>
+  </tr>
+  <tr>
+    <td>In this grocery list of requested food items, mark the items you intend
+        to purchase:
+    </td>
+    <td>
+      <label><input type="checkbox" name="form.grocery" value="bread" id="1"/>
+        Loaf of Bread</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="bread" id="2"/>
+        Loaf of Bread</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="bread" id="3"/>
+        Loaf of Bread</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="challah"/>
+        Loaf of Challah</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="eggs"/>
+        Dozen Eggs</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="milk"/>
+        Half-Gallon of Milk</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="milk"/>
+        Half-Gallon of Milk</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="diapers"/>
+        36 30lb. Diapers</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="diapers"/>
+        36 30lb. Diapers</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="diapers"/>
+        36 30lb. Diapers</label>&nbsp;|
+      <label><input type="checkbox" name="form.grocery" value="diapers"/>
+        36 30lb. Diapers</label>
+    </td>
+</table>
+<input type="submit" value="Submit" />
+</form>
+""")
+        form = ClientForm.ParseFile(f, "http://localhost/",
+                                    backwards_compat=False)[0]
+
+        # basic tests
+        self.assertEqual(form.find_control(label="Title").value,
+                         "The Grapes of Wrath")
+        self.assertEqual(form.find_control(label="Submit").value,
+                         "Submit")
+        self.assertEqual(
+            form.find_control(label="Country").get(
+                label="Britain").name, "EU: Great Britain")
+        self.assertEqual(
+            form.find_control(label="Origin").get(
+                label="GB").name, "EU: Great Britain")
+        self.assertEqual(form.find_control(label="Password").value,
+                         "123")
+        self.assertEqual(form.find_control(label="Title").value,
+                         "The Grapes of Wrath")
+
+        # Test item ambiguity, get, get_items, and set_value_by_label.
+        # A form can be in two states: either ignoring ambiguity or being
+        # careful about it.  Currently, by default, a form's backwards_compat
+        # attribute is True, so ambiguity is ignored.  For instance, notice
+        # that the form.grocery checkboxes include some loaves of bread and
+        # a loaf of challah.  The code just guesses what you mean:
+        form.backwards_compat = True
+        c = form.find_control("form.grocery")
+        # label substring matching is turned off for compat mode
+        self.assertRaises(ItemNotFoundError, c.get, label="Loaf")
+        self.assertEqual(c.get(label="Loaf of Bread"), c.items[0])
+        c.set_value_by_label(["Loaf of Bread"])
+        self.assertEqual(c.get_value_by_label(), ["Loaf of Bread"])
+        self.assertEqual(c.items[0].id, "1")
+        # However, if the form's backwards_compat attribute is False, Ambiguity
+        # Errors may be raised.  This is generally a preferred approach, but is
+        # not backwards compatible.
+        form.backwards_compat = False
+        self.assertRaises(ClientForm.AmbiguityError, c.get, label="Loaf")
+        self.assertRaises(
+            ClientForm.AmbiguityError, c.set_value_by_label, ["Loaf"])
+        # If items have the same name (value), set_value_by_label will
+        # be happy (since it is just setting the value anyway).
+        c.set_value_by_label(["Loaf of Bread"])
+        self.assertEqual(c.get_value_by_label(), ["Loaf of Bread"])
+        c.set_value_by_label(
+            ["Loaf of Bread", "Loaf of Bread", "Loaf of Challah"])
+        self.assertEqual(
+            c.get_value_by_label(),
+            ["Loaf of Bread", "Loaf of Bread", "Loaf of Challah"])
+        # "get" will still raise an exception, though.
+        self.assertRaises(
+            ClientForm.AmbiguityError, c.get, label="Loaf of Bread")
+        # If you want an item, you need to specify which one you want (or use
+        # get_items to explicitly get all of them).
+        self.assertEqual(c.get(label="Loaf of Bread", nr=0).selected, True)
+        self.assertEqual(c.get(label="Loaf of Bread", nr=1).selected, True)
+        self.assertEqual(c.get(label="Loaf of Bread", nr=2).selected, False)
+        self.assertEqual(c.get(label="Loaf of Challah").selected, True)
+        self.assertEqual(
+            [i.selected for i in c.get_items(label="Loaf of Bread")],
+            [True, True, False])
+        self.assertEqual(
+            [i.selected for i in c.get_items(label="Loaf of Challah")],
+            [True])
+        self.assertEqual(
+            [i.name for i in c.get_items(label="Loaf")],
+            ["bread", "bread", "bread", "challah"])
+        self.assertEqual(
+            [i.get_labels()[0].text for i in c.get_items("bread")],
+            ["Loaf of Bread", "Loaf of Bread", "Loaf of Bread"])
+
+        # test deprecation
+        if warnings_imported:
+            try:
+                for c, f in (
+                    (form.find_control("form.genre"), "western"),
+                    (form.find_control("form.country"), "zimbabwe"),
+                    (form.find_control("form.quality"), "good")):
+                    # warnings are nasty. :-(
+                    raise_deprecations()  # clear onceregistry
+                    try:
+                        c.possible_items()
+                    except DeprecationWarning:
+                        pass
+                    else:
+                        self.fail("deprecation failed")
+                    try:
+                        c.toggle_single()
+                    except DeprecationWarning:
+                        pass
+                    else:
+                        self.fail("deprecation failed")
+                    try:
+                        c.set_single(True)
+                    except DeprecationWarning:
+                        pass
+                    else:
+                        self.fail("deprecation failed")
+                    try:
+                        c.toggle(f)
+                    except DeprecationWarning:
+                        pass
+                    else:
+                        self.fail("deprecation failed")
+                    try:
+                        c.get_item_disabled(f)
+                    except DeprecationWarning:
+                        pass
+                    else:
+                        self.fail("deprecation failed")
+                    try:
+                        c.set_item_disabled(True, f)
+                    except DeprecationWarning:
+                        pass
+                    else:
+                        self.fail("deprecation failed")
+                    try:
+                        c.get_item_attrs(True, f)
+                    except DeprecationWarning:
+                        pass
+                    else:
+                        self.fail("deprecation failed")
+            finally:
+                reset_deprecations()
+
+    def testResults(self):
+        file = open("./testdata/Results.html", "r")
+        forms = ClientForm.ParseFile(file, self.base_uri,
+                                     backwards_compat=False)
+        self.assert_(len(forms) == 1)
+        form = forms[0]
+
+        hide_deprecations()
+        pvs = form.possible_items("marked_list_candidates")
+        reset_deprecations()
+        self.assert_(pvs == [
+            "000174872000059/1", "000174858300003/2", "000174827900006/3"])
+        def bad_setitem(form=form):
+            form["marked_list_candidates"] = ["blah"]
+        self.assertRaises(ItemNotFoundError, bad_setitem)
+        form["marked_list_candidates"] = [pvs[0]]
+
+        # I've removed most of the INPUT elements from this page, and
+        # corrected an HTML error
+        keys = ["Add marked records to list",
+                "Add records on page to list",
+                "Add all records retrieved to list",
+                "marked_list_candidates",
+                "Add marked records to list",
+                "Add records on page to list",
+                "Add all records retrieved to list"
+                ]
+        types = ["image", "image", "image",
+                 "checkbox",
+                 "image", "image", "image"]
+        values = ["", "", "",
+                  [pvs[0]],
+                  "", "", "",
+                 ]
+
+        for i in range(len(keys)):
+            key = keys[i]
+            control = form.find_control(key, nr=0)
+            self.assert_(control.value == values[i])
+            self.assert_(control.type == types[i])
+
+        pairs = form.click_pairs("Add all records retrieved to list")
+        self.assert_(pairs == [
+            ("Add all records retrieved to list.x", "1"),
+            ("Add all records retrieved to list.y", "1"),
+            ("marked_list_candidates", pvs[0])])
+
+    def testMarkedResults(self):
+        file = open("./testdata/MarkedResults.html", "r")
+        forms = ClientForm.ParseFile(file, self.base_uri,
+                                     backwards_compat=False)
+        self.assert_(len(forms) == 1)
+        form = forms[0]
+
+        pairs = form.click_pairs()
+        # I've removed most of the INPUT elements from this page, and
+        # corrected an HTML error
+        self.assert_(pairs == [
+            ("Add marked records to list.x", "1"),
+            ("Add marked records to list.y", "1"),
+            ("marked_list_candidates", "000174872000059/1"),
+            ("marked_list_candidates", "000174858300003/2"),
+            ("marked_list_candidates", "000174827900006/3")
+            ])
+
+    def testMarkedRecords(self):
+        pass  # XXX
+
+
+class MoreFormTests(TestCase):
+
+    def test_interspersed_controls(self):
+        # must preserve item ordering even across controls
+        f = StringIO("""\
+<form name="formname">
+    <input type="checkbox" name="murphy" value="a"></input>
+    <input type="checkbox" name="woof" value="d"></input>
+    <input type="checkbox" name="murphy" value="b"></input>
+    <input type="checkbox" name="murphy" value="c"></input>
+    <input type="submit"></input>
+</form>
+""")
+        form = ClientForm.ParseFile(f, "http://blah/",
+                                    backwards_compat=False)[0]
+        form["murphy"] = ["a", "b", "c"]
+        form["woof"] = ["d"]
+        self.assertEqual(form.click_pairs(), [
+                ("murphy", "a"),
+                ("woof", "d"),
+                ("murphy", "b"),
+                ("murphy", "c"),
+                ])
+
+        form.method = "POST"
+        form.enctype = "multipart/form-data"
+        lines = [line for line in form.click_request_data()[1].split("\r\n") if
+                 line != '' and not line.startswith("--")]
+        self.assertEqual(
+            lines,
+            ['Content-disposition: form-data; name="murphy"', 'a',
+             'Content-disposition: form-data; name="woof"', 'd',
+             'Content-disposition: form-data; name="murphy"', 'b',
+             'Content-disposition: form-data; name="murphy"', 'c',
+             ]
+            )
+
+    def make_form(self):
+        f = StringIO("""\
+<form blah="nonsense" name="formname">
+  <label><input type="checkbox" name="a" value="1" id="1a" blah="spam"></input>
+      One</label>
+  <label><input type="checkbox" name="a" value="2" blah="eggs"></input>
+      Two</label>
+  <input type="checkbox" name="a" value="3" id="3a"></input>
+      <label for="3a">Three</label>
+
+  <label><input type="radio" name="b" value="1"></input> One</label>
+  <label><input type="radio" name="b" value="2" id="2"></input> Two</label>
+  <input type="radio" name="b" value="3" id="3"></input>
+      <label for="3">Three</label>
+  <label for="4"><input type="radio" name="b" value="4" id="4"></input>
+      Four</label>
+
+  <select name="c" id="cselect" blah="foo">
+    <option id="coption1" blah="bar">1</option>
+    <option selected blah="baz">2</option>
+    <option id="coption3">3</option>
+  </select>
+
+  <select name="d" multiple>
+    <option value="v1">l1</option>
+    <option value="v2">l2</option>
+    <option blah="fee" rhubarb="fi" value="v3">l3</option>
+  </select>
+
+  <input type="checkbox" name="e" value="1"></input>
+</form>
+""")
+        return ClientForm.ParseFile(f, "http://blah/",
+                                    backwards_compat=False)[0]
+
+    def test_value(self):
+        form = self.make_form()
+
+        form.set_value(["v3"], type="select", kind="multilist")
+        self.assert_(form.get_value("d") == ["v3"])
+        hide_deprecations()
+        form.set_value(["l2"], type="select", kind="multilist", by_label=True)
+        self.assert_(form.get_value("d", by_label=True) == ["l2"])
+
+        self.assert_(form.get_value(
+            "b", "radio", "singlelist", None, 0, False) == [])
+        form.set_value(["One"], "b", by_label=True)
+        self.assertEqual(
+            form.get_value("b", "radio", "singlelist", None, 0, False),
+            ["1"])
+        form.set_value(["Three"], "b", by_label=True)
+        reset_deprecations()
+        self.assertEqual(
+            form.get_value("b", "radio", "singlelist", None, 0, False),
+            ["3"])
+
+    def test_id(self):
+        form = self.make_form()
+
+        self.assert_(form.find_control("c").id == "cselect")
+        self.assert_(form.find_control("a").id == "1a")
+        self.assert_(form.find_control("b").id is None)
+
+        self.assert_(form.find_control(id="cselect").id == "cselect")
+        self.assertRaises(ControlNotFoundError, form.find_control,
+                          id="coption1")
+        self.assert_(form.find_control(id="1a").id == "1a")
+        self.assertRaises(ControlNotFoundError, form.find_control, id="1")
+
+    def test_single(self):
+        form = self.make_form()
+
+        hide_deprecations()
+        self.assertRaises(ItemCountError, form.set_single, True, "d")
+        form.set_single(True, 'e', by_label=True)
+        self.assertEqual(form.get_value("e"), ["1"])
+        form.set_single(False, 'e', by_label=True)
+        self.assertEqual(form.get_value("e"), [])
+        form.toggle_single("e", "checkbox", "list", nr=0)
+        self.assert_("1" in form.get_value("e"))
+        form.set_single(False, "e", "checkbox", "list", nr=0)
+        self.assert_("1" not in form.get_value("e"))
+        form.set_single(True, "e", "checkbox", "list", nr=0)
+        self.assert_("1" in form.get_value("e"))
+        reset_deprecations()
+
+    def test_possible_items(self):
+        form = self.make_form()
+        hide_deprecations()
+        self.assert_(form.possible_items("c") == ["1", "2", "3"])
+        self.assert_(form.possible_items("d", by_label=True) ==
+                     ["l1", "l2", "l3"])
+
+        self.assert_(form.possible_items("a") == ["1", "2", "3"])
+        self.assertEqual(form.possible_items('e', by_label=True),
+                         [None])
+        self.assertEqual(form.possible_items('a', by_label=True),
+                         ['One', 'Two', 'Three'])
+        self.assertEqual(form.possible_items('b', by_label=True),
+                         ['One', 'Two', 'Three', 'Four'])
+        reset_deprecations()
+
+    def test_set_all_readonly(self):
+        form = self.make_form()
+
+        form.set_all_readonly(True)
+        for c in form.controls:
+            self.assert_(c.readonly)
+        form.set_all_readonly(False)
+        for c in form.controls:
+            self.assert_(not c.readonly)
+
+    def test_clear_all(self):
+        form = self.make_form()
+        form.set_all_readonly(True)
+        self.assertRaises(AttributeError, form.clear_all)
+        form.set_all_readonly(False)
+        form.clear_all()
+        for c in form.controls:
+            self.assert_(not c.value)
+
+    def test_clear(self):
+        form = self.make_form()
+        form.set_all_readonly(True)
+        self.assertRaises(AttributeError, form.clear, "b")
+        form.set_all_readonly(False)
+        form["b"] = ["1"]
+        self.assertEqual(form["b"], ["1"])
+        form.clear("b")
+        self.assertEqual(form["b"], [])
+
+    def test_attrs(self):
+        form = self.make_form()
+
+        self.assert_(form.attrs["blah"] == "nonsense")
+        self.assert_(form.attrs["name"] == "formname")
+
+        a = form.find_control("a")
+        self.assertRaises(AttributeError, getattr, a, 'attrs')
+        hide_deprecations()
+        self.assert_(a.get_item_attrs("1")["blah"] == "spam")
+        self.assert_(a.get_item_attrs("2")["blah"] == "eggs")
+        self.assert_(not a.get_item_attrs("3").has_key("blah"))
+
+        c = form.find_control("c")
+        self.assert_(c.attrs["blah"] == "foo")
+        self.assert_(c.get_item_attrs("1")["blah"] == "bar")
+        self.assert_(c.get_item_attrs("2")["blah"] == "baz")
+        self.assert_(not c.get_item_attrs("3").has_key("blah"))
+        reset_deprecations()
+
+    def test_select_control_nr_and_label(self):
+        for compat in [False, True]:
+            self._test_select_control_nr_and_label(compat)
+    def _test_select_control_nr_and_label(self, compat):
+        f = StringIO("""\
+<form>
+    <select multiple name="form.grocery">
+        <option value="p" label="a" id="1">a</option>
+        <option value="q" label="b" id="2">a</option>
+        <option value="p" label="a" id="3">b</option>
+    </select>
+</form>
+""")
+        if compat: hide_deprecations()
+        form = ClientForm.ParseFile(f, "http://example.com/",
+                                    backwards_compat=compat)[0]
+        if compat: reset_deprecations()
+        ctl = form.find_control("form.grocery")
+        # ordinary case
+        self.assertEqual(ctl.get("p", nr=1).id, "3")
+        # nr too high
+        self.assertRaises(ItemNotFoundError, ctl.get, "p", nr=50)
+        # first having label "a"
+        self.assertEqual(ctl.get(label="a", nr=0).id, "1")
+        # second having label "a"...
+        item = ctl.get(label="a", nr=1)
+        # ...as opposed to second with label attribute "a"! -- each item
+        # has multiple labels accessible by .get_labels(), but only one
+        # label HTML-attribute
+        self.assertEqual(item.id, "2")
+        self.assertEqual(item.attrs.get("label"), "b")  # !
+        # third having label "a" (but only the second whose label is "a")
+        self.assertEqual(ctl.get(label="a", nr=1).id, "2")
+        # nr too high again
+        self.assertRaises(ItemNotFoundError, ctl.get, label="a", nr=3)
+
+        self.assertEqual(ctl.get(id="2").id, "2")
+        self.assertRaises(ItemNotFoundError, ctl.get, id="4")
+        self.assertRaises(ItemNotFoundError, ctl.get, id="4")
+
+    def test_label_whitespace(self):
+        for compat in [False, True]:
+            f = StringIO("""\
+<form>
+    <select multiple name="eg">
+        <option value="p"> a b  c  </option>
+        <option value="q">b</option>
+    </select>
+</form>
+""")
+            if compat:
+                hide_deprecations()
+            form = ClientForm.ParseFile(f, "http://example.com/",
+                                        backwards_compat=compat)[0]
+            ctl = form.find_control("eg")
+            p = ctl.get("p")
+            q = ctl.get("q")
+            self.assertEqual(p.get_labels()[0].text,
+                             (compat and "a b  c" or "a b c"))
+            self.assertEqual(q.get_labels()[0].text, "b")
+            if compat:
+                reset_deprecations()
+
+    def test_nameless_list_control(self):
+        # ListControls are built up from elements that match by name and type
+        # attributes.  Nameless controls cause some tricky cases.  We should
+        # get a new control for nameless controls.
+        for data in [
+            """\
+<form>
+  <input type="checkbox" name="foo"/>
+  <input type="checkbox" name="bar"/>
+  <input type="checkbox" id="a" onclick="bar()" checked />
+</form>
+""",
+"""\
+<form>
+  <input type="checkbox" name="foo"/>
+  <input type="checkbox" id="a" onclick="bar()" checked />
+</form>
+""",
+"""\
+<form>
+  <input type="checkbox"/>
+  <input type="checkbox"/>
+  <input type="checkbox" id="a" onclick="bar()" checked />
+</form>
+""",
+            ]:
+            f = StringIO(data)
+            form = ClientForm.ParseFile(f, "http://example.com/",
+                                        backwards_compat=False)[0]
+            bar = form.find_control(type="checkbox", id="a")
+            # should have value "on", but not be successful
+            self.assertEqual([item.name for item in bar.items], ["on"])
+            self.assertEqual(bar.value, [])
+            self.assertEqual(form.click_pairs(), [])
+
+    def test_action_with_fragment(self):
+        for method in ["GET", "POST"]:
+            data = ('<form action="" method="%s">'
+                    '<input type="submit" name="s"/></form>' % method
+                    )
+            f = StringIO(data)
+            form = ClientForm.ParseFile(f, "http://example.com/",
+                                        backwards_compat=False)[0]
+            self.assertEqual(
+                form.click().get_full_url(),
+                "http://example.com/"+(method=="GET" and "?s=" or ""),
+                )
+        data = '<form action=""><isindex /></form>'
+        f = StringIO(data)
+        form = ClientForm.ParseFile(f, "http://example.com/",
+                                    backwards_compat=False)[0]
+        form.find_control(type="isindex").value = "blah"
+        self.assertEqual(form.click(type="isindex").get_full_url(),
+                         "http://example.com/?blah")
+
+
+class ContentTypeTests(TestCase):
+    def test_content_type(self):
+        import ClientForm
+        class OldStyleRequest:
+            def __init__(self, url, data=None, hdrs=None):
+                self.ah = self.auh = False
+            def add_header(self, key, val):
+                self.ah = True
+        class NewStyleRequest(OldStyleRequest):
+            def add_unredirected_header(self, key, val):
+                self.auh = True
+        class FakeForm(ClientForm.HTMLForm):
+            def __init__(self, hdr):
+                self.hdr = hdr
+            def _request_data(self):
+                return "http://example.com", "", [(self.hdr, "spam")]
+        for request_class, hdr, auh in [
+            (OldStyleRequest, "Foo", False),
+            (NewStyleRequest, "Foo", False),
+            (OldStyleRequest, "Content-type", False),
+            (NewStyleRequest, "Content-type", True),
+            ]:
+            form = FakeForm(hdr)
+            req = form._switch_click("request", request_class)
+            self.assertEqual(req.auh, auh)
+            self.assertEqual(req.ah, not auh)
+
+
+class FunctionTests(TestCase):
+
+    def test_normalize_line_endings(self):
+        def check(text, expected, self=self):
+            got = ClientForm.normalize_line_endings(text)
+            self.assertEqual(got, expected)
+
+        # unix
+        check("foo\nbar", "foo\r\nbar")
+        check("foo\nbar\n", "foo\r\nbar\r\n")
+        # mac
+        check("foo\rbar", "foo\r\nbar")
+        check("foo\rbar\r", "foo\r\nbar\r\n")
+        # dos
+        check("foo\r\nbar", "foo\r\nbar")
+        check("foo\r\nbar\r\n", "foo\r\nbar\r\n")
+
+        # inconsistent -- we just blithely convert anything that looks like a
+        # line ending to the DOS convention, following Firefox's behaviour when
+        # normalizing textarea content
+        check("foo\r\nbar\nbaz\rblah\r\n", "foo\r\nbar\r\nbaz\r\nblah\r\n")
+
+        # pathological ;-O
+        check("\r\n\n\r\r\r\n", "\r\n"*5)
+
+
+def startswith(string, initial):
+    if len(initial) > len(string): return False
+    return string[:len(initial)] == initial
+
+class CaseInsensitiveDict:
+    def __init__(self, items):
+        self._dict = {}
+        for key, val in items:
+            self._dict[string.lower(key)] = val
+
+    def __getitem__(self, key): return self._dict[key]
+
+    def __getattr__(self, name): return getattr(self._dict, name)
+
+
+class UploadTests(TestCase):
+    def test_choose_boundary(self):
+        from ClientForm import choose_boundary
+
+        bndy = choose_boundary()
+        ii = string.find(bndy, '.')
+        self.assert_(ii < 0)
+
+    def make_form(self):
+        html = """\
+<form action="/cgi-bin/upload.cgi" method="POST" enctype="multipart/form-data">
+<input type="file" name="data">
+<input type="text" name="user" value="nobody">
+<br>
+<input type="submit">
+</form>
+"""
+
+        return ClientForm.ParseFile(StringIO(html),
+                                    "http://localhost/cgi-bin/upload.cgi",
+                                    backwards_compat=False)[0]
+
+    def test_file_request(self):
+        import cgi
+
+        # fill in a file upload form...
+        form = self.make_form()
+        form["user"] = "john"
+        data_control = form.find_control("data")
+        data = "blah\nbaz\n"
+        data_control.add_file(StringIO(data))
+        #print "data_control._upload_data", data_control._upload_data
+        req = form.click()
+        self.assert_(startswith(get_header(req, "Content-type"),
+                                'multipart/form-data; boundary='))
+
+        #print "req.get_data()\n>>%s<<" % req.get_data()
+
+        # ...and check the resulting request is understood by cgi module
+        fs = cgi.FieldStorage(StringIO(req.get_data()),
+                              CaseInsensitiveDict(header_items(req)),
+                              environ={"REQUEST_METHOD": "POST"})
+        self.assert_(fs["user"].value == "john")
+        self.assert_(fs["data"].value == data)
+        self.assert_(fs["data"].filename is None)
+
+    def test_file_request_with_filename(self):
+        import cgi
+
+        # fill in a file upload form...
+        form = self.make_form()
+        form["user"] = "john"
+        data_control = form.find_control("data")
+        data = "blah\nbaz\n"
+        data_control.add_file(StringIO(data), filename="afilename")
+        req = form.click()
+        self.assert_(startswith(get_header(req, "Content-type"),
+                                'multipart/form-data; boundary='))
+
+        # ...and check the resulting request is understood by cgi module
+        fs = cgi.FieldStorage(StringIO(req.get_data()),
+                              CaseInsensitiveDict(header_items(req)),
+                              environ={"REQUEST_METHOD": "POST"})
+        self.assert_(fs["user"].value == "john")
+        self.assert_(fs["data"].value == data)
+        self.assert_(fs["data"].filename == "afilename")
+
+    def test_multipart_file_request(self):
+        import cgi
+
+        # fill in a file upload form...
+        form = self.make_form()
+        form["user"] = "john"
+        data_control = form.find_control("data")
+        data = "blah\nbaz\n"
+        data_control.add_file(StringIO(data), filename="filenamea")
+        more_data = "rhubarb\nrhubarb\n"
+        data_control.add_file(StringIO(more_data), filename="filenameb")
+        yet_more_data = "rheum\nrhaponicum\n"
+        data_control.add_file(StringIO(yet_more_data), filename="filenamec")
+        req = form.click()
+        self.assert_(startswith(get_header(req, "Content-type"),
+                                'multipart/form-data; boundary='))
+
+        #print "req.get_data()\n>>%s<<" % req.get_data()
+
+        # ...and check the resulting request is understood by cgi module
+        fs = cgi.FieldStorage(StringIO(req.get_data()),
+                              CaseInsensitiveDict(header_items(req)),
+                              environ={"REQUEST_METHOD": "POST"})
+        self.assert_(fs["user"].value == "john")
+
+        fss = fs["data"][None]
+        filenames = "filenamea", "filenameb", "filenamec"
+        datas = data, more_data, yet_more_data
+        for i in range(len(fss)):
+            fs = fss[i]
+            filename = filenames[i]
+            data = datas[i]
+            self.assert_(fs.filename == filename)
+            self.assert_(fs.value == data)
+
+    def test_upload_data(self):
+        form = self.make_form()
+        data = form.click().get_data()
+        self.assert_(startswith(data, "--"))
+
+    def test_empty_upload(self):
+        # no controls except for INPUT/SUBMIT
+        forms = ClientForm.ParseFile(StringIO("""<html>
+<form method="POST" action="./weird.html" enctype="multipart/form-data">
+<input type="submit" name="submit"></input>
+</form></html>"""), ".", backwards_compat=False)
+        form = forms[0]
+        data = form.click().get_data()
+        lines = string.split(data, "\r\n")
+        self.assert_(startswith(lines[0], "--"))
+        self.assert_(lines[1] == 
+                     'Content-disposition: form-data; name="submit"')
+        self.assert_(lines[2] == lines[3] == "")
+        self.assert_(startswith(lines[4], "--"))
+
+
+class ImportTests(TestCase):
+
+    def test_import_all(self):
+        # the following will raise an exception if __all__ contains undefined
+        # classes
+        from ClientForm import *
+
+
+if __name__ == "__main__":
+    import test_clientform
+    unittest.main(test_clientform)


Property changes on: ClientForm/tags/0.2.9/test/test_clientform.py
___________________________________________________________________
Added: svn:executable
   + 

Added: ClientForm/tags/0.2.9/test.py
===================================================================
--- ClientForm/tags/0.2.9/test.py	                        (rev 0)
+++ ClientForm/tags/0.2.9/test.py	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,25 @@
+import os
+import sys
+
+
+def main():
+    try:
+        __file__
+    except NameError:
+        this_file = os.path.abspath(sys.argv[0])
+    else:
+        this_file = os.path.abspath(__file__)
+    test_dir = os.path.join(os.path.dirname(this_file), "test")
+    sys.path.insert(0, test_dir)
+    try:
+        import warnings
+    except ImportError:
+        pass
+    else:
+        warnings.filterwarnings(
+            action="ignore", message="import \* only allowed at module level")
+    execfile(os.path.join(test_dir, "test_clientform.py"))
+
+
+if __name__ == "__main__":
+    main()

Added: ClientForm/tags/0.2.9/testdata/Auth.html
===================================================================
--- ClientForm/tags/0.2.9/testdata/Auth.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/testdata/Auth.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<HTML>
+<HEAD>
+<TITLE>Athens Authentication Point</TITLE>
+<META http-equiv="Content-Type" content="text/html;charset=iso-8859-1">
+</HEAD>
+
+<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#000000" VLINK="#000000">
+
+<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0" WIDTH=609>
+   <TR>
+      <TD ALIGN="RIGHT">
+         <IMG SRC="http://wos.mimas.ac.uk/isicgi/Images/main.jpg" ALT="ISI Web of Science" BORDER="0" WIDTH=470 HEIGHT=150>
+      </TD>
+   </TR>
+   <TR>
+      <TD>
+         <IMG SRC="http://auth.athensams.net/images/auth_point.gif" ALT="Athens Authentication Point">
+      </TD>
+   </TR>
+   <TR>
+      <TD>
+         <P>&nbsp;<P>
+      </TD>
+   </TR>
+   <TR>
+      <TD ALIGN="CENTER">
+         <FORM METHOD=POST ACTION="/?ath_returl=%22http%3A%2F%2Ftame.mimas.ac.uk%2Fisicgi%2FWOS-login.cgi%22&ath_dspid=MIMAS.WOS">
+         <TABLE ALIGN=CENTER BORDER=0 CELLPADDING=0 CELLSPACING=10 WIDTH="75%">
+                        <TR>
+	       <TD ALIGN=RIGHT WIDTH="40%">
+                  <FONT COLOR="#333366" SIZE=2 FACE="Verdana, Helvetica, Sans, Arial, Metabold, Geneva"><B>Username:</B></FONT>
+               </TD>
+               <TD ALIGN=LEFT>
+                  <FONT COLOR="#FFFFFF" SIZE=2 FACE="Verdana, Helvetica, Sans, Arial, Metabold, Geneva">
+                  <INPUT TYPE=TEXT NAME="ath_uname" VALUE="" MAXLENGTH=20>
+                  </FONT>
+               </TD>
+            </TR>
+            <TR>
+               <TD ALIGN=RIGHT>
+                  <FONT COLOR="#333366" SIZE=2 FACE="Verdana, Helvetica, Sans, Arial, Metabold, Geneva"><B>Password:</B></FONT>
+               </TD>
+               <TD ALIGN=LEFT>
+                  <FONT COLOR="#FFFFFF" SIZE=2 FACE="Verdana, Helvetica, Sans, Arial, Metabold, Geneva">
+                  <INPUT TYPE=PASSWORD NAME="ath_passwd" MAXLENGTH=20>
+                  </FONT>
+	       </TD>
+            </TR>
+            <TR>
+               <TD ALIGN=CENTER COLSPAN=2>
+                  <INPUT TYPE=IMAGE SRC="http://auth.athensams.net/images/login.gif" BORDER=0 ALT="Login" ALIGN=MIDDLE><BR>
+               </TD>
+	    </TR>
+         </TABLE>
+         </FORM>
+      </TD>
+   </TR>
+</TABLE>
+
+<TABLE WIDTH="609" BORDER="0">
+   <TR>
+      <TD>
+         <FONT FACE="Verdana, Helvetica, Sans, Arial, Metabold, Geneva" SIZE=1>
+            Athens is a service of <a href=http://www.eduserv.ac.uk>EduServ</a>
+         </FONT>
+         <BR>
+         <FONT FACE="Verdana, Arial, Helvetica" SIZE=1>(c) <A HREF="http://www.athensams.net/copyright.html">Copyright</a>, EduServ. All rights reserved. February 2002</FONT>
+      </TD>
+      <TD>
+         <A HREF="http://www.mimas.ac.uk"><img align="right"
+BORDER="0" SRC="http://wos.mimas.ac.uk/images/small_mimas2.gif" alt="MIMAS"></a>
+      </TD>
+   </TR>
+</TABLE>
+
+</BODY>
+</HTML>

Added: ClientForm/tags/0.2.9/testdata/FullSearch.html
===================================================================
--- ClientForm/tags/0.2.9/testdata/FullSearch.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/testdata/FullSearch.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,114 @@
+<HTML><HEAD><TITLE>Search -- Web of Science v4.31</TITLE>
+
+
+
+<SCRIPT LANGUAGE=JavaScript SRC=PeriodSelect.js>
+</SCRIPT>
+
+
+</HEAD>
+<BODY BGCOLOR=#FFFFFF ><FORM  ACTION=CIW.cgi NAME = "searchForm"  ENCTYPE="multipart/form-data" METHOD=POST>
+<INPUT TYPE=HIDDEN NAME="SID" VALUE="PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0">
+<INPUT TYPE=HIDDEN NAME="SESSION_DIR" VALUE="">
+
+                       <TABLE WIDTH="100%" BORDER="0" CELLSPACING="0" CELLPADDING="0">
+                       <TR><TD WIDTH="100%" BGCOLOR="#000000">
+                       <IMG SRC=http://tame.mimas.ac.uk:80/isicgi/Images/isihdr.gif BORDER=0 ALT="ISI Citation Indexes"  WIDTH="620" HEIGHT="20" ALIGN="BOTTOM" NATURALSIZEFLAG="3">
+                       </TD></TR>
+                       <TR></TR>
+                       </TABLE>
+                      
+<TABLE CELLSPACING=0 CELLPADDING=0>
+<TR ALIGN=CENTER VALIGN=CENTER>
+<TD><INPUT TYPE=IMAGE BORDER=0 NAME="Home" ALT="Home" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tbhome.gif"></TD><TD><a href="http://tame.mimas.ac.uk:80/isicgi/help/helpsrch.html#Full_Search"><IMG SRC=http://tame.mimas.ac.uk:80/isicgi/Images/tbhelp.gif ALT="Help" BORDER=0></a></TD><TD><INPUT TYPE=IMAGE BORDER=0 NAME="Log off" ALT="Log off" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tblogoff.gif"></TD></TR>
+</TABLE>
+<HR>
+<CENTER><STRONG><FONT SIZE=4>Full Search</FONT><BR></STRONG></CENTER><INPUT TYPE=CHECKBOX NAME="editions" VALUE="D">
+<A HREF=help/helptoc.html#sci>Science Citation Index Expanded (SCI-EXPANDED)--1981-present</A><BR>
+<INPUT TYPE=CHECKBOX NAME="editions" VALUE="S">
+<A HREF=help/helptoc.html#ssci>Social Sciences Citation Index (SSCI)--1981-present</A><BR>
+<INPUT TYPE=CHECKBOX NAME="editions" VALUE="H">
+<A HREF=help/helptoc.html#ahci>Arts & Humanities Citation Index (A&HCI)--1981-present</A><BR>
+<HR><INPUT TYPE=RADIO NAME="Period" VALUE="This Week" onClick="clear_years();">
+This week's update (Updated April 26, 2002)<BR><INPUT TYPE=RADIO NAME="Period" VALUE="Latest 2 Weeks" onClick="clear_years();">
+Latest 2 Weeks<BR><INPUT TYPE=RADIO NAME="Period" VALUE="Latest 4 Weeks" onClick="clear_years();">
+Latest 4 Weeks<BR><INPUT TYPE=RADIO NAME="Period" CHECKED VALUE="All Years" onClick="clear_years();">
+All years<BR><INPUT TYPE=RADIO NAME="Period" VALUE="Year Selection">
+Limit search to years selected below<BR><TABLE>
+<TR><TD><INPUT TYPE=CHECKBOX NAME="years" VALUE="2002" onClick="set_period(4);">
+2002&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="2001" onClick="set_period(4);">
+2001&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="2000" onClick="set_period(4);">
+2000&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1999" onClick="set_period(4);">
+1999&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1998" onClick="set_period(4);">
+1998&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1997" onClick="set_period(4);">
+1997&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1996" onClick="set_period(4);">
+1996&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1995" onClick="set_period(4);">
+1995&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1994" onClick="set_period(4);">
+1994&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1993" onClick="set_period(4);">
+1993&nbsp;&nbsp;
+
+<TR><TD><INPUT TYPE=CHECKBOX NAME="years" VALUE="1992" onClick="set_period(4);">
+1992&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1991" onClick="set_period(4);">
+1991&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1990" onClick="set_period(4);">
+1990&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1989" onClick="set_period(4);">
+1989&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1988" onClick="set_period(4);">
+1988&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1987" onClick="set_period(4);">
+1987&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1986" onClick="set_period(4);">
+1986&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1985" onClick="set_period(4);">
+1985&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1984" onClick="set_period(4);">
+1984&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1983" onClick="set_period(4);">
+1983&nbsp;&nbsp;
+
+<TR><TD><INPUT TYPE=CHECKBOX NAME="years" VALUE="1982" onClick="set_period(4);">
+1982&nbsp;&nbsp;
+<INPUT TYPE=CHECKBOX NAME="years" VALUE="1981" onClick="set_period(4);">
+1981&nbsp;&nbsp;
+</TABLE><HR><TABLE>
+		      <TR>
+                    <TD><INPUT TYPE=IMAGE BORDER=0 VSPACE=0 HSPACE=1 SRC=Images/gensrch.gif NAME="General Search" ALT="General Search"></TD>
+
+		      <TD>
+		     Search for articles by subject term, author name, journal title, or author affiliation<BR></TD>
+		      <TR>
+                      <TD><INPUT TYPE=IMAGE BORDER=0 VSPACE=0 HSPACE=1 SRC=Images/crsrch.gif NAME="Cited Ref Search" ALT="Cited Ref Search"></TD>
+
+		      <TD>Search for articles that cite an author or work</TD> </TR> </TABLE>
+                      <HR>
+                      <TABLE>
+                      <TR >
+                      <TD NOWRAP> <A HREF= http://tame.mimas.ac.uk:80/isicgi/CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=LoadQuery>Using Saved Queries:</A></TD><TD> Instructions for editing and running saved queries.</TD>
+                      </TR> </TABLE>
+                     
+                      Enter full pathname of saved query (e.g., c:\myqueries\query1) or use Browse.<BR>
+                      <TABLE>
+                      <TR>
+                      <TD NOWRAP>
+                      <INPUT TYPE=file NAME=fileToUpload VALUE = "" ALT="Browse"">
+                      </TD>
+                      <TD>
+                      <INPUT TYPE=SUBMIT NAME=Func VALUE="Open Query" ALT="Open Query">
+                      </TD>
+                      </TR>
+                      </TABLE>
+		  <INPUT TYPE=HIDDEN NAME=Form VALUE=Full>
+		 <HR></FORM>
+<CENTER><EM><A HREF=http://wos.isitrial.com/policy/Policy.htm><FONT SIZE=-1>Acceptable Use Policy</FONT></A></EM></CENTER><P>
+<CENTER><I>Copyright &copy; 2002 <A HREF=http://www.isinet.com>Institute for Scientific Information</A></I></CENTER></BODY></HTML>

Added: ClientForm/tags/0.2.9/testdata/GeneralSearch.html
===================================================================
--- ClientForm/tags/0.2.9/testdata/GeneralSearch.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/testdata/GeneralSearch.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,178 @@
+<HTML><HEAD><TITLE>General Search -- Web of Science v4.31</TITLE>
+
+
+</HEAD>
+<BODY BGCOLOR=#FFFFFF ><FORM  ACTION=http://tame.mimas.ac.uk:80/isicgi/CIW.cgi  METHOD=POST>
+<INPUT TYPE=HIDDEN NAME="SID" VALUE="PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0">
+<INPUT TYPE=HIDDEN NAME="SESSION_DIR" VALUE="">
+<A NAME=top>
+                       <TABLE WIDTH="100%" BORDER="0" CELLSPACING="0" CELLPADDING="0">
+                       <TR><TD WIDTH="100%" BGCOLOR="#000000">
+                       <IMG SRC=http://tame.mimas.ac.uk:80/isicgi/Images/isihdr.gif BORDER=0 ALT="ISI Citation Indexes"  WIDTH="620" HEIGHT="20" ALIGN="BOTTOM" NATURALSIZEFLAG="3">
+                       </TD></TR>
+                       <TR></TR>
+                       </TABLE>
+                      <TABLE CELLSPACING=0 CELLPADDING=0>
+<TR ALIGN=CENTER VALIGN=CENTER>
+<TD><INPUT TYPE=IMAGE BORDER=0 NAME="Home" ALT="Home" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tbhome.gif"></TD><TD><a href="http://tame.mimas.ac.uk:80/isicgi/help/helpgs.html#General_Search"><IMG SRC=http://tame.mimas.ac.uk:80/isicgi/Images/tbhelp.gif ALT="Help" BORDER=0></a></TD><TD><INPUT TYPE=IMAGE BORDER=0 NAME="Date & Database Limits" ALT="Date & Database Limits" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tblimits.gif"></TD><TD><INPUT TYPE=IMAGE BORDER=0 NAME="Cited Ref Search" ALT="Cited Ref Search" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tbcrsch.gif"></TD><TD><INPUT TYPE=IMAGE BORDER=0 NAME="Log off" ALT="Log off" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tblogoff.gif"></TD></TR>
+</TABLE>
+<HR>
+		  <CENTER><STRONG><FONT SIZE=4>
+		 General Search</FONT><BR></STRONG></CENTER>
+		 Enter individual search terms or phrases separated by search operators such as AND or OR then press SEARCH below.<BR>
+		  <A href=#setlimits><FONT SIZE=+1>
+		 Set language and document type limits and sort option.</A></FONT><BR>
+		 <TABLE><TR>
+                  <TD ALIGN=right HEIGHT="1" WIDTH="74"><INPUT TYPE=IMAGE BORDER=0 VSPACE=0 HSPACE=1 SRC=http://tame.mimas.ac.uk:80/isicgi/Images/search.gif NAME="Search" ALT="Search"></TD>
+
+		  <TD>
+		 Search using terms entered below.</TD></TABLE><HR>
+		  <A HREF=http://tame.mimas.ac.uk:80/isicgi/help/helpgs.html#Basic_Index>
+		 TOPIC:</A> Enter terms from the article title, keywords, or abstract&nbsp;
+		  <A HREF=http://tame.mimas.ac.uk:80/isicgi/help/helpgs.html#topic_search_examples>
+		 Examples</A><BR>
+		 <INPUT TYPE=TEXT NAME="topic" SIZE="50" VALUE="">
+ &nbsp;<INPUT TYPE=CHECKBOX NAME="titleonly">
+Title only<P>	
+		  <A HREF=http://tame.mimas.ac.uk:80/isicgi/help/helpgs.html#Author>
+		 AUTHOR:</A> 
+		 Enter one or more author names as O'BRIAN C* OR OBRIAN C*<BR>
+		 <INPUT TYPE=TEXT NAME="author" SIZE="50" VALUE="">
+<P>
+		  <A HREF=http://tame.mimas.ac.uk:80/isicgi/help/helpgs.html#Journal>
+		 SOURCE TITLE:</A> 
+		 Enter journal title or copy and paste from the  <A HREF=http://tame.mimas.ac.uk:80/isicgi/help/A_fulljt.html>
+		 source list</A><BR>
+		 <INPUT TYPE=TEXT NAME="journal" SIZE="50" VALUE="">
+<P>
+		  <A HREF=http://tame.mimas.ac.uk:80/isicgi/help/helpgs.html#Address>
+		 ADDRESS:</A> 
+		 Enter terms from an author's affiliation as YALE UNIV SAME HOSP (see <A HREF=http://tame.mimas.ac.uk:80/isicgi/help/adabrv.html>abbreviations list</A>)<BR>
+		 <INPUT TYPE=TEXT NAME="address" SIZE="50" VALUE="">
+<BR>
+		  <HR>	    
+		  <TABLE>
+		  <TR>
+                  <TD ALIGN=right><INPUT TYPE=IMAGE SRC=http://tame.mimas.ac.uk:80/isicgi/Images/search.gif ALT="Search" BORDER=0 VSPACE=0 HSPACE=1 NAME="Search"></TD>
+
+		  <TD>
+		 Search using terms entered above.<BR></TD> <TR>
+		  <TD ALIGN=RIGHT><INPUT TYPE=IMAGE BORDER=0 VSPACE=0 HSPACE=1 SRC=http://tame.mimas.ac.uk:80/isicgi/Images/saveq.gif ALT="Save query" NAME="Save query"></TD>
+		  <TD>
+		 Save the search terms for future use.<BR></TD>
+		  <TR>
+                  <TD ALIGN=right><INPUT TYPE=IMAGE BORDER=0 VSPACE=0 HSPACE=1 SRC=http://tame.mimas.ac.uk:80/isicgi/Images/clear.gif NAME="Clear" ALT="Clear"></TD>
+		  <TD>
+		 Clear all search terms entered above.</TD>
+		  </TABLE>
+		  <A NAME=setlimits>
+		  <HR>
+		  <STRONG>
+		 SET LIMITS AND SORT OPTION</STRONG><P>
+		 <TABLE FRAME=VOID> <TR ALIGN=LEFT VALIGN=TOP>
+ <TH COLSPAN=2> Restrict search to a specific language or document type: <TR ALIGN=LEFT VALIGN=TOP>
+<TH COLSPAN=2> (Multiple items may be selected from lists) <TH>Sort results by: <TR ALIGN=LEFT VALIGN=TOP>
+<TD> <SELECT NAME="languagetype" MULTIPLE SIZE="5">
+<OPTION VALUE="All languages" SELECTED>All languages
+<OPTION VALUE="English">English
+<OPTION VALUE="Afrikaans">Afrikaans
+<OPTION VALUE="Arabic">Arabic
+<OPTION VALUE="Bengali">Bengali
+<OPTION VALUE="Bulgarian">Bulgarian
+<OPTION VALUE="Byelorussian">Byelorussian
+<OPTION VALUE="Catalan">Catalan
+<OPTION VALUE="Chinese">Chinese
+<OPTION VALUE="Croatian">Croatian
+<OPTION VALUE="Czech">Czech
+<OPTION VALUE="Danish">Danish
+<OPTION VALUE="Dutch">Dutch
+<OPTION VALUE="Estonian">Estonian
+<OPTION VALUE="Finnish">Finnish
+<OPTION VALUE="Flemish">Flemish
+<OPTION VALUE="French">French
+<OPTION VALUE="Gaelic">Gaelic
+<OPTION VALUE="Galician">Galician
+<OPTION VALUE="Georgian">Georgian
+<OPTION VALUE="German">German
+<OPTION VALUE="Greek">Greek
+<OPTION VALUE="Hebrew">Hebrew
+<OPTION VALUE="Hungarian">Hungarian
+<OPTION VALUE="Icelandic">Icelandic
+<OPTION VALUE="Italian">Italian
+<OPTION VALUE="Japanese">Japanese
+<OPTION VALUE="Korean">Korean
+<OPTION VALUE="Latin">Latin
+<OPTION VALUE="Macedonian">Macedonian
+<OPTION VALUE="Multi-Language">Multi-Language
+<OPTION VALUE="Norwegian">Norwegian
+<OPTION VALUE="Persian">Persian
+<OPTION VALUE="Polish">Polish
+<OPTION VALUE="Portuguese">Portuguese
+<OPTION VALUE="Provencal">Provencal
+<OPTION VALUE="Rumanian">Rumanian
+<OPTION VALUE="Russian">Russian
+<OPTION VALUE="Serbian">Serbian
+<OPTION VALUE="Serbo-Croatian">Serbo-Croatian
+<OPTION VALUE="Slovak">Slovak
+<OPTION VALUE="Slovene">Slovene
+<OPTION VALUE="Spanish">Spanish
+<OPTION VALUE="Swedish">Swedish
+<OPTION VALUE="Turkish">Turkish
+<OPTION VALUE="Ukrainian">Ukrainian
+<OPTION VALUE="Welsh">Welsh
+</SELECT>
+<TD><SELECT NAME="doctype" MULTIPLE SIZE="5">
+<OPTION VALUE="All document types" SELECTED>All document types
+<OPTION VALUE="Article">Article
+<OPTION VALUE="Abstract of Published Item">Abstract of Published Item
+<OPTION VALUE="Art Exhibit Review">Art Exhibit Review
+<OPTION VALUE="Bibliography">Bibliography
+<OPTION VALUE="Biographical-Item">Biographical-Item
+<OPTION VALUE="Book Review">Book Review
+<OPTION VALUE="Chronology">Chronology
+<OPTION VALUE="Correction">Correction
+<OPTION VALUE="Correction, Addition">Correction, Addition
+<OPTION VALUE="Dance Performance Review">Dance Performance Review
+<OPTION VALUE="Database Review">Database Review
+<OPTION VALUE="Discussion">Discussion
+<OPTION VALUE="Editorial Material">Editorial Material
+<OPTION VALUE="Excerpt">Excerpt
+<OPTION VALUE="Fiction, Creative Prose">Fiction, Creative Prose
+<OPTION VALUE="Film Review">Film Review
+<OPTION VALUE="Hardware Review">Hardware Review
+<OPTION VALUE="Item About an Individual">Item About an Individual
+<OPTION VALUE="Letter">Letter
+<OPTION VALUE="Meeting Abstract">Meeting Abstract
+<OPTION VALUE="Meeting-Abstract">Meeting-Abstract
+<OPTION VALUE="Music Performance Review">Music Performance Review
+<OPTION VALUE="Music Score">Music Score
+<OPTION VALUE="Music Score Review">Music Score Review
+<OPTION VALUE="News Item">News Item
+<OPTION VALUE="Note">Note
+<OPTION VALUE="Poetry">Poetry
+<OPTION VALUE="Record Review">Record Review
+<OPTION VALUE="Reprint">Reprint
+<OPTION VALUE="Review">Review
+<OPTION VALUE="Script">Script
+<OPTION VALUE="Software Review">Software Review
+<OPTION VALUE="TV Review, Radio Review">TV Review, Radio Review
+<OPTION VALUE="TV Review, Radio Review, Video">TV Review, Radio Review, Video
+<OPTION VALUE="Theater Review">Theater Review
+</SELECT>
+<TD><SELECT NAME="Sort" SIZE="5">
+<OPTION VALUE="Latest date" SELECTED>Latest date
+<OPTION VALUE="Times Cited">Times Cited
+<OPTION VALUE="Relevance">Relevance
+<OPTION VALUE="First author">First author
+<OPTION VALUE="Source Title">Source Title
+</SELECT>
+</TABLE>Back to <A HREF=#top>
+		 top of Search</A> 
+		 page <P>
+		   <HR><BR>
+		   </OL>
+		   <INPUT TYPE=HIDDEN NAME=Form VALUE=General>
+		   <INPUT TYPE=HIDDEN NAME=Func VALUE=Search>
+		 </FORM>
+<CENTER><EM><A HREF=http://wos.isitrial.com/policy/Policy.htm><FONT SIZE=-1>Acceptable Use Policy</FONT></A></EM></CENTER><P>
+<CENTER><I>Copyright &copy; 2002 <A HREF=http://www.isinet.com>Institute for Scientific Information</A></I></CENTER></BODY></HTML>

Added: ClientForm/tags/0.2.9/testdata/MarkedRecords.html
===================================================================
--- ClientForm/tags/0.2.9/testdata/MarkedRecords.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/testdata/MarkedRecords.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,152 @@
+<HTML><HEAD><TITLE>Marked Records -- Web of Science v4.31</TITLE>
+
+
+
+<SCRIPT LANGUAGE=JavaScript SRC=Common.js>
+</SCRIPT>
+
+
+</HEAD>
+<BODY BGCOLOR=#FFFFFF ><FORM  ACTION=CIW.cgi  METHOD=POST>
+<INPUT TYPE=HIDDEN NAME="SID" VALUE="PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0">
+<INPUT TYPE=HIDDEN NAME="SESSION_DIR" VALUE="">
+<A NAME=top>
+<INPUT TYPE=HIDDEN NAME="Form" VALUE="Marked_Records">
+                       <TABLE WIDTH="100%" BORDER="0" CELLSPACING="0" CELLPADDING="0">
+                       <TR><TD WIDTH="100%" BGCOLOR="#000000">
+                       <IMG SRC=http://tame.mimas.ac.uk:80/isicgi/Images/isihdr.gif BORDER=0 ALT="ISI Citation Indexes"  WIDTH="620" HEIGHT="20" ALIGN="BOTTOM" NATURALSIZEFLAG="3">
+                       </TD></TR>
+                       <TR></TR>
+                       </TABLE>
+                      
+<TABLE CELLSPACING=0 CELLPADDING=0>
+<TR ALIGN=CENTER VALIGN=CENTER>
+<TD><INPUT TYPE=IMAGE BORDER=0 NAME="Home" ALT="Home" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tbhome.gif"></TD><TD><a href="http://tame.mimas.ac.uk:80/isicgi/help/helpprn.html#Print_&_Export_Marked_Records"><IMG SRC=http://tame.mimas.ac.uk:80/isicgi/Images/tbhelp.gif ALT="Help" BORDER=0></a></TD><TD><INPUT TYPE=IMAGE BORDER=0 NAME="Date & Database Limits" ALT="Date & Database Limits" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tblimits.gif"></TD><TD><INPUT TYPE=IMAGE BORDER=0 NAME="General Search" ALT="General Search" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tbgsch.gif"></TD><TD><INPUT TYPE=IMAGE BORDER=0 NAME="Cited Ref Search" ALT="Cited Ref Search" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tbcrsch.gif"></TD><TD><INPUT TYPE=IMAGE BORDER=0 NAME="Log off" ALT="Log off" SRC="http://tame.mimas.ac.uk:80/isicgi/Images/tblogoff.gif"></TD></TR>
+</TABLE>
+<HR>
+
+<INPUT TYPE=HIDDEN NAME=id VALUE=6>
+
+<div align="center">
+      <table width="650" border="0" cellspacing="0" cellpadding="0">
+        <tr>
+          <td width="231" align="center">
+        </td>
+          <td width="215">
+            <p align="center"><br>
+   <STRONG><FONT SIZE=4>Marked Records</FONT></STRONG>
+          </td>
+          <td align="right"> </td>
+        </tr>
+        <tr>
+          <td width="231" align="center">
+            <p align="right"><b>500</b></td>
+          <td width="215">
+            <p align="center">&nbsp;<b>Records on the marked list</b></p>
+          </td>
+          <td align="right"><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Clear+Mark+List onClick="this.href = confirmLink( 'Warning: Pressing OK will clear the marked list.', 'CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Clear+Mark+List', 'javascript:void(0)');">
+<IMG SRC="Images/clearlst.gif" ALT="Clear Marked List" BORDER="0"></A></td>
+        </tr>
+      </table>
+</div>
+<hr>
+
+<font size="+1"><b>STEP 1: Select sort and output fields for the entire  marked list.</b></font>
+
+<div align="center">
+<table width="92%" border="1" height="124">
+    <tr>
+        <td width="21%" valign="top" height="124">
+          <div align="left">
+            <p align="center"><b>Select sort option:</b>
+              </p>
+            </div>
+          <div align="left">
+            <p>&nbsp;
+			<SELECT NAME="MarkedSort" SIZE="4">
+<OPTION VALUE="Latest date" SELECTED>Latest date
+<OPTION VALUE="First author">First author
+<OPTION VALUE="Source Title">Source Title
+<OPTION VALUE="Times Cited">Times Cited
+</SELECT>
+
+            </p>
+          </div>
+        </td>
+        <td width="79%" height="124">
+
+
+        <p align="center"><b>Select fields to include in addition to the author(s),
+          article title and source.</b> </p>
+
+
+          <table width="481">
+            <tr>
+                       
+              <td width="150">
+		<INPUT TYPE=CHECKBOX NAME=include_refs >cited references*</td>
+              <td width="181">
+		<INPUT TYPE=CHECKBOX NAME=address >addresses</td>
+              <td width="130">
+		<INPUT TYPE=CHECKBOX NAME=abstract >abstract</td>
+            </tr>
+            <tr>
+              <td width="150">
+		<INPUT TYPE=CHECKBOX NAME=language >language</td>
+              <td width="181">
+		<INPUT TYPE=CHECKBOX NAME=publisher >publisher information</td>
+              <td width="130">
+		<INPUT TYPE=CHECKBOX NAME=ISSN >ISSN</td>
+            </tr>
+            <tr>
+              <td width="150">
+		<INPUT TYPE=CHECKBOX NAME=doctype >document type</td>
+              <td width="181">
+		<INPUT TYPE=CHECKBOX NAME=keywords >keywords</td>
+              <td width="130">
+		<INPUT TYPE=CHECKBOX NAME=timescited >times cited</td>
+            </tr>
+          </table>
+
+	  <FONT SIZE=-1><i>*Selecting the cited references may cause the server
+          to time out with large numbers of records.</i></FONT>
+
+       </td>
+   </tr>
+</table>
+</div>
+
+<br>
+
+<font size="+1"><b>STEP 2: Select action for output.</b></font><br>
+
+<div align="center">
+ <table width=650 height="28" cellspacing="0" cellpadding="0" border="0">
+   <tr align="center">
+      <td width="542"><INPUT TYPE=IMAGE SRC=Images/print.gif NAME="Format for Print" ALT="Format for Print" BORDER=0> <INPUT TYPE=IMAGE SRC=Images/savefile.gif NAME="Save to File" ALT="Save to File" BORDER=0> <INPUT TYPE=IMAGE SRC=Images/export.gif NAME="Export to reference software" ALT="Export to reference software" BORDER=0> <INPUT TYPE=IMAGE SRC=Images/email.gif NAME="E-Mail" ALT="E-Mail" BORDER=0>
+      </td>
+	     	       </tr></table>
+</div>
+<hr>
+ <BR>
+<DL><DT><INPUT TYPE=CHECKBOX name=marked_list_selected value=000174872000059  CHECKED> Jeppsson U, Alex J, Pons MN, et al.<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=6/1>Status and future trends of ICA in wastewater treatment - a European perspective</A><BR>WATER SCI TECHNOL 45 (4-5): 485-494 2002<!000174872000059>
+<BR><BR>
+ <DT><INPUT TYPE=CHECKBOX name=marked_list_selected value=000174858300003  CHECKED> Gregory PL, Biswas AC, Batt ME<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=6/2>Musculoskeletal problems of the chest wall in athletes</A><BR>SPORTS MED 32 (4): 235-250 2002<!000174858300003>
+<BR><BR>
+<!--snip-->
+ <DT><INPUT TYPE=CHECKBOX name=marked_list_selected value=000081310100003  CHECKED> Disney RHL<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=6/499>A troublesome sibling species complex of scuttle flies (Diptera : Phoridae) revisited</A><BR>J NAT HIST 33 (8): 1159-1216 AUG 1999<!000081310100003>
+<BR><BR>
+ <DT><INPUT TYPE=CHECKBOX name=marked_list_selected value=000081297200008  CHECKED> Rosanowski F, Eysholdt U<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=6/500>Medical expertise prior to voice change surgery in male-to-female transsexuals</A><BR>HNO 47 (6): 556-562 JUN 1999<!000081297200008>
+<BR><BR>
+</DL>
+<hr>
+<div align="center">
+ <table width=650 height="28" cellspacing="0" cellpadding="0" border="0">
+   <tr align="center">
+      <td width="542"><INPUT TYPE=IMAGE SRC=Images/print.gif NAME="Format for Print" ALT="Format for Print" BORDER=0> <INPUT TYPE=IMAGE SRC=Images/savefile.gif NAME="Save to File" ALT="Save to File" BORDER=0> <INPUT TYPE=IMAGE SRC=Images/export.gif NAME="Export to reference software" ALT="Export to reference software" BORDER=0> <INPUT TYPE=IMAGE SRC=Images/email.gif NAME="E-Mail" ALT="E-Mail" BORDER=0>
+      </td>
+	     	       </tr></table>
+</div>
+<BR>Back to <A HREF=#top>top of Marked Records</A> page<BR><BR><HR></FORM>
+<CENTER><EM><A HREF=http://wos.isitrial.com/policy/Policy.htm><FONT SIZE=-1>Acceptable Use Policy</FONT></A></EM></CENTER><P>
+<CENTER><I>Copyright &copy; 2002 <A HREF=http://www.isinet.com>Institute for Scientific Information</A></I></CENTER></BODY></HTML>

Added: ClientForm/tags/0.2.9/testdata/MarkedResults.html
===================================================================
--- ClientForm/tags/0.2.9/testdata/MarkedResults.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/testdata/MarkedResults.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,97 @@
+<HTML><HEAD><TITLE>General Search Results-Summary -- Web of Science v4.31</TITLE>
+
+
+
+<SCRIPT LANGUAGE=JavaScript SRC=PageSubmit.js>
+</SCRIPT>
+
+
+</HEAD>
+<BODY BGCOLOR=#FFFFFF ><FORM  ACTION=CIW.cgi  METHOD=POST>
+
+                       <TABLE WIDTH="100%" BORDER="0" CELLSPACING="0" CELLPADDING="0">
+                       <TR><TD WIDTH="100%" BGCOLOR="#000000">
+                       <IMG SRC=http://tame.mimas.ac.uk:80/isicgi/Images/isihdr.gif BORDER=0 ALT="ISI Citation Indexes"  WIDTH="620" HEIGHT="20" ALIGN="BOTTOM" NATURALSIZEFLAG="3">
+                       </TD></TR>
+                       <TR></TR>
+                       </TABLE>
+
+<HR><TABLE WIDTH=100%><TR ALIGN=CENTER><TD><STRONG><FONT SIZE=4>General Search Results--Summary</FONT></STRONG></TD></TR></TABLE>
+ Topic=troublesome; DocType=All document types; Language=All languages; Databases=SCI-EXPANDED; Timespan=All Years; (sorted by latest date) 
+
+			    <P><TABLE WIDTH="100%" BORDER=0 CELLSPACING=0 CELLPADDING=0>
+			    <TR ALIGN=LEFT VALIGN=CENTER>
+			     <TD WIDTH=230><TABLE WIDTH=230 BORDER=0><TR>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add marked records to list" ALT="Add marked records to list" SRC=Images/marksel.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Unmark Page" ALT="Unmark Page" SRC=Images/unmarkall.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add all records retrieved to list" ALT="Add all records retrieved to list" SRC=Images/markall_old.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			     </TR></TABLE></TD>
+			    <TD WIDTH="100%"><TABLE ALIGN=CENTER BORDER=0><TD NOWRAP><B>
+			   Page 
+			      1 (Articles 1 -- 10):</B></TD>
+			      <TD WIDTH="58%">&nbsp;</TD></TR></TABLE>
+			      </TR>
+			      </TABLE>                                     			      
+			      <CENTER>
+			      <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
+			      <TR ALIGN=CENTER VALIGN=MIDDLE>
+			      <TD><IMG SRC=Images/first10i.gif ALT="First Page"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      <TD><IMG SRC=Images/back10i.gif ALT="Previous 10 Pages"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      <TD><IMG SRC=Images/prevpgi.gif ALT="Previous Page"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      
+			    <TD>[ <I>1</I> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/11 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/11')&quot;;">2</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/21 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/21')&quot;;">3</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/31 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/31')&quot;;">4</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/41 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/41')&quot;;">5</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/51 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/51')&quot;;">6</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/61 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/61')&quot;;">7</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/71 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/71')&quot;;">8</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/81 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/81')&quot;;">9</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/91 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/91')&quot;;">10</A> ]&nbsp;</TD>
+			    </TR>
+			    </TABLE></CENTER>
+			   <HR><I><FONT SIZE=-1>Use the checkboxes to add individual articles to the Marked List. Be sure to click SUBMIT MARKS button before leaving page.</FONT></I><DL>
+<DT><INPUT TYPE=CHECKBOX name=marked_list_candidates value=000174872000059/1  CHECKED> Jeppsson U, Alex J, Pons MN, et al.<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=1/1 onClick="this.href=&quot;javascript:submit_page('Abstract', '1/1')&quot;;">Status and future trends of ICA in wastewater treatment - a European perspective</A><BR>WATER SCI TECHNOL 45 (4-5): 485-494 2002
+<BR><BR>
+ <DT><INPUT TYPE=CHECKBOX name=marked_list_candidates value=000174858300003/2  CHECKED> Gregory PL, Biswas AC, Batt ME<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=1/2 onClick="this.href=&quot;javascript:submit_page('Abstract', '1/2')&quot;;">Musculoskeletal problems of the chest wall in athletes</A><BR>SPORTS MED 32 (4): 235-250 2002
+<BR><BR>
+ <DT><INPUT TYPE=CHECKBOX name=marked_list_candidates value=000174827900006/3  CHECKED> Chang DW, Hussussian C, Lewin JS, et al.<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=1/3 onClick="this.href=&quot;javascript:submit_page('Abstract', '1/3')&quot;;">Analysis of pharyngocutaneous fistula following free jejunal transfer for total laryngopharyngectomy</A><BR>PLAST RECONSTR SURG 109 (5): 1522-1527 APR 15 2002
+<BR><BR>
+</DL><HR>
+
+			    <P><TABLE WIDTH="100%" BORDER=0 CELLSPACING=0 CELLPADDING=0>
+			    <TR ALIGN=LEFT VALIGN=CENTER>
+			     <TD WIDTH=230><TABLE WIDTH=230 BORDER=0><TR>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add marked records to list" ALT="Add marked records to list" SRC=Images/marksel.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Unmark Page" ALT="Unmark Page" SRC=Images/unmarkall.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add all records retrieved to list" ALT="Add all records retrieved to list" SRC=Images/markall_old.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			     </TR></TABLE></TD>
+			    <TD WIDTH="100%"><TABLE ALIGN=CENTER BORDER=0><TD NOWRAP><B>
+			   Page 
+			      1 (Articles 1 -- 10):</B></TD>
+			      <TD WIDTH="58%">&nbsp;</TD></TR></TABLE>
+			      </TR>
+			      </TABLE>                                     			      
+			      <CENTER>
+			      <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
+			      <TR ALIGN=CENTER VALIGN=MIDDLE>
+			      <TD><IMG SRC=Images/first10i.gif ALT="First Page"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      <TD><IMG SRC=Images/back10i.gif ALT="Previous 10 Pages"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      <TD><IMG SRC=Images/prevpgi.gif ALT="Previous Page"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      
+			    <TD>[ <I>1</I> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/11 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/11')&quot;;">2</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/21 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/21')&quot;;">3</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/31 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/31')&quot;;">4</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/41 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/41')&quot;;">5</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/51 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/51')&quot;;">6</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/61 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/61')&quot;;">7</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/71 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/71')&quot;;">8</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/81 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/81')&quot;;">9</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/91 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/91')&quot;;">10</A> ]&nbsp;</TD>
+			    </TR>
+			    </TABLE></CENTER>
+			   <BR>1783 of 16635816 documents matched the query. (500 shown)<HR></FORM>
+<CENTER><EM><A HREF=http://wos.isitrial.com/policy/Policy.htm><FONT SIZE=-1>Acceptable Use Policy</FONT></A></EM></CENTER><P>
+<CENTER><I>Copyright &copy; 2002 <A HREF=http://www.isinet.com>Institute for Scientific Information</A></I></CENTER></BODY></HTML>

Added: ClientForm/tags/0.2.9/testdata/Results.html
===================================================================
--- ClientForm/tags/0.2.9/testdata/Results.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/testdata/Results.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,94 @@
+<HTML><HEAD><TITLE>General Search Results-Summary -- Web of Science v4.31</TITLE>
+
+
+
+<SCRIPT LANGUAGE=JavaScript SRC=PageSubmit.js>
+</SCRIPT>
+
+
+</HEAD>
+<BODY BGCOLOR=#FFFFFF ><FORM  ACTION=CIW.cgi  METHOD=POST>
+
+                       <TABLE WIDTH="100%" BORDER="0" CELLSPACING="0" CELLPADDING="0">
+                       <TR><TD WIDTH="100%" BGCOLOR="#000000">
+                       <IMG SRC=http://tame.mimas.ac.uk:80/isicgi/Images/isihdr.gif BORDER=0 ALT="ISI Citation Indexes"  WIDTH="620" HEIGHT="20" ALIGN="BOTTOM" NATURALSIZEFLAG="3">
+                       </TD></TR>
+                       <TR></TR>
+                       </TABLE>
+
+<HR>
+<TABLE WIDTH=100%><TR ALIGN=CENTER><TD><STRONG><FONT SIZE=4>General Search Results--Summary</FONT></STRONG></TD></TR><TR><TD> </TD></TR></TABLE>
+  Topic=troublesome; DocType=All document types; Language=All languages; Databases=SCI-EXPANDED; Timespan=All Years; (sorted by latest date)
+
+			    <P><TABLE WIDTH="100%" BORDER=0 CELLSPACING=0 CELLPADDING=0>
+			    <TR ALIGN=LEFT VALIGN=CENTER>
+			     <TD WIDTH=230><TABLE WIDTH=230 BORDER=0><TR>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add marked records to list" ALT="Add marked records to list" SRC=Images/marksel.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add records on page to list" ALT="Add records on page to list" SRC=Images/markall.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add all records retrieved to list" ALT="Add all records retrieved to list" SRC=Images/markall_old.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			     </TR></TABLE></TD>
+			    <TD WIDTH="100%"><TABLE ALIGN=CENTER BORDER=0><TD NOWRAP><B>
+			   Page 
+			      1 (Articles 1 -- 10):</B></TD>
+			      <TD WIDTH="58%">&nbsp;</TD></TR></TABLE>
+			      </TR>
+			      </TABLE>                                     			      
+			      <CENTER>
+			      <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
+			      <TR ALIGN=CENTER VALIGN=MIDDLE>
+			      <TD><IMG SRC=Images/first10i.gif ALT="First Page"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      <TD><IMG SRC=Images/back10i.gif ALT="Previous 10 Pages"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      <TD><IMG SRC=Images/prevpgi.gif ALT="Previous Page"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      
+			    <TD>[ <I>1</I> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/11 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/11')&quot;;">2</A> </TD>
+<TD>. . . 
+			    <TD><IMG SRC=Images/frwrd10i.gif ALT="Next 10 Pages"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			    </TR>
+			    </TABLE></CENTER>
+			   <HR><I><FONT SIZE=-1>Use the checkboxes to add individual articles to the Marked List.  Be sure to click SUBMIT MARKS button before leaving page.</FONT></I><DL>
+<DT><INPUT TYPE=CHECKBOX name=marked_list_candidates value=000174872000059/1 > Jeppsson U, Alex J, Pons MN, et al.<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=1/1 onClick="this.href=&quot;javascript:submit_page('Abstract', '1/1')&quot;;">Status and future trends of ICA in wastewater treatment - a European perspective</A><BR>WATER SCI TECHNOL 45 (4-5): 485-494 2002
+<BR><BR>
+ <DT><INPUT TYPE=CHECKBOX name=marked_list_candidates value=000174858300003/2 > Gregory PL, Biswas AC, Batt ME<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=1/2 onClick="this.href=&quot;javascript:submit_page('Abstract', '1/2')&quot;;">Musculoskeletal problems of the chest wall in athletes</A><BR>SPORTS MED 32 (4): 235-250 2002
+<BR><BR>
+ <DT><INPUT TYPE=CHECKBOX name=marked_list_candidates value=000174827900006/3 > Chang DW, Hussussian C, Lewin JS, et al.<DD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Abstract&doc=1/3 onClick="this.href=&quot;javascript:submit_page('Abstract', '1/3')&quot;;">Analysis of pharyngocutaneous fistula following free jejunal transfer for total laryngopharyngectomy</A><BR>PLAST RECONSTR SURG 109 (5): 1522-1527 APR 15 2002
+<BR><BR>
+</DL><HR>
+ 
+			    <P><TABLE WIDTH="100%" BORDER=0 CELLSPACING=0 CELLPADDING=0>
+			    <TR ALIGN=LEFT VALIGN=CENTER>
+			     <TD WIDTH=230><TABLE WIDTH=230 BORDER=0><TR>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add marked records to list" ALT="Add marked records to list" SRC=Images/marksel.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add records on page to list" ALT="Add records on page to list" SRC=Images/markall.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			      <TD WIDTH=75> <INPUT TYPE=IMAGE NAME="Add all records retrieved to list" ALT="Add all records retrieved to list" SRC=Images/markall_old.gif  BORDER=0 VSPACE=1 HSPACE=1  > </TD>
+			     </TR></TABLE></TD>
+			    <TD WIDTH="100%"><TABLE ALIGN=CENTER BORDER=0><TD NOWRAP><B>
+			   Page 
+			      1 (Articles 1 -- 10):</B></TD>
+			      <TD WIDTH="58%">&nbsp;</TD></TR></TABLE>
+			      </TR>
+			      </TABLE>                                     			      
+			      <CENTER>
+			      <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
+			      <TR ALIGN=CENTER VALIGN=MIDDLE>
+			      <TD><IMG SRC=Images/first10i.gif ALT="First Page"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      <TD><IMG SRC=Images/back10i.gif ALT="Previous 10 Pages"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      <TD><IMG SRC=Images/prevpgi.gif ALT="Previous Page"  BORDER=0 VSPACE=1 HSPACE=1 > </TD>
+			      
+			    <TD>[ <I>1</I> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/11 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/11')&quot;;">2</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/21 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/21')&quot;;">3</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/31 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/31')&quot;;">4</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/41 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/41')&quot;;">5</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/51 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/51')&quot;;">6</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/61 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/61')&quot;;">7</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/71 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/71')&quot;;">8</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/81 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/81')&quot;;">9</A> |&nbsp;</TD>
+				<TD><A HREF=CIW.cgi?PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0&Func=Summary&curr_doc=1/91 onClick="this.href=&quot;javascript:submit_page('PageNo', '1/91')&quot;;">10</A> ]&nbsp;</TD>
+			    </TR>
+			    </TABLE></CENTER>
+
+<BR>1783 of 16635816 documents matched the query. (500 shown)<HR>
+</FORM>
+<CENTER><EM><A HREF=http://wos.isitrial.com/policy/Policy.htm><FONT SIZE=-1>Acceptable Use Policy</FONT></A></EM></CENTER><P>
+<CENTER><I>Copyright &copy; 2002 <A HREF=http://www.isinet.com>Institute for Scientific Information</A></I></CENTER></BODY></HTML>

Added: ClientForm/tags/0.2.9/testdata/SearchType.html
===================================================================
--- ClientForm/tags/0.2.9/testdata/SearchType.html	                        (rev 0)
+++ ClientForm/tags/0.2.9/testdata/SearchType.html	2008-12-01 05:16:09 UTC (rev 93460)
@@ -0,0 +1,55 @@
+<HTML><HEAD><TITLE>Welcome -- Web of Science v4.31</TITLE>
+
+
+
+<SCRIPT LANGUAGE=JavaScript SRC=Common.js>
+</SCRIPT>
+
+
+
+<SCRIPT LANGUAGE=JavaScript>
+<!-- Hide script from old browsers.
+function main(){
+	JavaScriptTest();
+}
+// End script hide. -->
+</SCRIPT>
+
+</HEAD>
+<BODY BGCOLOR=#FFFFFF onLoad="main()" ><FORM  ACTION=CIW.cgi  METHOD=POST>
+<INPUT TYPE=HIDDEN NAME="SID" VALUE="PMrU0IJYy4MAAELSXic_E2011300_PMrU0IJYy4MAAELSXic-0">
+<INPUT TYPE=HIDDEN NAME="SESSION_DIR" VALUE="">
+
+		<A NAME=top></A>
+		<CENTER><IMG SRC=Images/main.jpg ALT="Institute for Scientific Information"></CENTER>
+		
+		<P>
+		<CENTER>
+		<TABLE BORDER=2 CELLPADDING=0>
+		<TR>
+		<TD ALIGN=CENTER><INPUT TYPE=IMAGE SRC=Images/fullsch.gif NAME="Full Search" ALT="Full Search" BORDER=0>
+		<TD>
+		<TABLE>
+		<TR><TD><TD>Search by bibliographic information (topic, author, source, address) or by cited reference.
+</TABLE>
+		<TR>
+		<TD ALIGN=CENTER><INPUT TYPE=IMAGE SRC=Images/quiksch.gif NAME="Easy Search" ALT="Easy Search" BORDER=0>
+		 <TD><TABLE>
+		 <TR><TD><TD>Search for a limited number of articles on a specific topic, person, or address.</TABLE>
+<TR><TD ALIGN=CENTER>
+ <INPUT TYPE=IMAGE SRC=Images/newsession.gif NAME="New Session" ALT="New Session" BORDER=0>
+ <TD>
+ <TABLE><TR><TD><TD>
+Clear all search forms and the marked list.</TABLE> <TR>
+ <TD ALIGN=CENTER>
+<INPUT TYPE=IMAGE SRC=Images/logoff.gif NAME="Log off" ALT="Log off" BORDER=0>
+ <TD><TABLE>
+ <TR><TD>
+Fully disconnect from the database and make your connection available to another user at your institution.</TD></TABLE><INPUT TYPE=HIDDEN NAME=Form Value=Welcome>
+</TABLE></CENTER>
+<HR>
+<INPUT TYPE=HIDDEN NAME="JavaScript" VALUE="No">
+<P><CENTER><IMG SRC=Images/isilogo.gif ALT="ISI Thomson Scientific"></CENTER><P>
+</FORM>
+<CENTER><EM><A HREF=http://wos.isitrial.com/policy/Policy.htm><FONT SIZE=-1>Acceptable Use Policy</FONT></A></EM></CENTER><P>
+<CENTER><I>Copyright &copy; 2002 <A HREF=http://www.isinet.com>Institute for Scientific Information</A></I></CENTER></BODY></HTML>



More information about the Checkins mailing list