paulb@31 | 1 | #!/usr/bin/env python |
paulb@31 | 2 | |
paulb@31 | 3 | "A WebStack questionnaire application." |
paulb@31 | 4 | |
paulb@31 | 5 | import WebStack.Generic |
paulb@662 | 6 | from XSLForms.Resources.WebResources import \ |
paul@674 | 7 | XSLFormsResource, input, output, resources, prepare_resources as xslforms_prepare_resources |
paulb@31 | 8 | import os |
paul@674 | 9 | import libxml2dom |
paul@674 | 10 | import xml.dom |
paulb@31 | 11 | |
paulb@102 | 12 | # Site map imports. |
paulb@102 | 13 | |
paulb@102 | 14 | from WebStack.Resources.ResourceMap import MapResource |
paulb@560 | 15 | from WebStack.Resources.Selectors import EncodingSelector |
paulb@102 | 16 | from WebStack.Resources.Static import DirectoryResource |
paulb@102 | 17 | |
paulb@560 | 18 | # Configuration settings. |
paulb@560 | 19 | |
paulb@560 | 20 | encoding = "utf-8" |
paulb@560 | 21 | |
paulb@102 | 22 | # Resource classes. |
paulb@102 | 23 | |
paulb@662 | 24 | class QuestionnaireEditorResource(XSLFormsResource): |
paulb@31 | 25 | |
paulb@31 | 26 | "A resource providing a questionnaire editor." |
paulb@31 | 27 | |
paulb@662 | 28 | resource_dir = resources(__file__) |
paulb@90 | 29 | template_resources = { |
paulb@662 | 30 | "questionnaire" : output("question_template.xhtml") |
paulb@90 | 31 | } |
paul@674 | 32 | init_resources = { |
paul@674 | 33 | "questionnaire" : input("question_template.xhtml") |
paul@674 | 34 | } |
paul@674 | 35 | document_resources = { |
paul@674 | 36 | "response-types" : "question_response_types.xml" |
paul@674 | 37 | } |
paulb@31 | 38 | |
paulb@662 | 39 | def select_activity(self, trans, form): |
paulb@662 | 40 | form.set_activity("questionnaire") |
paulb@31 | 41 | |
paulb@662 | 42 | def respond_to_input(self, trans, form): |
paulb@31 | 43 | |
paulb@90 | 44 | parameters = form.get_parameters() |
paulb@90 | 45 | selectors = form.get_selectors() |
paulb@662 | 46 | questionnaire = form.get_document() |
paulb@31 | 47 | |
paulb@662 | 48 | self.remove_elements(selectors.get("remove-question")) |
paulb@662 | 49 | self.add_elements(selectors.get("add-choice"), "choice") |
paulb@662 | 50 | self.remove_elements(selectors.get("remove-choice")) |
paulb@31 | 51 | |
paul@674 | 52 | # Move questions. |
paul@674 | 53 | # NOTE: Potentially a DOM convenience method here. |
paul@674 | 54 | |
paul@674 | 55 | for question in selectors.get("move-question", []): |
paul@691 | 56 | |
paul@691 | 57 | # Only move a question to a different position. |
paul@691 | 58 | |
paul@691 | 59 | position = question.xpath("count(preceding-sibling::question) + 1") |
paul@674 | 60 | destination = int(question.getAttribute("destination")) |
paul@691 | 61 | |
paul@691 | 62 | if destination != position: |
paul@691 | 63 | |
paul@691 | 64 | # Where the question is before the destination, find the |
paul@691 | 65 | # question after the current one at that position, since the |
paul@691 | 66 | # subsequent deletion of the question being moved will then |
paul@691 | 67 | # reduce the destination by one. |
paul@691 | 68 | |
paul@691 | 69 | if destination > position: |
paul@691 | 70 | destination += 1 |
paul@691 | 71 | |
paul@691 | 72 | root = questionnaire.documentElement |
paul@691 | 73 | target = (root.xpath("question[position() = %d]" % destination) or [None])[0] |
paul@691 | 74 | root.removeChild(question) |
paul@691 | 75 | if target is not None: |
paul@691 | 76 | try: |
paul@691 | 77 | root.insertBefore(question, target) |
paul@691 | 78 | except xml.dom.NotFoundErr: |
paul@691 | 79 | target = (root.xpath("question[position() = %d]" % destination) or [None])[0] |
paul@691 | 80 | root.insertBefore(question, target) |
paul@691 | 81 | else: |
paul@691 | 82 | root.appendChild(question) |
paul@674 | 83 | |
paulb@31 | 84 | # Add questions using the normal request parameter. |
paulb@31 | 85 | |
paulb@31 | 86 | if parameters.has_key("add-question"): |
paulb@31 | 87 | new_question = questionnaire.ownerDocument.createElementNS(None, "question") |
paulb@31 | 88 | questionnaire.xpath("questionnaire")[0].appendChild(new_question) |
paulb@31 | 89 | |
paulb@31 | 90 | # Send a response according to certain parameters. |
paulb@31 | 91 | # When exported, an XML version of the data is returned. |
paulb@31 | 92 | |
paulb@31 | 93 | if parameters.has_key("export"): |
paulb@560 | 94 | trans.set_content_type(WebStack.Generic.ContentType("text/xml", encoding)) |
paul@677 | 95 | questionnaire.toStream(trans.get_response_stream(), trans.get_response_stream_encoding(), prettyprint=1) |
paulb@662 | 96 | raise WebStack.Generic.EndOfResponse |
paulb@31 | 97 | |
paul@674 | 98 | # Support uploading of files. |
paul@674 | 99 | |
paul@674 | 100 | if parameters.has_key("import"): |
paul@674 | 101 | importfile = parameters["importfile"][0] |
paul@687 | 102 | |
paul@687 | 103 | if hasattr(importfile, "stream"): |
paul@687 | 104 | try: |
paul@687 | 105 | doc = libxml2dom.parse(importfile.stream) |
paul@687 | 106 | form.set_document(doc) |
paul@687 | 107 | return # success |
paul@687 | 108 | except libxml2dom.LSException: |
paul@687 | 109 | pass |
paul@687 | 110 | |
paul@687 | 111 | questionnaire.documentElement.setAttribute("error", "true") |
paul@674 | 112 | |
paul@674 | 113 | def init_document(self, trans, form): |
paul@674 | 114 | |
paul@674 | 115 | # Transform, adding enumerations/ranges. |
paul@674 | 116 | |
paul@674 | 117 | response_types_xml = self.prepare_document("response-types") |
paul@674 | 118 | XSLFormsResource.init_document(self, trans, form, references={"response-type" : response_types_xml}) |
paul@674 | 119 | |
paulb@102 | 120 | # Site map initialisation. |
paulb@102 | 121 | |
paulb@102 | 122 | def get_site(): |
paulb@102 | 123 | |
paulb@102 | 124 | "Return a simple Web site resource." |
paulb@102 | 125 | |
paulb@102 | 126 | # Get the main resource and the directory used by the application. |
paulb@102 | 127 | |
paulb@102 | 128 | questionnaire_resource = QuestionnaireEditorResource() |
paulb@102 | 129 | directory = questionnaire_resource.resource_dir |
paulb@102 | 130 | |
paulb@102 | 131 | # Make a simple Web site. |
paulb@102 | 132 | |
paulb@102 | 133 | resource = MapResource({ |
paulb@102 | 134 | # Static resources: |
paulb@102 | 135 | "styles" : DirectoryResource(os.path.join(directory, "styles"), {"css" : "text/css"}), |
paulb@102 | 136 | # Main page: |
paulb@102 | 137 | "" : questionnaire_resource |
paulb@102 | 138 | }) |
paulb@102 | 139 | |
paulb@560 | 140 | return EncodingSelector(resource, encoding) |
paulb@102 | 141 | |
paulb@514 | 142 | # Resource preparation ahead of time - useful for making installations. |
paulb@514 | 143 | |
paulb@514 | 144 | def prepare_resources(): |
paulb@514 | 145 | for cls in [QuestionnaireEditorResource]: |
paulb@662 | 146 | xslforms_prepare_resources(cls) |
paulb@514 | 147 | |
paulb@31 | 148 | # vim: tabstop=4 expandtab shiftwidth=4 |