"XYAPTU: Lightweight XML/HTML Document Template Engine for Python" __version__ = '1.0.0' __author__= [ 'Alex Martelli (aleax@aleax.it)', 'Mario Ruggier (mario@ruggier.org)' ] __copyright__ = '(c) Python Style Copyright. All Rights Reserved. No Warranty.' __dependencies__ = ['YAPTU 1.2, http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52305'] __history__= { '1.0.0' : '2002/11/13: First Released Version', } #################################################### import sys, re, string from yaptu import copier class xcopier(copier): ' xcopier class, inherits from yaptu.copier ' def __init__(self, dns, rExpr=None, rOpen=None, rClose=None, rClause=None, ouf=sys.stdout, dbg=0, dbgOuf=sys.stdout): ' set default regular expressions required by yaptu.copier ' # Default regexps for yaptu delimeters (what xyaptu tags are first converted to) # These must be in sync with what is output in self._x2y_translate _reExpression = re.compile('_:@([^:@]+)@:_') _reOpen = re.compile('\++yaptu ') _reClose = re.compile('--yaptu') _reClause = re.compile('==yaptu ') rExpr = rExpr or _reExpression rOpen = rOpen or _reOpen rClose = rClose or _reClose rClause = rClause or _reClause # Debugging self.dbg = dbg self.dbgOuf = dbgOuf _preproc = self._preProcess if dbg: _preproc = self._preProcessDbg # Call super init copier.__init__(self, rExpr, dns, rOpen, rClose, rClause, preproc=_preproc, handle=self._handleBadExps, ouf=ouf) def xcopy(self, input=None): ''' Converts the value of the input stream (or contents of input filename) from xyaptu format to yaptu format, and invokes yaptu.copy ''' # Read the input inf = input try: inputText = inf.read() except AttributeError: inf = open(input) if inf is None: raise ValueError, "Can't open file (%s)" % input inputText = inf.read() try: inf.close() except: pass # Translate (xyaptu) input to (yaptu) input, and call yaptu.copy() from cStringIO import StringIO yinf = StringIO(self._x2y_translate(inputText)) self.copy(inf=yinf) yinf.close() def _x2y_translate(self, xStr): ' Converts xyaptu markup in input string to yaptu delimeters ' # Define regexps to match xml elements on. # The variations (all except for py-expr, py-close) we look for are: # | # ignored text | # {python code} # ${py-expr} | $py-expr | reExpr = re.compile(r''' \$\{([^}]+)\} | # ${py-expr} \$([_\w]+) | # $py-expr | [^<]* | ([^<]*) ''', re.VERBOSE) # reLine = re.compile(r''' | [^<]* | ([^<]*) ''', re.VERBOSE) # reOpen = re.compile(r''' | [^<]* | ([^<]*) ''', re.VERBOSE) # reClause = re.compile(r''' | [^<]* | ([^<]*) ''', re.VERBOSE) # reClose = re.compile(r''' | .* ''', re.VERBOSE) # Call-back functions for re substitutions # These must be in sync with what is expected in self.__init__ def rexpr(match,self=self): return '_:@%s@:_' % match.group(match.lastindex) def rline(match,self=self): return '\n++yaptu %s #\n--yaptu \n' % match.group(match.lastindex) def ropen(match,self=self): return '\n++yaptu %s \n' % match.group(match.lastindex) def rclause(match,self=self): return '\n==yaptu %s \n' % match.group(match.lastindex) def rclose(match,self=self): return '\n--yaptu \n' # Substitutions xStr = reExpr.sub(rexpr, xStr) xStr = reLine.sub(rline, xStr) xStr = reOpen.sub(ropen, xStr) xStr = reClause.sub(rclause, xStr) xStr = reClose.sub(rclose, xStr) # When in debug mode, keep a copy of intermediate template format if self.dbg: _sep = '====================\n' self.dbgOuf.write('%sIntermediate YAPTU format:\n%s\n%s' % (_sep, xStr, _sep)) return xStr # Handle expressions that do not evaluate def _handleBadExps(self, s): ' Handle expressions that do not evaluate ' if self.dbg: self.dbgOuf.write('!!! ERROR: failed to evaluate expression: %s \n' % s) return '***! %s !***' % s # Preprocess code def _preProcess(self, s, why): ' Preprocess embedded python statements and expressions ' return self._xmlDecode(s) def _preProcessDbg(self, s, why): ' Preprocess embedded python statements and expressions ' self.dbgOuf.write('!!! DBG: %s %s \n' % (s, why)) return self._xmlDecode(s) # Decode utility for XML/HTML special characters _xmlCodes = [ ['"', '"'], ['>', '>'], ['<', '<'], ['&', '&'], ] def _xmlDecode(self, s): ' Returns the ASCII decoded version of the given HTML string. ' codes = self._xmlCodes for code in codes: s = string.replace(s, code[1], code[0]) return s #################################################### if __name__=='__main__': ################################################## # Document Name Space (a dictionary, normally prepared by runtime application, # and that serves as the substitution namespace for instantiating a doc template). # DNS = { 'pageTitle' : 'Event Log (xyaptu test page)', 'baseUrl' : 'http://xproject.sourceforge.net/', 'sid' : 'a1b2c3xyz', 'session' : 1, 'userName' : 'mario', 'startTime' : '12:31:42', 'AllComputerCaptions' : 'No', 'ComputerCaption' : 'mymachine01', 'LogSeverity' : ['Info', 'Warning', 'Error' ], 'LogFileType' : 'Application', 'logTimeStamp' : 'Event Log Dump written on 25 May 2001 at 13:55', 'logHeadings' : ['Type', 'Date', 'Time', 'Source', 'Category', 'Computer', 'Message'] , 'logEntries' : [ ['Info', '14/05/2001', '15:26', 'MsiInstaller', '0', 'PC01', 'winzip80 install ok...'], ['Warning', '16/05/2001', '02:43', 'EventSystem', '4', 'PC02', 'COM+ failed...'], ['Error', '22/05/2001', '11:35', 'rasctrs', '0', 'PC03', '...', ' ** EXTRA ** ' ], ] } # and a function... def my_current_time(): import time return str(time.clock()) DNS['my_current_time'] = my_current_time ''' # To use functions defined in an external library import externalFunctionsLib dict['fcn'] = externalFunctionsLib # which will therefore permit to call functions with: ${fcn.somefun()} ''' ################################################## # Sample page template that uses the xyaptu tags and pcdata expressions. # Note that: # - source code indentation here is irrelevant for xyaptu # - xyaptu tags may span more than one source line # templateString = ''' $pageTitle Logged on as $userName, since startTime (Logout?)

${pageTitle}


${a bad expression}

Filtering Event Log With:
All Computers: $AllComputerCaptions
Computer Name: $ComputerCaption
Log Severity: $LG
Log File Type:


$logTimeStamp

code attribute takes precedence over this text, which is duly ignored for i in range(0,len(logentry)): ### close (this is ignored)
$h
${logentry[i]} Oops!

Current time: ${my_current_time()}
''' ################################################## # Set a filelike object to templateString from cStringIO import StringIO templateStream = StringIO(templateString) ################################################## # Initialise an xyaptu xcopier, and call xcopy xcp = xcopier(DNS) xcp.xcopy(templateStream) ################################################## # Test DBG 1 # Set dbg ON (writing dbg statements on output stream) ''' xcp = xcopier(DNS, dbg=1) xcp.xcopy(templateStream) ''' ################################################## # Test DBG 2 # Write dbg statements to a separate dbg stream ''' dbgStream = StringIO() dbgStream.write('DBG info: \n') xcp = xcopier(DNS, dbg=1, dbgOuf=dbgStream) xcp.xcopy(templateStream) print dbgStream.getvalue() dbgStream.close() ''' ####################################################