r771 - in python-clientform/trunk: . ClientForm.egg-info debian

Jérémy Bobbio lunar at alioth.debian.org
Mon Apr 9 22:05:00 UTC 2007


Author: lunar
Date: 2007-04-09 22:04:59 +0000 (Mon, 09 Apr 2007)
New Revision: 771

Added:
   python-clientform/trunk/ClientForm.egg-info/dependency_links.txt
   python-clientform/trunk/ez_setup.py
   python-clientform/trunk/setup.cfg
Removed:
   python-clientform/trunk/ez_setup/
Modified:
   python-clientform/trunk/ChangeLog.txt
   python-clientform/trunk/ClientForm.egg-info/PKG-INFO
   python-clientform/trunk/ClientForm.egg-info/SOURCES.txt
   python-clientform/trunk/ClientForm.egg-info/zip-safe
   python-clientform/trunk/ClientForm.py
   python-clientform/trunk/GeneralFAQ.html
   python-clientform/trunk/INSTALL.txt
   python-clientform/trunk/MANIFEST.in
   python-clientform/trunk/PKG-INFO
   python-clientform/trunk/README.html
   python-clientform/trunk/README.html.in
   python-clientform/trunk/README.txt
   python-clientform/trunk/debian/changelog
   python-clientform/trunk/debian/control
   python-clientform/trunk/setup.py
   python-clientform/trunk/test.py
Log:
Ready 0.2.6-1.


Modified: python-clientform/trunk/ChangeLog.txt
===================================================================
--- python-clientform/trunk/ChangeLog.txt	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/ChangeLog.txt	2007-04-09 22:04:59 UTC (rev 771)
@@ -1,6 +1,49 @@
 This isn't really in proper GNU ChangeLog format, it just happens to
 look that way.
 
+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

Modified: python-clientform/trunk/ClientForm.egg-info/PKG-INFO
===================================================================
--- python-clientform/trunk/ClientForm.egg-info/PKG-INFO	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/ClientForm.egg-info/PKG-INFO	2007-04-09 22:04:59 UTC (rev 771)
@@ -1,12 +1,12 @@
 Metadata-Version: 1.0
 Name: ClientForm
-Version: 0.2.2
+Version: 0.2.6
 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.2.tar.gz
+Download-URL: http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0.2.6.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'

Modified: python-clientform/trunk/ClientForm.egg-info/SOURCES.txt
===================================================================
--- python-clientform/trunk/ClientForm.egg-info/SOURCES.txt	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/ClientForm.egg-info/SOURCES.txt	2007-04-09 22:04:59 UTC (rev 771)
@@ -8,10 +8,12 @@
 README.html
 README.html.in
 README.txt
+ez_setup.py
 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
@@ -20,8 +22,6 @@
 examples/example.html
 examples/example.py
 examples/simple.py
-ez_setup/README.txt
-ez_setup/__init__.py
 testdata/Auth.html
 testdata/FullSearch.html
 testdata/GeneralSearch.html

Copied: python-clientform/trunk/ClientForm.egg-info/dependency_links.txt (from rev 770, python-clientform/branches/upstream/current/ClientForm.egg-info/dependency_links.txt)

Modified: python-clientform/trunk/ClientForm.egg-info/zip-safe
===================================================================
--- python-clientform/trunk/ClientForm.egg-info/zip-safe	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/ClientForm.egg-info/zip-safe	2007-04-09 22:04:59 UTC (rev 771)
@@ -0,0 +1 @@
+

Modified: python-clientform/trunk/ClientForm.py
===================================================================
--- python-clientform/trunk/ClientForm.py	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/ClientForm.py	2007-04-09 22:04:59 UTC (rev 771)
@@ -21,18 +21,15 @@
 Copyright 1998-2000 Gisle Aas.
 
 This code is free software; you can redistribute it and/or modify it
