XSLTools

Annotated XSLForms/Resources/PyQtWebResources.py

384:90e43ac77923
2005-11-08 paulb [project @ 2005-11-08 18:34:07 by paulb] Added in-page update information, although such updates are not yet supported.
paulb@354 1
#!/usr/bin/env python
paulb@354 2
paulb@354 3
"""
paulb@354 4
PyQt-compatible resources for use with WebStack.
paulb@354 5
paulb@354 6
Copyright (C) 2005 Paul Boddie <paul@boddie.org.uk>
paulb@354 7
paulb@354 8
This library is free software; you can redistribute it and/or
paulb@354 9
modify it under the terms of the GNU Lesser General Public
paulb@354 10
License as published by the Free Software Foundation; either
paulb@354 11
version 2.1 of the License, or (at your option) any later version.
paulb@354 12
paulb@354 13
This library is distributed in the hope that it will be useful,
paulb@354 14
but WITHOUT ANY WARRANTY; without even the implied warranty of
paulb@354 15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
paulb@354 16
Lesser General Public License for more details.
paulb@354 17
paulb@354 18
You should have received a copy of the GNU Lesser General Public
paulb@354 19
License along with this library; if not, write to the Free Software
paulb@354 20
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
paulb@354 21
"""
paulb@354 22
paulb@354 23
import XSLForms.Prepare
paulb@354 24
import XSLForms.Resources.Common
paulb@354 25
import XSLForms.Resources.WebResources
paulb@360 26
import WebStack.Generic
paulb@354 27
import os
paulb@354 28
import libxml2dom
paulb@354 29
paulb@368 30
class XSLFormsResource(XSLForms.Resources.WebResources.XSLFormsResource,
paulb@368 31
    XSLForms.Resources.Common.PyQtCommonResource):
paulb@354 32
paulb@354 33
    """
paulb@354 34
    An XSLForms resource supporting PyQt-compatible Web applications for use
paulb@354 35
    with WebStack.
paulb@354 36
    """
paulb@354 37
paulb@354 38
    widget_resources = {}
paulb@354 39
paulb@360 40
    def __init__(self, design_identifier):
paulb@360 41
        self.factory = Factory()
paulb@360 42
        self.default_design = design_identifier
paulb@360 43
paulb@375 44
        # NOTE: Filenames extended by string concatenation.
paulb@360 45
paulb@360 46
        self.template_resources = {}
paulb@368 47
        self.init_resources = {}
paulb@360 48
        for design_identifier, design_name in self.design_resources.items():
paulb@368 49
            self.template_resources[design_identifier] = (design_name + "_template.xhtml", design_name + "_output.xsl")
paulb@368 50
            self.init_resources[design_identifier] = (design_name + "_template.xhtml", design_name + "_input.xsl")
paulb@360 51
paulb@375 52
        # Initialisation of connections - just a mapping from field names to
paulb@375 53
        # methods in the Web version.
paulb@375 54
paulb@375 55
        self.method_resources = {}
paulb@375 56
        for design_identifier, design_name in self.design_resources.items():
paulb@375 57
            design_path = self.prepare_design(design_identifier)
paulb@375 58
            design_doc = libxml2dom.parse(design_path)
paulb@375 59
            connections = {}
paulb@375 60
            for connection in design_doc.xpath("UI/connections/connection"):
paulb@375 61
                receiver = "".join([n.nodeValue for n in connection.xpath("receiver/text()")])
paulb@375 62
                if receiver == design_identifier:
paulb@375 63
                    sender = "".join([n.nodeValue for n in connection.xpath("sender/text()")])
paulb@375 64
                    slot = "".join([n.nodeValue for n in connection.xpath("slot/text()")])
paulb@375 65
                    slot = slot.split("(")[0]
paulb@375 66
                    connections[sender] = slot
paulb@375 67
            self.method_resources[design_identifier] = connections
paulb@375 68
paulb@384 69
        # Initialisation of template fragments.
paulb@384 70
paulb@384 71
        self.in_page_resources = {}
paulb@384 72
        for widget_identifier, (design_name, fragment_id) in self.widget_resources.items():
paulb@384 73
            self.in_page_resources[widget_identifier] = (design_name + "_output.xsl", fragment_id)
paulb@384 74
paulb@375 75
        # Refresh status - avoiding multiple refresh calls.
paulb@375 76
paulb@375 77
        self._refreshed = 0
paulb@375 78
paulb@360 79
    # Resource methods.
paulb@360 80
paulb@360 81
    def prepare_output(self, design_identifier):
paulb@360 82
paulb@360 83
        """
paulb@360 84
        Prepare the output stylesheets using the given 'design_identifier' to
paulb@360 85
        indicate which templates and stylesheets are to be employed in the
paulb@360 86
        production of output from the resource.
paulb@360 87
paulb@360 88
        The 'design_identifier' is used as a key to the 'design_resources' and
paulb@360 89
        'template_resources' dictionary attributes.
paulb@360 90
paulb@360 91
        Return the full path to the output stylesheet for use with 'send_output'
paulb@360 92
        or 'get_result'.
paulb@360 93
        """
