Mercurial > libervia-backend
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) |