You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ws.apache.org by di...@apache.org on 2005/12/09 13:22:06 UTC

svn commit: r355462 [6/7] - in /webservices/admin/planet: ./ cache/ compat_logging/ examples/ fancy-examples/ output/ output/images/

Added: webservices/admin/planet/htmltmpl.py
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/htmltmpl.py?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/htmltmpl.py (added)
+++ webservices/admin/planet/htmltmpl.py Fri Dec  9 04:21:26 2005
@@ -0,0 +1,1480 @@
+
+""" A templating engine for separation of code and HTML.
+
+    The documentation of this templating engine is separated to two parts:
+    
+        1. Description of the templating language.
+           
+        2. Documentation of classes and API of this module that provides
+           a Python implementation of the templating language.
+    
+    All the documentation can be found in 'doc' directory of the
+    distribution tarball or at the homepage of the engine.
+    Latest versions of this module are also available at that website.
+
+    You can use and redistribute this module under conditions of the
+    GNU General Public License that can be found either at
+    [ http://www.gnu.org/ ] or in file "LICENSE" contained in the
+    distribution tarball of this module.
+
+    Copyright (c) 2001 Tomas Styblo, tripie@cpan.org
+
+    @name           htmltmpl
+    @version        1.22
+    @author-name    Tomas Styblo
+    @author-email   tripie@cpan.org
+    @website        http://htmltmpl.sourceforge.net/
+    @license-name   GNU GPL
+    @license-url    http://www.gnu.org/licenses/gpl.html
+"""
+
+__version__ = 1.22
+__author__ = "Tomas Styblo (tripie@cpan.org)"
+
+# All imported modules are part of the standard Python library.
+
+from types import *
+import re
+import os
+import os.path
+import pprint       # only for debugging
+import sys
+import copy
+import cgi          # for HTML escaping of variables
+import urllib       # for URL escaping of variables
+import cPickle      # for template compilation
+import gettext
+
+INCLUDE_DIR = "inc"
+
+# Total number of possible parameters.
+# Increment if adding a parameter to any statement.
+PARAMS_NUMBER = 3
+
+# Relative positions of parameters in TemplateCompiler.tokenize().
+PARAM_NAME = 1
+PARAM_ESCAPE = 2
+PARAM_GLOBAL = 3
+PARAM_GETTEXT_STRING = 1
+
+# Find a way to lock files. Currently implemented only for UNIX and windows.
+LOCKTYPE_FCNTL = 1
+LOCKTYPE_MSVCRT = 2
+LOCKTYPE = None
+try:
+    import fcntl
+except:
+    try:
+        import msvcrt
+    except:
+        LOCKTYPE = None
+    else:
+        LOCKTYPE = LOCKTYPE_MSVCRT
+else:
+    LOCKTYPE = LOCKTYPE_FCNTL
+LOCK_EX = 1
+LOCK_SH = 2
+LOCK_UN = 3
+
+##############################################
+#          CLASS: TemplateManager            #
+##############################################
+
+class TemplateManager:
+    """  Class that manages compilation and precompilation of templates.
+    
+         You should use this class whenever you work with templates
+         that are stored in a file. The class can create a compiled
+         template and transparently manage its precompilation. It also
+         keeps the precompiled templates up-to-date by modification times
+         comparisons. 
+    """
+
+    def __init__(self, include=1, max_include=5, precompile=1, comments=1,
+                 gettext=0, debug=0):
+        """ Constructor.
+        
+            @header
+            __init__(include=1, max_include=5, precompile=1, comments=1,
+                     gettext=0, debug=0)
+            
+            @param include Enable or disable included templates.
+            This optional parameter can be used to enable or disable
+            <em>TMPL_INCLUDE</em> inclusion of templates. Disabling of
+            inclusion can improve performance a bit. The inclusion is
+            enabled by default.
+      
+            @param max_include Maximum depth of nested inclusions.
+            This optional parameter can be used to specify maximum depth of
+            nested <em>TMPL_INCLUDE</em> inclusions. It defaults to 5.
+            This setting prevents infinite recursive inclusions.
+            
+            @param precompile Enable or disable precompilation of templates.
+            This optional parameter can be used to enable or disable
+            creation and usage of precompiled templates.
+      
+            A precompiled template is saved to the same directory in
+            which the main template file is located. You need write
+            permissions to that directory.
+
+            Precompilation provides a significant performance boost because
+            it's not necessary to parse the templates over and over again.
+            The boost is especially noticeable when templates that include
+            other templates are used.
+            
+            Comparison of modification times of the main template and all
+            included templates is used to ensure that the precompiled
+            templates are up-to-date. Templates are also recompiled if the
+            htmltmpl module is updated.
+
+            The <em>TemplateError</em>exception is raised when the precompiled
+            template cannot be saved. Precompilation is enabled by default.
+
+            Precompilation is available only on UNIX and Windows platforms,
+            because proper file locking which is necessary to ensure
+            multitask safe behaviour is platform specific and is not
+            implemented for other platforms. Attempts to enable precompilation
+            on the other platforms result in raise of the
+            <em>TemplateError</em> exception.
+            
+            @param comments Enable or disable template comments.
+            This optional parameter can be used to enable or disable
+            template comments.
+            Disabling of the comments can improve performance a bit.
+            Comments are enabled by default.
+            
+            @param gettext Enable or disable gettext support.
+
+            @param debug Enable or disable debugging messages.
+            This optional parameter is a flag that can be used to enable
+            or disable debugging messages which are printed to the standard
+            error output. The debugging messages are disabled by default.
+        """
+        # Save the optional parameters.
+        # These values are not modified by any method.
+        self._include = include
+        self._max_include = max_include
+        self._precompile = precompile
+        self._comments = comments
+        self._gettext = gettext
+        self._debug = debug
+
+        # Find what module to use to lock files.
+        # File locking is necessary for the 'precompile' feature to be
+        # multitask/thread safe. Currently it works only on UNIX
+        # and Windows. Anyone willing to implement it on Mac ?
+        if precompile and not LOCKTYPE:
+                raise TemplateError, "Template precompilation is not "\
+                                     "available on this platform."
+        self.DEB("INIT DONE")
+
+    def prepare(self, file):
+        """ Preprocess, parse, tokenize and compile the template.
+            
+            If precompilation is enabled then this method tries to load
+            a precompiled form of the template from the same directory
+            in which the template source file is located. If it succeeds,
+            then it compares modification times stored in the precompiled
+            form to modification times of source files of the template,
+            including source files of all templates included via the
+            <em>TMPL_INCLUDE</em> statements. If any of the modification times
+            differs, then the template is recompiled and the precompiled
+            form updated.
+            
+            If precompilation is disabled, then this method parses and
+            compiles the template.
+            
+            @header prepare(file)
+            
+            @return Compiled template.
+            The methods returns an instance of the <em>Template</em> class
+            which is a compiled form of the template. This instance can be
+            used as input for the <em>TemplateProcessor</em>.
+            
+            @param file Path to the template file to prepare.
+            The method looks for the template file in current directory
+            if the parameter is a relative path. All included templates must
+            be placed in subdirectory <strong>'inc'</strong> of the 
+            directory in which the main template file is located.
+        """
+        compiled = None
+        if self._precompile:
+            if self.is_precompiled(file):
+                try:
+                    precompiled = self.load_precompiled(file)
+                except PrecompiledError, template:
+                    print >> sys.stderr, "Htmltmpl: bad precompiled "\
+                                         "template '%s' removed" % template
+                    compiled = self.compile(file)
+                    self.save_precompiled(compiled)
+                else:
+                    precompiled.debug(self._debug)
+                    compile_params = (self._include, self._max_include,
+                                      self._comments, self._gettext)
+                    if precompiled.is_uptodate(compile_params):
+                        self.DEB("PRECOMPILED: UPTODATE")
+                        compiled = precompiled
+                    else:
+                        self.DEB("PRECOMPILED: NOT UPTODATE")
+                        compiled = self.update(precompiled)
+            else:
+                self.DEB("PRECOMPILED: NOT PRECOMPILED")
+                compiled = self.compile(file)
+                self.save_precompiled(compiled)
+        else:
+            self.DEB("PRECOMPILATION DISABLED")
+            compiled = self.compile(file)
+        return compiled
+    
+    def update(self, template):
+        """ Update (recompile) a compiled template.
+        
+            This method recompiles a template compiled from a file.
+            If precompilation is enabled then the precompiled form saved on
+            disk is also updated.
+            
+            @header update(template)
+            
+            @return Recompiled template.
+            It's ensured that the returned template is up-to-date.
+            
+            @param template A compiled template.
+            This parameter should be an instance of the <em>Template</em>
+            class, created either by the <em>TemplateManager</em> or by the
+            <em>TemplateCompiler</em>. The instance must represent a template
+            compiled from a file on disk.
+        """
+        self.DEB("UPDATE")
+        updated = self.compile(template.file())
+        if self._precompile:
+            self.save_precompiled(updated)
+        return updated
+
+    ##############################################
+    #              PRIVATE METHODS               #
+    ##############################################    
+
+    def DEB(self, str):
+        """ Print debugging message to stderr if debugging is enabled. 
+            @hidden
+        """
+        if self._debug: print >> sys.stderr, str
+
+    def lock_file(self, file, lock):
+        """ Provide platform independent file locking.
+            @hidden
+        """
+        fd = file.fileno()
+        if LOCKTYPE == LOCKTYPE_FCNTL:
+            if lock == LOCK_SH:
+                fcntl.flock(fd, fcntl.LOCK_SH)
+            elif lock == LOCK_EX:
+                fcntl.flock(fd, fcntl.LOCK_EX)
+            elif lock == LOCK_UN:
+                fcntl.flock(fd, fcntl.LOCK_UN)
+            else:
+                raise TemplateError, "BUG: bad lock in lock_file"
+        elif LOCKTYPE == LOCKTYPE_MSVCRT:
+            if lock == LOCK_SH:
+                # msvcrt does not support shared locks :-(
+                msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
+            elif lock == LOCK_EX:
+                msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
+            elif lock == LOCK_UN:
+                msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
+            else:
+                raise TemplateError, "BUG: bad lock in lock_file"
+        else:
+            raise TemplateError, "BUG: bad locktype in lock_file"
+
+    def compile(self, file):
+        """ Compile the template.
+            @hidden
+        """
+        return TemplateCompiler(self._include, self._max_include,
+                                self._comments, self._gettext,
+                                self._debug).compile(file)
+    
+    def is_precompiled(self, file):
+        """ Return true if the template is already precompiled on the disk.
+            This method doesn't check whether the compiled template is
+            uptodate.
+            @hidden
+        """
+        filename = file + "c"   # "template.tmplc"
+        if os.path.isfile(filename):
+            return 1
+        else:
+            return 0
+        
+    def load_precompiled(self, file):
+        """ Load precompiled template from disk.
+
+            Remove the precompiled template file and recompile it
+            if the file contains corrupted or unpicklable data.
+            
+            @hidden
+        """
+        filename = file + "c"   # "template.tmplc"
+        self.DEB("LOADING PRECOMPILED")
+        try:
+            remove_bad = 0
+            file = None
+            try:
+                file = open(filename, "rb")
+                self.lock_file(file, LOCK_SH)
+                precompiled = cPickle.load(file)
+            except IOError, (errno, errstr):
+                raise TemplateError, "IO error in load precompiled "\
+                                     "template '%s': (%d) %s"\
+                                     % (filename, errno, errstr)
+            except cPickle.UnpicklingError:
+                remove_bad = 1
+                raise PrecompiledError, filename
+            except:
+                remove_bad = 1
+                raise
+            else:
+                return precompiled
+        finally:
+            if file:
+                self.lock_file(file, LOCK_UN)
+                file.close()
+            if remove_bad and os.path.isfile(filename):
+                # X: We may lose the original exception here, raising OSError.
+                os.remove(filename)
+                
+    def save_precompiled(self, template):
+        """ Save compiled template to disk in precompiled form.
+            
+            Associated metadata is also saved. It includes: filename of the
+            main template file, modification time of the main template file,
+            modification times of all included templates and version of the
+            htmltmpl module which compiled the template.
+            
+            The method removes a file which is saved only partially because
+            of some error.
+            
+            @hidden
+        """
+        filename = template.file() + "c"   # creates "template.tmplc"
+        # Check if we have write permission to the template's directory.
+        template_dir = os.path.dirname(os.path.abspath(filename))
+        if not os.access(template_dir, os.W_OK):
+            raise TemplateError, "Cannot save precompiled templates "\
+                                 "to '%s': write permission denied."\
+                                 % template_dir
+        try:
+            remove_bad = 0
+            file = None
+            try:
+                file = open(filename, "wb")   # may truncate existing file
+                self.lock_file(file, LOCK_EX)
+                BINARY = 1
+                READABLE = 0
+                if self._debug:
+                    cPickle.dump(template, file, READABLE)
+                else:
+                    cPickle.dump(template, file, BINARY)
+            except IOError, (errno, errstr):
+                remove_bad = 1
+                raise TemplateError, "IO error while saving precompiled "\
+                                     "template '%s': (%d) %s"\
+                                      % (filename, errno, errstr)
+            except cPickle.PicklingError, error:
+                remove_bad = 1
+                raise TemplateError, "Pickling error while saving "\
+                                     "precompiled template '%s': %s"\
+                                     % (filename, error)
+            except:
+                remove_bad = 1
+                raise
+            else:
+                self.DEB("SAVING PRECOMPILED")
+        finally:
+            if file:
+                self.lock_file(file, LOCK_UN)
+                file.close()
+            if remove_bad and os.path.isfile(filename):
+                # X: We may lose the original exception here, raising OSError.
+                os.remove(filename)
+
+
+##############################################
+#          CLASS: TemplateProcessor          #
+##############################################
+
+class TemplateProcessor:
+    """ Fill the template with data and process it.
+
+        This class provides actual processing of a compiled template.
+        Use it to set template variables and loops and then obtain
+        result of the processing.
+    """
+
+    def __init__(self, html_escape=1, magic_vars=1, global_vars=0, debug=0):
+        """ Constructor.
+
+            @header __init__(html_escape=1, magic_vars=1, global_vars=0,
+                             debug=0)
+
+            @param html_escape Enable or disable HTML escaping of variables.
+            This optional parameter is a flag that can be used to enable or
+            disable automatic HTML escaping of variables.
+            All variables are by default automatically HTML escaped. 
+            The escaping process substitutes HTML brackets, ampersands and
+            double quotes with appropriate HTML entities.
+            
+            @param magic_vars Enable or disable loop magic variables.
+            This parameter can be used to enable or disable
+            "magic" context variables, that are automatically defined inside
+            loops. Magic variables are enabled by default.
+
+            Refer to the language specification for description of these
+            magic variables.
+      
+            @param global_vars Globally activate global lookup of variables.
+            This optional parameter is a flag that can be used to specify
+            whether variables which cannot be found in the current scope
+            should be automatically looked up in enclosing scopes.
+
+            Automatic global lookup is disabled by default. Global lookup
+            can be overriden on a per-variable basis by the
+            <strong>GLOBAL</strong> parameter of a <strong>TMPL_VAR</strong>
+            statement.
+
+            @param debug Enable or disable debugging messages.
+        """
+        self._html_escape = html_escape
+        self._magic_vars = magic_vars
+        self._global_vars = global_vars
+        self._debug = debug        
+
+        # Data structure containing variables and loops set by the
+        # application. Use debug=1, process some template and
+        # then check stderr to see how the structure looks.
+        # It's modified only by set() and reset() methods.
+        self._vars = {}        
+
+        # Following variables are for multipart templates.
+        self._current_part = 1
+        self._current_pos = 0
+
+    def set(self, var, value):
+        """ Associate a value with top-level template variable or loop.
+
+            A template identifier can represent either an ordinary variable
+            (string) or a loop.
+
+            To assign a value to a string identifier pass a scalar
+            as the 'value' parameter. This scalar will be automatically
+            converted to string.
+
+            To assign a value to a loop identifier pass a list of mappings as
+            the 'value' parameter. The engine iterates over this list and
+            assigns values from the mappings to variables in a template loop
+            block if a key in the mapping corresponds to a name of a variable
+            in the loop block. The number of mappings contained in this list
+            is equal to number of times the loop block is repeated in the
+            output.
+      
+            @header set(var, value)
+            @return No return value.
+
+            @param var Name of template variable or loop.
+            @param value The value to associate.
+            
+        """
+        # The correctness of character case is verified only for top-level
+        # variables.
+        if self.is_ordinary_var(value):
+            # template top-level ordinary variable
+            if not var.islower():
+                raise TemplateError, "Invalid variable name '%s'." % var
+        elif type(value) == ListType:
+            # template top-level loop
+            if var != var.capitalize():
+                raise TemplateError, "Invalid loop name '%s'." % var
+        else:
+            raise TemplateError, "Value of toplevel variable '%s' must "\
+                                 "be either a scalar or a list." % var
+        self._vars[var] = value
+        self.DEB("VALUE SET: " + str(var))
+        
+    def reset(self, keep_data=0):
+        """ Reset the template data.
+
+            This method resets the data contained in the template processor
+            instance. The template processor instance can be used to process
+            any number of templates, but this method must be called after
+            a template is processed to reuse the instance,
+
+            @header reset(keep_data=0)
+            @return No return value.
+
+            @param keep_data Do not reset the template data.
+            Use this flag if you do not want the template data to be erased.
+            This way you can reuse the data contained in the instance of
+            the <em>TemplateProcessor</em>.
+        """
+        self._current_part = 1
+        self._current_pos = 0
+        if not keep_data:
+            self._vars.clear()
+        self.DEB("RESET")
+
+    def process(self, template, part=None):
+        """ Process a compiled template. Return the result as string.
+
+            This method actually processes a template and returns
+            the result.
+
+            @header process(template, part=None)
+            @return Result of the processing as string.
+
+            @param template A compiled template.
+            Value of this parameter must be an instance of the
+            <em>Template</em> class created either by the
+            <em>TemplateManager</em> or by the <em>TemplateCompiler</em>.
+
+            @param part The part of a multipart template to process.
+            This parameter can be used only together with a multipart
+            template. It specifies the number of the part to process.
+            It must be greater than zero, because the parts are numbered
+            from one.
+
+            The parts must be processed in the right order. You
+            cannot process a part which precedes an already processed part.
+
+            If this parameter is not specified, then the whole template
+            is processed, or all remaining parts are processed.
+        """
+        self.DEB("APP INPUT:")
+        if self._debug: pprint.pprint(self._vars, sys.stderr)
+        if part != None and (part == 0 or part < self._current_part):
+            raise TemplateError, "process() - invalid part number"
+
+        # This flag means "jump behind the end of current statement" or
+        # "skip the parameters of current statement".
+        # Even parameters that actually are not present in the template
+        # do appear in the list of tokens as empty items !
+        skip_params = 0 
+
+        # Stack for enabling or disabling output in response to TMPL_IF,
+        # TMPL_UNLESS, TMPL_ELSE and TMPL_LOOPs with no passes.
+        output_control = []
+        ENABLE_OUTPUT = 1
+        DISABLE_OUTPUT = 0
+        
+        # Stacks for data related to loops.
+        loop_name = []        # name of a loop
+        loop_pass = []        # current pass of a loop (counted from zero)
+        loop_start = []       # index of loop start in token list
+        loop_total = []       # total number of passes in a loop
+        
+        tokens = template.tokens()
+        len_tokens = len(tokens)
+        out = ""              # buffer for processed output
+
+        # Recover position at which we ended after processing of last part.
+        i = self._current_pos
+            
+        # Process the list of tokens.
+        while 1:
+            if i == len_tokens: break            
+            if skip_params:   
+                # Skip the parameters following a statement.
+                skip_params = 0
+                i += PARAMS_NUMBER
+                continue
+
+            token = tokens[i]
+            if token.startswith("<TMPL_") or \
+               token.startswith("</TMPL_"):
+                if token == "<TMPL_VAR":
+                    # TMPL_VARs should be first. They are the most common.
+                    var = tokens[i + PARAM_NAME]
+                    if not var:
+                        raise TemplateError, "No identifier in <TMPL_VAR>."
+                    escape = tokens[i + PARAM_ESCAPE]
+                    globalp = tokens[i + PARAM_GLOBAL]
+                    skip_params = 1
+                    
+                    # If output of current block is not disabled then append
+                    # the substitued and escaped variable to the output.
+                    if DISABLE_OUTPUT not in output_control:
+                        value = str(self.find_value(var, loop_name, loop_pass,
+                                                    loop_total, globalp))
+                        out += self.escape(value, escape)
+                        self.DEB("VAR: " + str(var))
+
+                elif token == "<TMPL_LOOP":
+                    var = tokens[i + PARAM_NAME]
+                    if not var:
+                        raise TemplateError, "No identifier in <TMPL_LOOP>."
+                    skip_params = 1
+
+                    # Find total number of passes in this loop.
+                    passtotal = self.find_value(var, loop_name, loop_pass,
+                                                loop_total)
+                    if not passtotal: passtotal = 0
+                    # Push data for this loop on the stack.
+                    loop_total.append(passtotal)
+                    loop_start.append(i)
+                    loop_pass.append(0)
+                    loop_name.append(var)
+
+                    # Disable output of loop block if the number of passes
+                    # in this loop is zero.
+                    if passtotal == 0:
+                        # This loop is empty.
+                        output_control.append(DISABLE_OUTPUT)
+                        self.DEB("LOOP: DISABLE: " + str(var))
+                    else:
+                        output_control.append(ENABLE_OUTPUT)
+                        self.DEB("LOOP: FIRST PASS: %s TOTAL: %d"\
+                                 % (var, passtotal))
+
+                elif token == "<TMPL_IF":
+                    var = tokens[i + PARAM_NAME]
+                    if not var:
+                        raise TemplateError, "No identifier in <TMPL_IF>."
+                    globalp = tokens[i + PARAM_GLOBAL]
+                    skip_params = 1
+                    if self.find_value(var, loop_name, loop_pass,
+                                       loop_total, globalp):
+                        output_control.append(ENABLE_OUTPUT)
+                        self.DEB("IF: ENABLE: " + str(var))
+                    else:
+                        output_control.append(DISABLE_OUTPUT)
+                        self.DEB("IF: DISABLE: " + str(var))
+     
+                elif token == "<TMPL_UNLESS":
+                    var = tokens[i + PARAM_NAME]
+                    if not var:
+                        raise TemplateError, "No identifier in <TMPL_UNLESS>."
+                    globalp = tokens[i + PARAM_GLOBAL]
+                    skip_params = 1
+                    if self.find_value(var, loop_name, loop_pass,
+                                      loop_total, globalp):
+                        output_control.append(DISABLE_OUTPUT)
+                        self.DEB("UNLESS: DISABLE: " + str(var))
+                    else:
+                        output_control.append(ENABLE_OUTPUT)
+                        self.DEB("UNLESS: ENABLE: " + str(var))
+     
+                elif token == "</TMPL_LOOP":
+                    skip_params = 1
+                    if not loop_name:
+                        raise TemplateError, "Unmatched </TMPL_LOOP>."
+                    
+                    # If this loop was not disabled, then record the pass.
+                    if loop_total[-1] > 0: loop_pass[-1] += 1
+                    
+                    if loop_pass[-1] == loop_total[-1]:
+                        # There are no more passes in this loop. Pop
+                        # the loop from stack.
+                        loop_pass.pop()
+                        loop_name.pop()
+                        loop_start.pop()
+                        loop_total.pop()
+                        output_control.pop()
+                        self.DEB("LOOP: END")
+                    else:
+                        # Jump to the beggining of this loop block 
+                        # to process next pass of the loop.
+                        i = loop_start[-1]
+                        self.DEB("LOOP: NEXT PASS")
+     
+                elif token == "</TMPL_IF":
+                    skip_params = 1
+                    if not output_control:
+                        raise TemplateError, "Unmatched </TMPL_IF>."
+                    output_control.pop()
+                    self.DEB("IF: END")
+     
+                elif token == "</TMPL_UNLESS":
+                    skip_params = 1
+                    if not output_control:
+                        raise TemplateError, "Unmatched </TMPL_UNLESS>."
+                    output_control.pop()
+                    self.DEB("UNLESS: END")
+     
+                elif token == "<TMPL_ELSE":
+                    skip_params = 1
+                    if not output_control:
+                        raise TemplateError, "Unmatched <TMPL_ELSE>."
+                    if output_control[-1] == DISABLE_OUTPUT:
+                        # Condition was false, activate the ELSE block.
+                        output_control[-1] = ENABLE_OUTPUT
+                        self.DEB("ELSE: ENABLE")
+                    elif output_control[-1] == ENABLE_OUTPUT:
+                        # Condition was true, deactivate the ELSE block.
+                        output_control[-1] = DISABLE_OUTPUT
+                        self.DEB("ELSE: DISABLE")
+                    else:
+                        raise TemplateError, "BUG: ELSE: INVALID FLAG"
+
+                elif token == "<TMPL_BOUNDARY":
+                    if part and part == self._current_part:
+                        self.DEB("BOUNDARY ON")
+                        self._current_part += 1
+                        self._current_pos = i + 1 + PARAMS_NUMBER
+                        break
+                    else:
+                        skip_params = 1
+                        self.DEB("BOUNDARY OFF")
+                        self._current_part += 1
+
+                elif token == "<TMPL_INCLUDE":
+                    # TMPL_INCLUDE is left in the compiled template only
+                    # when it was not replaced by the parser.
+                    skip_params = 1
+                    filename = tokens[i + PARAM_NAME]
+                    out += """
+                        <br />
+                        <p>
+                        <strong>HTMLTMPL WARNING:</strong><br />
+                        Cannot include template: <strong>%s</strong>
+                        </p>
+                        <br />
+                    """ % filename
+                    self.DEB("CANNOT INCLUDE WARNING")
+
+                elif token == "<TMPL_GETTEXT":
+                    skip_params = 1
+                    if DISABLE_OUTPUT not in output_control:
+                        text = tokens[i + PARAM_GETTEXT_STRING]
+                        out += gettext.gettext(text)
+                        self.DEB("GETTEXT: " + text)
+                    
+                else:
+                    # Unknown processing directive.
+                    raise TemplateError, "Invalid statement %s>." % token
+                     
+            elif DISABLE_OUTPUT not in output_control:
+                # Raw textual template data.
+                # If output of current block is not disabled, then 
+                # append template data to the output buffer.
+                out += token
+                
+            i += 1
+            # end of the big while loop
+        
+        # Check whether all opening statements were closed.
+        if loop_name: raise TemplateError, "Missing </TMPL_LOOP>."
+        if output_control: raise TemplateError, "Missing </TMPL_IF> or </TMPL_UNLESS>"
+        return out
+
+    ##############################################
+    #              PRIVATE METHODS               #
+    ##############################################
+
+    def DEB(self, str):
+        """ Print debugging message to stderr if debugging is enabled.
+            @hidden
+        """
+        if self._debug: print >> sys.stderr, str
+
+    def find_value(self, var, loop_name, loop_pass, loop_total,
+                   global_override=None):
+        """ Search the self._vars data structure to find variable var
+            located in currently processed pass of a loop which
+            is currently being processed. If the variable is an ordinary
+            variable, then return it.
+            
+            If the variable is an identificator of a loop, then 
+            return the total number of times this loop will
+            be executed.
+            
+            Return an empty string, if the variable is not
+            found at all.
+
+            @hidden
+        """
+        # Search for the requested variable in magic vars if the name
+        # of the variable starts with "__" and if we are inside a loop.
+        if self._magic_vars and var.startswith("__") and loop_name:
+            return self.magic_var(var, loop_pass[-1], loop_total[-1])
+                    
+        # Search for an ordinary variable or for a loop.
+        # Recursively search in self._vars for the requested variable.
+        scope = self._vars
+        globals = []
+        for i in range(len(loop_name)):            
+            # If global lookup is on then push the value on the stack.
+            if ((self._global_vars and global_override != "0") or \
+                 global_override == "1") and scope.has_key(var) and \
+               self.is_ordinary_var(scope[var]):
+                globals.append(scope[var])
+            
+            # Descent deeper into the hierarchy.
+            if scope.has_key(loop_name[i]) and scope[loop_name[i]]:
+                scope = scope[loop_name[i]][loop_pass[i]]
+            else:
+                return ""
+            
+        if scope.has_key(var):
+            # Value exists in current loop.
+            if type(scope[var]) == ListType:
+                # The requested value is a loop.
+                # Return total number of its passes.
+                return len(scope[var])
+            else:
+                return scope[var]
+        elif globals and \
+             ((self._global_vars and global_override != "0") or \
+               global_override == "1"):
+            # Return globally looked up value.
+            return globals.pop()
+        else:
+            # No value found.
+            if var[0].isupper():
+                # This is a loop name.
+                # Return zero, because the user wants to know number
+                # of its passes.
+                return 0
+            else:
+                return ""
+
+    def magic_var(self, var, loop_pass, loop_total):
+        """ Resolve and return value of a magic variable.
+            Raise an exception if the magic variable is not recognized.
+
+            @hidden
+        """
+        self.DEB("MAGIC: '%s', PASS: %d, TOTAL: %d"\
+                 % (var, loop_pass, loop_total))
+        if var == "__FIRST__":
+            if loop_pass == 0:
+                return 1
+            else:
+                return 0
+        elif var == "__LAST__":
+            if loop_pass == loop_total - 1:
+                return 1
+            else:
+                return 0
+        elif var == "__INNER__":
+            # If this is neither the first nor the last pass.
+            if loop_pass != 0 and loop_pass != loop_total - 1:
+                return 1
+            else:
+                return 0        
+        elif var == "__PASS__":
+            # Magic variable __PASS__ counts passes from one.
+            return loop_pass + 1
+        elif var == "__PASSTOTAL__":
+            return loop_total
+        elif var == "__ODD__":
+            # Internally pass numbers stored in loop_pass are counted from
+            # zero. But the template language presents them counted from one.
+            # Therefore we must add one to the actual loop_pass value to get
+            # the value we present to the user.
+            if (loop_pass + 1) % 2 != 0:
+                return 1
+            else:
+                return 0
+        elif var.startswith("__EVERY__"):
+            # Magic variable __EVERY__x is never true in first or last pass.
+            if loop_pass != 0 and loop_pass != loop_total - 1:
+                # Check if an integer follows the variable name.
+                try:
+                    every = int(var[9:])   # nine is length of "__EVERY__"
+                except ValueError:
+                    raise TemplateError, "Magic variable __EVERY__x: "\
+                                         "Invalid pass number."
+                else:
+                    if not every:
+                        raise TemplateError, "Magic variable __EVERY__x: "\
+                                             "Pass number cannot be zero."
+                    elif (loop_pass + 1) % every == 0:
+                        self.DEB("MAGIC: EVERY: " + str(every))
+                        return 1
+                    else:
+                        return 0
+            else:
+                return 0
+        else:
+            raise TemplateError, "Invalid magic variable '%s'." % var
+
+    def escape(self, str, override=""):
+        """ Escape a string either by HTML escaping or by URL escaping.
+            @hidden
+        """
+        ESCAPE_QUOTES = 1
+        if (self._html_escape and override != "NONE" and override != "0" and \
+            override != "URL") or override == "HTML" or override == "1":
+            return cgi.escape(str, ESCAPE_QUOTES)
+        elif override == "URL":
+            return urllib.quote_plus(str)
+        else:
+            return str
+
+    def is_ordinary_var(self, var):
+        """ Return true if var is a scalar. (not a reference to loop)
+            @hidden
+        """
+        if type(var) == StringType or type(var) == IntType or \
+           type(var) == LongType or type(var) == FloatType:
+            return 1
+        else:
+            return 0
+
+
+##############################################
+#          CLASS: TemplateCompiler           #
+##############################################
+
+class TemplateCompiler:
+    """ Preprocess, parse, tokenize and compile the template.
+
+        This class parses the template and produces a 'compiled' form
+        of it. This compiled form is an instance of the <em>Template</em>
+        class. The compiled form is used as input for the TemplateProcessor
+        which uses it to actually process the template.
+
+        This class should be used direcly only when you need to compile
+        a template from a string. If your template is in a file, then you
+        should use the <em>TemplateManager</em> class which provides
+        a higher level interface to this class and also can save the
+        compiled template to disk in a precompiled form.
+    """
+
+    def __init__(self, include=1, max_include=5, comments=1, gettext=0,
+                 debug=0):
+        """ Constructor.
+
+        @header __init__(include=1, max_include=5, comments=1, gettext=0,
+                         debug=0)
+
+        @param include Enable or disable included templates.
+        @param max_include Maximum depth of nested inclusions.
+        @param comments Enable or disable template comments.
+        @param gettext Enable or disable gettext support.
+        @param debug Enable or disable debugging messages.
+        """
+        
+        self._include = include
+        self._max_include = max_include
+        self._comments = comments
+        self._gettext = gettext
+        self._debug = debug
+        
+        # This is a list of filenames of all included templates.
+        # It's modified by the include_templates() method.
+        self._include_files = []
+
+        # This is a counter of current inclusion depth. It's used to prevent
+        # infinite recursive includes.
+        self._include_level = 0
+    
+    def compile(self, file):
+        """ Compile template from a file.
+
+            @header compile(file)
+            @return Compiled template.
+            The return value is an instance of the <em>Template</em>
+            class.
+
+            @param file Filename of the template.
+            See the <em>prepare()</em> method of the <em>TemplateManager</em>
+            class for exaplanation of this parameter.
+        """
+        
+        self.DEB("COMPILING FROM FILE: " + file)
+        self._include_path = os.path.join(os.path.dirname(file), INCLUDE_DIR)
+        tokens = self.parse(self.read(file))
+        compile_params = (self._include, self._max_include, self._comments,
+                          self._gettext)
+        return Template(__version__, file, self._include_files,
+                        tokens, compile_params, self._debug)
+
+    def compile_string(self, data):
+        """ Compile template from a string.
+
+            This method compiles a template from a string. The
+            template cannot include any templates.
+            <strong>TMPL_INCLUDE</strong> statements are turned into warnings.
+
+            @header compile_string(data)
+            @return Compiled template.
+            The return value is an instance of the <em>Template</em>
+            class.
+
+            @param data String containing the template data.        
+        """
+        self.DEB("COMPILING FROM STRING")
+        self._include = 0
+        tokens = self.parse(data)
+        compile_params = (self._include, self._max_include, self._comments,
+                          self._gettext)
+        return Template(__version__, None, None, tokens, compile_params,
+                        self._debug)
+
+    ##############################################
+    #              PRIVATE METHODS               #
+    ##############################################
+                
+    def DEB(self, str):
+        """ Print debugging message to stderr if debugging is enabled.
+            @hidden
+        """
+        if self._debug: print >> sys.stderr, str
+    
+    def read(self, filename):
+        """ Read content of file and return it. Raise an error if a problem
+            occurs.
+            @hidden
+        """
+        self.DEB("READING: " + filename)
+        try:
+            f = None
+            try:
+                f = open(filename, "r")
+                data = f.read()
+            except IOError, (errno, errstr):
+                raise TemplateError, "IO error while reading template '%s': "\
+                                     "(%d) %s" % (filename, errno, errstr)
+            else:
+                return data
+        finally:
+            if f: f.close()
+               
+    def parse(self, template_data):
+        """ Parse the template. This method is recursively called from
+            within the include_templates() method.
+
+            @return List of processing tokens.
+            @hidden
+        """
+        if self._comments:
+            self.DEB("PREPROCESS: COMMENTS")
+            template_data = self.remove_comments(template_data)
+        tokens = self.tokenize(template_data)
+        if self._include:
+            self.DEB("PREPROCESS: INCLUDES")
+            self.include_templates(tokens)
+        return tokens
+
+    def remove_comments(self, template_data):
+        """ Remove comments from the template data.
+            @hidden
+        """
+        pattern = r"### .*"
+        return re.sub(pattern, "", template_data)
+           
+    def include_templates(self, tokens):
+        """ Process TMPL_INCLUDE statements. Use the include_level counter
+            to prevent infinite recursion. Record paths to all included
+            templates to self._include_files.
+            @hidden
+        """
+        i = 0
+        out = ""    # buffer for output
+        skip_params = 0
+        
+        # Process the list of tokens.
+        while 1:
+            if i == len(tokens): break
+            if skip_params:
+                skip_params = 0
+                i += PARAMS_NUMBER
+                continue
+
+            token = tokens[i]
+            if token == "<TMPL_INCLUDE":
+                filename = tokens[i + PARAM_NAME]
+                if not filename:
+                    raise TemplateError, "No filename in <TMPL_INCLUDE>."
+                self._include_level += 1
+                if self._include_level > self._max_include:
+                    # Do not include the template.
+                    # Protection against infinite recursive includes.
+                    skip_params = 1
+                    self.DEB("INCLUDE: LIMIT REACHED: " + filename)
+                else:
+                    # Include the template.
+                    skip_params = 0
+                    include_file = os.path.join(self._include_path, filename)
+                    self._include_files.append(include_file)
+                    include_data = self.read(include_file)
+                    include_tokens = self.parse(include_data)
+
+                    # Append the tokens from the included template to actual
+                    # position in the tokens list, replacing the TMPL_INCLUDE
+                    # token and its parameters.
+                    tokens[i:i+PARAMS_NUMBER+1] = include_tokens
+                    i = i + len(include_tokens)
+                    self.DEB("INCLUDED: " + filename)
+                    continue   # Do not increment 'i' below.
+            i += 1
+            # end of the main while loop
+
+        if self._include_level > 0: self._include_level -= 1
+        return out
+    
+    def tokenize(self, template_data):
+        """ Split the template into tokens separated by template statements.
+            The statements itself and associated parameters are also
+            separately  included in the resulting list of tokens.
+            Return list of the tokens.
+
+            @hidden
+        """
+        self.DEB("TOKENIZING TEMPLATE")
+        # NOTE: The TWO double quotes in character class in the regexp below
+        # are there only to prevent confusion of syntax highlighter in Emacs.
+        pattern = r"""
+            (?:^[ \t]+)?               # eat spaces, tabs (opt.)
+            (<
+             (?:!--[ ])?               # comment start + space (opt.)
+             /?TMPL_[A-Z]+             # closing slash / (opt.) + statement
+             [ a-zA-Z0-9""/.=:_\\-]*   # this spans also comments ending (--)
+             >)
+            [%s]?                      # eat trailing newline (opt.)
+        """ % os.linesep
+        rc = re.compile(pattern, re.VERBOSE | re.MULTILINE)
+        split = rc.split(template_data)
+        tokens = []
+        for statement in split:
+            if statement.startswith("<TMPL_") or \
+               statement.startswith("</TMPL_") or \
+               statement.startswith("<!-- TMPL_") or \
+               statement.startswith("<!-- /TMPL_"):
+                # Processing statement.
+                statement = self.strip_brackets(statement)
+                params = re.split(r"\s+", statement)
+                tokens.append(self.find_directive(params))
+                tokens.append(self.find_name(params))
+                tokens.append(self.find_param("ESCAPE", params))
+                tokens.append(self.find_param("GLOBAL", params))
+            else:
+                # "Normal" template data.
+                if self._gettext:
+                    self.DEB("PARSING GETTEXT STRINGS")
+                    self.gettext_tokens(tokens, statement)
+                else:
+                    tokens.append(statement)
+        return tokens
+    
+    def gettext_tokens(self, tokens, str):
+        """ Find gettext strings and return appropriate array of
+            processing tokens.
+            @hidden
+        """
+        escaped = 0
+        gt_mode = 0
+        i = 0
+        buf = ""
+        while(1):
+            if i == len(str): break
+            if str[i] == "\\":
+                escaped = 0
+                if str[i+1] == "\\":
+                    buf += "\\"
+                    i += 2
+                    continue
+                elif str[i+1] == "[" or str[i+1] == "]":
+                    escaped = 1
+                else:
+                    buf += "\\"
+            elif str[i] == "[" and str[i+1] == "[":
+                if gt_mode:
+                    if escaped:
+                        escaped = 0
+                        buf += "["
+                    else:
+                        buf += "["
+                else:
+                    if escaped:
+                        escaped = 0
+                        buf += "["
+                    else:
+                        tokens.append(buf)
+                        buf = ""
+                        gt_mode = 1
+                        i += 2
+                        continue
+            elif str[i] == "]" and str[i+1] == "]":
+                if gt_mode:
+                    if escaped:
+                        escaped = 0
+                        buf += "]"
+                    else:
+                        self.add_gettext_token(tokens, buf)
+                        buf = ""
+                        gt_mode = 0
+                        i += 2
+                        continue
+                else:
+                    if escaped:
+                        escaped = 0
+                        buf += "]"
+                    else:
+                        buf += "]"
+            else:
+                escaped = 0
+                buf += str[i]
+            i += 1
+            # end of the loop
+        
+        if buf:
+            tokens.append(buf)
+                
+    def add_gettext_token(self, tokens, str):
+        """ Append a gettext token and gettext string to the tokens array.
+            @hidden
+        """
+        self.DEB("GETTEXT PARSER: TOKEN: " + str)
+        tokens.append("<TMPL_GETTEXT")
+        tokens.append(str)
+        tokens.append(None)
+        tokens.append(None)
+    
+    def strip_brackets(self, statement):
+        """ Strip HTML brackets (with optional HTML comments) from the
+            beggining and from the end of a statement.
+            @hidden
+        """
+        if statement.startswith("<!-- TMPL_") or \
+           statement.startswith("<!-- /TMPL_"):
+            return statement[5:-4]
+        else:
+            return statement[1:-1]
+
+    def find_directive(self, params):
+        """ Extract processing directive (TMPL_*) from a statement.
+            @hidden
+        """
+        directive = params[0]
+        del params[0]
+        self.DEB("TOKENIZER: DIRECTIVE: " + directive)
+        return "<" + directive
+
+    def find_name(self, params):
+        """ Extract identifier from a statement. The identifier can be
+            specified both implicitely or explicitely as a 'NAME' parameter.
+            @hidden
+        """
+        if len(params) > 0 and '=' not in params[0]:
+            # implicit identifier
+            name = params[0]
+            del params[0]
+        else:
+            # explicit identifier as a 'NAME' parameter
+            name = self.find_param("NAME", params)
+        self.DEB("TOKENIZER: NAME: " + str(name))
+        return name
+
+    def find_param(self, param, params):
+        """ Extract value of parameter from a statement.
+            @hidden
+        """
+        for pair in params:
+            name, value = pair.split("=")
+            if not name or not value:
+                raise TemplateError, "Syntax error in template."
+            if name == param:
+                if value[0] == '"':
+                    # The value is in double quotes.
+                    ret_value = value[1:-1]
+                else:
+                    # The value is without double quotes.
+                    ret_value = value
+                self.DEB("TOKENIZER: PARAM: '%s' => '%s'" % (param, ret_value))
+                return ret_value
+        else:
+            self.DEB("TOKENIZER: PARAM: '%s' => NOT DEFINED" % param)
+            return None
+
+
+##############################################
+#              CLASS: Template               #
+##############################################
+
+class Template:
+    """ This class represents a compiled template.
+
+        This class provides storage and methods for the compiled template
+        and associated metadata. It's serialized by pickle if we need to
+        save the compiled template to disk in a precompiled form.
+
+        You should never instantiate this class directly. Always use the
+        <em>TemplateManager</em> or <em>TemplateCompiler</em> classes to
+        create the instances of this class.
+
+        The only method which you can directly use is the <em>is_uptodate</em>
+        method.
+    """
+    
+    def __init__(self, version, file, include_files, tokens, compile_params,
+                 debug=0):
+        """ Constructor.
+            @hidden
+        """
+        self._version = version
+        self._file = file
+        self._tokens = tokens
+        self._compile_params = compile_params
+        self._debug = debug
+        self._mtime = None        
+        self._include_mtimes = {}
+
+        if not file:
+            self.DEB("TEMPLATE WAS COMPILED FROM A STRING")
+            return
+
+        # Save modifitcation time of the main template file.           
+        if os.path.isfile(file):
+            self._mtime = os.path.getmtime(file)
+        else:
+            raise TemplateError, "Template: file does not exist: '%s'" % file
+
+        # Save modificaton times of all included template files.
+        for inc_file in include_files:
+            if os.path.isfile(inc_file):
+                self._include_mtimes[inc_file] = os.path.getmtime(inc_file)
+            else:
+                raise TemplateError, "Template: file does not exist: '%s'"\
+                                     % inc_file
+            
+        self.DEB("NEW TEMPLATE CREATED")
+
+    def is_uptodate(self, compile_params=None):
+        """ Check whether the compiled template is uptodate.
+
+            Return true if this compiled template is uptodate.
+            Return false, if the template source file was changed on the
+            disk since it was compiled.
+            Works by comparison of modification times.
+            Also takes modification times of all included templates
+            into account.
+
+            @header is_uptodate(compile_params=None)
+            @return True if the template is uptodate, false otherwise.
+
+            @param compile_params Only for internal use.
+            Do not use this optional parameter. It's intended only for
+            internal use by the <em>TemplateManager</em>.
+        """
+        if not self._file:
+            self.DEB("TEMPLATE COMPILED FROM A STRING")
+            return 0
+        
+        if self._version != __version__:
+            self.DEB("TEMPLATE: VERSION NOT UPTODATE")
+            return 0
+
+        if compile_params != None and compile_params != self._compile_params:
+            self.DEB("TEMPLATE: DIFFERENT COMPILATION PARAMS")
+            return 0
+    
+        # Check modification times of the main template and all included
+        # templates. If the included template no longer exists, then
+        # the problem will be resolved when the template is recompiled.
+
+        # Main template file.
+        if not (os.path.isfile(self._file) and \
+                self._mtime == os.path.getmtime(self._file)):
+            self.DEB("TEMPLATE: NOT UPTODATE: " + self._file)
+            return 0        
+
+        # Included templates.
+        for inc_file in self._include_mtimes.keys():
+            if not (os.path.isfile(inc_file) and \
+                    self._include_mtimes[inc_file] == \
+                    os.path.getmtime(inc_file)):
+                self.DEB("TEMPLATE: NOT UPTODATE: " + inc_file)
+                return 0
+        else:
+            self.DEB("TEMPLATE: UPTODATE")
+            return 1       
+    
+    def tokens(self):
+        """ Get tokens of this template.
+            @hidden
+        """
+        return self._tokens
+
+    def file(self):
+        """ Get filename of the main file of this template.
+            @hidden
+        """
+        return self._file
+
+    def debug(self, debug):
+        """ Get debugging state.
+            @hidden
+        """
+        self._debug = debug
+
+    ##############################################
+    #              PRIVATE METHODS               #
+    ##############################################
+
+    def __getstate__(self):
+        """ Used by pickle when the class is serialized.
+            Remove the 'debug' attribute before serialization.
+            @hidden
+        """
+        dict = copy.copy(self.__dict__)
+        del dict["_debug"]
+        return dict
+
+    def __setstate__(self, dict):
+        """ Used by pickle when the class is unserialized.
+            Add the 'debug' attribute.
+            @hidden
+        """
+        dict["_debug"] = 0
+        self.__dict__ = dict
+
+
+    def DEB(self, str):
+        """ Print debugging message to stderr.
+            @hidden
+        """
+        if self._debug: print >> sys.stderr, str
+
+
+##############################################
+#                EXCEPTIONS                  #
+##############################################
+
+class TemplateError(Exception):
+    """ Fatal exception. Raised on runtime or template syntax errors.
+
+        This exception is raised when a runtime error occurs or when a syntax
+        error in the template is found. It has one parameter which always
+        is a string containing a description of the error.
+
+        All potential IOError exceptions are handled by the module and are
+        converted to TemplateError exceptions. That means you should catch the
+        TemplateError exception if there is a possibility that for example
+        the template file will not be accesssible.
+
+        The exception can be raised by constructors or by any method of any
+        class.
+        
+        The instance is no longer usable when this exception is raised. 
+    """
+
+    def __init__(self, error):
+        """ Constructor.
+            @hidden
+        """
+        Exception.__init__(self, "Htmltmpl error: " + error)
+
+
+class PrecompiledError(Exception):
+    """ This exception is _PRIVATE_ and non fatal.
+        @hidden
+    """
+
+    def __init__(self, template):
+        """ Constructor.
+            @hidden
+        """
+        Exception.__init__(self, template)
+

