1#'$Id: HTMLgen.py 10290 2018-03-19 09:26:28Z epetrich $' 2 3# COPYRIGHT (C) 1996-9 ROBIN FRIEDRICH email:Robin.Friedrich@pdq.net 4# Permission to use, copy, modify, and distribute this software and 5# its documentation for any purpose and without fee is hereby granted, 6# provided that the above copyright notice appear in all copies and 7# that both that copyright notice and this permission notice appear in 8# supporting documentation. 9# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 10# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 11# FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 12# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 13# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF 14# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 15# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 17# Stripped down by Michael Hope <michaelh@juju.net.nz> to just support 18# template documents. 19 20"""A class library for the generation of HTML documents. 21 22Each HTML tag type has a supporting class which is responsible for 23emitting itself as valid HTML formatted text. An attempt is made to 24provide classes for newer HTML 3.2 and proposed tag elements. The 25definitive reference for HTML tag elements can be found at 26[W3C]. Also, I used the HTML book by Musciano and 27Kennedy from [O Reilly] (2nd. Ed.) as the guiding reference. 28 29The Document classes are container objects which act as a focal point 30to populate all the contents of a particular web page. It also can 31enforce consistent document formating according to the guidelines from 32the [Yale Web Style Manual]. 33 34Features include customization of document template graphics / colors 35through use of resource files, minimizing the need for modifying or 36subclassing from the module source code. Support for tables, frames, 37forms (persistent and otherwise) and client-side imagemaps are included. 38 39A newer implementation for the Table support is now included, 40TableLite(). In support of this there are new tag classes TD, TH, TR 41and Caption. These class instances can be assembled in any way to 42populate the TableLite container object. 43 44.. [W3C] http://www.W3.org/TR/REC-html32.html 45.. [O Reilly] http://www.oreilly.com/catalog/html3/index.html 46.. [Yale Web Style Manual] http://info.med.yale.edu/caim/manual/contents.html 47""" 48 49import string, re, time, os, sys 50 51__author__ = 'Robin Friedrich friedrich@pythonpros.com' 52__version__ = '2.2.2' 53 54################# 55# CLASS LIBRARY # 56################# 57 58class StringTemplate: 59 """Generate documents based on a template and a substitution mapping. 60 61 Must use Python 1.5 or newer. Uses re and the get method on dictionaries. 62 63 Usage: 64 T = TemplateDocument('Xfile') 65 T.substitutions = {'month': ObjectY, 'town': 'Scarborough'} 66 T.write('Maine.html') 67 68 A dictionary, or object that behaves like a dictionary, is assigned to the 69 *substitutions* attribute which has symbols as keys to objects. Upon every 70 occurance of these symbols surrounded by braces {} in the source template, 71 the corresponding value is converted to a string and substituted in the output. 72 73 For example, source text which looks like: 74 I lost my heart at {town} Fair. 75 becomes: 76 I lost my heart at Scarborough Fair. 77 78 Symbols in braces which do not correspond to a key in the dictionary remain 79 unchanged. 80 81 An optional third argument to the class is a list or two strings to be 82 used as the delimiters instead of { } braces. They must be of the same 83 length; for example ['##+', '##'] is invalid. 84 """ 85 def __init__(self, template, substitutions=None, **kw): 86 self.delimiters = ['{', '}'] 87 self.__dict__.update(kw) 88 if len(self.delimiters) != 2: 89 raise ValueError("delimiter argument must be a pair of strings") 90 self.delimiter_width = len(self.delimiters[0]) 91 delimiters = list(map(re.escape, self.delimiters)) 92 self.subpatstr = delimiters[0] + "[\w_]+" + delimiters[1] 93 self.subpat = re.compile(self.subpatstr) 94 self.substitutions = substitutions or {} 95 self.set_template(template) 96 97 def set_template(self, template): 98 self.source = template 99 100 def keys(self): 101 return list(self.substitutions.keys()) 102 103 def __setitem__(self, name, value): 104 self.substitutions[name] = value 105 106 def __getitem__(self, name): 107 return self.substitutions[name] 108 109 def __str__(self): 110 return self._sub(self.source) 111 112 def _sub(self, source, subs=None): 113 """Perform source text substitutions. 114 115 *source* string containing template source text 116 *subs* mapping of symbols to replacement values 117 """ 118 substitutions = subs or self.substitutions 119 dw = self.delimiter_width 120 i = 0 121 output = [] 122 matched = self.subpat.search(source[i:]) 123 while matched: 124 a, b = matched.span() 125 output.append(source[i:i+a]) 126 # using the new get method for dicts in 1.5 127 output.append(str(substitutions.get( 128 source[i+a+dw:i+b-dw], source[i+a:i+b]))) 129 i = i + b 130 matched = self.subpat.search(source[i:]) 131 else: 132 output.append(source[i:]) 133 return "".join(output) 134 135 def write(self, filename = None): 136 """Emit the Document HTML to a file or standard output. 137 138 Will not overwrite file is it exists and is textually the same. 139 In Unix you can use environment variables in filenames. 140 Will print to stdout if no argument given. 141 """ 142 if filename: 143 if os.path.exists(filename): 144 s = str(self) 145 if compare_s2f(s, filename): 146 if sys.version_info[0]<3: 147 f = open(filename, 'w') 148 else: 149 f = open(filename, 'w', encoding='latin-1') 150 f.write(str(self)) 151 f.close() 152 else: 153 if sys.version_info[0]<3: 154 f = open(filename, 'w') 155 else: 156 f = open(filename, 'w', encoding='latin-1') 157 f.write(str(self)) 158 f.close() 159 else: 160 sys.stdout.write(str(self)) 161 162class TemplateDocument(StringTemplate): 163 """Generate documents based on a template and a substitution mapping. 164 165 Must use Python 1.5 or newer. Uses re and the get method on dictionaries. 166 167 Usage: 168 T = TemplateDocument('Xfile') 169 T.substitutions = {'month': ObjectY, 'town': 'Scarborough'} 170 T.write('Maine.html') 171 172 A dictionary, or object that behaves like a dictionary, is assigned to the 173 *substitutions* attribute which has symbols as keys to objects. Upon every 174 occurance of these symbols surrounded by braces {} in the source template, 175 the corresponding value is converted to a string and substituted in the output. 176 177 For example, source text which looks like: 178 I lost my heart at {town} Fair. 179 becomes: 180 I lost my heart at Scarborough Fair. 181 182 Symbols in braces which do not correspond to a key in the dictionary remain 183 unchanged. 184 185 An optional third argument to the class is a list or two strings to be 186 used as the delimiters instead of { } braces. They must be of the same 187 length; for example ['##+', '##'] is invalid. 188 """ 189 def set_template(self, template): 190 if sys.version_info[0]<3: 191 f = open(template) 192 else: 193 f = open(template, encoding='latin-1') 194 self.source = f.read() 195 f.close() 196 197def compare_s2f(s, f2): 198 """Helper to compare a string to a file, return 0 if they are equal.""" 199 200 BUFSIZE = 8192 201 i = 0 202 if sys.version_info[0]<3: 203 fp2 = open(f2) 204 else: 205 fp2 = open(f2, encoding='latin-1') 206 try: 207 while 1: 208 try: 209 b1 = s[i: i + BUFSIZE] 210 i = i + BUFSIZE 211 except IndexError: 212 b1 = '' 213 b2 = fp2.read(BUFSIZE) 214 if not b1 and not b2: return 0 215 c = cmp(b1, b2) 216 if c: return c 217 finally: 218 fp2.close() 219