comparison env/lib/python3.7/site-packages/lxml/html/formfill.py @ 2:6af9afd405e9 draft

"planemo upload commit 0a63dd5f4d38a1f6944587f52a8cd79874177fc1"
author shellac
date Thu, 14 May 2020 14:56:58 -0400
parents 26e78fe6e8c4
children
comparison
equal deleted inserted replaced
1:75ca89e9b81c 2:6af9afd405e9
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(object):
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