Mercurial > libervia-backend
comparison sat/tools/xml_tools.py @ 4037:524856bd7b19
massive refactoring to switch from camelCase to snake_case:
historically, Libervia (SàT before) was using camelCase as allowed by PEP8 when using a
pre-PEP8 code, to use the same coding style as in Twisted.
However, snake_case is more readable and it's better to follow PEP8 best practices, so it
has been decided to move on full snake_case. Because Libervia has a huge codebase, this
ended with a ugly mix of camelCase and snake_case.
To fix that, this patch does a big refactoring by renaming every function and method
(including bridge) that are not coming from Twisted or Wokkel, to use fully snake_case.
This is a massive change, and may result in some bugs.
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 08 Apr 2023 13:54:42 +0200 |
parents | bb211f80c3e6 |
children | 2594e1951cf7 |
comparison
equal
deleted
inserted
replaced
4036:c4464d7ae97b | 4037:524856bd7b19 |
---|---|
45 html_entity_re = re.compile(r"&([a-zA-Z]+?);") | 45 html_entity_re = re.compile(r"&([a-zA-Z]+?);") |
46 XML_ENTITIES = ("quot", "amp", "apos", "lt", "gt") | 46 XML_ENTITIES = ("quot", "amp", "apos", "lt", "gt") |
47 | 47 |
48 # method to clean XHTML, receive raw unsecure XML or HTML, must return cleaned raw XHTML | 48 # method to clean XHTML, receive raw unsecure XML or HTML, must return cleaned raw XHTML |
49 # this method must be set during runtime | 49 # this method must be set during runtime |
50 cleanXHTML = None | 50 clean_xhtml = None |
51 | 51 |
52 # TODO: move XMLUI stuff in a separate module | 52 # TODO: move XMLUI stuff in a separate module |
53 # TODO: rewrite this with lxml or ElementTree or domish.Element: it's complicated and difficult to maintain with current minidom implementation | 53 # TODO: rewrite this with lxml or ElementTree or domish.Element: it's complicated and difficult to maintain with current minidom implementation |
54 | 54 |
55 # Helper functions | 55 # Helper functions |
56 | 56 |
57 | 57 |
58 def _dataFormField2XMLUIData(field, read_only=False): | 58 def _data_form_field_2_xmlui_data(field, read_only=False): |
59 """Get data needed to create an XMLUI's Widget from Wokkel's data_form's Field. | 59 """Get data needed to create an XMLUI's Widget from Wokkel's data_form's Field. |
60 | 60 |
61 The attribute field can be modified (if it's fixed and it has no value). | 61 The attribute field can be modified (if it's fixed and it has no value). |
62 @param field (data_form.Field): a field with attributes "value", "fieldType", | 62 @param field (data_form.Field): a field with attributes "value", "fieldType", |
63 "label" and "var" | 63 "label" and "var" |
142 if field.var: | 142 if field.var: |
143 widget_kwargs["name"] = field.var | 143 widget_kwargs["name"] = field.var |
144 | 144 |
145 return widget_type, widget_args, widget_kwargs | 145 return widget_type, widget_args, widget_kwargs |
146 | 146 |
147 def dataForm2Widgets(form_ui, form, read_only=False, prepend=None, filters=None): | 147 def data_form_2_widgets(form_ui, form, read_only=False, prepend=None, filters=None): |
148 """Complete an existing XMLUI with widget converted from XEP-0004 data forms. | 148 """Complete an existing XMLUI with widget converted from XEP-0004 data forms. |
149 | 149 |
150 @param form_ui (XMLUI): XMLUI instance | 150 @param form_ui (XMLUI): XMLUI instance |
151 @param form (data_form.Form): Wokkel's implementation of data form | 151 @param form (data_form.Form): Wokkel's implementation of data form |
152 @param read_only (bool): if True and it makes sense, create a read only input widget | 152 @param read_only (bool): if True and it makes sense, create a read only input widget |
153 @param prepend(iterable, None): widgets to prepend to main LabelContainer | 153 @param prepend(iterable, None): widgets to prepend to main LabelContainer |
154 if not None, must be an iterable of *args for addWidget. Those widgets will | 154 if not None, must be an iterable of *args for add_widget. Those widgets will |
155 be added first to the container. | 155 be added first to the container. |
156 @param filters(dict, None): if not None, a dictionary of callable: | 156 @param filters(dict, None): if not None, a dictionary of callable: |
157 key is the name of the widget to filter | 157 key is the name of the widget to filter |
158 the value is a callable, it will get form's XMLUI, widget's type, args and kwargs | 158 the value is a callable, it will get form's XMLUI, widget's type, args and kwargs |
159 and must return widget's type, args and kwargs (which can be modified) | 159 and must return widget's type, args and kwargs (which can be modified) |
163 if filters is None: | 163 if filters is None: |
164 filters = {} | 164 filters = {} |
165 if form.instructions: | 165 if form.instructions: |
166 form_ui.addText("\n".join(form.instructions), "instructions") | 166 form_ui.addText("\n".join(form.instructions), "instructions") |
167 | 167 |
168 form_ui.changeContainer("label") | 168 form_ui.change_container("label") |
169 | 169 |
170 if prepend is not None: | 170 if prepend is not None: |
171 for widget_args in prepend: | 171 for widget_args in prepend: |
172 form_ui.addWidget(*widget_args) | 172 form_ui.add_widget(*widget_args) |
173 | 173 |
174 for field in form.fieldList: | 174 for field in form.fieldList: |
175 widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData( | 175 widget_type, widget_args, widget_kwargs = _data_form_field_2_xmlui_data( |
176 field, read_only | 176 field, read_only |
177 ) | 177 ) |
178 try: | 178 try: |
179 widget_filter = filters[widget_kwargs["name"]] | 179 widget_filter = filters[widget_kwargs["name"]] |
180 except KeyError: | 180 except KeyError: |
188 if label: | 188 if label: |
189 form_ui.addLabel(label) | 189 form_ui.addLabel(label) |
190 else: | 190 else: |
191 form_ui.addEmpty() | 191 form_ui.addEmpty() |
192 | 192 |
193 form_ui.addWidget(widget_type, *widget_args, **widget_kwargs) | 193 form_ui.add_widget(widget_type, *widget_args, **widget_kwargs) |
194 | 194 |
195 return form_ui | 195 return form_ui |
196 | 196 |
197 | 197 |
198 def dataForm2XMLUI(form, submit_id, session_id=None, read_only=False): | 198 def data_form_2_xmlui(form, submit_id, session_id=None, read_only=False): |
199 """Take a data form (Wokkel's XEP-0004 implementation) and convert it to a SàT XMLUI. | 199 """Take a data form (Wokkel's XEP-0004 implementation) and convert it to a SàT XMLUI. |
200 | 200 |
201 @param form (data_form.Form): a Form instance | 201 @param form (data_form.Form): a Form instance |
202 @param submit_id (unicode): callback id to call when submitting form | 202 @param submit_id (unicode): callback id to call when submitting form |
203 @param session_id (unicode): session id to return with the data | 203 @param session_id (unicode): session id to return with the data |
204 @param read_only (bool): if True and it makes sense, create a read only input widget | 204 @param read_only (bool): if True and it makes sense, create a read only input widget |
205 @return: XMLUI instance | 205 @return: XMLUI instance |
206 """ | 206 """ |
207 form_ui = XMLUI("form", "vertical", submit_id=submit_id, session_id=session_id) | 207 form_ui = XMLUI("form", "vertical", submit_id=submit_id, session_id=session_id) |
208 return dataForm2Widgets(form_ui, form, read_only=read_only) | 208 return data_form_2_widgets(form_ui, form, read_only=read_only) |
209 | 209 |
210 | 210 |
211 def dataForm2dataDict(form: data_form.Form) -> dict: | 211 def data_form_2_data_dict(form: data_form.Form) -> dict: |
212 """Convert data form to a simple dict, easily serialisable | 212 """Convert data form to a simple dict, easily serialisable |
213 | 213 |
214 see dataDict2dataForm for a description of the format | 214 see data_dict_2_data_form for a description of the format |
215 """ | 215 """ |
216 fields = [] | 216 fields = [] |
217 data_dict = { | 217 data_dict = { |
218 "fields": fields | 218 "fields": fields |
219 } | 219 } |
251 f"{form_field.value.toXml()}" | 251 f"{form_field.value.toXml()}" |
252 ) | 252 ) |
253 return data_dict | 253 return data_dict |
254 | 254 |
255 | 255 |
256 def dataDict2dataForm(data_dict): | 256 def data_dict_2_data_form(data_dict): |
257 """Convert serialisable dict of data to a data form | 257 """Convert serialisable dict of data to a data form |
258 | 258 |
259 The format of the dict is as follow: | 259 The format of the dict is as follow: |
260 - an optional "namespace" key with form namespace | 260 - an optional "namespace" key with form namespace |
261 - a mandatory "fields" key with list of fields as follow: | 261 - a mandatory "fields" key with list of fields as follow: |
297 formNamespace=data_dict.get("namespace"), | 297 formNamespace=data_dict.get("namespace"), |
298 fields=fields | 298 fields=fields |
299 ) | 299 ) |
300 | 300 |
301 | 301 |
302 def dataFormEltResult2XMLUIData(form_xml): | 302 def data_form_elt_result_2_xmlui_data(form_xml): |
303 """Parse a data form result (not parsed by Wokkel's XEP-0004 implementation). | 303 """Parse a data form result (not parsed by Wokkel's XEP-0004 implementation). |
304 | 304 |
305 The raw data form is used because Wokkel doesn't manage result items parsing yet. | 305 The raw data form is used because Wokkel doesn't manage result items parsing yet. |
306 @param form_xml (domish.Element): element of the data form | 306 @param form_xml (domish.Element): element of the data form |
307 @return: a couple (headers, result_list): | 307 @return: a couple (headers, result_list): |
335 if elt.name != "field": | 335 if elt.name != "field": |
336 log.warning("Unexpected tag (%s)" % elt.name) | 336 log.warning("Unexpected tag (%s)" % elt.name) |
337 continue | 337 continue |
338 field = data_form.Field.fromElement(elt) | 338 field = data_form.Field.fromElement(elt) |
339 | 339 |
340 xmlui_data.append(_dataFormField2XMLUIData(field)) | 340 xmlui_data.append(_data_form_field_2_xmlui_data(field)) |
341 | 341 |
342 return headers, xmlui_data | 342 return headers, xmlui_data |
343 | 343 |
344 | 344 |
345 def XMLUIData2AdvancedList(xmlui, headers, xmlui_data): | 345 def xmlui_data_2_advanced_list(xmlui, headers, xmlui_data): |
346 """Take a raw data form result (not parsed by Wokkel's XEP-0004 implementation) and convert it to an advanced list. | 346 """Take a raw data form result (not parsed by Wokkel's XEP-0004 implementation) and convert it to an advanced list. |
347 | 347 |
348 The raw data form is used because Wokkel doesn't manage result items parsing yet. | 348 The raw data form is used because Wokkel doesn't manage result items parsing yet. |
349 @param xmlui (XMLUI): the XMLUI where the AdvancedList will be added | 349 @param xmlui (XMLUI): the XMLUI where the AdvancedList will be added |
350 @param headers (dict{unicode: unicode}): form headers (field labels and types) | 350 @param headers (dict{unicode: unicode}): form headers (field labels and types) |
352 @return: the completed XMLUI instance | 352 @return: the completed XMLUI instance |
353 """ | 353 """ |
354 adv_list = AdvancedListContainer( | 354 adv_list = AdvancedListContainer( |
355 xmlui, headers=headers, columns=len(headers), parent=xmlui.current_container | 355 xmlui, headers=headers, columns=len(headers), parent=xmlui.current_container |
356 ) | 356 ) |
357 xmlui.changeContainer(adv_list) | 357 xmlui.change_container(adv_list) |
358 | 358 |
359 for widget_type, widget_args, widget_kwargs in xmlui_data: | 359 for widget_type, widget_args, widget_kwargs in xmlui_data: |
360 xmlui.addWidget(widget_type, *widget_args, **widget_kwargs) | 360 xmlui.add_widget(widget_type, *widget_args, **widget_kwargs) |
361 | 361 |
362 return xmlui | 362 return xmlui |
363 | 363 |
364 | 364 |
365 def dataFormResult2AdvancedList(xmlui, form_xml): | 365 def data_form_result_2_advanced_list(xmlui, form_xml): |
366 """Take a raw data form result (not parsed by Wokkel's XEP-0004 implementation) and convert it to an advanced list. | 366 """Take a raw data form result (not parsed by Wokkel's XEP-0004 implementation) and convert it to an advanced list. |
367 | 367 |
368 The raw data form is used because Wokkel doesn't manage result items parsing yet. | 368 The raw data form is used because Wokkel doesn't manage result items parsing yet. |
369 @param xmlui (XMLUI): the XMLUI where the AdvancedList will be added | 369 @param xmlui (XMLUI): the XMLUI where the AdvancedList will be added |
370 @param form_xml (domish.Element): element of the data form | 370 @param form_xml (domish.Element): element of the data form |
371 @return: the completed XMLUI instance | 371 @return: the completed XMLUI instance |
372 """ | 372 """ |
373 headers, xmlui_data = dataFormEltResult2XMLUIData(form_xml) | 373 headers, xmlui_data = data_form_elt_result_2_xmlui_data(form_xml) |
374 XMLUIData2AdvancedList(xmlui, headers, xmlui_data) | 374 xmlui_data_2_advanced_list(xmlui, headers, xmlui_data) |
375 | 375 |
376 | 376 |
377 def dataFormEltResult2XMLUI(form_elt, session_id=None): | 377 def data_form_elt_result_2_xmlui(form_elt, session_id=None): |
378 """Take a raw data form (not parsed by XEP-0004) and convert it to a SàT XMLUI. | 378 """Take a raw data form (not parsed by XEP-0004) and convert it to a SàT XMLUI. |
379 | 379 |
380 The raw data form is used because Wokkel doesn't manage result items parsing yet. | 380 The raw data form is used because Wokkel doesn't manage result items parsing yet. |
381 @param form_elt (domish.Element): element of the data form | 381 @param form_elt (domish.Element): element of the data form |
382 @param session_id (unicode): session id to return with the data | 382 @param session_id (unicode): session id to return with the data |
383 @return: XMLUI instance | 383 @return: XMLUI instance |
384 """ | 384 """ |
385 xml_ui = XMLUI("window", "vertical", session_id=session_id) | 385 xml_ui = XMLUI("window", "vertical", session_id=session_id) |
386 try: | 386 try: |
387 dataFormResult2AdvancedList(xml_ui, form_elt) | 387 data_form_result_2_advanced_list(xml_ui, form_elt) |
388 except exceptions.DataError: | 388 except exceptions.DataError: |
389 parsed_form = data_form.Form.fromElement(form_elt) | 389 parsed_form = data_form.Form.fromElement(form_elt) |
390 dataForm2Widgets(xml_ui, parsed_form, read_only=True) | 390 data_form_2_widgets(xml_ui, parsed_form, read_only=True) |
391 return xml_ui | 391 return xml_ui |
392 | 392 |
393 | 393 |
394 def dataFormResult2XMLUI(result_form, base_form, session_id=None, prepend=None, | 394 def data_form_result_2_xmlui(result_form, base_form, session_id=None, prepend=None, |
395 filters=None, read_only=True): | 395 filters=None, read_only=True): |
396 """Convert data form result to SàT XMLUI. | 396 """Convert data form result to SàT XMLUI. |
397 | 397 |
398 @param result_form (data_form.Form): result form to convert | 398 @param result_form (data_form.Form): result form to convert |
399 @param base_form (data_form.Form): initial form (i.e. of form type "form") | 399 @param base_form (data_form.Form): initial form (i.e. of form type "form") |
400 this one is necessary to reconstruct options when needed (e.g. list elements) | 400 this one is necessary to reconstruct options when needed (e.g. list elements) |
401 @param session_id (unicode): session id to return with the data | 401 @param session_id (unicode): session id to return with the data |
402 @param prepend: same as for [dataForm2Widgets] | 402 @param prepend: same as for [data_form_2_widgets] |
403 @param filters: same as for [dataForm2Widgets] | 403 @param filters: same as for [data_form_2_widgets] |
404 @param read_only: same as for [dataForm2Widgets] | 404 @param read_only: same as for [data_form_2_widgets] |
405 @return: XMLUI instance | 405 @return: XMLUI instance |
406 """ | 406 """ |
407 # we deepcopy the form because _dataFormField2XMLUIData can modify the value | 407 # we deepcopy the form because _data_form_field_2_xmlui_data can modify the value |
408 # FIXME: check if it's really important, the only modified value seems to be | 408 # FIXME: check if it's really important, the only modified value seems to be |
409 # the replacement of None by "" on fixed fields | 409 # the replacement of None by "" on fixed fields |
410 # form = deepcopy(result_form) | 410 # form = deepcopy(result_form) |
411 form = result_form | 411 form = result_form |
412 for name, field in form.fields.items(): | 412 for name, field in form.fields.items(): |
414 base_field = base_form.fields[name] | 414 base_field = base_form.fields[name] |
415 except KeyError: | 415 except KeyError: |
416 continue | 416 continue |
417 field.options = base_field.options[:] | 417 field.options = base_field.options[:] |
418 xml_ui = XMLUI("window", "vertical", session_id=session_id) | 418 xml_ui = XMLUI("window", "vertical", session_id=session_id) |
419 dataForm2Widgets(xml_ui, form, read_only=read_only, prepend=prepend, filters=filters) | 419 data_form_2_widgets(xml_ui, form, read_only=read_only, prepend=prepend, filters=filters) |
420 return xml_ui | 420 return xml_ui |
421 | 421 |
422 | 422 |
423 def _cleanValue(value): | 423 def _clean_value(value): |
424 """Workaround method to avoid DBus types with D-Bus bridge. | 424 """Workaround method to avoid DBus types with D-Bus bridge. |
425 | 425 |
426 @param value: value to clean | 426 @param value: value to clean |
427 @return: value in a non DBus type (only clean string yet) | 427 @return: value in a non DBus type (only clean string yet) |
428 """ | 428 """ |
431 if isinstance(value, str): | 431 if isinstance(value, str): |
432 return str(value) | 432 return str(value) |
433 return value | 433 return value |
434 | 434 |
435 | 435 |
436 def XMLUIResult2DataFormResult(xmlui_data): | 436 def xmlui_result_2_data_form_result(xmlui_data): |
437 """ Extract form data from a XMLUI return. | 437 """ Extract form data from a XMLUI return. |
438 | 438 |
439 @param xmlui_data (dict): data returned by frontends for XMLUI form | 439 @param xmlui_data (dict): data returned by frontends for XMLUI form |
440 @return: dict of data usable by Wokkel's data form | 440 @return: dict of data usable by Wokkel's data form |
441 """ | 441 """ |
449 value = value.split('\n') | 449 value = value.split('\n') |
450 elif "\t" in value: | 450 elif "\t" in value: |
451 # FIXME: workaround to handle multiple values. Proper serialisation must | 451 # FIXME: workaround to handle multiple values. Proper serialisation must |
452 # be done in XMLUI | 452 # be done in XMLUI |
453 value = value.split("\t") | 453 value = value.split("\t") |
454 ret[key[len(SAT_FORM_PREFIX) :]] = _cleanValue(value) | 454 ret[key[len(SAT_FORM_PREFIX) :]] = _clean_value(value) |
455 return ret | 455 return ret |
456 | 456 |
457 | 457 |
458 def formEscape(name): | 458 def form_escape(name): |
459 """Return escaped name for forms. | 459 """Return escaped name for forms. |
460 | 460 |
461 @param name (unicode): form name | 461 @param name (unicode): form name |
462 @return: unicode | 462 @return: unicode |
463 """ | 463 """ |
464 return "%s%s" % (SAT_FORM_PREFIX, name) | 464 return "%s%s" % (SAT_FORM_PREFIX, name) |
465 | 465 |
466 | 466 |
467 def isXMLUICancelled(raw_xmlui): | 467 def is_xmlui_cancelled(raw_xmlui): |
468 """Tell if an XMLUI has been cancelled by checking raw XML""" | 468 """Tell if an XMLUI has been cancelled by checking raw XML""" |
469 return C.bool(raw_xmlui.get('cancelled', C.BOOL_FALSE)) | 469 return C.bool(raw_xmlui.get('cancelled', C.BOOL_FALSE)) |
470 | 470 |
471 | 471 |
472 def XMLUIResultToElt(xmlui_data): | 472 def xmlui_result_to_elt(xmlui_data): |
473 """Construct result domish.Element from XMLUI result. | 473 """Construct result domish.Element from XMLUI result. |
474 | 474 |
475 @param xmlui_data (dict): data returned by frontends for XMLUI form | 475 @param xmlui_data (dict): data returned by frontends for XMLUI form |
476 @return: domish.Element | 476 @return: domish.Element |
477 """ | 477 """ |
478 form = data_form.Form("submit") | 478 form = data_form.Form("submit") |
479 form.makeFields(XMLUIResult2DataFormResult(xmlui_data)) | 479 form.makeFields(xmlui_result_2_data_form_result(xmlui_data)) |
480 return form.toElement() | 480 return form.toElement() |
481 | 481 |
482 | 482 |
483 def tupleList2dataForm(values): | 483 def tuple_list_2_data_form(values): |
484 """Convert a list of tuples (name, value) to a wokkel submit data form. | 484 """Convert a list of tuples (name, value) to a wokkel submit data form. |
485 | 485 |
486 @param values (list): list of tuples | 486 @param values (list): list of tuples |
487 @return: data_form.Form | 487 @return: data_form.Form |
488 """ | 488 """ |
492 form.addField(field) | 492 form.addField(field) |
493 | 493 |
494 return form | 494 return form |
495 | 495 |
496 | 496 |
497 def paramsXML2XMLUI(xml): | 497 def params_xml_2_xmlui(xml): |
498 """Convert the XML for parameter to a SàT XML User Interface. | 498 """Convert the XML for parameter to a SàT XML User Interface. |
499 | 499 |
500 @param xml (unicode) | 500 @param xml (unicode) |
501 @return: XMLUI | 501 @return: XMLUI |
502 """ | 502 """ |
514 label = category.getAttribute("label") | 514 label = category.getAttribute("label") |
515 if not category_name: | 515 if not category_name: |
516 raise exceptions.DataError( | 516 raise exceptions.DataError( |
517 _("INTERNAL ERROR: params categories must have a name") | 517 _("INTERNAL ERROR: params categories must have a name") |
518 ) | 518 ) |
519 tabs_cont.addTab(category_name, label=label, container=LabelContainer) | 519 tabs_cont.add_tab(category_name, label=label, container=LabelContainer) |
520 for param in category.getElementsByTagName("param"): | 520 for param in category.getElementsByTagName("param"): |
521 widget_kwargs = {} | 521 widget_kwargs = {} |
522 | 522 |
523 param_name = param.getAttribute("name") | 523 param_name = param.getAttribute("name") |
524 param_label = param.getAttribute("label") | 524 param_label = param.getAttribute("label") |
528 | 528 |
529 value = param.getAttribute("value") or None | 529 value = param.getAttribute("value") or None |
530 callback_id = param.getAttribute("callback_id") or None | 530 callback_id = param.getAttribute("callback_id") or None |
531 | 531 |
532 if type_ == "list": | 532 if type_ == "list": |
533 options, selected = _paramsGetListOptions(param) | 533 options, selected = _params_get_list_options(param) |
534 widget_kwargs["options"] = options | 534 widget_kwargs["options"] = options |
535 widget_kwargs["selected"] = selected | 535 widget_kwargs["selected"] = selected |
536 widget_kwargs["styles"] = ["extensible"] | 536 widget_kwargs["styles"] = ["extensible"] |
537 elif type_ == "jids_list": | 537 elif type_ == "jids_list": |
538 widget_kwargs["jids"] = _paramsGetListJids(param) | 538 widget_kwargs["jids"] = _params_get_list_jids(param) |
539 | 539 |
540 if type_ in ("button", "text"): | 540 if type_ in ("button", "text"): |
541 param_ui.addEmpty() | 541 param_ui.addEmpty() |
542 value = param_label | 542 value = param_label |
543 else: | 543 else: |
560 category_name, | 560 category_name, |
561 SAT_PARAM_SEPARATOR, | 561 SAT_PARAM_SEPARATOR, |
562 param_name, | 562 param_name, |
563 ) | 563 ) |
564 | 564 |
565 param_ui.addWidget(type_, **widget_kwargs) | 565 param_ui.add_widget(type_, **widget_kwargs) |
566 | 566 |
567 return param_ui.toXml() | 567 return param_ui.toXml() |
568 | 568 |
569 | 569 |
570 def _paramsGetListOptions(param): | 570 def _params_get_list_options(param): |
571 """Retrieve the options for list element. | 571 """Retrieve the options for list element. |
572 | 572 |
573 The <option/> tags must be direct children of <param/>. | 573 The <option/> tags must be direct children of <param/>. |
574 @param param (domish.Element): element | 574 @param param (domish.Element): element |
575 @return: a tuple (options, selected_value) | 575 @return: a tuple (options, selected_value) |
597 if elem.getAttribute("selected") == "true" | 597 if elem.getAttribute("selected") == "true" |
598 ] | 598 ] |
599 return (options, selected) | 599 return (options, selected) |
600 | 600 |
601 | 601 |
602 def _paramsGetListJids(param): | 602 def _params_get_list_jids(param): |
603 """Retrive jids from a jids_list element. | 603 """Retrive jids from a jids_list element. |
604 | 604 |
605 the <jid/> tags must be direct children of <param/> | 605 the <jid/> tags must be direct children of <param/> |
606 @param param (domish.Element): element | 606 @param param (domish.Element): element |
607 @return: a list of jids | 607 @return: a list of jids |
677 raise exceptions.DataError(_("TabElement must be a child of TabsContainer")) | 677 raise exceptions.DataError(_("TabElement must be a child of TabsContainer")) |
678 super(TabElement, self).__init__(parent.xmlui, parent) | 678 super(TabElement, self).__init__(parent.xmlui, parent) |
679 self.elem.setAttribute("name", name) | 679 self.elem.setAttribute("name", name) |
680 self.elem.setAttribute("label", label) | 680 self.elem.setAttribute("label", label) |
681 if selected: | 681 if selected: |
682 self.setSelected(selected) | 682 self.set_selected(selected) |
683 | 683 |
684 def setSelected(self, selected=False): | 684 def set_selected(self, selected=False): |
685 """Set the tab selected. | 685 """Set the tab selected. |
686 | 686 |
687 @param selected (bool): set to True to select this tab | 687 @param selected (bool): set to True to select this tab |
688 """ | 688 """ |
689 self.elem.setAttribute("selected", "true" if selected else "false") | 689 self.elem.setAttribute("selected", "true" if selected else "false") |
822 """ | 822 """ |
823 self.elem = xmlui.doc.createElement("container") | 823 self.elem = xmlui.doc.createElement("container") |
824 super(Container, self).__init__(xmlui, parent) | 824 super(Container, self).__init__(xmlui, parent) |
825 self.elem.setAttribute("type", self.type) | 825 self.elem.setAttribute("type", self.type) |
826 | 826 |
827 def getParentContainer(self): | 827 def get_parent_container(self): |
828 """ Return first parent container | 828 """ Return first parent container |
829 | 829 |
830 @return: parent container or None | 830 @return: parent container or None |
831 """ | 831 """ |
832 current = self.parent | 832 current = self.parent |
854 | 854 |
855 | 855 |
856 class TabsContainer(Container): | 856 class TabsContainer(Container): |
857 type = "tabs" | 857 type = "tabs" |
858 | 858 |
859 def addTab(self, name, label=None, selected=None, container=VerticalContainer): | 859 def add_tab(self, name, label=None, selected=None, container=VerticalContainer): |
860 """Add a tab. | 860 """Add a tab. |
861 | 861 |
862 @param name (unicode): tab name | 862 @param name (unicode): tab name |
863 @param label (unicode): tab label | 863 @param label (unicode): tab label |
864 @param selected (bool): set to True to select this tab | 864 @param selected (bool): set to True to select this tab |
867 """ | 867 """ |
868 if not label: | 868 if not label: |
869 label = name | 869 label = name |
870 tab_elt = TabElement(self, name, label, selected) | 870 tab_elt = TabElement(self, name, label, selected) |
871 new_container = container(self.xmlui, tab_elt) | 871 new_container = container(self.xmlui, tab_elt) |
872 return self.xmlui.changeContainer(new_container) | 872 return self.xmlui.change_container(new_container) |
873 | 873 |
874 def end(self): | 874 def end(self): |
875 """ Called when we have finished tabs | 875 """ Called when we have finished tabs |
876 | 876 |
877 change current container to first container parent | 877 change current container to first container parent |
878 """ | 878 """ |
879 parent_container = self.getParentContainer() | 879 parent_container = self.get_parent_container() |
880 self.xmlui.changeContainer(parent_container) | 880 self.xmlui.change_container(parent_container) |
881 | 881 |
882 | 882 |
883 class AdvancedListContainer(Container): | 883 class AdvancedListContainer(Container): |
884 """A list which can contain other widgets, headers, etc""" | 884 """A list which can contain other widgets, headers, etc""" |
885 | 885 |
926 if headers: | 926 if headers: |
927 if len(headers) != self._columns: | 927 if len(headers) != self._columns: |
928 raise exceptions.DataError( | 928 raise exceptions.DataError( |
929 _("Headers lenght doesn't correspond to columns") | 929 _("Headers lenght doesn't correspond to columns") |
930 ) | 930 ) |
931 self.addHeaders(headers) | 931 self.add_headers(headers) |
932 if items: | 932 if items: |
933 self.addItems(items) | 933 self.add_items(items) |
934 self.elem.setAttribute("columns", str(self._columns)) | 934 self.elem.setAttribute("columns", str(self._columns)) |
935 if callback_id is not None: | 935 if callback_id is not None: |
936 self.elem.setAttribute("callback", callback_id) | 936 self.elem.setAttribute("callback", callback_id) |
937 self.elem.setAttribute("selectable", selectable) | 937 self.elem.setAttribute("selectable", selectable) |
938 self.auto_index = auto_index | 938 self.auto_index = auto_index |
939 if auto_index: | 939 if auto_index: |
940 self.elem.setAttribute("auto_index", "true") | 940 self.elem.setAttribute("auto_index", "true") |
941 self.next_row_idx = None | 941 self.next_row_idx = None |
942 | 942 |
943 def addHeaders(self, headers): | 943 def add_headers(self, headers): |
944 for header in headers: | 944 for header in headers: |
945 self.addHeader(header) | 945 self.addHeader(header) |
946 | 946 |
947 def addHeader(self, header): | 947 def addHeader(self, header): |
948 pass # TODO | 948 pass # TODO |
949 | 949 |
950 def addItems(self, items): | 950 def add_items(self, items): |
951 for item in items: | 951 for item in items: |
952 self.append(item) | 952 self.append(item) |
953 | 953 |
954 def setRowIndex(self, idx): | 954 def set_row_index(self, idx): |
955 """ Set index for next row | 955 """ Set index for next row |
956 | 956 |
957 index are returned when a row is selected, in data's "index" key | 957 index are returned when a row is selected, in data's "index" key |
958 @param idx: string index to associate to the next row | 958 @param idx: string index to associate to the next row |
959 """ | 959 """ |
972 | 972 |
973 change current container to first container parent | 973 change current container to first container parent |
974 """ | 974 """ |
975 if self._item_idx % self._columns != 0: | 975 if self._item_idx % self._columns != 0: |
976 raise exceptions.DataError(_("Incorrect number of items in list")) | 976 raise exceptions.DataError(_("Incorrect number of items in list")) |
977 parent_container = self.getParentContainer() | 977 parent_container = self.get_parent_container() |
978 self.xmlui.changeContainer(parent_container) | 978 self.xmlui.change_container(parent_container) |
979 | 979 |
980 | 980 |
981 ## Widgets ## | 981 ## Widgets ## |
982 | 982 |
983 | 983 |
1002 ) | 1002 ) |
1003 ) | 1003 ) |
1004 xmlui.named_widgets[name] = self | 1004 xmlui.named_widgets[name] = self |
1005 self.elem.setAttribute("type", self.type) | 1005 self.elem.setAttribute("type", self.type) |
1006 | 1006 |
1007 def setInternalCallback(self, callback, fields, data_elts=None): | 1007 def set_internal_callback(self, callback, fields, data_elts=None): |
1008 """Set an internal UI callback when the widget value is changed. | 1008 """Set an internal UI callback when the widget value is changed. |
1009 | 1009 |
1010 The internal callbacks are NO callback ids, they are strings from | 1010 The internal callbacks are NO callback ids, they are strings from |
1011 a predefined set of actions that are running in the scope of XMLUI. | 1011 a predefined set of actions that are running in the scope of XMLUI. |
1012 @param callback (string): a value from: | 1012 @param callback (string): a value from: |
1167 @param clean(bool): if True, the XHTML is considered insecure and will be cleaned | 1167 @param clean(bool): if True, the XHTML is considered insecure and will be cleaned |
1168 Only set to False if you are absolutely sure that the XHTML is safe (in other | 1168 Only set to False if you are absolutely sure that the XHTML is safe (in other |
1169 word, set to False only if you made the XHTML yourself) | 1169 word, set to False only if you made the XHTML yourself) |
1170 """ | 1170 """ |
1171 if clean: | 1171 if clean: |
1172 if cleanXHTML is None: | 1172 if clean_xhtml is None: |
1173 raise exceptions.NotFound( | 1173 raise exceptions.NotFound( |
1174 "No cleaning method set, can't clean the XHTML") | 1174 "No cleaning method set, can't clean the XHTML") |
1175 value = cleanXHTML(value) | 1175 value = clean_xhtml(value) |
1176 | 1176 |
1177 super(XHTMLBoxWidget, self).__init__( | 1177 super(XHTMLBoxWidget, self).__init__( |
1178 xmlui, value=value, name=name, parent=parent, read_only=read_only) | 1178 xmlui, value=value, name=name, parent=parent, read_only=read_only) |
1179 | 1179 |
1180 | 1180 |
1277 # we can have no options if we get a submitted data form | 1277 # we can have no options if we get a submitted data form |
1278 # but we can't use submitted values directly, | 1278 # but we can't use submitted values directly, |
1279 # because we would not have the labels | 1279 # because we would not have the labels |
1280 log.warning(_('empty "options" list')) | 1280 log.warning(_('empty "options" list')) |
1281 super(ListWidget, self).__init__(xmlui, name, parent) | 1281 super(ListWidget, self).__init__(xmlui, name, parent) |
1282 self.addOptions(options, selected) | 1282 self.add_options(options, selected) |
1283 self.setStyles(styles) | 1283 self.set_styles(styles) |
1284 | 1284 |
1285 def addOptions(self, options, selected=None): | 1285 def add_options(self, options, selected=None): |
1286 """Add options to a multi-values element (e.g. list) """ | 1286 """Add options to a multi-values element (e.g. list) """ |
1287 if selected: | 1287 if selected: |
1288 if isinstance(selected, str): | 1288 if isinstance(selected, str): |
1289 selected = [selected] | 1289 selected = [selected] |
1290 else: | 1290 else: |
1292 for option in options: | 1292 for option in options: |
1293 assert isinstance(option, str) or isinstance(option, tuple) | 1293 assert isinstance(option, str) or isinstance(option, tuple) |
1294 value = option if isinstance(option, str) else option[0] | 1294 value = option if isinstance(option, str) else option[0] |
1295 OptionElement(self, option, value in selected) | 1295 OptionElement(self, option, value in selected) |
1296 | 1296 |
1297 def setStyles(self, styles): | 1297 def set_styles(self, styles): |
1298 if not styles.issubset(self.STYLES): | 1298 if not styles.issubset(self.STYLES): |
1299 raise exceptions.DataError(_("invalid styles")) | 1299 raise exceptions.DataError(_("invalid styles")) |
1300 for style in styles: | 1300 for style in styles: |
1301 self.elem.setAttribute(style, "yes") | 1301 self.elem.setAttribute(style, "yes") |
1302 # TODO: check flags incompatibily (noselect and multi) like in __init__ | 1302 # TODO: check flags incompatibily (noselect and multi) like in __init__ |
1303 | 1303 |
1304 def setStyle(self, style): | 1304 def setStyle(self, style): |
1305 self.setStyles([style]) | 1305 self.set_styles([style]) |
1306 | 1306 |
1307 @property | 1307 @property |
1308 def value(self): | 1308 def value(self): |
1309 """Return the value of first selected option""" | 1309 """Return the value of first selected option""" |
1310 for child in self.elem.childNodes: | 1310 for child in self.elem.childNodes: |
1334 for style in styles: | 1334 for style in styles: |
1335 self.elem.setAttribute(style, "yes") | 1335 self.elem.setAttribute(style, "yes") |
1336 if not jids: | 1336 if not jids: |
1337 log.debug("empty jids list") | 1337 log.debug("empty jids list") |
1338 else: | 1338 else: |
1339 self.addJids(jids) | 1339 self.add_jids(jids) |
1340 | 1340 |
1341 def addJids(self, jids): | 1341 def add_jids(self, jids): |
1342 for jid_ in jids: | 1342 for jid_ in jids: |
1343 JidElement(self, jid_) | 1343 JidElement(self, jid_) |
1344 | 1344 |
1345 | 1345 |
1346 ## Dialog Elements ## | 1346 ## Dialog Elements ## |
1497 self.submit_id = submit_id | 1497 self.submit_id = submit_id |
1498 self.session_id = session_id | 1498 self.session_id = session_id |
1499 if panel_type == C.XMLUI_DIALOG: | 1499 if panel_type == C.XMLUI_DIALOG: |
1500 if dialog_opt is None: | 1500 if dialog_opt is None: |
1501 dialog_opt = {} | 1501 dialog_opt = {} |
1502 self._createDialog(dialog_opt) | 1502 self._create_dialog(dialog_opt) |
1503 return | 1503 return |
1504 self.main_container = self._createContainer(container, TopElement(self)) | 1504 self.main_container = self._create_container(container, TopElement(self)) |
1505 self.current_container = self.main_container | 1505 self.current_container = self.main_container |
1506 self.named_widgets = {} | 1506 self.named_widgets = {} |
1507 | 1507 |
1508 @staticmethod | 1508 @staticmethod |
1509 def creatorWrapper(widget_cls, is_input): | 1509 def creator_wrapper(widget_cls, is_input): |
1510 # TODO: once moved to Python 3, use functools.partialmethod and | 1510 # TODO: once moved to Python 3, use functools.partialmethod and |
1511 # remove the creatorWrapper | 1511 # remove the creator_wrapper |
1512 def createWidget(self, *args, **kwargs): | 1512 def create_widget(self, *args, **kwargs): |
1513 if self.type == C.XMLUI_DIALOG: | 1513 if self.type == C.XMLUI_DIALOG: |
1514 raise exceptions.InternalError(_( | 1514 raise exceptions.InternalError(_( |
1515 "createWidget can't be used with dialogs")) | 1515 "create_widget can't be used with dialogs")) |
1516 if "parent" not in kwargs: | 1516 if "parent" not in kwargs: |
1517 kwargs["parent"] = self.current_container | 1517 kwargs["parent"] = self.current_container |
1518 if "name" not in kwargs and is_input: | 1518 if "name" not in kwargs and is_input: |
1519 # name can be given as first argument or in keyword | 1519 # name can be given as first argument or in keyword |
1520 # arguments for InputWidgets | 1520 # arguments for InputWidgets |
1521 args = list(args) | 1521 args = list(args) |
1522 kwargs["name"] = args.pop(0) | 1522 kwargs["name"] = args.pop(0) |
1523 return widget_cls(self, *args, **kwargs) | 1523 return widget_cls(self, *args, **kwargs) |
1524 return createWidget | 1524 return create_widget |
1525 | 1525 |
1526 @classmethod | 1526 @classmethod |
1527 def _introspect(cls): | 1527 def _introspect(cls): |
1528 """ Introspect module to find Widgets and Containers, and create addXXX methods""" | 1528 """ Introspect module to find Widgets and Containers, and create addXXX methods""" |
1529 # FIXME: we can't log anything because this file is used | 1529 # FIXME: we can't log anything because this file is used |
1545 # FIXME: cf. above comment | 1545 # FIXME: cf. above comment |
1546 # log.debug(u"Adding {creator_name} creator (is_input={is_input}))" | 1546 # log.debug(u"Adding {creator_name} creator (is_input={is_input}))" |
1547 # .format(creator_name=creator_name, is_input=is_input)) | 1547 # .format(creator_name=creator_name, is_input=is_input)) |
1548 | 1548 |
1549 assert not hasattr(cls, creator_name) | 1549 assert not hasattr(cls, creator_name) |
1550 # XXX: we need to use creatorWrapper because we are in a loop | 1550 # XXX: we need to use creator_wrapper because we are in a loop |
1551 # and Python 2 doesn't support default values in kwargs | 1551 # and Python 2 doesn't support default values in kwargs |
1552 # when using *args, **kwargs | 1552 # when using *args, **kwargs |
1553 setattr(cls, creator_name, cls.creatorWrapper(obj, is_input)) | 1553 setattr(cls, creator_name, cls.creator_wrapper(obj, is_input)) |
1554 | 1554 |
1555 elif issubclass(obj, Container): | 1555 elif issubclass(obj, Container): |
1556 if obj.__name__ == "Container": | 1556 if obj.__name__ == "Container": |
1557 continue | 1557 continue |
1558 cls._containers[obj.type] = obj | 1558 cls._containers[obj.type] = obj |
1600 elif value: | 1600 elif value: |
1601 top_element.setAttribute("session_id", value) | 1601 top_element.setAttribute("session_id", value) |
1602 else: | 1602 else: |
1603 raise exceptions.DataError("session_id can't be empty") | 1603 raise exceptions.DataError("session_id can't be empty") |
1604 | 1604 |
1605 def _createDialog(self, dialog_opt): | 1605 def _create_dialog(self, dialog_opt): |
1606 dialog_type = dialog_opt.setdefault(C.XMLUI_DATA_TYPE, C.XMLUI_DIALOG_MESSAGE) | 1606 dialog_type = dialog_opt.setdefault(C.XMLUI_DATA_TYPE, C.XMLUI_DIALOG_MESSAGE) |
1607 if ( | 1607 if ( |
1608 dialog_type in [C.XMLUI_DIALOG_CONFIRM, C.XMLUI_DIALOG_FILE] | 1608 dialog_type in [C.XMLUI_DIALOG_CONFIRM, C.XMLUI_DIALOG_FILE] |
1609 and self.submit_id is None | 1609 and self.submit_id is None |
1610 ): | 1610 ): |
1628 try: | 1628 try: |
1629 FileElement(dialog_elt, dialog_opt[C.XMLUI_DATA_FILETYPE]) | 1629 FileElement(dialog_elt, dialog_opt[C.XMLUI_DATA_FILETYPE]) |
1630 except KeyError: | 1630 except KeyError: |
1631 pass | 1631 pass |
1632 | 1632 |
1633 def _createContainer(self, container, parent=None, **kwargs): | 1633 def _create_container(self, container, parent=None, **kwargs): |
1634 """Create a container element | 1634 """Create a container element |
1635 | 1635 |
1636 @param type: container type (cf init doc) | 1636 @param type: container type (cf init doc) |
1637 @parent: parent element or None | 1637 @parent: parent element or None |
1638 """ | 1638 """ |
1640 raise exceptions.DataError(_("Unknown container type [%s]") % container) | 1640 raise exceptions.DataError(_("Unknown container type [%s]") % container) |
1641 cls = self._containers[container] | 1641 cls = self._containers[container] |
1642 new_container = cls(self, parent=parent, **kwargs) | 1642 new_container = cls(self, parent=parent, **kwargs) |
1643 return new_container | 1643 return new_container |
1644 | 1644 |
1645 def changeContainer(self, container, **kwargs): | 1645 def change_container(self, container, **kwargs): |
1646 """Change the current container | 1646 """Change the current container |
1647 | 1647 |
1648 @param container: either container type (container it then created), | 1648 @param container: either container type (container it then created), |
1649 or an Container instance""" | 1649 or an Container instance""" |
1650 if isinstance(container, str): | 1650 if isinstance(container, str): |
1651 self.current_container = self._createContainer( | 1651 self.current_container = self._create_container( |
1652 container, | 1652 container, |
1653 self.current_container.getParentContainer() or self.main_container, | 1653 self.current_container.get_parent_container() or self.main_container, |
1654 **kwargs | 1654 **kwargs |
1655 ) | 1655 ) |
1656 else: | 1656 else: |
1657 self.current_container = ( | 1657 self.current_container = ( |
1658 self.main_container if container is None else container | 1658 self.main_container if container is None else container |
1659 ) | 1659 ) |
1660 assert isinstance(self.current_container, Container) | 1660 assert isinstance(self.current_container, Container) |
1661 return self.current_container | 1661 return self.current_container |
1662 | 1662 |
1663 def addWidget(self, type_, *args, **kwargs): | 1663 def add_widget(self, type_, *args, **kwargs): |
1664 """Convenience method to add an element""" | 1664 """Convenience method to add an element""" |
1665 if "parent" not in kwargs: | 1665 if "parent" not in kwargs: |
1666 kwargs["parent"] = self.current_container | 1666 kwargs["parent"] = self.current_container |
1667 try: | 1667 try: |
1668 cls = self._widgets[type_] | 1668 cls = self._widgets[type_] |
1700 title=title, | 1700 title=title, |
1701 ) | 1701 ) |
1702 return note_xmlui | 1702 return note_xmlui |
1703 | 1703 |
1704 | 1704 |
1705 def quickNote(host, client, message, title="", level=C.XMLUI_DATA_LVL_INFO): | 1705 def quick_note(host, client, message, title="", level=C.XMLUI_DATA_LVL_INFO): |
1706 """more sugar to do the whole note process""" | 1706 """more sugar to do the whole note process""" |
1707 note_ui = note(message, title, level) | 1707 note_ui = note(message, title, level) |
1708 host.actionNew({"xmlui": note_ui.toXml()}, profile=client.profile) | 1708 host.action_new({"xmlui": note_ui.toXml()}, profile=client.profile) |
1709 | 1709 |
1710 | 1710 |
1711 def deferredUI(host, xmlui, chained=False): | 1711 def deferred_ui(host, xmlui, chained=False): |
1712 """create a deferred linked to XMLUI | 1712 """create a deferred linked to XMLUI |
1713 | 1713 |
1714 @param xmlui(XMLUI): instance of the XMLUI | 1714 @param xmlui(XMLUI): instance of the XMLUI |
1715 Must be an XMLUI that you can submit, with submit_id set to '' | 1715 Must be an XMLUI that you can submit, with submit_id set to '' |
1716 @param chained(bool): True if the Deferred result must be returned to the frontend | 1716 @param chained(bool): True if the Deferred result must be returned to the frontend |
1718 @return (D(data)): a deferred which fire the data | 1718 @return (D(data)): a deferred which fire the data |
1719 """ | 1719 """ |
1720 assert xmlui.submit_id == "" | 1720 assert xmlui.submit_id == "" |
1721 xmlui_d = defer.Deferred() | 1721 xmlui_d = defer.Deferred() |
1722 | 1722 |
1723 def onSubmit(data, profile): | 1723 def on_submit(data, profile): |
1724 xmlui_d.callback(data) | 1724 xmlui_d.callback(data) |
1725 return xmlui_d if chained else {} | 1725 return xmlui_d if chained else {} |
1726 | 1726 |
1727 xmlui.submit_id = host.registerCallback(onSubmit, with_data=True, one_shot=True) | 1727 xmlui.submit_id = host.register_callback(on_submit, with_data=True, one_shot=True) |
1728 return xmlui_d | 1728 return xmlui_d |
1729 | 1729 |
1730 | 1730 |
1731 def deferXMLUI(host, xmlui, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, | 1731 def defer_xmlui(host, xmlui, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, |
1732 chained=False, profile=C.PROF_KEY_NONE): | 1732 chained=False, profile=C.PROF_KEY_NONE): |
1733 """Create a deferred linked to XMLUI | 1733 """Create a deferred linked to XMLUI |
1734 | 1734 |
1735 @param xmlui(XMLUI): instance of the XMLUI | 1735 @param xmlui(XMLUI): instance of the XMLUI |
1736 Must be an XMLUI that you can submit, with submit_id set to '' | 1736 Must be an XMLUI that you can submit, with submit_id set to '' |
1737 @param profile: %(doc_profile)s | 1737 @param profile: %(doc_profile)s |
1738 @param action_extra(None, dict): extra action to merge with xmlui | 1738 @param action_extra(None, dict): extra action to merge with xmlui |
1739 mainly used to add meta informations (see actionNew doc) | 1739 mainly used to add meta informations (see action_new doc) |
1740 @param security_limit: %(doc_security_limit)s | 1740 @param security_limit: %(doc_security_limit)s |
1741 @param chained(bool): True if the Deferred result must be returned to the frontend | 1741 @param chained(bool): True if the Deferred result must be returned to the frontend |
1742 useful when backend is in a series of dialogs with an ui | 1742 useful when backend is in a series of dialogs with an ui |
1743 @return (data): a deferred which fire the data | 1743 @return (data): a deferred which fire the data |
1744 """ | 1744 """ |
1745 xmlui_d = deferredUI(host, xmlui, chained) | 1745 xmlui_d = deferred_ui(host, xmlui, chained) |
1746 action_data = {"xmlui": xmlui.toXml()} | 1746 action_data = {"xmlui": xmlui.toXml()} |
1747 if action_extra is not None: | 1747 if action_extra is not None: |
1748 action_data.update(action_extra) | 1748 action_data.update(action_extra) |
1749 host.actionNew( | 1749 host.action_new( |
1750 action_data, | 1750 action_data, |
1751 security_limit=security_limit, | 1751 security_limit=security_limit, |
1752 keep_id=xmlui.submit_id, | 1752 keep_id=xmlui.submit_id, |
1753 profile=profile, | 1753 profile=profile, |
1754 ) | 1754 ) |
1755 return xmlui_d | 1755 return xmlui_d |
1756 | 1756 |
1757 | 1757 |
1758 def deferDialog(host, message, title="Please confirm", type_=C.XMLUI_DIALOG_CONFIRM, | 1758 def defer_dialog(host, message, title="Please confirm", type_=C.XMLUI_DIALOG_CONFIRM, |
1759 options=None, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False, | 1759 options=None, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False, |
1760 profile=C.PROF_KEY_NONE): | 1760 profile=C.PROF_KEY_NONE): |
1761 """Create a submitable dialog and manage it with a deferred | 1761 """Create a submitable dialog and manage it with a deferred |
1762 | 1762 |
1763 @param message(unicode): message to display | 1763 @param message(unicode): message to display |
1764 @param title(unicode): title of the dialog | 1764 @param title(unicode): title of the dialog |
1765 @param type(unicode): dialog type (C.XMLUI_DIALOG_*) | 1765 @param type(unicode): dialog type (C.XMLUI_DIALOG_*) |
1766 @param options(None, dict): if not None, will be used to update (extend) dialog_opt | 1766 @param options(None, dict): if not None, will be used to update (extend) dialog_opt |
1767 arguments of XMLUI | 1767 arguments of XMLUI |
1768 @param action_extra(None, dict): extra action to merge with xmlui | 1768 @param action_extra(None, dict): extra action to merge with xmlui |
1769 mainly used to add meta informations (see actionNew doc) | 1769 mainly used to add meta informations (see action_new doc) |
1770 @param security_limit: %(doc_security_limit)s | 1770 @param security_limit: %(doc_security_limit)s |
1771 @param chained(bool): True if the Deferred result must be returned to the frontend | 1771 @param chained(bool): True if the Deferred result must be returned to the frontend |
1772 useful when backend is in a series of dialogs with an ui | 1772 useful when backend is in a series of dialogs with an ui |
1773 @param profile: %(doc_profile)s | 1773 @param profile: %(doc_profile)s |
1774 @return (dict): Deferred dict | 1774 @return (dict): Deferred dict |
1776 assert profile is not None | 1776 assert profile is not None |
1777 dialog_opt = {"type": type_, "message": message} | 1777 dialog_opt = {"type": type_, "message": message} |
1778 if options is not None: | 1778 if options is not None: |
1779 dialog_opt.update(options) | 1779 dialog_opt.update(options) |
1780 dialog = XMLUI(C.XMLUI_DIALOG, title=title, dialog_opt=dialog_opt, submit_id="") | 1780 dialog = XMLUI(C.XMLUI_DIALOG, title=title, dialog_opt=dialog_opt, submit_id="") |
1781 return deferXMLUI(host, dialog, action_extra, security_limit, chained, profile) | 1781 return defer_xmlui(host, dialog, action_extra, security_limit, chained, profile) |
1782 | 1782 |
1783 | 1783 |
1784 def deferConfirm(*args, **kwargs): | 1784 def defer_confirm(*args, **kwargs): |
1785 """call deferDialog and return a boolean instead of the whole data dict""" | 1785 """call defer_dialog and return a boolean instead of the whole data dict""" |
1786 d = deferDialog(*args, **kwargs) | 1786 d = defer_dialog(*args, **kwargs) |
1787 d.addCallback(lambda data: C.bool(data["answer"])) | 1787 d.addCallback(lambda data: C.bool(data["answer"])) |
1788 return d | 1788 return d |
1789 | 1789 |
1790 | 1790 |
1791 # Misc other funtions | 1791 # Misc other funtions |
1792 | 1792 |
1793 def elementCopy( | 1793 def element_copy( |
1794 element: domish.Element, | 1794 element: domish.Element, |
1795 with_parent: bool = True, | 1795 with_parent: bool = True, |
1796 with_children: bool = True | 1796 with_children: bool = True |
1797 ) -> domish.Element: | 1797 ) -> domish.Element: |
1798 """Make a copy of a domish.Element | 1798 """Make a copy of a domish.Element |
1813 if with_children: | 1813 if with_children: |
1814 new_elt.children = element.children[:] | 1814 new_elt.children = element.children[:] |
1815 return new_elt | 1815 return new_elt |
1816 | 1816 |
1817 | 1817 |
1818 def isXHTMLField(field): | 1818 def is_xhtml_field(field): |
1819 """Check if a data_form.Field is an XHTML one""" | 1819 """Check if a data_form.Field is an XHTML one""" |
1820 return (field.fieldType is None and field.ext_type == "xml" and | 1820 return (field.fieldType is None and field.ext_type == "xml" and |
1821 field.value.uri == C.NS_XHTML) | 1821 field.value.uri == C.NS_XHTML) |
1822 | 1822 |
1823 | 1823 |
1824 class ElementParser: | 1824 class ElementParser: |
1825 """Callable class to parse XML string into Element""" | 1825 """Callable class to parse XML string into Element""" |
1826 | 1826 |
1827 # XXX: Found at http://stackoverflow.com/questions/2093400/how-to-create-twisted-words-xish-domish-element-entirely-from-raw-xml/2095942#2095942 | 1827 # XXX: Found at http://stackoverflow.com/questions/2093400/how-to-create-twisted-words-xish-domish-element-entirely-from-raw-xml/2095942#2095942 |
1828 | 1828 |
1829 def _escapeHTML(self, matchobj): | 1829 def _escape_html(self, matchobj): |
1830 entity = matchobj.group(1) | 1830 entity = matchobj.group(1) |
1831 if entity in XML_ENTITIES: | 1831 if entity in XML_ENTITIES: |
1832 # we don't escape XML entities | 1832 # we don't escape XML entities |
1833 return matchobj.group(0) | 1833 return matchobj.group(0) |
1834 else: | 1834 else: |
1852 raw_xml = "<div xmlns='{}'>{}</div>".format(namespace, raw_xml) | 1852 raw_xml = "<div xmlns='{}'>{}</div>".format(namespace, raw_xml) |
1853 else: | 1853 else: |
1854 raw_xml = "<div>{}</div>".format(raw_xml) | 1854 raw_xml = "<div>{}</div>".format(raw_xml) |
1855 | 1855 |
1856 # avoid ParserError on HTML escaped chars | 1856 # avoid ParserError on HTML escaped chars |
1857 raw_xml = html_entity_re.sub(self._escapeHTML, raw_xml) | 1857 raw_xml = html_entity_re.sub(self._escape_html, raw_xml) |
1858 | 1858 |
1859 self.result = None | 1859 self.result = None |
1860 | 1860 |
1861 def onStart(elem): | 1861 def on_start(elem): |
1862 self.result = elem | 1862 self.result = elem |
1863 | 1863 |
1864 def onEnd(): | 1864 def on_end(): |
1865 pass | 1865 pass |
1866 | 1866 |
1867 def onElement(elem): | 1867 def onElement(elem): |
1868 self.result.addChild(elem) | 1868 self.result.addChild(elem) |
1869 | 1869 |
1870 parser = domish.elementStream() | 1870 parser = domish.elementStream() |
1871 parser.DocumentStartEvent = onStart | 1871 parser.DocumentStartEvent = on_start |
1872 parser.ElementEvent = onElement | 1872 parser.ElementEvent = onElement |
1873 parser.DocumentEndEvent = onEnd | 1873 parser.DocumentEndEvent = on_end |
1874 tmp = domish.Element((None, "s")) | 1874 tmp = domish.Element((None, "s")) |
1875 if force_spaces: | 1875 if force_spaces: |
1876 raw_xml = raw_xml.replace("\n", " ").replace("\t", " ") | 1876 raw_xml = raw_xml.replace("\n", " ").replace("\t", " ") |
1877 tmp.addRawXml(raw_xml) | 1877 tmp.addRawXml(raw_xml) |
1878 parser.parse(tmp.toXml().encode("utf-8")) | 1878 parser.parse(tmp.toXml().encode("utf-8")) |
1886 | 1886 |
1887 | 1887 |
1888 parse = ElementParser() | 1888 parse = ElementParser() |
1889 | 1889 |
1890 | 1890 |
1891 # FIXME: this method is duplicated from frontends.tools.xmlui.getText | 1891 # FIXME: this method is duplicated from frontends.tools.xmlui.get_text |
1892 def getText(node): | 1892 def get_text(node): |
1893 """Get child text nodes of a domish.Element. | 1893 """Get child text nodes of a domish.Element. |
1894 | 1894 |
1895 @param node (domish.Element) | 1895 @param node (domish.Element) |
1896 @return: joined unicode text of all nodes | 1896 @return: joined unicode text of all nodes |
1897 """ | 1897 """ |
1900 if child.nodeType == child.TEXT_NODE: | 1900 if child.nodeType == child.TEXT_NODE: |
1901 data.append(child.wholeText) | 1901 data.append(child.wholeText) |
1902 return "".join(data) | 1902 return "".join(data) |
1903 | 1903 |
1904 | 1904 |
1905 def findAll(elt, namespaces=None, names=None): | 1905 def find_all(elt, namespaces=None, names=None): |
1906 """Find child element at any depth matching criteria | 1906 """Find child element at any depth matching criteria |
1907 | 1907 |
1908 @param elt(domish.Element): top parent of the elements to find | 1908 @param elt(domish.Element): top parent of the elements to find |
1909 @param names(iterable[unicode], basestring, None): names to match | 1909 @param names(iterable[unicode], basestring, None): names to match |
1910 None to accept every names | 1910 None to accept every names |
1922 domish.IElement.providedBy(child) | 1922 domish.IElement.providedBy(child) |
1923 and (not names or child.name in names) | 1923 and (not names or child.name in names) |
1924 and (not namespaces or child.uri in namespaces) | 1924 and (not namespaces or child.uri in namespaces) |
1925 ): | 1925 ): |
1926 yield child | 1926 yield child |
1927 for found in findAll(child, namespaces, names): | 1927 for found in find_all(child, namespaces, names): |
1928 yield found | 1928 yield found |
1929 | 1929 |
1930 | 1930 |
1931 def findAncestor( | 1931 def find_ancestor( |
1932 elt, | 1932 elt, |
1933 name: str, | 1933 name: str, |
1934 namespace: Optional[Union[str, Iterable[str]]] = None | 1934 namespace: Optional[Union[str, Iterable[str]]] = None |
1935 ) -> domish.Element: | 1935 ) -> domish.Element: |
1936 """Retrieve ancestor of an element | 1936 """Retrieve ancestor of an element |
1956 if current.name == name and (namespace is None or current.uri in namespace): | 1956 if current.name == name and (namespace is None or current.uri in namespace): |
1957 return current | 1957 return current |
1958 current = current.parent | 1958 current = current.parent |
1959 | 1959 |
1960 | 1960 |
1961 def pFmtElt(elt, indent=0, defaultUri=""): | 1961 def p_fmt_elt(elt, indent=0, defaultUri=""): |
1962 """Pretty format a domish.Element""" | 1962 """Pretty format a domish.Element""" |
1963 strings = [] | 1963 strings = [] |
1964 for child in elt.children: | 1964 for child in elt.children: |
1965 if domish.IElement.providedBy(child): | 1965 if domish.IElement.providedBy(child): |
1966 strings.append(pFmtElt(child, indent+2, defaultUri=elt.defaultUri)) | 1966 strings.append(p_fmt_elt(child, indent+2, defaultUri=elt.defaultUri)) |
1967 else: | 1967 else: |
1968 strings.append(f"{(indent+2)*' '}{child!s}") | 1968 strings.append(f"{(indent+2)*' '}{child!s}") |
1969 if elt.children: | 1969 if elt.children: |
1970 nochild_elt = domish.Element( | 1970 nochild_elt = domish.Element( |
1971 (elt.uri, elt.name), elt.defaultUri, elt.attributes, elt.localPrefixes | 1971 (elt.uri, elt.name), elt.defaultUri, elt.attributes, elt.localPrefixes |
1975 else: | 1975 else: |
1976 strings.append(f"{indent*' '}{elt.toXml(defaultUri=defaultUri)}") | 1976 strings.append(f"{indent*' '}{elt.toXml(defaultUri=defaultUri)}") |
1977 return '\n'.join(strings) | 1977 return '\n'.join(strings) |
1978 | 1978 |
1979 | 1979 |
1980 def ppElt(elt): | 1980 def pp_elt(elt): |
1981 """Pretty print a domish.Element""" | 1981 """Pretty print a domish.Element""" |
1982 print(pFmtElt(elt)) | 1982 print(p_fmt_elt(elt)) |
1983 | 1983 |
1984 | 1984 |
1985 # ElementTree | 1985 # ElementTree |
1986 | 1986 |
1987 def et_get_namespace_and_name(et_elt: ET.Element) -> Tuple[Optional[str], str]: | 1987 def et_get_namespace_and_name(et_elt: ET.Element) -> Tuple[Optional[str], str]: |