SVN: Zope3/trunk/src/ClientForm/ClientForm.py Update ClientForm to version 0.2.9.
Marius Gedminas <[email protected]>
| Newsgroups | gmane.comp.web.zope.zope3.cvs |
|---|---|
| Message-ID | <20080721155842.A5B35399EE__19548.3505995528$1216656006$gmane$org@mail.zope.org> |
Log message for revision 88652: Update ClientForm to version 0.2.9. Fixes https://bugs.launchpad.net/zope3/+bug/243785. Changed: U Zope3/trunk/src/ClientForm/ClientForm.py -=- Modified: Zope3/trunk/src/ClientForm/ClientForm.py =================================================================== --- Zope3/trunk/src/ClientForm/ClientForm.py 2008-07-21 15:54:05 UTC (rev 88651) +++ Zope3/trunk/src/ClientForm/ClientForm.py 2008-07-21 15:58:42 UTC (rev 88652) @@ -15,24 +15,20 @@ HTML 4.01 Specification, W3C Recommendation 24 December 1999 -Copyright 2002-2006 John J. Lee <[email protected]> +Copyright 2002-2007 John J. Lee <[email protected]> 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 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 # 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 +37,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 @@ -65,6 +56,15 @@ # 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: @@ -79,6 +79,7 @@ try: import logging + import inspect except ImportError: def debug(msg, *args, **kwds): pass @@ -90,11 +91,7 @@ if OPTIMIZATION_HACK: return - try: - raise Exception() - except: - caller_name = ( - sys.exc_info()[2].tb_frame.f_back.f_back.f_code.co_name) + caller_name = inspect.stack()[1][3] extended_msg = '%%s %s' % msg extended_args = (caller_name,)+args debug = _logger.debug(extended_msg, *extended_args, **kwds) @@ -109,27 +106,45 @@ 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): + def deprecation(message, stack_offset=0): pass else: - def deprecation(message): - warnings.warn(message, DeprecationWarning, stacklevel=2) + def deprecation(message, stack_offset=0): + warnings.warn(message, DeprecationWarning, stacklevel=3+stack_offset) -VERSION = "0.2.2" +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. @@ -429,10 +444,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,22 +482,29 @@ 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 = value + 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 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 @@ -476,14 +513,14 @@ d = {} for key, value in attrs: if key == "name": - name = value + name = self.unescape_attr_if_required(value) elif key == "action": - action = value + action = self.unescape_attr_if_required(value) elif key == "method": - method = value.upper() + method = self.unescape_attr_if_required(value.upper()) elif key == "enctype": - enctype = value.lower() - d[key] = value + 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 @@ -491,22 +528,20 @@ 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: raise ParseError("SELECT inside TEXTAREA") d = {} for key, val in attrs: - d[key] = val + d[key] = self.unescape_attr_if_required(val) self._select = d self._add_label(d) @@ -515,8 +550,6 @@ def end_select(self): debug("") - if self._current_form is None: - raise ParseError("end of SELECT before start of FORM") if self._select is None: raise ParseError("end of SELECT before start") @@ -531,7 +564,7 @@ raise ParseError("OPTGROUP outside of SELECT") d = {} for key, val in attrs: - d[key] = val + d[key] = self.unescape_attr_if_required(val) self._optgroup = d @@ -550,7 +583,7 @@ d = {} for key, val in attrs: - d[key] = val + d[key] = self.unescape_attr_if_required(val) self._option = {} self._option.update(d) @@ -583,23 +616,19 @@ 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: raise ParseError("TEXTAREA inside SELECT") d = {} for key, val in attrs: - d[key] = val + d[key] = self.unescape_attr_if_required(val) self._add_label(d) self._textarea = d def end_textarea(self): debug("") - if self._current_form is None: - raise ParseError("end of TEXTAREA before start of FORM") if self._textarea is None: raise ParseError("end of TEXTAREA before start") controls = self._current_form[2] @@ -613,7 +642,7 @@ self.end_label() d = {} for key, val in attrs: - d[key] = val + d[key] = self.unescape_attr_if_required(val) taken = bool(d.get("for")) # empty id is invalid d["__text"] = "" d["__taken"] = taken @@ -628,28 +657,28 @@ # something is ugly in the HTML, but we're ignoring it return self._current_label = None - label["__text"] = label["__text"] # 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 self._current_label["__taken"]: - self.end_label() # be fuzzy - else: + 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: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 +689,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,12 +704,10 @@ 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: - d[key] = val + d[key] = self.unescape_attr_if_required(val) controls = self._current_form[2] type = d["type"] @@ -694,12 +722,10 @@ 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: - d[key] = val + d[key] = self.unescape_attr_if_required(val) controls = self._current_form[2] type = d["type"] @@ -709,11 +735,9 @@ 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 + d[key] = self.unescape_attr_if_required(val) controls = self._current_form[2] self._add_label(d) @@ -750,11 +774,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 +786,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 +829,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 +883,115 @@ 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 + ) + __all__ += ['RobustFormParser', 'NestingRobustFormParser'] + + #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 +1051,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,8 +1067,22 @@ 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") + deprecation("operating in backwards-compatibility mode", 1) fp = form_parser_class(entitydefs, encoding) while 1: data = file.read(CHUNK) @@ -980,21 +1111,18 @@ if action is None: action = base_uri else: - 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) + 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] - attrs = fp.unescape_attrs_if_required(attrs) - name = fp.unescape_attr_if_required(name) # index=ii*10 allows ImageControl to return multiple ordered pairs - form.new_control(type, name, attrs, select_default=select_default, - index=ii*10) + form.new_control( + type, name, attrs, select_default=select_default, index=ii*10) forms.append(form) for form in forms: form.fixup() @@ -1181,6 +1309,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 +1520,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": @@ -1515,6 +1646,8 @@ 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__, @@ -1628,6 +1761,7 @@ 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 @@ -1856,16 +1990,27 @@ "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 - 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) + 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) @@ -1911,6 +2056,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 +2227,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 @@ -2163,9 +2310,6 @@ OPTION 'values', in HTML parlance, are Item 'names' in ClientForm parlance. - - 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: @@ -2625,6 +2769,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 @@ -2682,7 +2829,17 @@ 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. @@ -3060,7 +3217,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") @@ -3082,7 +3240,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 @@ -3112,7 +3271,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) @@ -3169,19 +3328,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)])