blob: 25dbe73d78cb418625f47652be015ad8cbade9c2 [file] [log] [blame]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +00001import regex
2import regsub
3import string
4import sys
Guido van Rossum9787bdaf1996-05-28 23:50:49 +00005from types import StringType
Guido van Rossuma0eab1d1995-08-07 20:07:36 +00006
7
8AS_IS = None
9
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000010
Guido van Rossum909507d1995-10-06 15:31:30 +000011class NullFormatter:
12
Guido van Rossumccd8b191996-10-07 21:29:49 +000013 def __init__(self, writer=None):
14 if not writer:
15 writer = NullWriter()
16 self.writer = writer
Guido van Rossum909507d1995-10-06 15:31:30 +000017 def end_paragraph(self, blankline): pass
18 def add_line_break(self): pass
Fred Drakeca8cdc61996-10-08 21:57:47 +000019 def add_hor_rule(self, *args, **kw): pass
20 def add_label_data(self, format, counter, blankline=None): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000021 def add_flowing_data(self, data): pass
22 def add_literal_data(self, data): pass
23 def flush_softspace(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000024 def push_alignment(self, align): pass
25 def pop_alignment(self): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000026 def push_font(self, x): pass
27 def pop_font(self): pass
28 def push_margin(self, margin): pass
29 def pop_margin(self): pass
30 def set_spacing(self, spacing): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000031 def push_style(self, *styles): pass
32 def pop_style(self, n=1): pass
33 def assert_line_data(self, flag=1): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000034
35
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000036class AbstractFormatter:
37
Guido van Rossum8e449911996-08-26 16:19:23 +000038 # Space handling policy: blank spaces at the boundary between elements
39 # are handled by the outermost context. "Literal" data is not checked
40 # to determine context, so spaces in literal data are handled directly
41 # in all circumstances.
42
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000043 def __init__(self, writer):
44 self.writer = writer # Output device
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000045 self.align = None # Current alignment
46 self.align_stack = [] # Alignment stack
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000047 self.font_stack = [] # Font state
48 self.margin_stack = [] # Margin state
49 self.spacing = None # Vertical spacing state
50 self.style_stack = [] # Other state, e.g. color
Guido van Rossum909507d1995-10-06 15:31:30 +000051 self.nospace = 1 # Should leading space be suppressed
52 self.softspace = 0 # Should a space be inserted
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000053 self.para_end = 1 # Just ended a paragraph
54 self.parskip = 0 # Skipped space between paragraphs?
55 self.hard_break = 1 # Have a hard break
56 self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000057
58 def end_paragraph(self, blankline):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000059 if not self.hard_break:
60 self.writer.send_line_break()
61 self.have_label = 0
62 if self.parskip < blankline and not self.have_label:
63 self.writer.send_paragraph(blankline - self.parskip)
64 self.parskip = blankline
65 self.have_label = 0
66 self.hard_break = self.nospace = self.para_end = 1
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000067 self.softspace = 0
68
69 def add_line_break(self):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000070 if not (self.hard_break or self.para_end):
71 self.writer.send_line_break()
72 self.have_label = self.parskip = 0
73 self.hard_break = self.nospace = 1
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000074 self.softspace = 0
75
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000076 def add_hor_rule(self, *args, **kw):
77 if not self.hard_break:
78 self.writer.send_line_break()
79 apply(self.writer.send_hor_rule, args, kw)
80 self.hard_break = self.nospace = 1
81 self.have_label = self.para_end = self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000082
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000083 def add_label_data(self, format, counter, blankline = None):
84 if self.have_label or not self.hard_break:
85 self.writer.send_line_break()
86 if not self.para_end:
87 self.writer.send_paragraph((blankline and 1) or 0)
88 if type(format) is StringType:
89 self.writer.send_label_data(self.format_counter(format, counter))
90 else:
91 self.writer.send_label_data(format)
92 self.nospace = self.have_label = self.hard_break = self.para_end = 1
93 self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000094
95 def format_counter(self, format, counter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000096 label = ''
97 for c in format:
98 try:
99 if c == '1':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000100 label = label + ('%d' % counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000101 elif c in 'aA':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000102 if counter > 0:
103 label = label + self.format_letter(c, counter)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000104 elif c in 'iI':
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000105 if counter > 0:
106 label = label + self.format_roman(c, counter)
107 else:
108 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000109 except:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000110 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000111 return label
112
113 def format_letter(self, case, counter):
114 label = ''
115 while counter > 0:
116 counter, x = divmod(counter-1, 26)
117 s = chr(ord(case) + x)
118 label = s + label
119 return label
120
121 def format_roman(self, case, counter):
122 ones = ['i', 'x', 'c', 'm']
123 fives = ['v', 'l', 'd']
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000124 label, index = '', 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000125 # This will die of IndexError when counter is too big
126 while counter > 0:
127 counter, x = divmod(counter, 10)
128 if x == 9:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000129 label = ones[index] + ones[index+1] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000130 elif x == 4:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000131 label = ones[index] + fives[index] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000132 else:
133 if x >= 5:
134 s = fives[index]
135 x = x-5
136 else:
137 s = ''
138 s = s + ones[index]*x
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000139 label = s + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000140 index = index + 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000141 if case == 'I':
142 return string.upper(label)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000143 return label
144
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000145 def add_flowing_data(self, data,
146 # These are only here to load them into locals:
147 whitespace = string.whitespace,
148 join = string.join, split = string.split):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000149 if not data: return
Guido van Rossum93dc8011996-02-12 23:59:54 +0000150 # The following looks a bit convoluted but is a great improvement over
151 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000152 prespace = data[:1] in whitespace
153 postspace = data[-1:] in whitespace
154 data = join(split(data))
155 if self.nospace and not data:
156 return
157 elif prespace or self.softspace:
158 if not data:
159 if not self.nospace:
160 self.softspace = 1
161 self.parskip = 0
162 return
163 if not self.nospace:
164 data = ' ' + data
165 self.hard_break = self.nospace = self.para_end = \
166 self.parskip = self.have_label = 0
167 self.softspace = postspace
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000168 self.writer.send_flowing_data(data)
169
170 def add_literal_data(self, data):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000171 if not data: return
Guido van Rossum8e449911996-08-26 16:19:23 +0000172 if self.softspace:
173 self.writer.send_flowing_data(" ")
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000174 self.hard_break = data[-1:] == '\n'
175 self.nospace = self.para_end = self.softspace = \
176 self.parskip = self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000177 self.writer.send_literal_data(data)
178
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000179 def flush_softspace(self):
180 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000181 self.hard_break = self.para_end = self.parskip = \
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000182 self.have_label = self.softspace = 0
Guido van Rossum8e449911996-08-26 16:19:23 +0000183 self.nospace = 1
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000184 self.writer.send_flowing_data(' ')
185
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000186 def push_alignment(self, align):
187 if align and align != self.align:
188 self.writer.new_alignment(align)
189 self.align = align
190 self.align_stack.append(align)
191 else:
192 self.align_stack.append(self.align)
193
194 def pop_alignment(self):
195 if self.align_stack:
196 del self.align_stack[-1]
197 if self.align_stack:
198 self.align = align = self.align_stack[-1]
199 self.writer.new_alignment(align)
200 else:
201 self.align = None
202 self.writer.new_alignment(None)
203
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000204 def push_font(self, (size, i, b, tt)):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000205 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000206 self.hard_break = self.para_end = self.softspace = 0
207 self.nospace = 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000208 self.writer.send_flowing_data(' ')
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000209 if self.font_stack:
210 csize, ci, cb, ctt = self.font_stack[-1]
211 if size is AS_IS: size = csize
212 if i is AS_IS: i = ci
213 if b is AS_IS: b = cb
214 if tt is AS_IS: tt = ctt
215 font = (size, i, b, tt)
216 self.font_stack.append(font)
217 self.writer.new_font(font)
218
219 def pop_font(self):
220 if self.font_stack:
221 del self.font_stack[-1]
222 if self.font_stack:
223 font = self.font_stack[-1]
224 else:
225 font = None
226 self.writer.new_font(font)
227
228 def push_margin(self, margin):
229 self.margin_stack.append(margin)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000230 fstack = filter(None, self.margin_stack)
231 if not margin and fstack:
232 margin = fstack[-1]
233 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000234
235 def pop_margin(self):
236 if self.margin_stack:
237 del self.margin_stack[-1]
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000238 fstack = filter(None, self.margin_stack)
239 if fstack:
240 margin = fstack[-1]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000241 else:
242 margin = None
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000243 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000244
245 def set_spacing(self, spacing):
246 self.spacing = spacing
247 self.writer.new_spacing(spacing)
248
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000249 def push_style(self, *styles):
250 if self.softspace:
Guido van Rossum8e449911996-08-26 16:19:23 +0000251 self.hard_break = self.para_end = self.softspace = 0
252 self.nospace = 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000253 self.writer.send_flowing_data(' ')
254 for style in styles:
255 self.style_stack.append(style)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000256 self.writer.new_styles(tuple(self.style_stack))
257
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000258 def pop_style(self, n=1):
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000259 del self.style_stack[-n:]
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000260 self.writer.new_styles(tuple(self.style_stack))
261
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000262 def assert_line_data(self, flag=1):
263 self.nospace = self.hard_break = not flag
Guido van Rossum8e449911996-08-26 16:19:23 +0000264 self.para_end = self.parskip = self.have_label = 0
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000265
266
267class NullWriter:
Fred Drake28231681996-12-31 20:50:51 +0000268 """Minimal writer interface to use in testing & inheritance."""
Guido van Rossum3672aa21996-05-29 00:02:30 +0000269 def __init__(self): pass
Fred Drake28231681996-12-31 20:50:51 +0000270 def flush(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000271 def new_alignment(self, align): pass
272 def new_font(self, font): pass
273 def new_margin(self, margin, level): pass
274 def new_spacing(self, spacing): pass
275 def new_styles(self, styles): pass
276 def send_paragraph(self, blankline): pass
277 def send_line_break(self): pass
278 def send_hor_rule(self, *args, **kw): pass
279 def send_label_data(self, data): pass
280 def send_flowing_data(self, data): pass
281 def send_literal_data(self, data): pass
282
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000283
Guido van Rossum3672aa21996-05-29 00:02:30 +0000284class AbstractWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000285
286 def __init__(self):
287 pass
288
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000289 def new_alignment(self, align):
290 print "new_alignment(%s)" % `align`
291
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000292 def new_font(self, font):
293 print "new_font(%s)" % `font`
294
295 def new_margin(self, margin, level):
296 print "new_margin(%s, %d)" % (`margin`, level)
297
298 def new_spacing(self, spacing):
299 print "new_spacing(%s)" % `spacing`
300
301 def new_styles(self, styles):
302 print "new_styles(%s)" % `styles`
303
304 def send_paragraph(self, blankline):
305 print "send_paragraph(%s)" % `blankline`
306
307 def send_line_break(self):
308 print "send_line_break()"
309
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000310 def send_hor_rule(self, *args, **kw):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000311 print "send_hor_rule()"
312
313 def send_label_data(self, data):
314 print "send_label_data(%s)" % `data`
315
316 def send_flowing_data(self, data):
317 print "send_flowing_data(%s)" % `data`
318
319 def send_literal_data(self, data):
320 print "send_literal_data(%s)" % `data`
321
322
Guido van Rossum3672aa21996-05-29 00:02:30 +0000323class DumbWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000324
325 def __init__(self, file=None, maxcol=72):
326 self.file = file or sys.stdout
327 self.maxcol = maxcol
Guido van Rossum3672aa21996-05-29 00:02:30 +0000328 NullWriter.__init__(self)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000329 self.reset()
330
331 def reset(self):
332 self.col = 0
333 self.atbreak = 0
334
335 def send_paragraph(self, blankline):
336 self.file.write('\n' + '\n'*blankline)
337 self.col = 0
338 self.atbreak = 0
339
340 def send_line_break(self):
341 self.file.write('\n')
342 self.col = 0
343 self.atbreak = 0
344
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000345 def send_hor_rule(self, *args, **kw):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000346 self.file.write('\n')
347 self.file.write('-'*self.maxcol)
348 self.file.write('\n')
349 self.col = 0
350 self.atbreak = 0
351
352 def send_literal_data(self, data):
353 self.file.write(data)
354 i = string.rfind(data, '\n')
355 if i >= 0:
356 self.col = 0
357 data = data[i+1:]
358 data = string.expandtabs(data)
359 self.col = self.col + len(data)
360 self.atbreak = 0
361
362 def send_flowing_data(self, data):
363 if not data: return
364 atbreak = self.atbreak or data[0] in string.whitespace
365 col = self.col
366 maxcol = self.maxcol
367 write = self.file.write
368 for word in string.split(data):
369 if atbreak:
370 if col + len(word) >= maxcol:
371 write('\n')
372 col = 0
373 else:
374 write(' ')
375 col = col + 1
376 write(word)
377 col = col + len(word)
378 atbreak = 1
379 self.col = col
380 self.atbreak = data[-1] in string.whitespace
381
382
383def test(file = None):
384 w = DumbWriter()
385 f = AbstractFormatter(w)
386 if file:
387 fp = open(file)
388 elif sys.argv[1:]:
389 fp = open(sys.argv[1])
390 else:
391 fp = sys.stdin
392 while 1:
393 line = fp.readline()
394 if not line:
395 break
396 if line == '\n':
397 f.end_paragraph(1)
398 else:
399 f.add_flowing_data(line)
400 f.end_paragraph(0)
401
402
403if __name__ == '__main__':
404 test()