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]: