XSLTools

Annotated XSLForms/Resources/PyQtWebResources.py

380:b79f6301afe8
2005-11-05 paulb [project @ 2005-11-05 01:45:28 by paulb] Changed the "faked" sender of events to be an element rather than an attribute.
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@375 69
        # Refresh status - avoiding multiple refresh calls.
paulb@375 70
paulb@375 71
        self._refreshed = 0
paulb@375 72
paulb@360 73
    # Resource methods.
paulb@360 74
paulb@360 75
    def prepare_output(self, design_identifier):
paulb@360 76
paulb@360 77
        """
paulb@360 78
        Prepare the output stylesheets using the given 'design_identifier' to
paulb@360 79
        indicate which templates and stylesheets are to be employed in the
paulb@360 80
        production of output from the resource.
paulb@360 81
paulb@360 82
        The 'design_identifier' is used as a key to the 'design_resources' and
paulb@360 83
        'template_resources' dictionary attributes.
paulb@360 84
paulb@360 85
        Return the full path to the output stylesheet for use with 'send_output'
paulb@360 86
        or 'get_result'.
paulb@360 87
        """
paulb@360 88
paulb@360 89
        design_path = self.prepare_design(design_identifier)
paulb@368 90
        template_filename, output_filename = self.template_resources[design_identifier]
paulb@360 91
        output_path = os.path.abspath(os.path.join(self.resource_dir, output_filename))
paulb@360 92
        template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename))
paulb@360 93
        XSLForms.Prepare.ensure_qt_template(design_path, template_path)
paulb@360 94
        XSLForms.Prepare.ensure_stylesheet(template_path, output_path)
paulb@360 95
        return output_path
paulb@360 96
paulb@360 97
    # PyQt compatibility methods.
paulb@360 98
paulb@354 99
    def get_document(self, document_identifier):
paulb@354 100
        return libxml2dom.parse(self.prepare_document(document_identifier))
paulb@354 101
paulb@354 102
    def prepare_widget(self, design_identifier, widget_identifier, parent=None):
paulb@354 103
        fragment_name, widget_name = self.widget_resources[widget_identifier]
paulb@375 104
        element = UINode(self.doc._node.ownerDocument.createElement(widget_name))
paulb@375 105
paulb@375 106
        # NOTE: Creating an element which may not be appropriate.
paulb@375 107
paulb@375 108
        element._node.appendChild(self.doc._node.ownerDocument.createElement(widget_name + "_value"))
paulb@375 109
        return element
paulb@354 110
paulb@360 111
    def child(self, name):
paulb@360 112
        return self.doc.child(name)
paulb@360 113
paulb@375 114
    def sender(self):
paulb@375 115
        return self._sender
paulb@375 116
paulb@360 117
    # PyQt structural methods.
paulb@360 118
paulb@360 119
    def form_init(self):
paulb@368 120
paulb@368 121
        "Initialise a newly-created form."
paulb@368 122
paulb@360 123
        raise NotImplementedError, "form_init"
paulb@360 124
paulb@368 125
    def form_populate(self):
paulb@368 126
paulb@368 127
        "Populate the values in a form."
paulb@368 128
paulb@368 129
        raise NotImplementedError, "form_populate"
paulb@368 130
paulb@360 131
    def form_refresh(self):
paulb@368 132
paulb@368 133
        "Refresh the form."
paulb@368 134
paulb@360 135
        raise NotImplementedError, "form_refresh"
paulb@360 136
paulb@375 137
    def request_refresh(self, *args, **kw):
paulb@375 138
paulb@375 139
        "Request a refresh of the form."
paulb@375 140
paulb@375 141
        if not self._refreshed:
paulb@375 142
            self._refreshed = 1
paulb@375 143
            self.form_refresh(*args, **kw)
paulb@375 144
paulb@360 145
    # Standard XSLFormsResource method, overridden to handle presentation.
paulb@360 146
paulb@360 147
    def respond_to_form(self, trans, form):
paulb@360 148
paulb@360 149
        """
paulb@360 150
        Respond to the request described by the given transaction 'trans', using
paulb@360 151
        the given 'form' object to conveniently retrieve field (request
paulb@360 152
        parameter) information and structured form information (as DOM-style XML
paulb@360 153
        documents).
paulb@360 154
        """
paulb@360 155
paulb@375 156
        self._refreshed = 0
paulb@375 157
paulb@368 158
        # Ensure the presence of the template.
paulb@368 159
paulb@368 160
        self.prepare_output(self.default_design)
paulb@368 161
paulb@360 162
        # Remember the document since it is accessed independently elsewhere.
paulb@360 163
paulb@360 164
        doc = form.get_document(self.default_design)
paulb@360 165
        if doc is None:
paulb@368 166
            doc = form.new_document(self.default_design)
paulb@375 167
            doc = self._get_initialised_form(doc)
paulb@368 168
            self.doc = UINode(doc.xpath("*")[0])
paulb@368 169
            self.form_init()
paulb@360 170
        else:
paulb@375 171
            doc = self._get_initialised_form(doc)
paulb@368 172
            self.doc = UINode(doc.xpath("*")[0])
paulb@360 173
paulb@368 174
        self.form_populate()
paulb@375 175
paulb@375 176
        # Updates happen here.
paulb@375 177
paulb@375 178
        form.set_document(self.default_design, doc)
paulb@375 179
        selectors = form.get_selectors()
paulb@375 180
        connections = self.method_resources[self.default_design]
paulb@375 181
        for selector_name, selector_values in selectors.items():
paulb@375 182
            if connections.has_key(selector_name):
