~hardware-certification/zope3/certify-staging-2.5

« back to all changes in this revision

Viewing changes to src/ClientForm/.svn/text-base/ClientForm.py.svn-base

  • Committer: Marc Tardif
  • Date: 2008-04-26 19:03:34 UTC
  • Revision ID: cr3@lime-20080426190334-u16xo4llz56vliqf
Initial import.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""HTML form handling for web clients.
 
2
 
 
3
ClientForm is a Python module for handling HTML forms on the client
 
4
side, useful for parsing HTML forms, filling them in and returning the
 
5
completed forms to the server.  It has developed from a port of Gisle
 
6
Aas' Perl module HTML::Form, from the libwww-perl library, but the
 
7
interface is not the same.
 
8
 
 
9
The most useful docstring is the one for HTMLForm.
 
10
 
 
11
RFC 1866: HTML 2.0
 
12
RFC 1867: Form-based File Upload in HTML
 
13
RFC 2388: Returning Values from Forms: multipart/form-data
 
14
HTML 3.2 Specification, W3C Recommendation 14 January 1997 (for ISINDEX)
 
15
HTML 4.01 Specification, W3C Recommendation 24 December 1999
 
16
 
 
17
 
 
18
Copyright 2002-2006 John J. Lee <jjl@pobox.com>
 
19
Copyright 2005 Gary Poster
 
20
Copyright 2005 Zope Corporation
 
21
Copyright 1998-2000 Gisle Aas.
 
22
 
 
23
This code is free software; you can redistribute it and/or modify it
 
24
under the terms of the BSD License (see the file COPYING included with
 
25
the distribution).
 
26
 
 
27
"""
 
28
 
 
29
# XXX
 
30
# Remove unescape_attr method
 
31
# Remove parser testing hack
 
32
# safeUrl()-ize action
 
33
# Really should to merge CC, CF, pp and mechanize as soon as mechanize
 
34
#  goes to beta...
 
35
# Add url attribute to ParseError
 
36
# Switch to unicode throughout (would be 0.3.x)
 
37
#  See Wichert Akkerman's 2004-01-22 message to c.l.py.
 
38
# Add charset parameter to Content-type headers?  How to find value??
 
39
# Add some more functional tests
 
40
#  Especially single and multiple file upload on the internet.
 
41
#  Does file upload work when name is missing?  Sourceforge tracker form
 
42
#   doesn't like it.  Check standards, and test with Apache.  Test
 
43
#   binary upload with Apache.
 
44
# Controls can have name=None (e.g. forms constructed partly with
 
45
#  JavaScript), but find_control can't be told to find a control
 
46
#  with that name, because None there means 'unspecified'.  Can still
 
47
#  get at by nr, but would be nice to be able to specify something
 
48
#  equivalent to name=None, too.
 
49
# mailto submission & enctype text/plain
 
50
# I'm not going to fix this unless somebody tells me what real servers
 
51
#  that want this encoding actually expect: If enctype is
 
52
#  application/x-www-form-urlencoded and there's a FILE control present.
 
53
#  Strictly, it should be 'name=data' (see HTML 4.01 spec., section
 
54
#  17.13.2), but I send "name=" ATM.  What about multiple file upload??
 
55
 
 
56
# Would be nice, but I'm not going to do it myself:
 
57
# -------------------------------------------------
 
58
# Maybe a 0.4.x?
 
59
#   Replace by_label etc. with moniker / selector concept. Allows, eg.,
 
60
#    a choice between selection by value / id / label / element
 
61
#    contents.  Or choice between matching labels exactly or by
 
62
#    substring.  Etc.
 
63
#   Remove deprecated methods.
 
64
#   ...what else?
 
65
# Work on DOMForm.
 
66
# XForms?  Don't know if there's a need here.
 
67
 
 
68
 
 
69
try: True
 
70
except NameError:
 
71
    True = 1
 
72
    False = 0
 
73
 
 
74
try: bool
 
75
except NameError:
 
76
    def bool(expr):
 
77
        if expr: return True
 
78
        else: return False
 
79
 
 
80
try:
 
81
    import logging
 
82
except ImportError:
 
83
    def debug(msg, *args, **kwds):
 
84
        pass
 
85
else:
 
86
    _logger = logging.getLogger("ClientForm")
 
87
    OPTIMIZATION_HACK = True
 
88
 
 
89
    def debug(msg, *args, **kwds):
 
90
        if OPTIMIZATION_HACK:
 
91
            return
 
92
 
 
93
        try:
 
94
            raise Exception()
 
95
        except:
 
96
            caller_name = (
 
97
                sys.exc_info()[2].tb_frame.f_back.f_back.f_code.co_name)
 
98
        extended_msg = '%%s %s' % msg
 
99
        extended_args = (caller_name,)+args
 
100
        debug = _logger.debug(extended_msg, *extended_args, **kwds)
 
101
 
 
102
    def _show_debug_messages():
 
103
        global OPTIMIZATION_HACK
 
104
        OPTIMIZATION_HACK = False
 
105
        _logger.setLevel(logging.DEBUG)
 
106
        handler = logging.StreamHandler(sys.stdout)
 
107
        handler.setLevel(logging.DEBUG)
 
108
        _logger.addHandler(handler)
 
109
 
 
110
import sys, urllib, urllib2, types, mimetools, copy, urlparse, \
 
111
       htmlentitydefs, re, random
 
112
from urlparse import urljoin
 
113
from cStringIO import StringIO
 
114
 
 
115
try:
 
116
    import warnings
 
117
except ImportError:
 
118
    def deprecation(message):
 
119
        pass
 
120
else:
 
121
    def deprecation(message):
 
122
        warnings.warn(message, DeprecationWarning, stacklevel=2)
 
123
 
 
124
VERSION = "0.2.2"
 
125
 
 
126
CHUNK = 1024  # size of chunks fed to parser, in bytes
 
127
 
 
128
DEFAULT_ENCODING = "latin-1"
 
129
 
 
130
_compress_re = re.compile(r"\s+")
 
131
def compress_text(text): return _compress_re.sub(" ", text.strip())
 
132
 
 
133
# This version of urlencode is from my Python 1.5.2 back-port of the
 
134
# Python 2.1 CVS maintenance branch of urllib.  It will accept a sequence
 
135
# of pairs instead of a mapping -- the 2.0 version only accepts a mapping.
 
136
def urlencode(query,doseq=False,):
 
137
    """Encode a sequence of two-element tuples or dictionary into a URL query \
 
138
string.
 
139
 
 
140
    If any values in the query arg are sequences and doseq is true, each
 
141
    sequence element is converted to a separate parameter.
 
142
 
 
143
    If the query arg is a sequence of two-element tuples, the order of the
 
144
    parameters in the output will match the order of parameters in the
 
145
    input.
 
146
    """
 
147
 
 
148
    if hasattr(query,"items"):
 
149
        # mapping objects
 
150
        query = query.items()
 
151
    else:
 
152
        # it's a bother at times that strings and string-like objects are
 
153
        # sequences...
 
154
        try:
 
155
            # non-sequence items should not work with len()
 
156
            x = len(query)
 
157
            # non-empty strings will fail this
 
158
            if len(query) and type(query[0]) != types.TupleType:
 
159
                raise TypeError()
 
160
            # zero-length sequences of all types will get here and succeed,
 
161
            # but that's a minor nit - since the original implementation
 
162
            # allowed empty dicts that type of behavior probably should be
 
163
            # preserved for consistency
 
164
        except TypeError:
 
165
            ty,va,tb = sys.exc_info()
 
166
            raise TypeError("not a valid non-string sequence or mapping "
 
167
                            "object", tb)
 
168
 
 
169
    l = []
 
170
    if not doseq:
 
171
        # preserve old behavior
 
172
        for k, v in query:
 
173
            k = urllib.quote_plus(str(k))
 
174
            v = urllib.quote_plus(str(v))
 
175
            l.append(k + '=' + v)
 
176
    else:
 
177
        for k, v in query:
 
178
            k = urllib.quote_plus(str(k))
 
179
            if type(v) == types.StringType:
 
180
                v = urllib.quote_plus(v)
 
181
                l.append(k + '=' + v)
 
182
            elif type(v) == types.UnicodeType:
 
183
                # is there a reasonable way to convert to ASCII?
 
184
                # encode generates a string, but "replace" or "ignore"
 
185
                # lose information and "strict" can raise UnicodeError
 
186
                v = urllib.quote_plus(v.encode("ASCII","replace"))
 
187
                l.append(k + '=' + v)
 
188
            else:
 
189
                try:
 
190
                    # is this a sufficient test for sequence-ness?
 
191
                    x = len(v)
 
192
                except TypeError:
 
193
                    # not a sequence
 
194
                    v = urllib.quote_plus(str(v))
 
195
                    l.append(k + '=' + v)
 
196
                else:
 
197
                    # loop over the sequence
 
198
                    for elt in v:
 
199
                        l.append(k + '=' + urllib.quote_plus(str(elt)))
 
200
    return '&'.join(l)
 
201
 
 
202
def unescape(data, entities, encoding=DEFAULT_ENCODING):
 
203
    if data is None or "&" not in data:
 
204
        return data
 
205
 
 
206
    def replace_entities(match, entities=entities, encoding=encoding):
 
207
        ent = match.group()
 
208
        if ent[1] == "#":
 
209
            return unescape_charref(ent[2:-1], encoding)
 
210
 
 
211
        repl = entities.get(ent)
 
212
        if repl is not None:
 
213
            if type(repl) != type(""):
 
214
                try:
 
215
                    repl = repl.encode(encoding)
 
216
                except UnicodeError:
 
217
                    repl = ent
 
218
        else:
 
219
            repl = ent
 
220
 
 
221
        return repl
 
222
 
 
223
    return re.sub(r"&#?[A-Za-z0-9]+?;", replace_entities, data)
 
224
 
 
225
def unescape_charref(data, encoding):
 
226
    name, base = data, 10
 
227
    if name.startswith("x"):
 
228
        name, base= name[1:], 16
 
229
    uc = unichr(int(name, base))
 
230
    if encoding is None:
 
231
        return uc
 
232
    else:
 
233
        try:
 
234
            repl = uc.encode(encoding)
 
235
        except UnicodeError:
 
236
            repl = "&#%s;" % data
 
237
        return repl
 
238
 
 
239
def get_entitydefs():
 
240
    import htmlentitydefs
 
241
    from codecs import latin_1_decode
 
242
    entitydefs = {}
 
243
    try:
 
244
        htmlentitydefs.name2codepoint
 
245
    except AttributeError:
 
246
        entitydefs = {}
 
247
        for name, char in htmlentitydefs.entitydefs.items():
 
248
            uc = latin_1_decode(char)[0]
 
249
            if uc.startswith("&#") and uc.endswith(";"):
 
250
                uc = unescape_charref(uc[2:-1], None)
 
251
            entitydefs["&%s;" % name] = uc
 
252
    else:
 
253
        for name, codepoint in htmlentitydefs.name2codepoint.items():
 
254
            entitydefs["&%s;" % name] = unichr(codepoint)
 
255
    return entitydefs
 
256
 
 
257
 
 
258
def issequence(x):
 
259
    try:
 
260
        x[0]
 
261
    except (TypeError, KeyError):
 
262
        return False
 
263
    except IndexError:
 
264
        pass
 
265
    return True
 
266
 
 
267
def isstringlike(x):
 
268
    try: x+""
 
269
    except: return False
 
270
    else: return True
 
271
 
 
272
 
 
273
def choose_boundary():
 
274
    """Return a string usable as a multipart boundary."""
 
275
    # follow IE and firefox
 
276
    nonce = "".join([str(random.randint(0, sys.maxint-1)) for i in 0,1,2])
 
277
    return "-"*27 + nonce
 
278
 
 
279
# This cut-n-pasted MimeWriter from standard library is here so can add
 
280
# to HTTP headers rather than message body when appropriate.  It also uses
 
281
# \r\n in place of \n.  This is a bit nasty.
 
282
class MimeWriter:
 
283
 
 
284
    """Generic MIME writer.
 
285
 
 
286
    Methods:
 
287
 
 
288
    __init__()
 
289
    addheader()
 
290
    flushheaders()
 
291
    startbody()
 
292
    startmultipartbody()
 
293
    nextpart()
 
294
    lastpart()
 
295
 
 
296
    A MIME writer is much more primitive than a MIME parser.  It
 
297
    doesn't seek around on the output file, and it doesn't use large
 
298
    amounts of buffer space, so you have to write the parts in the
 
299
    order they should occur on the output file.  It does buffer the
 
300
    headers you add, allowing you to rearrange their order.
 
301
 
 
302
    General usage is:
 
303
 
 
304
    f = <open the output file>
 
305
    w = MimeWriter(f)
 
306
    ...call w.addheader(key, value) 0 or more times...
 
307
 
 
308
    followed by either:
 
309
 
 
310
    f = w.startbody(content_type)
 
311
    ...call f.write(data) for body data...
 
312
 
 
313
    or:
 
314
 
 
315
    w.startmultipartbody(subtype)
 
316
    for each part:
 
317
        subwriter = w.nextpart()
 
318
        ...use the subwriter's methods to create the subpart...
 
319
    w.lastpart()
 
320
 
 
321
    The subwriter is another MimeWriter instance, and should be
 
322
    treated in the same way as the toplevel MimeWriter.  This way,
 
323
    writing recursive body parts is easy.
 
324
 
 
325
    Warning: don't forget to call lastpart()!
 
326
 
 
327
    XXX There should be more state so calls made in the wrong order
 
328
    are detected.
 
329
 
 
330
    Some special cases:
 
331
 
 
332
    - startbody() just returns the file passed to the constructor;
 
333
      but don't use this knowledge, as it may be changed.
 
334
 
 
335
    - startmultipartbody() actually returns a file as well;
 
336
      this can be used to write the initial 'if you can read this your
 
337
      mailer is not MIME-aware' message.
 
338
 
 
339
    - If you call flushheaders(), the headers accumulated so far are
 
340
      written out (and forgotten); this is useful if you don't need a
 
341
      body part at all, e.g. for a subpart of type message/rfc822
 
342
      that's (mis)used to store some header-like information.
 
343
 
 
344
    - Passing a keyword argument 'prefix=<flag>' to addheader(),
 
345
      start*body() affects where the header is inserted; 0 means
 
346
      append at the end, 1 means insert at the start; default is
 
347
      append for addheader(), but insert for start*body(), which use
 
348
      it to determine where the Content-type header goes.
 
349
 
 
350
    """
 
351
 
 
352
    def __init__(self, fp, http_hdrs=None):
 
353
        self._http_hdrs = http_hdrs
 
354
        self._fp = fp
 
355
        self._headers = []
 
356
        self._boundary = []
 
357
        self._first_part = True
 
358
 
 
359
    def addheader(self, key, value, prefix=0,
 
360
                  add_to_http_hdrs=0):
 
361
        """
 
362
        prefix is ignored if add_to_http_hdrs is true.
 
363
        """
 
364
        lines = value.split("\r\n")
 
365
        while lines and not lines[-1]: del lines[-1]
 
366
        while lines and not lines[0]: del lines[0]
 
367
        if add_to_http_hdrs:
 
368
            value = "".join(lines)
 
369
            self._http_hdrs.append((key, value))
 
370
        else:
 
371
            for i in range(1, len(lines)):
 
372
                lines[i] = "    " + lines[i].strip()
 
373
            value = "\r\n".join(lines) + "\r\n"
 
374
            line = key + ": " + value
 
375
            if prefix:
 
376
                self._headers.insert(0, line)
 
377
            else:
 
378
                self._headers.append(line)
 
379
 
 
380
    def flushheaders(self):
 
381
        self._fp.writelines(self._headers)
 
382
        self._headers = []
 
383
 
 
384
    def startbody(self, ctype=None, plist=[], prefix=1,
 
385
                  add_to_http_hdrs=0, content_type=1):
 
386
        """
 
387
        prefix is ignored if add_to_http_hdrs is true.
 
