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