changeset 2782:b17e6fa1e607

core (XMLUI): new XHTMLBox widget: XHTMLBox is a textbox specialised in XHTML, i.e. it renders the XHTML when in read_only, and it allows to edit it. The XHTML is cleaned by default, the cleaning is done by Text Syntaxes plugin (actually there is a cleaning method which can be set by any plugin, but Text Syntaxes is the one which does it).
author Goffi <goffi@goffi.org>
date Sat, 19 Jan 2019 11:39:02 +0100 (2019-01-19)
parents 816be0a23877
children 442ab697f831
files sat/plugins/plugin_misc_text_syntaxes.py sat/tools/xml_tools.py
diffstat 2 files changed, 52 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/sat/plugins/plugin_misc_text_syntaxes.py	Sat Jan 19 11:39:02 2019 +0100
+++ b/sat/plugins/plugin_misc_text_syntaxes.py	Sat Jan 19 11:39:02 2019 +0100
@@ -26,6 +26,7 @@
 from twisted.internet import defer
 from twisted.internet.threads import deferToThread
 from sat.core import exceptions
+from sat.tools import xml_tools
 
 try:
     from lxml import html
@@ -205,6 +206,9 @@
         host.bridge.addMethod(
             "syntaxGet", ".plugin", in_sign="s", out_sign="s", method=self.getSyntax
         )
+        if xml_tools.cleanXHTML is None:
+            log.debug(u"Installing cleaning method")
+            xml_tools.cleanXHTML = self.cleanXHTML
 
     def _updateParamOptions(self):
         data_synt = TextSyntaxes.syntaxes
--- a/sat/tools/xml_tools.py	Sat Jan 19 11:39:02 2019 +0100
+++ b/sat/tools/xml_tools.py	Sat Jan 19 11:39:02 2019 +0100
@@ -41,6 +41,10 @@
 html_entity_re = re.compile(r"&([a-zA-Z]+?);")
 XML_ENTITIES = ("quot", "amp", "apos", "lt", "gt")
 
+# method to clean XHTML, receive raw unsecure XML or HTML, must return cleaned raw XHTML
+# this method must be set during runtime
+cleanXHTML = None
+
 # TODO: move XMLUI stuff in a separate module
 # TODO: rewrite this with lxml or ElementTree or domish.Element: it's complicated and difficult to maintain with current minidom implementation
 
@@ -58,11 +62,28 @@
     """
     widget_args = [field.value]
     widget_kwargs = {}
-    if field.fieldType == "fixed" or field.fieldType is None:
+    if field.fieldType is None and field.ext_type is not None:
+        # we have an extended field
+        if field.ext_type == u"xml":
+            element = field.value
+            if element.uri == C.NS_XHTML:
+                widget_type = "xhtmlbox"
+                widget_args[0] = element.toXml()
+                widget_kwargs["read_only"] = read_only
+            else:
+                log.warning(u"unknown XML element, falling back to textbox")
+                widget_type = "textbox"
+                widget_args[0] = element.toXml()
+                widget_kwargs["read_only"] = read_only
+        else:
+            raise exceptions.DataError(u"unknown extended type {ext_type}".format(
+                ext_type = field.ext_type))
+
+    elif field.fieldType == "fixed" or field.fieldType is None:
         widget_type = "text"
         if field.value is None:
             if field.label is None:
-                log.warning(_("Fixed field has neither value nor label, ignoring it"))
+                log.warning(_(u"Fixed field has neither value nor label, ignoring it"))
                 field.value = ""
             else:
                 field.value = field.label
@@ -1011,6 +1032,26 @@
     type = "textbox"
 
 
+class XHTMLBoxWidget(StringWidget):
+    """Specialized textbox to manipulate XHTML"""
+    type = "xhtmlbox"
+
+    def __init__(self, xmlui, value, name=None, parent=None, read_only=False, clean=True):
+        """
+        @param clean(bool): if True, the XHTML is considered insecure and will be cleaned
+            Only set to False if you are absolutely sure that the XHTML is safe (in other
+            word, set to False only if you made the XHTML yourself)
+        """
+        if clean:
+            if cleanXHTML is None:
+                raise exceptions.NotFound(
+                    u"No cleaning method set, can't clean the XHTML")
+            value = cleanXHTML(value)
+
+        super(XHTMLBoxWidget, self).__init__(
+            xmlui, value=value, name=name, parent=parent, read_only=read_only)
+
+
 class JidInputWidget(StringWidget):
     type = "jid_input"
 
@@ -1623,6 +1664,11 @@
 
 # Misc other funtions
 
+def isXHTMLField(field):
+    """Check if a data_form.Field is an XHTML one"""
+    return (field.fieldType is None and field.ext_type == u"xml" and
+            field.value.uri == C.NS_XHTML)
+
 
 class ElementParser(object):
     """callable class to parse XML string into Element"""