paulb@360 94
paulb@360 95
        design_path = self.prepare_design(design_identifier)
paulb@368 96
        template_filename, output_filename = self.template_resources[design_identifier]
paulb@360 97
        output_path = os.path.abspath(os.path.join(self.resource_dir, output_filename))
paulb@360 98
        template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename))
paulb@360 99
        XSLForms.Prepare.ensure_qt_template(design_path, template_path)
paulb@360 100
        XSLForms.Prepare.ensure_stylesheet(template_path, output_path)
paulb@360 101
        return output_path
paulb@360 102
paulb@360 103
    # PyQt compatibility methods.
paulb@360 104
paulb@354 105
    def get_document(self, document_identifier):
paulb@354 106
        return libxml2dom.parse(self.prepare_document(document_identifier))
paulb@354 107
paulb@354 108
    def prepare_widget(self, design_identifier, widget_identifier, parent=None):
paulb@354 109
        fragment_name, widget_name = self.widget_resources[widget_identifier]
paulb@375 110
        element = UINode(self.doc._node.ownerDocument.createElement(widget_name))
paulb@375 111
paulb@375 112
        # NOTE: Creating an element which may not be appropriate.
paulb@375 113
paulb@375 114
        element._node.appendChild(self.doc._node.ownerDocument.createElement(widget_name + "_value"))
paulb@375 115
        return element
paulb@354 116
paulb@360 117
    def child(self, name):
paulb@360 118
        return self.doc.child(name)
paulb@360 119
paulb@375 120
    def sender(self):
paulb@375 121
        return self._sender
paulb@375 122
paulb@360 123
    # PyQt structural methods.
paulb@360 124
paulb@360 125
    def form_init(self):
paulb@368 126
paulb@368 127
        "Initialise a newly-created form."
paulb@368 128
paulb@360 129
        raise NotImplementedError, "form_init"
paulb@360 130
paulb@368 131
    def form_populate(self):
paulb@368 132
paulb@368 133
        "Populate the values in a form."
paulb@368 134
paulb@368 135
        raise NotImplementedError, "form_populate"
paulb@368 136
paulb@360 137
    def form_refresh(self):
paulb@368 138
paulb@368 139
        "Refresh the form."
paulb@368 140
paulb@360 141
        raise NotImplementedError, "form_refresh"
paulb@360 142
paulb@375 143
    def request_refresh(self, *args, **kw):
paulb@375 144
paulb@375 145
        "Request a refresh of the form."
paulb@375 146
paulb@375 147
        if not self._refreshed:
paulb@375 148
            self._refreshed = 1
paulb@375 149
            self.form_refresh(*args, **kw)
paulb@375 150
paulb@360 151
    # Standard XSLFormsResource method, overridden to handle presentation.
paulb@360 152
paulb@360 153
    def respond_to_form(self, trans, form):
paulb@360 154
paulb@360 155
        """
paulb@360 156
        Respond to the request described by the given transaction 'trans', using
paulb@360 157
        the given 'form' object to conveniently retrieve field (request
paulb@360 158
        parameter) information and structured form information (as DOM-style XML
paulb@360 159
        documents).
paulb@360 160
        """
paulb@360 161
paulb@375 162
        self._refreshed = 0
paulb@375 163
paulb@368 164
        # Ensure the presence of the template.
paulb@368 165
paulb@368 166
        self.prepare_output(self.default_design)
paulb@368 167
paulb@360 168
        # Remember the document since it is accessed independently elsewhere.
paulb@360 169
paulb@360 170
        doc = form.get_document(self.default_design)
paulb@360 171
        if doc is None:
paulb@368 172
            doc = form.new_document(self.default_design)
paulb@375 173
            doc = self._get_initialised_form(doc)
paulb@368 174
            self.doc = UINode(doc.xpath("*")[0])
paulb@368 175
            self.form_init()
paulb@360 176
        else:
paulb@375 177
            doc = self._get_initialised_form(doc)
paulb@368 178
            self.doc = UINode(doc.xpath("*")[0])
paulb@360 179
paulb@368 180
        self.form_populate()
paulb@375 181
paulb@375 182
        # Updates happen here.
paulb@375 183
paulb@375 184
        form.set_document(self.default_design, doc)
paulb@375 185
        selectors = form.get_selectors()
paulb@375 186
        connections = self.method_resources[self.default_design]
paulb@375 187
        for selector_name, selector_values in selectors.items():
paulb@375 188
            if connections.has_key(selector_name):
paulb@375 189
                slot = connections[selector_name]
paulb@375 190
                if hasattr(self, slot):
paulb@375 191
paulb@375 192
                    # Initialise the sender.
paulb@375 193
paulb@375 194
                    for selector_value in selector_values:
paulb@380 195
                        # NOTE: Fake a special element to simulate the Qt widget hierarchy.
paulb@380 196
                        # NOTE: We could instead have set the underlying annotations for
paulb@380 197
                        # NOTE: selector-field differently, but that would be more work.
paulb@380 198
                        # NOTE: An alternative which works in certain cases is a new
