1 #!/usr/bin/env python 2 3 """ 4 Resources for use with WebStack. 5 6 Copyright (C) 2005 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 21 """ 22 23 import WebStack.Generic 24 import XSLForms.Fields 25 import XSLForms.Prepare 26 import XSLForms.Output 27 import XSLForms.Resources.Common 28 from XSLTools import XSLOutput 29 import os 30 31 class XSLFormsResource(XSLForms.Resources.Common.CommonResource): 32 33 """ 34 A generic XSLForms resource for use with WebStack. 35 36 When overriding this class, define the following attributes appropriately: 37 38 * template_resources - a dictionary mapping output identifiers to 39 (template_filename, output_filename) tuples, 40 indicating the template and stylesheet filenames 41 to be employed 42 43 * in_page_resources - a dictionary mapping fragment identifiers to 44 (output_identifier, output_filename, 45 node_identifier) tuples, indicating the output 46 identifier for which the fragment applies, the 47 stylesheet filename to be employed, along with 48 the node identifier used in the original 49 template and output documents to mark a region 50 of those documents as the fragment to be updated 51 upon "in-page" requests 52 53 * init_resources - a dictionary mapping initialiser/input 54 identifiers to (template_filename, 55 input_filename) tuples, indicating the template 56 and initialiser/input stylesheet filenames to be 57 employed 58 59 * transform_resources - a dictionary mapping transform identifiers to 60 lists of stylesheet filenames for use with the 61 transformation methods 62 63 * document_resources - a dictionary mapping document identifiers to 64 single filenames for use as source documents or 65 as references with the transformation methods 66 67 * resource_dir - the absolute path of the directory in which 68 stylesheet resources are to reside 69 70 All filenames shall be simple leafnames for files residing in the resource's 71 special resource directory 'resource_dir'. 72 73 The following attributes may also be specified: 74 75 * path_encoding - the assumed encoding of characters in request 76 paths 77 78 * encoding - the assumed encoding of characters in request 79 bodies 80 """ 81 82 path_encoding = "utf-8" 83 encoding = "utf-8" 84 template_resources = {} 85 in_page_resources = {} 86 init_resources = {} 87 transform_resources = {} 88 89 def clean_parameters(self, parameters): 90 91 """ 92 Workaround stray zero value characters from Konqueror in XMLHttpRequest 93 communications. 94 """ 95 96 for name, values in parameters.items(): 97 new_values = [] 98 for value in values: 99 if value.endswith("\x00"): 100 new_values.append(value[:-1]) 101 else: 102 new_values.append(value) 103 parameters[name] = new_values 104 105 def prepare_output(self, output_identifier): 106 107 """ 108 Prepare the output stylesheets using the given 'output_identifier' to 109 indicate which templates and stylesheets are to be employed in the 110 production of output from the resource. 111 112 The 'output_identifier' is used as a key to the 'template_resources' 113 dictionary attribute. 114 115 Return the full path to the output stylesheet for use with 'send_output' 116 or 'get_result'. 117 """ 118 119 template_path, output_path = prepare_output(self, output_identifier) 120 return output_path 121 122 def prepare_fragment(self, fragment_identifier): 123 124 """ 125 Prepare the output stylesheets for the given 'fragment_identifier', 126 indicating which templates and stylesheets are to be employed in the 127 production of output from the resource. 128 129 The 'fragment_identifier' is used as a key to the 'in_page_resources' 130 dictionary attribute which in turn obtains an 'output_identifier', which 131 is used as a key to the 'template_resources' dictionary attribute. 132 133 Return the full path to the output stylesheet for use with 'send_output' 134 or 'get_result'. 135 """ 136 137 template_path, fragment_path = prepare_fragment(self, fragment_identifier) 138 return fragment_path 139 140 def prepare_parameters(self, parameters): 141 142 """ 143 Prepare the stylesheet parameters from the given request 'parameters'. 144 This is most useful when preparing fragments for in-page update output. 145 """ 146 147 element_path = parameters.get("element-path", [""])[0] 148 if element_path: 149 return {"element-path" : element_path} 150 else: 151 return {} 152 153 def send_output(self, trans, stylesheet_filenames, document, stylesheet_parameters=None, 154 stylesheet_expressions=None, references=None): 155 156 """ 157 Send the output from the resource to the user employing the transaction 158 'trans', stylesheets having the given 'stylesheet_filenames', the 159 'document' upon which the output will be based, the optional parameters 160 as defined in the 'stylesheet_parameters' dictionary, the optional 161 expressions are defined in the 'stylesheet_expressions' dictionary, and 162 the optional 'references' to external documents. 163 """ 164 165 # Sanity check for the filenames list. 166 167 if isinstance(stylesheet_filenames, str) or isinstance(stylesheet_filenames, unicode): 168 raise ValueError, stylesheet_filenames 169 170 proc = XSLOutput.Processor(stylesheet_filenames, parameters=stylesheet_parameters, 171 expressions=stylesheet_expressions, references=references) 172 proc.send_output(trans.get_response_stream(), trans.get_response_stream_encoding(), 173 document) 174 175 def get_result(self, stylesheet_filenames, document, stylesheet_parameters=None, 176 stylesheet_expressions=None, references=None): 177 178 """ 179 Get the result of applying a transformation using stylesheets with the 180 given 'stylesheet_filenames', the 'document' upon which the result will 181 be based, the optional parameters as defined in the 182 'stylesheet_parameters' dictionary, the optional parameters as defined 183 in the 'stylesheet_parameters' dictionary and the optional 'references' 184 to external documents. 185 """ 186 187 # Sanity check for the filenames list. 188 189 if isinstance(stylesheet_filenames, str) or isinstance(stylesheet_filenames, unicode): 190 raise ValueError, stylesheet_filenames 191 192 proc = XSLOutput.Processor(stylesheet_filenames, parameters=stylesheet_parameters, 193 expressions=stylesheet_expressions, references=references) 194 return proc.get_result(document) 195 196 def prepare_initialiser(self, input_identifier, init_enumerations=1): 197 198 """ 199 Prepare an initialiser/input transformation using the given 200 'input_identifier'. The optional 'init_enumerations' (defaulting to 201 true) may be used to indicate whether enumerations are to be initialised 202 from external documents. 203 204 Return the full path to the input stylesheet for use with 'send_output' 205 or 'get_result'. 206 """ 207 208 template_path, input_path = prepare_initialiser(self, input_identifier, init_enumerations) 209 return input_path 210 211 def prepare_transform(self, transform_identifier): 212 213 """ 214 Prepare a transformation using the given 'transform_identifier'. 215 216 Return a list of full paths to the output stylesheets for use with 217 'send_output' or 'get_result'. 218 """ 219 220 filenames = self.transform_resources[transform_identifier] 221 222 # Sanity check for the filenames list. 223 224 if isinstance(filenames, str) or isinstance(filenames, unicode): 225 raise ValueError, filenames 226 227 paths = [] 228 for filename in filenames: 229 paths.append(os.path.abspath(os.path.join(self.resource_dir, filename))) 230 return paths 231 232 def get_in_page_resource(self, trans): 233 234 """ 235 Return the in-page resource being referred to in the given transaction 236 'trans'. 237 """ 238 239 return trans.get_path_info(self.path_encoding).split("/")[-1] 240 241 def respond(self, trans): 242 243 """ 244 Respond to the request described by the given transaction 'trans'. 245 """ 246 247 # Only obtain field information according to the stated method. 248 249 content_type = trans.get_content_type() 250 method = trans.get_request_method() 251 in_page_resource = self.get_in_page_resource(trans) 252 253 # Handle typical request methods, processing request information. 254 255 if method == "GET": 256 257 # Get the fields from the request path (URL). 258 # NOTE: The encoding is actually redundant since WebStack produces 259 # NOTE: Unicode values. 260 261 form = XSLForms.Fields.Form(encoding=self.path_encoding, values_are_lists=1) 262 parameters = trans.get_fields_from_path() 263 form.set_parameters(parameters) 264 265 elif method == "POST" and content_type.media_type == "application/x-www-form-urlencoded": 266 267 # Get the fields from the request body. 268 # NOTE: The encoding is actually redundant since WebStack produces 269 # NOTE: Unicode values. 270 271 form = XSLForms.Fields.Form(encoding=self.encoding, values_are_lists=1) 272 parameters = trans.get_fields_from_body(self.encoding) 273 274 # NOTE: Konqueror workaround. 275 self.clean_parameters(parameters) 276 277 form.set_parameters(parameters) 278 279 else: 280 281 # Initialise empty container. 282 283 form = XSLForms.Fields.Form(encoding=self.encoding, values_are_lists=1) 284 285 # Call an overridden method with the processed request information. 286 287 self.respond_to_form(trans, form) 288 289 def respond_to_form(self, trans, form): 290 291 """ 292 Respond to the request described by the given transaction 'trans', using 293 the given 'form' object to conveniently retrieve field (request 294 parameter) information and structured form information (as DOM-style XML 295 documents). 296 """ 297 298 trans.set_response_code(500) 299 trans.set_content_type(WebStack.Generic.ContentType("text/plain")) 300 out = trans.get_response_stream() 301 out.write("Resource not fully defined to respond.") 302 raise WebStack.Generic.EndOfResponse 303 304 def prepare_output(self, output_identifier): 305 306 """ 307 Prepare the output stylesheet for the resource class or object 'self' 308 corresponding to the given 'output_identifier'. Return the template path 309 and the output stylesheet path in a 2-tuple. 310 """ 311 312 template_filename, output_filename = self.template_resources[output_identifier] 313 output_path = os.path.abspath(os.path.join(self.resource_dir, output_filename)) 314 template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename)) 315 XSLForms.Prepare.ensure_stylesheet(template_path, output_path) 316 return template_path, output_path 317 318 def prepare_fragment(self, fragment_identifier): 319 320 """ 321 Prepare the output stylesheet for the resource class or object 'self' 322 corresponding to the given 'fragment_identifier'. Return the template path 323 and the output stylesheet path in a 2-tuple. 324 """ 325 326 output_identifier, fragment_filename, node_identifier = self.in_page_resources[fragment_identifier] 327 fragment_path = os.path.abspath(os.path.join(self.resource_dir, fragment_filename)) 328 template_filename, output_filename = self.template_resources[output_identifier] 329 template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename)) 330 XSLForms.Prepare.ensure_stylesheet_fragment(template_path, fragment_path, node_identifier) 331 return template_path, fragment_path 332 333 def prepare_initialiser(self, input_identifier, init_enumerations): 334 335 """ 336 Prepare the initialising stylesheet for the resource class or object 'self' 337 corresponding to the given 'input_identifier' and 'init_enumerations' flag. 338 Return the template path and the initialising stylesheet path in a 2-tuple. 339 """ 340 341 template_filename, input_filename = self.init_resources[input_identifier] 342 input_path = os.path.abspath(os.path.join(self.resource_dir, input_filename)) 343 template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename)) 344 XSLForms.Prepare.ensure_input_stylesheet(template_path, input_path, init_enumerations) 345 return template_path, input_path 346 347 def prepare_resources(cls): 348 349 "Prepare the resources associated with the class 'cls'." 350 351 for output_identifier in cls.template_resources.keys(): 352 prepare_output(cls, output_identifier) 353 for fragment_identifier in cls.in_page_resources.keys(): 354 prepare_fragment(cls, fragment_identifier) 355 356 # NOTE: Using init_enumerations=1 here. 357 358 for input_identifier in cls.init_resources.keys(): 359 prepare_initialiser(cls, input_identifier, 1) 360 361 # vim: tabstop=4 expandtab shiftwidth=4