388
        """
 
389
        if content_type and ctype:
 
390
            for name, value in plist:
 
391
                ctype = ctype + ';\r\n %s=%s' % (name, value)
 
392
            self.addheader("Content-type", ctype, prefix=prefix,
 
393
                           add_to_http_hdrs=add_to_http_hdrs)
 
394
        self.flushheaders()
 
395
        if not add_to_http_hdrs: self._fp.write("\r\n")
 
396
        self._first_part = True
 
397
        return self._fp
 
398
 
 
399
    def startmultipartbody(self, subtype, boundary=None, plist=[], prefix=1,
 
400
                           add_to_http_hdrs=0, content_type=1):
 
401
        boundary = boundary or choose_boundary()
 
402
        self._boundary.append(boundary)
 
403
        return self.startbody("multipart/" + subtype,
 
404
                              [("boundary", boundary)] + plist,
 
405
                              prefix=prefix,
 
406
                              add_to_http_hdrs=add_to_http_hdrs,
 
407
                              content_type=content_type)
 
408
 
 
409
    def nextpart(self):
 
410
        boundary = self._boundary[-1]
 
411
        if self._first_part:
 
412
            self._first_part = False
 
413
        else:
 
414
            self._fp.write("\r\n")
 
415
        self._fp.write("--" + boundary + "\r\n")
 
416
        return self.__class__(self._fp)
 
417
 
 
418
    def lastpart(self):
 
419
        if self._first_part:
 
420
            self.nextpart()
 
421
        boundary = self._boundary.pop()
 
422
        self._fp.write("\r\n--" + boundary + "--\r\n")
 
423
 
 
424
 
 
425
class LocateError(ValueError): pass
 
426
class AmbiguityError(LocateError): pass
 
427
class ControlNotFoundError(LocateError): pass
 
428
class ItemNotFoundError(LocateError): pass
 
429
 
 
430
class ItemCountError(ValueError): pass
 
431
 
 
432
 
 
433
class ParseError(Exception): pass
 
434
 
 
435
 
 
436
class _AbstractFormParser:
 
437
    """forms attribute contains HTMLForm instances on completion."""
 
438
    # thanks to Moshe Zadka for an example of sgmllib/htmllib usage
 
439
    def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
 
440
        if entitydefs is None:
 
441
            entitydefs = get_entitydefs()
 
442
        self._entitydefs = entitydefs
 
443
        self._encoding = encoding
 
444
 
 
445
        self.base = None
 
446
        self.forms = []
 
447
        self.labels = []
 
448
        self._current_label = None
 
449
        self._current_form = None
 
450
        self._select = None
 
451
        self._optgroup = None
 
452
        self._option = None
 
453
        self._textarea = None
 
454
 
 
455
    def do_base(self, attrs):
 
456
        debug("%s", attrs)
 
457
        for key, value in attrs:
 
458
            if key == "href":
 
459
                self.base = value
 
460
 
 
461
    def end_body(self):
 
462
        debug("")
 
463
        if self._current_label is not None:
 
464
            self.end_label()
 
465
        if self._current_form is not None:
 
466
            self.end_form()
 
467
 
 
468
    def start_form(self, attrs):
 
469
        debug("%s", attrs)
 
470
        if self._current_form is not None:
 
471
            raise ParseError("nested FORMs")
 
472
        name = None
 
473
        action = None
 
474
        enctype = "application/x-www-form-urlencoded"
 
475
        method = "GET"
 
476
        d = {}
 
477
        for key, value in attrs:
 
478
            if key == "name":
 
479
                name = value
 
480
            elif key == "action":
 
481
                action = value
 
482
            elif key == "method":
 
483
                method = value.upper()
 
484
            elif key == "enctype":
 
485
                enctype = value.lower()
 
486
            d[key] = value
 
487
        controls = []
 
488
        self._current_form = (name, action, method, enctype), d, controls
 
489
 
 
490
    def end_form(self):
 
491
        debug("")
 
492
        if self._current_label is not None:
 
493
            self.end_label()
 
494
        if self._current_form is None:
 
495
            raise ParseError("end of FORM before start")
 
496
        self.forms.append(self._current_form)
 
497
        self._current_form = None
 
498
 
 
499
    def start_select(self, attrs):
 
500
        debug("%s", attrs)
 
501
        if self._current_form is None:
 
502
            raise ParseError("start of SELECT before start of FORM")
 
503
        if self._select is not None:
 
504
            raise ParseError("nested SELECTs")
 
505
        if self._textarea is not None:
 
506
            raise ParseError("SELECT inside TEXTAREA")
 
507
        d = {}
 
508
        for key, val in attrs:
 
509
            d[key] = val
 
510
 
 
511
        self._select = d
 
512
        self._add_label(d)
 
513
 
 
514
        self._append_select_control({"__select": d})
 
515
 
 
516
    def end_select(self):
 
517
        debug("")
 
518
        if self._current_form is None:
 
519
            raise ParseError("end of SELECT before start of FORM")
 
520
        if self._select is None:
 
521
            raise ParseError("end of SELECT before start")
 
522
 
 
523
        if self._option is not None:
 
524
            self._end_option()
 
525
 
 
526
        self._select = None
 
527
 
 
528
    def start_optgroup(self, attrs):
 
529
        debug("%s", attrs)
 
530
        if self._select is None:
 
531
            raise ParseError("OPTGROUP outside of SELECT")
 
532
        d = {}
 
533
        for key, val in attrs:
 
534
            d[key] = val
 
535
 
 
536
        self._optgroup = d
 
537
 
 
538
    def end_optgroup(self):
 
539
        debug("")
 
540
        if self._optgroup is None:
 
541
            raise ParseError("end of OPTGROUP before start")
 
542
        self._optgroup = None
 
543
 
 
544
    def _start_option(self, attrs):
 
545
        debug("%s", attrs)
 
546
        if self._select is None:
 
547
            raise ParseError("OPTION outside of SELECT")
 
548
        if self._option is not None:
 
549
            self._end_option()
 
550
 
 
551
        d = {}
 
552
        for key, val in attrs:
 
553
            d[key] = val
 
554
 
 
555
        self._option = {}
 
556
        self._option.update(d)
 
557
        if (self._optgroup and self._optgroup.has_key("disabled") and
 
558
            not self._option.has_key("disabled")):
 
559
            self._option["disabled"] = None
 
560
 
 
561
    def _end_option(self):
 
562
        debug("")
 
563
        if self._option is None:
 
564
            raise ParseError("end of OPTION before start")
 
565
 
 
566
        contents = self._option.get("contents", "").strip()
 
567
        self._option["contents"] = contents
 
568
        if not self._option.has_key("value"):
 
569
            self._option["value"] = contents
 
570
        if not self._option.has_key("label"):
 
571
            self._option["label"] = contents
 
572
        # stuff dict of SELECT HTML attrs into a special private key
 
573
        #  (gets deleted again later)
 
574
        self._option["__select"] = self._select
 
575
        self._append_select_control(self._option)
 
576
        self._option = None
 
577
 
 
578
    def _append_select_control(self, attrs):
 
579
        debug("%s", attrs)
 
580
        controls = self._current_form[2]
 
581
        name = self._select.get("name")
 
582
        controls.append(("select", name, attrs))
 
583
 
 
584
    def start_textarea(self, attrs):
 
585
        debug("%s", attrs)
 
586
        if self._current_form is None:
 
587
            raise ParseError("start of TEXTAREA before start of FORM")
 
588
        if self._textarea is not None:
 
589
            raise ParseError("nested TEXTAREAs")
 
590
        if self._select is not None:
 
591
            raise ParseError("TEXTAREA inside SELECT")
 
592
        d = {}
 
593
        for key, val in attrs:
 
594
            d[key] = val
 
595
        self._add_label(d)
 
596
 
 
597
        self._textarea = d
 
598
 
 
599
    def end_textarea(self):
 
600
        debug("")
 
601
        if self._current_form is None:
 
602
            raise ParseError("end of TEXTAREA before start of FORM")
 
603
        if self._textarea is None:
 
604
            raise ParseError("end of TEXTAREA before start")
 
605
        controls = self._current_form[2]
 
606
        name = self._textarea.get("name")
 
607
        controls.append(("textarea", name, self._textarea))
 
608
        self._textarea = None
 
609
 
 
610
    def start_label(self, attrs):
 
611
        debug("%s", attrs)
 
612
        if self._current_label:
 
613
            self.end_label()
 
614
        d = {}
 
615
        for key, val in attrs:
 
616
            d[key] = val
 
617
        taken = bool(d.get("for"))  # empty id is invalid
 
618
        d["__text"] = ""
 
619
        d["__taken"] = taken
 
620
        if taken:
 
621
            self.labels.append(d)
 
622
        self._current_label = d
 
623
 
 
624
    def end_label(self):
 
625
        debug("")
 
626
        label = self._current_label
 
627
        if label is None:
 
628
            # something is ugly in the HTML, but we're ignoring it
 
629
            return
 
630
        self._current_label = None
 
631
        label["__text"] = label["__text"]
 
632
        # if it is staying around, it is True in all cases
 
633
        del label["__taken"]
 
634
 
 
635
    def _add_label(self, d):
 
636
        #debug("%s", d)
 
637
        if self._current_label is not None:
 
638
            if self._current_label["__taken"]:
 
639
                self.end_label()  # be fuzzy
 
640
            else:
 
641
                self._current_label["__taken"] = True
 
642
                d["__label"] = self._current_label
 
643
 
 
644
    def handle_data(self, data):
 
645
        # according to http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1
 
646
        # line break immediately after start tags or immediately before end
 
647
        # tags must be ignored, but real browsers only ignore a line break
 
648
        # after a start tag, so we'll do that.
 
649
        if data[0:1] == '\n':
 
650
            data = data[1:]
 
651
 
 
652
        debug("%s", data)
 
653
        if self._option is not None:
 
654
            # self._option is a dictionary of the OPTION element's HTML
 
655
            # attributes, but it has two special keys, one of which is the
 
656
            # special "contents" key contains text between OPTION tags (the
 
657
            # other is the "__select" key: see the end_option method)
 
658
            map = self._option
 
659
            key = "contents"
 
660
        elif self._textarea is not None:
 
661
            map = self._textarea
 
662
            key = "value"
 
663
        # not if within option or textarea
 
664
        elif self._current_label is not None:
 
665
            map = self._current_label
 
666
            key = "__text"
 
667
        else:
 
668
            return
 
669
 
 
670
        if not map.has_key(key):
 
671
            map[key] = data
 
672
        else:
 
673
            map[key] = map[key] + data
 
674
 
 
675
    def do_button(self, attrs):
 
676
        debug("%s", attrs)
 
677
        if self._current_form is None:
 
678
            raise ParseError("start of BUTTON before start of FORM")
 
679
        d = {}
 
680
        d["type"] = "submit"  # default
 
681
        for key, val in attrs:
 
682
            d[key] = val
 
683
        controls = self._current_form[2]
 
684
 
 
685
        type = d["type"]
 
686
        name = d.get("name")
 
687
        # we don't want to lose information, so use a type string that
 
688
        # doesn't clash with INPUT TYPE={SUBMIT,RESET,BUTTON}
 
689
        # e.g. type for BUTTON/RESET is "resetbutton"
 
690
        #     (type for INPUT/RESET is "reset")
 
691
        type = type+"button"
 
692
        self._add_label(d)
 
693
        controls.append((type, name, d))
 
694
 
 
695
    def do_input(self, attrs):
 
696
        debug("%s", attrs)
 
697
        if self._current_form is None:
 
698
            raise ParseError("start of INPUT before start of FORM")
 
699
        d = {}
 
700
        d["type"] = "text"  # default
 
701
        for key, val in attrs:
 
702
            d[key] = val
 
703
        controls = self._current_form[2]
 
704
 
 
705
        type = d["type"]
 
706
        name = d.get("name")
 
707
        self._add_label(d)
 
708
        controls.append((type, name, d))
 
709
 
 
710
    def do_isindex(self, attrs):
 
711
        debug("%s", attrs)
 
712
        if self._current_form is None:
 
713
            raise ParseError("start of ISINDEX before start of FORM")
 
714
        d = {}
 
715
        for key, val in attrs:
 
716
            d[key] = val
 
717
        controls = self._current_form[2]
 
718
 
 
719
        self._add_label(d)
 
720
        # isindex doesn't have type or name HTML attributes
 
721
        controls.append(("isindex", None, d))
 
722
 
 
723
    def handle_entityref(self, name):
 
724
        #debug("%s", name)
 
725
        self.handle_data(unescape(
 
726
            '&%s;' % name, self._entitydefs, self._encoding))
 
727
 
 
728
    def handle_charref(self, name):
 
729
        #debug("%s", name)
 
730
        self.handle_data(unescape_charref(name, self._encoding))
 
731
 
 
732
    def unescape_attr(self, name):
 
733
        #debug("%s", name)
 
734
        return unescape(name, self._entitydefs, self._encoding)
 
735
 
 
736
    def unescape_attrs(self, attrs):
 
737
        #debug("%s", attrs)
 
738
        escaped_attrs = {}
 
739
        for key, val in attrs.items():
 
740
            try:
 
741
                val.items
 
742
            except AttributeError:
 
743
                escaped_attrs[key] = self.unescape_attr(val)
 
744
            else:
 
745
                # e.g. "__select" -- yuck!
 
746
                escaped_attrs[key] = self.unescape_attrs(val)
 
747
        return escaped_attrs
 
748
 
 
749
    def unknown_entityref(self, ref): self.handle_data("&%s;" % ref)
 
750
    def unknown_charref(self, ref): self.handle_data("&#%s;" % ref)
 
751
 
 
752
 
 
753
# HTMLParser.HTMLParser is recent, so live without it if it's not available
 
754
# (also, htmllib.HTMLParser is much more tolerant of bad HTML)
 
755
try:
 
756
    import HTMLParser
 
757
except ImportError:
 
758
    class XHTMLCompatibleFormParser:
 
759
        def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
 
760
            raise ValueError("HTMLParser could not be imported")
 
761
else:
 
762
    class XHTMLCompatibleFormParser(_AbstractFormParser, HTMLParser.HTMLParser):
 
763
        """Good for XHTML, bad for tolerance of incorrect HTML."""
 
764
        # thanks to Michael Howitz for this!
 
765
        def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
 
766
            HTMLParser.HTMLParser.__init__(self)
 
767
            _AbstractFormParser.__init__(self, entitydefs, encoding)
 
768
 
 
769
        def start_option(self, attrs):
 
770
            _AbstractFormParser._start_option(self, attrs)
 
771
 
 
772
        def end_option(self):
 
773
            _AbstractFormParser._end_option(self)
 
774
 
 
775
        def handle_starttag(self, tag, attrs):
 
776
            try:
 
777
                method = getattr(self, "start_" + tag)
 
778
            except AttributeError:
 
779
                try:
 
780
                    method = getattr(self, "do_" + tag)
 
781
                except AttributeError:
 
782
                    pass  # unknown tag
 
783
                else:
 
784
                    method(attrs)
 
785
            else:
 
786
                method(attrs)
 
787
 
 
788
        def handle_endtag(self, tag):
 
789
            try:
 
790
                method = getattr(self, "end_" + tag)
 
791
            except AttributeError:
 
792
                pass  # unknown tag
 
793
            else:
 
794
                method()
 
795
 
 
796
        def unescape(self, name):
 
797
            # Use the entitydefs passed into constructor, not
 
798
            # HTMLParser.HTMLParser's entitydefs.
 
799
            return self.unescape_attr(name)
 
800
 
 
801
        def unescape_attr_if_required(self, name):
 
802
            return name  # HTMLParser.HTMLParser already did it
 
803
        def unescape_attrs_if_required(self, attrs):
 
804
            return attrs  # ditto
 
805
 
 
806
import sgmllib
 
807
# monkeypatch to fix http://www.python.org/sf/803422 :-(
 
808
sgmllib.charref = re.compile("&#(x?[0-9a-fA-F]+)[^0-9a-fA-F]")
 
809
class _AbstractSgmllibParser(_AbstractFormParser):
 
810
    def do_option(self, attrs):
 
811
        _AbstractFormParser._start_option(self, attrs)
 
812
 
 
813
    def unescape_attr_if_required(self, name):
 
814
        return self.unescape_attr(name)
 
815
    def unescape_attrs_if_required(self, attrs):
 
816
        return self.unescape_attrs(attrs)
 
817
 
 
818
class FormParser(_AbstractSgmllibParser, sgmllib.SGMLParser):
 
819
    """Good for tolerance of incorrect HTML, bad for XHTML."""
 
820
    def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
 
821
        sgmllib.SGMLParser.__init__(self)
 
822
        _AbstractFormParser.__init__(self, entitydefs, encoding)
 
823
 
 
824
try:
 
825
    if sys.version_info[:2] < (2, 2):
 
826
        raise ImportError  # BeautifulSoup uses generators
 
827
    import BeautifulSoup
 
828
except ImportError:
 
829
    pass
 
830
else:
 
831
    class _AbstractBSFormParser(_AbstractSgmllibParser):
 
832
        bs_base_class = None
 
833
        def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
 
834
            _AbstractFormParser.__init__(self, entitydefs, encoding)
 
835
            self.bs_base_class.__init__(self)
 
836
        def handle_data(self, data):
 
837
            _AbstractFormParser.handle_data(self, data)
 
838
            self.bs_base_class.handle_data(self, data)
 
839
 
 
840
    class RobustFormParser(_AbstractBSFormParser, BeautifulSoup.BeautifulSoup):
 
841
        """Tries to be highly tolerant of incorrect HTML."""
 
842
        bs_base_class = BeautifulSoup.BeautifulSoup
 
843
    class NestingRobustFormParser(_AbstractBSFormParser,
 
844
                                  BeautifulSoup.ICantBelieveItsBeautifulSoup):
 
845
        """Tries to be highly tolerant of incorrect HTML.
 
846
 
 
847
        Different from RobustFormParser in that it more often guesses nesting
 
848
        above missing end tags (see BeautifulSoup docs).
 
849
 
 
850
        """
 
851
        bs_base_class = BeautifulSoup.ICantBelieveItsBeautifulSoup
 
852
 
 
853
#FormParser = XHTMLCompatibleFormParser  # testing hack
 
854
#FormParser = RobustFormParser  # testing hack
 
855
 
 
856
def ParseResponse(response, select_default=False,
 
857
                  ignore_errors=False,  # ignored!
 
858
                  form_parser_class=FormParser,
 
859
                  request_class=urllib2.Request,
 
860
                  entitydefs=None,
 
861
                  backwards_compat=True,
 
862
                  encoding=DEFAULT_ENCODING,
 
863
                  ):
 
864
    """Parse HTTP response and return a list of HTMLForm instances.
 
