PyXR

c:\projects\bitpim\src \ xyaptu.py



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     ['"', '&quot;'],
0281     ['>', '&gt;'],
0282     ['<', '&lt;'],
0283     ['&', '&amp;'],
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