[wwwsearch-commits] r19192 - wwwsearch/ClientForm/trunk
jjlee at codespeak.net
jjlee at codespeak.net
Sun Oct 30 19:08:05 CET 2005
Author: jjlee
Date: Sun Oct 30 19:08:04 2005
New Revision: 19192
Modified:
wwwsearch/ClientForm/trunk/ClientForm.py
wwwsearch/ClientForm/trunk/test.py
Log:
Fix ordering of interspersed list controls (Balazs Ree)
Modified: wwwsearch/ClientForm/trunk/ClientForm.py
==============================================================================
--- wwwsearch/ClientForm/trunk/ClientForm.py (original)
+++ wwwsearch/ClientForm/trunk/ClientForm.py Sun Oct 30 19:08:04 2005
@@ -26,6 +26,15 @@
"""
+# XXXX
+# Fix case where items for controls are interspersed:
+# <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>
+# Firefox (and IE?) --> ?murphy=a&woof=d&murphy=b&murphy=c
+# ClientForm --> ?murphy=a&murphy=b&murphy=c&woof=d
+
# XXX
# Add some more functional tests
# Especially single and multiple file upload on the internet.
@@ -912,10 +921,12 @@
form = HTMLForm(
action, method, enctype, name, attrs, request_class,
forms, labels, id_to_labels, backwards_compat)
- for type, name, attrs in controls:
+ 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)
- form.new_control(type, name, attrs, select_default=select_default)
+ form.new_control(type, name, attrs, select_default=select_default,
+ index=ii)
forms.append(form)
for form in forms:
form.fixup()
@@ -1007,7 +1018,7 @@
id: value of id HTML attribute
"""
- def __init__(self, type, name, attrs):
+ 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)
@@ -1036,6 +1047,15 @@
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):
@@ -1080,7 +1100,8 @@
control to their values
"""
- def __init__(self, type, name, attrs):
+ def __init__(self, type, name, attrs, index=None):
+ self._index = index
self._label = _getLabel(attrs)
self.__dict__["type"] = type.lower()
self.__dict__["name"] = name
@@ -1114,12 +1135,12 @@
else:
self.__dict__[name] = value
- def pairs(self):
+ def _totally_ordered_pairs(self):
name = self.name
value = self.value
if name is None or value is None or self.disabled:
return []
- return [(name, value)]
+ return [(self._index, name, value)]
def clear(self):
if self.readonly:
@@ -1154,8 +1175,8 @@
TEXTAREA
"""
- def __init__(self, type, name, attrs):
- ScalarControl.__init__(self, type, name, attrs)
+ 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 = ""
@@ -1172,8 +1193,8 @@
"""
- def __init__(self, type, name, attrs):
- ScalarControl.__init__(self, type, name, attrs)
+ def __init__(self, type, name, attrs, index=None):
+ ScalarControl.__init__(self, type, name, attrs, index)
self._value = None
self._upload_data = []
@@ -1201,11 +1222,11 @@
content_type = "application/octet-stream"
self._upload_data.append((file_object, content_type, filename))
- def pairs(self):
+ def _totally_ordered_pairs(self):
# XXX should it be successful even if unnamed?
if self.name is None or self.disabled:
return []
- return [(self.name, "")]
+ return [(self._index, self.name, "")]
def _write_mime_data(self, mw):
# called by HTMLForm
@@ -1285,14 +1306,14 @@
result = urllib2.urlopen(url)
"""
- def __init__(self, type, name, attrs):
- ScalarControl.__init__(self, type, name, attrs)
+ 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 pairs(self):
+ def _totally_ordered_pairs(self):
return []
def _click(self, form, coord, return_type, request_class=urllib2.Request):
@@ -1346,8 +1367,8 @@
The value attribute of IgnoreControl is always None.
"""
- def __init__(self, type, name, attrs):
- ScalarControl.__init__(self, type, name, attrs)
+ 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
@@ -1368,7 +1389,7 @@
# helpers and subsidiary classes
class Item:
- def __init__(self, control, attrs):
+ def __init__(self, control, attrs, index=None):
label = _getLabel(attrs)
self.__dict__.update({
"name": attrs["value"],
@@ -1378,6 +1399,7 @@
"disabled": attrs.has_key("disabled"),
"_selected": False,
"id": attrs.get("id"),
+ "_index": index,
})
control.items.append(self)
@@ -1523,7 +1545,7 @@
_label = None
def __init__(self, type, name, attrs={}, select_default=False,
- called_as_base_class=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
@@ -1990,11 +2012,11 @@
return res
return [o.name for o in self.items]
- def pairs(self):
+ def _totally_ordered_pairs(self):
if self.disabled:
return []
else:
- return [(self.name, o.name) for o in self.items
+ return [(o._index, self.name, o.name) for o in self.items
if o.selected and not o.disabled]
def __str__(self):
@@ -2020,12 +2042,12 @@
INPUT/RADIO
"""
- def __init__(self, type, name, attrs, select_default=False):
+ 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)
+ called_as_base_class=True, index=index)
self.__dict__["multiple"] = False
- o = Item(self, attrs)
+ o = Item(self, attrs, index)
o.__dict__["_selected"] = attrs.has_key("checked")
def fixup(self):
@@ -2053,12 +2075,12 @@
INPUT/CHECKBOX
"""
- def __init__(self, type, name, attrs, select_default=False):
+ 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)
+ called_as_base_class=True, index=index)
self.__dict__["multiple"] = True
- o = Item(self, attrs)
+ o = Item(self, attrs, index)
o.__dict__["_selected"] = attrs.has_key("checked")
def get_labels(self):
@@ -2119,7 +2141,7 @@
# -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):
+ 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()
@@ -2132,12 +2154,12 @@
del attrs["__select"]
ListControl.__init__(self, type, name, self.attrs, select_default,
- called_as_base_class=True)
+ 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)
+ 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
@@ -2181,8 +2203,8 @@
BUTTON/SUBMIT
"""
- def __init__(self, type, name, attrs):
- ScalarControl.__init__(self, type, name, attrs)
+ 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.
@@ -2204,10 +2226,10 @@
self._clicked = False
return r
- def pairs(self):
+ def _totally_ordered_pairs(self):
if not self._clicked:
return []
- return ScalarControl.pairs(self)
+ return ScalarControl._totally_ordered_pairs(self)
#---------------------------------------------------
@@ -2220,23 +2242,23 @@
Coordinates are specified using one of the HTMLForm.click* methods.
"""
- def __init__(self, type, name, attrs):
- SubmitControl.__init__(self, type, name, attrs)
+ def __init__(self, type, name, attrs, index=None):
+ SubmitControl.__init__(self, type, name, attrs, index)
self.readonly = False
- def pairs(self):
+ def _totally_ordered_pairs(self):
clicked = self._clicked
if self.disabled or not clicked:
return []
name = self.name
if name is None: return []
pairs = [
- ("%s.x" % name, str(clicked[0])),
- ("%s.y" % name, str(clicked[1])),
+ (self._index, "%s.x" % name, str(clicked[0])),
+ (self._index, "%s.y" % name, str(clicked[1])),
]
value = self._value
if value:
- pairs.append((name, value))
+ pairs.append((self._index, name, value))
return pairs
get_labels = ScalarControl.get_labels
@@ -2545,7 +2567,7 @@
self.__dict__[name] = value
def new_control(self, type, name, attrs,
- ignore_unknown=False, select_default=False):
+ 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
@@ -2563,6 +2585,8 @@
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()
@@ -2575,9 +2599,9 @@
a = attrs.copy()
if issubclass(klass, ListControl):
- control = klass(type, name, a, select_default)
+ control = klass(type, name, a, select_default, index)
else:
- control = klass(type, name, a)
+ control = klass(type, name, a, index)
control.add_to_form(self)
def fixup(self):
@@ -3039,9 +3063,18 @@
def _pairs(self):
"""Return sequence of (key, value) pairs suitable for urlencoding."""
- pairs = []
+ opairs = []
for control in self.controls:
- pairs.extend(control.pairs())
+ opairs.extend(control._totally_ordered_pairs())
+
+ # stable sort by ONLY first item in tuple
+ sorter = []
+ for jj in range(len(opairs)):
+ ii, key, val = opairs[jj]
+ sorter.append((ii, jj, key, val))
+ sorter.sort()
+ pairs = [(key, val) for (ii, jj, key, val) in sorter]
+
return pairs
def _request_data(self):
Modified: wwwsearch/ClientForm/trunk/test.py
==============================================================================
--- wwwsearch/ClientForm/trunk/test.py (original)
+++ wwwsearch/ClientForm/trunk/test.py Sun Oct 30 19:08:04 2005
@@ -2609,6 +2609,29 @@
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"),
+ ])
+
def make_form(self):
f = StringIO("""\
<form blah="nonsense" name="formname">
More information about the wwwsearch-commits
mailing list