865
 
 
866
    The return value of urllib2.urlopen can be conveniently passed to this
 
867
    function as the response parameter.
 
868
 
 
869
    ClientForm.ParseError is raised on parse errors.
 
870
 
 
871
    response: file-like object (supporting read() method) with a method
 
872
     geturl(), returning the URI of the HTTP response
 
873
    select_default: for multiple-selection SELECT controls and RADIO controls,
 
874
     pick the first item as the default if none are selected in the HTML
 
875
    form_parser_class: class to instantiate and use to pass
 
876
    request_class: class to return from .click() method (default is
 
877
     urllib2.Request)
 
878
    entitydefs: mapping like {"&amp;": "&", ...} containing HTML entity
 
879
     definitions (a sensible default is used)
 
880
    encoding: character encoding used for encoding numeric character references
 
881
     when matching link text.  ClientForm does not attempt to find the encoding
 
882
     in a META HTTP-EQUIV attribute in the document itself (mechanize, for
 
883
     example, does do that and will pass the correct value to ClientForm using
 
884
     this parameter).
 
885
 
 
886
    backwards_compat: boolean that determines whether the returned HTMLForm
 
887
     objects are backwards-compatible with old code.  If backwards_compat is
 
888
     true:
 
889
 
 
890
     - ClientForm 0.1 code will continue to work as before.
 
891
 
 
892
     - Label searches that do not specify a nr (number or count) will always
 
893
       get the first match, even if other controls match.  If
 
894
       backwards_compat is False, label searches that have ambiguous results
 
895
       will raise an AmbiguityError.
 
896
 
 
897
     - Item label matching is done by strict string comparison rather than
 
898
       substring matching.
 
899
 
 
900
     - De-selecting individual list items is allowed even if the Item is
 
901
       disabled.
 
902
 
 
903
    The backwards_compat argument will be deprecated in a future release.
 
904
 
 
905
    Pass a true value for select_default if you want the behaviour specified by
 
906
    RFC 1866 (the HTML 2.0 standard), which is to select the first item in a
 
907
    RADIO or multiple-selection SELECT control if none were selected in the
 
908
    HTML.  Most browsers (including Microsoft Internet Explorer (IE) and
 
909
    Netscape Navigator) instead leave all items unselected in these cases.  The
 
910
    W3C HTML 4.0 standard leaves this behaviour undefined in the case of
 
911
    multiple-selection SELECT controls, but insists that at least one RADIO
 
912
    button should be checked at all times, in contradiction to browser
 
913
    behaviour.
 
914
 
 
915
    There is a choice of parsers.  ClientForm.XHTMLCompatibleFormParser (uses
 
916
    HTMLParser.HTMLParser) works best for XHTML, ClientForm.FormParser (uses
 
917
    sgmllib.SGMLParser) (the default) works better for ordinary grubby HTML.
 
918
    Note that HTMLParser is only available in Python 2.2 and later.  You can
 
919
    pass your own class in here as a hack to work around bad HTML, but at your
 
920
    own risk: there is no well-defined interface.
 
921
 
 
922
    """
 
923
    return ParseFile(response, response.geturl(), select_default,
 
924
                     False,
 
925
                     form_parser_class,
 
926
                     request_class,
 
927
                     entitydefs,
 
928
                     backwards_compat,
 
929
                     encoding,
 
930
                     )
 
931
 
 
932
def ParseFile(file, base_uri, select_default=False,
 
933
              ignore_errors=False,  # ignored!
 
934
              form_parser_class=FormParser,
 
935
              request_class=urllib2.Request,
 
936
              entitydefs=None,
 
937
              backwards_compat=True,
 
938
              encoding=DEFAULT_ENCODING,
 
939
              ):
 
940
    """Parse HTML and return a list of HTMLForm instances.
 
941
 
 
942
    ClientForm.ParseError is raised on parse errors.
 
943
 
 
944
    file: file-like object (supporting read() method) containing HTML with zero
 
945
     or more forms to be parsed
 
946
    base_uri: the URI of the document (note that the base URI used to submit
 
947
     the form will be that given in the BASE element if present, not that of
 
948
     the document)
 
949
 
 
950
    For the other arguments and further details, see ParseResponse.__doc__.
 
951
 
 
952
    """
 
953
    if backwards_compat:
 
954
        deprecation("operating in backwards-compatibility mode")
 
955
    fp = form_parser_class(entitydefs, encoding)
 
956
    while 1:
 
957
        data = file.read(CHUNK)
 
958
        try:
 
959
            fp.feed(data)
 
960
        except ParseError, e:
 
961
            e.base_uri = base_uri
 
962
            raise
 
963
        if len(data) != CHUNK: break
 
964
    if fp.base is not None:
 
965
        # HTML BASE element takes precedence over document URI
 
966
        base_uri = fp.base
 
967
    labels = []  # Label(label) for label in fp.labels]
 
968
    id_to_labels = {}
 
969
    for l in fp.labels:
 
970
        label = Label(l)
 
971
        labels.append(label)
 
972
        for_id = l["for"]
 
973
        coll = id_to_labels.get(for_id)
 
974
        if coll is None:
 
975
            id_to_labels[for_id] = [label]
 
976
        else:
 
977
            coll.append(label)
 
978
    forms = []
 
979
    for (name, action, method, enctype), attrs, controls in fp.forms:
 
980
        if action is None:
 
981
            action = base_uri
 
982
        else:
 
983
            action = urljoin(base_uri, action)
 
984
        action = fp.unescape_attr_if_required(action)
 
985
        name = fp.unescape_attr_if_required(name)
 
986
        attrs = fp.unescape_attrs_if_required(attrs)
 
987
        # would be nice to make HTMLForm class (form builder) pluggable
 
988
        form = HTMLForm(
 
989
            action, method, enctype, name, attrs, request_class,
 
990
            forms, labels, id_to_labels, backwards_compat)
 
991
        for ii in range(len(controls)):
 
992
            type, name, attrs = controls[ii]
 
993
            attrs = fp.unescape_attrs_if_required(attrs)
 
994
            name = fp.unescape_attr_if_required(name)
 
995
            # index=ii*10 allows ImageControl to return multiple ordered pairs
 
996
            form.new_control(type, name, attrs, select_default=select_default,
 
997
                             index=ii*10)
 
998
        forms.append(form)
 
999
    for form in forms:
 
1000
        form.fixup()
 
1001
    return forms
 
1002
 
 
1003
 
 
1004
class Label:
 
1005
    def __init__(self, attrs):
 
1006
        self.id = attrs.get("for")
 
1007
        self._text = attrs.get("__text").strip()
 
1008
        self._ctext = compress_text(self._text)
 
1009
        self.attrs = attrs
 
1010
        self._backwards_compat = False  # maintained by HTMLForm
 
1011
 
 
1012
    def __getattr__(self, name):
 
1013
        if name == "text":
 
1014
            if self._backwards_compat:
 
1015
                return self._text
 
1016
            else:
 
1017
                return self._ctext
 
1018
        return getattr(Label, name)
 
1019
 
 
1020
    def __setattr__(self, name, value):
 
1021
        if name == "text":
 
1022
            # don't see any need for this, so make it read-only
 
1023
            raise AttributeError("text attribute is read-only")
 
1024
        self.__dict__[name] = value
 
1025
 
 
1026
    def __str__(self):
 
1027
        return "<Label(id=%r, text=%r)>" % (self.id, self.text)
 
1028
 
 
1029
 
 
1030
def _get_label(attrs):
 
1031
    text = attrs.get("__label")
 
1032
    if text is not None:
 
1033
        return Label(text)
 
1034
    else:
 
1035
        return None
 
1036
 
 
1037
class Control:
 
1038
    """An HTML form control.
 
1039
 
 
1040
    An HTMLForm contains a sequence of Controls.  The Controls in an HTMLForm
 
1041
    are accessed using the HTMLForm.find_control method or the
 
1042
    HTMLForm.controls attribute.
 
1043
 
 
1044
    Control instances are usually constructed using the ParseFile /
 
1045
    ParseResponse functions.  If you use those functions, you can ignore the
 
1046
    rest of this paragraph.  A Control is only properly initialised after the
 
1047
    fixup method has been called.  In fact, this is only strictly necessary for
 
1048
    ListControl instances.  This is necessary because ListControls are built up
 
1049
    from ListControls each containing only a single item, and their initial
 
1050
    value(s) can only be known after the sequence is complete.
 
1051
 
 
1052
    The types and values that are acceptable for assignment to the value
 
1053
    attribute are defined by subclasses.
 
1054
 
 
1055
    If the disabled attribute is true, this represents the state typically
 
1056
    represented by browsers by 'greying out' a control.  If the disabled
 
1057
    attribute is true, the Control will raise AttributeError if an attempt is
 
1058
    made to change its value.  In addition, the control will not be considered
 
1059
    'successful' as defined by the W3C HTML 4 standard -- ie. it will
 
1060
    contribute no data to the return value of the HTMLForm.click* methods.  To
 
1061
    enable a control, set the disabled attribute to a false value.
 
1062
 
 
1063
    If the readonly attribute is true, the Control will raise AttributeError if
 
1064
    an attempt is made to change its value.  To make a control writable, set
 
1065
    the readonly attribute to a false value.
 
1066
 
 
1067
    All controls have the disabled and readonly attributes, not only those that
 
1068
    may have the HTML attributes of the same names.
 
1069
 
 
1070
    On assignment to the value attribute, the following exceptions are raised:
 
1071
    TypeError, AttributeError (if the value attribute should not be assigned
 
1072
    to, because the control is disabled, for example) and ValueError.
 
1073
 
 
1074
    If the name or value attributes are None, or the value is an empty list, or
 
1075
    if the control is disabled, the control is not successful.
 
1076
 
 
1077
    Public attributes:
 
1078
 
 
1079
    type: string describing type of control (see the keys of the
 
1080
     HTMLForm.type2class dictionary for the allowable values) (readonly)
 
1081
    name: name of control (readonly)
 
1082
    value: current value of control (subclasses may allow a single value, a
 
1083
     sequence of values, or either)
 
1084
    disabled: disabled state
 
1085
    readonly: readonly state
 
1086
    id: value of id HTML attribute
 
1087
 
 
1088
    """
 
1089
    def __init__(self, type, name, attrs, index=None):
 
1090
        """
 
1091
        type: string describing type of control (see the keys of the
 
1092
         HTMLForm.type2class dictionary for the allowable values)
 
1093
        name: control name
 
1094
        attrs: HTML attributes of control's HTML element
 
1095
 
 
1096
        """
 
1097
        raise NotImplementedError()
 
1098
 
 
1099
    def add_to_form(self, form):
 
1100
        self._form = form
 
1101
        form.controls.append(self)
 
1102
 
 
1103
    def fixup(self):
 
1104
        pass
 
1105
 
 
1106
    def is_of_kind(self, kind):
 
1107
        raise NotImplementedError()
 
1108
 
 
1109
    def clear(self):
 
1110
        raise NotImplementedError()
 
1111
 
 
1112
    def __getattr__(self, name): raise NotImplementedError()
 
1113
    def __setattr__(self, name, value): raise NotImplementedError()
 
1114
 
 
1115
    def pairs(self):
 
1116
        """Return list of (key, value) pairs suitable for passing to urlencode.
 
1117
        """
 
1118
        return [(k, v) for (i, k, v) in self._totally_ordered_pairs()]
 
1119
 
 
1120
    def _totally_ordered_pairs(self):
 
1121
        """Return list of (key, value, index) tuples.
 
1122
 
 
1123
        Like pairs, but allows preserving correct ordering even where several
 
1124
        controls are involved.
 
1125
 
 
1126
        """
 
1127
        raise NotImplementedError()
 
1128
 
 
1129
    def _write_mime_data(self, mw, name, value):
 
1130
        """Write data for a subitem of this control to a MimeWriter."""
 
1131
        # called by HTMLForm
 
1132
        mw2 = mw.nextpart()
 
1133
        mw2.addheader("Content-disposition",
 
1134
                      'form-data; name="%s"' % name, 1)
 
1135
        f = mw2.startbody(prefix=0)
 
1136
        f.write(value)
 
1137
 
 
1138
    def __str__(self):
 
1139
        raise NotImplementedError()
 
1140
 
 
1141
    def get_labels(self):
 
1142
        """Return all labels (Label instances) for this control.
 
1143
        
 
1144
        If the control was surrounded by a <label> tag, that will be the first
 
1145
        label; all other labels, connected by 'for' and 'id', are in the order
 
1146
        that appear in the HTML.
 
1147
 
 
1148
        """
 
1149
        res = []
 
1150
        if self._label:
 
1151
            res.append(self._label)
 
1152
        if self.id:
 
1153
            res.extend(self._form._id_to_labels.get(self.id, ()))
 
1154
        return res
 
1155
 
 
1156
 
 
1157
#---------------------------------------------------
 
1158
class ScalarControl(Control):
 
1159
    """Control whose value is not restricted to one of a prescribed set.
 
1160
 
 
1161
    Some ScalarControls don't accept any value attribute.  Otherwise, takes a
 
1162
    single value, which must be string-like.
 
1163
 
 
1164
    Additional read-only public attribute:
 
1165
 
 
1166
    attrs: dictionary mapping the names of original HTML attributes of the
 
1167
     control to their values
 
1168
 
 
1169
    """
 
1170
    def __init__(self, type, name, attrs, index=None):
 
1171
        self._index = index
 
1172
        self._label = _get_label(attrs)
 
1173
        self.__dict__["type"] = type.lower()
 
1174
        self.__dict__["name"] = name
 
1175
        self._value = attrs.get("value")
 
1176
        self.disabled = attrs.has_key("disabled")
 
1177
        self.readonly = attrs.has_key("readonly")
 
1178
        self.id = attrs.get("id")
 
1179
 
 
1180
        self.attrs = attrs.copy()
 
1181
 
 
1182
        self._clicked = False
 
1183
 
 
1184
    def __getattr__(self, name):
 
1185
        if name == "value":
 
1186
            return self.__dict__["_value"]
 
1187
        else:
 
1188
            raise AttributeError("%s instance has no attribute '%s'" %
 
1189
                                 (self.__class__.__name__, name))
 
1190
 
 
1191
    def __setattr__(self, name, value):
 
1192
        if name == "value":
 
1193
            if not isstringlike(value):
 
1194
                raise TypeError("must assign a string")
 
1195
            elif self.readonly:
 
1196
                raise AttributeError("control '%s' is readonly" % self.name)
 
1197
            elif self.disabled:
 
1198
                raise AttributeError("control '%s' is disabled" % self.name)
 
1199
            self.__dict__["_value"] = value
 
1200
        elif name in ("name", "type"):
 
1201
            raise AttributeError("%s attribute is readonly" % name)
 
1202
        else:
 
1203
            self.__dict__[name] = value
 
1204
 
 
1205
    def _totally_ordered_pairs(self):
 
1206
        name = self.name
 
1207
        value = self.value
 
1208
        if name is None or value is None or self.disabled:
 
1209
            return []
 
1210
        return [(self._index, name, value)]
 
1211
 
 
1212
    def clear(self):
 
1213
        if self.readonly:
 
1214
            raise AttributeError("control '%s' is readonly" % self.name)
 
1215
        self.__dict__["_value"] = None
 
1216
 
 
1217
    def __str__(self):
 
1218
        name = self.name
 
1219
        value = self.value
 
1220
        if name is None: name = "<None>"
 
1221
        if value is None: value = "<None>"
 
1222
 
 
1223
        infos = []
 
1224
        if self.disabled: infos.append("disabled")
 
1225
        if self.readonly: infos.append("readonly")
 
1226
        info = ", ".join(infos)
 
1227
        if info: info = " (%s)" % info
 
1228
 
 
1229
        return "<%s(%s=%s)%s>" % (self.__class__.__name__, name, value, info)
 
1230
 
 
1231
 
 
1232
#---------------------------------------------------
 
1233
class TextControl(ScalarControl):
 
1234
    """Textual input control.
 
1235
 
 
1236
    Covers:
 
1237
 
 
1238
    INPUT/TEXT
 
1239
    INPUT/PASSWORD
 
1240
    INPUT/HIDDEN
 
1241
    TEXTAREA
 
1242
 
 
1243
    """
 
1244
    def __init__(self, type, name, attrs, index=None):
 
1245
        ScalarControl.__init__(self, type, name, attrs, index)
 
1246
        if self.type == "hidden": self.readonly = True
 
1247
        if self._value is None:
 
1248
            self._value = ""
 
1249
 
 
1250
    def is_of_kind(self, kind): return kind == "text"
 
1251
 
 
1252
#---------------------------------------------------
 
1253
class FileControl(ScalarControl):
 
1254
    """File upload with INPUT TYPE=FILE.
 
1255
 
 
1256
    The value attribute of a FileControl is always None.  Use add_file instead.
 
1257
 
 
1258
    Additional public method: add_file
 
