0001 """Generic output formatting. 0002 0003 Formatter objects transform an abstract flow of formatting events into 0004 specific output events on writer objects. Formatters manage several stack 0005 structures to allow various properties of a writer object to be changed and 0006 restored; writers need not be able to handle relative changes nor any sort 0007 of ``change back'' operation. Specific writer properties which may be 0008 controlled via formatter objects are horizontal alignment, font, and left 0009 margin indentations. A mechanism is provided which supports providing 0010 arbitrary, non-exclusive style settings to a writer as well. Additional 0011 interfaces facilitate formatting events which are not reversible, such as 0012 paragraph separation. 0013 0014 Writer objects encapsulate device interfaces. Abstract devices, such as 0015 file formats, are supported as well as physical devices. The provided 0016 implementations all work with abstract devices. The interface makes 0017 available mechanisms for setting the properties which formatter objects 0018 manage and inserting data into the output. 0019 """ 0020 0021 import sys 0022 0023 0024 AS_IS = None 0025 0026 0027 class NullFormatter: 0028 """A formatter which does nothing. 0029 0030 If the writer parameter is omitted, a NullWriter instance is created. 0031 No methods of the writer are called by NullFormatter instances. 0032 0033 Implementations should inherit from this class if implementing a writer 0034 interface but don't need to inherit any implementation. 0035 0036 """ 0037 0038 def __init__(self, writer=None): 0039 if writer is None: 0040 writer = NullWriter() 0041 self.writer = writer 0042 def end_paragraph(self, blankline): pass 0043 def add_line_break(self): pass 0044 def add_hor_rule(self, *args, **kw): pass 0045 def add_label_data(self, format, counter, blankline=None): pass 0046 def add_flowing_data(self, data): pass 0047 def add_literal_data(self, data): pass 0048 def flush_softspace(self): pass 0049 def push_alignment(self, align): pass 0050 def pop_alignment(self): pass 0051 def push_font(self, x): pass 0052 def pop_font(self): pass 0053 def push_margin(self, margin): pass 0054 def pop_margin(self): pass 0055 def set_spacing(self, spacing): pass 0056 def push_style(self, *styles): pass 0057 def pop_style(self, n=1): pass 0058 def assert_line_data(self, flag=1): pass 0059 0060 0061 class AbstractFormatter: 0062 """The standard formatter. 0063 0064 This implementation has demonstrated wide applicability to many writers, 0065 and may be used directly in most circumstances. It has been used to 0066 implement a full-featured World Wide Web browser. 0067 0068 """ 0069 0070 # Space handling policy: blank spaces at the boundary between elements 0071 # are handled by the outermost context. "Literal" data is not checked 0072 # to determine context, so spaces in literal data are handled directly 0073 # in all circumstances. 0074 0075 def __init__(self, writer): 0076 self.writer = writer # Output device 0077 self.align = None # Current alignment 0078 self.align_stack = [] # Alignment stack 0079 self.font_stack = [] # Font state 0080 self.margin_stack = [] # Margin state 0081 self.spacing = None # Vertical spacing state 0082 self.style_stack = [] # Other state, e.g. color 0083 self.nospace = 1 # Should leading space be suppressed 0084 self.softspace = 0 # Should a space be inserted 0085 self.para_end = 1 # Just ended a paragraph 0086 self.parskip = 0 # Skipped space between paragraphs? 0087 self.hard_break = 1 # Have a hard break 0088 self.have_label = 0 0089 0090 def end_paragraph(self, blankline): 0091 if not self.hard_break: 0092 self.writer.send_line_break() 0093 self.have_label = 0 0094 if self.parskip < blankline and not self.have_label: 0095 self.writer.send_paragraph(blankline - self.parskip) 0096 self.parskip = blankline 0097 self.have_label = 0 0098 self.hard_break = self.nospace = self.para_end = 1 0099 self.softspace = 0 0100 0101 def add_line_break(self): 0102 if not (self.hard_break or self.para_end): 0103 self.writer.send_line_break() 0104 self.have_label = self.parskip = 0 0105 self.hard_break = self.nospace = 1 0106 self.softspace = 0 0107 0108 def add_hor_rule(self, *args, **kw): 0109 if not self.hard_break: 0110 self.writer.send_line_break() 0111 self.writer.send_hor_rule(*args, **kw) 0112 self.hard_break = self.nospace = 1 0113 self.have_label = self.para_end = self.softspace = self.parskip = 0 0114 0115 def add_label_data(self, format, counter, blankline = None): 0116 if self.have_label or not self.hard_break: 0117 self.writer.send_line_break() 0118 if not self.para_end: 0119 self.writer.send_paragraph((blankline and 1) or 0) 0120 if isinstance(format, str): 0121 self.writer.send_label_data(self.format_counter(format, counter)) 0122 else: 0123 self.writer.send_label_data(format) 0124 self.nospace = self.have_label = self.hard_break = self.para_end = 1 0125 self.softspace = self.parskip = 0 0126 0127 def format_counter(self, format, counter): 0128 label = '' 0129 for c in format: 0130 if c == '1': 0131 label = label + ('%d' % counter) 0132 elif c in 'aA': 0133 if counter > 0: 0134 label = label + self.format_letter(c, counter) 0135 elif c in 'iI': 0136 if counter > 0: 0137 label = label + self.format_roman(c, counter) 0138 else: 0139 label = label + c 0140 return label 0141 0142 def format_letter(self, case, counter): 0143 label = '' 0144 while counter > 0: 0145 counter, x = divmod(counter-1, 26) 0146 # This makes a strong assumption that lowercase letters 0147 # and uppercase letters form two contiguous blocks, with 0148 # letters in order! 0149 s = chr(ord(case) + x) 0150 label = s + label 0151 return label 0152 0153 def format_roman(self, case, counter): 0154 ones = ['i', 'x', 'c', 'm'] 0155 fives = ['v', 'l', 'd'] 0156 label, index = '', 0 0157 # This will die of IndexError when counter is too big 0158 while counter > 0: 0159 counter, x = divmod(counter, 10) 0160 if x == 9: 0161 label = ones[index] + ones[index+1] + label 0162 elif x == 4: 0163 label = ones[index] + fives[index] + label 0164 else: 0165 if x >= 5: 0166 s = fives[index] 0167 x = x-5 0168 else: 0169 s = '' 0170 s = s + ones[index]*x 0171 label = s + label 0172 index = index + 1 0173 if case == 'I': 0174 return label.upper() 0175 return label 0176 0177 def add_flowing_data(self, data): 0178 if not data: return 0179 # The following looks a bit convoluted but is a great improvement over 0180 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data) 0181 prespace = data[:1].isspace() 0182 postspace = data[-1:].isspace() 0183 data = " ".join(data.split()) 0184 if self.nospace and not data: 0185 return 0186 elif prespace or self.softspace: 0187 if not data: 0188 if not self.nospace: 0189 self.softspace = 1 0190 self.parskip = 0 0191 return 0192 if not self.nospace: 0193 data = ' ' + data 0194 self.hard_break = self.nospace = self.para_end = \ 0195 self.parskip = self.have_label = 0 0196 self.softspace = postspace 0197 self.writer.send_flowing_data(data) 0198 0199 def add_literal_data(self, data): 0200 if not data: return 0201 if self.softspace: 0202 self.writer.send_flowing_data(" ") 0203 self.hard_break = data[-1:] == '\n' 0204 self.nospace = self.para_end = self.softspace = \ 0205 self.parskip = self.have_label = 0 0206 self.writer.send_literal_data(data) 0207 0208 def flush_softspace(self): 0209 if self.softspace: 0210 self.hard_break = self.para_end = self.parskip = \ 0211 self.have_label = self.softspace = 0 0212 self.nospace = 1 0213 self.writer.send_flowing_data(' ') 0214 0215 def push_alignment(self, align): 0216 if align and align != self.align: 0217 self.writer.new_alignment(align) 0218 self.align = align 0219 self.align_stack.append(align) 0220 else: 0221 self.align_stack.append(self.align) 0222 0223 def pop_alignment(self): 0224 if self.align_stack: 0225 del self.align_stack[-1] 0226 if self.align_stack: 0227 self.align = align = self.align_stack[-1] 0228 self.writer.new_alignment(align) 0229 else: 0230 self.align = None 0231 self.writer.new_alignment(None) 0232 0233 def push_font(self, (size, i, b, tt)): 0234 if self.softspace: 0235 self.hard_break = self.para_end = self.softspace = 0 0236 self.nospace = 1 0237 self.writer.send_flowing_data(' ') 0238 if self.font_stack: 0239 csize, ci, cb, ctt = self.font_stack[-1] 0240 if size is AS_IS: size = csize 0241 if i is AS_IS: i = ci 0242 if b is AS_IS: b = cb 0243 if tt is AS_IS: tt = ctt 0244 font = (size, i, b, tt) 0245 self.font_stack.append(font) 0246 self.writer.new_font(font) 0247 0248 def pop_font(self): 0249 if self.font_stack: 0250 del self.font_stack[-1] 0251 if self.font_stack: 0252 font = self.font_stack[-1] 0253 else: 0254 font = None 0255 self.writer.new_font(font) 0256 0257 def push_margin(self, margin): 0258 self.margin_stack.append(margin) 0259 fstack = filter(None, self.margin_stack) 0260 if not margin and fstack: 0261 margin = fstack[-1] 0262 self.writer.new_margin(margin, len(fstack)) 0263 0264 def pop_margin(self): 0265 if self.margin_stack: 0266 del self.margin_stack[-1] 0267 fstack = filter(None, self.margin_stack) 0268 if fstack: 0269 margin = fstack[-1] 0270 else: 0271 margin = None 0272 self.writer.new_margin(margin, len(fstack)) 0273 0274 def set_spacing(self, spacing): 0275 self.spacing = spacing 0276 self.writer.new_spacing(spacing) 0277 0278 def push_style(self, *styles): 0279 if self.softspace: 0280 self.hard_break = self.para_end = self.softspace = 0 0281 self.nospace = 1 0282 self.writer.send_flowing_data(' ') 0283 for style in styles: 0284 self.style_stack.append(style) 0285 self.writer.new_styles(tuple(self.style_stack)) 0286 0287 def pop_style(self, n=1): 0288 del self.style_stack[-n:] 0289 self.writer.new_styles(tuple(self.style_stack)) 0290 0291 def assert_line_data(self, flag=1): 0292 self.nospace = self.hard_break = not flag 0293 self.para_end = self.parskip = self.have_label = 0 0294 0295 0296 class NullWriter: 0297 """Minimal writer interface to use in testing & inheritance. 0298 0299 A writer which only provides the interface definition; no actions are 0300 taken on any methods. This should be the base class for all writers 0301 which do not need to inherit any implementation methods. 0302 0303 """ 0304 def __init__(self): pass 0305 def flush(self): pass 0306 def new_alignment(self, align): pass 0307 def new_font(self, font): pass 0308 def new_margin(self, margin, level): pass 0309 def new_spacing(self, spacing): pass 0310 def new_styles(self, styles): pass 0311 def send_paragraph(self, blankline): pass 0312 def send_line_break(self): pass 0313 def send_hor_rule(self, *args, **kw): pass 0314 def send_label_data(self, data): pass 0315 def send_flowing_data(self, data): pass 0316 def send_literal_data(self, data): pass 0317 0318 0319 class AbstractWriter(NullWriter): 0320 """A writer which can be used in debugging formatters, but not much else. 0321 0322 Each method simply announces itself by printing its name and 0323 arguments on standard output. 0324 0325 """ 0326 0327 def new_alignment(self, align): 0328 print "new_alignment(%r)" % (align,) 0329 0330 def new_font(self, font): 0331 print "new_font(%r)" % (font,) 0332 0333 def new_margin(self, margin, level): 0334 print "new_margin(%r, %d)" % (margin, level) 0335 0336 def new_spacing(self, spacing): 0337 print "new_spacing(%r)" % (spacing,) 0338 0339 def new_styles(self, styles): 0340 print "new_styles(%r)" % (styles,) 0341 0342 def send_paragraph(self, blankline): 0343 print "send_paragraph(%r)" % (blankline,) 0344 0345 def send_line_break(self): 0346 print "send_line_break()" 0347 0348 def send_hor_rule(self, *args, **kw): 0349 print "send_hor_rule()" 0350 0351 def send_label_data(self, data): 0352 print "send_label_data(%r)" % (data,) 0353 0354 def send_flowing_data(self, data): 0355 print "send_flowing_data(%r)" % (data,) 0356 0357 def send_literal_data(self, data): 0358 print "send_literal_data(%r)" % (data,) 0359 0360 0361 class DumbWriter(NullWriter): 0362 """Simple writer class which writes output on the file object passed in 0363 as the file parameter or, if file is omitted, on standard output. The 0364 output is simply word-wrapped to the number of columns specified by 0365 the maxcol parameter. This class is suitable for reflowing a sequence 0366 of paragraphs. 0367 0368 """ 0369 0370 def __init__(self, file=None, maxcol=72): 0371 self.file = file or sys.stdout 0372 self.maxcol = maxcol 0373 NullWriter.__init__(self) 0374 self.reset() 0375 0376 def reset(self): 0377 self.col = 0 0378 self.atbreak = 0 0379 0380 def send_paragraph(self, blankline): 0381 self.file.write('\n'*blankline) 0382 self.col = 0 0383 self.atbreak = 0 0384 0385 def send_line_break(self): 0386 self.file.write('\n') 0387 self.col = 0 0388 self.atbreak = 0 0389 0390 def send_hor_rule(self, *args, **kw): 0391 self.file.write('\n') 0392 self.file.write('-'*self.maxcol) 0393 self.file.write('\n') 0394 self.col = 0 0395 self.atbreak = 0 0396 0397 def send_literal_data(self, data): 0398 self.file.write(data) 0399 i = data.rfind('\n') 0400 if i >= 0: 0401 self.col = 0 0402 data = data[i+1:] 0403 data = data.expandtabs() 0404 self.col = self.col + len(data) 0405 self.atbreak = 0 0406 0407 def send_flowing_data(self, data): 0408 if not data: return 0409 atbreak = self.atbreak or data[0].isspace() 0410 col = self.col 0411 maxcol = self.maxcol 0412 write = self.file.write 0413 for word in data.split(): 0414 if atbreak: 0415 if col + len(word) >= maxcol: 0416 write('\n') 0417 col = 0 0418 else: 0419 write(' ') 0420 col = col + 1 0421 write(word) 0422 col = col + len(word) 0423 atbreak = 1 0424 self.col = col 0425 self.atbreak = data[-1].isspace() 0426 0427 0428 def test(file = None): 0429 w = DumbWriter() 0430 f = AbstractFormatter(w) 0431 if file is not None: 0432 fp = open(file) 0433 elif sys.argv[1:]: 0434 fp = open(sys.argv[1]) 0435 else: 0436 fp = sys.stdin 0437 while 1: 0438 line = fp.readline() 0439 if not line: 0440 break 0441 if line == '\n': 0442 f.end_paragraph(1) 0443 else: 0444 f.add_flowing_data(line) 0445 f.end_paragraph(0) 0446 0447 0448 if __name__ == '__main__': 0449 test() 0450
Generated by PyXR 0.9.4