-under the terms of the BSD License (see the file COPYING included with
-the distribution).
+under the terms of the BSD or ZPL 2.1 licenses (see the file
+COPYING.txt included with the distribution).
 
 """
 
 # XXX
-# Remove unescape_attr method
+# add an __all__
 # Remove parser testing hack
 # safeUrl()-ize action
-# Really should to merge CC, CF, pp and mechanize as soon as mechanize
-#  goes to beta...
-# Add url attribute to ParseError
 # 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??
@@ -41,11 +38,6 @@
 #  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.
-# Controls can have name=None (e.g. forms constructed partly with
-#  JavaScript), but find_control can't be told to find a control
-#  with that name, because None there means 'unspecified'.  Can still
-#  get at by nr, but would be nice to be able to specify something
-#  equivalent to name=None, too.
 # 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
@@ -109,10 +101,22 @@
 
 import sys, urllib, urllib2, types, mimetools, copy, urlparse, \
        htmlentitydefs, re, random
-from urlparse import urljoin
 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):
@@ -121,15 +125,21 @@
     def deprecation(message):
         warnings.warn(message, DeprecationWarning, stacklevel=2)
 
-VERSION = "0.2.2"
+VERSION = "0.2.6"
 
 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.
@@ -429,10 +439,25 @@
 
 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 ParseError(Exception): pass
 
-
 class _AbstractFormParser:
     """forms attribute contains HTMLForm instances on completion."""
     # thanks to Moshe Zadka for an example of sgmllib/htmllib usage
@@ -452,6 +477,13 @@
         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:
@@ -462,12 +494,12 @@
         debug("")
         if self._current_label is not None:
             self.end_label()
-        if self._current_form is not None:
+        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 None:
+        if self._current_form is not self._global_form:
             raise ParseError("nested FORMs")
         name = None
         action = None
@@ -491,15 +523,13 @@
         debug("")
         if self._current_label is not None:
             self.end_label()
-        if self._current_form is None:
+        if self._current_form is self._global_form:
             raise ParseError("end of FORM before start")
         self.forms.append(self._current_form)
-        self._current_form = None
+        self._current_form = self._global_form
 
     def start_select(self, attrs):
         debug("%s", attrs)
-        if self._current_form is None:
-            raise ParseError("start of SELECT before start of FORM")
         if self._select is not None:
             raise ParseError("nested SELECTs")
         if self._textarea is not None:
@@ -515,8 +545,8 @@
 
     def end_select(self):
         debug("")
-        if self._current_form is None:
-            raise ParseError("end of SELECT before start of FORM")
+        if self._current_form is self._global_form:
+            return
         if self._select is None:
             raise ParseError("end of SELECT before start")
 
@@ -583,8 +613,6 @@
 
     def start_textarea(self, attrs):
         debug("%s", attrs)
-        if self._current_form is None:
-            raise ParseError("start of TEXTAREA before start of FORM")
         if self._textarea is not None:
             raise ParseError("nested TEXTAREAs")
         if self._select is not None:
@@ -598,8 +626,8 @@
 
     def end_textarea(self):
         debug("")
-        if self._current_form is None:
-            raise ParseError("end of TEXTAREA before start of FORM")
+        if self._current_form is self._global_form:
+            return
         if self._textarea is None:
             raise ParseError("end of TEXTAREA before start")
         controls = self._current_form[2]
@@ -642,14 +670,17 @@
                 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:1] == '\n':
+        if data[0:2] == "\r\n":
+            data = data[2:]
+        if data[0:1] in ["\n", "\r"]:
             data = data[1:]
 
-        debug("%s", data)
         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
@@ -660,6 +691,7 @@
         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
@@ -674,8 +706,6 @@
 
     def do_button(self, attrs):
         debug("%s", attrs)
-        if self._current_form is None:
-            raise ParseError("start of BUTTON before start of FORM")
         d = {}
         d["type"] = "submit"  # default
         for key, val in attrs:
@@ -694,8 +724,6 @@
 
     def do_input(self, attrs):
         debug("%s", attrs)
-        if self._current_form is None:
-            raise ParseError("start of INPUT before start of FORM")
         d = {}
         d["type"] = "text"  # default
         for key, val in attrs:
@@ -709,8 +737,6 @@
 
     def do_isindex(self, attrs):
         debug("%s", attrs)
-        if self._current_form is None:
-            raise ParseError("start of ISINDEX before start of FORM")
         d = {}
         for key, val in attrs:
             d[key] = val
@@ -750,11 +776,7 @@
     def unknown_charref(self, ref): self.handle_data("&#%s;" % ref)
 
 
-# HTMLParser.HTMLParser is recent, so live without it if it's not available
-# (also, htmllib.HTMLParser is much more tolerant of bad HTML)
-try:
-    import HTMLParser
-except ImportError:
+if not HAVE_MODULE_HTMLPARSER:
     class XHTMLCompatibleFormParser:
         def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
             raise ValueError("HTMLParser could not be imported")
@@ -766,6 +788,12 @@
             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)
 
@@ -803,31 +831,52 @@
         def unescape_attrs_if_required(self, attrs):
             return attrs  # ditto
 
-import sgmllib
-# monkeypatch to fix http://www.python.org/sf/803422 :-(
-sgmllib.charref = re.compile("&#(x?[0-9a-fA-F]+)[^0-9a-fA-F]")
+
 class _AbstractSgmllibParser(_AbstractFormParser):
+
     def do_option(self, attrs):
         _AbstractFormParser._start_option(self, attrs)
 
-    def unescape_attr_if_required(self, name):
-        return self.unescape_attr(name)
-    def unescape_attrs_if_required(self, attrs):
-        return self.unescape_attrs(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)
 
-try:
-    if sys.version_info[:2] < (2, 2):
-        raise ImportError  # BeautifulSoup uses generators
-    import BeautifulSoup
-except ImportError:
-    pass
-else:
+    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):
@@ -836,31 +885,114 @@
         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, BeautifulSoup.BeautifulSoup):
+
+    class RobustFormParser(_AbstractBSFormParser, bs):
         """Tries to be highly tolerant of incorrect HTML."""
-        bs_base_class = BeautifulSoup.BeautifulSoup
-    class NestingRobustFormParser(_AbstractBSFormParser,
-                                  BeautifulSoup.ICantBelieveItsBeautifulSoup):
+        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).
 
         """