1259
 
 
1260
    """
 
1261
 
 
1262
    def __init__(self, type, name, attrs, index=None):
 
1263
        ScalarControl.__init__(self, type, name, attrs, index)
 
1264
        self._value = None
 
1265
        self._upload_data = []
 
1266
 
 
1267
    def is_of_kind(self, kind): return kind == "file"
 
1268
 
 
1269
    def clear(self):
 
1270
        if self.readonly:
 
1271
            raise AttributeError("control '%s' is readonly" % self.name)
 
1272
        self._upload_data = []
 
1273
 
 
1274
    def __setattr__(self, name, value):
 
1275
        if name in ("value", "name", "type"):
 
1276
            raise AttributeError("%s attribute is readonly" % name)
 
1277
        else:
 
1278
            self.__dict__[name] = value
 
1279
 
 
1280
    def add_file(self, file_object, content_type=None, filename=None):
 
1281
        if not hasattr(file_object, "read"):
 
1282
            raise TypeError("file-like object must have read method")
 
1283
        if content_type is not None and not isstringlike(content_type):
 
1284
            raise TypeError("content type must be None or string-like")
 
1285
        if filename is not None and not isstringlike(filename):
 
1286
            raise TypeError("filename must be None or string-like")
 
1287
        if content_type is None:
 
1288
            content_type = "application/octet-stream"
 
1289
        self._upload_data.append((file_object, content_type, filename))
 
1290
 
 
1291
    def _totally_ordered_pairs(self):
 
1292
        # XXX should it be successful even if unnamed?
 
1293
        if self.name is None or self.disabled:
 
1294
            return []
 
1295
        return [(self._index, self.name, "")]
 
1296
 
 
1297
    def _write_mime_data(self, mw, _name, _value):
 
1298
        # called by HTMLForm
 
1299
        # assert _name == self.name and _value == ''
 
1300
        if len(self._upload_data) == 1:
 
1301
            # single file
 
1302
            file_object, content_type, filename = self._upload_data[0]
 
1303
            mw2 = mw.nextpart()
 
1304
            fn_part = filename and ('; filename="%s"' % filename) or ""
 
1305
            disp = 'form-data; name="%s"%s' % (self.name, fn_part)
 
1306
            mw2.addheader("Content-disposition", disp, prefix=1)
 
1307
            fh = mw2.startbody(content_type, prefix=0)
 
1308
            fh.write(file_object.read())
 
1309
        elif len(self._upload_data) != 0:
 
1310
            # multiple files
 
1311
            mw2 = mw.nextpart()
 
1312
            disp = 'form-data; name="%s"' % self.name
 
1313
            mw2.addheader("Content-disposition", disp, prefix=1)
 
1314
            fh = mw2.startmultipartbody("mixed", prefix=0)
 
1315
            for file_object, content_type, filename in self._upload_data:
 
1316
                mw3 = mw2.nextpart()
 
1317
                fn_part = filename and ('; filename="%s"' % filename) or ""
 
1318
                disp = "file%s" % fn_part
 
1319
                mw3.addheader("Content-disposition", disp, prefix=1)
 
1320
                fh2 = mw3.startbody(content_type, prefix=0)
 
1321
                fh2.write(file_object.read())
 
1322
            mw2.lastpart()
 
1323
 
 
1324
    def __str__(self):
 
1325
        name = self.name
 
1326
        if name is None: name = "<None>"
 
1327
 
 
1328
        if not self._upload_data:
 
1329
            value = "<No files added>"
 
1330
        else:
 
1331
            value = []
 
1332
            for file, ctype, filename in self._upload_data:
 
1333
                if filename is None:
 
1334
                    value.append("<Unnamed file>")
 
1335
                else:
 
1336
                    value.append(filename)
 
1337
            value = ", ".join(value)
 
1338
 
 
1339
        info = []
 
1340
        if self.disabled: info.append("disabled")
 
1341
        if self.readonly: info.append("readonly")
 
1342
        info = ", ".join(info)
 
1343
        if info: info = " (%s)" % info
 
1344
 
 
1345
        return "<%s(%s=%s)%s>" % (self.__class__.__name__, name, value, info)
 
1346
 
 
1347
 
 
1348
#---------------------------------------------------
 
1349
class IsindexControl(ScalarControl):
 
1350
    """ISINDEX control.
 
1351
 
 
1352
    ISINDEX is the odd-one-out of HTML form controls.  In fact, it isn't really
 
1353
    part of regular HTML forms at all, and predates it.  You're only allowed
 
1354
    one ISINDEX per HTML document.  ISINDEX and regular form submission are
 
1355
    mutually exclusive -- either submit a form, or the ISINDEX.
 
1356
 
 
1357
    Having said this, since ISINDEX controls may appear in forms (which is
 
1358
    probably bad HTML), ParseFile / ParseResponse will include them in the
 
1359
    HTMLForm instances it returns.  You can set the ISINDEX's value, as with
 
1360
    any other control (but note that ISINDEX controls have no name, so you'll
 
1361
    need to use the type argument of set_value!).  When you submit the form,
 
1362
    the ISINDEX will not be successful (ie., no data will get returned to the
 
1363
    server as a result of its presence), unless you click on the ISINDEX
 
1364
    control, in which case the ISINDEX gets submitted instead of the form:
 
1365
 
 
1366
    form.set_value("my isindex value", type="isindex")
 
1367
    urllib2.urlopen(form.click(type="isindex"))
 
1368
 
 
1369
    ISINDEX elements outside of FORMs are ignored.  If you want to submit one
 
1370
    by hand, do it like so:
 
1371
 
 
1372
    url = urlparse.urljoin(page_uri, "?"+urllib.quote_plus("my isindex value"))
 
1373
    result = urllib2.urlopen(url)
 
1374
 
 
1375
    """
 
1376
    def __init__(self, type, name, attrs, index=None):
 
1377
        ScalarControl.__init__(self, type, name, attrs, index)
 
1378
        if self._value is None:
 
1379
            self._value = ""
 
1380
 
 
1381
    def is_of_kind(self, kind): return kind in ["text", "clickable"]
 
1382
 
 
1383
    def _totally_ordered_pairs(self):
 
1384
        return []
 
1385
 
 
1386
    def _click(self, form, coord, return_type, request_class=urllib2.Request):
 
1387
        # Relative URL for ISINDEX submission: instead of "foo=bar+baz",
 
1388
        # want "bar+baz".
 
1389
        # This doesn't seem to be specified in HTML 4.01 spec. (ISINDEX is
 
1390
        # deprecated in 4.01, but it should still say how to submit it).
 
1391
        # Submission of ISINDEX is explained in the HTML 3.2 spec, though.
 
1392
        parts = urlparse.urlparse(form.action)
 
1393
        rest, (query, frag) = parts[:-2], parts[-2:]
 
1394
        parts = rest + (urllib.quote_plus(self.value), "")
 
1395
        url = urlparse.urlunparse(parts)
 
1396
        req_data = url, None, []
 
1397
 
 
1398
        if return_type == "pairs":
 
1399
            return []
 
1400
        elif return_type == "request_data":
 
1401
            return req_data
 
1402
        else:
 
1403
            return request_class(url)
 
1404
 
 
1405
    def __str__(self):
 
1406
        value = self.value
 
1407
        if value is None: value = "<None>"
 
1408
 
 
1409
        infos = []
 
1410
        if self.disabled: infos.append("disabled")
 
1411
        if self.readonly: infos.append("readonly")
 
1412
        info = ", ".join(infos)
 
1413
        if info: info = " (%s)" % info
 
1414
 
 
1415
        return "<%s(%s)%s>" % (self.__class__.__name__, value, info)
 
1416
 
 
1417
 
 
1418
#---------------------------------------------------
 
1419
class IgnoreControl(ScalarControl):
 
1420
    """Control that we're not interested in.
 
1421
 
 
1422
    Covers:
 
1423
 
 
1424
    INPUT/RESET
 
1425
    BUTTON/RESET
 
1426
    INPUT/BUTTON
 
1427
    BUTTON/BUTTON
 
1428
 
 
1429
    These controls are always unsuccessful, in the terminology of HTML 4 (ie.
 
1430
    they never require any information to be returned to the server).
 
1431
 
 
1432
    BUTTON/BUTTON is used to generate events for script embedded in HTML.
 
1433
 
 
1434
    The value attribute of IgnoreControl is always None.
 
1435
 
 
1436
    """
 
1437
    def __init__(self, type, name, attrs, index=None):
 
1438
        ScalarControl.__init__(self, type, name, attrs, index)
 
1439
        self._value = None
 
1440
 
 
1441
    def is_of_kind(self, kind): return False
 
1442
 
 
1443
    def __setattr__(self, name, value):
 
1444
        if name == "value":
 
1445
            raise AttributeError(
 
1446
                "control '%s' is ignored, hence read-only" % self.name)
 
1447
        elif name in ("name", "type"):
 
1448
            raise AttributeError("%s attribute is readonly" % name)
 
1449
        else:
 
1450
            self.__dict__[name] = value
 
1451
 
 
1452
 
 
1453
#---------------------------------------------------
 
1454
# ListControls
 
1455
 
 
1456
# helpers and subsidiary classes
 
1457
 
 
1458
class Item:
 
1459
    def __init__(self, control, attrs, index=None):
 
1460
        label = _get_label(attrs)
 
1461
        self.__dict__.update({
 
1462
            "name": attrs["value"],
 
1463
            "_labels": label and [label] or [],
 
1464
            "attrs": attrs,
 
1465
            "_control": control,
 
1466
            "disabled": attrs.has_key("disabled"),
 
1467
            "_selected": False,
 
1468
            "id": attrs.get("id"),
 
1469
            "_index": index,
 
1470
            })
 
1471
        control.items.append(self)
 
1472
 
 
1473
    def get_labels(self):
 
1474
        """Return all labels (Label instances) for this item.
 
1475
        
 
1476
        For items that represent radio buttons or checkboxes, if the item was
 
1477
        surrounded by a <label> tag, that will be the first label; all other
 
1478
        labels, connected by 'for' and 'id', are in the order that appear in
 
1479
        the HTML.
 
1480
        
 
1481
        For items that represent select options, if the option had a label
 
1482
        attribute, that will be the first label.  If the option has contents
 
1483
        (text within the option tags) and it is not the same as the label
 
1484
        attribute (if any), that will be a label.  There is nothing in the
 
1485
        spec to my knowledge that makes an option with an id unable to be the
 
1486
        target of a label's for attribute, so those are included, if any, for
 
1487
        the sake of consistency and completeness.
 
1488
 
 
1489
        """
 
1490
        res = []
 
1491
        res.extend(self._labels)
 
1492
        if self.id:
 
1493
            res.extend(self._control._form._id_to_labels.get(self.id, ()))
 
1494
        return res
 
1495
 
 
1496
    def __getattr__(self, name):
 
1497
        if name=="selected":
 
1498
            return self._selected
 
1499
        raise AttributeError(name)
 
1500
 
 
1501
    def __setattr__(self, name, value):
 
1502
        if name == "selected":
 
1503
            self._control._set_selected_state(self, value)
 
1504
        elif name == "disabled":
 
1505
            self.__dict__["disabled"] = bool(value)
 
1506
        else:
 
1507
            raise AttributeError(name)
 
1508
 
 
1509
    def __str__(self):
 
1510
        res = self.name
 
1511
        if self.selected:
 
1512
            res = "*" + res
 
1513
        if self.disabled:
 
1514
            res = "(%s)" % res
 
1515
        return res
 
1516
 
 
1517
    def __repr__(self):
 
1518
        attrs = [("name", self.name), ("id", self.id)]+self.attrs.items()
 
1519
        return "<%s %s>" % (
 
1520
            self.__class__.__name__,
 
1521
            " ".join(["%s=%r" % (k, v) for k, v in attrs])
 
1522
            )
 
1523
 
 
1524
def disambiguate(items, nr, **kwds):
 
1525
    msgs = []
 
1526
    for key, value in kwds.items():
 
1527
        msgs.append("%s=%r" % (key, value))
 
1528
    msg = " ".join(msgs)
 
1529
    if not items:
 
1530
        raise ItemNotFoundError(msg)
 
1531
    if nr is None:
 
1532
        if len(items) > 1:
 
1533
            raise AmbiguityError(msg)
 
1534
        nr = 0
 
1535
    if len(items) <= nr:
 
1536
        raise ItemNotFoundError(msg)
 
1537
    return items[nr]
 
1538
 
 
1539
class ListControl(Control):
 
1540
    """Control representing a sequence of items.
 
1541
 
 
1542
    The value attribute of a ListControl represents the successful list items
 
1543
    in the control.  The successful list items are those that are selected and
 
1544
    not disabled.
 
1545
 
 
1546
    ListControl implements both list controls that take a length-1 value
 
1547
    (single-selection) and those that take length >1 values
 
1548
    (multiple-selection).
 
1549
 
 
1550
    ListControls accept sequence values only.  Some controls only accept
 
1551
    sequences of length 0 or 1 (RADIO, and single-selection SELECT).
 
1552
    In those cases, ItemCountError is raised if len(sequence) > 1.  CHECKBOXes
 
1553
    and multiple-selection SELECTs (those having the "multiple" HTML attribute)
 
1554
    accept sequences of any length.
 
1555
 
 
1556
    Note the following mistake:
 
1557
 
 
1558
    control.value = some_value
 
1559
    assert control.value == some_value    # not necessarily true
 
1560
 
 
1561
    The reason for this is that the value attribute always gives the list items
 
1562
    in the order they were listed in the HTML.
 
1563
 
 
1564
    ListControl items can also be referred to by their labels instead of names.
 
1565
    Use the label argument to .get(), and the .set_value_by_label(),
 
1566
    .get_value_by_label() methods.
 
1567
 
 
1568
    Note that, rather confusingly, though SELECT controls are represented in
 
1569
    HTML by SELECT elements (which contain OPTION elements, representing
 
1570
    individual list items), CHECKBOXes and RADIOs are not represented by *any*
 
1571
    element.  Instead, those controls are represented by a collection of INPUT
 
1572
    elements.  For example, this is a SELECT control, named "control1":
 
1573
 
 
1574
    <select name="control1">
 
1575
     <option>foo</option>
 
1576
     <option value="1">bar</option>
 
1577
    </select>
 
1578
 
 
1579
    and this is a CHECKBOX control, named "control2":
 
1580
 
 
1581
    <input type="checkbox" name="control2" value="foo" id="cbe1">
 
1582
    <input type="checkbox" name="control2" value="bar" id="cbe2">
 
1583
 
 
1584
    The id attribute of a CHECKBOX or RADIO ListControl is always that of its
 
1585
    first element (for example, "cbe1" above).
 
1586
 
 
1587
 
 
1588
    Additional read-only public attribute: multiple.
 
1589
 
 
1590
    """
 
1591
 
 
1592
    # ListControls are built up by the parser from their component items by
 
1593
    # creating one ListControl per item, consolidating them into a single
 
1594
    # master ListControl held by the HTMLForm:
 
1595
 
 
1596
    # -User calls form.new_control(...)
 
1597
    # -Form creates Control, and calls control.add_to_form(self).
 
1598
    # -Control looks for a Control with the same name and type in the form,
 
1599
    #  and if it finds one, merges itself with that control by calling
 
1600
    #  control.merge_control(self).  The first Control added to the form, of
 
1601
    #  a particular name and type, is the only one that survives in the
 
1602
    #  form.
 
1603
    # -Form calls control.fixup for all its controls.  ListControls in the
 
1604
    #  form know they can now safely pick their default values.
 
1605
 
 
1606
    # To create a ListControl without an HTMLForm, use:
 
1607
 
 
1608
    # control.merge_control(new_control)
 
1609
 
 
1610
    # (actually, it's much easier just to use ParseFile)
 
1611
 
 
1612
    _label = None
 
1613
 
 
1614
    def __init__(self, type, name, attrs={}, select_default=False,
 
1615
                 called_as_base_class=False, index=None):
 
1616
        """
 
1617
        select_default: for RADIO and multiple-selection SELECT controls, pick
 
1618
         the first item as the default if no 'selected' HTML attribute is
 
1619
         present
 
1620
 
 
1621
        """
 
1622
        if not called_as_base_class:
 
1623
            raise NotImplementedError()
 
1624
 
 
1625
        self.__dict__["type"] = type.lower()
 
1626
        self.__dict__["name"] = name
 
1627
        self._value = attrs.get("value")
 
1628
        self.disabled = False
 
1629
        self.readonly = False
 
1630
        self.id = attrs.get("id")
 
1631
 
 
1632
        # As Controls are merged in with .merge_control(), self.attrs will
 
1633
        # refer to each Control in turn -- always the most recently merged
 
1634
        # control.  Each merged-in Control instance corresponds to a single
 
1635
        # list item: see ListControl.__doc__.
 
1636
        self.items = []
 
1637
        self._form = None
 
1638
 
 
1639
        self._select_default = select_default
 
1640
        self._clicked = False
 
1641
 
 
1642
    def clear(self):
 
1643
        self.value = []
 
1644
 
 
1645
    def is_of_kind(self, kind):
 
1646
        if kind  == "list":
 
1647
            return True
 
1648
        elif kind == "multilist":
 
1649
            return bool(self.multiple)
 
1650
        elif kind == "singlelist":
 
1651
            return not self.multiple
 
1652
        else:
 
1653
            return False
 
1654
 
 
1655
    def get_items(self, name=None, label=None, id=None,
 
1656
                  exclude_disabled=False):
 
1657
        """Return matching items by name or label.
 
