[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