Added: webservices/admin/planet/htmltmpl.pyc
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/htmltmpl.pyc?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/htmltmpl.pyc
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/index.html.tmpl
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/index.html.tmpl?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/index.html.tmpl (added)
+++ webservices/admin/planet/index.html.tmpl Fri Dec  9 04:21:26 2005
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ 
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" href="default.css" type="text/css">
+<title><TMPL_VAR name></title>
+</head>
+
+<body>
+<h1><TMPL_VAR name></h1>
+
+<div id="sidebar" float="right">
+
+<h2>Disclaimer</h2>
+<p> The postings on this site are the viewpoints of their authors and don't necessarily represent WSO2's positions, strategies or opinions.</p>
+<h2>Subscriptions</h2>
+<ul>
+<TMPL_LOOP Channels>
+<li><a href="<TMPL_VAR link>" title="<TMPL_VAR title>"><TMPL_VAR name></a> <a href="<TMPL_VAR uri>">(feed)</a></li>
+</TMPL_LOOP>
+</ul>
+
+<h2>Feeds</h2>
+
+<p>Planet WSO2 provides its aggregated feeds in <a href="http://wso2.com/blogs/rss20.xml">RSS 2.0</a>,<a href="http://wso2.com/blogs/rss10.xml">RSS 1.0</a> and <a href="http://wso2.com/blogs/rss09.xml">RSS 0.9</a>, and its blogroll in <a href="http://wso2.com/blogs/foafroll.xml">FOAF</a> and <a href="http://wso2.com/blogs/opml.xml">OPML</a> (the most horrific abuse of XML known to man).</p>
+
+<h2><a href="http://www.planetplanet.org/">Planetarium</a></h2>
+
+<p>There is life on other planets. A heck of a lot, considering the community of Planet sites forming. It's the Big Bang all over again!</p>
+
+	<ul>
+		<li><a href="http://advogato.org/recentlog.html?thresh=4">Advogato</a>
+			<a href="http://advogato.org/">(main)</a></li>
+		<li><a href="http://planet.arslinux.com/">Ars Linux</a></li>
+                <li><a href="http://classpath.wildebeest.org/planet/">ClassPath</a></li>
+		<li><a href="http://planet.debian.net/">Debian</a></li>
+                <li><a href="http://fedora.linux.duke.edu/fedorapeople/">Fedora People</a></li>
+		<li><a href="http://fossplanet.osdir.com/">FOSS</a></li>
+                <li><a href="http://planet.freedesktop.org/">FreeDesktop</a></li>
+                <li><a href="http://planet.gnome.org/">GNOME</a></li>
+		<li><a href="http://gnome.or.kr/pgk/">GNOME Korea</a></li>
+                <li><a href="http://gstreamer.freedesktop.org/planet/">Planet GStreamer</a></li>
+                <li><a href="http://planetjava.org/">Java</a></li>
+		<li><a href="http://www.kdedevelopers.org/blog">KDE Developers</a></li>
+		<li><a href="http://kerneltrap.org/hackers/linux">Linux @ KernelTrap</a></li>
+                <li><a href="http://live.linuxchix.org/">LinuxChix</a></li>
+                <li><a href="http://xach.com/planet-lisp/">Lisp</a></li>
+		<li><a href="http://www.go-mono.com/monologue/">Mono</a></li>
+		<li><a href="http://planet.mozilla.org/">Mozilla</a></li>
+		<li><a href="http://people.novell.com/pn/">Novell</a> (offline atm)</li>
+                <li><a href="http://planet.perl.org/">Perl</a></li>
+		<li><a href="http://www.planet-php.org/">PHP</a></li>
+		<li><a href="http://www.planetpython.org/">Python</a></li>
+                <li><a href="http://www.mechanicalcat.net/pyblagg.html">Python People</a></li>
+		<li><a href="http://planet.rdfhack.com/">RDF</a></li>
+                <li><a href="http://www.planetsuse.org/">SuSE</a></li>
+                <li><a href="http://planetsun.org/">Sun</a></li>
+                <li><a href="http://planet.twistedmatrix.com/">Twisted</a></li>
+	</ul>
+
+</div>
+
+<div id="body">
+
+<TMPL_LOOP Items>
+
+<TMPL_IF new_date>
+<h2><TMPL_VAR new_date></h2>
+</TMPL_IF>
+
+<div class="news">
+<h3><a href="<TMPL_VAR channel_link>"><TMPL_VAR channel_name></a><TMPL_IF title> &mdash; <a href="<TMPL_VAR link>"><TMPL_VAR title></a></TMPL_IF></h3>
+<div class="content">
+<TMPL_VAR content>
+<div class="permalink">
+<a href="<TMPL_VAR link>" class="permalink"><TMPL_VAR date></a>
+</div>
+</div>
+</div>
+
+</TMPL_LOOP>
+
+</div>
+
+<div id="footer">
+<p>Updated <TMPL_VAR date></p>
+</div>
+
+</body>
+
+</html>