1658
 
 
1659
        For argument docs, see the docstring for .get()
 
1660
 
 
1661
        """
 
1662
        if name is not None and not isstringlike(name):
 
1663
            raise TypeError("item name must be string-like")
 
1664
        if label is not None and not isstringlike(label):
 
1665
            raise TypeError("item label must be string-like")
 
1666
        if id is not None and not isstringlike(id):
 
1667
            raise TypeError("item id must be string-like")
 
1668
        items = []  # order is important
 
1669
        compat = self._form.backwards_compat
 
1670
        for o in self.items:
 
1671
            if exclude_disabled and o.disabled:
 
1672
                continue
 
1673
            if name is not None and o.name != name:
 
1674
                continue
 
1675
            if label is not None:
 
1676
                for l in o.get_labels():
 
1677
                    if ((compat and l.text == label) or
 
1678
                        (not compat and l.text.find(label) > -1)):
 
1679
                        break
 
1680
                else:
 
1681
                    continue
 
1682
            if id is not None and o.id != id:
 
1683
                continue
 
1684
            items.append(o)
 
1685
        return items
 
1686
 
 
1687
    def get(self, name=None, label=None, id=None, nr=None,
 
1688
            exclude_disabled=False):
 
1689
        """Return item by name or label, disambiguating if necessary with nr.
 
1690
 
 
1691
        All arguments must be passed by name, with the exception of 'name',
 
1692
        which may be used as a positional argument.
 
1693
 
 
1694
        If name is specified, then the item must have the indicated name.
 
1695
 
 
1696
        If label is specified, then the item must have a label whose
 
1697
        whitespace-compressed, stripped, text substring-matches the indicated
 
1698
        label string (eg. label="please choose" will match
 
1699
        "  Do  please  choose an item ").
 
1700
 
 
1701
        If id is specified, then the item must have the indicated id.
 
1702
 
 
1703
        nr is an optional 0-based index of the items matching the query.
 
1704
 
 
1705
        If nr is the default None value and more than item is found, raises
 
1706
        AmbiguityError (unless the HTMLForm instance's backwards_compat
 
1707
        attribute is true).
 
1708
 
 
1709
        If no item is found, or if items are found but nr is specified and not
 
1710
        found, raises ItemNotFoundError.
 
1711
 
 
1712
        Optionally excludes disabled items.
 
1713
 
 
1714
        """
 
1715
        if nr is None and self._form.backwards_compat:
 
1716
            nr = 0  # :-/
 
1717
        items = self.get_items(name, label, id, exclude_disabled)
 
1718
        return disambiguate(items, nr, name=name, label=label, id=id)
 
1719
 
 
1720
    def _get(self, name, by_label=False, nr=None, exclude_disabled=False):
 
1721
        # strictly for use by deprecated methods
 
1722
        if by_label:
 
1723
            name, label = None, name
 
1724
        else:
 
1725
            name, label = name, None
 
1726
        return self.get(name, label, nr, exclude_disabled)
 
1727
 
 
1728
    def toggle(self, name, by_label=False, nr=None):
 
1729
        """Deprecated: given a name or label and optional disambiguating index
 
1730
        nr, toggle the matching item's selection.
 
1731
 
 
1732
        Selecting items follows the behavior described in the docstring of the
 
1733
        'get' method.
 
1734
 
 
1735
        if the item is disabled, or this control is disabled or readonly,
 
1736
        raise AttributeError.
 
1737
 
 
1738
        """
 
1739
        deprecation(
 
1740
            "item = control.get(...); item.selected = not item.selected")
 
1741
        o = self._get(name, by_label, nr)
 
1742
        self._set_selected_state(o, not o.selected)
 
1743
 
 
1744
    def set(self, selected, name, by_label=False, nr=None):
 
1745
        """Deprecated: given a name or label and optional disambiguating index
 
1746
        nr, set the matching item's selection to the bool value of selected.
 
1747
 
 
1748
        Selecting items follows the behavior described in the docstring of the
 
1749
        'get' method.
 
1750
 
 
1751
        if the item is disabled, or this control is disabled or readonly,
 
1752
        raise AttributeError.
 
1753
 
 
1754
        """
 
1755
        deprecation(
 
1756
            "control.get(...).selected = <boolean>")
 
1757
        self._set_selected_state(self._get(name, by_label, nr), selected)
 
1758
 
 
1759
    def _set_selected_state(self, item, action):
 
1760
        # action:
 
1761
        # bool False: off
 
1762
        # bool True: on
 
1763
        if self.disabled:
 
1764
            raise AttributeError("control '%s' is disabled" % self.name)
 
1765
        if self.readonly:
 
1766
            raise AttributeError("control '%s' is readonly" % self.name)
 
1767
        action == bool(action)
 
1768
        compat = self._form.backwards_compat
 
1769
        if not compat and item.disabled:
 
1770
            raise AttributeError("item is disabled")
 
1771
        else:
 
1772
            if compat and item.disabled and action:
 
1773
                raise AttributeError("item is disabled")
 
1774
            if self.multiple:
 
1775
                item.__dict__["_selected"] = action
 
1776
            else:
 
1777
                if not action:
 
1778
                    item.__dict__["_selected"] = False
 
1779
                else:
 
1780
                    for o in self.items:
 
1781
                        o.__dict__["_selected"] = False
 
1782
                    item.__dict__["_selected"] = True
 
1783
 
 
1784
    def toggle_single(self, by_label=None):
 
1785
        """Deprecated: toggle the selection of the single item in this control.
 
1786
        
 
1787
        Raises ItemCountError if the control does not contain only one item.
 
1788
        
 
1789
        by_label argument is ignored, and included only for backwards
 
1790
        compatibility.
 
1791
 
 
1792
        """
 
1793
        deprecation(
 
1794
            "control.items[0].selected = not control.items[0].selected")
 
1795
        if len(self.items) != 1:
 
1796
            raise ItemCountError(
 
1797
                "'%s' is not a single-item control" % self.name)
 
1798
        item = self.items[0]
 
1799
        self._set_selected_state(item, not item.selected)
 
1800
 
 
1801
    def set_single(self, selected, by_label=None):
 
1802
        """Deprecated: set the selection of the single item in this control.
 
1803
        
 
1804
        Raises ItemCountError if the control does not contain only one item.
 
1805
        
 
1806
        by_label argument is ignored, and included only for backwards
 
1807
        compatibility.
 
1808
 
 
1809
        """
 
1810
        deprecation(
 
1811
            "control.items[0].selected = <boolean>")
 
1812
        if len(self.items) != 1:
 
1813
            raise ItemCountError(
 
1814
                "'%s' is not a single-item control" % self.name)
 
1815
        self._set_selected_state(self.items[0], selected)
 
1816
 
 
1817
    def get_item_disabled(self, name, by_label=False, nr=None):
 
1818
        """Get disabled state of named list item in a ListControl."""
 
1819
        deprecation(
 
1820
            "control.get(...).disabled")
 
1821
        return self._get(name, by_label, nr).disabled
 
1822
 
 
1823
    def set_item_disabled(self, disabled, name, by_label=False, nr=None):
 
1824
        """Set disabled state of named list item in a ListControl.
 
1825
 
 
1826
        disabled: boolean disabled state
 
1827
 
 
1828
        """
 
1829
        deprecation(
 
1830
            "control.get(...).disabled = <boolean>")
 
1831
        self._get(name, by_label, nr).disabled = disabled
 
1832
 
 
1833
    def set_all_items_disabled(self, disabled):
 
1834
        """Set disabled state of all list items in a ListControl.
 
1835
 
 
1836
        disabled: boolean disabled state
 
1837
 
 
1838
        """
 
1839
        for o in self.items:
 
1840
            o.disabled = disabled
 
1841
 
 
1842
    def get_item_attrs(self, name, by_label=False, nr=None):
 
1843
        """Return dictionary of HTML attributes for a single ListControl item.
 
1844
 
 
1845
        The HTML element types that describe list items are: OPTION for SELECT
 
1846
        controls, INPUT for the rest.  These elements have HTML attributes that
 
1847
        you may occasionally want to know about -- for example, the "alt" HTML
 
1848
        attribute gives a text string describing the item (graphical browsers
 
1849
        usually display this as a tooltip).
 
1850
 
 
1851
        The returned dictionary maps HTML attribute names to values.  The names
 
1852
        and values are taken from the original HTML.
 
1853
 
 
1854
        """
 
1855
        deprecation(
 
1856
            "control.get(...).attrs")
 
1857
        return self._get(name, by_label, nr).attrs
 
1858
 
 
1859
    def add_to_form(self, form):
 
1860
        assert self._form is None or form == self._form, (
 
1861
            "can't add control to more than one form")
 
1862
        self._form = form
 
1863
        try:
 
1864
            control = form.find_control(self.name, self.type)
 
1865
        except ControlNotFoundError:
 
1866
            Control.add_to_form(self, form)
 
1867
        else:
 
1868
            control.merge_control(self)
 
1869
 
 
1870
    def merge_control(self, control):
 
1871
        assert bool(control.multiple) == bool(self.multiple)
 
1872
        # usually, isinstance(control, self.__class__)
 
1873
        self.items.extend(control.items)
 
1874
 
 
1875
    def fixup(self):
 
1876
        """
 
1877
        ListControls are built up from component list items (which are also
 
1878
        ListControls) during parsing.  This method should be called after all
 
1879
        items have been added.  See ListControl.__doc__ for the reason this is
 
1880
        required.
 
1881
 
 
1882
        """
 
1883
        # Need to set default selection where no item was indicated as being
 
1884
        # selected by the HTML:
 
1885
 
 
1886
        # CHECKBOX:
 
1887
        #  Nothing should be selected.
 
1888
        # SELECT/single, SELECT/multiple and RADIO:
 
1889
        #  RFC 1866 (HTML 2.0): says first item should be selected.
 
1890
        #  W3C HTML 4.01 Specification: says that client behaviour is
 
1891
        #   undefined in this case.  For RADIO, exactly one must be selected,
 
1892
        #   though which one is undefined.
 
1893
        #  Both Netscape and Microsoft Internet Explorer (IE) choose first
 
1894
        #   item for SELECT/single.  However, both IE5 and Mozilla (both 1.0
 
1895
        #   and Firebird 0.6) leave all items unselected for RADIO and
 
1896
        #   SELECT/multiple.
 
1897
 
 
1898
        # Since both Netscape and IE all choose the first item for
 
1899
        # SELECT/single, we do the same.  OTOH, both Netscape and IE
 
1900
        # leave SELECT/multiple with nothing selected, in violation of RFC 1866
 
1901
        # (but not in violation of the W3C HTML 4 standard); the same is true
 
1902
        # of RADIO (which *is* in violation of the HTML 4 standard).  We follow
 
1903
        # RFC 1866 if the _select_default attribute is set, and Netscape and IE
 
1904
        # otherwise.  RFC 1866 and HTML 4 are always violated insofar as you
 
1905
        # can deselect all items in a RadioControl.
 
1906
        
 
1907
        for o in self.items: 
 
1908
            # set items' controls to self, now that we've merged
 
1909
            o.__dict__["_control"] = self
 
1910
 
 
1911
    def __getattr__(self, name):
 
1912
        if name == "value":
 
1913
            compat = self._form.backwards_compat
 
1914
            return [o.name for o in self.items if o.selected and
 
1915
                    (not o.disabled or compat)]
 
1916
        else:
 
1917
            raise AttributeError("%s instance has no attribute '%s'" %
 
1918
                                 (self.__class__.__name__, name))
 
1919
 
 
1920
    def __setattr__(self, name, value):
 
1921
        if name == "value":
 
1922
            if self.disabled:
 
1923
                raise AttributeError("control '%s' is disabled" % self.name)
 
1924
            if self.readonly:
 
1925
                raise AttributeError("control '%s' is readonly" % self.name)
 
1926
            self._set_value(value)
 
1927
        elif name in ("name", "type", "multiple"):
 
1928
            raise AttributeError("%s attribute is readonly" % name)
 
1929
        else:
 
1930
            self.__dict__[name] = value
 
1931
 
 
1932
    def _set_value(self, value):
 
1933
        if value is None or isstringlike(value):
 
1934
            raise TypeError("ListControl, must set a sequence")
 
1935
        if not value:
 
1936
            compat = self._form.backwards_compat
 
1937
            for o in self.items:
 
1938
                if not o.disabled or compat:
 
1939
                    o.selected = False
 
1940
        elif self.multiple:
 
1941
            self._multiple_set_value(value)
 
1942
        elif len(value) > 1:
 
1943
            raise ItemCountError(
 
1944
                "single selection list, must set sequence of "
 
1945
                "length 0 or 1")
 
1946
        else:
 
1947
            self._single_set_value(value)
 
1948
 
 
1949
    def _get_items(self, name, target=1):
 
1950
        all_items = self.get_items(name)
 
1951
        items = [o for o in all_items if not o.disabled]
 
1952
        if len(items) < target:
 
1953
            if len(all_items) < target:
 
1954
                raise ItemNotFoundError(
 
1955
                    "insufficient items with name %r" % name)
 
1956
            else:
 
1957
                raise AttributeError(
 
1958
                    "insufficient non-disabled items with name %s" % name)
 
1959
        on = []
 
1960
        off = []
 
1961
        for o in items:
 
1962
            if o.selected:
 
1963
                on.append(o)
 
1964
            else:
 
1965
                off.append(o)
 
1966
        return on, off
 
1967
 
 
1968
    def _single_set_value(self, value):
 
1969
        assert len(value) == 1
 
1970
        on, off = self._get_items(value[0])
 
1971
        assert len(on) <= 1
 
1972
        if not on:
 
1973
            off[0].selected = True
 
1974
 
 
1975
    def _multiple_set_value(self, value):
 
1976
        compat = self._form.backwards_compat
 
1977
        turn_on = []  # transactional-ish
 
1978
        turn_off = [item for item in self.items if
 
1979
                    item.selected and (not item.disabled or compat)]
 
1980
        names = {}
 
1981
        for nn in value:
 
1982
            if nn in names.keys():
 
1983
                names[nn] += 1
 
1984
            else:
 
1985
                names[nn] = 1
 
1986
        for name, count in names.items():
 
1987
            on, off = self._get_items(name, count)
 
1988
            for i in range(count):
 
1989
                if on:
 
1990
                    item = on[0]
 
1991
                    del on[0]
 
1992
                    del turn_off[turn_off.index(item)]
 
1993
                else:
 
1994
                    item = off[0]
 
1995
                    del off[0]
 
1996
                    turn_on.append(item)
 
1997
        for item in turn_off:
 
1998
            item.selected = False
 
1999
        for item in turn_on:
 
2000
            item.selected = True
 
2001
 
 
2002
    def set_value_by_label(self, value):
 
2003
        """Set the value of control by item labels.
 
2004
 
 
2005
        value is expected to be an iterable of strings that are substrings of
 
2006
        the item labels that should be selected.  Before substring matching is
 
2007
        performed, the original label text is whitespace-compressed
 
2008
        (consecutive whitespace characters are converted to a single space
 
2009
        character) and leading and trailing whitespace is stripped.  Ambiguous
 
2010
        labels are accepted without complaint if the form's backwards_compat is
 
2011
        True; otherwise, it will not complain as long as all ambiguous labels
 
2012
        share the same item name (e.g. OPTION value).
 
2013
 
 
2014
        """
 
2015
        if isstringlike(value):
 
2016
            raise TypeError(value)
 
2017
        if not self.multiple and len(value) > 1:
 
2018
            raise ItemCountError(
 
2019
                "single selection list, must set sequence of "
 
2020
                "length 0 or 1")
 
2021
        items = []
 
2022
        for nn in value:
 
2023
            found = self.get_items(label=nn)
 
2024
            if len(found) > 1:
 
2025
                if not self._form.backwards_compat:
 
2026
                    # ambiguous labels are fine as long as item names (e.g.
 
2027
                    # OPTION values) are same
 
2028
                    opt_name = found[0].name
 
2029
                    if [o for o in found[1:] if o.name != opt_name]:
 
2030
                        raise AmbiguityError(nn)
 
2031
                else:
 
2032
                    # OK, we'll guess :-(  Assume first available item.
 
2033
                    found = found[:1]
 
2034
            for o in found:
 
2035
                # For the multiple-item case, we could try to be smarter,
 
2036
                # saving them up and trying to resolve, but that's too much.
 
2037
                if self._form.backwards_compat or o not in items:
 
2038
                    items.append(o)
 
2039
                    break
 
2040
            else:  # all of them are used
 
2041
                raise ItemNotFoundError(nn)
 
2042
        # now we have all the items that should be on
 
2043
        # let's just turn everything off and then back on.
 
2044
        self.value = []
 
2045
        for o in items:
 
2046
            o.selected = True
 
2047
 
 
2048
    def get_value_by_label(self):
 
2049
        """Return the value of the control as given by normalized labels."""
 
2050
        res = []
 
2051
        compat = self._form.backwards_compat
 
2052
        for o in self.items:
 
2053
            if (not o.disabled or compat) and o.selected:
 
2054
                for l in o.get_labels():
 
2055
                    if l.text:
 
2056
                        res.append(l.text)
 
2057
                        break
 
2058
                else:
 
2059
                    res.append(None)
 
2060
        return res
 
2061
 
 
2062
    def possible_items(self, by_label=False):
 
2063
        """Deprecated: return the names or labels of all possible items.
 
