Mercurial > repos > shellac > guppy_basecaller
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 |