blob: a3e82a440a7093501358baabf450e874a5419d0d [file] [log] [blame]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +00001import string
2import sys
Guido van Rossum9787bdaf1996-05-28 23:50:49 +00003from types import StringType
Guido van Rossuma0eab1d1995-08-07 20:07:36 +00004
5
6AS_IS = None
7
Guido van Rossuma0eab1d1995-08-07 20:07:36 +00008
Guido van Rossum909507d1995-10-06 15:31:30 +00009class NullFormatter:
10
Guido van Rossumccd8b191996-10-07 21:29:49 +000011 def __init__(self, writer=None):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000012 if not writer:
13 writer = NullWriter()
14 self.writer = writer
Guido van Rossum909507d1995-10-06 15:31:30 +000015 def end_paragraph(self, blankline): pass
16 def add_line_break(self): pass
Fred Drakeca8cdc61996-10-08 21:57:47 +000017 def add_hor_rule(self, *args, **kw): pass
18 def add_label_data(self, format, counter, blankline=None): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000019 def add_flowing_data(self, data): pass
20 def add_literal_data(self, data): pass
21 def flush_softspace(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000022 def push_alignment(self, align): pass
23 def pop_alignment(self): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000024 def push_font(self, x): pass
25 def pop_font(self): pass
26 def push_margin(self, margin): pass
27 def pop_margin(self): pass
28 def set_spacing(self, spacing): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000029 def push_style(self, *styles): pass
30 def pop_style(self, n=1): pass
31 def assert_line_data(self, flag=1): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000032
33
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000034class AbstractFormatter:
35
Guido van Rossum8e449911996-08-26 16:19:23 +000036 # Space handling policy: blank spaces at the boundary between elements
37 # are handled by the outermost context. "Literal" data is not checked
38 # to determine context, so spaces in literal data are handled directly
39 # in all circumstances.
40
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000041 def __init__(self, writer):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000042 self.writer = writer # Output device
43 self.align = None # Current alignment
44 self.align_stack = [] # Alignment stack
45 self.font_stack = [] # Font state
46 self.margin_stack = [] # Margin state
47 self.spacing = None # Vertical spacing state
48 self.style_stack = [] # Other state, e.g. color
49 self.nospace = 1 # Should leading space be suppressed
50 self.softspace = 0 # Should a space be inserted
51 self.para_end = 1 # Just ended a paragraph
52 self.parskip = 0 # Skipped space between paragraphs?
53 self.hard_break = 1 # Have a hard break
54 self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000055
56 def end_paragraph(self, blankline):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000057 if not self.hard_break:
58 self.writer.send_line_break()
59 self.have_label = 0
60 if self.parskip < blankline and not self.have_label:
61 self.writer.send_paragraph(blankline - self.parskip)
62 self.parskip = blankline
63 self.have_label = 0
64 self.hard_break = self.nospace = self.para_end = 1
65 self.softspace = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000066
67 def add_line_break(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000068 if not (self.hard_break or self.para_end):
69 self.writer.send_line_break()
70 self.have_label = self.parskip = 0
71 self.hard_break = self.nospace = 1
72 self.softspace = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000073
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000074 def add_hor_rule(self, *args, **kw):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000075 if not self.hard_break:
76 self.writer.send_line_break()
77 apply(self.writer.send_hor_rule, args, kw)
78 self.hard_break = self.nospace = 1
79 self.have_label = self.para_end = self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000080
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000081 def add_label_data(self, format, counter, blankline = None):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000082 if self.have_label or not self.hard_break:
83 self.writer.send_line_break()
84 if not self.para_end:
85 self.writer.send_paragraph((blankline and 1) or 0)
86 if type(format) is StringType:
87 self.writer.send_label_data(self.format_counter(format, counter))
88 else:
89 self.writer.send_label_data(format)
90 self.nospace = self.have_label = self.hard_break = self.para_end = 1
91 self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000092
93 def format_counter(self, format, counter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000094 label = ''
95 for c in format:
96 try:
97 if c == '1':
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000098 label = label + ('%d' % counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000099 elif c in 'aA':
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000100 if counter > 0:
101 label = label + self.format_letter(c, counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000102 elif c in 'iI':
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000103 if counter > 0:
104 label = label + self.format_roman(c, counter)
105 else:
106 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000107 except:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000108 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000109 return label
110
111 def format_letter(self, case, counter):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000112 label = ''
113 while counter > 0:
114 counter, x = divmod(counter-1, 26)
115 s = chr(ord(case) + x)
116 label = s + label
117 return label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000118
119 def format_roman(self, case, counter):
120 ones = ['i', 'x', 'c', 'm']
121 fives = ['v', 'l', 'd']
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000122 label, index = '', 0
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000123 # This will die of IndexError when counter is too big
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000124 while counter > 0:
125 counter, x = divmod(counter, 10)
126 if x == 9:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000127 label = ones[index] + ones[index+1] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000128 elif x == 4:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000129 label = ones[index] + fives[index] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000130 else:
131 if x >= 5:
132 s = fives[index]
133 x = x-5
134 else:
135 s = ''
136 s = s + ones[index]*x
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000137 label = s + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000138 index = index + 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000139 if case == 'I':
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000140 return string.upper(label)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000141 return label
142
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000143 def add_flowing_data(self, data,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000144 # These are only here to load them into locals:
145 whitespace = string.whitespace,
146 join = string.join, split = string.split):
147 if not data: return
148 # The following looks a bit convoluted but is a great improvement over
149 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
150 prespace = data[:1] in whitespace
151 postspace = data[-1:] in whitespace
152 data = join(split(data))
153 if self.nospace and not data:
154 return
155 elif prespace or self.softspace:
156 if not data:
157 if not self.nospace:
158 self.softspace = 1
159 self.parskip = 0
160 return
161 if not self.nospace:
162 data = ' ' + data
163 self.hard_break = self.nospace = self.para_end = \
164 self.parskip = self.have_label = 0
165 self.softspace = postspace
166 self.writer.send_flowing_data(data)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000167
168 def add_literal_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000169 if not data: return
170 if self.softspace:
171 self.writer.send_flowing_data(" ")
172 self.hard_break = data[-1:] == '\n'
173 self.nospace = self.para_end = self.softspace = \
174 self.parskip = self.have_label = 0
175 self.writer.send_literal_data(data)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000176
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000177 def flush_softspace(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000178 if self.softspace:
179 self.hard_break = self.para_end = self.parskip = \
180 self.have_label = self.softspace = 0
181 self.nospace = 1
182 self.writer.send_flowing_data(' ')
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000183
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000184 def push_alignment(self, align):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000185 if align and align != self.align:
186 self.writer.new_alignment(align)
187 self.align = align
188 self.align_stack.append(align)
189 else:
190 self.align_stack.append(self.align)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000191
192 def pop_alignment(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000193 if self.align_stack:
194 del self.align_stack[-1]
195 if self.align_stack:
196 self.align = align = self.align_stack[-1]
197 self.writer.new_alignment(align)
198 else:
199 self.align = None
200 self.writer.new_alignment(None)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000201
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000202 def push_font(self, (size, i, b, tt)):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000203 if self.softspace:
204 self.hard_break = self.para_end = self.softspace = 0
205 self.nospace = 1
206 self.writer.send_flowing_data(' ')
207 if self.font_stack:
208 csize, ci, cb, ctt = self.font_stack[-1]
209 if size is AS_IS: size = csize
210 if i is AS_IS: i = ci
211 if b is AS_IS: b = cb
212 if tt is AS_IS: tt = ctt
213 font = (size, i, b, tt)
214 self.font_stack.append(font)
215 self.writer.new_font(font)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000216
217 def pop_font(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000218 if self.font_stack:
219 del self.font_stack[-1]
220 if self.font_stack:
221 font = self.font_stack[-1]
222 else:
223 font = None
224 self.writer.new_font(font)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000225
226 def push_margin(self, margin):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000227 self.margin_stack.append(margin)
228 fstack = filter(None, self.margin_stack)
229 if not margin and fstack:
230 margin = fstack[-1]
231 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000232
233 def pop_margin(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000234 if self.margin_stack:
235 del self.margin_stack[-1]
236 fstack = filter(None, self.margin_stack)
237 if fstack:
238 margin = fstack[-1]
239 else:
240 margin = None
241 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000242
243 def set_spacing(self, spacing):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000244 self.spacing = spacing
245 self.writer.new_spacing(spacing)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000246
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000247 def push_style(self, *styles):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000248 if self.softspace:
249 self.hard_break = self.para_end = self.softspace = 0
250 self.nospace = 1
251 self.writer.send_flowing_data(' ')
252 for style in styles:
253 self.style_stack.append(style)
254 self.writer.new_styles(tuple(self.style_stack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000255
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000256 def pop_style(self, n=1):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000257 del self.style_stack[-n:]
258 self.writer.new_styles(tuple(self.style_stack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000259
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000260 def assert_line_data(self, flag=1):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000261 self.nospace = self.hard_break = not flag
262 self.para_end = self.parskip = self.have_label = 0
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000263
264
265class NullWriter:
Fred Drake28231681996-12-31 20:50:51 +0000266 """Minimal writer interface to use in testing & inheritance."""
Guido van Rossum3672aa21996-05-29 00:02:30 +0000267 def __init__(self): pass
Fred Drake28231681996-12-31 20:50:51 +0000268 def flush(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000269 def new_alignment(self, align): pass
270 def new_font(self, font): pass
271 def new_margin(self, margin, level): pass
272 def new_spacing(self, spacing): pass
273 def new_styles(self, styles): pass
274 def send_paragraph(self, blankline): pass
275 def send_line_break(self): pass
276 def send_hor_rule(self, *args, **kw): pass
277 def send_label_data(self, data): pass
278 def send_flowing_data(self, data): pass
279 def send_literal_data(self, data): pass
280
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000281
Guido van Rossum3672aa21996-05-29 00:02:30 +0000282class AbstractWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000283
284 def __init__(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000285 pass
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000286
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000287 def new_alignment(self, align):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000288 print "new_alignment(%s)" % `align`
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000289
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000290 def new_font(self, font):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000291 print "new_font(%s)" % `font`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000292
293 def new_margin(self, margin, level):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000294 print "new_margin(%s, %d)" % (`margin`, level)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000295
296 def new_spacing(self, spacing):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000297 print "new_spacing(%s)" % `spacing`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000298
299 def new_styles(self, styles):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000300 print "new_styles(%s)" % `styles`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000301
302 def send_paragraph(self, blankline):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000303 print "send_paragraph(%s)" % `blankline`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000304
305 def send_line_break(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000306 print "send_line_break()"
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000307
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000308 def send_hor_rule(self, *args, **kw):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000309 print "send_hor_rule()"
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000310
311 def send_label_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000312 print "send_label_data(%s)" % `data`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000313
314 def send_flowing_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000315 print "send_flowing_data(%s)" % `data`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000316
317 def send_literal_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000318 print "send_literal_data(%s)" % `data`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000319
320
Guido van Rossum3672aa21996-05-29 00:02:30 +0000321class DumbWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000322
323 def __init__(self, file=None, maxcol=72):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000324 self.file = file or sys.stdout
325 self.maxcol = maxcol
326 NullWriter.__init__(self)
327 self.reset()
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000328
329 def reset(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000330 self.col = 0
331 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000332
333 def send_paragraph(self, blankline):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000334 self.file.write('\n' + '\n'*blankline)
335 self.col = 0
336 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000337
338 def send_line_break(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000339 self.file.write('\n')
340 self.col = 0
341 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000342
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000343 def send_hor_rule(self, *args, **kw):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000344 self.file.write('\n')
345 self.file.write('-'*self.maxcol)
346 self.file.write('\n')
347 self.col = 0
348 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000349
350 def send_literal_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000351 self.file.write(data)
352 i = string.rfind(data, '\n')
353 if i >= 0:
354 self.col = 0
355 data = data[i+1:]
356 data = string.expandtabs(data)
357 self.col = self.col + len(data)
358 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000359
360 def send_flowing_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000361 if not data: return
362 atbreak = self.atbreak or data[0] in string.whitespace
363 col = self.col
364 maxcol = self.maxcol
365 write = self.file.write
366 for word in string.split(data):
367 if atbreak:
368 if col + len(word) >= maxcol:
369 write('\n')
370 col = 0
371 else:
372 write(' ')
373 col = col + 1
374 write(word)
375 col = col + len(word)
376 atbreak = 1
377 self.col = col
378 self.atbreak = data[-1] in string.whitespace
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000379
380
381def test(file = None):
382 w = DumbWriter()
383 f = AbstractFormatter(w)
384 if file:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000385 fp = open(file)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000386 elif sys.argv[1:]:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000387 fp = open(sys.argv[1])
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000388 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000389 fp = sys.stdin
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000390 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000391 line = fp.readline()
392 if not line:
393 break
394 if line == '\n':
395 f.end_paragraph(1)
396 else:
397 f.add_flowing_data(line)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000398 f.end_paragraph(0)
399
400
401if __name__ == '__main__':
402 test()