comparison libervia/backend/tools/xml_tools.py @ 4270:0d7bb4df2343

Reformatted code base using black.
author Goffi <goffi@goffi.org>
date Wed, 19 Jun 2024 18:44:57 +0200
parents b274f0d5c138
children d2deddd6df44
comparison
equal deleted inserted replaced
4269:64a85ce8be70 4270:0d7bb4df2343
79 widget_type = "textbox" 79 widget_type = "textbox"
80 widget_args[0] = element.toXml() 80 widget_args[0] = element.toXml()
81 widget_kwargs["read_only"] = read_only 81 widget_kwargs["read_only"] = read_only
82 else: 82 else:
83 83
84 raise exceptions.DataError("unknown extended type {ext_type}".format( 84 raise exceptions.DataError(
85 ext_type = field.ext_type)) 85 "unknown extended type {ext_type}".format(ext_type=field.ext_type)
86 )
86 87
87 elif field.fieldType == "fixed" or field.fieldType is None: 88 elif field.fieldType == "fixed" or field.fieldType is None:
88 widget_type = "text" 89 widget_type = "text"
89 if field.value is None: 90 if field.value is None:
90 if field.label is None: 91 if field.label is None:
128 widget_type = "list" 129 widget_type = "list"
129 widget_kwargs["options"] = [ 130 widget_kwargs["options"] = [
130 (option.value, option.label or option.value) for option in field.options 131 (option.value, option.label or option.value) for option in field.options
131 ] 132 ]
132 widget_kwargs["selected"] = widget_args 133 widget_kwargs["selected"] = widget_args
133 widget_kwargs["styles"] = ["multi"] 134 widget_kwargs["styles"] = ["multi"]
134 widget_args = [] 135 widget_args = []
135 else: 136 else:
136 log.error( 137 log.error(
137 "FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType 138 "FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType
138 ) 139 )
141 142
142 if field.var: 143 if field.var:
143 widget_kwargs["name"] = field.var 144 widget_kwargs["name"] = field.var
144 145
145 return widget_type, widget_args, widget_kwargs 146 return widget_type, widget_args, widget_kwargs
147
146 148
147 def data_form_2_widgets(form_ui, form, read_only=False, prepend=None, filters=None): 149 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. 150 """Complete an existing XMLUI with widget converted from XEP-0004 data forms.
149 151
150 @param form_ui (XMLUI): XMLUI instance 152 @param form_ui (XMLUI): XMLUI instance
212 """Convert data form to a simple dict, easily serialisable 214 """Convert data form to a simple dict, easily serialisable
213 215
214 see data_dict_2_data_form for a description of the format 216 see data_dict_2_data_form for a description of the format
215 """ 217 """
216 fields = [] 218 fields = []
217 data_dict = { 219 data_dict = {"fields": fields}
218 "fields": fields
219 }
220 if form.formNamespace: 220 if form.formNamespace:
221 data_dict["namespace"] = form.formNamespace 221 data_dict["namespace"] = form.formNamespace
222 for form_field in form.fieldList: 222 for form_field in form.fieldList:
223 field = {"type": form_field.fieldType} 223 field = {"type": form_field.fieldType}
224 fields.append(field) 224 fields.append(field)
225 for src_name, dest_name in ( 225 for src_name, dest_name in (
226 ('var', 'name'), 226 ("var", "name"),
227 ('label', 'label'), 227 ("label", "label"),
228 ('value', 'value'), 228 ("value", "value"),
229 # FIXME: we probably should have only "values" 229 # FIXME: we probably should have only "values"
230 ('values', 'values') 230 ("values", "values"),
231 ): 231 ):
232 value = getattr(form_field, src_name, None) 232 value = getattr(form_field, src_name, None)
233 if value: 233 if value:
234 field[dest_name] = value 234 field[dest_name] = value
235 if form_field.options: 235 if form_field.options:
240 opt["label"] = form_opt.label 240 opt["label"] = form_opt.label
241 options.append(opt) 241 options.append(opt)
242 242
243 if form_field.fieldType is None and form_field.ext_type == "xml": 243 if form_field.fieldType is None and form_field.ext_type == "xml":
244 if isinstance(form_field.value, domish.Element): 244 if isinstance(form_field.value, domish.Element):
245 if ((form_field.value.uri == C.NS_XHTML 245 if form_field.value.uri == C.NS_XHTML and form_field.value.name == "div":
246 and form_field.value.name == "div")):
247 field["type"] = "xhtml" 246 field["type"] = "xhtml"
248 if form_field.value.children: 247 if form_field.value.children:
249 log.warning( 248 log.warning(
250 "children are not managed for XHTML fields: " 249 "children are not managed for XHTML fields: "
251 f"{form_field.value.toXml()}" 250 f"{form_field.value.toXml()}"
268 - "required" is the same as data_form.Field.required 267 - "required" is the same as data_form.Field.required
269 """ 268 """
270 # TODO: describe format 269 # TODO: describe format
271 fields = [] 270 fields = []
272 for field_data in data_dict["fields"]: 271 for field_data in data_dict["fields"]:
273 field_type = field_data.get('type', 'text-single') 272 field_type = field_data.get("type", "text-single")
274 kwargs = { 273 kwargs = {
275 "fieldType": field_type, 274 "fieldType": field_type,
276 "var": field_data["name"], 275 "var": field_data["name"],
277 "label": field_data.get('label'), 276 "label": field_data.get("label"),
278 "value": field_data.get("value"), 277 "value": field_data.get("value"),
279 "required": field_data.get("required") 278 "required": field_data.get("required"),
280 } 279 }
281 if field_type == "xhtml": 280 if field_type == "xhtml":
282 kwargs.update({ 281 kwargs.update(
283 "fieldType": None, 282 {
284 "ext_type": "xml", 283 "fieldType": None,
285 }) 284 "ext_type": "xml",
285 }
286 )
286 if kwargs["value"] is None: 287 if kwargs["value"] is None:
287 kwargs["value"] = domish.Element((C.NS_XHTML, "div")) 288 kwargs["value"] = domish.Element((C.NS_XHTML, "div"))
288 elif "options" in field_data: 289 elif "options" in field_data:
289 kwargs["options"] = [ 290 kwargs["options"] = [
290 data_form.Option(o["value"], o.get("label")) 291 data_form.Option(o["value"], o.get("label"))
291 for o in field_data["options"] 292 for o in field_data["options"]
292 ] 293 ]
293 field = data_form.Field(**kwargs) 294 field = data_form.Field(**kwargs)
294 fields.append(field) 295 fields.append(field)
295 return data_form.Form( 296 return data_form.Form("form", formNamespace=data_dict.get("namespace"), fields=fields)
296 "form",
297 formNamespace=data_dict.get("namespace"),
298 fields=fields
299 )
300 297
301 298
302 def data_form_elt_result_2_xmlui_data(form_xml): 299 def data_form_elt_result_2_xmlui_data(form_xml):
303 """Parse a data form result (not parsed by Wokkel's XEP-0004 implementation). 300 """Parse a data form result (not parsed by Wokkel's XEP-0004 implementation).
304 301
389 parsed_form = data_form.Form.fromElement(form_elt) 386 parsed_form = data_form.Form.fromElement(form_elt)
390 data_form_2_widgets(xml_ui, parsed_form, read_only=True) 387 data_form_2_widgets(xml_ui, parsed_form, read_only=True)
391 return xml_ui 388 return xml_ui
392 389
393 390
394 def data_form_result_2_xmlui(result_form, base_form, session_id=None, prepend=None, 391 def data_form_result_2_xmlui(
395 filters=None, read_only=True): 392 result_form, base_form, session_id=None, prepend=None, filters=None, read_only=True
393 ):
396 """Convert data form result to SàT XMLUI. 394 """Convert data form result to SàT XMLUI.
397 395
398 @param result_form (data_form.Form): result form to convert 396 @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") 397 @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) 398 this one is necessary to reconstruct options when needed (e.g. list elements)
405 @return: XMLUI instance 403 @return: XMLUI instance
406 """ 404 """
407 # we deepcopy the form because _data_form_field_2_xmlui_data can modify the value 405 # 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 406 # FIXME: check if it's really important, the only modified value seems to be
409 # the replacement of None by "" on fixed fields 407 # the replacement of None by "" on fixed fields
410 # form = deepcopy(result_form) 408 # form = deepcopy(result_form)
411 form = result_form 409 form = result_form
412 for name, field in form.fields.items(): 410 for name, field in form.fields.items():
413 try: 411 try:
414 base_field = base_form.fields[name] 412 base_field = base_form.fields[name]
415 except KeyError: 413 except KeyError:
416 continue 414 continue
417 field.options = base_field.options[:] 415 field.options = base_field.options[:]
418 xml_ui = XMLUI("window", "vertical", session_id=session_id) 416 xml_ui = XMLUI("window", "vertical", session_id=session_id)
419 data_form_2_widgets(xml_ui, form, read_only=read_only, prepend=prepend, filters=filters) 417 data_form_2_widgets(
418 xml_ui, form, read_only=read_only, prepend=prepend, filters=filters
419 )
420 return xml_ui 420 return xml_ui
421 421
422 422
423 def _clean_value(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.
432 return str(value) 432 return str(value)
433 return value 433 return value
434 434
435 435
436 def xmlui_result_2_data_form_result(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 """
442 ret = {} 442 ret = {}
444 if not key.startswith(SAT_FORM_PREFIX): 444 if not key.startswith(SAT_FORM_PREFIX):
445 continue 445 continue
446 if isinstance(value, str): 446 if isinstance(value, str):
447 if "\n" in value: 447 if "\n" in value:
448 # data form expects multi-lines text to be in separated values 448 # data form expects multi-lines text to be in separated values
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) :]] = _clean_value(value) 454 ret[key[len(SAT_FORM_PREFIX) :]] = _clean_value(value)
464 return "%s%s" % (SAT_FORM_PREFIX, name) 464 return "%s%s" % (SAT_FORM_PREFIX, name)
465 465
466 466
467 def is_xmlui_cancelled(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 xmlui_result_to_elt(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
617 617
618 ### XMLUI Elements ### 618 ### XMLUI Elements ###
619 619
620 620
621 class Element(object): 621 class Element(object):
622 """ Base XMLUI element """ 622 """Base XMLUI element"""
623 623
624 type = None 624 type = None
625 625
626 def __init__(self, xmlui, parent=None): 626 def __init__(self, xmlui, parent=None):
627 """Create a container element 627 """Create a container element
649 self.children.append(child) 649 self.children.append(child)
650 return child 650 return child
651 651
652 652
653 class TopElement(Element): 653 class TopElement(Element):
654 """ Main XML Element """ 654 """Main XML Element"""
655 655
656 type = "top" 656 type = "top"
657 657
658 def __init__(self, xmlui): 658 def __init__(self, xmlui):
659 self.elem = xmlui.doc.documentElement 659 self.elem = xmlui.doc.documentElement
660 super(TopElement, self).__init__(xmlui) 660 super(TopElement, self).__init__(xmlui)
661 661
662 662
663 class TabElement(Element): 663 class TabElement(Element):
664 """ Used by TabsContainer to give name and label to tabs.""" 664 """Used by TabsContainer to give name and label to tabs."""
665 665
666 type = "tab" 666 type = "tab"
667 667
668 def __init__(self, parent, name, label, selected=False): 668 def __init__(self, parent, name, label, selected=False):
669 """ 669 """
688 """ 688 """
689 self.elem.setAttribute("selected", "true" if selected else "false") 689 self.elem.setAttribute("selected", "true" if selected else "false")
690 690
691 691
692 class FieldBackElement(Element): 692 class FieldBackElement(Element):
693 """ Used by ButtonWidget to indicate which field have to be sent back """ 693 """Used by ButtonWidget to indicate which field have to be sent back"""
694 694
695 type = "field_back" 695 type = "field_back"
696 696
697 def __init__(self, parent, name): 697 def __init__(self, parent, name):
698 assert isinstance(parent, ButtonWidget) 698 assert isinstance(parent, ButtonWidget)
699 super(FieldBackElement, self).__init__(parent.xmlui, parent) 699 super(FieldBackElement, self).__init__(parent.xmlui, parent)
700 self.elem.setAttribute("name", name) 700 self.elem.setAttribute("name", name)
701 701
702 702
703 class InternalFieldElement(Element): 703 class InternalFieldElement(Element):
704 """ Used by internal callbacks to indicate which fields are manipulated """ 704 """Used by internal callbacks to indicate which fields are manipulated"""
705 705
706 type = "internal_field" 706 type = "internal_field"
707 707
708 def __init__(self, parent, name): 708 def __init__(self, parent, name):
709 super(InternalFieldElement, self).__init__(parent.xmlui, parent) 709 super(InternalFieldElement, self).__init__(parent.xmlui, parent)
710 self.elem.setAttribute("name", name) 710 self.elem.setAttribute("name", name)
711 711
712 712
713 class InternalDataElement(Element): 713 class InternalDataElement(Element):
714 """ Used by internal callbacks to retrieve extra data """ 714 """Used by internal callbacks to retrieve extra data"""
715 715
716 type = "internal_data" 716 type = "internal_data"
717 717
718 def __init__(self, parent, children): 718 def __init__(self, parent, children):
719 super(InternalDataElement, self).__init__(parent.xmlui, parent) 719 super(InternalDataElement, self).__init__(parent.xmlui, parent)
721 for child in children: 721 for child in children:
722 self.elem.childNodes.append(child) 722 self.elem.childNodes.append(child)
723 723
724 724
725 class OptionElement(Element): 725 class OptionElement(Element):
726 """" Used by ListWidget to specify options """ 726 """ " Used by ListWidget to specify options"""
727 727
728 type = "option" 728 type = "option"
729 729
730 def __init__(self, parent, option, selected=False): 730 def __init__(self, parent, option, selected=False):
731 """ 731 """
747 if selected: 747 if selected:
748 self.elem.setAttribute("selected", "true") 748 self.elem.setAttribute("selected", "true")
749 749
750 750
751 class JidElement(Element): 751 class JidElement(Element):
752 """" Used by JidsListWidget to specify jids""" 752 """ " Used by JidsListWidget to specify jids"""
753 753
754 type = "jid" 754 type = "jid"
755 755
756 def __init__(self, parent, jid_): 756 def __init__(self, parent, jid_):
757 """ 757 """
768 jid_txt = self.xmlui.doc.createTextNode(value) 768 jid_txt = self.xmlui.doc.createTextNode(value)
769 self.elem.appendChild(jid_txt) 769 self.elem.appendChild(jid_txt)
770 770
771 771
772 class RowElement(Element): 772 class RowElement(Element):
773 """" Used by AdvancedListContainer """ 773 """ " Used by AdvancedListContainer"""
774 774
775 type = "row" 775 type = "row"
776 776
777 def __init__(self, parent): 777 def __init__(self, parent):
778 assert isinstance(parent, AdvancedListContainer) 778 assert isinstance(parent, AdvancedListContainer)
783 self.elem.setAttribute("index", parent.next_row_idx) 783 self.elem.setAttribute("index", parent.next_row_idx)
784 parent.next_row_idx = None 784 parent.next_row_idx = None
785 785
786 786
787 class HeaderElement(Element): 787 class HeaderElement(Element):
788 """" Used by AdvancedListContainer """ 788 """ " Used by AdvancedListContainer"""
789 789
790 type = "header" 790 type = "header"
791 791
792 def __init__(self, parent, name=None, label=None, description=None): 792 def __init__(self, parent, name=None, label=None, description=None):
793 """ 793 """
808 808
809 ## Containers ## 809 ## Containers ##
810 810
811 811
812 class Container(Element): 812 class Container(Element):
813 """ And Element which contains other ones and has a layout """ 813 """And Element which contains other ones and has a layout"""
814 814
815 type = None 815 type = None
816 816
817 def __init__(self, xmlui, parent=None): 817 def __init__(self, xmlui, parent=None):
818 """Create a container element 818 """Create a container element
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 get_parent_container(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
833 while not isinstance(current, (Container)) and current is not None: 833 while not isinstance(current, (Container)) and current is not None:
843 type = "horizontal" 843 type = "horizontal"
844 844
845 845
846 class PairsContainer(Container): 846 class PairsContainer(Container):
847 """Container with series of 2 elements""" 847 """Container with series of 2 elements"""
848
848 type = "pairs" 849 type = "pairs"
849 850
850 851
851 class LabelContainer(Container): 852 class LabelContainer(Container):
852 """Like PairsContainer, but first element can only be a label""" 853 """Like PairsContainer, but first element can only be a label"""
854
853 type = "label" 855 type = "label"
854 856
855 857
856 class TabsContainer(Container): 858 class TabsContainer(Container):
857 type = "tabs" 859 type = "tabs"
870 tab_elt = TabElement(self, name, label, selected) 872 tab_elt = TabElement(self, name, label, selected)
871 new_container = container(self.xmlui, tab_elt) 873 new_container = container(self.xmlui, tab_elt)
872 return self.xmlui.change_container(new_container) 874 return self.xmlui.change_container(new_container)
873 875
874 def end(self): 876 def end(self):
875 """ Called when we have finished tabs 877 """Called when we have finished tabs
876 878
877 change current container to first container parent 879 change current container to first container parent
878 """ 880 """
879 parent_container = self.get_parent_container() 881 parent_container = self.get_parent_container()
880 self.xmlui.change_container(parent_container) 882 self.xmlui.change_container(parent_container)
950 def add_items(self, items): 952 def add_items(self, items):
951 for item in items: 953 for item in items:
952 self.append(item) 954 self.append(item)
953 955
954 def set_row_index(self, idx): 956 def set_row_index(self, idx):
955 """ Set index for next row 957 """Set index for next row
956 958
957 index are returned when a row is selected, in data's "index" key 959 index are returned when a row is selected, in data's "index" key
958 @param idx: string index to associate to the next row 960 @param idx: string index to associate to the next row
959 """ 961 """
960 self.next_row_idx = idx 962 self.next_row_idx = idx
966 self.current_row = RowElement(self) 968 self.current_row = RowElement(self)
967 self.current_row.append(child) 969 self.current_row.append(child)
968 self._item_idx += 1 970 self._item_idx += 1
969 971
970 def end(self): 972 def end(self):
971 """ Called when we have finished list 973 """Called when we have finished list
972 974
973 change current container to first container parent 975 change current container to first container parent
974 """ 976 """
975 if self._item_idx % self._columns != 0: 977 if self._item_idx % self._columns != 0:
976 raise exceptions.DataError(_("Incorrect number of items in list")) 978 raise exceptions.DataError(_("Incorrect number of items in list"))
995 super(Widget, self).__init__(xmlui, parent) 997 super(Widget, self).__init__(xmlui, parent)
996 if name: 998 if name:
997 self.elem.setAttribute("name", name) 999 self.elem.setAttribute("name", name)
998 if name in xmlui.named_widgets: 1000 if name in xmlui.named_widgets:
999 raise exceptions.ConflictError( 1001 raise exceptions.ConflictError(
1000 _('A widget with the name "{name}" already exists.').format( 1002 _('A widget with the name "{name}" already exists.').format(name=name)
1001 name=name
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 set_internal_callback(self, callback, fields, data_elts=None): 1007 def set_internal_callback(self, callback, fields, data_elts=None):
1056 class LabelWidget(Widget): 1056 class LabelWidget(Widget):
1057 """One line blob of text 1057 """One line blob of text
1058 1058
1059 used most of time to display the desciption or name of the next widget 1059 used most of time to display the desciption or name of the next widget
1060 """ 1060 """
1061
1061 type = "label" 1062 type = "label"
1062 1063
1063 def __init__(self, xmlui, label, name=None, parent=None): 1064 def __init__(self, xmlui, label, name=None, parent=None):
1064 super(LabelWidget, self).__init__(xmlui, name, parent) 1065 super(LabelWidget, self).__init__(xmlui, name, parent)
1065 self.elem.setAttribute("value", label) 1066 self.elem.setAttribute("value", label)
1066 1067
1067 1068
1068 class HiddenWidget(Widget): 1069 class HiddenWidget(Widget):
1069 """Not displayed widget, frontends will just copy the value(s)""" 1070 """Not displayed widget, frontends will just copy the value(s)"""
1071
1070 type = "hidden" 1072 type = "hidden"
1071 1073
1072 def __init__(self, xmlui, value, name, parent=None): 1074 def __init__(self, xmlui, value, name, parent=None):
1073 super(HiddenWidget, self).__init__(xmlui, name, parent) 1075 super(HiddenWidget, self).__init__(xmlui, name, parent)
1074 value_elt = self.xmlui.doc.createElement("value") 1076 value_elt = self.xmlui.doc.createElement("value")
1100 1102
1101 class DividerWidget(Widget): 1103 class DividerWidget(Widget):
1102 type = "divider" 1104 type = "divider"
1103 1105
1104 def __init__(self, xmlui, style="line", name=None, parent=None): 1106 def __init__(self, xmlui, style="line", name=None, parent=None):
1105 """ Create a divider 1107 """Create a divider
1106 1108
1107 @param xmlui: XMLUI instance 1109 @param xmlui: XMLUI instance
1108 @param style: one of: 1110 @param style: one of:
1109 - line: a simple line 1111 - line: a simple line
1110 - dot: a line of dots 1112 - dot: a line of dots
1158 type = "textbox" 1160 type = "textbox"
1159 1161
1160 1162
1161 class XHTMLBoxWidget(StringWidget): 1163 class XHTMLBoxWidget(StringWidget):
1162 """Specialized textbox to manipulate XHTML""" 1164 """Specialized textbox to manipulate XHTML"""
1165
1163 type = "xhtmlbox" 1166 type = "xhtmlbox"
1164 1167
1165 def __init__(self, xmlui, value, name=None, parent=None, read_only=False, clean=True): 1168 def __init__(self, xmlui, value, name=None, parent=None, read_only=False, clean=True):
1166 """ 1169 """
1167 @param clean(bool): if True, the XHTML is considered insecure and will be cleaned 1170 @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 1171 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) 1172 word, set to False only if you made the XHTML yourself)
1170 """ 1173 """
1171 if clean: 1174 if clean:
1172 if clean_xhtml is None: 1175 if clean_xhtml is None:
1173 raise exceptions.NotFound( 1176 raise exceptions.NotFound("No cleaning method set, can't clean the XHTML")
1174 "No cleaning method set, can't clean the XHTML")
1175 value = clean_xhtml(value) 1177 value = clean_xhtml(value)
1176 1178
1177 super(XHTMLBoxWidget, self).__init__( 1179 super(XHTMLBoxWidget, self).__init__(
1178 xmlui, value=value, name=name, parent=parent, read_only=read_only) 1180 xmlui, value=value, name=name, parent=parent, read_only=read_only
1181 )
1179 1182
1180 1183
1181 class JidInputWidget(StringWidget): 1184 class JidInputWidget(StringWidget):
1182 type = "jid_input" 1185 type = "jid_input"
1183 1186
1281 super(ListWidget, self).__init__(xmlui, name, parent) 1284 super(ListWidget, self).__init__(xmlui, name, parent)
1282 self.add_options(options, selected) 1285 self.add_options(options, selected)
1283 self.set_styles(styles) 1286 self.set_styles(styles)
1284 1287
1285 def add_options(self, options, selected=None): 1288 def add_options(self, options, selected=None):
1286 """Add options to a multi-values element (e.g. list) """ 1289 """Add options to a multi-values element (e.g. list)"""
1287 if selected: 1290 if selected:
1288 if isinstance(selected, str): 1291 if isinstance(selected, str):
1289 selected = [selected] 1292 selected = [selected]
1290 else: 1293 else:
1291 selected = [] 1294 selected = []
1345 1348
1346 ## Dialog Elements ## 1349 ## Dialog Elements ##
1347 1350
1348 1351
1349 class DialogElement(Element): 1352 class DialogElement(Element):
1350 """Main dialog element """ 1353 """Main dialog element"""
1351 1354
1352 type = "dialog" 1355 type = "dialog"
1353 1356
1354 def __init__(self, parent, type_, level=None): 1357 def __init__(self, parent, type_, level=None):
1355 if not isinstance(parent, TopElement): 1358 if not isinstance(parent, TopElement):
1408 1411
1409 1412
1410 class XMLUI(object): 1413 class XMLUI(object):
1411 """This class is used to create a user interface (form/window/parameters/etc) using SàT XML""" 1414 """This class is used to create a user interface (form/window/parameters/etc) using SàT XML"""
1412 1415
1413 def __init__(self, panel_type="window", container="vertical", dialog_opt=None, 1416 def __init__(
1414 title=None, submit_id=None, session_id=None): 1417 self,
1418 panel_type="window",
1419 container="vertical",
1420 dialog_opt=None,
1421 title=None,
1422 submit_id=None,
1423 session_id=None,
1424 ):
1415 """Init SàT XML Panel 1425 """Init SàT XML Panel
1416 1426
1417 @param panel_type: one of 1427 @param panel_type: one of
1418 - C.XMLUI_WINDOW (new window) 1428 - C.XMLUI_WINDOW (new window)
1419 - C.XMLUI_POPUP 1429 - C.XMLUI_POPUP
1509 def creator_wrapper(widget_cls, is_input): 1519 def creator_wrapper(widget_cls, is_input):
1510 # TODO: once moved to Python 3, use functools.partialmethod and 1520 # TODO: once moved to Python 3, use functools.partialmethod and
1511 # remove the creator_wrapper 1521 # remove the creator_wrapper
1512 def create_widget(self, *args, **kwargs): 1522 def create_widget(self, *args, **kwargs):
1513 if self.type == C.XMLUI_DIALOG: 1523 if self.type == C.XMLUI_DIALOG:
1514 raise exceptions.InternalError(_( 1524 raise exceptions.InternalError(
1515 "create_widget can't be used with dialogs")) 1525 _("create_widget can't be used with dialogs")
1526 )
1516 if "parent" not in kwargs: 1527 if "parent" not in kwargs:
1517 kwargs["parent"] = self.current_container 1528 kwargs["parent"] = self.current_container
1518 if "name" not in kwargs and is_input: 1529 if "name" not in kwargs and is_input:
1519 # name can be given as first argument or in keyword 1530 # name can be given as first argument or in keyword
1520 # arguments for InputWidgets 1531 # arguments for InputWidgets
1521 args = list(args) 1532 args = list(args)
1522 kwargs["name"] = args.pop(0) 1533 kwargs["name"] = args.pop(0)
1523 return widget_cls(self, *args, **kwargs) 1534 return widget_cls(self, *args, **kwargs)
1535
1524 return create_widget 1536 return create_widget
1525 1537
1526 @classmethod 1538 @classmethod
1527 def _introspect(cls): 1539 def _introspect(cls):
1528 """ Introspect module to find Widgets and Containers, and create addXXX methods""" 1540 """Introspect module to find Widgets and Containers, and create addXXX methods"""
1529 # FIXME: we can't log anything because this file is used 1541 # FIXME: we can't log anything because this file is used
1530 # in bin/sat script then evaluated 1542 # in bin/sat script then evaluated
1531 # bin/sat should be refactored 1543 # bin/sat should be refactored
1532 # log.debug(u'introspecting XMLUI widgets and containers') 1544 # log.debug(u'introspecting XMLUI widgets and containers')
1533 cls._containers = {} 1545 cls._containers = {}
1537 if issubclass(obj, Widget): 1549 if issubclass(obj, Widget):
1538 if obj.__name__ == "Widget": 1550 if obj.__name__ == "Widget":
1539 continue 1551 continue
1540 cls._widgets[obj.type] = obj 1552 cls._widgets[obj.type] = obj
1541 creator_name = "add" + obj.__name__ 1553 creator_name = "add" + obj.__name__
1542 if creator_name.endswith('Widget'): 1554 if creator_name.endswith("Widget"):
1543 creator_name = creator_name[:-6] 1555 creator_name = creator_name[:-6]
1544 is_input = issubclass(obj, InputWidget) 1556 is_input = issubclass(obj, InputWidget)
1545 # FIXME: cf. above comment 1557 # FIXME: cf. above comment
1546 # log.debug(u"Adding {creator_name} creator (is_input={is_input}))" 1558 # log.debug(u"Adding {creator_name} creator (is_input={is_input}))"
1547 # .format(creator_name=creator_name, is_input=is_input)) 1559 # .format(creator_name=creator_name, is_input=is_input))
1649 or an Container instance""" 1661 or an Container instance"""
1650 if isinstance(container, str): 1662 if isinstance(container, str):
1651 self.current_container = self._create_container( 1663 self.current_container = self._create_container(
1652 container, 1664 container,
1653 self.current_container.get_parent_container() or self.main_container, 1665 self.current_container.get_parent_container() or self.main_container,
1654 **kwargs 1666 **kwargs,
1655 ) 1667 )
1656 else: 1668 else:
1657 self.current_container = ( 1669 self.current_container = (
1658 self.main_container if container is None else container 1670 self.main_container if container is None else container
1659 ) 1671 )
1726 1738
1727 xmlui.submit_id = host.register_callback(on_submit, with_data=True, one_shot=True) 1739 xmlui.submit_id = host.register_callback(on_submit, with_data=True, one_shot=True)
1728 return xmlui_d 1740 return xmlui_d
1729 1741
1730 1742
1731 def defer_xmlui(host, xmlui, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, 1743 def defer_xmlui(
1732 chained=False, profile=C.PROF_KEY_NONE): 1744 host,
1745 xmlui,
1746 action_extra=None,
1747 security_limit=C.NO_SECURITY_LIMIT,
1748 chained=False,
1749 profile=C.PROF_KEY_NONE,
1750 ):
1733 """Create a deferred linked to XMLUI 1751 """Create a deferred linked to XMLUI
1734 1752
1735 @param xmlui(XMLUI): instance of the XMLUI 1753 @param xmlui(XMLUI): instance of the XMLUI
1736 Must be an XMLUI that you can submit, with submit_id set to '' 1754 Must be an XMLUI that you can submit, with submit_id set to ''
1737 @param profile: %(doc_profile)s 1755 @param profile: %(doc_profile)s
1762 type_: str = C.XMLUI_DIALOG_CONFIRM, 1780 type_: str = C.XMLUI_DIALOG_CONFIRM,
1763 options: Optional[dict] = None, 1781 options: Optional[dict] = None,
1764 action_extra: Optional[dict] = None, 1782 action_extra: Optional[dict] = None,
1765 security_limit: int = C.NO_SECURITY_LIMIT, 1783 security_limit: int = C.NO_SECURITY_LIMIT,
1766 chained: bool = False, 1784 chained: bool = False,
1767 profile: str = C.PROF_KEY_NONE 1785 profile: str = C.PROF_KEY_NONE,
1768 ) -> defer.Deferred: 1786 ) -> defer.Deferred:
1769 """Create a submitable dialog and manage it with a deferred 1787 """Create a submitable dialog and manage it with a deferred
1770 1788
1771 @param message: message to display 1789 @param message: message to display
1772 @param title: title of the dialog 1790 @param title: title of the dialog
1796 return d 1814 return d
1797 1815
1798 1816
1799 # Misc other funtions 1817 # Misc other funtions
1800 1818
1819
1801 def element_copy( 1820 def element_copy(
1802 element: domish.Element, 1821 element: domish.Element, with_parent: bool = True, with_children: bool = True
1803 with_parent: bool = True,
1804 with_children: bool = True
1805 ) -> domish.Element: 1822 ) -> domish.Element:
1806 """Make a copy of a domish.Element 1823 """Make a copy of a domish.Element
1807 1824
1808 The copy will have its own children list, so other elements 1825 The copy will have its own children list, so other elements
1809 can be added as direct children without modifying orignal one. 1826 can be added as direct children without modifying orignal one.
1811 it will also affect original element. 1828 it will also affect original element.
1812 @param element: Element to clone 1829 @param element: Element to clone
1813 """ 1830 """
1814 new_elt = domish.Element( 1831 new_elt = domish.Element(
1815 (element.uri, element.name), 1832 (element.uri, element.name),
1816 defaultUri = element.defaultUri, 1833 defaultUri=element.defaultUri,
1817 attribs = element.attributes, 1834 attribs=element.attributes,
1818 localPrefixes = element.localPrefixes) 1835 localPrefixes=element.localPrefixes,
1836 )
1819 if with_parent: 1837 if with_parent:
1820 new_elt.parent = element.parent 1838 new_elt.parent = element.parent
1821 if with_children: 1839 if with_children:
1822 new_elt.children = element.children[:] 1840 new_elt.children = element.children[:]
1823 return new_elt 1841 return new_elt
1824 1842
1825 1843
1826 def is_xhtml_field(field): 1844 def is_xhtml_field(field):
1827 """Check if a data_form.Field is an XHTML one""" 1845 """Check if a data_form.Field is an XHTML one"""
1828 return (field.fieldType is None and field.ext_type == "xml" and 1846 return (
1829 field.value.uri == C.NS_XHTML) 1847 field.fieldType is None
1848 and field.ext_type == "xml"
1849 and field.value.uri == C.NS_XHTML
1850 )
1830 1851
1831 1852
1832 class ElementParser: 1853 class ElementParser:
1833 """Callable class to parse XML string into Element""" 1854 """Callable class to parse XML string into Element"""
1834 1855
1935 for found in find_all(child, namespaces, names): 1956 for found in find_all(child, namespaces, names):
1936 yield found 1957 yield found
1937 1958
1938 1959
1939 def find_ancestor( 1960 def find_ancestor(
1940 elt, 1961 elt, name: str, namespace: Optional[Union[str, Iterable[str]]] = None
1941 name: str, 1962 ) -> domish.Element:
1942 namespace: Optional[Union[str, Iterable[str]]] = None
1943 ) -> domish.Element:
1944 """Retrieve ancestor of an element 1963 """Retrieve ancestor of an element
1945 1964
1946 @param elt: starting element, its parent will be checked recursively until the 1965 @param elt: starting element, its parent will be checked recursively until the
1947 required one if found 1966 required one if found
1948 @param name: name of the element to find 1967 @param name: name of the element to find
1969 def p_fmt_elt(elt, indent=0, defaultUri=""): 1988 def p_fmt_elt(elt, indent=0, defaultUri=""):
1970 """Pretty format a domish.Element""" 1989 """Pretty format a domish.Element"""
1971 strings = [] 1990 strings = []
1972 for child in elt.children: 1991 for child in elt.children:
1973 if domish.IElement.providedBy(child): 1992 if domish.IElement.providedBy(child):
1974 strings.append(p_fmt_elt(child, indent+2, defaultUri=elt.defaultUri)) 1993 strings.append(p_fmt_elt(child, indent + 2, defaultUri=elt.defaultUri))
1975 else: 1994 else:
1976 strings.append(f"{(indent+2)*' '}{child!s}") 1995 strings.append(f"{(indent+2)*' '}{child!s}")
1977 if elt.children: 1996 if elt.children:
1978 nochild_elt = domish.Element( 1997 nochild_elt = domish.Element(
1979 (elt.uri, elt.name), elt.defaultUri, elt.attributes, elt.localPrefixes 1998 (elt.uri, elt.name), elt.defaultUri, elt.attributes, elt.localPrefixes
1980 ) 1999 )
1981 strings.insert(0, f"{indent*' '}{nochild_elt.toXml(defaultUri=defaultUri)[:-2]}>") 2000 strings.insert(0, f"{indent*' '}{nochild_elt.toXml(defaultUri=defaultUri)[:-2]}>")
1982 strings.append(f"{indent*' '}</{nochild_elt.name}>") 2001 strings.append(f"{indent*' '}</{nochild_elt.name}>")
1983 else: 2002 else:
1984 strings.append(f"{indent*' '}{elt.toXml(defaultUri=defaultUri)}") 2003 strings.append(f"{indent*' '}{elt.toXml(defaultUri=defaultUri)}")
1985 return '\n'.join(strings) 2004 return "\n".join(strings)
1986 2005
1987 2006
1988 def pp_elt(elt): 2007 def pp_elt(elt):
1989 """Pretty print a domish.Element""" 2008 """Pretty print a domish.Element"""
1990 print(p_fmt_elt(elt)) 2009 print(p_fmt_elt(elt))
1991 2010
1992 2011
1993 # ElementTree 2012 # ElementTree
2013
1994 2014
1995 def et_get_namespace_and_name(et_elt: ET.Element) -> Tuple[Optional[str], str]: 2015 def et_get_namespace_and_name(et_elt: ET.Element) -> Tuple[Optional[str], str]:
1996 """Retrieve element namespace and name from ElementTree element 2016 """Retrieve element namespace and name from ElementTree element
1997 2017
1998 @param et_elt: ElementTree element 2018 @param et_elt: ElementTree element
2005 elif name[0] != "{": 2025 elif name[0] != "{":
2006 return None, name 2026 return None, name
2007 end_idx = name.find("}") 2027 end_idx = name.find("}")
2008 if end_idx == -1: 2028 if end_idx == -1:
2009 raise ValueError("Invalid ET name") 2029 raise ValueError("Invalid ET name")
2010 return name[1:end_idx], name[end_idx+1:] 2030 return name[1:end_idx], name[end_idx + 1 :]
2011 2031
2012 2032
2013 def et_elt_2_domish_elt(et_elt: Union[ET.Element, etree.Element]) -> domish.Element: 2033 def et_elt_2_domish_elt(et_elt: Union[ET.Element, etree.Element]) -> domish.Element:
2014 """Convert ElementTree element to Twisted's domish.Element 2034 """Convert ElementTree element to Twisted's domish.Element
2015 2035
2024 elt.addChild(et_elt_2_domish_elt(child)) 2044 elt.addChild(et_elt_2_domish_elt(child))
2025 return elt 2045 return elt
2026 2046
2027 2047
2028 @overload 2048 @overload
2029 def domish_elt_2_et_elt(elt: domish.Element, lxml: Literal[False]) -> ET.Element: 2049 def domish_elt_2_et_elt(elt: domish.Element, lxml: Literal[False]) -> ET.Element: ...
2030 ... 2050
2031 2051
2032 @overload 2052 @overload
2033 def domish_elt_2_et_elt(elt: domish.Element, lxml: Literal[True]) -> etree.Element: 2053 def domish_elt_2_et_elt(elt: domish.Element, lxml: Literal[True]) -> etree.Element: ...
2034 ... 2054
2035 2055
2036 @overload 2056 @overload
2037 def domish_elt_2_et_elt( 2057 def domish_elt_2_et_elt(
2038 elt: domish.Element, lxml: bool 2058 elt: domish.Element, lxml: bool
2039 ) -> Union[ET.Element, etree.Element]: 2059 ) -> Union[ET.Element, etree.Element]: ...
2040 ... 2060
2041 2061
2042 def domish_elt_2_et_elt(elt, lxml = False): 2062 def domish_elt_2_et_elt(elt, lxml=False):
2043 """Convert Twisted's domish.Element to ElementTree equivalent 2063 """Convert Twisted's domish.Element to ElementTree equivalent
2044 2064
2045 Note: this is a naive implementation, adapted to XMPP, and some text content may be 2065 Note: this is a naive implementation, adapted to XMPP, and some text content may be
2046 missing (content put after a tag, i.e. what would go to the "tail" attribute of ET 2066 missing (content put after a tag, i.e. what would go to the "tail" attribute of ET
2047 Element) 2067 Element)