paulb@380 199
                        # NOTE: attribute whose node is retained.
paulb@380 200
                        _sender = self.doc._node.ownerDocument.createElement("_sender")
paulb@380 201
                        self._sender = UINode(selector_value.appendChild(_sender))
paulb@375 202
                        getattr(self, slot)()
paulb@375 203
paulb@375 204
        # Consistency is ensured and filtering enforced.
paulb@375 205
paulb@375 206
        self.request_refresh()
paulb@368 207
        #print self.doc._node.toString("iso-8859-1")
paulb@360 208
paulb@375 209
        # Output is produced.
paulb@360 210
paulb@360 211
        trans.set_content_type(WebStack.Generic.ContentType("application/xhtml+xml", self.encoding))
paulb@360 212
        design_xsl = self.prepare_output(self.default_design)
paulb@360 213
        self.send_output(trans, [design_xsl], doc._node)
paulb@360 214
paulb@375 215
    def _get_initialised_form(self, doc):
paulb@368 216
        input_xsl = self.prepare_initialiser(self.default_design, init_enumerations=0)
paulb@368 217
        return self.get_result([input_xsl], doc)
paulb@368 218
paulb@360 219
class UINode:
paulb@354 220
paulb@360 221
    "A PyQt widget tree emulation node."
paulb@360 222
paulb@360 223
    def __init__(self, node):
paulb@360 224
        self._node = node
paulb@360 225
paulb@375 226
    def add(self, node):
paulb@375 227
        self._node.appendChild(node._node)
paulb@375 228
paulb@360 229
    def child(self, name):
paulb@360 230
        nodes = self._node.xpath(name)
paulb@360 231
        if len(nodes) > 0:
paulb@360 232
            return UINode(nodes[0])
paulb@360 233
        else:
paulb@360 234
            return None
paulb@360 235
paulb@360 236
    def children(self):
paulb@360 237
        return [UINode(node) for node in self._node.childNodes]
paulb@360 238
paulb@360 239
    def count(self):
paulb@360 240
        return len(self._node.childNodes)
paulb@360 241
paulb@360 242
    def currentText(self):
paulb@368 243
        return self._node.getAttribute("value")
paulb@360 244
paulb@360 245
    def currentItem(self):
paulb@360 246
        found = self._node.xpath("*[@value=current()/@value]")
paulb@360 247
        if found:
paulb@360 248
            return int(found.xpath("count(preceding-sibling::*)"))
paulb@360 249
        else:
paulb@360 250
            return 0
paulb@360 251
paulb@375 252
    def deleteLater(self):
paulb@375 253
        pass
paulb@375 254
paulb@360 255
    def insertItem(self, item, position=-1):
paulb@368 256
        # NOTE: Names invented rather than being extracted from the schema.
paulb@368 257
        new_element = self._node.ownerDocument.createElement(self._node.localName + "_enum")
paulb@368 258
        new_element.setAttribute("value", item)
paulb@368 259
        if position == -1:
paulb@368 260
            self._node.appendChild(new_element)
paulb@368 261
        else:
paulb@368 262
            elements = self._node.xpath("*")
paulb@368 263
            if position < len(elements) - 1:
paulb@368 264
                self._node.insertBefore(new_element, elements[position])
paulb@368 265
            else:
paulb@368 266
                self._node.appendChild(new_element)
paulb@360 267
paulb@375 268
    def layout(self):
paulb@375 269
        return self
paulb@375 270
paulb@360 271
    def parent(self):
paulb@360 272
        return UINode(self._node.parentNode)
paulb@360 273
paulb@360 274
    def removeItem(self, item):
paulb@375 275
        elements = self._node.xpath("*")
paulb@375 276
        if item < len(elements):
paulb@375 277
            self._node.removeChild(elements[item])
paulb@360 278
paulb@360 279
    def remove(self, item):
paulb@375 280
        self._node.removeChild(item._node)
paulb@360 281
paulb@368 282
    def setCurrentItem(self, index):
paulb@368 283
        pass # NOTE: Not implemented yet!
paulb@360 284
paulb@375 285
    def show(self):
paulb@375 286
        pass
paulb@354 287
paulb@354 288
class Factory:
paulb@354 289
paulb@354 290
    "A widget factory helper class."
paulb@354 291
paulb@354 292
    def connect(self, widget, obj):
paulb@375 293
paulb@375 294
        """
paulb@375 295
        Connection is done all at once by mapping field names to method names in
paulb@375 296
        the resource object.
paulb@375 297
        """
paulb@375 298
paulb@354 299
        pass
paulb@354 300
paulb@354 301
    def find_widgets(self, widget, name):
paulb@360 302
paulb@360 303
        """
paulb@360 304
        Find within the given 'widget' (a DOM node) the widget with the given
paulb@360 305
        'name'.
paulb@360 306
        """
paulb@360 307
paulb@368 308
        return [UINode(node) for node in widget.doc._node.getElementsByTagName(name)]
paulb@354 309
paulb@354 310
# vim: tabstop=4 expandtab shiftwidth=4