2064
 
 
2065
        Includes disabled items, which may be misleading for some use cases.
 
2066
 
 
2067
        """
 
2068
        deprecation(
 
2069
            "[item.name for item in self.items]")
 
2070
        if by_label:
 
2071
            res = []
 
2072
            for o in self.items:
 
2073
                for l in o.get_labels():
 
2074
                    if l.text:
 
2075
                        res.append(l.text)
 
2076
                        break
 
2077
                else:
 
2078
                    res.append(None)
 
2079
            return res
 
2080
        return [o.name for o in self.items]
 
2081
 
 
2082
    def _totally_ordered_pairs(self):
 
2083
        if self.disabled:
 
2084
            return []
 
2085
        else:
 
2086
            return [(o._index, self.name, o.name) for o in self.items
 
2087
                    if o.selected and not o.disabled]
 
2088
 
 
2089
    def __str__(self):
 
2090
        name = self.name
 
2091
        if name is None: name = "<None>"
 
2092
 
 
2093
        display = [str(o) for o in self.items]
 
2094
 
 
2095
        infos = []
 
2096
        if self.disabled: infos.append("disabled")
 
2097
        if self.readonly: infos.append("readonly")
 
2098
        info = ", ".join(infos)
 
2099
        if info: info = " (%s)" % info
 
2100
 
 
2101
        return "<%s(%s=[%s])%s>" % (self.__class__.__name__,
 
2102
                                    name, ", ".join(display), info)
 
2103
 
 
2104
 
 
2105
class RadioControl(ListControl):
 
2106
    """
 
2107
    Covers:
 
2108
 
 
2109
    INPUT/RADIO
 
2110
 
 
2111
    """
 
2112
    def __init__(self, type, name, attrs, select_default=False, index=None):
 
2113
        attrs.setdefault("value", "on")
 
2114
        ListControl.__init__(self, type, name, attrs, select_default,
 
2115
                             called_as_base_class=True, index=index)
 
2116
        self.__dict__["multiple"] = False
 
2117
        o = Item(self, attrs, index)
 
2118
        o.__dict__["_selected"] = attrs.has_key("checked")
 
2119
 
 
2120
    def fixup(self):
 
2121
        ListControl.fixup(self)
 
2122
        found = [o for o in self.items if o.selected and not o.disabled]
 
2123
        if not found:
 
2124
            if self._select_default:
 
2125
                for o in self.items:
 
2126
                    if not o.disabled:
 
2127
                        o.selected = True
 
2128
                        break
 
2129
        else:
 
2130
            # Ensure only one item selected.  Choose the last one,
 
2131
            # following IE and Firefox.
 
2132
            for o in found[:-1]:
 
2133
                o.selected = False
 
2134
 
 
2135
    def get_labels(self):
 
2136
        return []
 
2137
 
 
2138
class CheckboxControl(ListControl):
 
2139
    """
 
2140
    Covers:
 
2141
 
 
2142
    INPUT/CHECKBOX
 
2143
 
 
2144
    """
 
2145
    def __init__(self, type, name, attrs, select_default=False, index=None):
 
2146
        attrs.setdefault("value", "on")
 
2147
        ListControl.__init__(self, type, name, attrs, select_default,
 
2148
                             called_as_base_class=True, index=index)
 
2149
        self.__dict__["multiple"] = True
 
2150
        o = Item(self, attrs, index)
 
2151
        o.__dict__["_selected"] = attrs.has_key("checked")
 
2152
 
 
2153
    def get_labels(self):
 
2154
        return []
 
2155
 
 
2156
 
 
2157
class SelectControl(ListControl):
 
2158
    """
 
2159
    Covers:
 
2160
 
 
2161
    SELECT (and OPTION)
 
2162
 
 
2163
 
 
2164
    OPTION 'values', in HTML parlance, are Item 'names' in ClientForm parlance.
 
2165
 
 
2166
 
 
2167
    OPTION 'values', in HTML parlance, are Item 'names' in ClientForm parlance.
 
2168
 
 
2169
    SELECT control values and labels are subject to some messy defaulting
 
2170
    rules.  For example, if the HTML representation of the control is:
 
2171
 
 
2172
    <SELECT name=year>
 
2173
      <OPTION value=0 label="2002">current year</OPTION>
 
2174
      <OPTION value=1>2001</OPTION>
 
2175
      <OPTION>2000</OPTION>
 
2176
    </SELECT>
 
2177
 
 
2178
    The items, in order, have labels "2002", "2001" and "2000", whereas their
 
2179
    names (the OPTION values) are "0", "1" and "2000" respectively.  Note that
 
2180
    the value of the last OPTION in this example defaults to its contents, as
 
2181
    specified by RFC 1866, as do the labels of the second and third OPTIONs.
 
2182
 
 
2183
    The OPTION labels are sometimes more meaningful than the OPTION values,
 
2184
    which can make for more maintainable code.
 
2185
 
 
2186
    Additional read-only public attribute: attrs
 
2187
 
 
2188
    The attrs attribute is a dictionary of the original HTML attributes of the
 
2189
    SELECT element.  Other ListControls do not have this attribute, because in
 
2190
    other cases the control as a whole does not correspond to any single HTML
 
2191
    element.  control.get(...).attrs may be used as usual to get at the HTML
 
2192
    attributes of the HTML elements corresponding to individual list items (for
 
2193
    SELECT controls, these are OPTION elements).
 
2194
 
 
2195
    Another special case is that the Item.attrs dictionaries have a special key
 
2196
    "contents" which does not correspond to any real HTML attribute, but rather
 
2197
    contains the contents of the OPTION element:
 
2198
 
 
2199
    <OPTION>this bit</OPTION>
 
2200
 
 
2201
    """
 
2202
    # HTML attributes here are treated slightly differently from other list
 
2203
    # controls:
 
2204
    # -The SELECT HTML attributes dictionary is stuffed into the OPTION
 
2205
    #  HTML attributes dictionary under the "__select" key.
 
2206
    # -The content of each OPTION element is stored under the special
 
2207
    #  "contents" key of the dictionary.
 
2208
    # After all this, the dictionary is passed to the SelectControl constructor
 
2209
    # as the attrs argument, as usual.  However:
 
2210
    # -The first SelectControl constructed when building up a SELECT control
 
2211
    #  has a constructor attrs argument containing only the __select key -- so
 
2212
    #  this SelectControl represents an empty SELECT control.
 
2213
    # -Subsequent SelectControls have both OPTION HTML-attribute in attrs and
 
2214
    #  the __select dictionary containing the SELECT HTML-attributes.
 
2215
 
 
2216
    def __init__(self, type, name, attrs, select_default=False, index=None):
 
2217
        # fish out the SELECT HTML attributes from the OPTION HTML attributes
 
2218
        # dictionary
 
2219
        self.attrs = attrs["__select"].copy()
 
2220
        self.__dict__["_label"] = _get_label(self.attrs)
 
2221
        self.__dict__["id"] = self.attrs.get("id")
 
2222
        self.__dict__["multiple"] = self.attrs.has_key("multiple")
 
2223
        # the majority of the contents, label, and value dance already happened
 
2224
        contents = attrs.get("contents")
 
2225
        attrs = attrs.copy()
 
2226
        del attrs["__select"]
 
2227
 
 
2228
        ListControl.__init__(self, type, name, self.attrs, select_default,
 
2229
                             called_as_base_class=True, index=index)
 
2230
        self.disabled = self.attrs.has_key("disabled")
 
2231
        self.readonly = self.attrs.has_key("readonly")
 
2232
        if attrs.has_key("value"):
 
2233
            # otherwise it is a marker 'select started' token
 
2234
            o = Item(self, attrs, index)
 
2235
            o.__dict__["_selected"] = attrs.has_key("selected")
 
2236
            # add 'label' label and contents label, if different.  If both are
 
2237
            # provided, the 'label' label is used for display in HTML 
 
2238
            # 4.0-compliant browsers (and any lower spec? not sure) while the
 
2239
            # contents are used for display in older or less-compliant
 
2240
            # browsers.  We make label objects for both, if the values are
 
2241
            # different.
 
2242
            label = attrs.get("label")
 
2243
            if label:
 
2244
                o._labels.append(Label({"__text": label}))
 
2245
                if contents and contents != label:
 
2246
                    o._labels.append(Label({"__text": contents}))
 
2247
            elif contents:
 
2248
                o._labels.append(Label({"__text": contents}))
 
2249
 
 
2250
    def fixup(self):
 
2251
        ListControl.fixup(self)
 
2252
        # Firefox doesn't exclude disabled items from those considered here
 
2253
        # (i.e. from 'found', for both branches of the if below).  Note that
 
2254
        # IE6 doesn't support the disabled attribute on OPTIONs at all.
 
2255
        found = [o for o in self.items if o.selected]
 
2256
        if not found:
 
2257
            if not self.multiple or self._select_default:
 
2258
                for o in self.items:
 
2259
                    if not o.disabled:
 
2260
                        was_disabled = self.disabled
 
2261
                        self.disabled = False
 
2262
                        try:
 
2263
                            o.selected = True
 
2264
                        finally:
 
2265
                            o.disabled = was_disabled
 
2266
                        break
 
2267
        elif not self.multiple:
 
2268
            # Ensure only one item selected.  Choose the last one,
 
2269
            # following IE and Firefox.
 
2270
            for o in found[:-1]:
 
2271
                o.selected = False
 
2272
 
 
2273
 
 
2274
#---------------------------------------------------
 
2275
class SubmitControl(ScalarControl):
 
2276
    """
 
2277
    Covers:
 
2278
 
 
2279
    INPUT/SUBMIT
 
2280
    BUTTON/SUBMIT
 
2281
 
 
2282
    """
 
2283
    def __init__(self, type, name, attrs, index=None):
 
2284
        ScalarControl.__init__(self, type, name, attrs, index)
 
2285
        # IE5 defaults SUBMIT value to "Submit Query"; Firebird 0.6 leaves it
 
2286
        # blank, Konqueror 3.1 defaults to "Submit".  HTML spec. doesn't seem
 
2287
        # to define this.
 
2288
        if self.value is None: self.value = ""
 
2289
        self.readonly = True
 
2290
 
 
2291
    def get_labels(self):
 
2292
        res = []
 
2293
        if self.value:
 
2294
            res.append(Label({"__text": self.value}))
 
2295
        res.extend(ScalarControl.get_labels(self))
 
2296
        return res
 
2297
 
 
2298
    def is_of_kind(self, kind): return kind == "clickable"
 
2299
 
 
2300
    def _click(self, form, coord, return_type, request_class=urllib2.Request):
 
2301
        self._clicked = coord
 
2302
        r = form._switch_click(return_type, request_class)
 
2303
        self._clicked = False
 
2304
        return r
 
2305
 
 
2306
    def _totally_ordered_pairs(self):
 
2307
        if not self._clicked:
 
2308
            return []
 
2309
        return ScalarControl._totally_ordered_pairs(self)
 
2310
 
 
2311
 
 
2312
#---------------------------------------------------
 
2313
class ImageControl(SubmitControl):
 
2314
    """
 
2315
    Covers:
 
2316
 
 
2317
    INPUT/IMAGE
 
2318
 
 
2319
    Coordinates are specified using one of the HTMLForm.click* methods.
 
2320
 
 
2321
    """
 
2322
    def __init__(self, type, name, attrs, index=None):
 
2323
        SubmitControl.__init__(self, type, name, attrs, index)
 
2324
        self.readonly = False
 
2325
 
 
2326
    def _totally_ordered_pairs(self):
 
2327
        clicked = self._clicked
 
2328
        if self.disabled or not clicked:
 
2329
            return []
 
2330
        name = self.name
 
2331
        if name is None: return []
 
2332
        pairs = [
 
2333
            (self._index, "%s.x" % name, str(clicked[0])),
 
2334
            (self._index+1, "%s.y" % name, str(clicked[1])),
 
2335
            ]
 
2336
        value = self._value
 
2337
        if value:
 
2338
            pairs.append((self._index+2, name, value))
 
2339
        return pairs
 
2340
 
 
2341
    get_labels = ScalarControl.get_labels
 
2342
 
 
2343
# aliases, just to make str(control) and str(form) clearer
 
2344
class PasswordControl(TextControl): pass
 
2345
class HiddenControl(TextControl): pass
 
2346
class TextareaControl(TextControl): pass
 
2347
class SubmitButtonControl(SubmitControl): pass
 
2348
 
 
2349
 
 
2350
def is_listcontrol(control): return control.is_of_kind("list")
 
2351
 
 
2352
 
 
2353
class HTMLForm:
 
2354
    """Represents a single HTML <form> ... </form> element.
 
2355
 
 
2356
    A form consists of a sequence of controls that usually have names, and
 
2357
    which can take on various values.  The values of the various types of
 
2358
    controls represent variously: text, zero-or-one-of-many or many-of-many
 
2359
    choices, and files to be uploaded.  Some controls can be clicked on to
 
2360
    submit the form, and clickable controls' values sometimes include the
 
2361
    coordinates of the click.
 
2362
 
 
2363
    Forms can be filled in with data to be returned to the server, and then
 
2364
    submitted, using the click method to generate a request object suitable for
 
2365
    passing to urllib2.urlopen (or the click_request_data or click_pairs
 
2366
    methods if you're not using urllib2).
 
2367
 
 
2368
    import ClientForm
 
2369
    forms = ClientForm.ParseFile(html, base_uri)
 
2370
    form = forms[0]
 
2371
 
 
2372
    form["query"] = "Python"
 
2373
    form.find_control("nr_results").get("lots").selected = True
 
2374
 
 
2375
    response = urllib2.urlopen(form.click())
 
2376
 
 
2377
    Usually, HTMLForm instances are not created directly.  Instead, the
 
2378
    ParseFile or ParseResponse factory functions are used.  If you do construct
 
2379
    HTMLForm objects yourself, however, note that an HTMLForm instance is only
 
2380
    properly initialised after the fixup method has been called (ParseFile and
 
2381
    ParseResponse do this for you).  See ListControl.__doc__ for the reason
 
2382
    this is required.
 
2383
 
 
2384
    Indexing a form (form["control_name"]) returns the named Control's value
 
2385
    attribute.  Assignment to a form index (form["control_name"] = something)
 
2386
    is equivalent to assignment to the named Control's value attribute.  If you
 
2387
    need to be more specific than just supplying the control's name, use the
 
2388
    set_value and get_value methods.
 
2389
 
 
2390
    ListControl values are lists of item names (specifically, the names of the
 
2391
    items that are selected and not disabled, and hence are "successful" -- ie.
 
2392
    cause data to be returned to the server).  The list item's name is the
 
2393
    value of the corresponding HTML element's"value" attribute.
 
2394
 
 
2395
    Example:
 
2396
 
 
2397
      <INPUT type="CHECKBOX" name="cheeses" value="leicester"></INPUT>
 
2398
      <INPUT type="CHECKBOX" name="cheeses" value="cheddar"></INPUT>
 
2399
 
 
2400
    defines a CHECKBOX control with name "cheeses" which has two items, named
 
2401
    "leicester" and "cheddar".
 
2402
 
 
2403
    Another example:
 
2404
 
 
2405
      <SELECT name="more_cheeses">
 
2406
        <OPTION>1</OPTION>
 
2407
        <OPTION value="2" label="CHEDDAR">cheddar</OPTION>
 
2408
      </SELECT>
 
2409
 
 
2410
    defines a SELECT control with name "more_cheeses" which has two items,
 
2411
    named "1" and "2" (because the OPTION element's value HTML attribute
 
2412
    defaults to the element contents -- see SelectControl.__doc__ for more on
 
2413
    these defaulting rules).
 
2414
 
 
2415
    To select, deselect or otherwise manipulate individual list items, use the
 
2416
    HTMLForm.find_control() and ListControl.get() methods.  To set the whole
 
2417
    value, do as for any other control: use indexing or the set_/get_value
 
2418
    methods.
 
2419
 
 
2420
    Example:
 
2421
 
 
2422
    # select *only* the item named "cheddar"
 
2423
    form["cheeses"] = ["cheddar"]
 
2424
    # select "cheddar", leave other items unaffected
 
2425
    form.find_control("cheeses").get("cheddar").selected = True
 
2426
 
 
2427
    Some controls (RADIO and SELECT without the multiple attribute) can only
 
2428
    have zero or one items selected at a time.  Some controls (CHECKBOX and
 
2429
    SELECT with the multiple attribute) can have multiple items selected at a
 
2430
    time.  To set the whole value of a ListControl, assign a sequence to a form
 
2431
    index:
 
2432
 
 
2433
    form["cheeses"] = ["cheddar", "leicester"]
 
2434
 
 
2435
    If the ListControl is not multiple-selection, the assigned list must be of
 
2436
    length one.
 
2437
 
 
2438
    To check if a control has an item, if an item is selected, or if an item is
 
2439
    successful (selected and not disabled), respectively:
 
2440
 
 
2441
    "cheddar" in [item.name for item in form.find_control("cheeses").items]
 
2442
    "cheddar" in [item.name for item in form.find_control("cheeses").items and
 
2443
                  item.selected]
 
2444
    "cheddar" in form["cheeses"]  # (or "cheddar" in form.get_value("cheeses"))
 
2445
 
 
2446
    Note that some list items may be disabled (see below).
 
2447
 
 
2448
    Note the following mistake:
 
2449
 
 
2450
    form[control_name] = control_value
 
2451
    assert form[control_name] == control_value  # not necessarily true
 
2452
 
 
2453
    The reason for this is that form[control_name] always gives the list items
 
2454
    in the order they were listed in the HTML.
 
2455
 
 
2456
    List items (hence list values, too) can be referred to in terms of list
 
2457
    item labels rather than list item names using the appropriate label
 
2458
    arguments.  Note that each item may have several labels.
 
2459
 
 
2460
    The question of default values of OPTION contents, labels and values is
 
2461
    somewhat complicated: see SelectControl.__doc__ and
 
2462
    ListControl.get_item_attrs.__doc__ if you think you need to know.
 
2463
 
 
2464
    Controls can be disabled or readonly.  In either case, the control's value
 
2465
    cannot be changed until you clear those flags (see example below).
 
2466
    Disabled is the state typically represented by browsers by 'greying out' a
 
2467
    control.  Disabled controls are not 'successful' -- they don't cause data
 
2468
    to get returned to the server.  Readonly controls usually appear in
 
2469
    browsers as read-only text boxes.  Readonly controls are successful.  List
 
2470
    items can also be disabled.  Attempts to select or deselect disabled items
 
2471
    fail with AttributeError.
 
2472
 
 
2473
    If a lot of controls are readonly, it can be useful to do this:
 
2474
 
 
2475
    form.set_all_readonly(False)
 
2476
 
 
2477
    To clear a control's value attribute, so that it is not successful (until a
 
2478
    value is subsequently set):
 