paulb@375 183
                slot = connections[selector_name]
paulb@375 184
                if hasattr(self, slot):
paulb@375 185
paulb@375 186
                    # Initialise the sender.
paulb@375 187
paulb@375 188
                    for selector_value in selector_values:
paulb@380 189
                        # NOTE: Fake a special element to simulate the Qt widget hierarchy.
paulb@380 190
                        # NOTE: We could instead have set the underlying annotations for
paulb@380 191
                        # NOTE: selector-field differently, but that would be more work.
paulb@380 192
                        # NOTE: An alternative which works in certain cases is a new
paulb@380 193
                        # NOTE: attribute whose node is retained.
paulb@380 194
                        _sender = self.doc._node.ownerDocument.createElement("_sender")
paulb@380 195
                        self._sender = UINode(selector_value.appendChild(_sender))
paulb@375 196
                        getattr(self, slot)()
paulb@375 197
paulb@375 198
        # Consistency is ensured and filtering enforced.
paulb@375 199
paulb@375 200
        self.request_refresh()
paulb@368 201
        #print self.doc._node.toString("iso-8859-1")
paulb@360 202
paulb@375 203
        # Output is produced.
paulb@360 204
paulb@360 205
        trans.set_content_type(WebStack.Generic.ContentType("application/xhtml+xml", self.encoding))
paulb@360 206
        design_xsl = self.prepare_output(self.default_design)
paulb@360 207
        self.send_output(trans, [design_xsl], doc._node)
paulb@360 208
paulb@375 209
    def _get_initialised_form(self, doc):
paulb@368 210
        input_xsl = self.prepare_initialiser(self.default_design, init_enumerations=0)
paulb@368 211
        return self.get_result([input_xsl], doc)
paulb@368 212
paulb@360 213
class UINode:
paulb@354 214
paulb@360 215
    "A PyQt widget tree emulation node."
paulb@360 216
paulb@360 217
    def __init__(self, node):
paulb@360 218
        self._node = node
paulb@360 219
paulb@375 220
    def add(self, node):
paulb@375 221
        self._node.appendChild(node._node)
paulb@375 222
paulb@360 223
    def child(self, name):
paulb@360 224
        nodes = self._node.xpath(name)
paulb@360 225
        if len(nodes) > 0:
paulb@360 226
            return UINode(nodes[0])
paulb@360 227
        else:
paulb@360 228
            return None
paulb@360 229
paulb@360 230
    def children(self):
paulb@360 231
        return [UINode(node) for node in self._node.childNodes]
paulb@360 232
paulb@360 233
    def count(self):
paulb@360 234
        return len(self._node.childNodes)
paulb@360 235
paulb@360 236
    def currentText(self):
paulb@368 237
        return self._node.getAttribute("value")
paulb@360 238
paulb@360 239
    def currentItem(self):
paulb@360 240
        found = self._node.xpath("*[@value=current()/@value]")
paulb@360 241
        if found:
paulb@360 242
            return int(found.xpath("count(preceding-sibling::*)"))
paulb@360 243
        else:
paulb@360 244
            return 0
paulb@360 245
paulb@375 246
    def deleteLater(self):
paulb@375 247
        pass
paulb@375 248
paulb@360 249
    def insertItem(self, item, position=-1):
paulb@368 250
        # NOTE: Names invented rather than being extracted from the schema.
paulb@368 251
        new_element = self._node.ownerDocument.createElement(self._node.localName + "_enum")
paulb@368 252
        new_element.setAttribute("value", item)
paulb@368 253
        if position == -1:
paulb@368 254
            self._node.appendChild(new_element)
paulb@368 255
        else:
paulb@368 256
            elements = self._node.xpath("*")
paulb@368 257
            if position < len(elements) - 1:
paulb@368 258
                self._node.insertBefore(new_element, elements[position])
paulb@368 259
            else:
paulb@368 260
                self._node.appendChild(new_element)
paulb@360 261
paulb@375 262
    def layout(self):
paulb@375 263
        return self
paulb@375 264
paulb@360 265
    def parent(self):
paulb@360 266
        return UINode(self._node.parentNode)
paulb@360 267
paulb@360 268
    def removeItem(self, item):
paulb@375 269
        elements = self._node.xpath("*")
paulb@375 270
        if item < len(elements):
paulb@375 271
            self._node.removeChild(elements[item])
paulb@360 272
paulb@360 273
    def remove(self, item):
paulb@375 274
        self._node.removeChild(item._node)
paulb@360 275
paulb@368 276
    def setCurrentItem(self, index):
paulb@368 277
        pass # NOTE: Not implemented yet!
paulb@360 278
paulb@375 279
    def show(self):
paulb@375 280
        pass
paulb@354 281
paulb@354 282
class Factory:
paulb@354 283
paulb@354 284
    "A widget factory helper class."
paulb@354 285
paulb@354 286
    def connect(self, widget, obj):
paulb@375 287
paulb@375 288
        """
paulb@375 289
        Connection is done all at once by mapping field names to method names in
paulb@375 290
        the resource object.
paulb@375 291
        """
paulb@375 292
paulb@354 293
        pass
paulb@354 294
paulb@354 295
    def find_widgets(self, widget, name):
paulb@360 296
paulb@360 297
        """
paulb@360 298
        Find within the given 'widget' (a DOM node) the widget with the given
paulb@360 299
        'name'.
paulb@360 300
        """
paulb@360 301
paulb@368 302
        return [UINode(node) for node in widget.doc._node.getElementsByTagName(name)]
paulb@354 303
paulb@354 304
# vim: tabstop=4 expandtab shiftwidth=4