Added: webservices/admin/planet/index.html.tmplc
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/index.html.tmplc?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/index.html.tmplc
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/opml.xml.tmpl
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/opml.xml.tmpl?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/opml.xml.tmpl (added)
+++ webservices/admin/planet/opml.xml.tmpl Fri Dec  9 04:21:26 2005
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<opml version="1.1">
+	<head>
+		<title><TMPL_VAR name></title>
+		<dateCreated><TMPL_VAR date></dateCreated>
+		<dateModified><TMPL_VAR date></dateModified>
+		<ownerName>The Apache Community</ownerName>
+		<ownerEmail>community@apache.org</ownerEmail>
+	</head>
+	
+	<body>
+		<TMPL_LOOP Channels>
+		<outline text="<TMPL_VAR title ESCAPE="HTML">" xmlUrl="<TMPL_VAR uri ESCAPE="HTML">"/>
+		</TMPL_LOOP>
+	</body>
+</opml>

Added: webservices/admin/planet/opml.xml.tmplc
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/opml.xml.tmplc?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/opml.xml.tmplc
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/output/images/edd.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/edd.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/output/images/edd.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/output/images/evolution.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/evolution.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/output/images/evolution.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/output/images/jdub.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/jdub.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/output/images/jdub.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/output/images/keybuk.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/keybuk.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/output/images/keybuk.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/output/images/logo.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/logo.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/output/images/logo.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/output/images/planet.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/planet.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/output/images/planet.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/output/images/thom.png
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/images/thom.png?rev=355462&view=auto
==============================================================================
Binary file - no diff available.