2479
 
 
2480
    form.clear("cheeses")
 
2481
 
 
2482
    More examples:
 
2483
 
 
2484
    control = form.find_control("cheeses")
 
2485
    control.disabled = False
 
2486
    control.readonly = False
 
2487
    control.get("gruyere").disabled = True
 
2488
    control.items[0].selected = True
 
2489
 
 
2490
    See the various Control classes for further documentation.  Many methods
 
2491
    take name, type, kind, id, label and nr arguments to specify the control to
 
2492
    be operated on: see HTMLForm.find_control.__doc__.
 
2493
 
 
2494
    ControlNotFoundError (subclass of ValueError) is raised if the specified
 
2495
    control can't be found.  This includes occasions where a non-ListControl
 
2496
    is found, but the method (set, for example) requires a ListControl.
 
2497
    ItemNotFoundError (subclass of ValueError) is raised if a list item can't
 
2498
    be found.  ItemCountError (subclass of ValueError) is raised if an attempt
 
2499
    is made to select more than one item and the control doesn't allow that, or
 
2500
    set/get_single are called and the control contains more than one item.
 
2501
    AttributeError is raised if a control or item is readonly or disabled and
 
2502
    an attempt is made to alter its value.
 
2503
 
 
2504
    Security note: Remember that any passwords you store in HTMLForm instances
 
2505
    will be saved to disk in the clear if you pickle them (directly or
 
2506
    indirectly).  The simplest solution to this is to avoid pickling HTMLForm
 
2507
    objects.  You could also pickle before filling in any password, or just set
 
2508
    the password to "" before pickling.
 
2509
 
 
2510
 
 
2511
    Public attributes:
 
2512
 
 
2513
    action: full (absolute URI) form action
 
2514
    method: "GET" or "POST"
 
2515
    enctype: form transfer encoding MIME type
 
2516
    name: name of form (None if no name was specified)
 
2517
    attrs: dictionary mapping original HTML form attributes to their values
 
2518
 
 
2519
    controls: list of Control instances; do not alter this list
 
2520
     (instead, call form.new_control to make a Control and add it to the
 
2521
     form, or control.add_to_form if you already have a Control instance)
 
2522
 
 
2523
 
 
2524
 
 
2525
    Methods for form filling:
 
2526
    -------------------------
 
2527
 
 
2528
    Most of the these methods have very similar arguments.  See
 
2529
    HTMLForm.find_control.__doc__ for details of the name, type, kind, label
 
2530
    and nr arguments.
 
2531
 
 
2532
    def find_control(self,
 
2533
                     name=None, type=None, kind=None, id=None, predicate=None,
 
2534
                     nr=None, label=None)
 
2535
 
 
2536
    get_value(name=None, type=None, kind=None, id=None, nr=None,
 
2537
              by_label=False,  # by_label is deprecated
 
2538
              label=None)
 
2539
    set_value(value,
 
2540
              name=None, type=None, kind=None, id=None, nr=None,
 
2541
              by_label=False,  # by_label is deprecated
 
2542
              label=None)
 
2543
 
 
2544
    clear_all()
 
2545
    clear(name=None, type=None, kind=None, id=None, nr=None, label=None)
 
2546
 
 
2547
    set_all_readonly(readonly)
 
2548
 
 
2549
 
 
2550
    Method applying only to FileControls:
 
2551
 
 
2552
    add_file(file_object,
 
2553
             content_type="application/octet-stream", filename=None,
 
2554
             name=None, id=None, nr=None, label=None)
 
2555
 
 
2556
 
 
2557
    Methods applying only to clickable controls:
 
2558
 
 
2559
    click(name=None, type=None, id=None, nr=0, coord=(1,1), label=None)
 
2560
    click_request_data(name=None, type=None, id=None, nr=0, coord=(1,1),
 
2561
                       label=None)
 
2562
    click_pairs(name=None, type=None, id=None, nr=0, coord=(1,1), label=None)
 
2563
 
 
2564
    """
 
2565
 
 
2566
    type2class = {
 
2567
        "text": TextControl,
 
2568
        "password": PasswordControl,
 
2569
        "hidden": HiddenControl,
 
2570
        "textarea": TextareaControl,
 
2571
 
 
2572
        "isindex": IsindexControl,
 
2573
 
 
2574
        "file": FileControl,
 
2575
 
 
2576
        "button": IgnoreControl,
 
2577
        "buttonbutton": IgnoreControl,
 
2578
        "reset": IgnoreControl,
 
2579
        "resetbutton": IgnoreControl,
 
2580
 
 
2581
        "submit": SubmitControl,
 
2582
        "submitbutton": SubmitButtonControl,
 
2583
        "image": ImageControl,
 
2584
 
 
2585
        "radio": RadioControl,
 
2586
        "checkbox": CheckboxControl,
 
2587
        "select": SelectControl,
 
2588
        }
 
2589
 
 
2590
#---------------------------------------------------
 
2591
# Initialisation.  Use ParseResponse / ParseFile instead.
 
2592
 
 
2593
    def __init__(self, action, method="GET",
 
2594
                 enctype="application/x-www-form-urlencoded",
 
2595
                 name=None, attrs=None,
 
2596
                 request_class=urllib2.Request,
 
2597
                 forms=None, labels=None, id_to_labels=None,
 
2598
                 backwards_compat=True):
 
2599
        """
 
2600
        In the usual case, use ParseResponse (or ParseFile) to create new
 
2601
        HTMLForm objects.
 
2602
 
 
2603
        action: full (absolute URI) form action
 
2604
        method: "GET" or "POST"
 
2605
        enctype: form transfer encoding MIME type
 
2606
        name: name of form
 
2607
        attrs: dictionary mapping original HTML form attributes to their values
 
2608
 
 
2609
        """
 
2610
        self.action = action
 
2611
        self.method = method
 
2612
        self.enctype = enctype
 
2613
        self.name = name
 
2614
        if attrs is not None:
 
2615
            self.attrs = attrs.copy()
 
2616
        else:
 
2617
            self.attrs = {}
 
2618
        self.controls = []
 
2619
        self._request_class = request_class
 
2620
 
 
2621
        # these attributes are used by zope.testbrowser
 
2622
        self._forms = forms  # this is a semi-public API!
 
2623
        self._labels = labels  # this is a semi-public API!
 
2624
        self._id_to_labels = id_to_labels  # this is a semi-public API!
 
2625
 
 
2626
        self.backwards_compat = backwards_compat  # note __setattr__
 
2627
 
 
2628
    def __getattr__(self, name):
 
2629
        if name == "backwards_compat":
 
2630
            return self._backwards_compat
 
2631
        return getattr(HTMLForm, name)
 
2632
 
 
2633
    def __setattr__(self, name, value):
 
2634
        # yuck
 
2635
        if name == "backwards_compat":
 
2636
            name = "_backwards_compat"
 
2637
            value = bool(value)
 
2638
            for cc in self.controls:
 
2639
                try:
 
2640
                    items = cc.items 
 
2641
                except AttributeError:
 
2642
                    continue
 
2643
                else:
 
2644
                    for ii in items:
 
2645
                        for ll in ii.get_labels():
 
2646
                            ll._backwards_compat = value
 
2647
        self.__dict__[name] = value
 
2648
 
 
2649
    def new_control(self, type, name, attrs,
 
2650
                    ignore_unknown=False, select_default=False, index=None):
 
2651
        """Adds a new control to the form.
 
2652
 
 
2653
        This is usually called by ParseFile and ParseResponse.  Don't call it
 
2654
        youself unless you're building your own Control instances.
 
2655
 
 
2656
        Note that controls representing lists of items are built up from
 
2657
        controls holding only a single list item.  See ListControl.__doc__ for
 
2658
        further information.
 
2659
 
 
2660
        type: type of control (see Control.__doc__ for a list)
 
2661
        attrs: HTML attributes of control
 
2662
        ignore_unknown: if true, use a dummy Control instance for controls of
 
2663
         unknown type; otherwise, use a TextControl
 
2664
        select_default: for RADIO and multiple-selection SELECT controls, pick
 
2665
         the first item as the default if no 'selected' HTML attribute is
 
2666
         present (this defaulting happens when the HTMLForm.fixup method is
 
2667
         called)
 
2668
        index: index of corresponding element in HTML (see
 
2669
         MoreFormTests.test_interspersed_controls for motivation)
 
2670
 
 
2671
        """
 
2672
        type = type.lower()
 
2673
        klass = self.type2class.get(type)
 
2674
        if klass is None:
 
2675
            if ignore_unknown:
 
2676
                klass = IgnoreControl
 
2677
            else:
 
2678
                klass = TextControl
 
2679
 
 
2680
        a = attrs.copy()
 
2681
        if issubclass(klass, ListControl):
 
2682
            control = klass(type, name, a, select_default, index)
 
2683
        else:
 
2684
            control = klass(type, name, a, index)
 
2685
        control.add_to_form(self)
 
2686
 
 
2687
    def fixup(self):
 
2688
        """Normalise form after all controls have been added.
 
2689
 
 
2690
        This is usually called by ParseFile and ParseResponse.  Don't call it
 
2691
        youself unless you're building your own Control instances.
 
2692
 
 
2693
        This method should only be called once, after all controls have been
 
2694
        added to the form.
 
2695
 
 
2696
        """
 
2697
        for control in self.controls:
 
2698
            control.fixup()
 
2699
        self.backwards_compat = self._backwards_compat
 
2700
 
 
2701
#---------------------------------------------------
 
2702
    def __str__(self):
 
2703
        header = "%s%s %s %s" % (
 
2704
            (self.name and self.name+" " or ""),
 
2705
            self.method, self.action, self.enctype)
 
2706
        rep = [header]
 
2707
        for control in self.controls:
 
2708
            rep.append("  %s" % str(control))
 
2709
        return "<%s>" % "\n".join(rep)
 
2710
 
 
2711
#---------------------------------------------------
 
2712
# Form-filling methods.
 
2713
 
 
2714
    def __getitem__(self, name):
 
2715
        return self.find_control(name).value
 
2716
    def __contains__(self, name):
 
2717
        return bool(self.find_control(name))
 
2718
    def __setitem__(self, name, value):
 
2719
        control = self.find_control(name)
 
2720
        try:
 
2721
            control.value = value
 
2722
        except AttributeError, e:
 
2723
            raise ValueError(str(e))
 
2724
 
 
2725
    def get_value(self,
 
2726
                  name=None, type=None, kind=None, id=None, nr=None,
 
2727
                  by_label=False,  # by_label is deprecated
 
2728
                  label=None):
 
2729
        """Return value of control.
 
2730
 
 
2731
        If only name and value arguments are supplied, equivalent to
 
2732
 
 
2733
        form[name]
 
2734
 
 
2735
        """
 
2736
        if by_label:
 
2737
            deprecation("form.get_value_by_label(...)")
 
2738
        c = self.find_control(name, type, kind, id, label=label, nr=nr)
 
2739
        if by_label:
 
2740
            try:
 
2741
                meth = c.get_value_by_label
 
2742
            except AttributeError:
 
2743
                raise NotImplementedError(
 
2744
                    "control '%s' does not yet support by_label" % c.name)
 
2745
            else:
 
2746
                return meth()
 
2747
        else:
 
2748
            return c.value
 
2749
    def set_value(self, value,
 
2750
                  name=None, type=None, kind=None, id=None, nr=None,
 
2751
                  by_label=False,  # by_label is deprecated
 
2752
                  label=None):
 
2753
        """Set value of control.
 
2754
 
 
2755
        If only name and value arguments are supplied, equivalent to
 
2756
 
 
2757
        form[name] = value
 
2758
 
 
2759
        """
 
2760
        if by_label:
 
2761
            deprecation("form.get_value_by_label(...)")
 
2762
        c = self.find_control(name, type, kind, id, label=label, nr=nr)
 
2763
        if by_label:
 
2764
            try:
 
2765
                meth = c.set_value_by_label
 
2766
            except AttributeError:
 
2767
                raise NotImplementedError(
 
2768
                    "control '%s' does not yet support by_label" % c.name)
 
2769
            else:
 
2770
                meth(value)
 
2771
        else:
 
2772
            c.value = value
 
2773
    def get_value_by_label(
 
2774
        self, name=None, type=None, kind=None, id=None, label=None, nr=None):
 
2775
        """
 
2776
 
 
2777
        All arguments should be passed by name.
 
2778
 
 
2779
        """
 
2780
        c = self.find_control(name, type, kind, id, label=label, nr=nr)
 
2781
        return c.get_value_by_label()
 
2782
 
 
2783
    def set_value_by_label(
 
2784
        self, value,
 
2785
        name=None, type=None, kind=None, id=None, label=None, nr=None):
 
2786
        """
 
2787
 
 
2788
        All arguments should be passed by name.
 
2789
 
 
2790
        """
 
2791
        c = self.find_control(name, type, kind, id, label=label, nr=nr)
 
2792
        c.set_value_by_label(value)
 
2793
 
 
2794
    def set_all_readonly(self, readonly):
 
2795
        for control in self.controls:
 
2796
            control.readonly = bool(readonly)
 
2797
 
 
2798
    def clear_all(self):
 
2799
        """Clear the value attributes of all controls in the form.
 
2800
 
 
2801
        See HTMLForm.clear.__doc__.
 
2802
 
 
2803
        """
 
2804
        for control in self.controls:
 
2805
            control.clear()
 
2806
 
 
2807
    def clear(self,
 
2808
              name=None, type=None, kind=None, id=None, nr=None, label=None):
 
2809
        """Clear the value attribute of a control.
 
2810
 
 
2811
        As a result, the affected control will not be successful until a value
 
2812
        is subsequently set.  AttributeError is raised on readonly controls.
 
2813
 
 
2814
        """
 
2815
        c = self.find_control(name, type, kind, id, label=label, nr=nr)
 
2816
        c.clear()
 
2817
 
 
2818
 
 
2819
#---------------------------------------------------
 
2820
# Form-filling methods applying only to ListControls.
 
2821
 
 
2822
    def possible_items(self,  # deprecated
 
2823
                       name=None, type=None, kind=None, id=None,
 
2824
                       nr=None, by_label=False, label=None):
 
2825
        """Return a list of all values that the specified control can take."""
 
2826
        c = self._find_list_control(name, type, kind, id, label, nr)
 
2827
        return c.possible_items(by_label)
 
2828
 
 
2829
    def set(self, selected, item_name,  # deprecated
 
2830
            name=None, type=None, kind=None, id=None, nr=None,
 
2831
            by_label=False, label=None):
 
2832
        """Select / deselect named list item.
 
2833
 
 
2834
        selected: boolean selected state
 
2835
 
 
2836
        """
 
2837
        self._find_list_control(name, type, kind, id, label, nr).set(
 
2838
            selected, item_name, by_label)
 
2839
    def toggle(self, item_name,  # deprecated
 
2840
               name=None, type=None, kind=None, id=None, nr=None,
 
2841
               by_label=False, label=None):
 
2842
        """Toggle selected state of named list item."""
 
2843
        self._find_list_control(name, type, kind, id, label, nr).toggle(
 
2844
            item_name, by_label)
 
2845
 
 
2846
    def set_single(self, selected,  # deprecated
 
2847
                   name=None, type=None, kind=None, id=None,
 
2848
                   nr=None, by_label=None, label=None):
 
2849
        """Select / deselect list item in a control having only one item.
 
