XSLTools

XSLForms/Resources/WebResources.py

685:0a69a04690ae
2009-03-21 Paul Boddie Updated release notes.
     1 #!/usr/bin/env python     2      3 """     4 Resources for use with WebStack.     5      6 Copyright (C) 2005, 2006, 2007, 2008, 2009 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.Utils    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     To provide actual functionality to resources, either override the    82     'respond_to_form' method and write the code for obtaining input,    83     initialising documents, creating output, and so on in that method, or    84     provide implementations for the following methods:    85     86       * select_activity       - sets the activity name which will be used by the    87                                 default implementations of the other methods    88     89       * create_document       - creates or obtains a document for the resource's    90                                 activity (need not be overridden)    91     92       * respond_to_input      - application logic relying on any input from the    93                                 request, including submitted document    94                                 information    95     96       * init_document         - initialises the document according to the    97                                 'init_resources' attribute described above (need    98                                 not be overridden)    99    100       * respond_to_document   - application logic relying on any information   101                                 from the initialised document   102    103       * create_output         - creates and sends final output to the user (need   104                                 not be overridden)   105     """   106    107     EMPTY_NAMESPACE = XSLForms.Fields.EMPTY_NAMESPACE   108     FILE_NAMESPACE = XSLForms.Fields.FILE_NAMESPACE   109    110     #path_encoding = "utf-8"   111     #encoding = "utf-8"   112    113     template_resources = {}   114     in_page_resources = {}   115     init_resources = {}   116     transform_resources = {}   117    118     def clean_parameters(self, parameters):   119    120         """   121         Workaround stray zero value characters from Konqueror in XMLHttpRequest   122         communications.   123         """   124    125         for name, values in parameters.items():   126             new_values = []   127             for value in values:   128                 if isinstance(value, (str, unicode)) and value.endswith("\x00"):   129                     new_values.append(value[:-1])   130                 else:   131                     new_values.append(value)   132             parameters[name] = new_values   133    134     def prepare_output(self, output_identifier):   135    136         """   137         Prepare the output stylesheets using the given 'output_identifier' to   138         indicate which templates and stylesheets are to be employed in the   139         production of output from the resource.   140    141         The 'output_identifier' is used as a key to the 'template_resources'   142         dictionary attribute.   143    144         Return the full path to the output stylesheet for use with 'send_output'   145         or 'get_result'.   146         """   147    148         template_path, output_path = prepare_output(self, output_identifier)   149         return output_path   150    151     def prepare_fragment(self, fragment_identifier):   152    153         """   154         Prepare the output stylesheets for the given 'fragment_identifier',   155         indicating which templates and stylesheets are to be employed in the   156         production of output from the resource.   157    158         The 'fragment_identifier' is used as a key to the 'in_page_resources'   159         dictionary attribute which in turn obtains an 'output_identifier', which   160         is used as a key to the 'template_resources' dictionary attribute.   161    162         Return the full path to the output stylesheet for use with 'send_output'   163         or 'get_result'.   164         """   165    166         template_path, fragment_path = prepare_fragment(self, fragment_identifier)   167         return fragment_path   168    169     def prepare_parameters(self, parameters):   170    171         """   172         Prepare the stylesheet parameters from the given request 'parameters'.   173         This is most useful when preparing fragments for in-page update output.   174         """   175    176         element_path = parameters.get("element-path", [""])[0]   177         if element_path:   178             return {"element-path" : element_path}   179         else:   180             return {}   181    182     def send_output(self, trans, stylesheet_filenames, document, stylesheet_parameters=None,   183         stylesheet_expressions=None, references=None):   184    185         """   186         Send the output from the resource to the user employing the transaction   187         'trans', stylesheets having the given 'stylesheet_filenames', the   188         'document' upon which the output will be based, the optional parameters   189         as defined in the 'stylesheet_parameters' dictionary, the optional   190         expressions are defined in the 'stylesheet_expressions' dictionary, and   191         the optional 'references' to external documents.   192         """   193    194         # Sanity check for the filenames list.   195    196         if isinstance(stylesheet_filenames, str) or isinstance(stylesheet_filenames, unicode):   197             raise ValueError, stylesheet_filenames   198    199         proc = XSLOutput.Processor(stylesheet_filenames, parameters=stylesheet_parameters,   200             expressions=stylesheet_expressions, references=references)   201         proc.send_output(trans.get_response_stream(), trans.get_response_stream_encoding(),   202             document)   203    204     def get_result(self, stylesheet_filenames, document, stylesheet_parameters=None,   205         stylesheet_expressions=None, references=None):   206    207         """   208         Get the result of applying a transformation using stylesheets with the   209         given 'stylesheet_filenames', the 'document' upon which the result will   210         be based, the optional parameters as defined in the   211         'stylesheet_parameters' dictionary, the optional parameters as defined   212         in the 'stylesheet_parameters' dictionary and the optional 'references'   213         to external documents.   214         """   215    216         # Sanity check for the filenames list.   217    218         if isinstance(stylesheet_filenames, str) or isinstance(stylesheet_filenames, unicode):   219             raise ValueError, stylesheet_filenames   220    221         proc = XSLOutput.Processor(stylesheet_filenames, parameters=stylesheet_parameters,   222             expressions=stylesheet_expressions, references=references)   223         return proc.get_result(document)   224    225     def prepare_initialiser(self, input_identifier, init_enumerations=1):   226    227         """   228         Prepare an initialiser/input transformation using the given   229         'input_identifier'. The optional 'init_enumerations' (defaulting to   230         true) may be used to indicate whether enumerations are to be initialised   231         from external documents.   232    233         Return the full path to the input stylesheet for use with 'send_output'   234         or 'get_result'.   235         """   236    237         template_path, input_path = prepare_initialiser(self, input_identifier, init_enumerations)   238         return input_path   239    240     def prepare_transform(self, transform_identifier):   241    242         """   243         Prepare a transformation using the given 'transform_identifier'.   244    245         Return a list of full paths to the output stylesheets for use with   246         'send_output' or 'get_result'.   247         """   248    249         filenames = self.transform_resources[transform_identifier]   250    251         # Sanity check for the filenames list.   252    253         if isinstance(filenames, str) or isinstance(filenames, unicode):   254             raise ValueError, filenames   255    256         paths = []   257         for filename in filenames:   258             paths.append(os.path.abspath(os.path.join(self.resource_dir, filename)))   259         return paths   260    261     def _get_in_page_resource(self, trans):   262    263         """   264         Return the in-page resource being referred to in the given transaction   265         'trans'.   266         """   267    268         if hasattr(self, "path_encoding"):   269             return trans.get_path_info(self.path_encoding).split("/")[-1]   270         else:   271             return trans.get_path_info().split("/")[-1]   272    273     def get_in_page_resource(self, trans):   274    275         """   276         Return the in-page resource being referred to in the given transaction   277         'trans' or None if no valid in-page resource is being referenced.   278         """   279    280         name = self._get_in_page_resource(trans)   281         if self.in_page_resources.has_key(name):   282             return name   283         else:   284             return None   285    286     def respond(self, trans):   287    288         """   289         Respond to the request described by the given transaction 'trans'.   290         """   291    292         # Only obtain field information according to the stated method.   293    294         content_type = trans.get_content_type()   295         method = trans.get_request_method()   296         in_page_resource = self.get_in_page_resource(trans)   297    298         # Handle typical request methods, processing request information.   299    300         if method == "GET":   301    302             # Get the fields from the request path (URL).   303    304             form = XSLForms.Fields.Form(encoding=None, values_are_lists=1)   305             parameters = trans.get_fields_from_path()   306             form.set_parameters(parameters)   307    308         elif method == "POST" and content_type.media_type in (   309             "application/x-www-form-urlencoded", "multipart/form-data"):   310    311             # Get the fields from the request body.   312    313             form = XSLForms.Fields.Form(encoding=None, values_are_lists=1)   314             if hasattr(self, "encoding"):   315                 parameters = trans.get_fields_from_body(self.encoding)   316             else:   317                 parameters = trans.get_fields_from_body()   318    319             # NOTE: Konqueror workaround.   320             self.clean_parameters(parameters)   321    322             form.set_parameters(parameters)   323    324         else:   325    326             # Initialise empty container.   327    328             form = XSLForms.Fields.Form(encoding=None, values_are_lists=1)   329    330         # Call an overridden method with the processed request information.   331    332         self.respond_to_form(trans, form)   333    334     def respond_to_form(self, trans, form):   335    336         """   337         Respond to the request described by the given transaction 'trans', using   338         the given 'form' object to conveniently retrieve field (request   339         parameter) information and structured form information (as DOM-style XML   340         documents).   341         """   342    343         self.select_activity(trans, form)   344         self.create_document(trans, form)   345         self.respond_to_input(trans, form)   346         self.init_document(trans, form)   347         self.respond_to_document(trans, form)   348         self.create_output(trans, form)   349         raise WebStack.Generic.EndOfResponse   350    351     # Modular methods for responding to requests.   352    353     def select_activity(self, trans, form):   354    355         """   356         Using the given transaction 'trans' and 'form' information, select the   357         activity being performed and set the 'current_activity' attribute in the   358         transaction.   359         """   360    361         pass   362    363     def create_document(self, trans, form):   364    365         """   366         Using the given transaction 'trans' and 'form' information, create the   367         document involved in the current activity and set the 'current_document'   368         attribute in the transaction.   369    370         Return whether a new document was created.   371         """   372    373         documents = form.get_documents()   374         activity = form.get_activity()   375    376         if documents.has_key(activity):   377             form.set_document(documents[activity])   378             return 0   379         else:   380             form.new_document(activity)   381             form.new_documents.add(activity)   382             return 1   383    384     def respond_to_input(self, trans, form):   385    386         """   387         Using the given transaction 'trans' and 'form' information, perform the   388         parts of the current activity which rely on the information supplied in   389         the current document.   390         """   391    392         pass   393    394     def init_document(self, trans, form, stylesheet_parameters=None,   395         stylesheet_expressions=None, references=None):   396    397         """   398         Using the given transaction 'trans' and 'form' information, initialise   399         the current document.   400         """   401    402         activity = form.get_activity()   403    404         # Transform, adding enumerations/ranges.   405    406         if self.init_resources.has_key(activity):   407             init_xsl = self.prepare_initialiser(activity)   408             form.set_document(   409                 self.get_result(   410                     [init_xsl], form.get_document(), stylesheet_parameters,   411                     stylesheet_expressions, references   412                     )   413                 )   414    415     def respond_to_document(self, trans, form):   416    417         """   418         Using the given transaction 'trans' and 'form' information, perform the   419         parts of the current activity which rely on a populated version of the   420         current document.   421         """   422    423         pass   424    425     def create_output(self, trans, form, content_type=None,   426         stylesheet_parameters=None, stylesheet_expressions=None, references=None):   427    428         """   429         Using the given transaction 'trans' and 'form' information, create the   430         output for the current activity using the previously set attributes in   431         the transaction.   432         """   433    434         attributes = trans.get_attributes()   435         in_page_resource = self.get_in_page_resource(trans)   436         parameters = form.get_parameters()   437    438         # Start the response.   439    440         if attributes.has_key("encoding"):   441             encoding = attributes["encoding"] # NOTE: Potentially redundant.   442         elif hasattr(self, "encoding"):   443             encoding = self.encoding   444         else:   445             encoding = trans.default_charset   446    447         content_type = content_type or WebStack.Generic.ContentType("application/xhtml+xml", encoding)   448         trans.set_content_type(content_type)   449    450         # Ensure that an output stylesheet exists.   451    452         stylesheet_parameters = stylesheet_parameters or {}   453    454         if in_page_resource:   455             trans_xsl = self.prepare_fragment(in_page_resource)   456             stylesheet_parameters.update(self.prepare_parameters(parameters))   457         else:   458             trans_xsl = self.prepare_output(form.get_activity())   459    460         # Complete the response.   461    462         self.send_output(trans, [trans_xsl], form.get_document(),   463             stylesheet_parameters, stylesheet_expressions, references)   464    465     # General helper methods.   466    467     def add_elements(self, positions, *element_names):   468    469         """   470         At the given 'positions', typically obtained as "selectors", add the   471         hierarchy of elements given in the 'element_names' parameters.   472         """   473    474         XSLForms.Utils.add_elements(positions, *element_names)   475    476     def remove_elements(self, positions):   477    478         """   479         Remove elements at the given 'positions', typically obtained as   480         "selectors".   481         """   482    483         XSLForms.Utils.remove_elements(positions)   484    485 def prepare_output(self, output_identifier):   486    487     """   488     Prepare the output stylesheet for the resource class or object 'self'   489     corresponding to the given 'output_identifier'. Return the template path   490     and the output stylesheet path in a 2-tuple.   491     """   492    493     template_filename, output_filename = self.template_resources[output_identifier]   494     output_path = os.path.abspath(os.path.join(self.resource_dir, output_filename))   495     template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename))   496     XSLForms.Prepare.ensure_stylesheet(template_path, output_path)   497     return template_path, output_path   498    499 def prepare_fragment(self, fragment_identifier):   500    501     """   502     Prepare the output stylesheet for the resource class or object 'self'   503     corresponding to the given 'fragment_identifier'. Return the template path   504     and the output stylesheet path in a 2-tuple.   505     """   506    507     output_identifier, fragment_filename, node_identifier = self.in_page_resources[fragment_identifier]   508     fragment_path = os.path.abspath(os.path.join(self.resource_dir, fragment_filename))   509     template_filename, output_filename = self.template_resources[output_identifier]   510     template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename))   511     XSLForms.Prepare.ensure_stylesheet_fragment(template_path, fragment_path, node_identifier)   512     return template_path, fragment_path   513    514 def prepare_initialiser(self, input_identifier, init_enumerations):   515    516     """   517     Prepare the initialising stylesheet for the resource class or object 'self'   518     corresponding to the given 'input_identifier' and 'init_enumerations' flag.   519     Return the template path and the initialising stylesheet path in a 2-tuple.   520     """   521    522     template_filename, input_filename = self.init_resources[input_identifier]   523     input_path = os.path.abspath(os.path.join(self.resource_dir, input_filename))   524     template_path = os.path.abspath(os.path.join(self.resource_dir, template_filename))   525     XSLForms.Prepare.ensure_input_stylesheet(template_path, input_path, init_enumerations)   526     return template_path, input_path   527    528 def prepare_resources(cls):   529    530     "Prepare the resources associated with the class 'cls'."   531    532     for output_identifier in cls.template_resources.keys():   533         prepare_output(cls, output_identifier)   534     for fragment_identifier in cls.in_page_resources.keys():   535         prepare_fragment(cls, fragment_identifier)   536    537     # NOTE: Using init_enumerations=1 here.   538    539     for input_identifier in cls.init_resources.keys():   540         prepare_initialiser(cls, input_identifier, 1)   541    542 # Convenience methods for specifying resources.   543    544 def split(filename):   545    546     """   547     Return a tuple containing the directory and filename without extension for   548     'filename'.   549     """   550    551     d, leafname = os.path.split(filename)   552     name, ext = os.path.splitext(leafname)   553     return d, name   554    555 def output(template_filename):   556    557     """   558     Return a tuple containing the 'template_filename' and a suitable output   559     stylesheet filename.   560     """   561    562     d, name = split(template_filename)   563     output_name = name.replace("_template", "_output") + os.path.extsep + "xsl"   564     return (template_filename, os.path.join(d, output_name))   565    566 def input(template_filename):   567    568     """   569     Return a tuple containing the 'template_filename' and a suitable output   570     stylesheet filename.   571     """   572    573     d, name = split(template_filename)   574     input_name = name.replace("_template", "_input") + os.path.extsep + "xsl"   575     return (template_filename, os.path.join(d, input_name))   576    577 def resources(filename, d="Resources"):   578    579     """   580     Return the resource directory for the given 'filename', using the optional   581     directory name 'd' to indicate the directory relative to the directory of   582     'filename' (or the default directory name, indicating that the directory   583     called "Resources" - a sibling of 'filename' - is the resource directory).   584    585     It is envisaged that callers provide the value of the __file__ special   586     variable to get the resource directory relative to a particular module.   587     """   588    589     return os.path.join(os.path.split(filename)[0], d)   590    591 # vim: tabstop=4 expandtab shiftwidth=4