Propchange: webservices/admin/planet/output/images/thom.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: webservices/admin/planet/output/planet.css
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/output/planet.css?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/output/planet.css (added)
+++ webservices/admin/planet/output/planet.css Fri Dec  9 04:21:26 2005
@@ -0,0 +1,122 @@
+body {
+	border-right: 1px solid black;
+	margin-right: 200px;
+
+	padding-left: 20px;
+	padding-right: 20px;
+}
+
+h1 {
+	margin-top: 0px;
+	padding-top: 20px;
+
+	font-family: "Bitstream Vera Sans", sans-serif;
+	font-weight: normal;
+	letter-spacing: -2px;
+	text-transform: lowercase;
+	text-align: right;
+
+	color: grey;
+}
+
+h2 {
+	font-family: "Bitstream Vera Sans", sans-serif;
+	font-weight: normal;
+	color: #200080;
+
+	margin-left: -20px;
+}
+
+h3 {
+	font-family: "Bitstream Vera Sans", sans-serif;
+	font-weight: normal;
+
+	background-color: #a0c0ff;
+	border: 1px solid #5080b0;
+
+	padding: 4px;
+}
+
+h3 a {
+	text-decoration: none;
+	color: inherit;
+}
+
+h4 {
+	font-family: "Bitstream Vera Sans", sans-serif;
+	font-weight: bold;
+}
+
+h4 a {
+	text-decoration: none;
+	color: inherit;
+}
+
+img.face {
+	float: right;
+	margin-top: -3em;
+}
+
+.entry {
+	margin-bottom: 2em;
+}
+
+.entry .date {
+	font-family: "Bitstream Vera Sans", sans-serif;
+	color: grey;
+}
+
+.entry .date a {
+	text-decoration: none;
+	color: inherit;
+}
+
+.sidebar {
+	position: absolute;
+	top: 0px;
+	right: 0px;
+	width: 200px;
+
+	margin-left: 0px;
+	margin-right: 0px;
+	padding-right: 0px;
+
+	padding-top: 20px;
+	padding-left: 0px;
+
+	font-family: "Bitstream Vera Sans", sans-serif;
+	font-size: 85%;
+}
+
+.sidebar h2 {
+	font-size: 110%;
+	font-weight: bold;
+	color: black;
+
+	padding-left: 5px;
+	margin-left: 0px;
+}
+
+.sidebar ul {
+	padding-left: 2em;
+	margin-left: 0px;
+
+	list-style-type: square;
+}
+
+.sidebar ul li:hover {
+	color: grey;
+}
+
+.sidebar p {
+	border-top: 1px solid grey;
+	margin-top: 30px;
+	padding-top: 10px;
+
+	padding-left: 5px;
+}
+
+a:hover {
+	text-decoration: underline !important;
+	color: blue !important;
+}
\ No newline at end of file