-        bs_base_class = BeautifulSoup.ICantBelieveItsBeautifulSoup
+        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
+        )
+
+
 #FormParser = XHTMLCompatibleFormParser  # testing hack
 #FormParser = RobustFormParser  # testing hack
 
-def ParseResponse(response, select_default=False,
-                  ignore_errors=False,  # ignored!
-                  form_parser_class=FormParser,
-                  request_class=urllib2.Request,
-                  entitydefs=None,
-                  backwards_compat=True,
-                  encoding=DEFAULT_ENCODING,
-                  ):
+
+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
@@ -920,23 +1052,9 @@
     own risk: there is no well-defined interface.
 
     """
-    return ParseFile(response, response.geturl(), select_default,
-                     False,
-                     form_parser_class,
-                     request_class,
-                     entitydefs,
-                     backwards_compat,
-                     encoding,
-                     )
+    return _ParseFileEx(response, response.geturl(), *args, **kwds)[1:]
 
-def ParseFile(file, base_uri, select_default=False,
-              ignore_errors=False,  # ignored!
-              form_parser_class=FormParser,
-              request_class=urllib2.Request,
-              entitydefs=None,
-              backwards_compat=True,
-              encoding=DEFAULT_ENCODING,
-              ):
+def ParseFile(file, base_uri, *args, **kwds):
     """Parse HTML and return a list of HTMLForm instances.
 
     ClientForm.ParseError is raised on parse errors.
@@ -950,6 +1068,20 @@
     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")
     fp = form_parser_class(entitydefs, encoding)
@@ -980,7 +1112,7 @@
         if action is None:
             action = base_uri
         else:
-            action = urljoin(base_uri, action)
+            action = _urljoin(base_uri, action)
         action = fp.unescape_attr_if_required(action)
         name = fp.unescape_attr_if_required(name)
         attrs = fp.unescape_attrs_if_required(attrs)
@@ -988,6 +1120,8 @@
         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]
             attrs = fp.unescape_attrs_if_required(attrs)
@@ -1181,6 +1315,9 @@
 
         self._clicked = False
 
+        self._urlparse = urlparse.urlparse
+        self._urlunparse = urlparse.urlunparse
+
     def __getattr__(self, name):
         if name == "value":
             return self.__dict__["_value"]
@@ -1389,10 +1526,10 @@
         # 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 = urlparse.urlparse(form.action)
