2009-03-13 Xan Lopez <xlopez@igalia.com>
[WebKit-https.git] / PlanetWebKit / planet / planet / htmltmpl.py
1
2 """ A templating engine for separation of code and HTML.
3
4     The documentation of this templating engine is separated to two parts:
5     
6         1. Description of the templating language.
7            
8         2. Documentation of classes and API of this module that provides
9            a Python implementation of the templating language.
10     
11     All the documentation can be found in 'doc' directory of the
12     distribution tarball or at the homepage of the engine.
13     Latest versions of this module are also available at that website.
14
15     You can use and redistribute this module under conditions of the
16     GNU General Public License that can be found either at
17     [ http://www.gnu.org/ ] or in file "LICENSE" contained in the
18     distribution tarball of this module.
19
20     Copyright (c) 2001 Tomas Styblo, tripie@cpan.org
21
22     @name           htmltmpl
23     @version        1.22
24     @author-name    Tomas Styblo
25     @author-email   tripie@cpan.org
26     @website        http://htmltmpl.sourceforge.net/
27     @license-name   GNU GPL
28     @license-url    http://www.gnu.org/licenses/gpl.html
29 """
30
31 __version__ = 1.22
32 __author__ = "Tomas Styblo (tripie@cpan.org)"
33
34 # All imported modules are part of the standard Python library.
35
36 from types import *
37 import re
38 import os
39 import os.path
40 import pprint       # only for debugging
41 import sys
42 import copy
43 import cgi          # for HTML escaping of variables
44 import urllib       # for URL escaping of variables
45 import cPickle      # for template compilation
46 import gettext
47
48 INCLUDE_DIR = "inc"
49
50 # Total number of possible parameters.
51 # Increment if adding a parameter to any statement.
52 PARAMS_NUMBER = 3
53
54 # Relative positions of parameters in TemplateCompiler.tokenize().
55 PARAM_NAME = 1
56 PARAM_ESCAPE = 2
57 PARAM_GLOBAL = 3
58 PARAM_GETTEXT_STRING = 1
59
60 # Find a way to lock files. Currently implemented only for UNIX and windows.
61 LOCKTYPE_FCNTL = 1
62 LOCKTYPE_MSVCRT = 2
63 LOCKTYPE = None
64 try:
65     import fcntl
66 except:
67     try:
68         import msvcrt
69     except:
70         LOCKTYPE = None
71     else:
72         LOCKTYPE = LOCKTYPE_MSVCRT
73 else:
74     LOCKTYPE = LOCKTYPE_FCNTL
75 LOCK_EX = 1
76 LOCK_SH = 2
77 LOCK_UN = 3
78
79 ##############################################
80 #          CLASS: TemplateManager            #
81 ##############################################
82
83 class TemplateManager:
84     """  Class that manages compilation and precompilation of templates.
85     
86          You should use this class whenever you work with templates
87          that are stored in a file. The class can create a compiled
88          template and transparently manage its precompilation. It also
89          keeps the precompiled templates up-to-date by modification times
90          comparisons. 
91     """
92
93     def __init__(self, include=1, max_include=5, precompile=1, comments=1,
94                  gettext=0, debug=0):
95         """ Constructor.
96         
97             @header
98             __init__(include=1, max_include=5, precompile=1, comments=1,
99                      gettext=0, debug=0)
100             
101             @param include Enable or disable included templates.
102             This optional parameter can be used to enable or disable
103             <em>TMPL_INCLUDE</em> inclusion of templates. Disabling of
104             inclusion can improve performance a bit. The inclusion is
105             enabled by default.
106       
107             @param max_include Maximum depth of nested inclusions.
108             This optional parameter can be used to specify maximum depth of
109             nested <em>TMPL_INCLUDE</em> inclusions. It defaults to 5.
110             This setting prevents infinite recursive inclusions.
111             
112             @param precompile Enable or disable precompilation of templates.
113             This optional parameter can be used to enable or disable
114             creation and usage of precompiled templates.
115       
116             A precompiled template is saved to the same directory in
117             which the main template file is located. You need write
118             permissions to that directory.
119
120             Precompilation provides a significant performance boost because
121             it's not necessary to parse the templates over and over again.
122             The boost is especially noticeable when templates that include
123             other templates are used.
124             
125             Comparison of modification times of the main template and all
126             included templates is used to ensure that the precompiled
127             templates are up-to-date. Templates are also recompiled if the
128             htmltmpl module is updated.
129
130             The <em>TemplateError</em>exception is raised when the precompiled
131             template cannot be saved. Precompilation is enabled by default.
132
133             Precompilation is available only on UNIX and Windows platforms,
134             because proper file locking which is necessary to ensure
135             multitask safe behaviour is platform specific and is not
136             implemented for other platforms. Attempts to enable precompilation
137             on the other platforms result in raise of the
138             <em>TemplateError</em> exception.
139             
140             @param comments Enable or disable template comments.
141             This optional parameter can be used to enable or disable
142             template comments.
143             Disabling of the comments can improve performance a bit.
144             Comments are enabled by default.
145             
146             @param gettext Enable or disable gettext support.
147
148             @param debug Enable or disable debugging messages.
149             This optional parameter is a flag that can be used to enable
150             or disable debugging messages which are printed to the standard
151             error output. The debugging messages are disabled by default.
152         """
153         # Save the optional parameters.
154         # These values are not modified by any method.
155         self._include = include
156         self._max_include = max_include
157         self._precompile = precompile
158         self._comments = comments
159         self._gettext = gettext
160         self._debug = debug
161
162         # Find what module to use to lock files.
163         # File locking is necessary for the 'precompile' feature to be
164         # multitask/thread safe. Currently it works only on UNIX
165         # and Windows. Anyone willing to implement it on Mac ?
166         if precompile and not LOCKTYPE:
167                 raise TemplateError, "Template precompilation is not "\
168                                      "available on this platform."
169         self.DEB("INIT DONE")
170
171     def prepare(self, file):
172         """ Preprocess, parse, tokenize and compile the template.
173             
174             If precompilation is enabled then this method tries to load
175             a precompiled form of the template from the same directory
176             in which the template source file is located. If it succeeds,
177             then it compares modification times stored in the precompiled
178             form to modification times of source files of the template,
179             including source files of all templates included via the
180             <em>TMPL_INCLUDE</em> statements. If any of the modification times
181             differs, then the template is recompiled and the precompiled
182             form updated.
183             
184             If precompilation is disabled, then this method parses and
185             compiles the template.
186             
187             @header prepare(file)
188             
189             @return Compiled template.
190             The methods returns an instance of the <em>Template</em> class
191             which is a compiled form of the template. This instance can be
192             used as input for the <em>TemplateProcessor</em>.
193             
194             @param file Path to the template file to prepare.
195             The method looks for the template file in current directory
196             if the parameter is a relative path. All included templates must
197             be placed in subdirectory <strong>'inc'</strong> of the 
198             directory in which the main template file is located.
199         """
200         compiled = None
201         if self._precompile:
202             if self.is_precompiled(file):
203                 try:
204                     precompiled = self.load_precompiled(file)
205                 except PrecompiledError, template:
206                     print >> sys.stderr, "Htmltmpl: bad precompiled "\
207                                          "template '%s' removed" % template
208                     compiled = self.compile(file)
209                     self.save_precompiled(compiled)
210                 else:
211                     precompiled.debug(self._debug)
212                     compile_params = (self._include, self._max_include,
213                                       self._comments, self._gettext)
214                     if precompiled.is_uptodate(compile_params):
215                         self.DEB("PRECOMPILED: UPTODATE")
216                         compiled = precompiled
217                     else:
218                         self.DEB("PRECOMPILED: NOT UPTODATE")
219                         compiled = self.update(precompiled)
220             else:
221                 self.DEB("PRECOMPILED: NOT PRECOMPILED")
222                 compiled = self.compile(file)
223                 self.save_precompiled(compiled)
224         else:
225             self.DEB("PRECOMPILATION DISABLED")
226             compiled = self.compile(file)
227         return compiled
228     
229     def update(self, template):
230         """ Update (recompile) a compiled template.
231         
232             This method recompiles a template compiled from a file.
233             If precompilation is enabled then the precompiled form saved on
234             disk is also updated.
235             
236             @header update(template)
237             
238             @return Recompiled template.
239             It's ensured that the returned template is up-to-date.
240             
241             @param template A compiled template.
242             This parameter should be an instance of the <em>Template</em>
243             class, created either by the <em>TemplateManager</em> or by the
244             <em>TemplateCompiler</em>. The instance must represent a template
245             compiled from a file on disk.
246         """
247         self.DEB("UPDATE")
248         updated = self.compile(template.file())
249         if self._precompile:
250             self.save_precompiled(updated)
251         return updated
252
253     ##############################################
254     #              PRIVATE METHODS               #
255     ##############################################    
256
257     def DEB(self, str):
258         """ Print debugging message to stderr if debugging is enabled. 
259             @hidden
260         """
261         if self._debug: print >> sys.stderr, str
262
263     def lock_file(self, file, lock):
264         """ Provide platform independent file locking.
265             @hidden
266         """
267         fd = file.fileno()
268         if LOCKTYPE == LOCKTYPE_FCNTL:
269             if lock == LOCK_SH:
270                 fcntl.flock(fd, fcntl.LOCK_SH)
271             elif lock == LOCK_EX:
272                 fcntl.flock(fd, fcntl.LOCK_EX)
273             elif lock == LOCK_UN:
274                 fcntl.flock(fd, fcntl.LOCK_UN)
275             else:
276                 raise TemplateError, "BUG: bad lock in lock_file"
277         elif LOCKTYPE == LOCKTYPE_MSVCRT:
278             if lock == LOCK_SH:
279                 # msvcrt does not support shared locks :-(
280                 msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
281             elif lock == LOCK_EX:
282                 msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
283             elif lock == LOCK_UN:
284                 msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
285             else:
286                 raise TemplateError, "BUG: bad lock in lock_file"
287         else:
288             raise TemplateError, "BUG: bad locktype in lock_file"
289
290     def compile(self, file):
291         """ Compile the template.
292             @hidden
293         """
294         return TemplateCompiler(self._include, self._max_include,
295                                 self._comments, self._gettext,
296                                 self._debug).compile(file)
297     
298     def is_precompiled(self, file):
299         """ Return true if the template is already precompiled on the disk.
300             This method doesn't check whether the compiled template is
301             uptodate.
302             @hidden
303         """
304         filename = file + "c"   # "template.tmplc"
305         if os.path.isfile(filename):
306             return 1
307         else:
308             return 0
309         
310     def load_precompiled(self, file):
311         """ Load precompiled template from disk.
312
313             Remove the precompiled template file and recompile it
314             if the file contains corrupted or unpicklable data.
315             
316             @hidden
317         """
318         filename = file + "c"   # "template.tmplc"
319         self.DEB("LOADING PRECOMPILED")
320         try:
321             remove_bad = 0
322             file = None
323             try:
324                 file = open(filename, "rb")
325                 self.lock_file(file, LOCK_SH)
326                 precompiled = cPickle.load(file)
327             except IOError, (errno, errstr):
328                 raise TemplateError, "IO error in load precompiled "\
329                                      "template '%s': (%d) %s"\
330                                      % (filename, errno, errstr)
331             except cPickle.UnpicklingError:
332                 remove_bad = 1
333                 raise PrecompiledError, filename
334             except:
335                 remove_bad = 1
336                 raise
337             else:
338                 return precompiled
339         finally:
340             if file:
341                 self.lock_file(file, LOCK_UN)
342                 file.close()
343             if remove_bad and os.path.isfile(filename):
344                 # X: We may lose the original exception here, raising OSError.
345                 os.remove(filename)
346                 
347     def save_precompiled(self, template):
348         """ Save compiled template to disk in precompiled form.
349             
350             Associated metadata is also saved. It includes: filename of the
351             main template file, modification time of the main template file,
352             modification times of all included templates and version of the
353             htmltmpl module which compiled the template.
354             
355             The method removes a file which is saved only partially because
356             of some error.
357             
358             @hidden
359         """
360         filename = template.file() + "c"   # creates "template.tmplc"
361         # Check if we have write permission to the template's directory.
362         template_dir = os.path.dirname(os.path.abspath(filename))
363         if not os.access(template_dir, os.W_OK):
364             raise TemplateError, "Cannot save precompiled templates "\
365                                  "to '%s': write permission denied."\
366                                  % template_dir
367         try:
368             remove_bad = 0
369             file = None
370             try:
371                 file = open(filename, "wb")   # may truncate existing file
372                 self.lock_file(file, LOCK_EX)
373                 BINARY = 1
374                 READABLE = 0
375                 if self._debug:
376                     cPickle.dump(template, file, READABLE)
377                 else:
378                     cPickle.dump(template, file, BINARY)
379             except IOError, (errno, errstr):
380                 remove_bad = 1
381                 raise TemplateError, "IO error while saving precompiled "\
382                                      "template '%s': (%d) %s"\
383                                       % (filename, errno, errstr)
384             except cPickle.PicklingError, error:
385                 remove_bad = 1
386                 raise TemplateError, "Pickling error while saving "\
387                                      "precompiled template '%s': %s"\
388                                      % (filename, error)
389             except:
390                 remove_bad = 1
391                 raise
392             else:
393                 self.DEB("SAVING PRECOMPILED")
394         finally:
395             if file:
396                 self.lock_file(file, LOCK_UN)
397                 file.close()
398             if remove_bad and os.path.isfile(filename):
399                 # X: We may lose the original exception here, raising OSError.
400                 os.remove(filename)
401
402
403 ##############################################
404 #          CLASS: TemplateProcessor          #
405 ##############################################
406
407 class TemplateProcessor:
408     """ Fill the template with data and process it.
409
410         This class provides actual processing of a compiled template.
411         Use it to set template variables and loops and then obtain
412         result of the processing.
413     """
414
415     def __init__(self, html_escape=1, magic_vars=1, global_vars=0, debug=0):
416         """ Constructor.
417
418             @header __init__(html_escape=1, magic_vars=1, global_vars=0,
419                              debug=0)
420
421             @param html_escape Enable or disable HTML escaping of variables.
422             This optional parameter is a flag that can be used to enable or
423             disable automatic HTML escaping of variables.
424             All variables are by default automatically HTML escaped. 
425             The escaping process substitutes HTML brackets, ampersands and
426             double quotes with appropriate HTML entities.
427             
428             @param magic_vars Enable or disable loop magic variables.
429             This parameter can be used to enable or disable
430             "magic" context variables, that are automatically defined inside
431             loops. Magic variables are enabled by default.
432
433             Refer to the language specification for description of these
434             magic variables.
435       
436             @param global_vars Globally activate global lookup of variables.
437             This optional parameter is a flag that can be used to specify
438             whether variables which cannot be found in the current scope
439             should be automatically looked up in enclosing scopes.
440
441             Automatic global lookup is disabled by default. Global lookup
442             can be overriden on a per-variable basis by the
443             <strong>GLOBAL</strong> parameter of a <strong>TMPL_VAR</strong>
444             statement.
445
446             @param debug Enable or disable debugging messages.
447         """
448         self._html_escape = html_escape
449         self._magic_vars = magic_vars
450         self._global_vars = global_vars
451         self._debug = debug        
452
453         # Data structure containing variables and loops set by the
454         # application. Use debug=1, process some template and
455         # then check stderr to see how the structure looks.
456         # It's modified only by set() and reset() methods.
457         self._vars = {}        
458
459         # Following variables are for multipart templates.
460         self._current_part = 1
461         self._current_pos = 0
462
463     def set(self, var, value):
464         """ Associate a value with top-level template variable or loop.
465
466             A template identifier can represent either an ordinary variable
467             (string) or a loop.
468
469             To assign a value to a string identifier pass a scalar
470             as the 'value' parameter. This scalar will be automatically
471             converted to string.
472
473             To assign a value to a loop identifier pass a list of mappings as
474             the 'value' parameter. The engine iterates over this list and
475             assigns values from the mappings to variables in a template loop
476             block if a key in the mapping corresponds to a name of a variable
477             in the loop block. The number of mappings contained in this list
478             is equal to number of times the loop block is repeated in the
479             output.
480       
481             @header set(var, value)
482             @return No return value.
483
484             @param var Name of template variable or loop.
485             @param value The value to associate.
486             
487         """
488         # The correctness of character case is verified only for top-level
489         # variables.
490         if self.is_ordinary_var(value):
491             # template top-level ordinary variable
492             if not var.islower():
493                 raise TemplateError, "Invalid variable name '%s'." % var
494         elif type(value) == ListType:
495             # template top-level loop
496             if var != var.capitalize():
497                 raise TemplateError, "Invalid loop name '%s'." % var
498         else:
499             raise TemplateError, "Value of toplevel variable '%s' must "\
500                                  "be either a scalar or a list." % var
501         self._vars[var] = value
502         self.DEB("VALUE SET: " + str(var))
503         
504     def reset(self, keep_data=0):
505         """ Reset the template data.
506
507             This method resets the data contained in the template processor
508             instance. The template processor instance can be used to process
509             any number of templates, but this method must be called after
510             a template is processed to reuse the instance,
511
512             @header reset(keep_data=0)
513             @return No return value.
514
515             @param keep_data Do not reset the template data.
516             Use this flag if you do not want the template data to be erased.
517             This way you can reuse the data contained in the instance of
518             the <em>TemplateProcessor</em>.
519         """
520         self._current_part = 1
521         self._current_pos = 0
522         if not keep_data:
523             self._vars.clear()
524         self.DEB("RESET")
525
526     def process(self, template, part=None):
527         """ Process a compiled template. Return the result as string.
528
529             This method actually processes a template and returns
530             the result.
531
532             @header process(template, part=None)
533             @return Result of the processing as string.
534
535             @param template A compiled template.
536             Value of this parameter must be an instance of the
537             <em>Template</em> class created either by the
538             <em>TemplateManager</em> or by the <em>TemplateCompiler</em>.
539
540             @param part The part of a multipart template to process.
541             This parameter can be used only together with a multipart
542             template. It specifies the number of the part to process.
543             It must be greater than zero, because the parts are numbered
544             from one.
545
546             The parts must be processed in the right order. You
547             cannot process a part which precedes an already processed part.
548
549             If this parameter is not specified, then the whole template
550             is processed, or all remaining parts are processed.
551         """
552         self.DEB("APP INPUT:")
553         if self._debug: pprint.pprint(self._vars, sys.stderr)
554         if part != None and (part == 0 or part < self._current_part):
555             raise TemplateError, "process() - invalid part number"
556
557         # This flag means "jump behind the end of current statement" or
558         # "skip the parameters of current statement".
559         # Even parameters that actually are not present in the template
560         # do appear in the list of tokens as empty items !
561         skip_params = 0 
562
563         # Stack for enabling or disabling output in response to TMPL_IF,
564         # TMPL_UNLESS, TMPL_ELSE and TMPL_LOOPs with no passes.
565         output_control = []
566         ENABLE_OUTPUT = 1
567         DISABLE_OUTPUT = 0
568         
569         # Stacks for data related to loops.
570         loop_name = []        # name of a loop
571         loop_pass = []        # current pass of a loop (counted from zero)
572         loop_start = []       # index of loop start in token list
573         loop_total = []       # total number of passes in a loop
574         
575         tokens = template.tokens()
576         len_tokens = len(tokens)
577         out = ""              # buffer for processed output
578
579         # Recover position at which we ended after processing of last part.
580         i = self._current_pos
581             
582         # Process the list of tokens.
583         while 1:
584             if i == len_tokens: break            
585             if skip_params:   
586                 # Skip the parameters following a statement.
587                 skip_params = 0
588                 i += PARAMS_NUMBER
589                 continue
590
591             token = tokens[i]
592             if token.startswith("<TMPL_") or \
593                token.startswith("</TMPL_"):
594                 if token == "<TMPL_VAR":
595                     # TMPL_VARs should be first. They are the most common.
596                     var = tokens[i + PARAM_NAME]
597                     if not var:
598                         raise TemplateError, "No identifier in <TMPL_VAR>."
599                     escape = tokens[i + PARAM_ESCAPE]
600                     globalp = tokens[i + PARAM_GLOBAL]
601                     skip_params = 1
602                     
603                     # If output of current block is not disabled then append
604                     # the substitued and escaped variable to the output.
605                     if DISABLE_OUTPUT not in output_control:
606                         value = str(self.find_value(var, loop_name, loop_pass,
607                                                     loop_total, globalp))
608                         out += self.escape(value, escape)
609                         self.DEB("VAR: " + str(var))
610
611                 elif token == "<TMPL_LOOP":
612                     var = tokens[i + PARAM_NAME]
613                     if not var:
614                         raise TemplateError, "No identifier in <TMPL_LOOP>."
615                     skip_params = 1
616
617                     # Find total number of passes in this loop.
618                     passtotal = self.find_value(var, loop_name, loop_pass,
619                                                 loop_total)
620                     if not passtotal: passtotal = 0
621                     # Push data for this loop on the stack.
622                     loop_total.append(passtotal)
623                     loop_start.append(i)
624                     loop_pass.append(0)
625                     loop_name.append(var)
626
627                     # Disable output of loop block if the number of passes
628                     # in this loop is zero.
629                     if passtotal == 0:
630                         # This loop is empty.
631                         output_control.append(DISABLE_OUTPUT)
632                         self.DEB("LOOP: DISABLE: " + str(var))
633                     else:
634                         output_control.append(ENABLE_OUTPUT)
635                         self.DEB("LOOP: FIRST PASS: %s TOTAL: %d"\
636                                  % (var, passtotal))
637
638                 elif token == "<TMPL_IF":
639                     var = tokens[i + PARAM_NAME]
640                     if not var:
641                         raise TemplateError, "No identifier in <TMPL_IF>."
642                     globalp = tokens[i + PARAM_GLOBAL]
643                     skip_params = 1
644                     if self.find_value(var, loop_name, loop_pass,
645                                        loop_total, globalp):
646                         output_control.append(ENABLE_OUTPUT)
647                         self.DEB("IF: ENABLE: " + str(var))
648                     else:
649                         output_control.append(DISABLE_OUTPUT)
650                         self.DEB("IF: DISABLE: " + str(var))
651      
652                 elif token == "<TMPL_UNLESS":
653                     var = tokens[i + PARAM_NAME]
654                     if not var:
655                         raise TemplateError, "No identifier in <TMPL_UNLESS>."
656                     globalp = tokens[i + PARAM_GLOBAL]
657                     skip_params = 1
658                     if self.find_value(var, loop_name, loop_pass,
659                                       loop_total, globalp):
660                         output_control.append(DISABLE_OUTPUT)
661                         self.DEB("UNLESS: DISABLE: " + str(var))
662                     else:
663                         output_control.append(ENABLE_OUTPUT)
664                         self.DEB("UNLESS: ENABLE: " + str(var))
665      
666                 elif token == "</TMPL_LOOP":
667                     skip_params = 1
668                     if not loop_name:
669                         raise TemplateError, "Unmatched </TMPL_LOOP>."
670                     
671                     # If this loop was not disabled, then record the pass.
672                     if loop_total[-1] > 0: loop_pass[-1] += 1
673                     
674                     if loop_pass[-1] == loop_total[-1]:
675                         # There are no more passes in this loop. Pop
676                         # the loop from stack.
677                         loop_pass.pop()
678                         loop_name.pop()
679                         loop_start.pop()
680                         loop_total.pop()
681                         output_control.pop()
682                         self.DEB("LOOP: END")
683                     else:
684                         # Jump to the beggining of this loop block 
685                         # to process next pass of the loop.
686                         i = loop_start[-1]
687                         self.DEB("LOOP: NEXT PASS")
688      
689                 elif token == "</TMPL_IF":
690                     skip_params = 1
691                     if not output_control:
692                         raise TemplateError, "Unmatched </TMPL_IF>."
693                     output_control.pop()
694                     self.DEB("IF: END")
695      
696                 elif token == "</TMPL_UNLESS":
697                     skip_params = 1
698                     if not output_control:
699                         raise TemplateError, "Unmatched </TMPL_UNLESS>."
700                     output_control.pop()
701                     self.DEB("UNLESS: END")
702      
703                 elif token == "<TMPL_ELSE":
704                     skip_params = 1
705                     if not output_control:
706                         raise TemplateError, "Unmatched <TMPL_ELSE>."
707                     if output_control[-1] == DISABLE_OUTPUT:
708                         # Condition was false, activate the ELSE block.
709                         output_control[-1] = ENABLE_OUTPUT
710                         self.DEB("ELSE: ENABLE")
711                     elif output_control[-1] == ENABLE_OUTPUT:
712                         # Condition was true, deactivate the ELSE block.
713                         output_control[-1] = DISABLE_OUTPUT
714                         self.DEB("ELSE: DISABLE")
715                     else:
716                         raise TemplateError, "BUG: ELSE: INVALID FLAG"
717
718                 elif token == "<TMPL_BOUNDARY":
719                     if part and part == self._current_part:
720                         self.DEB("BOUNDARY ON")
721                         self._current_part += 1
722                         self._current_pos = i + 1 + PARAMS_NUMBER
723                         break
724                     else:
725                         skip_params = 1
726                         self.DEB("BOUNDARY OFF")
727                         self._current_part += 1
728
729                 elif token == "<TMPL_INCLUDE":
730                     # TMPL_INCLUDE is left in the compiled template only
731                     # when it was not replaced by the parser.
732                     skip_params = 1
733                     filename = tokens[i + PARAM_NAME]
734                     out += """
735                         <br />
736                         <p>
737                         <strong>HTMLTMPL WARNING:</strong><br />
738                         Cannot include template: <strong>%s</strong>
739                         </p>
740                         <br />
741                     """ % filename
742                     self.DEB("CANNOT INCLUDE WARNING")
743
744                 elif token == "<TMPL_GETTEXT":
745                     skip_params = 1
746                     if DISABLE_OUTPUT not in output_control:
747                         text = tokens[i + PARAM_GETTEXT_STRING]
748                         out += gettext.gettext(text)
749                         self.DEB("GETTEXT: " + text)
750                     
751                 else:
752                     # Unknown processing directive.
753                     raise TemplateError, "Invalid statement %s>." % token
754                      
755             elif DISABLE_OUTPUT not in output_control:
756                 # Raw textual template data.
757                 # If output of current block is not disabled, then 
758                 # append template data to the output buffer.
759                 out += token
760                 
761             i += 1
762             # end of the big while loop
763         
764         # Check whether all opening statements were closed.
765         if loop_name: raise TemplateError, "Missing </TMPL_LOOP>."
766         if output_control: raise TemplateError, "Missing </TMPL_IF> or </TMPL_UNLESS>"
767         return out
768
769     ##############################################
770     #              PRIVATE METHODS               #
771     ##############################################
772
773     def DEB(self, str):
774         """ Print debugging message to stderr if debugging is enabled.
775             @hidden
776         """
777         if self._debug: print >> sys.stderr, str
778
779     def find_value(self, var, loop_name, loop_pass, loop_total,
780                    global_override=None):
781         """ Search the self._vars data structure to find variable var
782             located in currently processed pass of a loop which
783             is currently being processed. If the variable is an ordinary
784             variable, then return it.
785             
786             If the variable is an identificator of a loop, then 
787             return the total number of times this loop will
788             be executed.
789             
790             Return an empty string, if the variable is not
791             found at all.
792
793             @hidden
794         """
795         # Search for the requested variable in magic vars if the name
796         # of the variable starts with "__" and if we are inside a loop.
797         if self._magic_vars and var.startswith("__") and loop_name:
798             return self.magic_var(var, loop_pass[-1], loop_total[-1])
799                     
800         # Search for an ordinary variable or for a loop.
801         # Recursively search in self._vars for the requested variable.
802         scope = self._vars
803         globals = []
804         for i in range(len(loop_name)):            
805             # If global lookup is on then push the value on the stack.
806             if ((self._global_vars and global_override != "0") or \
807                  global_override == "1") and scope.has_key(var) and \
808                self.is_ordinary_var(scope[var]):
809                 globals.append(scope[var])
810             
811             # Descent deeper into the hierarchy.
812             if scope.has_key(loop_name[i]) and scope[loop_name[i]]:
813                 scope = scope[loop_name[i]][loop_pass[i]]
814             else:
815                 return ""
816             
817         if scope.has_key(var):
818             # Value exists in current loop.
819             if type(scope[var]) == ListType:
820                 # The requested value is a loop.
821                 # Return total number of its passes.
822                 return len(scope[var])
823             else:
824                 return scope[var]
825         elif globals and \
826              ((self._global_vars and global_override != "0") or \
827                global_override == "1"):
828             # Return globally looked up value.
829             return globals.pop()
830         else:
831             # No value found.
832             if var[0].isupper():
833                 # This is a loop name.
834                 # Return zero, because the user wants to know number
835                 # of its passes.
836                 return 0
837             else:
838                 return ""
839
840     def magic_var(self, var, loop_pass, loop_total):
841         """ Resolve and return value of a magic variable.
842             Raise an exception if the magic variable is not recognized.
843
844             @hidden
845         """
846         self.DEB("MAGIC: '%s', PASS: %d, TOTAL: %d"\
847                  % (var, loop_pass, loop_total))
848         if var == "__FIRST__":
849             if loop_pass == 0:
850                 return 1
851             else:
852                 return 0
853         elif var == "__LAST__":
854             if loop_pass == loop_total - 1:
855                 return 1
856             else:
857                 return 0
858         elif var == "__INNER__":
859             # If this is neither the first nor the last pass.
860             if loop_pass != 0 and loop_pass != loop_total - 1:
861                 return 1
862             else:
863                 return 0        
864         elif var == "__PASS__":
865             # Magic variable __PASS__ counts passes from one.
866             return loop_pass + 1
867         elif var == "__PASSTOTAL__":
868             return loop_total
869         elif var == "__ODD__":
870             # Internally pass numbers stored in loop_pass are counted from
871             # zero. But the template language presents them counted from one.
872             # Therefore we must add one to the actual loop_pass value to get
873             # the value we present to the user.
874             if (loop_pass + 1) % 2 != 0:
875                 return 1
876             else:
877                 return 0
878         elif var.startswith("__EVERY__"):
879             # Magic variable __EVERY__x is never true in first or last pass.
880             if loop_pass != 0 and loop_pass != loop_total - 1:
881                 # Check if an integer follows the variable name.
882                 try:
883                     every = int(var[9:])   # nine is length of "__EVERY__"
884                 except ValueError:
885                     raise TemplateError, "Magic variable __EVERY__x: "\
886                                          "Invalid pass number."
887                 else:
888                     if not every:
889                         raise TemplateError, "Magic variable __EVERY__x: "\
890                                              "Pass number cannot be zero."
891                     elif (loop_pass + 1) % every == 0:
892                         self.DEB("MAGIC: EVERY: " + str(every))
893                         return 1
894                     else:
895                         return 0
896             else:
897                 return 0
898         else:
899             raise TemplateError, "Invalid magic variable '%s'." % var
900
901     def escape(self, str, override=""):
902         """ Escape a string either by HTML escaping or by URL escaping.
903             @hidden
904         """
905         ESCAPE_QUOTES = 1
906         if (self._html_escape and override != "NONE" and override != "0" and \
907             override != "URL") or override == "HTML" or override == "1":
908             return cgi.escape(str, ESCAPE_QUOTES)
909         elif override == "URL":
910             return urllib.quote_plus(str)
911         else:
912             return str
913
914     def is_ordinary_var(self, var):
915         """ Return true if var is a scalar. (not a reference to loop)
916             @hidden
917         """
918         if type(var) == StringType or type(var) == IntType or \
919            type(var) == LongType or type(var) == FloatType:
920             return 1
921         else:
922             return 0
923
924
925 ##############################################
926 #          CLASS: TemplateCompiler           #
927 ##############################################
928
929 class TemplateCompiler:
930     """ Preprocess, parse, tokenize and compile the template.
931
932         This class parses the template and produces a 'compiled' form
933         of it. This compiled form is an instance of the <em>Template</em>
934         class. The compiled form is used as input for the TemplateProcessor
935         which uses it to actually process the template.
936
937         This class should be used direcly only when you need to compile
938         a template from a string. If your template is in a file, then you
939         should use the <em>TemplateManager</em> class which provides
940         a higher level interface to this class and also can save the
941         compiled template to disk in a precompiled form.
942     """
943
944     def __init__(self, include=1, max_include=5, comments=1, gettext=0,
945                  debug=0):
946         """ Constructor.
947
948         @header __init__(include=1, max_include=5, comments=1, gettext=0,
949                          debug=0)
950
951         @param include Enable or disable included templates.
952         @param max_include Maximum depth of nested inclusions.
953         @param comments Enable or disable template comments.
954         @param gettext Enable or disable gettext support.
955         @param debug Enable or disable debugging messages.
956         """
957         
958         self._include = include
959         self._max_include = max_include
960         self._comments = comments
961         self._gettext = gettext
962         self._debug = debug
963         
964         # This is a list of filenames of all included templates.
965         # It's modified by the include_templates() method.
966         self._include_files = []
967
968         # This is a counter of current inclusion depth. It's used to prevent
969         # infinite recursive includes.
970         self._include_level = 0
971     
972     def compile(self, file):
973         """ Compile template from a file.
974
975             @header compile(file)
976             @return Compiled template.
977             The return value is an instance of the <em>Template</em>
978             class.
979
980             @param file Filename of the template.
981             See the <em>prepare()</em> method of the <em>TemplateManager</em>
982             class for exaplanation of this parameter.
983         """
984         
985         self.DEB("COMPILING FROM FILE: " + file)
986         self._include_path = os.path.join(os.path.dirname(file), INCLUDE_DIR)
987         tokens = self.parse(self.read(file))
988         compile_params = (self._include, self._max_include, self._comments,
989                           self._gettext)
990         return Template(__version__, file, self._include_files,
991                         tokens, compile_params, self._debug)
992
993     def compile_string(self, data):
994         """ Compile template from a string.
995
996             This method compiles a template from a string. The
997             template cannot include any templates.
998             <strong>TMPL_INCLUDE</strong> statements are turned into warnings.
999
1000             @header compile_string(data)
1001             @return Compiled template.
1002             The return value is an instance of the <em>Template</em>
1003             class.
1004
1005             @param data String containing the template data.        
1006         """
1007         self.DEB("COMPILING FROM STRING")
1008         self._include = 0
1009         tokens = self.parse(data)
1010         compile_params = (self._include, self._max_include, self._comments,
1011                           self._gettext)
1012         return Template(__version__, None, None, tokens, compile_params,
1013                         self._debug)
1014
1015     ##############################################
1016     #              PRIVATE METHODS               #
1017     ##############################################
1018                 
1019     def DEB(self, str):
1020         """ Print debugging message to stderr if debugging is enabled.
1021             @hidden
1022         """
1023         if self._debug: print >> sys.stderr, str
1024     
1025     def read(self, filename):
1026         """ Read content of file and return it. Raise an error if a problem
1027             occurs.
1028             @hidden
1029         """
1030         self.DEB("READING: " + filename)
1031         try:
1032             f = None
1033             try:
1034                 f = open(filename, "r")
1035                 data = f.read()
1036             except IOError, (errno, errstr):
1037                 raise TemplateError, "IO error while reading template '%s': "\
1038                                      "(%d) %s" % (filename, errno, errstr)
1039             else:
1040                 return data
1041         finally:
1042             if f: f.close()
1043                
1044     def parse(self, template_data):
1045         """ Parse the template. This method is recursively called from
1046             within the include_templates() method.
1047
1048             @return List of processing tokens.
1049             @hidden
1050         """
1051         if self._comments:
1052             self.DEB("PREPROCESS: COMMENTS")
1053             template_data = self.remove_comments(template_data)
1054         tokens = self.tokenize(template_data)
1055         if self._include:
1056             self.DEB("PREPROCESS: INCLUDES")
1057             self.include_templates(tokens)
1058         return tokens
1059
1060     def remove_comments(self, template_data):
1061         """ Remove comments from the template data.
1062             @hidden
1063         """
1064         pattern = r"### .*"
1065         return re.sub(pattern, "", template_data)
1066            
1067     def include_templates(self, tokens):
1068         """ Process TMPL_INCLUDE statements. Use the include_level counter
1069             to prevent infinite recursion. Record paths to all included
1070             templates to self._include_files.
1071             @hidden
1072         """
1073         i = 0
1074         out = ""    # buffer for output
1075         skip_params = 0
1076         
1077         # Process the list of tokens.
1078         while 1:
1079             if i == len(tokens): break
1080             if skip_params:
1081                 skip_params = 0
1082                 i += PARAMS_NUMBER
1083                 continue
1084
1085             token = tokens[i]
1086             if token == "<TMPL_INCLUDE":
1087                 filename = tokens[i + PARAM_NAME]
1088                 if not filename:
1089                     raise TemplateError, "No filename in <TMPL_INCLUDE>."
1090                 self._include_level += 1
1091                 if self._include_level > self._max_include:
1092                     # Do not include the template.
1093                     # Protection against infinite recursive includes.
1094                     skip_params = 1
1095                     self.DEB("INCLUDE: LIMIT REACHED: " + filename)
1096                 else:
1097                     # Include the template.
1098                     skip_params = 0
1099                     include_file = os.path.join(self._include_path, filename)
1100                     self._include_files.append(include_file)
1101                     include_data = self.read(include_file)
1102                     include_tokens = self.parse(include_data)
1103
1104                     # Append the tokens from the included template to actual
1105                     # position in the tokens list, replacing the TMPL_INCLUDE
1106                     # token and its parameters.
1107                     tokens[i:i+PARAMS_NUMBER+1] = include_tokens
1108                     i = i + len(include_tokens)
1109                     self.DEB("INCLUDED: " + filename)
1110                     continue   # Do not increment 'i' below.
1111             i += 1
1112             # end of the main while loop
1113
1114         if self._include_level > 0: self._include_level -= 1
1115         return out
1116     
1117     def tokenize(self, template_data):
1118         """ Split the template into tokens separated by template statements.
1119             The statements itself and associated parameters are also
1120             separately  included in the resulting list of tokens.
1121             Return list of the tokens.
1122
1123             @hidden
1124         """
1125         self.DEB("TOKENIZING TEMPLATE")
1126         # NOTE: The TWO double quotes in character class in the regexp below
1127         # are there only to prevent confusion of syntax highlighter in Emacs.
1128         pattern = r"""
1129             (?:^[ \t]+)?               # eat spaces, tabs (opt.)
1130             (<
1131              (?:!--[ ])?               # comment start + space (opt.)
1132              /?TMPL_[A-Z]+             # closing slash / (opt.) + statement
1133              [ a-zA-Z0-9""/.=:_\\-]*   # this spans also comments ending (--)
1134              >)
1135             [%s]?                      # eat trailing newline (opt.)
1136         """ % os.linesep
1137         rc = re.compile(pattern, re.VERBOSE | re.MULTILINE)
1138         split = rc.split(template_data)
1139         tokens = []
1140         for statement in split:
1141             if statement.startswith("<TMPL_") or \
1142                statement.startswith("</TMPL_") or \
1143                statement.startswith("<!-- TMPL_") or \
1144                statement.startswith("<!-- /TMPL_"):
1145                 # Processing statement.
1146                 statement = self.strip_brackets(statement)
1147                 params = re.split(r"\s+", statement)
1148                 tokens.append(self.find_directive(params))
1149                 tokens.append(self.find_name(params))
1150                 tokens.append(self.find_param("ESCAPE", params))
1151                 tokens.append(self.find_param("GLOBAL", params))
1152             else:
1153                 # "Normal" template data.
1154                 if self._gettext:
1155                     self.DEB("PARSING GETTEXT STRINGS")
1156                     self.gettext_tokens(tokens, statement)
1157                 else:
1158                     tokens.append(statement)
1159         return tokens
1160     
1161     def gettext_tokens(self, tokens, str):
1162         """ Find gettext strings and return appropriate array of
1163             processing tokens.
1164             @hidden
1165         """
1166         escaped = 0
1167         gt_mode = 0
1168         i = 0
1169         buf = ""
1170         while(1):
1171             if i == len(str): break
1172             if str[i] == "\\":
1173                 escaped = 0
1174                 if str[i+1] == "\\":
1175                     buf += "\\"
1176                     i += 2
1177                     continue
1178                 elif str[i+1] == "[" or str[i+1] == "]":
1179                     escaped = 1
1180                 else:
1181                     buf += "\\"
1182             elif str[i] == "[" and str[i+1] == "[":
1183                 if gt_mode:
1184                     if escaped:
1185                         escaped = 0
1186                         buf += "["
1187                     else:
1188                         buf += "["
1189                 else:
1190                     if escaped:
1191                         escaped = 0
1192                         buf += "["
1193                     else:
1194                         tokens.append(buf)
1195                         buf = ""
1196                         gt_mode = 1
1197                         i += 2
1198                         continue
1199             elif str[i] == "]" and str[i+1] == "]":
1200                 if gt_mode:
1201                     if escaped:
1202                         escaped = 0
1203                         buf += "]"
1204                     else:
1205                         self.add_gettext_token(tokens, buf)
1206                         buf = ""
1207                         gt_mode = 0
1208                         i += 2
1209                         continue
1210                 else:
1211                     if escaped:
1212                         escaped = 0
1213                         buf += "]"
1214                     else:
1215                         buf += "]"
1216             else:
1217                 escaped = 0
1218                 buf += str[i]
1219             i += 1
1220             # end of the loop
1221         
1222         if buf:
1223             tokens.append(buf)
1224                 
1225     def add_gettext_token(self, tokens, str):
1226         """ Append a gettext token and gettext string to the tokens array.
1227             @hidden
1228         """
1229         self.DEB("GETTEXT PARSER: TOKEN: " + str)
1230         tokens.append("<TMPL_GETTEXT")
1231         tokens.append(str)
1232         tokens.append(None)
1233         tokens.append(None)
1234     
1235     def strip_brackets(self, statement):
1236         """ Strip HTML brackets (with optional HTML comments) from the
1237             beggining and from the end of a statement.
1238             @hidden
1239         """
1240         if statement.startswith("<!-- TMPL_") or \
1241            statement.startswith("<!-- /TMPL_"):
1242             return statement[5:-4]
1243         else:
1244             return statement[1:-1]
1245
1246     def find_directive(self, params):
1247         """ Extract processing directive (TMPL_*) from a statement.
1248             @hidden
1249         """
1250         directive = params[0]
1251         del params[0]
1252         self.DEB("TOKENIZER: DIRECTIVE: " + directive)
1253         return "<" + directive
1254
1255     def find_name(self, params):
1256         """ Extract identifier from a statement. The identifier can be
1257             specified both implicitely or explicitely as a 'NAME' parameter.
1258             @hidden
1259         """
1260         if len(params) > 0 and '=' not in params[0]:
1261             # implicit identifier
1262             name = params[0]
1263             del params[0]
1264         else:
1265             # explicit identifier as a 'NAME' parameter
1266             name = self.find_param("NAME", params)
1267         self.DEB("TOKENIZER: NAME: " + str(name))
1268         return name
1269
1270     def find_param(self, param, params):
1271         """ Extract value of parameter from a statement.
1272             @hidden
1273         """
1274         for pair in params:
1275             name, value = pair.split("=")
1276             if not name or not value:
1277                 raise TemplateError, "Syntax error in template."
1278             if name == param:
1279                 if value[0] == '"':
1280                     # The value is in double quotes.
1281                     ret_value = value[1:-1]
1282                 else:
1283                     # The value is without double quotes.
1284                     ret_value = value
1285                 self.DEB("TOKENIZER: PARAM: '%s' => '%s'" % (param, ret_value))
1286                 return ret_value
1287         else:
1288             self.DEB("TOKENIZER: PARAM: '%s' => NOT DEFINED" % param)
1289             return None
1290
1291
1292 ##############################################
1293 #              CLASS: Template               #
1294 ##############################################
1295
1296 class Template:
1297     """ This class represents a compiled template.
1298
1299         This class provides storage and methods for the compiled template
1300         and associated metadata. It's serialized by pickle if we need to
1301         save the compiled template to disk in a precompiled form.
1302
1303         You should never instantiate this class directly. Always use the
1304         <em>TemplateManager</em> or <em>TemplateCompiler</em> classes to
1305         create the instances of this class.
1306
1307         The only method which you can directly use is the <em>is_uptodate</em>
1308         method.
1309     """
1310     
1311     def __init__(self, version, file, include_files, tokens, compile_params,
1312                  debug=0):
1313         """ Constructor.
1314             @hidden
1315         """
1316         self._version = version
1317         self._file = file
1318         self._tokens = tokens
1319         self._compile_params = compile_params
1320         self._debug = debug
1321         self._mtime = None        
1322         self._include_mtimes = {}
1323
1324         if not file:
1325             self.DEB("TEMPLATE WAS COMPILED FROM A STRING")
1326             return
1327
1328         # Save modifitcation time of the main template file.           
1329         if os.path.isfile(file):
1330             self._mtime = os.path.getmtime(file)
1331         else:
1332             raise TemplateError, "Template: file does not exist: '%s'" % file
1333
1334         # Save modificaton times of all included template files.
1335         for inc_file in include_files:
1336             if os.path.isfile(inc_file):
1337                 self._include_mtimes[inc_file] = os.path.getmtime(inc_file)
1338             else:
1339                 raise TemplateError, "Template: file does not exist: '%s'"\
1340                                      % inc_file
1341             
1342         self.DEB("NEW TEMPLATE CREATED")
1343
1344     def is_uptodate(self, compile_params=None):
1345         """ Check whether the compiled template is uptodate.
1346
1347             Return true if this compiled template is uptodate.
1348             Return false, if the template source file was changed on the
1349             disk since it was compiled.
1350             Works by comparison of modification times.
1351             Also takes modification times of all included templates
1352             into account.
1353
1354             @header is_uptodate(compile_params=None)
1355             @return True if the template is uptodate, false otherwise.
1356
1357             @param compile_params Only for internal use.
1358             Do not use this optional parameter. It's intended only for
1359             internal use by the <em>TemplateManager</em>.
1360         """
1361         if not self._file:
1362             self.DEB("TEMPLATE COMPILED FROM A STRING")
1363             return 0
1364         
1365         if self._version != __version__:
1366             self.DEB("TEMPLATE: VERSION NOT UPTODATE")
1367             return 0
1368
1369         if compile_params != None and compile_params != self._compile_params:
1370             self.DEB("TEMPLATE: DIFFERENT COMPILATION PARAMS")
1371             return 0
1372     
1373         # Check modification times of the main template and all included
1374         # templates. If the included template no longer exists, then
1375         # the problem will be resolved when the template is recompiled.
1376
1377         # Main template file.
1378         if not (os.path.isfile(self._file) and \
1379                 self._mtime == os.path.getmtime(self._file)):
1380             self.DEB("TEMPLATE: NOT UPTODATE: " + self._file)
1381             return 0        
1382
1383         # Included templates.
1384         for inc_file in self._include_mtimes.keys():
1385             if not (os.path.isfile(inc_file) and \
1386                     self._include_mtimes[inc_file] == \
1387                     os.path.getmtime(inc_file)):
1388                 self.DEB("TEMPLATE: NOT UPTODATE: " + inc_file)
1389                 return 0
1390         else:
1391             self.DEB("TEMPLATE: UPTODATE")
1392             return 1       
1393     
1394     def tokens(self):
1395         """ Get tokens of this template.
1396             @hidden
1397         """
1398         return self._tokens
1399
1400     def file(self):
1401         """ Get filename of the main file of this template.
1402             @hidden
1403         """
1404         return self._file
1405
1406     def debug(self, debug):
1407         """ Get debugging state.
1408             @hidden
1409         """
1410         self._debug = debug
1411
1412     ##############################################
1413     #              PRIVATE METHODS               #
1414     ##############################################
1415
1416     def __getstate__(self):
1417         """ Used by pickle when the class is serialized.
1418             Remove the 'debug' attribute before serialization.
1419             @hidden
1420         """
1421         dict = copy.copy(self.__dict__)
1422         del dict["_debug"]
1423         return dict
1424
1425     def __setstate__(self, dict):
1426         """ Used by pickle when the class is unserialized.
1427             Add the 'debug' attribute.
1428             @hidden
1429         """
1430         dict["_debug"] = 0
1431         self.__dict__ = dict
1432
1433
1434     def DEB(self, str):
1435         """ Print debugging message to stderr.
1436             @hidden
1437         """
1438         if self._debug: print >> sys.stderr, str
1439
1440
1441 ##############################################
1442 #                EXCEPTIONS                  #
1443 ##############################################
1444
1445 class TemplateError(Exception):
1446     """ Fatal exception. Raised on runtime or template syntax errors.
1447
1448         This exception is raised when a runtime error occurs or when a syntax
1449         error in the template is found. It has one parameter which always
1450         is a string containing a description of the error.
1451
1452         All potential IOError exceptions are handled by the module and are
1453         converted to TemplateError exceptions. That means you should catch the
1454         TemplateError exception if there is a possibility that for example
1455         the template file will not be accesssible.
1456
1457         The exception can be raised by constructors or by any method of any
1458         class.
1459         
1460         The instance is no longer usable when this exception is raised. 
1461     """
1462
1463     def __init__(self, error):
1464         """ Constructor.
1465             @hidden
1466         """
1467         Exception.__init__(self, "Htmltmpl error: " + error)
1468
1469
1470 class PrecompiledError(Exception):
1471     """ This exception is _PRIVATE_ and non fatal.
1472         @hidden
1473     """
1474
1475     def __init__(self, template):
1476         """ Constructor.
1477             @hidden
1478         """
1479         Exception.__init__(self, template)
1480