2850
 
 
2851
        If the control has multiple list items, ItemCountError is raised.
 
2852
 
 
2853
        This is just a convenience method, so you don't need to know the item's
 
2854
        name -- the item name in these single-item controls is usually
 
2855
        something meaningless like "1" or "on".
 
2856
 
 
2857
        For example, if a checkbox has a single item named "on", the following
 
2858
        two calls are equivalent:
 
2859
 
 
2860
        control.toggle("on")
 
2861
        control.toggle_single()
 
2862
 
 
2863
        """  # by_label ignored and deprecated
 
2864
        self._find_list_control(
 
2865
            name, type, kind, id, label, nr).set_single(selected)
 
2866
    def toggle_single(self, name=None, type=None, kind=None, id=None,
 
2867
                      nr=None, by_label=None, label=None):  # deprecated
 
2868
        """Toggle selected state of list item in control having only one item.
 
2869
 
 
2870
        The rest is as for HTMLForm.set_single.__doc__.
 
2871
 
 
2872
        """  # by_label ignored and deprecated
 
2873
        self._find_list_control(name, type, kind, id, label, nr).toggle_single()
 
2874
 
 
2875
#---------------------------------------------------
 
2876
# Form-filling method applying only to FileControls.
 
2877
 
 
2878
    def add_file(self, file_object, content_type=None, filename=None,
 
2879
                 name=None, id=None, nr=None, label=None):
 
2880
        """Add a file to be uploaded.
 
2881
 
 
2882
        file_object: file-like object (with read method) from which to read
 
2883
         data to upload
 
2884
        content_type: MIME content type of data to upload
 
2885
        filename: filename to pass to server
 
2886
 
 
2887
        If filename is None, no filename is sent to the server.
 
2888
 
 
2889
        If content_type is None, the content type is guessed based on the
 
2890
        filename and the data from read from the file object.
 
2891
 
 
2892
        XXX
 
2893
        At the moment, guessed content type is always application/octet-stream.
 
2894
        Use sndhdr, imghdr modules.  Should also try to guess HTML, XML, and
 
2895
        plain text.
 
2896
 
 
2897
        Note the following useful HTML attributes of file upload controls (see
 
2898
        HTML 4.01 spec, section 17):
 
2899
 
 
2900
        accept: comma-separated list of content types that the server will
 
2901
         handle correctly; you can use this to filter out non-conforming files
 
2902
        size: XXX IIRC, this is indicative of whether form wants multiple or
 
2903
         single files
 
2904
        maxlength: XXX hint of max content length in bytes?
 
2905
 
 
2906
        """
 
2907
        self.find_control(name, "file", id=id, label=label, nr=nr).add_file(
 
2908
            file_object, content_type, filename)
 
2909
 
 
2910
#---------------------------------------------------
 
2911
# Form submission methods, applying only to clickable controls.
 
2912
 
 
2913
    def click(self, name=None, type=None, id=None, nr=0, coord=(1,1),
 
2914
              request_class=urllib2.Request,
 
2915
              label=None):
 
2916
        """Return request that would result from clicking on a control.
 
2917
 
 
2918
        The request object is a urllib2.Request instance, which you can pass to
 
2919
        urllib2.urlopen (or ClientCookie.urlopen).
 
2920
 
 
2921
        Only some control types (INPUT/SUBMIT & BUTTON/SUBMIT buttons and
 
2922
        IMAGEs) can be clicked.
 
2923
 
 
2924
        Will click on the first clickable control, subject to the name, type
 
2925
        and nr arguments (as for find_control).  If no name, type, id or number
 
2926
        is specified and there are no clickable controls, a request will be
 
2927
        returned for the form in its current, un-clicked, state.
 
2928
 
 
2929
        IndexError is raised if any of name, type, id or nr is specified but no
 
2930
        matching control is found.  ValueError is raised if the HTMLForm has an
 
2931
        enctype attribute that is not recognised.
 
2932
 
 
2933
        You can optionally specify a coordinate to click at, which only makes a
 
2934
        difference if you clicked on an image.
 
2935
 
 
2936
        """
 
2937
        return self._click(name, type, id, label, nr, coord, "request",
 
2938
                           self._request_class)
 
2939
 
 
2940
    def click_request_data(self,
 
2941
                           name=None, type=None, id=None,
 
2942
                           nr=0, coord=(1,1),
 
2943
                           request_class=urllib2.Request,
 
2944
                           label=None):
 
2945
        """As for click method, but return a tuple (url, data, headers).
 
2946
 
 
2947
        You can use this data to send a request to the server.  This is useful
 
2948
        if you're using httplib or urllib rather than urllib2.  Otherwise, use
 
2949
        the click method.
 
2950
 
 
2951
        # Untested.  Have to subclass to add headers, I think -- so use urllib2
 
2952
        # instead!
 
2953
        import urllib
 
2954
        url, data, hdrs = form.click_request_data()
 
2955
        r = urllib.urlopen(url, data)
 
2956
 
 
2957
        # Untested.  I don't know of any reason to use httplib -- you can get
 
2958
        # just as much control with urllib2.
 
2959
        import httplib, urlparse
 
2960
        url, data, hdrs = form.click_request_data()
 
2961
        tup = urlparse(url)
 
2962
        host, path = tup[1], urlparse.urlunparse((None, None)+tup[2:])
 
2963
        conn = httplib.HTTPConnection(host)
 
2964
        if data:
 
2965
            httplib.request("POST", path, data, hdrs)
 
2966
        else:
 
2967
            httplib.request("GET", path, headers=hdrs)
 
2968
        r = conn.getresponse()
 
2969
 
 
2970
        """
 
2971
        return self._click(name, type, id, label, nr, coord, "request_data",
 
2972
                           self._request_class)
 
2973
 
 
2974
    def click_pairs(self, name=None, type=None, id=None,
 
2975
                    nr=0, coord=(1,1),
 
2976
                    label=None):
 
2977
        """As for click_request_data, but returns a list of (key, value) pairs.
 
2978
 
 
2979
        You can use this list as an argument to ClientForm.urlencode.  This is
 
2980
        usually only useful if you're using httplib or urllib rather than
 
2981
        urllib2 or ClientCookie.  It may also be useful if you want to manually
 
2982
        tweak the keys and/or values, but this should not be necessary.
 
2983
        Otherwise, use the click method.
 
2984
 
 
2985
        Note that this method is only useful for forms of MIME type
 
2986
        x-www-form-urlencoded.  In particular, it does not return the
 
2987
        information required for file upload.  If you need file upload and are
 
2988
        not using urllib2, use click_request_data.
 
2989
 
 
2990
        Also note that Python 2.0's urllib.urlencode is slightly broken: it
 
2991
        only accepts a mapping, not a sequence of pairs, as an argument.  This
 
2992
        messes up any ordering in the argument.  Use ClientForm.urlencode
 
2993
        instead.
 
2994
 
 
2995
        """
 
2996
        return self._click(name, type, id, label, nr, coord, "pairs",
 
2997
                           self._request_class)
 
2998
 
 
2999
#---------------------------------------------------
 
3000
 
 
3001
    def find_control(self,
 
3002
                     name=None, type=None, kind=None, id=None,
 
3003
                     predicate=None, nr=None,
 
3004
                     label=None):
 
3005
        """Locate and return some specific control within the form.
 
3006
 
 
3007
        At least one of the name, type, kind, predicate and nr arguments must
 
3008
        be supplied.  If no matching control is found, ControlNotFoundError is
 
3009
        raised.
 
3010
 
 
3011
        If name is specified, then the control must have the indicated name.
 
3012
 
 
3013
        If type is specified then the control must have the specified type (in
 
3014
        addition to the types possible for <input> HTML tags: "text",
 
3015
        "password", "hidden", "submit", "image", "button", "radio", "checkbox",
 
3016
        "file" we also have "reset", "buttonbutton", "submitbutton",
 
3017
        "resetbutton", "textarea", "select" and "isindex").
 
3018
 
 
3019
        If kind is specified, then the control must fall into the specified
 
3020
        group, each of which satisfies a particular interface.  The types are
 
3021
        "text", "list", "multilist", "singlelist", "clickable" and "file".
 
3022
 
 
3023
        If id is specified, then the control must have the indicated id.
 
3024
 
 
3025
        If predicate is specified, then the control must match that function.
 
3026
        The predicate function is passed the control as its single argument,
 
3027
        and should return a boolean value indicating whether the control
 
3028
        matched.
 
3029
 
 
3030
        nr, if supplied, is the sequence number of the control (where 0 is the
 
3031
        first).  Note that control 0 is the first control matching all the
 
3032
        other arguments (if supplied); it is not necessarily the first control
 
3033
        in the form.  If no nr is supplied, AmbiguityError is raised if
 
3034
        multiple controls match the other arguments (unless the
 
3035
        .backwards-compat attribute is true).
 
3036
 
 
3037
        If label is specified, then the control must have this label.  Note
 
3038
        that radio controls and checkboxes never have labels: their items do.
 
3039
 
 
3040
        """
 
3041
        if ((name is None) and (type is None) and (kind is None) and
 
3042
            (id is None) and (label is None) and (predicate is None) and
 
3043
            (nr is None)):
 
3044
            raise ValueError(
 
3045
                "at least one argument must be supplied to specify control")
 
3046
        return self._find_control(name, type, kind, id, label, predicate, nr)
 
3047
 
 
3048
#---------------------------------------------------
 
3049
# Private methods.
 
3050
 
 
3051
    def _find_list_control(self,
 
3052
                           name=None, type=None, kind=None, id=None, 
 
3053
                           label=None, nr=None):
 
3054
        if ((name is None) and (type is None) and (kind is None) and
 
3055
            (id is None) and (label is None) and (nr is None)):
 
3056
            raise ValueError(
 
3057
                "at least one argument must be supplied to specify control")
 
3058
 
 
3059
        return self._find_control(name, type, kind, id, label, 
 
3060
                                  is_listcontrol, nr)
 
3061
 
 
3062
    def _find_control(self, name, type, kind, id, label, predicate, nr):
 
3063
        if (name is not None) and not isstringlike(name):
 
3064
            raise TypeError("control name must be string-like")
 
3065
        if (type is not None) and not isstringlike(type):
 
3066
            raise TypeError("control type must be string-like")
 
3067
        if (kind is not None) and not isstringlike(kind):
 
3068
            raise TypeError("control kind must be string-like")
 
3069
        if (id is not None) and not isstringlike(id):
 
3070
            raise TypeError("control id must be string-like")
 
3071
        if (label is not None) and not isstringlike(label):
 
3072
            raise TypeError("control label must be string-like")
 
3073
        if (predicate is not None) and not callable(predicate):
 
3074
            raise TypeError("control predicate must be callable")
 
3075
        if (nr is not None) and nr < 0:
 
3076
            raise ValueError("control number must be a positive integer")
 
3077
 
 
3078
        orig_nr = nr
 
3079
        found = None
 
3080
        ambiguous = False
 
3081
        if nr is None and self.backwards_compat:
 
3082
            nr = 0
 
3083
 
 
3084
        for control in self.controls:
 
3085
            if name is not None and name != control.name:
 
3086
                continue
 
3087
            if type is not None and type != control.type:
 
3088
                continue
 
3089
            if kind is not None and not control.is_of_kind(kind):
 
3090
                continue
 
3091
            if id is not None and id != control.id:
 
3092
                continue
 
3093
            if predicate and not predicate(control):
 
3094
                continue
 
3095
            if label:
 
3096
                for l in control.get_labels():
 
3097
                    if l.text.find(label) > -1:
 
3098
                        break
 
3099
                else:
 
3100
                    continue
 
3101
            if nr is not None:
 
3102
                if nr == 0:
 
3103
                    return control  # early exit: unambiguous due to nr
 
3104
                nr -= 1
 
3105
                continue
 
3106
            if found:
 
3107
                ambiguous = True
 
3108
                break
 
3109
            found = control
 
3110
 
 
3111
        if found and not ambiguous:
 
3112
            return found
 
3113
 
 
3114
        description = []
 
3115
        if name is not None: description.append("name '%s'" % name)
 
3116
        if type is not None: description.append("type '%s'" % type)
 
3117
        if kind is not None: description.append("kind '%s'" % kind)
 
3118
        if id is not None: description.append("id '%s'" % id)
 
3119
        if label is not None: description.append("label '%s'" % label)
 
3120
        if predicate is not None:
 
3121
            description.append("predicate %s" % predicate)
 
3122
        if orig_nr: description.append("nr %d" % orig_nr)
 
3123
        description = ", ".join(description)
 
3124
 
 
3125
        if ambiguous:
 
3126
            raise AmbiguityError("more than one control matching "+description)
 
3127
        elif not found:
 
3128
            raise ControlNotFoundError("no control matching "+description)
 
3129
        assert False
 
3130
 
 
3131
    def _click(self, name, type, id, label, nr, coord, return_type,
 
3132
               request_class=urllib2.Request):
 
3133
        try:
 
3134
            control = self._find_control(
 
3135
                name, type, "clickable", id, label, None, nr)
 
3136
        except ControlNotFoundError:
 
3137
            if ((name is not None) or (type is not None) or (id is not None) or
 
3138
                (nr != 0)):
 
3139
                raise
 
3140
            # no clickable controls, but no control was explicitly requested,
 
3141
            # so return state without clicking any control
 
3142
            return self._switch_click(return_type, request_class)
 
3143
        else:
 
3144
            return control._click(self, coord, return_type, request_class)
 
3145
 
 
3146
    def _pairs(self):
 
3147
        """Return sequence of (key, value) pairs suitable for urlencoding."""
 
3148
        return [(k, v) for (i, k, v, c_i) in self._pairs_and_controls()]
 
3149
 
 
3150
 
 
3151
    def _pairs_and_controls(self):
 
3152
        """Return sequence of (index, key, value, control_index)
 
3153
        of totally ordered pairs suitable for urlencoding.
 
3154
 
 
3155
        control_index is the index of the control in self.controls
 
3156
        """
 
3157
        pairs = []
 
3158
        for control_index in range(len(self.controls)):
 
3159
            control = self.controls[control_index]
 
3160
            for ii, key, val in control._totally_ordered_pairs():
 
3161
                pairs.append((ii, key, val, control_index))
 
3162
 
 
3163
        # stable sort by ONLY first item in tuple
 
3164
        pairs.sort()
 
3165
 
 
3166
        return pairs
 
3167
 
 
3168
    def _request_data(self):
 
3169
        """Return a tuple (url, data, headers)."""
 
3170
        method = self.method.upper()
 
3171
        #scheme, netloc, path, parameters, query, frag = urlparse.urlparse(self.action)
 
3172
        parts = urlparse.urlparse(self.action)
 
3173
        rest, (query, frag) = parts[:-2], parts[-2:]
 
3174
 
 
3175
        if method == "GET":
 
3176
            if self.enctype != "application/x-www-form-urlencoded":
 
3177
                raise ValueError(
 
3178
                    "unknown GET form encoding type '%s'" % self.enctype)
 
3179
            parts = rest + (urlencode(self._pairs()), "")
 
3180
            uri = urlparse.urlunparse(parts)
 
3181
            return uri, None, []
 
3182
        elif method == "POST":
 
3183
            parts = rest + (query, "")
 
3184
            uri = urlparse.urlunparse(parts)
 
3185
            if self.enctype == "application/x-www-form-urlencoded":
 
3186
                return (uri, urlencode(self._pairs()),
 
3187
                        [("Content-type", self.enctype)])
 
3188
            elif self.enctype == "multipart/form-data":
 
3189
                data = StringIO()
 
3190
                http_hdrs = []
 
3191
                mw = MimeWriter(data, http_hdrs)
 
3192
                f = mw.startmultipartbody("form-data", add_to_http_hdrs=True,
 
3193
                                          prefix=0)
 
3194
                for ii, k, v, control_index in self._pairs_and_controls():
 
3195
                    self.controls[control_index]._write_mime_data(mw, k, v)
 
3196
                mw.lastpart()
 
3197
                return uri, data.getvalue(), http_hdrs
 
3198
            else:
 
3199
                raise ValueError(
 
3200
                    "unknown POST form encoding type '%s'" % self.enctype)
 
3201
        else:
 
3202
            raise ValueError("Unknown method '%s'" % method)
 
3203
 
 
3204
    def _switch_click(self, return_type, request_class=urllib2.Request):
 
3205
        # This is called by HTMLForm and clickable Controls to hide switching
 
3206
        # on return_type.
 
3207
        if return_type == "pairs":
 
3208
            return self._pairs()
 
3209
        elif return_type == "request_data":
 
3210
            return self._request_data()
 
3211
        else:
 
3212
            req_data = self._request_data()
 
3213
            req = request_class(req_data[0], req_data[1])
 
3214
            for key, val in req_data[2]:
 
3215
                add_hdr = req.add_header
 
3216
                if key.lower() == "content-type":
 
3217
                    try:
 
3218
                        add_hdr = req.add_unredirected_header
 
3219
                    except AttributeError:
 
3220
                        # pre-2.4 and not using ClientCookie
 
3221
                        pass
 
3222
                add_hdr(key, val)
 
3223
            return req