Added: webservices/admin/planet/planet.py
URL: http://svn.apache.org/viewcvs/webservices/admin/planet/planet.py?rev=355462&view=auto
==============================================================================
--- webservices/admin/planet/planet.py (added)
+++ webservices/admin/planet/planet.py Fri Dec  9 04:21:26 2005
@@ -0,0 +1,260 @@
+#!/usr/bin/env python
+"""The Planet RSS Aggregator.
+
+Requires Python 2.2.
+"""
+
+__version__ = "0.2"
+__authors__ = [ "Scott James Remnant <sc...@netsplit.com>",
+                "Jeff Waugh <jd...@perkypants.org>" ]
+__credits__ = "Originally based on spycyroll."
+__license__ = "Python"
+
+
+import sys
+import time
+import os
+
+try:
+    import logging
+except:
+    import compat_logging
+    logging = compat_logging
+
+import planetlib
+stripper = planetlib.Stripper()
+
+from ConfigParser import ConfigParser
+from htmltmpl import TemplateManager, TemplateProcessor
+
+generator = " ".join(("Planet", planetlib.__version__,
+    "http://www.planetplanet.org/"))
+
+
+# Defaults for [Planet] config sections
+CONFIG_FILE = 'config.ini'
+PLANET_NAME = 'Unconfigured Planet'
+PLANET_LINK = 'Unconfigured Planet'
+OWNER_NAME = 'Anonymous Coward'
+OWNER_EMAIL = ''
+TEMPLATE_FILES = " ".join(('examples/planet.html.tmpl',
+                           'examples/planet.rss10.tmpl',
+                           'examples/planet.rss20.tmpl'))
+OUTPUT_DIR = 'output'
+ITEMS_PER_PAGE = 60
+DAYS_PER_PAGE = 0
+DATE_FORMAT = '%B %d, %Y %I:%M %p'
+LOG_LEVEL = 'WARNING'
+
+
+def config_get(config, section, option, default=None, raw=0, vars=None):
+    """Get a value from the configuration, with a default."""
+    if config.has_option(section, option):
+        return config.get(section, option, raw=raw, vars=None)
+    else:
+        return default
+
+def tcfg_get(config, template, option, default=None, raw=0, vars=None):
+    """Get a template value from the configuration, with a default."""
+    if config.has_option(template, option):
+        return config.get(template, option, raw=raw, vars=None)
+    elif config.has_option("Planet", option):
+        return config.get("Planet", option, raw=raw, vars=None)
+    else:
+        return default
+
+
+if __name__ == "__main__":
+    config_file = CONFIG_FILE
+    offline = 0
+
+    for arg in sys.argv[1:]:
+        if arg == '-h' or arg == '--help':
+            print "Usage: planet [options] [CONFIGFILE]"
+            print
+            print "Options:"
+            print " -o, --offline       Update the Planet from the cache only"
+            print " -h, --help          Display this help message and exit"
+            print
+            sys.exit(0)
+        elif arg == '-o' or arg == '--offline':
+            offline = 1
+        elif arg.startswith("-"):
+            print >>sys.stderr, "Unknown option:", arg
+            sys.exit(1)
+        else:
+            config_file = arg
+
+    config = ConfigParser()
+    config.read(config_file)
+    if not config.has_section('Planet'):
+        print >>sys.stderr, "Configuration missing [Planet] section."
+        sys.exit(1)
+
+    # Read the global configuration values
+    planet_name = config_get(config, 'Planet', 'name', PLANET_NAME)
+    planet_link = config_get(config, 'Planet', 'link', PLANET_LINK)
+    owner_name = config_get(config, 'Planet', 'owner_name', OWNER_NAME)
+    owner_email = config_get(config, 'Planet', 'owner_email', OWNER_EMAIL)
+    if config.has_option('Planet', 'cache_directory'):
+        planetlib.cache_directory = config.get('Planet', 'cache_directory')
+    log_level = config_get(config, 'Planet', 'log_level', LOG_LEVEL)
+    template_files = config_get(config, 'Planet', 'template_files',
+                                TEMPLATE_FILES).split(" ")
+
+    # Activate the settings
+    logging.getLogger().setLevel(logging.getLevelName(log_level))
+    planetlib.user_agent = " ".join((planet_name, planet_link,
+                                     planetlib.user_agent))
+
+    # The other configuration blocks are channels to subscribe to
+    channels = {}
+    planet = planetlib.Planet()
+    for feed in config.sections():
+        if feed == 'Planet' or feed in template_files: continue
+
+        # Build a configuration dict the slow, 2.2-compatible way
+        channel_options = {}
+        options = config.options(feed)
+        for option in options:
+            channel_options[option] = config.get(feed, option)
+
+        # Create the Channel and subscribe to it
+        try:
+            logging.info("Subscribing <" + feed + ">")
+            channel = planetlib.Channel(feed, channel_options)
+            planet.subscribe(channel)
+        except:
+            logging.error("Subscription failure <" + feed + ">",
+                          exc_info=1)
+            continue
+
+        try:
+            if not offline:
+                channel.update()
+        except:
+            logging.error("Update from <" + channel.uri + "> failed", exc_info=1)
+
+        # Prepare the template information now
+        info = {}
+        info["uri"] = channel.uri
+        info["title"] = channel.title
+        info["description"] = channel.description
+        info["link"] = channel.link
+        for k, v in channel.props.items():
+            info[k] = v
+        if "name" not in info:
+            info["name"] = channel.title
+
+        channels[channel] = info
+
+    # Sort the channels list by name
+    channel_list = channels.values()
+    channel_list.sort(lambda x,y: cmp(x["name"], y["name"]))
+
+
+    # Go-go-gadget-template
+    for template_file in template_files:
+        output_dir = tcfg_get(config, template_file, 'output_dir', OUTPUT_DIR)
+        date_format = tcfg_get(config, template_file, 'date_format',
+                               DATE_FORMAT, raw=1)
+        items_per_page = int(tcfg_get(config, template_file, 'items_per_page',
+                                      ITEMS_PER_PAGE))
+        days_per_page = int(tcfg_get(config, template_file, 'days_per_page',
+                                     DAYS_PER_PAGE))
+
+        # We treat each template individually
+        base = os.path.basename(template_file)
+        logging.info("Processing " + base)
+        output_file = os.path.join(output_dir, os.path.splitext(base)[0])
+
+        # Work out the URI to this output file
+        uri = planet_link
+        if not uri.endswith("/"): uri += "/"
+        uri += os.path.basename(output_file)
+
+        # Prepare the template information for the items
+        items = []
+        prev_date = None
+        prev_channel = None
+        date_today_raw = time.time()
+
+        # Prepare information for this template
+        item_count = 0
+        for newsitem in planet.items():
+            format_date = newsitem.channel.format_date
+
+            info = {}
+            info["id"] = newsitem.id
+            info["date"] = format_date(newsitem.date, date_format)
+            info["date_iso"] = format_date(newsitem.date, 'iso')
+            info["date_822"] = format_date(newsitem.date, 'rfc822')
+            info["title"] = newsitem.title
+            info["summary"] = newsitem.summary
+            info["content"] = newsitem.content
+            info["link"] = newsitem.link
+            info["creator"] = newsitem.creator
+
+            chaninfo = channels[newsitem.channel]
+            for k, v in chaninfo.items():
+                info["channel_" + k] = v
+
+            if "channel_keyword" in info:
+                term = info["channel_keyword"]
+                def ifind(needle, haystack):
+                    return haystack.lower().find(needle.lower())
+
+                if ifind(term, newsitem.title) == -1 \
+                    and ifind(term, stripper.strip(newsitem.summary)) == -1 \
+                    and ifind(term, stripper.strip(newsitem.content)) == -1:
+                    continue
+
+            date_raw = newsitem.channel.utctime(newsitem.date)
+            date = time.gmtime(date_raw)
+
+            if prev_date != date[:3]:
+                if days_per_page and date_today_raw > date_raw:
+                    days_passed = time.gmtime(date_today_raw - date_raw).tm_yday
+                    if days_passed >= days_per_page:
+                        continue
+
+                info["new_date"] = time.strftime("%B %d, %Y", date)
+                prev_date = date[:3]
+            else:
+                info["new_date"] = ""
+
+            if prev_channel != info["channel_uri"] or info["new_date"] != "":
+                info["new_channel"] = info["channel_uri"]
+                prev_channel = info["channel_uri"]
+            else:
+                info["new_channel"] = ""
+
+            items.append(info)
+
+            # track the number of items added manually, because we skip some
+            item_count += 1
+            if item_count >= items_per_page:
+                break
+
+        # Process the template
+        template = TemplateManager().prepare(template_file)
+        tp = TemplateProcessor(html_escape=0)
+        tp.set("generator", generator)
+        tp.set("name", planet_name)
+        tp.set("link", planet_link)
+        tp.set("owner_name", owner_name)
+        tp.set("owner_email", owner_email)
+        tp.set("uri", uri)
+        tp.set("date", time.strftime(date_format, time.gmtime()))
+        tp.set("date_iso", time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()))
+        tp.set("date_822", time.strftime("%a, %d %b %Y %H:%M:%S +0000",
+                                         time.gmtime()))
+        tp.set("Items", items)
+        tp.set("Channels", channel_list)
+        try:
+            logging.info("Writing " + output_file)
+            output = open(output_file, "w")
+            output.write(tp.process(template))
+            output.close()
+        except:
+            logging.error("Couldn't save " + output_file, exc_info=1)