0001 #!/usr/bin/env python 0002 0003 """ 0004 This code is taken from the ASPN Python cookbook. I have 0005 combined YAPTU and XYAPTU into a single file. The copyright, 0006 warranty and license remain with the original authors. Please 0007 consult these two URLs 0008 0009 - U{http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52305} 0010 - U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/162292} 0011 0012 The following changes were made: 0013 0014 - Removed very lengthy examples from the end (see the above URLs for 0015 them) 0016 - Added: setupxcopy() takes the template text and remembers it 0017 - Added: xcopywithdns() does the copy with supplied DNS (Document Name Space which is 0018 a dict of variables) and remembered template 0019 and returns the resulting string 0020 - Exception handling for statements was added (xyaptu only did it for 0021 expressions) 0022 - The default behaviour for exceptions now puts a dump of the exception 0023 into the generated output as an HTML <!-- --> style comment so you 0024 can view source to find out what has happened. My common library 0025 exception formatter (which also dumps local variables) is used. 0026 0027 0028 """ 0029 0030 import sys 0031 import re 0032 import string 0033 import common 0034 0035 ## "Yet Another Python Templating Utility, Version 1.2" 0036 0037 # utility stuff to avoid tests in the mainline code 0038 class _nevermatch: 0039 "Polymorphic with a regex that never matches" 0040 def match(self, line): 0041 return None 0042 _never = _nevermatch() # one reusable instance of it suffices 0043 def identity(string, why): 0044 "A do-nothing-special-to-the-input, just-return-it function" 0045 return string 0046 def nohandle(*args): 0047 "A do-nothing handler that just re-raises the exception" 0048 raise 0049 0050 # and now the real thing 0051 class copier: 0052 "Smart-copier (YAPTU) class" 0053 def copyblock(self, i=0, last=None): 0054 "Main copy method: process lines [i,last) of block" 0055 def repl(match, self=self): 0056 "return the eval of a found expression, for replacement" 0057 # uncomment for debug: print '!!! replacing',match.group(1) 0058 expr = self.preproc(match.group(1), 'eval') 0059 try: return common.strorunicode(eval(expr, self.globals, self.locals)) 0060 except: return common.strorunicode(self.handle(expr)) 0061 block = self.locals['_bl'] 0062 if last is None: last = len(block) 0063 while i<last: 0064 line = block[i] 0065 match = self.restat.match(line) 0066 if match: # a statement starts "here" (at line block[i]) 0067 # i is the last line to _not_ process 0068 stat = match.string[match.end(0):].strip() 0069 j=i+1 # look for 'finish' from here onwards 0070 nest=1 # count nesting levels of statements 0071 while j<last: 0072 line = block[j] 0073 # first look for nested statements or 'finish' lines 0074 if self.restend.match(line): # found a statement-end 0075 nest = nest - 1 # update (decrease) nesting 0076 if nest==0: break # j is first line to _not_ process 0077 elif self.restat.match(line): # found a nested statement 0078 nest = nest + 1 # update (increase) nesting 0079 elif nest==1: # look for continuation only at this nesting 0080 match = self.recont.match(line) 0081 if match: # found a contin.-statement 0082 nestat = match.string[match.end(0):].strip() 0083 stat = '%s _cb(%s,%s)\n%s' % (stat,i+1,j,nestat) 0084 i=j # again, i is the last line to _not_ process 0085 j=j+1 0086 stat = self.preproc(stat, 'exec') 0087 stat = '%s _cb(%s,%s)' % (stat,i+1,j) 0088 # for debugging, uncomment...: print "-> Executing: {"+stat+"}" 0089 try: 0090 exec stat in self.globals,self.locals 0091 except: 0092 # not as good as it could be 0093 self.ouf.write(str(self.handle(stat,self.locals,self.globals))) 0094 i=j+1 0095 else: # normal line, just copy with substitution 0096 self.ouf.write(self.regex.sub(repl,line)) 0097 i=i+1 0098 def __init__(self, regex=_never, dict={}, 0099 restat=_never, restend=_never, recont=_never, 0100 preproc=identity, handle=nohandle, ouf=sys.stdout): 0101 "Initialize self's attributes" 0102 self.regex = regex 0103 self.globals = dict 0104 self.locals = { '_cb':self.copyblock } 0105 self.restat = restat 0106 self.restend = restend 0107 self.recont = recont 0108 self.preproc = preproc 0109 self.handle = handle 0110 self.ouf = ouf 0111 def copy(self, block=None, inf=sys.stdin): 0112 "Entry point: copy-with-processing a file, or a block of lines" 0113 if block is None: block = inf.readlines() 0114 self.locals['_bl'] = block 0115 self.copyblock() 0116 0117 ##"XYAPTU: Lightweight XML/HTML Document Template Engine for Python" 0118 0119 ##__version__ = '1.0.0' 0120 ##__author__= [ 0121 ## 'Alex Martelli (aleax@aleax.it)', 0122 ## 'Mario Ruggier (mario@ruggier.org)' 0123 ##] 0124 ##__copyright__ = '(c) Python Style Copyright. All Rights Reserved. No Warranty.' 0125 ##__dependencies__ = ['YAPTU 1.2, http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52305'] 0126 ##__history__= { 0127 ## '1.0.0' : '2002/11/13: First Released Version', 0128 ##} 0129 0130 class xcopier(copier): 0131 ' xcopier class, inherits from yaptu.copier ' 0132 0133 def __init__(self, dns, rExpr=None, rOpen=None, rClose=None, rClause=None, 0134 ouf=sys.stdout, dbg=0, dbgOuf=sys.stdout): 0135 ' set default regular expressions required by yaptu.copier ' 0136 0137 # Default regexps for yaptu delimeters (what xyaptu tags are first converted to) 0138 # These must be in sync with what is output in self._x2y_translate 0139 _reExpression = re.compile('_:@([^:@]+)@:_') 0140 _reOpen = re.compile('\++yaptu ') 0141 _reClose = re.compile('--yaptu') 0142 _reClause = re.compile('==yaptu ') 0143 0144 rExpr = rExpr or _reExpression 0145 rOpen = rOpen or _reOpen 0146 rClose = rClose or _reClose 0147 rClause = rClause or _reClause 0148 0149 # Debugging 0150 self.dbg = dbg 0151 self.dbgOuf = dbgOuf 0152 _preproc = self._preProcess 0153 if dbg: _preproc = self._preProcessDbg 0154 0155 # Call super init 0156 copier.__init__(self, rExpr, dns, rOpen, rClose, rClause, 0157 preproc=_preproc, handle=self._handleBadExps, ouf=ouf) 0158 0159 0160 def xcopy(self, inputText): 0161 ''' 0162 Converts the value of the input stream (or contents of input filename) 0163 from xyaptu format to yaptu format, and invokes yaptu.copy 0164 ''' 0165 0166 # Translate (xyaptu) input to (yaptu) input, and call yaptu.copy() 0167 from StringIO import StringIO 0168 yinf = StringIO(self._x2y_translate(inputText)) 0169 self.copy(inf=yinf) 0170 yinf.close() 0171 0172 def setupxcopy(self, inputText): 0173 from StringIO import StringIO 0174 yinf = StringIO(self._x2y_translate(inputText)) 0175 # we have to build the list since you can only run 0176 # readline/s once on a file 0177 self.remembered=[line for line in yinf.readlines()] 0178 0179 def xcopywithdns(self, dns): 0180 from StringIO import StringIO 0181 self.globals=dns 0182 out=StringIO() 0183 self.ouf=out 0184 self.copy(self.remembered) 0185 return out.getvalue() 0186 0187 def _x2y_translate(self, xStr): 0188 ' Converts xyaptu markup in input string to yaptu delimeters ' 0189 0190 # Define regexps to match xml elements on. 0191 # The variations (all except for py-expr, py-close) we look for are: 0192 # <py-elem code="{python code}" /> | 0193 # <py-elem code="{python code}">ignored text</py-elem> | 0194 # <py-elem>{python code}</py-elem> 0195 0196 # ${py-expr} | $py-expr | <py-expr code="pvkey" /> 0197 reExpr = re.compile(r''' 0198 \$\{([^}]+)\} | # ${py-expr} 0199 \$([_\w]+) | # $py-expr 0200 <py-expr\s+code\s*=\s*"([^"]*)"\s*/> | 0201 <py-expr\s+code\s*=\s*"([^"]*)"\s*>[^<]*</py-expr> | 0202 <py-expr\s*>([^<]*)</py-expr\s*> 0203 ''', re.VERBOSE) 0204 0205 # <py-line code="pvkeys=pageVars.keys()"/> 0206 reLine = re.compile(r''' 0207 <py-line\s+code\s*=\s*"([^"]*)"\s*/> | 0208 <py-line\s+code\s*=\s*"([^"]*)"\s*>[^<]*</py-line> | 0209 <py-line\s*>([^<]*)</py-line\s*> 0210 ''', re.VERBOSE) 0211 0212 # <py-open code="for k in pageVars.keys():" /> 0213 reOpen = re.compile(r''' 0214 <py-open\s+code\s*=\s*"([^"]*)"\s*/> | 0215 <py-open\s+code\s*=\s*"([^"]*)"\s*>[^<]*</py-open\s*> | 0216 <py-open\s*>([^<]*)</py-open\s*> 0217 ''', re.VERBOSE) 0218 0219 # <py-clause code="else:" /> 0220 reClause = re.compile(r''' 0221 <py-clause\s+code\s*=\s*"([^"]*)"\s*/> | 0222 <py-clause\s+code\s*=\s*"([^"]*)"\s*>[^<]*</py-clause\s*> | 0223 <py-clause\s*>([^<]*)</py-clause\s*> 0224 ''', re.VERBOSE) 0225 0226 # <py-close /> 0227 reClose = re.compile(r''' 0228 <py-close\s*/> | 0229 <py-close\s*>.*</py-close\s*> 0230 ''', re.VERBOSE) 0231 0232 # Call-back functions for re substitutions 0233 # These must be in sync with what is expected in self.__init__ 0234 def rexpr(match,self=self): 0235 return '_:@%s@:_' % match.group(match.lastindex) 0236 def rline(match,self=self): 0237 return '\n++yaptu %s #\n--yaptu \n' % match.group(match.lastindex) 0238 def ropen(match,self=self): 0239 return '\n++yaptu %s \n' % match.group(match.lastindex) 0240 def rclause(match,self=self): 0241 return '\n==yaptu %s \n' % match.group(match.lastindex) 0242 def rclose(match,self=self): 0243 return '\n--yaptu \n' 0244 0245 # Substitutions 0246 xStr = reExpr.sub(rexpr, xStr) 0247 xStr = reLine.sub(rline, xStr) 0248 xStr = reOpen.sub(ropen, xStr) 0249 xStr = reClause.sub(rclause, xStr) 0250 xStr = reClose.sub(rclose, xStr) 0251 0252 # When in debug mode, keep a copy of intermediate template format 0253 if self.dbg: 0254 _sep = '====================\n' 0255 self.dbgOuf.write('%sIntermediate YAPTU format:\n%s\n%s' % (_sep, xStr, _sep)) 0256 0257 return xStr 0258 0259 # Handle expressions that do not evaluate 0260 def _handleBadExps(self, s, locals=None, globals=None): 0261 ' Handle expressions that do not evaluate ' 0262 if self.dbg: 0263 self.dbgOuf.write('!!! ERROR: failed to evaluate expression: %s \n' % s) 0264 res="<!-- EXCEPTION: \nExpression: "+s+"\n" 0265 res+=common.formatexception()+"\n-->" 0266 print common.formatexception() 0267 return res+('***! %s !***' % s) 0268 0269 # Preprocess code 0270 def _preProcess(self, s, why): 0271 ' Preprocess embedded python statements and expressions ' 0272 return self._xmlDecode(s) 0273 def _preProcessDbg(self, s, why): 0274 ' Preprocess embedded python statements and expressions ' 0275 self.dbgOuf.write('!!! DBG: %s %s \n' % (s, why)) 0276 return self._xmlDecode(s) 0277 0278 # Decode utility for XML/HTML special characters 0279 _xmlCodes = [ 0280 ['"', '"'], 0281 ['>', '>'], 0282 ['<', '<'], 0283 ['&', '&'], 0284 ] 0285 def _xmlDecode(self, s): 0286 ' Returns the ASCII decoded version of the given HTML string. ' 0287 codes = self._xmlCodes 0288 for code in codes: 0289 s = string.replace(s, code[1], code[0]) 0290 return s 0291
Generated by PyXR 0.9.4