Use the same rounding for width as for height in platform sections -- covers ticket...
[pyTivo.git] / Cheetah / Filters.py
blob2bf4784fd5670e43fd71bf0ea6ac42e402e8a5b6
1 #!/usr/bin/env python
2 # $Id: Filters.py,v 1.28 2006/06/16 20:15:24 hierro Exp $
3 """Filters for the #filter directive; output filters Cheetah's $placeholders .
5 Filters may now be used standalone, for debugging or for use outside Cheetah.
6 Class DummyTemplate, instance _dummyTemplateObj and class NoDefault exist only
7 for this, to provide a default argument for the filter constructors (which
8 would otherwise require a real template object).
10 The default filter is now RawOrEncodedUnicode. Please use this as a base class instead of Filter because it handles non-ASCII characters better.
12 Meta-Data
13 ================================================================================
14 Author: Tavis Rudd <tavis@damnsimple.com>
15 Version: $Revision: 1.28 $
16 Start Date: 2001/08/01
17 Last Revision Date: $Date: 2006/06/16 20:15:24 $
18 """
19 __author__ = "Tavis Rudd <tavis@damnsimple.com>"
20 __revision__ = "$Revision: 1.28 $"[11:-2]
22 from StringIO import StringIO # not cStringIO because of unicode support
24 # Additional entities WebSafe knows how to transform. No need to include
25 # '<', '>' or '&' since those will have been done already.
26 webSafeEntities = {' ': '&nbsp;', '"': '&quot;'}
28 class Error(Exception):
29 pass
31 class NoDefault:
32 pass
35 class DummyTemplate:
36 """Fake template class to allow filters to be used standalone.
38 This is provides only the level of Template compatibility required by the
39 standard filters. Namely, the get-settings interface works but there are
40 no settings. Other aspects of Template are not implemented.
41 """
42 def setting(self, name, default=NoDefault):
43 if default is NoDefault:
44 raise KeyError(name)
45 else:
46 return default
48 def settings(self):
49 return {}
51 _dummyTemplateObj = DummyTemplate()
54 ##################################################
55 ## BASE CLASS
57 class Filter(object):
58 """A baseclass for the Cheetah Filters."""
60 def __init__(self, templateObj=_dummyTemplateObj):
61 """Setup a ref to the templateObj. Subclasses should call this method.
62 """
63 if hasattr(templateObj, 'setting'):
64 self.setting = templateObj.setting
65 else:
66 self.setting = lambda k: None
68 if hasattr(templateObj, 'settings'):
69 self.settings = templateObj.settings
70 else:
71 self.settings = lambda: {}
73 def generateAutoArgs(self):
75 """This hook allows the filters to generate an arg-list that will be
76 appended to the arg-list of a $placeholder tag when it is being
77 translated into Python code during the template compilation process. See
78 the 'Pager' filter class for an example."""
80 return ''
82 def filter(self, val, **kw):
84 """Reimplement this method if you want more advanced filterting."""
86 return str(val)
89 ##################################################
90 ## ENHANCED FILTERS
92 #####
93 class ReplaceNone(Filter):
94 def filter(self, val, **kw):
96 """Replace None with an empty string. Reimplement this method if you
97 want more advanced filterting."""
99 if val is None:
100 return ''
101 return str(val)
102 #####
103 class EncodeUnicode(Filter):
104 def filter(self, val,
105 encoding='utf8',
106 str=str, type=type, unicodeType=type(u''),
107 **kw):
108 """Encode Unicode strings, by default in UTF-8.
110 >>> import Cheetah.Template
111 >>> t = Cheetah.Template.Template('''
112 ... $myvar
113 ... ${myvar, encoding='utf16'}
114 ... ''', searchList=[{'myvar': u'Asni\xe8res'}],
115 ... filter='EncodeUnicode')
116 >>> print t
118 if type(val)==unicodeType:
119 filtered = val.encode(encoding)
120 elif val is None:
121 filtered = ''
122 else:
123 filtered = str(val)
124 return filtered
126 class RawOrEncodedUnicode(Filter):
127 def filter(self, val,
128 #encoding='utf8',
129 encoding=None,
130 str=str, type=type, unicodeType=type(u''),
131 **kw):
132 """Pass Unicode strings through unmolested, unless an encoding is specified.
134 if type(val)==unicodeType:
135 if encoding:
136 filtered = val.encode(encoding)
137 else:
138 filtered = val
139 elif val is None:
140 filtered = ''
141 else:
142 filtered = str(val)
143 return filtered
145 #####
146 class MaxLen(RawOrEncodedUnicode):
147 def filter(self, val, **kw):
148 """Replace None with '' and cut off at maxlen."""
150 output = super(MaxLen, self).filter(val, **kw)
151 if kw.has_key('maxlen') and len(output) > kw['maxlen']:
152 return output[:kw['maxlen']]
153 return output
156 #####
157 class Pager(RawOrEncodedUnicode):
158 def __init__(self, templateObj=_dummyTemplateObj):
159 Filter.__init__(self, templateObj)
160 self._IDcounter = 0
162 def buildQString(self,varsDict, updateDict):
163 finalDict = varsDict.copy()
164 finalDict.update(updateDict)
165 qString = '?'
166 for key, val in finalDict.items():
167 qString += str(key) + '=' + str(val) + '&'
168 return qString
170 def generateAutoArgs(self):
171 ID = str(self._IDcounter)
172 self._IDcounter += 1
173 return ', trans=trans, ID=' + ID
175 def filter(self, val, **kw):
176 """Replace None with '' and cut off at maxlen."""
177 output = super(Pager, self).filter(val, **kw)
178 if kw.has_key('trans') and kw['trans']:
179 ID = kw['ID']
180 marker = kw.get('marker', '<split>')
181 req = kw['trans'].request()
182 URI = req.environ()['SCRIPT_NAME'] + req.environ()['PATH_INFO']
183 queryVar = 'pager' + str(ID) + '_page'
184 fields = req.fields()
185 page = int(fields.get( queryVar, 1))
186 pages = output.split(marker)
187 output = pages[page-1]
188 output += '<BR>'
189 if page > 1:
190 output +='<A HREF="' + URI + self.buildQString(fields, {queryVar:max(page-1,1)}) + \
191 '">Previous Page</A>&nbsp;&nbsp;&nbsp;'
192 if page < len(pages):
193 output += '<A HREF="' + URI + self.buildQString(
194 fields,
195 {queryVar:
196 min(page+1,len(pages))}) + \
197 '">Next Page</A>'
199 return output
200 return output
203 #####
204 class WebSafe(RawOrEncodedUnicode):
205 """Escape HTML entities in $placeholders.
207 def filter(self, val, **kw):
208 s = super(WebSafe, self).filter(val, **kw)
209 # These substitutions are copied from cgi.escape().
210 s = s.replace("&", "&amp;") # Must be done first!
211 s = s.replace("<", "&lt;")
212 s = s.replace(">", "&gt;")
213 # Process the additional transformations if any.
214 if kw.has_key('also'):
215 also = kw['also']
216 entities = webSafeEntities # Global variable.
217 for k in also:
218 if entities.has_key(k):
219 v = entities[k]
220 else:
221 v = "&#%s;" % ord(k)
222 s = s.replace(k, v)
223 # Return the puppy.
224 return s
227 #####
228 class Strip(RawOrEncodedUnicode):
229 """Strip leading/trailing whitespace but preserve newlines.
231 This filter goes through the value line by line, removing leading and
232 trailing whitespace on each line. It does not strip newlines, so every
233 input line corresponds to one output line, with its trailing newline intact.
235 We do not use val.split('\n') because that would squeeze out consecutive
236 blank lines. Instead, we search for each newline individually. This
237 makes us unable to use the fast C .split method, but it makes the filter
238 much more widely useful.
240 This filter is intended to be usable both with the #filter directive and
241 with the proposed #sed directive (which has not been ratified yet.)
243 def filter(self, val, **kw):
244 s = super(Strip, self).filter(val, **kw)
245 result = []
246 start = 0 # The current line will be s[start:end].
247 while 1: # Loop through each line.
248 end = s.find('\n', start) # Find next newline.
249 if end == -1: # If no more newlines.
250 break
251 chunk = s[start:end].strip()
252 result.append(chunk)
253 result.append('\n')
254 start = end + 1
255 # Write the unfinished portion after the last newline, if any.
256 chunk = s[start:].strip()
257 result.append(chunk)
258 return "".join(result)
260 #####
261 class StripSqueeze(RawOrEncodedUnicode):
262 """Canonicalizes every chunk of whitespace to a single space.
264 Strips leading/trailing whitespace. Removes all newlines, so multi-line
265 input is joined into one ling line with NO trailing newline.
267 def filter(self, val, **kw):
268 s = super(StripSqueeze, self).filter(val, **kw)
269 s = s.split()
270 return " ".join(s)
272 ##################################################
273 ## MAIN ROUTINE -- testing
275 def test():
276 s1 = "abc <=> &"
277 s2 = " asdf \n\t 1 2 3\n"
278 print "WebSafe INPUT:", `s1`
279 print " WebSafe:", `WebSafe().filter(s1)`
281 print
282 print " Strip INPUT:", `s2`
283 print " Strip:", `Strip().filter(s2)`
284 print "StripSqueeze:", `StripSqueeze().filter(s2)`
286 print "Unicode:", `EncodeUnicode().filter(u'aoeu12345\u1234')`
288 if __name__ == "__main__": test()
290 # vim: shiftwidth=4 tabstop=4 expandtab