lxml 5.2.0__cp310-cp310-win32.whl → 5.2.2__cp310-cp310-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lxml/ElementInclude.py +244 -244
- lxml/__init__.py +22 -22
- lxml/_elementpath.cp310-win32.pyd +0 -0
- lxml/_elementpath.py +341 -341
- lxml/apihelpers.pxi +1793 -1793
- lxml/builder.cp310-win32.pyd +0 -0
- lxml/builder.py +232 -232
- lxml/classlookup.pxi +580 -580
- lxml/cleanup.pxi +215 -215
- lxml/cssselect.py +101 -101
- lxml/debug.pxi +90 -90
- lxml/docloader.pxi +178 -178
- lxml/doctestcompare.py +488 -488
- lxml/dtd.pxi +478 -478
- lxml/etree.cp310-win32.pyd +0 -0
- lxml/etree.h +6 -6
- lxml/etree.pyx +3732 -3711
- lxml/extensions.pxi +833 -833
- lxml/html/ElementSoup.py +10 -10
- lxml/html/__init__.py +1923 -1923
- lxml/html/_diffcommand.py +86 -86
- lxml/html/_html5builder.py +100 -100
- lxml/html/_setmixin.py +56 -56
- lxml/html/builder.py +133 -133
- lxml/html/clean.py +21 -21
- lxml/html/defs.py +135 -135
- lxml/html/diff.cp310-win32.pyd +0 -0
- lxml/html/diff.py +878 -878
- lxml/html/formfill.py +299 -299
- lxml/html/html5parser.py +260 -260
- lxml/html/soupparser.py +314 -314
- lxml/html/usedoctest.py +13 -13
- lxml/includes/c14n.pxd +25 -25
- lxml/includes/config.pxd +3 -3
- lxml/includes/dtdvalid.pxd +18 -18
- lxml/includes/etree_defs.h +379 -379
- lxml/includes/etreepublic.pxd +237 -237
- lxml/includes/htmlparser.pxd +56 -56
- lxml/includes/lxml-version.h +1 -1
- lxml/includes/relaxng.pxd +64 -64
- lxml/includes/schematron.pxd +34 -34
- lxml/includes/tree.pxd +494 -494
- lxml/includes/uri.pxd +5 -5
- lxml/includes/xinclude.pxd +22 -22
- lxml/includes/xmlerror.pxd +852 -852
- lxml/includes/xmlparser.pxd +265 -265
- lxml/includes/xmlschema.pxd +35 -35
- lxml/includes/xpath.pxd +136 -136
- lxml/includes/xslt.pxd +190 -190
- lxml/isoschematron/__init__.py +348 -348
- lxml/isoschematron/resources/rng/iso-schematron.rng +709 -709
- lxml/isoschematron/resources/xsl/RNG2Schtrn.xsl +75 -75
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_abstract_expand.xsl +312 -312
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_dsdl_include.xsl +1159 -1159
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_message.xsl +54 -54
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_skeleton_for_xslt1.xsl +1796 -1796
- lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_svrl_for_xslt1.xsl +588 -588
- lxml/iterparse.pxi +438 -438
- lxml/lxml.etree.h +6 -6
- lxml/nsclasses.pxi +281 -281
- lxml/objectify.cp310-win32.pyd +0 -0
- lxml/objectify.pyx +2145 -2145
- lxml/objectpath.pxi +332 -332
- lxml/parser.pxi +1994 -1994
- lxml/parsertarget.pxi +180 -180
- lxml/proxy.pxi +619 -619
- lxml/public-api.pxi +178 -178
- lxml/pyclasslookup.py +3 -3
- lxml/readonlytree.pxi +565 -565
- lxml/relaxng.pxi +165 -165
- lxml/sax.cp310-win32.pyd +0 -0
- lxml/sax.py +275 -275
- lxml/saxparser.pxi +875 -875
- lxml/schematron.pxi +168 -168
- lxml/serializer.pxi +1871 -1871
- lxml/usedoctest.py +13 -13
- lxml/xinclude.pxi +67 -67
- lxml/xmlerror.pxi +1654 -1654
- lxml/xmlid.pxi +179 -179
- lxml/xmlschema.pxi +215 -215
- lxml/xpath.pxi +487 -487
- lxml/xslt.pxi +950 -950
- lxml/xsltext.pxi +242 -242
- {lxml-5.2.0.dist-info → lxml-5.2.2.dist-info}/LICENSE.txt +29 -29
- {lxml-5.2.0.dist-info → lxml-5.2.2.dist-info}/LICENSES.txt +29 -29
- {lxml-5.2.0.dist-info → lxml-5.2.2.dist-info}/METADATA +9 -17
- {lxml-5.2.0.dist-info → lxml-5.2.2.dist-info}/RECORD +89 -89
- {lxml-5.2.0.dist-info → lxml-5.2.2.dist-info}/WHEEL +0 -0
- {lxml-5.2.0.dist-info → lxml-5.2.2.dist-info}/top_level.txt +0 -0
lxml/html/formfill.py
CHANGED
@@ -1,299 +1,299 @@
|
|
1
|
-
from lxml.etree import XPath, ElementBase
|
2
|
-
from lxml.html import fromstring, XHTML_NAMESPACE
|
3
|
-
from lxml.html import _forms_xpath, _options_xpath, _nons, _transform_result
|
4
|
-
from lxml.html import defs
|
5
|
-
import copy
|
6
|
-
|
7
|
-
try:
|
8
|
-
basestring
|
9
|
-
except NameError:
|
10
|
-
# Python 3
|
11
|
-
basestring = str
|
12
|
-
|
13
|
-
__all__ = ['FormNotFound', 'fill_form', 'fill_form_html',
|
14
|
-
'insert_errors', 'insert_errors_html',
|
15
|
-
'DefaultErrorCreator']
|
16
|
-
|
17
|
-
class FormNotFound(LookupError):
|
18
|
-
"""
|
19
|
-
Raised when no form can be found
|
20
|
-
"""
|
21
|
-
|
22
|
-
_form_name_xpath = XPath('descendant-or-self::form[name=$name]|descendant-or-self::x:form[name=$name]', namespaces={'x':XHTML_NAMESPACE})
|
23
|
-
_input_xpath = XPath('|'.join(['descendant-or-self::'+_tag for _tag in ('input','select','textarea','x:input','x:select','x:textarea')]),
|
24
|
-
namespaces={'x':XHTML_NAMESPACE})
|
25
|
-
_label_for_xpath = XPath('//label[@for=$for_id]|//x:label[@for=$for_id]',
|
26
|
-
namespaces={'x':XHTML_NAMESPACE})
|
27
|
-
_name_xpath = XPath('descendant-or-self::*[@name=$name]')
|
28
|
-
|
29
|
-
def fill_form(
|
30
|
-
el,
|
31
|
-
values,
|
32
|
-
form_id=None,
|
33
|
-
form_index=None,
|
34
|
-
):
|
35
|
-
el = _find_form(el, form_id=form_id, form_index=form_index)
|
36
|
-
_fill_form(el, values)
|
37
|
-
|
38
|
-
def fill_form_html(html, values, form_id=None, form_index=None):
|
39
|
-
result_type = type(html)
|
40
|
-
if isinstance(html, basestring):
|
41
|
-
doc = fromstring(html)
|
42
|
-
else:
|
43
|
-
doc = copy.deepcopy(html)
|
44
|
-
fill_form(doc, values, form_id=form_id, form_index=form_index)
|
45
|
-
return _transform_result(result_type, doc)
|
46
|
-
|
47
|
-
def _fill_form(el, values):
|
48
|
-
counts = {}
|
49
|
-
if hasattr(values, 'mixed'):
|
50
|
-
# For Paste request parameters
|
51
|
-
values = values.mixed()
|
52
|
-
inputs = _input_xpath(el)
|
53
|
-
for input in inputs:
|
54
|
-
name = input.get('name')
|
55
|
-
if not name:
|
56
|
-
continue
|
57
|
-
if _takes_multiple(input):
|
58
|
-
value = values.get(name, [])
|
59
|
-
if not isinstance(value, (list, tuple)):
|
60
|
-
value = [value]
|
61
|
-
_fill_multiple(input, value)
|
62
|
-
elif name not in values:
|
63
|
-
continue
|
64
|
-
else:
|
65
|
-
index = counts.get(name, 0)
|
66
|
-
counts[name] = index + 1
|
67
|
-
value = values[name]
|
68
|
-
if isinstance(value, (list, tuple)):
|
69
|
-
try:
|
70
|
-
value = value[index]
|
71
|
-
except IndexError:
|
72
|
-
continue
|
73
|
-
elif index > 0:
|
74
|
-
continue
|
75
|
-
_fill_single(input, value)
|
76
|
-
|
77
|
-
def _takes_multiple(input):
|
78
|
-
if _nons(input.tag) == 'select' and input.get('multiple'):
|
79
|
-
# FIXME: multiple="0"?
|
80
|
-
return True
|
81
|
-
type = input.get('type', '').lower()
|
82
|
-
if type in ('radio', 'checkbox'):
|
83
|
-
return True
|
84
|
-
return False
|
85
|
-
|
86
|
-
def _fill_multiple(input, value):
|
87
|
-
type = input.get('type', '').lower()
|
88
|
-
if type == 'checkbox':
|
89
|
-
v = input.get('value')
|
90
|
-
if v is None:
|
91
|
-
if not value:
|
92
|
-
result = False
|
93
|
-
else:
|
94
|
-
result = value[0]
|
95
|
-
if isinstance(value, basestring):
|
96
|
-
# The only valid "on" value for an unnamed checkbox is 'on'
|
97
|
-
result = result == 'on'
|
98
|
-
_check(input, result)
|
99
|
-
else:
|
100
|
-
_check(input, v in value)
|
101
|
-
elif type == 'radio':
|
102
|
-
v = input.get('value')
|
103
|
-
_check(input, v in value)
|
104
|
-
else:
|
105
|
-
assert _nons(input.tag) == 'select'
|
106
|
-
for option in _options_xpath(input):
|
107
|
-
v = option.get('value')
|
108
|
-
if v is None:
|
109
|
-
# This seems to be the default, at least on IE
|
110
|
-
# FIXME: but I'm not sure
|
111
|
-
v = option.text_content()
|
112
|
-
_select(option, v in value)
|
113
|
-
|
114
|
-
def _check(el, check):
|
115
|
-
if check:
|
116
|
-
el.set('checked', '')
|
117
|
-
else:
|
118
|
-
if 'checked' in el.attrib:
|
119
|
-
del el.attrib['checked']
|
120
|
-
|
121
|
-
def _select(el, select):
|
122
|
-
if select:
|
123
|
-
el.set('selected', '')
|
124
|
-
else:
|
125
|
-
if 'selected' in el.attrib:
|
126
|
-
del el.attrib['selected']
|
127
|
-
|
128
|
-
def _fill_single(input, value):
|
129
|
-
if _nons(input.tag) == 'textarea':
|
130
|
-
input.text = value
|
131
|
-
else:
|
132
|
-
input.set('value', value)
|
133
|
-
|
134
|
-
def _find_form(el, form_id=None, form_index=None):
|
135
|
-
if form_id is None and form_index is None:
|
136
|
-
forms = _forms_xpath(el)
|
137
|
-
for form in forms:
|
138
|
-
return form
|
139
|
-
raise FormNotFound(
|
140
|
-
"No forms in page")
|
141
|
-
if form_id is not None:
|
142
|
-
form = el.get_element_by_id(form_id)
|
143
|
-
if form is not None:
|
144
|
-
return form
|
145
|
-
forms = _form_name_xpath(el, name=form_id)
|
146
|
-
if forms:
|
147
|
-
return forms[0]
|
148
|
-
else:
|
149
|
-
raise FormNotFound(
|
150
|
-
"No form with the name or id of %r (forms: %s)"
|
151
|
-
% (id, ', '.join(_find_form_ids(el))))
|
152
|
-
if form_index is not None:
|
153
|
-
forms = _forms_xpath(el)
|
154
|
-
try:
|
155
|
-
return forms[form_index]
|
156
|
-
except IndexError:
|
157
|
-
raise FormNotFound(
|
158
|
-
"There is no form with the index %r (%i forms found)"
|
159
|
-
% (form_index, len(forms)))
|
160
|
-
|
161
|
-
def _find_form_ids(el):
|
162
|
-
forms = _forms_xpath(el)
|
163
|
-
if not forms:
|
164
|
-
yield '(no forms)'
|
165
|
-
return
|
166
|
-
for index, form in enumerate(forms):
|
167
|
-
if form.get('id'):
|
168
|
-
if form.get('name'):
|
169
|
-
yield '%s or %s' % (form.get('id'),
|
170
|
-
form.get('name'))
|
171
|
-
else:
|
172
|
-
yield form.get('id')
|
173
|
-
elif form.get('name'):
|
174
|
-
yield form.get('name')
|
175
|
-
else:
|
176
|
-
yield '(unnamed form %s)' % index
|
177
|
-
|
178
|
-
############################################################
|
179
|
-
## Error filling
|
180
|
-
############################################################
|
181
|
-
|
182
|
-
class DefaultErrorCreator:
|
183
|
-
insert_before = True
|
184
|
-
block_inside = True
|
185
|
-
error_container_tag = 'div'
|
186
|
-
error_message_class = 'error-message'
|
187
|
-
error_block_class = 'error-block'
|
188
|
-
default_message = "Invalid"
|
189
|
-
|
190
|
-
def __init__(self, **kw):
|
191
|
-
for name, value in kw.items():
|
192
|
-
if not hasattr(self, name):
|
193
|
-
raise TypeError(
|
194
|
-
"Unexpected keyword argument: %s" % name)
|
195
|
-
setattr(self, name, value)
|
196
|
-
|
197
|
-
def __call__(self, el, is_block, message):
|
198
|
-
error_el = el.makeelement(self.error_container_tag)
|
199
|
-
if self.error_message_class:
|
200
|
-
error_el.set('class', self.error_message_class)
|
201
|
-
if is_block and self.error_block_class:
|
202
|
-
error_el.set('class', error_el.get('class', '')+' '+self.error_block_class)
|
203
|
-
if message is None or message == '':
|
204
|
-
message = self.default_message
|
205
|
-
if isinstance(message, ElementBase):
|
206
|
-
error_el.append(message)
|
207
|
-
else:
|
208
|
-
assert isinstance(message, basestring), (
|
209
|
-
"Bad message; should be a string or element: %r" % message)
|
210
|
-
error_el.text = message or self.default_message
|
211
|
-
if is_block and self.block_inside:
|
212
|
-
if self.insert_before:
|
213
|
-
error_el.tail = el.text
|
214
|
-
el.text = None
|
215
|
-
el.insert(0, error_el)
|
216
|
-
else:
|
217
|
-
el.append(error_el)
|
218
|
-
else:
|
219
|
-
parent = el.getparent()
|
220
|
-
pos = parent.index(el)
|
221
|
-
if self.insert_before:
|
222
|
-
parent.insert(pos, error_el)
|
223
|
-
else:
|
224
|
-
error_el.tail = el.tail
|
225
|
-
el.tail = None
|
226
|
-
parent.insert(pos+1, error_el)
|
227
|
-
|
228
|
-
default_error_creator = DefaultErrorCreator()
|
229
|
-
|
230
|
-
|
231
|
-
def insert_errors(
|
232
|
-
el,
|
233
|
-
errors,
|
234
|
-
form_id=None,
|
235
|
-
form_index=None,
|
236
|
-
error_class="error",
|
237
|
-
error_creator=default_error_creator,
|
238
|
-
):
|
239
|
-
el = _find_form(el, form_id=form_id, form_index=form_index)
|
240
|
-
for name, error in errors.items():
|
241
|
-
if error is None:
|
242
|
-
continue
|
243
|
-
for error_el, message in _find_elements_for_name(el, name, error):
|
244
|
-
assert isinstance(message, (basestring, type(None), ElementBase)), (
|
245
|
-
"Bad message: %r" % message)
|
246
|
-
_insert_error(error_el, message, error_class, error_creator)
|
247
|
-
|
248
|
-
def insert_errors_html(html, values, **kw):
|
249
|
-
result_type = type(html)
|
250
|
-
if isinstance(html, basestring):
|
251
|
-
doc = fromstring(html)
|
252
|
-
else:
|
253
|
-
doc = copy.deepcopy(html)
|
254
|
-
insert_errors(doc, values, **kw)
|
255
|
-
return _transform_result(result_type, doc)
|
256
|
-
|
257
|
-
def _insert_error(el, error, error_class, error_creator):
|
258
|
-
if _nons(el.tag) in defs.empty_tags or _nons(el.tag) == 'textarea':
|
259
|
-
is_block = False
|
260
|
-
else:
|
261
|
-
is_block = True
|
262
|
-
if _nons(el.tag) != 'form' and error_class:
|
263
|
-
_add_class(el, error_class)
|
264
|
-
if el.get('id'):
|
265
|
-
labels = _label_for_xpath(el, for_id=el.get('id'))
|
266
|
-
if labels:
|
267
|
-
for label in labels:
|
268
|
-
_add_class(label, error_class)
|
269
|
-
error_creator(el, is_block, error)
|
270
|
-
|
271
|
-
def _add_class(el, class_name):
|
272
|
-
if el.get('class'):
|
273
|
-
el.set('class', el.get('class')+' '+class_name)
|
274
|
-
else:
|
275
|
-
el.set('class', class_name)
|
276
|
-
|
277
|
-
def _find_elements_for_name(form, name, error):
|
278
|
-
if name is None:
|
279
|
-
# An error for the entire form
|
280
|
-
yield form, error
|
281
|
-
return
|
282
|
-
if name.startswith('#'):
|
283
|
-
# By id
|
284
|
-
el = form.get_element_by_id(name[1:])
|
285
|
-
if el is not None:
|
286
|
-
yield el, error
|
287
|
-
return
|
288
|
-
els = _name_xpath(form, name=name)
|
289
|
-
if not els:
|
290
|
-
# FIXME: should this raise an exception?
|
291
|
-
return
|
292
|
-
if not isinstance(error, (list, tuple)):
|
293
|
-
yield els[0], error
|
294
|
-
return
|
295
|
-
# FIXME: if error is longer than els, should it raise an error?
|
296
|
-
for el, err in zip(els, error):
|
297
|
-
if err is None:
|
298
|
-
continue
|
299
|
-
yield el, err
|
1
|
+
from lxml.etree import XPath, ElementBase
|
2
|
+
from lxml.html import fromstring, XHTML_NAMESPACE
|
3
|
+
from lxml.html import _forms_xpath, _options_xpath, _nons, _transform_result
|
4
|
+
from lxml.html import defs
|
5
|
+
import copy
|
6
|
+
|
7
|
+
try:
|
8
|
+
basestring
|
9
|
+
except NameError:
|
10
|
+
# Python 3
|
11
|
+
basestring = str
|
12
|
+
|
13
|
+
__all__ = ['FormNotFound', 'fill_form', 'fill_form_html',
|
14
|
+
'insert_errors', 'insert_errors_html',
|
15
|
+
'DefaultErrorCreator']
|
16
|
+
|
17
|
+
class FormNotFound(LookupError):
|
18
|
+
"""
|
19
|
+
Raised when no form can be found
|
20
|
+
"""
|
21
|
+
|
22
|
+
_form_name_xpath = XPath('descendant-or-self::form[name=$name]|descendant-or-self::x:form[name=$name]', namespaces={'x':XHTML_NAMESPACE})
|
23
|
+
_input_xpath = XPath('|'.join(['descendant-or-self::'+_tag for _tag in ('input','select','textarea','x:input','x:select','x:textarea')]),
|
24
|
+
namespaces={'x':XHTML_NAMESPACE})
|
25
|
+
_label_for_xpath = XPath('//label[@for=$for_id]|//x:label[@for=$for_id]',
|
26
|
+
namespaces={'x':XHTML_NAMESPACE})
|
27
|
+
_name_xpath = XPath('descendant-or-self::*[@name=$name]')
|
28
|
+
|
29
|
+
def fill_form(
|
30
|
+
el,
|
31
|
+
values,
|
32
|
+
form_id=None,
|
33
|
+
form_index=None,
|
34
|
+
):
|
35
|
+
el = _find_form(el, form_id=form_id, form_index=form_index)
|
36
|
+
_fill_form(el, values)
|
37
|
+
|
38
|
+
def fill_form_html(html, values, form_id=None, form_index=None):
|
39
|
+
result_type = type(html)
|
40
|
+
if isinstance(html, basestring):
|
41
|
+
doc = fromstring(html)
|
42
|
+
else:
|
43
|
+
doc = copy.deepcopy(html)
|
44
|
+
fill_form(doc, values, form_id=form_id, form_index=form_index)
|
45
|
+
return _transform_result(result_type, doc)
|
46
|
+
|
47
|
+
def _fill_form(el, values):
|
48
|
+
counts = {}
|
49
|
+
if hasattr(values, 'mixed'):
|
50
|
+
# For Paste request parameters
|
51
|
+
values = values.mixed()
|
52
|
+
inputs = _input_xpath(el)
|
53
|
+
for input in inputs:
|
54
|
+
name = input.get('name')
|
55
|
+
if not name:
|
56
|
+
continue
|
57
|
+
if _takes_multiple(input):
|
58
|
+
value = values.get(name, [])
|
59
|
+
if not isinstance(value, (list, tuple)):
|
60
|
+
value = [value]
|
61
|
+
_fill_multiple(input, value)
|
62
|
+
elif name not in values:
|
63
|
+
continue
|
64
|
+
else:
|
65
|
+
index = counts.get(name, 0)
|
66
|
+
counts[name] = index + 1
|
67
|
+
value = values[name]
|
68
|
+
if isinstance(value, (list, tuple)):
|
69
|
+
try:
|
70
|
+
value = value[index]
|
71
|
+
except IndexError:
|
72
|
+
continue
|
73
|
+
elif index > 0:
|
74
|
+
continue
|
75
|
+
_fill_single(input, value)
|
76
|
+
|
77
|
+
def _takes_multiple(input):
|
78
|
+
if _nons(input.tag) == 'select' and input.get('multiple'):
|
79
|
+
# FIXME: multiple="0"?
|
80
|
+
return True
|
81
|
+
type = input.get('type', '').lower()
|
82
|
+
if type in ('radio', 'checkbox'):
|
83
|
+
return True
|
84
|
+
return False
|
85
|
+
|
86
|
+
def _fill_multiple(input, value):
|
87
|
+
type = input.get('type', '').lower()
|
88
|
+
if type == 'checkbox':
|
89
|
+
v = input.get('value')
|
90
|
+
if v is None:
|
91
|
+
if not value:
|
92
|
+
result = False
|
93
|
+
else:
|
94
|
+
result = value[0]
|
95
|
+
if isinstance(value, basestring):
|
96
|
+
# The only valid "on" value for an unnamed checkbox is 'on'
|
97
|
+
result = result == 'on'
|
98
|
+
_check(input, result)
|
99
|
+
else:
|
100
|
+
_check(input, v in value)
|
101
|
+
elif type == 'radio':
|
102
|
+
v = input.get('value')
|
103
|
+
_check(input, v in value)
|
104
|
+
else:
|
105
|
+
assert _nons(input.tag) == 'select'
|
106
|
+
for option in _options_xpath(input):
|
107
|
+
v = option.get('value')
|
108
|
+
if v is None:
|
109
|
+
# This seems to be the default, at least on IE
|
110
|
+
# FIXME: but I'm not sure
|
111
|
+
v = option.text_content()
|
112
|
+
_select(option, v in value)
|
113
|
+
|
114
|
+
def _check(el, check):
|
115
|
+
if check:
|
116
|
+
el.set('checked', '')
|
117
|
+
else:
|
118
|
+
if 'checked' in el.attrib:
|
119
|
+
del el.attrib['checked']
|
120
|
+
|
121
|
+
def _select(el, select):
|
122
|
+
if select:
|
123
|
+
el.set('selected', '')
|
124
|
+
else:
|
125
|
+
if 'selected' in el.attrib:
|
126
|
+
del el.attrib['selected']
|
127
|
+
|
128
|
+
def _fill_single(input, value):
|
129
|
+
if _nons(input.tag) == 'textarea':
|
130
|
+
input.text = value
|
131
|
+
else:
|
132
|
+
input.set('value', value)
|
133
|
+
|
134
|
+
def _find_form(el, form_id=None, form_index=None):
|
135
|
+
if form_id is None and form_index is None:
|
136
|
+
forms = _forms_xpath(el)
|
137
|
+
for form in forms:
|
138
|
+
return form
|
139
|
+
raise FormNotFound(
|
140
|
+
"No forms in page")
|
141
|
+
if form_id is not None:
|
142
|
+
form = el.get_element_by_id(form_id)
|
143
|
+
if form is not None:
|
144
|
+
return form
|
145
|
+
forms = _form_name_xpath(el, name=form_id)
|
146
|
+
if forms:
|
147
|
+
return forms[0]
|
148
|
+
else:
|
149
|
+
raise FormNotFound(
|
150
|
+
"No form with the name or id of %r (forms: %s)"
|
151
|
+
% (id, ', '.join(_find_form_ids(el))))
|
152
|
+
if form_index is not None:
|
153
|
+
forms = _forms_xpath(el)
|
154
|
+
try:
|
155
|
+
return forms[form_index]
|
156
|
+
except IndexError:
|
157
|
+
raise FormNotFound(
|
158
|
+
"There is no form with the index %r (%i forms found)"
|
159
|
+
% (form_index, len(forms)))
|
160
|
+
|
161
|
+
def _find_form_ids(el):
|
162
|
+
forms = _forms_xpath(el)
|
163
|
+
if not forms:
|
164
|
+
yield '(no forms)'
|
165
|
+
return
|
166
|
+
for index, form in enumerate(forms):
|
167
|
+
if form.get('id'):
|
168
|
+
if form.get('name'):
|
169
|
+
yield '%s or %s' % (form.get('id'),
|
170
|
+
form.get('name'))
|
171
|
+
else:
|
172
|
+
yield form.get('id')
|
173
|
+
elif form.get('name'):
|
174
|
+
yield form.get('name')
|
175
|
+
else:
|
176
|
+
yield '(unnamed form %s)' % index
|
177
|
+
|
178
|
+
############################################################
|
179
|
+
## Error filling
|
180
|
+
############################################################
|
181
|
+
|
182
|
+
class DefaultErrorCreator:
|
183
|
+
insert_before = True
|
184
|
+
block_inside = True
|
185
|
+
error_container_tag = 'div'
|
186
|
+
error_message_class = 'error-message'
|
187
|
+
error_block_class = 'error-block'
|
188
|
+
default_message = "Invalid"
|
189
|
+
|
190
|
+
def __init__(self, **kw):
|
191
|
+
for name, value in kw.items():
|
192
|
+
if not hasattr(self, name):
|
193
|
+
raise TypeError(
|
194
|
+
"Unexpected keyword argument: %s" % name)
|
195
|
+
setattr(self, name, value)
|
196
|
+
|
197
|
+
def __call__(self, el, is_block, message):
|
198
|
+
error_el = el.makeelement(self.error_container_tag)
|
199
|
+
if self.error_message_class:
|
200
|
+
error_el.set('class', self.error_message_class)
|
201
|
+
if is_block and self.error_block_class:
|
202
|
+
error_el.set('class', error_el.get('class', '')+' '+self.error_block_class)
|
203
|
+
if message is None or message == '':
|
204
|
+
message = self.default_message
|
205
|
+
if isinstance(message, ElementBase):
|
206
|
+
error_el.append(message)
|
207
|
+
else:
|
208
|
+
assert isinstance(message, basestring), (
|
209
|
+
"Bad message; should be a string or element: %r" % message)
|
210
|
+
error_el.text = message or self.default_message
|
211
|
+
if is_block and self.block_inside:
|
212
|
+
if self.insert_before:
|
213
|
+
error_el.tail = el.text
|
214
|
+
el.text = None
|
215
|
+
el.insert(0, error_el)
|
216
|
+
else:
|
217
|
+
el.append(error_el)
|
218
|
+
else:
|
219
|
+
parent = el.getparent()
|
220
|
+
pos = parent.index(el)
|
221
|
+
if self.insert_before:
|
222
|
+
parent.insert(pos, error_el)
|
223
|
+
else:
|
224
|
+
error_el.tail = el.tail
|
225
|
+
el.tail = None
|
226
|
+
parent.insert(pos+1, error_el)
|
227
|
+
|
228
|
+
default_error_creator = DefaultErrorCreator()
|
229
|
+
|
230
|
+
|
231
|
+
def insert_errors(
|
232
|
+
el,
|
233
|
+
errors,
|
234
|
+
form_id=None,
|
235
|
+
form_index=None,
|
236
|
+
error_class="error",
|
237
|
+
error_creator=default_error_creator,
|
238
|
+
):
|
239
|
+
el = _find_form(el, form_id=form_id, form_index=form_index)
|
240
|
+
for name, error in errors.items():
|
241
|
+
if error is None:
|
242
|
+
continue
|
243
|
+
for error_el, message in _find_elements_for_name(el, name, error):
|
244
|
+
assert isinstance(message, (basestring, type(None), ElementBase)), (
|
245
|
+
"Bad message: %r" % message)
|
246
|
+
_insert_error(error_el, message, error_class, error_creator)
|
247
|
+
|
248
|
+
def insert_errors_html(html, values, **kw):
|
249
|
+
result_type = type(html)
|
250
|
+
if isinstance(html, basestring):
|
251
|
+
doc = fromstring(html)
|
252
|
+
else:
|
253
|
+
doc = copy.deepcopy(html)
|
254
|
+
insert_errors(doc, values, **kw)
|
255
|
+
return _transform_result(result_type, doc)
|
256
|
+
|
257
|
+
def _insert_error(el, error, error_class, error_creator):
|
258
|
+
if _nons(el.tag) in defs.empty_tags or _nons(el.tag) == 'textarea':
|
259
|
+
is_block = False
|
260
|
+
else:
|
261
|
+
is_block = True
|
262
|
+
if _nons(el.tag) != 'form' and error_class:
|
263
|
+
_add_class(el, error_class)
|
264
|
+
if el.get('id'):
|
265
|
+
labels = _label_for_xpath(el, for_id=el.get('id'))
|
266
|
+
if labels:
|
267
|
+
for label in labels:
|
268
|
+
_add_class(label, error_class)
|
269
|
+
error_creator(el, is_block, error)
|
270
|
+
|
271
|
+
def _add_class(el, class_name):
|
272
|
+
if el.get('class'):
|
273
|
+
el.set('class', el.get('class')+' '+class_name)
|
274
|
+
else:
|
275
|
+
el.set('class', class_name)
|
276
|
+
|
277
|
+
def _find_elements_for_name(form, name, error):
|
278
|
+
if name is None:
|
279
|
+
# An error for the entire form
|
280
|
+
yield form, error
|
281
|
+
return
|
282
|
+
if name.startswith('#'):
|
283
|
+
# By id
|
284
|
+
el = form.get_element_by_id(name[1:])
|
285
|
+
if el is not None:
|
286
|
+
yield el, error
|
287
|
+
return
|
288
|
+
els = _name_xpath(form, name=name)
|
289
|
+
if not els:
|
290
|
+
# FIXME: should this raise an exception?
|
291
|
+
return
|
292
|
+
if not isinstance(error, (list, tuple)):
|
293
|
+
yield els[0], error
|
294
|
+
return
|
295
|
+
# FIXME: if error is longer than els, should it raise an error?
|
296
|
+
for el, err in zip(els, error):
|
297
|
+
if err is None:
|
298
|
+
continue
|
299
|
+
yield el, err
|