+        parts = self._urlparse(form.action)
         rest, (query, frag) = parts[:-2], parts[-2:]
-        parts = rest + (urllib.quote_plus(self.value), "")
-        url = urlparse.urlunparse(parts)
+        parts = rest + (urllib.quote_plus(self.value), None)
+        url = self._urlunparse(parts)
         req_data = url, None, []
 
         if return_type == "pairs":
@@ -1860,12 +1997,16 @@
         assert self._form is None or form == self._form, (
             "can't add control to more than one form")
         self._form = form
-        try:
-            control = form.find_control(self.name, self.type)
-        except ControlNotFoundError:
+        if self.name is None:
+            # always count nameless elements as separate controls
             Control.add_to_form(self, form)
         else:
-            control.merge_control(self)
+            try:
+                control = form.find_control(self.name, self.type)
+            except (ControlNotFoundError, AmbiguityError):
+                Control.add_to_form(self, form)
+            else:
+                control.merge_control(self)
 
     def merge_control(self, control):
         assert bool(control.multiple) == bool(self.multiple)
@@ -1911,6 +2052,8 @@
     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:
@@ -2080,7 +2223,7 @@
         return [o.name for o in self.items]
 
     def _totally_ordered_pairs(self):
-        if self.disabled:
+        if self.disabled or self.name is None:
             return []
         else:
             return [(o._index, self.name, o.name) for o in self.items
@@ -2622,6 +2765,9 @@
 
         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
@@ -2680,6 +2826,8 @@
         else:
             control = klass(type, name, a, index)
         control.add_to_form(self)
+        control._urlparse = self._urlparse
+        control._urlunparse = self._urlunparse
 
     def fixup(self):
         """Normalise form after all controls have been added.
@@ -3057,7 +3205,8 @@
                                   is_listcontrol, nr)
 
     def _find_control(self, name, type, kind, id, label, predicate, nr):
-        if (name is not None) and not isstringlike(name):
+        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")
@@ -3079,7 +3228,8 @@
             nr = 0
 
         for control in self.controls:
-            if name is not None and name != control.name:
+            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
@@ -3109,7 +3259,7 @@
             return found
 
         description = []
-        if name is not None: description.append("name '%s'" % name)
+        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)
@@ -3166,19 +3316,19 @@
         """Return a tuple (url, data, headers)."""
         method = self.method.upper()
         #scheme, netloc, path, parameters, query, frag = urlparse.urlparse(self.action)
-        parts = 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()), "")
-            uri = urlparse.urlunparse(parts)
+            parts = rest + (urlencode(self._pairs()), None)
+            uri = self._urlunparse(parts)
             return uri, None, []
         elif method == "POST":
-            parts = rest + (query, "")
-            uri = urlparse.urlunparse(parts)
+            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)])

Modified: python-clientform/trunk/GeneralFAQ.html
===================================================================
--- python-clientform/trunk/GeneralFAQ.html	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/GeneralFAQ.html	2007-04-09 22:04:59 UTC (rev 771)
@@ -8,7 +8,7 @@
 <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-01-05">
+  <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>
@@ -26,10 +26,10 @@
 <div id="Content">
 <ul>
   <li>Is there any example code?
-     <p>There's (still!) a bit of a shortage of example code for ClientCookie
-     and ClientForm &amp;co., because the stuff I've written tends to either
-     require access to restricted-access sites, or is proprietary code (and the
-     same goes for other people's 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.
@@ -67,7 +67,7 @@
        <code>CookieJar</code> instance, calling methods on
        <code>HTMLForm</code>s, calling <code>urlopen</code>, etc.
 
-       <li>Dump ClientCookie and ClientForm and automate a browser instead.
+       <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.
@@ -127,7 +127,7 @@
   <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
-     ClientCookie have been merged into <code>urllib2</code> for Python 2.4.
+     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>
@@ -141,7 +141,7 @@
 mailing list</a> rather than direct to me.
 
 <p><a href="mailto:jjl at pobox.com">John J. Lee</a>,
-January 2006.
+May 2006.
 
 </div> <!--id="Content"-->
 
@@ -152,11 +152,12 @@
 <span class="thispage">General FAQs</span><br>
 <br>
 <a href="../mechanize/">mechanize</a><br>
-<a href="../pullparser/">pullparser</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="../ClientForm/">ClientForm</a><br>
-<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>

Modified: python-clientform/trunk/INSTALL.txt
===================================================================
--- python-clientform/trunk/INSTALL.txt	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/INSTALL.txt	2007-04-09 22:04:59 UTC (rev 771)
@@ -54,8 +54,8 @@
 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 licenses (see the
-file COPYING.txt).
+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

Modified: python-clientform/trunk/MANIFEST.in
===================================================================
--- python-clientform/trunk/MANIFEST.in	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/MANIFEST.in	2007-04-09 22:04:59 UTC (rev 771)
@@ -10,4 +10,3 @@
 include *.py
 recursive-include testdata *.html
 recursive-include examples *.dat *.txt *.html *.cgi *.py
-recursive-include ez_setup *.py

Modified: python-clientform/trunk/PKG-INFO
===================================================================
--- python-clientform/trunk/PKG-INFO	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/PKG-INFO	2007-04-09 22:04:59 UTC (rev 771)
@@ -1,12 +1,12 @@
 Metadata-Version: 1.0
 Name: ClientForm
-Version: 0.2.2
+Version: 0.2.6
 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.2.tar.gz
+Download-URL: http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0.2.6.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'

Modified: python-clientform/trunk/README.html
===================================================================
--- python-clientform/trunk/README.html	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/README.html	2007-04-09 22:04:59 UTC (rev 771)
@@ -2,18 +2,15 @@
         "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="2006-03-22">
+  <meta name="date" content="2006-10-25">
   <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>
-  <base href="http://wwwsearch.sourceforge.net/ClientForm/">
+  
 </head>
 <body>
 
@@ -267,9 +264,9 @@
 
 <p>For full documentation, see the docstrings in ClientForm.py.
 
-<p><em><strong>Note: this page describes the 0.2 (development release)
-interface.  See <a href="./src/README_0_1_18.html">here</a> for the stable
-0.1 interface.</strong> </em>
+<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>
@@ -338,7 +335,7 @@
 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=True</code> behaviour.  </ul>
+0.2 <code>backwards_compat=False</code> behaviour.  </ul>
 
 <a name="credits"></a>
 <h2>Credits</h2>
@@ -369,8 +366,8 @@
 
 <ul>
 
-<li><a href="./src/ClientForm-0.2.2.tar.gz">ClientForm-0.2.2.tar.gz</a>
-<li><a href="./src/ClientForm-0.2.2.zip">ClientForm-0.2.2.zip</a>
+<li><a href="./src/ClientForm-0.2.6.tar.gz">ClientForm-0.2.6.tar.gz</a>
+<li><a href="./src/ClientForm-0.2.6.zip">ClientForm-0.2.6.zip</a>
 <li><a href="./src/ChangeLog.txt">Change Log</a> (included in distribution)
 <li><a href="./src/">Older releases.</a>
 </ul>
@@ -543,7 +540,7 @@
 mailing list</a> rather than direct to me.
 
 <p><a href="mailto:jjl at pobox.com">John J. Lee</a>,
-March 2006.
+October 2006.
 
 </div>
 
@@ -554,11 +551,12 @@
 <a href="../bits/GeneralFAQ.html">General FAQs</a><br>
 <br>
 <a href="../mechanize/">mechanize</a><br>
-<a href="../pullparser/">pullparser</a><br>
-<a href="../ClientCookie/">ClientCookie</a><br>
-<a href="../ClientCookie/doc.html"><span class="subpage">ClientCookie docs</span></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>

Modified: python-clientform/trunk/README.html.in
===================================================================
--- python-clientform/trunk/README.html.in	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/README.html.in	2007-04-09 22:04:59 UTC (rev 771)
@@ -3,10 +3,16 @@
 @# 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 24825 2006-03-22 21:41:49Z jjlee $")}
+@{
+from colorize import colorize
+import time
+import release
+last_modified = release.svn_id_to_time("$Id: README.html.in 33738 2006-10-25 19:54:30Z jjlee $")
+try:
+    base
+except NameError:
+    base = False
+}
 <html>
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
@@ -15,7 +21,7 @@
   <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>
-  <base href="http://wwwsearch.sourceforge.net/ClientForm/">
+  @[if base]<base href="http://wwwsearch.sourceforge.net/ClientForm/">@[end if]
 </head>
 <body>
 
@@ -75,9 +81,9 @@
 
 <p>For full documentation, see the docstrings in ClientForm.py.
 
-<p><em><strong>Note: this page describes the 0.2 (development release)
-interface.  See <a href="./src/README_0_1_18.html">here</a> for the stable
-0.1 interface.</strong> </em>
+<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>
@@ -146,7 +152,7 @@
 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=True</code> behaviour.  </ul>
+0.2 <code>backwards_compat=False</code> behaviour.  </ul>
 
 <a name="credits"></a>
 <h2>Credits</h2>
@@ -176,7 +182,7 @@
 methods are still there, but some have been deprecated and a few added).
 
 <ul>
-@{version = "0.2.2"}
+@{version = "0.2.6"}
 <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)

Modified: python-clientform/trunk/README.txt
===================================================================
--- python-clientform/trunk/README.txt	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/README.txt	2007-04-09 22:04:59 UTC (rev 771)
@@ -1,5 +1,6 @@
-   [1]SourceForge.net Logo
 
+   [1]SourceForge.net Logo 
+
                                   ClientForm
 
    ClientForm is a Python module for handling HTML forms on the client
@@ -231,8 +232,8 @@
 
    For full documentation, see the docstrings in ClientForm.py.
 
-   Note: this page describes the 0.2 (development release) interface. See
-   [4]here for the stable 0.1 interface.
+   Note: this page describes the 0.2 (stable release) interface. See
+   [4]here for the old 0.1 interface. 
 
 Parsers
 
@@ -289,7 +290,7 @@
        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=True behaviour.
+       backwards_compat=False behaviour.
 
 Credits
 
@@ -312,8 +313,8 @@
    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.2.tar.gz
-     * [11]ClientForm-0.2.2.zip
+     * [10]ClientForm-0.2.6.tar.gz
+     * [11]ClientForm-0.2.6.zip
      * [12]Change Log (included in distribution)
      * [13]Older releases.
 
@@ -444,83 +445,85 @@
    I prefer questions and comments to be sent to the [35]mailing list
    rather than direct to me.
 
-   [36]John J. Lee, March 2006.
+   [36]John J. Lee, October 2006.
 
    [37]Home
    [38]General FAQs
    [39]mechanize
-   [40]pullparser
+   [40]mechanize docs
+   ClientForm
    [41]ClientCookie
    [42]ClientCookie docs
-   ClientForm
-   [43]DOMForm
-   [44]python-spidermonkey
-   [45]ClientTable
-   [46]1.5.2 urllib2.py
-   [47]1.5.2 urllib.py
-   [48]Other stuff
-   [49]Example
-   [50]Notes
-   [51]Parsers
-   [52]Compatibility
-   [53]Credits
-   [54]Download
-   [55]FAQs
+   [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. http://wwwsearch.sourceforge.net/ClientForm/src/README_0_1_18.html
-   5. http://wwwsearch.sourceforge.net/ClientForm/#faq
+   4. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/README-0_1_17.html
+   5. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#faq
    6. http://www.egenix.com/files/python/mxTidy.html
    7. http://utidylib.berlios.de/
    8. http://www.crummy.com/software/BeautifulSoup/
-   9. http://wwwsearch.sourceforge.net/ClientForm/#compat
-  10. http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0.2.2.tar.gz
-  11. http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0.2.2.zip
-  12. http://wwwsearch.sourceforge.net/ClientForm/src/ChangeLog.txt
-  13. http://wwwsearch.sourceforge.net/ClientForm/src/
-  14. http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0.1.17.tar.gz
-  15. http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0_1_17.zip
-  16. http://wwwsearch.sourceforge.net/ClientForm/src/ChangeLog.txt
-  17. http://wwwsearch.sourceforge.net/ClientForm/src/
-  18. http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0.0.16.tar.gz
-  19. http://wwwsearch.sourceforge.net/ClientForm/src/ClientForm-0_0_16.zip
-  20. http://wwwsearch.sourceforge.net/ClientForm/src/ChangeLog.txt
-  21. http://wwwsearch.sourceforge.net/ClientForm/src/
+   9. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#compat
+  10. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ClientForm-0.2.6.tar.gz
+  11. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ClientForm-0.2.6.zip
+  12. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ChangeLog.txt
+  13. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/
+  14. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ClientForm-0.1.17.tar.gz
+  15. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ClientForm-0_1_17.zip
+  16. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ChangeLog.txt
+  17. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/
+  18. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ClientForm-0.0.16.tar.gz
+  19. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ClientForm-0_0_16.zip
+  20. file://localhost/home/lunar/debian/pkg-zope/python-clientform/src/ChangeLog.txt
+  21. file://localhost/home/lunar/debian/pkg-zope/python-clientform/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. http://wwwsearch.sourceforge.net/bits/GeneralFAQ.html
-  28. http://wwwsearch.sourceforge.net/ClientCookie/
-  29. http://wwwsearch.sourceforge.net/ClientCookie/doc.html#debugging
-  30. http://wwwsearch.sourceforge.net/bits/GeneralFAQ.html
+  27. file://localhost/home/lunar/debian/pkg-zope/bits/GeneralFAQ.html
+  28. file://localhost/home/lunar/debian/pkg-zope/ClientCookie/
+  29. file://localhost/home/lunar/debian/pkg-zope/ClientCookie/doc.html#debugging
+  30. file://localhost/home/lunar/debian/pkg-zope/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. http://wwwsearch.sourceforge.net/
-  38. http://wwwsearch.sourceforge.net/bits/GeneralFAQ.html
-  39. http://wwwsearch.sourceforge.net/mechanize/
-  40. http://wwwsearch.sourceforge.net/pullparser/
-  41. http://wwwsearch.sourceforge.net/ClientCookie/
-  42. http://wwwsearch.sourceforge.net/ClientCookie/doc.html
-  43. http://wwwsearch.sourceforge.net/DOMForm/
-  44. http://wwwsearch.sourceforge.net/python-spidermonkey/
-  45. http://wwwsearch.sourceforge.net/ClientTable/
-  46. http://wwwsearch.sourceforge.net/bits/urllib2_152.py
-  47. http://wwwsearch.sourceforge.net/bits/urllib_152.py
-  48. http://wwwsearch.sourceforge.net/#other
-  49. http://wwwsearch.sourceforge.net/ClientForm/#example
-  50. http://wwwsearch.sourceforge.net/ClientForm/#notes
-  51. http://wwwsearch.sourceforge.net/ClientForm/#parsers
-  52. http://wwwsearch.sourceforge.net/ClientForm/#compat
-  53. http://wwwsearch.sourceforge.net/ClientForm/#credits
-  54. http://wwwsearch.sourceforge.net/ClientForm/#download
-  55. http://wwwsearch.sourceforge.net/ClientForm/#faq
+  37. file://localhost/home/lunar/debian/pkg-zope
+  38. file://localhost/home/lunar/debian/pkg-zope/bits/GeneralFAQ.html
+  39. file://localhost/home/lunar/debian/pkg-zope/mechanize/
+  40. file://localhost/home/lunar/debian/pkg-zope/mechanize/doc.html
+  41. file://localhost/home/lunar/debian/pkg-zope/ClientCookie/
+  42. file://localhost/home/lunar/debian/pkg-zope/ClientCookie/doc.html
+  43. file://localhost/home/lunar/debian/pkg-zope/pullparser/
+  44. file://localhost/home/lunar/debian/pkg-zope/DOMForm/
+  45. file://localhost/home/lunar/debian/pkg-zope/python-spidermonkey/
+  46. file://localhost/home/lunar/debian/pkg-zope/ClientTable/
+  47. file://localhost/home/lunar/debian/pkg-zope/bits/urllib2_152.py
+  48. file://localhost/home/lunar/debian/pkg-zope/bits/urllib_152.py
+  49. file://localhost/home/lunar/debian/pkg-zope/#other
+  50. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#example
+  51. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#notes
+  52. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#parsers
+  53. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#compat
+  54. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#credits
+  55. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#download
+  56. file://localhost/home/lunar/debian/pkg-zope/python-clientform/#faq

Modified: python-clientform/trunk/debian/changelog
===================================================================
--- python-clientform/trunk/debian/changelog	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/debian/changelog	2007-04-09 22:04:59 UTC (rev 771)
@@ -1,3 +1,12 @@
+python-clientform (0.2.6-1) unstable; urgency=low
+
+  * New upstream release. (Closes: #418460)
+  * Drop patch to ClientForm.py that has been integrated upstream.
+  * Re-generate README.txt with "lynx -dump" as upstream forgot to do it.
+  * Add myself as an Uploader.
+
+ -- Jérémy Bobbio <lunar at debian.org>  Tue, 10 Apr 2007 00:04:05 +0200
+
 python-clientform (0.2.2-3) unstable; urgency=low
 
   * Back-port patch from Zope 3.3 release branch

Modified: python-clientform/trunk/debian/control
===================================================================
--- python-clientform/trunk/debian/control	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/debian/control	2007-04-09 22:04:59 UTC (rev 771)
@@ -2,7 +2,7 @@
 Section: python
 Priority: extra
 Maintainer: Debian/Ubuntu Zope Team <pkg-zope-developers at lists.alioth.debian.org>
-Uploaders: Brian Sutherland <jinty at web.de>, Fabio Tranchitella <kobold at debian.org>
+Uploaders: Brian Sutherland <jinty at web.de>, Fabio Tranchitella <kobold at debian.org>, Jérémy Bobbio <lunar at debian.org>
 Build-Depends-Indep: python (>= 2.3.5-7), python-all-dev, python-central (>= 0.5)
 Build-Depends: debhelper (>= 5.0.37.2), python-setuptools (>= 0.6a9)
 Standards-Version: 3.7.2

Copied: python-clientform/trunk/ez_setup.py (from rev 770, python-clientform/branches/upstream/current/ez_setup.py)

Copied: python-clientform/trunk/setup.cfg (from rev 770, python-clientform/branches/upstream/current/setup.cfg)

Modified: python-clientform/trunk/setup.py
===================================================================
--- python-clientform/trunk/setup.py	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/setup.py	2007-04-09 22:04:59 UTC (rev 771)
@@ -15,11 +15,11 @@
 import re
 #VERSION_MATCH = re.search(r'VERSION = "(.*)"', open("ClientForm.py").read())
 #VERSION = VERSION_MATCH.group(1)
-VERSION = '0.2.2'
+VERSION = '0.2.6'
 INSTALL_REQUIRES = []
 NAME = "ClientForm"
 PACKAGE = False
-LICENSE = "BSD"
+LICENSE = "BSD"  # or ZPL 2.1
 PLATFORMS = ["any"]
 ZIP_SAFE = True
 CLASSIFIERS = """\

Modified: python-clientform/trunk/test.py
===================================================================
--- python-clientform/trunk/test.py	2007-04-09 21:18:27 UTC (rev 770)
+++ python-clientform/trunk/test.py	2007-04-09 22:04:59 UTC (rev 771)
@@ -212,7 +212,33 @@
     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">
@@ -226,6 +252,84 @@
         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,
+            )
+
+        forms = ClientForm.ParseResponse(r)
+        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">
@@ -285,10 +389,12 @@
         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")
+        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,\nRhubarb.\n\n")
+        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>=)>")
@@ -499,7 +605,7 @@
         form = forms[0]
         self.assert_(form.controls[0].name is None)
 
-    def testNamelessListControls(self):
+    def testNamelessListItems(self):
         # XXX SELECT
         # these controls have no item names
         file = StringIO("""<form action="./weird.html">
@@ -621,7 +727,18 @@
         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/",
+            )
+        ctl = forms[0].find_control(type="textarea")
+        self.assertEqual(ctl.value, "\r\nblah\r\n")
 
+
 class DisabledTests(TestCase):
     def testOptgroup(self):
         for compat in [False, True]:
@@ -2012,6 +2129,22 @@
             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)
@@ -2974,7 +3107,62 @@
             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
@@ -3003,6 +3191,32 @@
             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




More information about the pkg-zope-commits mailing list